mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
Merge pull request #4 from zvx-echo6/feat/auto-mode-backend
feat(offroute): backend Auto mode probes [vehicle,atv,mtb,foot]
This commit is contained in:
commit
f72a05bcb7
2 changed files with 108 additions and 1 deletions
|
|
@ -68,6 +68,10 @@ MODE_TO_COSTING = {
|
|||
"vehicle": "auto",
|
||||
}
|
||||
|
||||
# Auto mode probes these concrete modes in capability order (most -> least
|
||||
# demanding terrain) and uses the first that yields a usable route.
|
||||
AUTO_MODE_PRIORITY = ["vehicle", "atv", "mtb", "foot"]
|
||||
|
||||
# Mode to valid entry point highway classes
|
||||
# foot = any trail/track/road, mtb = tracks and roads, vehicle = roads only
|
||||
MODE_TO_VALID_HIGHWAYS = {
|
||||
|
|
@ -511,7 +515,7 @@ class OffrouteRouter:
|
|||
start_lon: float,
|
||||
end_lat: float,
|
||||
end_lon: float,
|
||||
mode: Literal["foot", "mtb", "atv", "vehicle"] = "foot",
|
||||
mode: Literal["auto", "foot", "mtb", "atv", "vehicle"] = "foot",
|
||||
boundary_mode: Literal["strict", "pragmatic", "emergency"] = "pragmatic"
|
||||
) -> Dict:
|
||||
"""
|
||||
|
|
@ -531,6 +535,11 @@ class OffrouteRouter:
|
|||
|
||||
Returns a GeoJSON FeatureCollection with route segments.
|
||||
"""
|
||||
if mode == "auto":
|
||||
return self._route_auto(
|
||||
start_lat, start_lon, end_lat, end_lon, boundary_mode
|
||||
)
|
||||
|
||||
if mode not in MODE_TO_COSTING:
|
||||
return {"status": "error", "message": f"Unknown mode: {mode}"}
|
||||
|
||||
|
|
@ -563,6 +572,37 @@ class OffrouteRouter:
|
|||
start_lat, start_lon, end_lat, end_lon, mode, boundary_mode
|
||||
)
|
||||
|
||||
def _route_auto(
|
||||
self,
|
||||
start_lat: float, start_lon: float,
|
||||
end_lat: float, end_lon: float,
|
||||
boundary_mode: str
|
||||
) -> Dict:
|
||||
"""
|
||||
Auto mode: pick the best concrete travel mode by terrain feasibility.
|
||||
|
||||
Probes AUTO_MODE_PRIORITY (vehicle -> atv -> mtb -> foot) and returns the
|
||||
first mode whose network can serve the route. Each candidate's route()
|
||||
already reports status="error" when its network can't reach an endpoint,
|
||||
so the first status="ok" is the most road-capable feasible mode. The chosen
|
||||
mode is reported back as result["selected_mode"] for the UI.
|
||||
"""
|
||||
last_error = None
|
||||
for candidate in AUTO_MODE_PRIORITY:
|
||||
result = self.route(
|
||||
start_lat, start_lon, end_lat, end_lon,
|
||||
mode=candidate, boundary_mode=boundary_mode
|
||||
)
|
||||
if result.get("status") == "ok":
|
||||
result["selected_mode"] = candidate
|
||||
return result
|
||||
last_error = result
|
||||
|
||||
return last_error or {
|
||||
"status": "error",
|
||||
"message": "No route found in any mode"
|
||||
}
|
||||
|
||||
def _route_D_network_only(
|
||||
self,
|
||||
start_lat: float, start_lon: float,
|
||||
|
|
|
|||
|
|
@ -256,3 +256,70 @@ def test_admin_info_no_secrets_and_probes(client, monkeypatch):
|
|||
assert {'dem', 'osm_pbf', 'navi_db', 'barriers_tif', 'wilderness_tif',
|
||||
'trails_tif', 'friction_vrt'} == fs_names
|
||||
assert all(set(f) == {'name', 'path', 'exists', 'readable'} for f in d['filesystem'])
|
||||
|
||||
|
||||
# ── OffrouteRouter._route_auto — feasibility-based mode selection ─────────
|
||||
# Tested in isolation: a bare router (no __init__/readers) with OffrouteRouter.route
|
||||
# monkeypatched to a per-mode fixture. Exercises _route_auto directly, not the blueprint.
|
||||
|
||||
from services.navi_offroute.router import OffrouteRouter, AUTO_MODE_PRIORITY
|
||||
|
||||
|
||||
def _stub_route(per_mode, calls):
|
||||
def stub(self, start_lat, start_lon, end_lat, end_lon, mode="foot", boundary_mode="pragmatic"):
|
||||
calls.append(mode)
|
||||
return dict(per_mode[mode])
|
||||
return stub
|
||||
|
||||
|
||||
def test_route_auto_first_probe_ok_returns_vehicle(monkeypatch):
|
||||
calls = []
|
||||
per_mode = {m: {"status": "ok"} for m in AUTO_MODE_PRIORITY}
|
||||
monkeypatch.setattr(OffrouteRouter, "route", _stub_route(per_mode, calls))
|
||||
r = object.__new__(OffrouteRouter)
|
||||
out = r._route_auto(42.0, -114.0, 42.5, -114.5, "pragmatic")
|
||||
assert out["status"] == "ok"
|
||||
assert out["selected_mode"] == "vehicle"
|
||||
assert calls == ["vehicle"] # stops probing after first success
|
||||
|
||||
|
||||
def test_route_auto_falls_through_to_foot(monkeypatch):
|
||||
calls = []
|
||||
per_mode = {
|
||||
"vehicle": {"status": "error", "message": "No roads found"},
|
||||
"atv": {"status": "error", "message": "No tracks found"},
|
||||
"mtb": {"status": "error", "message": "No tracks found"},
|
||||
"foot": {"status": "ok"},
|
||||
}
|
||||
monkeypatch.setattr(OffrouteRouter, "route", _stub_route(per_mode, calls))
|
||||
r = object.__new__(OffrouteRouter)
|
||||
out = r._route_auto(42.0, -114.0, 42.5, -114.5, "pragmatic")
|
||||
assert out["status"] == "ok"
|
||||
assert out["selected_mode"] == "foot"
|
||||
assert calls == ["vehicle", "atv", "mtb", "foot"]
|
||||
|
||||
|
||||
def test_route_auto_all_error_returns_error(monkeypatch):
|
||||
calls = []
|
||||
per_mode = {m: {"status": "error", "message": f"{m} failed"} for m in AUTO_MODE_PRIORITY}
|
||||
monkeypatch.setattr(OffrouteRouter, "route", _stub_route(per_mode, calls))
|
||||
r = object.__new__(OffrouteRouter)
|
||||
out = r._route_auto(42.0, -114.0, 42.5, -114.5, "pragmatic")
|
||||
assert out["status"] == "error"
|
||||
assert "selected_mode" not in out
|
||||
assert calls == ["vehicle", "atv", "mtb", "foot"]
|
||||
|
||||
|
||||
def test_route_auto_selected_mode_present_in_ok_response(monkeypatch):
|
||||
calls = []
|
||||
per_mode = {
|
||||
"vehicle": {"status": "error", "message": "No roads found"},
|
||||
"atv": {"status": "ok", "route": {"type": "FeatureCollection", "features": []}},
|
||||
"mtb": {"status": "ok"},
|
||||
"foot": {"status": "ok"},
|
||||
}
|
||||
monkeypatch.setattr(OffrouteRouter, "route", _stub_route(per_mode, calls))
|
||||
r = object.__new__(OffrouteRouter)
|
||||
out = r._route_auto(42.0, -114.0, 42.5, -114.5, "pragmatic")
|
||||
assert out["status"] == "ok"
|
||||
assert "selected_mode" in out and out["selected_mode"] == "atv"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue