"""Reverse-Geocoding ueber Nominatim, Ergebnisse werden dauerhaft gecacht.""" import json import urllib.parse import urllib.request from datetime import datetime, timezone from . import config, db def _label(address: dict) -> str: city = ( address.get("city") or address.get("town") or address.get("village") or address.get("municipality") or address.get("county") ) detail = address.get("suburb") or address.get("neighbourhood") or address.get("road") parts = [p for p in (city, detail) if p] if not parts: return address.get("state") or address.get("country") or "" return ", ".join(parts[:2]) def reverse(lat: float | None, lon: float | None) -> str | None: if lat is None or lon is None or not config.REVERSE_GEOCODE: return None # Auf ~100 m runden: benachbarte Scans teilen sich denselben Cache-Eintrag. key = f"{lat:.3f},{lon:.3f}" cached = db.query_one("SELECT place FROM geocache WHERE key = ?", (key,)) if cached is not None: return cached["place"] url = config.NOMINATIM_URL + "?" + urllib.parse.urlencode( {"lat": lat, "lon": lon, "format": "jsonv2", "zoom": "16", "accept-language": "de"} ) place = None try: request = urllib.request.Request(url, headers={"User-Agent": config.USER_AGENT}) with urllib.request.urlopen(request, timeout=6) as response: data = json.loads(response.read().decode("utf-8")) place = _label(data.get("address") or {}) or data.get("display_name") except Exception: # Ohne Ortsnamen bleiben die Koordinaten - kein Grund, den Scan abzubrechen. return None db.execute( "INSERT OR REPLACE INTO geocache (key, place, fetched_at) VALUES (?, ?, ?)", (key, place, datetime.now(timezone.utc).isoformat()), ) return place