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