navi/backend/services/navi_offroute/tests/test_mvum_spatial.py
malice 5e83a6e63a
MVUM Layer 0: spatial index foundation (#22)
* MVUM Layer 0: spatial index foundation

Additive only — no routing logic, no response-format, no Valhalla changes.

- mvum.py: add MVUMSpatialIndex. Loads mvum_roads + mvum_trails from navi.db (read-only),
  decodes the pure-WKB shape blobs with shapely, builds a shapely.strtree.STRtree, and
  keeps a parallel list of full feature records (all columns minus the blob, plus the
  parsed geometry) with a by_id lookup. Exposes query_bbox(min_lat,min_lon,max_lat,max_lon)
  and query_buffered_line(coords, tolerance_m) returning candidate records (coarse bbox +
  buffer; full parallelism filter is a TODO for PR-B). Reports road_count, trail_count,
  bbox, build_time_seconds, memory_estimate_mb (RSS delta).
- app.py: build the index once per process (singleton) at service init; stored on
  app.config[MVUM_SPATIAL_INDEX]. Failure is logged and degrades to None.
- admin.py: GET /api/admin/mvum-spatial/info (Authentik-gated, read-only) returning the
  counts/bbox/build-time/memory stats.
- tests/test_mvum_spatial.py: index loads, query_bbox returns Boise-area features,
  query_buffered_line returns a feature, admin endpoint returns counts.

Diagnostic before coding (read-only): roads_with_shape=150568/null=68,
trails_with_shape=21995/null=6746 (green), shape blobs are pure WKB MultiLineString.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

---------

Co-authored-by: Matt <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 18:53:58 -06:00

49 lines
1.8 KiB
Python

"""MVUM spatial index foundation (Layer 0) tests.
Exercises MVUMSpatialIndex against the real navi.db on this host (read-only) and the
admin-info endpoint. Pure spatial lookup — no routing or response-format coverage.
"""
import pytest
from services.navi_offroute.mvum import MVUMSpatialIndex
@pytest.fixture(scope="module")
def index():
return MVUMSpatialIndex()
def test_index_loads_without_error(index):
assert index.road_count > 0
assert index.trail_count > 0
assert len(index.by_id) == index.road_count + index.trail_count
assert len(index.bbox) == 4
def test_query_bbox_returns_features(index):
# Generous Boise National Forest area bbox.
feats = index.query_bbox(43.8, -116.3, 44.4, -115.5)
assert len(feats) > 0
assert all("geometry" in f and "feature_id" in f for f in feats)
def test_query_buffered_line_returns_features(index):
# Take a point on an actual indexed feature and query a small buffer around it.
sample = next(iter(index.by_id.values()))
pt = sample["geometry"].representative_point()
feats = index.query_buffered_line([(pt.y, pt.x)], tolerance_m=50)
assert len(feats) >= 1
def test_admin_info_endpoint_returns_counts(index, monkeypatch):
import services.navi_offroute.app as app_mod
# Reuse the already-built fixture index instead of rebuilding in create_app.
monkeypatch.setattr(app_mod, "MVUMSpatialIndex", lambda *a, **k: index)
client = app_mod.create_app().test_client()
resp = client.get("/api/admin/mvum-spatial/info",
headers={"X-Authentik-Username": "matt"})
assert resp.status_code == 200
d = resp.get_json()
assert d["road_count"] > 0 and d["trail_count"] > 0
assert len(d["bbox"]) == 4
assert "build_time_seconds" in d and "memory_estimate_mb" in d