6231e3157e
Adds location.resolve() with ICAO CSV lookup and Nominatim geocoding, POST /find-location portal route with inline confirmation/error display, and full test coverage with mocked network calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
import csv
|
|
import importlib.resources
|
|
import logging
|
|
|
|
import requests
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
NOMINATIM_URL = "https://nominatim.openstreetmap.org/search"
|
|
_USER_AGENT = "planemapper/0.1 (https://github.com/football2801/planeMapper)"
|
|
|
|
|
|
def _lookup_icao(code: str) -> tuple[float, float, str] | None:
|
|
traversable = importlib.resources.files("planemapper.data").joinpath("airports.csv")
|
|
with traversable.open("r", encoding="utf-8") as fh:
|
|
reader = csv.DictReader(fh)
|
|
for row in reader:
|
|
if row["ident"] == code:
|
|
return float(row["latitude_deg"]), float(row["longitude_deg"]), row["name"]
|
|
return None
|
|
|
|
|
|
def _geocode(query: str) -> tuple[float, float, str] | None:
|
|
resp = requests.get(
|
|
NOMINATIM_URL,
|
|
params={"q": query, "format": "json", "limit": 1},
|
|
headers={"User-Agent": _USER_AGENT},
|
|
timeout=10,
|
|
)
|
|
resp.raise_for_status()
|
|
results = resp.json()
|
|
if not results:
|
|
return None
|
|
r = results[0]
|
|
return float(r["lat"]), float(r["lon"]), r["display_name"]
|
|
|
|
|
|
def resolve(query: str) -> tuple[float, float, str]:
|
|
query = query.strip().upper()
|
|
if len(query) == 4 and query.isalpha():
|
|
result = _lookup_icao(query)
|
|
if result is None:
|
|
raise ValueError("ICAO code not found — try an address instead")
|
|
log.info("ICAO %s resolved to %s", query, result[2])
|
|
return result
|
|
result = _geocode(query)
|
|
if result is None:
|
|
raise ValueError("Location not found — try a different search term")
|
|
log.info("Address '%s' resolved to %s", query, result[2])
|
|
return result
|