initial commit
This commit is contained in:
114
api/app/services/geocoding.py
Normal file
114
api/app/services/geocoding.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from typing import Any
|
||||
|
||||
from app.config import CACHE_TTL_GEOCODE, NOMINATIM_BASE_URL, NOMINATIM_USER_AGENT
|
||||
from app.http_client import get_client
|
||||
from app.database import get_cached_json, set_cached_json
|
||||
|
||||
|
||||
def _query_hash(query: str) -> str:
|
||||
return hashlib.sha256(query.strip().lower().encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
async def _nominatim_get(path: str, params: dict[str, Any]) -> Any:
|
||||
headers = {"User-Agent": NOMINATIM_USER_AGENT, "Accept-Language": "de"}
|
||||
client = get_client()
|
||||
response = await client.get(f"{NOMINATIM_BASE_URL}{path}", params=params, headers=headers, timeout=12.0)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
async def search_places(query: str, limit: int = 8) -> list[dict[str, Any]]:
|
||||
if len(query.strip()) < 2:
|
||||
return []
|
||||
|
||||
qhash = _query_hash(f"search:{query}:{limit}")
|
||||
cached = await get_cached_json("geocode_cache", {"query_hash": qhash}, CACHE_TTL_GEOCODE)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
raw = await _nominatim_get(
|
||||
"/search",
|
||||
{
|
||||
"q": query,
|
||||
"format": "jsonv2",
|
||||
"addressdetails": 1,
|
||||
"limit": limit,
|
||||
"countrycodes": "de,at,ch",
|
||||
},
|
||||
)
|
||||
results = []
|
||||
for item in raw:
|
||||
address = item.get("address") or {}
|
||||
label = item.get("display_name", "")
|
||||
place = (
|
||||
address.get("city")
|
||||
or address.get("town")
|
||||
or address.get("village")
|
||||
or address.get("municipality")
|
||||
or address.get("county")
|
||||
or label.split(",")[0]
|
||||
)
|
||||
results.append(
|
||||
{
|
||||
"label": label,
|
||||
"place": place,
|
||||
"lat": float(item["lat"]),
|
||||
"lon": float(item["lon"]),
|
||||
"type": item.get("type"),
|
||||
"postcode": address.get("postcode"),
|
||||
"county": address.get("county"),
|
||||
"state": address.get("state"),
|
||||
}
|
||||
)
|
||||
|
||||
await set_cached_json(
|
||||
"geocode_cache",
|
||||
{"query_hash": qhash, "query_text": query},
|
||||
results,
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
async def reverse_geocode(lat: float, lon: float) -> dict[str, Any]:
|
||||
qhash = _query_hash(f"reverse:{lat:.4f}:{lon:.4f}")
|
||||
cached = await get_cached_json("geocode_cache", {"query_hash": qhash}, CACHE_TTL_GEOCODE)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
raw = await _nominatim_get(
|
||||
"/reverse",
|
||||
{"lat": lat, "lon": lon, "format": "jsonv2", "addressdetails": 1, "zoom": 10},
|
||||
)
|
||||
address = raw.get("address") or {}
|
||||
payload = {
|
||||
"label": raw.get("display_name"),
|
||||
"place": address.get("city")
|
||||
or address.get("town")
|
||||
or address.get("village")
|
||||
or address.get("municipality")
|
||||
or address.get("county"),
|
||||
"county": address.get("county"),
|
||||
"state": address.get("state"),
|
||||
"postcode": address.get("postcode"),
|
||||
"admin_names": [
|
||||
value
|
||||
for value in [
|
||||
address.get("city"),
|
||||
address.get("town"),
|
||||
address.get("village"),
|
||||
address.get("municipality"),
|
||||
address.get("county"),
|
||||
address.get("state"),
|
||||
]
|
||||
if value
|
||||
],
|
||||
}
|
||||
await set_cached_json(
|
||||
"geocode_cache",
|
||||
{"query_hash": qhash, "query_text": f"{lat},{lon}"},
|
||||
payload,
|
||||
)
|
||||
return payload
|
||||
Reference in New Issue
Block a user