diff --git a/BUGS.md b/BUGS.md index 7d598ae..eef9ec0 100644 --- a/BUGS.md +++ b/BUGS.md @@ -9,16 +9,25 @@ When CLOSED, move the entire entry to Resolved — don't duplicate. ## Open bugs ### BUG-13 — Server Web UI shows raw coordinates instead of geocoded location on trips -- **Status**: `OPEN` +- **Status**: `IN PROGRESS` - **Reported**: 2026-08-12 - **Affected trips**: 18517, 18516 (and possibly others) - **Symptom**: The trip detail/replay page shows raw (x, y) coordinates instead of reverse-geocoded location names (street/city/address). -- **Likely cause**: Geocoding pipeline is either not running, failing silently, or - the cache lookup isn't populating for these coordinates. Could be a regression - from the BUG-09 geocode preloading changes or a server-side cache issue. -- **Plan**: Rework the location/geocoding pipeline primarily on the server side. - If simple enough, also backport the fix to the Pi (local dash) for consistency. +- **Root cause**: Server `_geocode_cached()` returned `None` when both Nominatim and + reverse_geocoder failed to produce a label. The `/api/geocode` endpoint then returned + HTTP 204 with no body. The frontend JS treated 204 as success, called `r.json()` on + empty body → threw → showed raw-coordinate fallback. The Pi version always returned + a label string (with coordinate fallback) and never 204. +- **Fix**: + - Server `_geocode_cached()` now always returns a non-empty string — coordinate + fallback (`lat, lon`) when both geocoders fail, matching Pi behavior. + - Server `/api/geocode` always returns HTTP 200 with `{label: "..."}` (never 204). + - Server `/api/geocode/batch` always includes every coord key in the result dict. + - Server Nominatim call now uses `jsonv2` + `accept-language=en` (aligned with Pi). + - Server reverse_geocoder now uses `mode=1` (aligned with Pi). + - Pi `_geocode_cached()` now checks for empty reverse_geocoder label before + returning (same guard the server has), falling through to coordinate string. --- diff --git a/app.py b/app.py index 183305b..52b9a7a 100644 --- a/app.py +++ b/app.py @@ -167,7 +167,8 @@ import urllib.request @functools.lru_cache(maxsize=512) def _geocode_cached(lat4, lon4): - """Try Nominatim first (street-level), fall back to reverse_geocoder (city).""" + """Try Nominatim first (street-level), fall back to reverse_geocoder (city), + then raw coordinates. Always returns a non-empty label string.""" lat = lat4 / 10000.0 lon = lon4 / 10000.0 try: @@ -188,7 +189,10 @@ def _geocode_cached(lat4, lon4): 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) + label = ", ".join(p for p in [r.get("name", ""), r.get("admin1", "")] if p) + if label: + return label + # Last resort — raw coordinates so the frontend never gets an empty label return f"{lat:.5f}, {lon:.5f}" @app.route('/api/geocode') diff --git a/server/app.py b/server/app.py index 08ce58f..332d7dc 100644 --- a/server/app.py +++ b/server/app.py @@ -29,29 +29,37 @@ except ImportError: @functools.lru_cache(maxsize=512) def _geocode_cached(lat4, lon4): + """Try Nominatim first (street-level), fall back to reverse_geocoder (city), + then raw coordinates. Always returns a non-empty label string — never None.""" lat = lat4 / 10000.0 lon = lon4 / 10000.0 try: import urllib.request, urllib.error, json as _json url = ( f"https://nominatim.openstreetmap.org/reverse" - f"?lat={lat}&lon={lon}&format=json&addressdetails=1" + f"?lat={lat}&lon={lon}&format=jsonv2&accept-language=en" ) req = urllib.request.Request(url, headers={"User-Agent": "yaris-obd-server/1.0"}) with urllib.request.urlopen(req, timeout=4) as resp: data = _json.loads(resp.read()) - addr = data.get("address", {}) - road = addr.get("road") or addr.get("pedestrian") or addr.get("cycleway", "") - city = addr.get("city") or addr.get("town") or addr.get("village", "") - return f"{road}, {city}".strip(", ") if road or city else None + 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 if _RG_AVAILABLE: - results = _rg.search([(lat, lon)], verbose=False) + results = _rg.search([(lat, lon)], mode=1, verbose=False) if results: r = results[0] - return f"{r.get('name', '')}, {r.get('admin1', '')}".strip(", ") - return None + label = ", ".join(p for p in [r.get("name", ""), r.get("admin1", "")] if p) + if label: + return label + # Last resort — raw coordinates so the frontend never gets an empty label + return f"{lat:.5f}, {lon:.5f}" app = Flask(__name__) @@ -611,10 +619,8 @@ def geocode(): return jsonify(error="lat and lon required"), 400 lat4 = int(round(lat, 4) * 10000) lon4 = int(round(lon, 4) * 10000) - name = _geocode_cached(lat4, lon4) - if name: - return jsonify(label=name) - return jsonify(name=None), 204 + label = _geocode_cached(lat4, lon4) + return jsonify(label=label) @app.post("/api/geocode/batch") @@ -636,8 +642,7 @@ def geocode_batch(): lon4 = int(round(lon, 4) * 10000) key = f"{lat:.3f},{lon:.3f}" name = _geocode_cached(lat4, lon4) - if name: - result[key] = name + result[key] = name return jsonify(result)