Speed up Wereldexport map loading dramatically.
Limit world overview payload, optimize SQL joins, cache bundle and tiles, reuse map instance, and show loading state.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
@@ -15,6 +16,14 @@ BASE = Path(__file__).resolve().parent.parent.parent
|
||||
templates = Jinja2Templates(directory=str(BASE / "templates"))
|
||||
TOOLS = os.getenv("TOOLS_API_URL", "http://tools-api:8700").rstrip("/")
|
||||
|
||||
_TILE_CACHE: dict[str, tuple[float, bytes]] = {}
|
||||
_TILE_CACHE_TTL = 86400.0
|
||||
_TILE_CACHE_MAX = 900
|
||||
|
||||
_BUNDLE_CACHE: dict[str, tuple[float, bytes]] = {}
|
||||
_BUNDLE_CACHE_TTL = 120.0
|
||||
|
||||
|
||||
|
||||
async def _proxy(method: str, path: str, **kwargs) -> Any:
|
||||
url = f"{TOOLS}/export-intel{path}"
|
||||
@@ -161,7 +170,12 @@ async def api_gov_sources(country: Optional[str] = None):
|
||||
|
||||
@router.get("/api/map/tiles/{z}/{x}/{y}.png")
|
||||
async def map_tile_proxy(z: int, x: int, y: int) -> Response:
|
||||
"""Proxy Carto dark basemap tiles via cockpit (no client internet needed)."""
|
||||
"""Proxy Carto dark basemap tiles via cockpit (cached, no client internet needed)."""
|
||||
key = f"{z}/{x}/{y}"
|
||||
hit = _TILE_CACHE.get(key)
|
||||
if hit and time.time() - hit[0] < _TILE_CACHE_TTL:
|
||||
return Response(content=hit[1], media_type="image/png", headers={"Cache-Control": "public, max-age=604800"})
|
||||
|
||||
host = ("a", "b", "c", "d")[(x + y) % 4]
|
||||
url = f"https://{host}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"
|
||||
try:
|
||||
@@ -169,11 +183,12 @@ async def map_tile_proxy(z: int, x: int, y: int) -> Response:
|
||||
r = await client.get(url)
|
||||
if r.status_code != 200:
|
||||
return Response(status_code=502, content=b"")
|
||||
return Response(
|
||||
content=r.content,
|
||||
media_type="image/png",
|
||||
headers={"Cache-Control": "public, max-age=604800"},
|
||||
)
|
||||
body = r.content
|
||||
if len(_TILE_CACHE) >= _TILE_CACHE_MAX:
|
||||
oldest = min(_TILE_CACHE.items(), key=lambda item: item[1][0])[0]
|
||||
_TILE_CACHE.pop(oldest, None)
|
||||
_TILE_CACHE[key] = (time.time(), body)
|
||||
return Response(content=body, media_type="image/png", headers={"Cache-Control": "public, max-age=604800"})
|
||||
except Exception:
|
||||
return Response(status_code=502, content=b"")
|
||||
|
||||
@@ -193,7 +208,25 @@ async def api_map_bundle(
|
||||
"entity_types": entity_types, "q": q, "halal_min": halal_min,
|
||||
"favorite_only": favorite_only, "crm_linked": crm_linked,
|
||||
}.items() if v is not None and v is not False}
|
||||
return await _proxy("GET", "/map/bundle", params=params)
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
cache_key = hashlib.md5(json.dumps(params, sort_keys=True, default=str).encode()).hexdigest()
|
||||
hit = _BUNDLE_CACHE.get(cache_key)
|
||||
if hit and time.time() - hit[0] < _BUNDLE_CACHE_TTL:
|
||||
return Response(content=hit[1], media_type="application/json")
|
||||
|
||||
url = f"{TOOLS}/export-intel/map/bundle"
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
r = await client.get(url, params=params)
|
||||
if r.status_code >= 400:
|
||||
return {"error": r.text, "status": r.status_code}
|
||||
body = r.content
|
||||
if len(_BUNDLE_CACHE) > 32:
|
||||
oldest = min(_BUNDLE_CACHE.items(), key=lambda item: item[1][0])[0]
|
||||
_BUNDLE_CACHE.pop(oldest, None)
|
||||
_BUNDLE_CACHE[cache_key] = (time.time(), body)
|
||||
return Response(content=body, media_type="application/json")
|
||||
|
||||
|
||||
@router.post("/api/export-intel/entities/favorites")
|
||||
|
||||
Reference in New Issue
Block a user