diff --git a/app.py b/app.py index 9644f64..f6c0868 100644 --- a/app.py +++ b/app.py @@ -193,6 +193,26 @@ def geocode_route(): label = _geocode_cached(int(round(lat, 4) * 10000), int(round(lon, 4) * 10000)) 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') def dash_devices(): devices = app_dash.list_usb_storage_partitions() diff --git a/server/app.py b/server/app.py index 91cc8ac..d7fa7ae 100644 --- a/server/app.py +++ b/server/app.py @@ -566,6 +566,30 @@ def geocode(): 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) ────────────────────── @app.post("/api/v1/portal/recompute-trips") diff --git a/static/js/history.js b/static/js/history.js index 100778e..e319ce2 100644 --- a/static/js/history.js +++ b/static/js/history.js @@ -40,7 +40,6 @@ } function preloadGeocodes(points) { - // Collect unique rounded coordinates and batch-fetch all labels upfront. _geoCache = {}; const seen = new Set(); const queue = []; @@ -50,25 +49,27 @@ seen.add(key); queue.push({ key, lat: p.latitude, lon: p.longitude }); } + if (!queue.length) return; - // Fetch in parallel with a small concurrency cap - const CONCURRENCY = 4; - let idx = 0; - function next() { - if (idx >= queue.length) return; - const item = queue[idx++]; - const ctrl = new AbortController(); - const t = setTimeout(() => ctrl.abort(), 4000); - return fetch(`/api/geocode?lat=${item.lat}&lon=${item.lon}`, { signal: ctrl.signal }) - .then(r => { clearTimeout(t); return r.ok ? r.json() : null; }) - .then(data => { - _geoCache[item.key] = (data && data.label) || null; - }) - .catch(() => { clearTimeout(t); _geoCache[item.key] = null; }) - .finally(() => next()); - } - // Start initial batch - for (let i = 0; i < CONCURRENCY && i < queue.length; i++) next(); + // Try batch endpoint first (server has it, Pi falls back to 404) + const coords = queue.map(q => ({lat: q.lat, lon: q.lon})); + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), 15000); + 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(data => { + if (data) { + for (const q of queue) { + if (data[q.key]) _geoCache[q.key] = data[q.key]; + } + } + }) + .catch(() => { clearTimeout(t); }); } function geocodePoint(lat, lon) {