mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
navi-offroute: vectorize road_terminus_transitions (O2b, perf)
Replace the per-road-cell Python 8-neighbour scan with a single 3x3 binary dilation of the off-network mask (scipy.ndimage), AND'd with the road/track mask. `border_value=0` treats out-of-bounds neighbours as on-network, matching the scalar version's OOB skip -- NOT np.roll, which would wrap the raster edges and fabricate phantom neighbours. A road cell is never off-network itself, so dilating with the centre included is equivalent to the loop's strict-neighbour test; the surviving cell set and the 2 directed foot<->vehicle tuples per cell are identical (only emission order differs; cap/kernel are order-independent). Synthetic eyeball benchmark (1234x470, dense road block, worst case for the loop's early-break): scalar 843 ms -> vector 9.3 ms; set-equal True. Targets the ~3.75s road_terminus stage of Route B's gather_transition_cells. Adds test_road_terminus_dilation_no_wrap (np.roll regression guard); existing test_road_terminus_transitions_pure_raster + gather/cost parity tests unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
69d2920bfd
commit
1faf8bd322
2 changed files with 32 additions and 22 deletions
|
|
@ -960,6 +960,19 @@ def test_road_terminus_transitions_pure_raster():
|
||||||
assert sorted(edges) == sorted([(f, v, 60.0), (v, f, 60.0)])
|
assert sorted(edges) == sorted([(f, v, 60.0), (v, f, 60.0)])
|
||||||
|
|
||||||
|
|
||||||
|
def test_road_terminus_dilation_no_wrap():
|
||||||
|
"""O2b safety net: the 3×3 dilation must NOT wrap the raster edges. The only road cell sits
|
||||||
|
at corner (0,0) with all real in-bounds neighbours on-network; the only off-network cell is
|
||||||
|
the opposite corner (4,4). With border_value=0 the corner road cell has no off-network
|
||||||
|
neighbour -> no termini. np.roll would wrap (4,4) into (0,0)'s neighbourhood and falsely fire."""
|
||||||
|
rows, cols = 5, 5
|
||||||
|
trail_grid = _p3np.full((rows, cols), 10, _p3np.uint8) # all on-network (trail), non-road
|
||||||
|
trail_grid[0, 0] = 5 # the only road cell, at the corner
|
||||||
|
trail_grid[4, 4] = 0 # the only off-network cell, opposite corner
|
||||||
|
meta = _p3_meta(rows, cols)
|
||||||
|
assert _trans.road_terminus_transitions(meta, trail_grid) == []
|
||||||
|
|
||||||
|
|
||||||
def test_transition_cap_closest_15(monkeypatch):
|
def test_transition_cap_closest_15(monkeypatch):
|
||||||
""">15 parking lots within 5 km -> only the closest 15 (by perp distance) survive."""
|
""">15 parking lots within 5 km -> only the closest 15 (by perp distance) survive."""
|
||||||
line = ((40.0, -111.0), (40.0, -110.0)) # ~east-west; lat offset = perp distance, all <5 km
|
line = ((40.0, -111.0), (40.0, -110.0)) # ~east-west; lat offset = perp distance, all <5 km
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ DEMReader.latlon_to_pixel (shared/dem.py).
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import scipy.ndimage as ndi
|
||||||
|
|
||||||
from .cost import (
|
from .cost import (
|
||||||
TRANSITION_COST_PARKING_S,
|
TRANSITION_COST_PARKING_S,
|
||||||
|
|
@ -143,32 +144,28 @@ def trailhead_transitions_near_line(line, buffer_m=5000):
|
||||||
def road_terminus_transitions(meta, trail_grid, elevation=None):
|
def road_terminus_transitions(meta, trail_grid, elevation=None):
|
||||||
"""Road-terminus mode switches from the trail raster (spec §4): a road(5)/track(15) cell
|
"""Road-terminus mode switches from the trail raster (spec §4): a road(5)/track(15) cell
|
||||||
with a passable off-network 8-neighbour (value 0; finite elev when `elevation` given).
|
with a passable off-network 8-neighbour (value 0; finite elev when `elevation` given).
|
||||||
foot↔vehicle at TRANSITION_COST_ROAD_TERMINUS_S. Pure raster scan, no DB — fixes §1."""
|
foot↔vehicle at TRANSITION_COST_ROAD_TERMINUS_S. Pure raster scan, no DB — fixes §1.
|
||||||
rows, cols = trail_grid.shape
|
|
||||||
|
O2b: the per-road-cell 8-neighbour Python loop is replaced by one 3×3 binary dilation of
|
||||||
|
the off-network mask (scipy.ndimage). `border_value=0` treats out-of-bounds neighbours as
|
||||||
|
on-network, matching the scalar version's OOB skip — NOT np.roll, which would wrap the
|
||||||
|
raster edges and fabricate phantom neighbours. A road cell is never off-network itself, so
|
||||||
|
dilating with the centre included is equivalent to the loop's strict-neighbour test. The
|
||||||
|
surviving cell set (and the 2 directed tuples per cell) is identical; only emission order
|
||||||
|
differs, and the cap / kernel consume the cells order-independently."""
|
||||||
pairs = (("foot", "vehicle"),)
|
pairs = (("foot", "vehicle"),)
|
||||||
road = (trail_grid == 5) | (trail_grid == 15)
|
road = (trail_grid == 5) | (trail_grid == 15)
|
||||||
rs, cs = np.nonzero(road)
|
offnet = (trail_grid == 0)
|
||||||
|
if elevation is not None:
|
||||||
|
offnet &= np.isfinite(elevation)
|
||||||
|
offnet_neighbour = ndi.binary_dilation(
|
||||||
|
offnet, structure=np.ones((3, 3), dtype=bool), border_value=0)
|
||||||
|
terminus = road & offnet_neighbour # road cell with ≥1 off-network 8-neighbour
|
||||||
|
rs, cs = np.nonzero(terminus)
|
||||||
out = []
|
out = []
|
||||||
for r, c in zip(rs.tolist(), cs.tolist()):
|
for r, c in zip(rs.tolist(), cs.tolist()):
|
||||||
is_terminus = False
|
lat, lon = _pixel_to_latlon(r, c, meta)
|
||||||
for dr in (-1, 0, 1):
|
out.extend(_bidir(lat, lon, pairs, TRANSITION_COST_ROAD_TERMINUS_S))
|
||||||
for dc in (-1, 0, 1):
|
|
||||||
if dr == 0 and dc == 0:
|
|
||||||
continue
|
|
||||||
nr, nc = r + dr, c + dc
|
|
||||||
if nr < 0 or nr >= rows or nc < 0 or nc >= cols:
|
|
||||||
continue
|
|
||||||
if trail_grid[nr, nc] != 0:
|
|
||||||
continue
|
|
||||||
if elevation is not None and not np.isfinite(elevation[nr, nc]):
|
|
||||||
continue
|
|
||||||
is_terminus = True
|
|
||||||
break
|
|
||||||
if is_terminus:
|
|
||||||
break
|
|
||||||
if is_terminus:
|
|
||||||
lat, lon = _pixel_to_latlon(r, c, meta)
|
|
||||||
out.extend(_bidir(lat, lon, pairs, TRANSITION_COST_ROAD_TERMINUS_S))
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue