perf: geocode pipeline — batch POST + parallel server, 200→1 roundtrip
JS preload(): replaced N individual GET /api/geocode (concurrency 2) with a single POST /api/geocode/batch — one HTTP roundtrip instead of up to N/2 sequential ones. schedLoc debounce reduced 600→200ms. Server geocode_batch(): ThreadPoolExecutor(max_workers=4) parallelises Nominatim/reverse_geocoder calls instead of sequential loop. Deduplicates coords before submitting futures. lru_cache absorbs duplicates within batch. Pi geocode_batch_route(): deduplicate coords, removed stale 'if label:' guard (label always non-empty after BUG-13 fix).
This commit is contained in:
+30
-5
@@ -2,6 +2,7 @@
|
||||
uploaded by the Pi, storing them in Postgres + on-disk file storage.
|
||||
Also serves the remote web portal for browsing and managing the configured car.
|
||||
"""
|
||||
import concurrent.futures
|
||||
import functools
|
||||
import hashlib
|
||||
import os
|
||||
@@ -626,12 +627,20 @@ def geocode():
|
||||
@app.post("/api/geocode/batch")
|
||||
@require_login
|
||||
def geocode_batch():
|
||||
"""Batch geocode — returns {key: label} for all unique coords in one request."""
|
||||
"""Batch geocode — returns {key: label} for all unique coords in one request.
|
||||
|
||||
Parallelised with ThreadPoolExecutor (max_workers=4) so Nominatim HTTP
|
||||
calls and reverse_geocoder lookups run concurrently instead of sequentially.
|
||||
_geocode_cached's lru_cache absorbs duplicate coords within the batch."""
|
||||
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 = {}
|
||||
|
||||
# Deduplicate — same (lat4,lon4) would hit lru_cache anyway, but this
|
||||
# avoids submitting redundant futures.
|
||||
seen = set()
|
||||
items = []
|
||||
for item in coords:
|
||||
try:
|
||||
lat = float(item["lat"])
|
||||
@@ -640,9 +649,25 @@ def geocode_batch():
|
||||
continue
|
||||
lat4 = int(round(lat, 4) * 10000)
|
||||
lon4 = int(round(lon, 4) * 10000)
|
||||
key = f"{lat:.3f},{lon:.3f}"
|
||||
name = _geocode_cached(lat4, lon4)
|
||||
result[key] = name
|
||||
if (lat4, lon4) not in seen:
|
||||
seen.add((lat4, lon4))
|
||||
items.append((f"{lat:.3f},{lon:.3f}", lat4, lon4))
|
||||
|
||||
result = {}
|
||||
if not items:
|
||||
return jsonify(result)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as ex:
|
||||
futures = {ex.submit(_geocode_cached, lat4, lon4): key for key, lat4, lon4 in items}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
key = futures[future]
|
||||
try:
|
||||
result[key] = future.result(timeout=8)
|
||||
except Exception:
|
||||
# Last resort — raw coords if even the fallback failed
|
||||
parts = key.split(",")
|
||||
result[key] = f"{float(parts[0]):.5f}, {float(parts[1]):.5f}"
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user