Fix map loading and tab navigation v0.4.1
This commit is contained in:
1 parent
e290bd1fff
commit
9b7d302064
9 files changed
+148
-47
No files matched your search
+63
-3
@@ -11,6 +11,7 @@ import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
@@ -21,12 +22,15 @@ from fastapi.staticfiles import StaticFiles
|
||||
|
||||
APP_NAME = os.getenv("APP_NAME", "Photo Date Editor")
|
||||
APP_URL = os.getenv("APP_URL", "http://localhost:8080")
|
||||
APP_VERSION = "0.4.0"
|
||||
APP_VERSION = "0.4.1"
|
||||
MAX_UPLOAD_MB = int(os.getenv("MAX_UPLOAD_MB", "150"))
|
||||
MAX_UPLOAD_BYTES = MAX_UPLOAD_MB * 1024 * 1024
|
||||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
|
||||
MAP_TILE_URL = os.getenv("MAP_TILE_URL", "https://tile.openstreetmap.org/{z}/{x}/{y}.png")
|
||||
MAP_TILE_SOURCE_URL = os.getenv(
|
||||
"MAP_TILE_SOURCE_URL",
|
||||
os.getenv("MAP_TILE_URL", "https://tile.openstreetmap.org/{z}/{x}/{y}.png"),
|
||||
)
|
||||
MAP_ATTRIBUTION = os.getenv(
|
||||
"MAP_ATTRIBUTION",
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>',
|
||||
@@ -43,6 +47,9 @@ GEOCODER_USER_AGENT = os.getenv(
|
||||
_geocode_lock = asyncio.Lock()
|
||||
_geocode_cache: dict[str, list[dict[str, Any]]] = {}
|
||||
_last_geocode_request = 0.0
|
||||
_tile_cache: OrderedDict[str, tuple[bytes, str]] = OrderedDict()
|
||||
_tile_cache_limit = 512
|
||||
_tile_lock = asyncio.Lock()
|
||||
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, LOG_LEVEL, logging.INFO),
|
||||
@@ -68,7 +75,7 @@ def config() -> dict[str, str | int | float]:
|
||||
"appUrl": APP_URL,
|
||||
"version": APP_VERSION,
|
||||
"maxUploadMb": MAX_UPLOAD_MB,
|
||||
"mapTileUrl": MAP_TILE_URL,
|
||||
"mapTileUrl": "/api/map/tiles/{z}/{x}/{y}.png",
|
||||
"mapAttribution": MAP_ATTRIBUTION,
|
||||
"defaultMapLat": DEFAULT_MAP_LAT,
|
||||
"defaultMapLon": DEFAULT_MAP_LON,
|
||||
@@ -77,6 +84,59 @@ def config() -> dict[str, str | int | float]:
|
||||
|
||||
|
||||
|
||||
def _fetch_map_tile(z: int, x: int, y: int) -> tuple[bytes, str]:
|
||||
tile_url = (
|
||||
MAP_TILE_SOURCE_URL
|
||||
.replace("{z}", str(z))
|
||||
.replace("{x}", str(x))
|
||||
.replace("{y}", str(y))
|
||||
)
|
||||
request = urllib.request.Request(
|
||||
tile_url,
|
||||
headers={
|
||||
"User-Agent": GEOCODER_USER_AGENT,
|
||||
"Accept": "image/avif,image/webp,image/png,image/*,*/*;q=0.8",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=20) as response:
|
||||
content_type = response.headers.get_content_type() or "image/png"
|
||||
return response.read(), content_type
|
||||
|
||||
|
||||
@app.get("/api/map/tiles/{z}/{x}/{y}.png", include_in_schema=False)
|
||||
async def map_tile(z: int, x: int, y: int) -> Response:
|
||||
if not 0 <= z <= 19:
|
||||
raise HTTPException(status_code=404, detail="Unsupported map zoom level.")
|
||||
limit = 1 << z
|
||||
if not 0 <= x < limit or not 0 <= y < limit:
|
||||
raise HTTPException(status_code=404, detail="Invalid map tile coordinates.")
|
||||
|
||||
cache_key = f"{z}/{x}/{y}"
|
||||
cached = _tile_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
_tile_cache.move_to_end(cache_key)
|
||||
payload, content_type = cached
|
||||
return Response(payload, media_type=content_type, headers={"Cache-Control": "public, max-age=86400"})
|
||||
|
||||
async with _tile_lock:
|
||||
cached = _tile_cache.get(cache_key)
|
||||
if cached is None:
|
||||
try:
|
||||
cached = await asyncio.to_thread(_fetch_map_tile, z, x, y)
|
||||
except (urllib.error.URLError, TimeoutError) as exc:
|
||||
logger.warning("Map tile request failed for %s: %s", cache_key, exc)
|
||||
raise HTTPException(status_code=502, detail="The map tile service could not be reached.") from exc
|
||||
_tile_cache[cache_key] = cached
|
||||
if len(_tile_cache) > _tile_cache_limit:
|
||||
_tile_cache.popitem(last=False)
|
||||
else:
|
||||
_tile_cache.move_to_end(cache_key)
|
||||
|
||||
payload, content_type = cached
|
||||
return Response(payload, media_type=content_type, headers={"Cache-Control": "public, max-age=86400"})
|
||||
|
||||
|
||||
|
||||
def _fetch_geocode_results(query: str) -> list[dict[str, Any]]:
|
||||
parameters = urllib.parse.urlencode(
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user