Implement story 1.4: location resolution ICAO and address
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>
This commit is contained in:
@@ -1 +1,50 @@
|
||||
# stub
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user