fix(phase26): batch geocode endpoint for faster server trip scrubber
This commit is contained in:
@@ -193,6 +193,26 @@ def geocode_route():
|
|||||||
label = _geocode_cached(int(round(lat, 4) * 10000), int(round(lon, 4) * 10000))
|
label = _geocode_cached(int(round(lat, 4) * 10000), int(round(lon, 4) * 10000))
|
||||||
return jsonify(label=label)
|
return jsonify(label=label)
|
||||||
|
|
||||||
|
@app.route('/api/geocode/batch', methods=['POST'])
|
||||||
|
def geocode_batch_route():
|
||||||
|
body = request.get_json(silent=True) or {}
|
||||||
|
coords = body.get("coords")
|
||||||
|
if not isinstance(coords, list) or not coords:
|
||||||
|
return jsonify(error="expected {coords: [{lat, lon}, ...]}"), 400
|
||||||
|
result = {}
|
||||||
|
for item in coords:
|
||||||
|
try:
|
||||||
|
lat = float(item["lat"])
|
||||||
|
lon = float(item["lon"])
|
||||||
|
except (KeyError, ValueError, TypeError):
|
||||||
|
continue
|
||||||
|
lat4 = int(round(lat, 4) * 10000)
|
||||||
|
lon4 = int(round(lon, 4) * 10000)
|
||||||
|
label = _geocode_cached(lat4, lon4)
|
||||||
|
if label:
|
||||||
|
result[f"{lat:.3f},{lon:.3f}"] = label
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
@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()
|
||||||
|
|||||||
@@ -566,6 +566,30 @@ def geocode():
|
|||||||
return jsonify(name=None), 204
|
return jsonify(name=None), 204
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/geocode/batch")
|
||||||
|
@require_login
|
||||||
|
def geocode_batch():
|
||||||
|
"""Batch geocode — returns {key: label} for all unique coords in one request."""
|
||||||
|
body = request.get_json(silent=True) or {}
|
||||||
|
coords = body.get("coords")
|
||||||
|
if not isinstance(coords, list) or not coords:
|
||||||
|
return jsonify(error="expected {coords: [{lat, lon}, ...]}"), 400
|
||||||
|
result = {}
|
||||||
|
for item in coords:
|
||||||
|
try:
|
||||||
|
lat = float(item["lat"])
|
||||||
|
lon = float(item["lon"])
|
||||||
|
except (KeyError, ValueError, TypeError):
|
||||||
|
continue
|
||||||
|
lat4 = int(round(lat, 4) * 10000)
|
||||||
|
lon4 = int(round(lon, 4) * 10000)
|
||||||
|
key = f"{lat:.3f},{lon:.3f}"
|
||||||
|
name = _geocode_cached(lat4, lon4)
|
||||||
|
if name:
|
||||||
|
result[key] = name
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
# ── Uplink trigger (recompute trips after new batch) ──────────────────────
|
# ── Uplink trigger (recompute trips after new batch) ──────────────────────
|
||||||
|
|
||||||
@app.post("/api/v1/portal/recompute-trips")
|
@app.post("/api/v1/portal/recompute-trips")
|
||||||
|
|||||||
+16
-15
@@ -40,7 +40,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function preloadGeocodes(points) {
|
function preloadGeocodes(points) {
|
||||||
// Collect unique rounded coordinates and batch-fetch all labels upfront.
|
|
||||||
_geoCache = {};
|
_geoCache = {};
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const queue = [];
|
const queue = [];
|
||||||
@@ -50,25 +49,27 @@
|
|||||||
seen.add(key);
|
seen.add(key);
|
||||||
queue.push({ key, lat: p.latitude, lon: p.longitude });
|
queue.push({ key, lat: p.latitude, lon: p.longitude });
|
||||||
}
|
}
|
||||||
|
if (!queue.length) return;
|
||||||
|
|
||||||
// Fetch in parallel with a small concurrency cap
|
// Try batch endpoint first (server has it, Pi falls back to 404)
|
||||||
const CONCURRENCY = 4;
|
const coords = queue.map(q => ({lat: q.lat, lon: q.lon}));
|
||||||
let idx = 0;
|
|
||||||
function next() {
|
|
||||||
if (idx >= queue.length) return;
|
|
||||||
const item = queue[idx++];
|
|
||||||
const ctrl = new AbortController();
|
const ctrl = new AbortController();
|
||||||
const t = setTimeout(() => ctrl.abort(), 4000);
|
const t = setTimeout(() => ctrl.abort(), 15000);
|
||||||
return fetch(`/api/geocode?lat=${item.lat}&lon=${item.lon}`, { signal: ctrl.signal })
|
fetch('/api/geocode/batch', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({coords: coords}),
|
||||||
|
signal: ctrl.signal,
|
||||||
|
})
|
||||||
.then(r => { clearTimeout(t); return r.ok ? r.json() : null; })
|
.then(r => { clearTimeout(t); return r.ok ? r.json() : null; })
|
||||||
.then(data => {
|
.then(data => {
|
||||||
_geoCache[item.key] = (data && data.label) || null;
|
if (data) {
|
||||||
})
|
for (const q of queue) {
|
||||||
.catch(() => { clearTimeout(t); _geoCache[item.key] = null; })
|
if (data[q.key]) _geoCache[q.key] = data[q.key];
|
||||||
.finally(() => next());
|
|
||||||
}
|
}
|
||||||
// Start initial batch
|
}
|
||||||
for (let i = 0; i < CONCURRENCY && i < queue.length; i++) next();
|
})
|
||||||
|
.catch(() => { clearTimeout(t); });
|
||||||
}
|
}
|
||||||
|
|
||||||
function geocodePoint(lat, lon) {
|
function geocodePoint(lat, lon) {
|
||||||
|
|||||||
Reference in New Issue
Block a user