feat(geocode): fully offline reverse geocode via local Pi API
- Remove all Nominatim/external calls from frontend - Add /api/geocode?lat=&lon= route using reverse_geocoder (20MB offline KD-tree) - JS hits /api/geocode only — works with no internet at all - Falls back to raw coords if Pi route fails - Add reverse_geocoder to requirements.txt Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -89,6 +89,23 @@ 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)
|
||||||
|
|
||||||
|
@app.route('/api/geocode')
|
||||||
|
def geocode_route():
|
||||||
|
try:
|
||||||
|
lat = float(request.args['lat'])
|
||||||
|
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}"
|
||||||
|
return jsonify(label=label)
|
||||||
|
|
||||||
@app.route('/api/dash/devices')
|
@app.route('/api/dash/devices')
|
||||||
def dash_devices():
|
def dash_devices():
|
||||||
devices = app_dash.list_usb_storage_partitions()
|
devices = app_dash.list_usb_storage_partitions()
|
||||||
|
|||||||
+1
-1
@@ -7,4 +7,4 @@ pyserial
|
|||||||
RPi.GPIO
|
RPi.GPIO
|
||||||
obd
|
obd
|
||||||
requests
|
requests
|
||||||
requests-toolbelt
|
requests-toolbeltreverse_geocoder
|
||||||
|
|||||||
+4
-14
@@ -32,20 +32,10 @@
|
|||||||
if (key === _lastGeoKey) return;
|
if (key === _lastGeoKey) return;
|
||||||
_lastGeoKey = key;
|
_lastGeoKey = key;
|
||||||
const fallback = `${lat.toFixed(5)}, ${lon.toFixed(5)}`;
|
const fallback = `${lat.toFixed(5)}, ${lon.toFixed(5)}`;
|
||||||
fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lon}`, {
|
fetch(`/api/geocode?lat=${lat}&lon=${lon}`, { signal: AbortSignal.timeout(4000) })
|
||||||
headers: { 'Accept-Language': 'en' },
|
.then(r => r.ok ? r.json() : null)
|
||||||
signal: AbortSignal.timeout(5000),
|
.then(data => { replayLocation.textContent = (data && data.label) ? data.label : fallback; })
|
||||||
})
|
.catch(() => { replayLocation.textContent = fallback; });
|
||||||
.then(r => r.ok ? r.json() : null)
|
|
||||||
.then(data => {
|
|
||||||
if (!data) { replayLocation.textContent = fallback; return; }
|
|
||||||
const a = data.address || {};
|
|
||||||
const road = a.road || a.hamlet || a.village || a.suburb || '';
|
|
||||||
const city = a.city || a.town || a.village || a.municipality || '';
|
|
||||||
replayLocation.textContent = [road, city].filter(Boolean).join(', ')
|
|
||||||
|| data.display_name?.split(',').slice(0, 2).join(',') || fallback;
|
|
||||||
})
|
|
||||||
.catch(() => { replayLocation.textContent = fallback; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleGeocode(lat, lon) {
|
function scheduleGeocode(lat, lon) {
|
||||||
|
|||||||
+5
-15
@@ -196,22 +196,12 @@ _recenterBtn.addEventListener('click', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function reverseGeocode(lat, lon) {
|
function reverseGeocode(lat, lon) {
|
||||||
const fallback = `${lat.toFixed(5)}, ${lon.toFixed(5)}`;
|
|
||||||
const el = document.getElementById('gps-location-name');
|
const el = document.getElementById('gps-location-name');
|
||||||
fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lon}`, {
|
const fallback = `${lat.toFixed(5)}, ${lon.toFixed(5)}`;
|
||||||
headers: { 'Accept-Language': 'en' },
|
fetch(`/api/geocode?lat=${lat}&lon=${lon}`, { signal: AbortSignal.timeout(4000) })
|
||||||
signal: AbortSignal.timeout(5000),
|
.then(r => r.ok ? r.json() : null)
|
||||||
})
|
.then(data => { el.textContent = (data && data.label) ? data.label : fallback; })
|
||||||
.then(r => r.ok ? r.json() : null)
|
.catch(() => { el.textContent = fallback; });
|
||||||
.then(data => {
|
|
||||||
if (!data) { el.textContent = fallback; return; }
|
|
||||||
const a = data.address || {};
|
|
||||||
const name = a.road || a.hamlet || a.village || a.suburb || '';
|
|
||||||
const city = a.city || a.town || a.village || a.municipality || '';
|
|
||||||
const label = [name, city].filter(Boolean).join(', ');
|
|
||||||
el.textContent = label || data.display_name?.split(',').slice(0, 2).join(',') || fallback;
|
|
||||||
})
|
|
||||||
.catch(() => { el.textContent = fallback; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('gps_location', function(data) {
|
socket.on('gps_location', function(data) {
|
||||||
|
|||||||
Reference in New Issue
Block a user