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(error=f"Trip not found: {trip_id}"), 404
|
||||||
return jsonify(trip)
|
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')
|
@app.route('/api/geocode')
|
||||||
def geocode_route():
|
def geocode_route():
|
||||||
try:
|
try:
|
||||||
@@ -96,14 +125,8 @@ def geocode_route():
|
|||||||
lon = float(request.args['lon'])
|
lon = float(request.args['lon'])
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
return jsonify(error="lat and lon required"), 400
|
return jsonify(error="lat and lon required"), 400
|
||||||
import reverse_geocoder as rg
|
# Round to 3 decimals (~111m precision) for cache key
|
||||||
results = rg.search([(lat, lon)], mode=1, verbose=False)
|
label = _geocode_cached(round(lat, 3) * 1000 // 1, round(lon, 3) * 1000 // 1)
|
||||||
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}"
|
|
||||||
return jsonify(label=label)
|
return jsonify(label=label)
|
||||||
|
|
||||||
@app.route('/api/dash/devices')
|
@app.route('/api/dash/devices')
|
||||||
|
|||||||
Reference in New Issue
Block a user