From adfc1105203b72a6c3e341f332fe9f1de0c2d93e Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 25 May 2026 23:22:01 +0000 Subject: [PATCH] Fix lat-aware buffer + count WKB parse failures - query_buffered_line: replace the static _DEG_PER_M with _buffer_degrees_for_meters(), which scales longitude degrees by cos(lat) and uses the larger lat/lon equivalent; buffer at the polyline avg latitude. Early-return [] for empty coords. - _load_table: count WKB parse failures and logger.warning once per table when > 0. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/services/navi_offroute/mvum.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/backend/services/navi_offroute/mvum.py b/backend/services/navi_offroute/mvum.py index aad310d..ae1c192 100755 --- a/backend/services/navi_offroute/mvum.py +++ b/backend/services/navi_offroute/mvum.py @@ -7,6 +7,7 @@ indicating which roads/trails are open or closed to specific vehicle modes. MVUM is motor-vehicle specific — foot mode should skip this layer entirely. """ import logging +import math import os import re import sqlite3 @@ -33,9 +34,14 @@ def navi_db_path() -> Path: logger = logging.getLogger("navi_offroute.mvum_spatial") -# Rough degrees-per-metre for small buffers (latitude scale; good enough for the -# coarse candidate filter at Layer 0). -_DEG_PER_M = 1.0 / 111320.0 +def _buffer_degrees_for_meters(meters: float, lat: float) -> float: + """Approximate buffer radius in degrees for a metre tolerance at a given latitude. + Longitude degrees shrink with cos(lat); use the larger of the lat/lon equivalents so + the bbox-coarse buffer stays conservative.""" + cos_lat = max(math.cos(math.radians(lat)), 0.01) + lat_deg = meters / 111320.0 + lon_deg = meters / (111320.0 * cos_lat) + return max(lat_deg, lon_deg) class MVUMSpatialIndex: @@ -74,6 +80,7 @@ class MVUMSpatialIndex: def _load_table(self, table, kind): count = 0 + parse_errors = 0 conn = sqlite3.connect(f"file:{self.db_path}?mode=ro", uri=True) conn.row_factory = sqlite3.Row try: @@ -83,6 +90,7 @@ class MVUMSpatialIndex: try: geom = wkb.loads(bytes(row["shape"])) except Exception: + parse_errors += 1 continue if geom.is_empty: continue @@ -101,6 +109,8 @@ class MVUMSpatialIndex: count += 1 finally: conn.close() + if parse_errors > 0: + logger.warning("%s: %d rows had unparseable WKB shape blobs", table, parse_errors) return count @property @@ -127,9 +137,12 @@ class MVUMSpatialIndex: MVUM features which merely cross the route are rejected, keeping only those that run alongside it. """ + if not coords: + return [] pts = [(lon, lat) for (lat, lon) in coords] geom = LineString(pts) if len(pts) >= 2 else Point(pts[0]) - return self._query_geom(geom.buffer(tolerance_m * _DEG_PER_M)) + avg_lat = sum(lat for (lat, lon) in coords) / len(coords) + return self._query_geom(geom.buffer(_buffer_degrees_for_meters(tolerance_m, avg_lat))) def parse_date_range(date_str: str) -> List[Tuple[int, int, int, int]]: