feat(geocode): street-level via Nominatim when online, city fallback offline
- Try Nominatim first (road + city), 4s timeout - Fall back to reverse_geocoder (city + region) if no internet - LRU cache (512 entries, ~111m precision) — repeated lookups are instant - No external calls from browser, all via /api/georecode on Pi Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -89,6 +89,35 @@ def trip_route(trip_id):
|
||||
return jsonify(error=f"Trip not found: {trip_id}"), 404
|
||||
return jsonify(trip)
|
||||
|
||||
import functools
|
||||
import urllib.request
|
||||
|
||||
@functools.lru_cache(maxsize=512)
|
||||
def _geocode_cached(lat3, lon3):
|
||||
"""Try Nominatim first (street-level), fall back to reverse_geocoder (city)."""
|
||||
lat = lat3 / 1000.0
|
||||
lon = lon3 / 1000.0
|
||||
try:
|
||||
url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}&accept-language=en"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "yaris-obd/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=4) as resp:
|
||||
data = json.loads(resp.read())
|
||||
a = data.get("address", {})
|
||||
road = a.get("road") or a.get("hamlet") or a.get("suburb") or ""
|
||||
city = a.get("city") or a.get("town") or a.get("village") or a.get("municipality") or ""
|
||||
label = ", ".join(p for p in [road, city] if p)
|
||||
if label:
|
||||
return label
|
||||
except Exception:
|
||||
pass
|
||||
# Offline fallback
|
||||
import reverse_geocoder as rg
|
||||
results = rg.search([(lat, lon)], mode=1, verbose=False)
|
||||
if results:
|
||||
r = results[0]
|
||||
return ", ".join(p for p in [r.get("name", ""), r.get("admin1", "")] if p)
|
||||
return f"{lat:.5f}, {lon:.5f}"
|
||||
|
||||
@app.route('/api/geocode')
|
||||
def geocode_route():
|
||||
try:
|
||||
@@ -96,14 +125,8 @@ def geocode_route():
|
||||
lon = float(request.args['lon'])
|
||||
except (KeyError, ValueError):
|
||||
return jsonify(error="lat and lon required"), 400
|
||||
import reverse_geocoder as rg
|
||||
results = rg.search([(lat, lon)], mode=1, verbose=False)
|
||||
if results:
|
||||
r = results[0]
|
||||
parts = [r.get('name', ''), r.get('admin1', '')]
|
||||
label = ', '.join(p for p in parts if p)
|
||||
else:
|
||||
label = f"{lat:.5f}, {lon:.5f}"
|
||||
# Round to 3 decimals (~111m precision) for cache key
|
||||
label = _geocode_cached(round(lat, 3) * 1000 // 1, round(lon, 3) * 1000 // 1)
|
||||
return jsonify(label=label)
|
||||
|
||||
@app.route('/api/dash/devices')
|
||||
|
||||
Reference in New Issue
Block a user