mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
Rename mode constants: mtb→2w, atv→4w (#20)
* Rename mode constants: mtb->2w, atv->4w Rename the OFFROUTE travel-mode identifiers mtb->2w and atv->4w across backend and frontend. MVUM vehicle-access classes/columns (atv, motorcycle) are a separate vocabulary and are left untouched. UI labels (MTB/ATV) and the cost.py __main__ demo local variables (cannot be digit-initial identifiers) are unchanged. Backend: MODE_PROFILES, MODE_TO_COSTING, MODE_TO_VALID_HIGHWAYS, AUTO_MODE_PRIORITY, _MODES_* sets, CATEGORY_ELIGIBLE_MODES, route()/compute_cost*/_pathfind_wilderness Literals, VALID_MODES, and tests. offroute_route.py adds a backward-compat shim mapping legacy mode=mtb->2w and mode=atv->4w before validation so bookmarked URLs still work. Frontend: store.js routeMode doc, DirectionsPanel TRAVEL_MODES ids + SELECTED_MODE_LABEL keys, Panel TRAVEL_MODES ids, ManeuverList network_mode->verb map keys, api.js jsdoc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix user-visible labels: MTB->2W, ATV->4W + grep cleanup Update the user-facing travel-mode labels to match the renamed ids: - DirectionsPanel TRAVEL_MODES (2w -> "2W", 4w -> "4W") + SELECTED_MODE_LABEL values. - Panel TRAVEL_MODES labels. - Stale test comment: mode=mtb -> mode=2w. Grep pass over frontend/src + backend/services/navi_offroute found no remaining mode-identifier string literals to rename. Residual hits are all out-of-scope: MVUM vehicle-access vocabulary (mvum.py, /api/mvum output, single-quoted test fixtures), the cost.py __main__ demo locals, the offroute_route.py back-compat shim, and comments referencing the historical names. 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>
This commit is contained in:
parent
f244625551
commit
ede6852657
9 changed files with 69 additions and 64 deletions
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Multi-mode travel cost functions for OFFROUTE.
|
||||
|
||||
Supports four travel modes: foot, mtb, atv, vehicle.
|
||||
Supports four travel modes: foot, 2w, 4w, vehicle.
|
||||
Each mode has its own speed function, max slope, trail access rules,
|
||||
and terrain friction overrides.
|
||||
|
||||
|
|
@ -120,8 +120,8 @@ MODE_PROFILES: Dict[str, ModeProfile] = {
|
|||
wilderness_impassable=False,
|
||||
),
|
||||
|
||||
"mtb": ModeProfile(
|
||||
name="mtb",
|
||||
"2w": ModeProfile(
|
||||
name="2w",
|
||||
description="Mountain bike / dirt bike (Herzog wheeled model)",
|
||||
speed_function="herzog",
|
||||
base_speed_kmh=12.0,
|
||||
|
|
@ -143,8 +143,8 @@ MODE_PROFILES: Dict[str, ModeProfile] = {
|
|||
wilderness_impassable=True,
|
||||
),
|
||||
|
||||
"atv": ModeProfile(
|
||||
name="atv",
|
||||
"4w": ModeProfile(
|
||||
name="4w",
|
||||
description="ATV / side-by-side (Herzog wheeled model, higher base speed)",
|
||||
speed_function="herzog",
|
||||
base_speed_kmh=25.0,
|
||||
|
|
@ -225,7 +225,7 @@ def compute_cost_multiplier_grid(
|
|||
friction: Optional[np.ndarray] = None,
|
||||
friction_raw: Optional[np.ndarray] = None,
|
||||
wilderness: Optional[np.ndarray] = None,
|
||||
mode: Literal["foot", "mtb", "atv", "vehicle"] = "foot",
|
||||
mode: Literal["foot", "2w", "4w", "vehicle"] = "foot",
|
||||
) -> np.ndarray:
|
||||
"""Per-cell SLOPE-FREE context cost multiplier for the anisotropic A* pathfinder.
|
||||
|
||||
|
|
@ -300,7 +300,7 @@ def compute_cost_grid(
|
|||
wilderness: Optional[np.ndarray] = None,
|
||||
mvum: Optional[np.ndarray] = None,
|
||||
boundary_mode: Literal["strict", "pragmatic", "emergency"] = "pragmatic",
|
||||
mode: Literal["foot", "mtb", "atv", "vehicle"] = "foot"
|
||||
mode: Literal["foot", "2w", "4w", "vehicle"] = "foot"
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Compute isotropic travel cost grid from elevation data.
|
||||
|
|
@ -327,7 +327,7 @@ def compute_cost_grid(
|
|||
MVUM closures respond to boundary_mode (strict/pragmatic/emergency).
|
||||
Foot mode should pass None (MVUM is motor-vehicle specific).
|
||||
boundary_mode: How to handle barriers ("strict", "pragmatic", "emergency")
|
||||
mode: Travel mode ("foot", "mtb", "atv", "vehicle")
|
||||
mode: Travel mode ("foot", "2w", "4w", "vehicle")
|
||||
|
||||
Returns:
|
||||
2D array of travel cost in seconds per cell.
|
||||
|
|
@ -550,7 +550,7 @@ if __name__ == "__main__":
|
|||
trails = np.zeros((10, 10), dtype=np.uint8)
|
||||
trails[5, :] = 5 # Road across middle
|
||||
|
||||
for mode_name in ["foot", "mtb", "atv", "vehicle"]:
|
||||
for mode_name in ["foot", "2w", "4w", "vehicle"]:
|
||||
cost = compute_cost_grid(
|
||||
elev, cell_size_m=30.0,
|
||||
friction=friction,
|
||||
|
|
@ -567,7 +567,7 @@ if __name__ == "__main__":
|
|||
wilderness = np.zeros((10, 10), dtype=np.uint8)
|
||||
wilderness[3:7, 3:7] = 255
|
||||
|
||||
for mode_name in ["foot", "mtb", "atv", "vehicle"]:
|
||||
for mode_name in ["foot", "2w", "4w", "vehicle"]:
|
||||
cost = compute_cost_grid(
|
||||
elev, cell_size_m=30.0,
|
||||
wilderness=wilderness,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ logger = logging.getLogger('navi_offroute.route')
|
|||
|
||||
bp = Blueprint('offroute', __name__)
|
||||
|
||||
VALID_MODES = ("auto", "foot", "mtb", "atv", "vehicle")
|
||||
VALID_MODES = ("auto", "foot", "2w", "4w", "vehicle")
|
||||
VALID_BOUNDARY_MODES = ("strict", "pragmatic", "emergency")
|
||||
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ def api_offroute():
|
|||
|
||||
Request body:
|
||||
{start:[lat,lon], end:[lat,lon],
|
||||
mode: auto|foot|mtb|atv|vehicle (default foot),
|
||||
mode: auto|foot|2w|4w|vehicle (default foot),
|
||||
boundary_mode: strict|pragmatic|emergency (default pragmatic)}
|
||||
|
||||
Response: {status:"ok", route:<GeoJSON FeatureCollection>, summary:{...}}
|
||||
|
|
@ -53,8 +53,13 @@ def api_offroute():
|
|||
end_lat, end_lon = float(end[0]), float(end[1])
|
||||
|
||||
mode = data.get("mode", "foot")
|
||||
# Backward-compat: map legacy mode names from bookmarked URLs.
|
||||
if mode == "mtb":
|
||||
mode = "2w"
|
||||
elif mode == "atv":
|
||||
mode = "4w"
|
||||
if mode not in VALID_MODES:
|
||||
return jsonify({"status": "error", "message": "mode must be auto, foot, mtb, atv, or vehicle"}), 400
|
||||
return jsonify({"status": "error", "message": "mode must be auto, foot, 2w, 4w, or vehicle"}), 400
|
||||
|
||||
boundary_mode = data.get("boundary_mode", "pragmatic")
|
||||
if boundary_mode not in VALID_BOUNDARY_MODES:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Off-network detection: Valhalla /locate snap distance > 500m = off-network.
|
|||
|
||||
IMPORTANT: The wilderness segment ALWAYS uses foot mode for pathfinding.
|
||||
The user's selected mode affects:
|
||||
1. Which entry points are valid (foot=any, mtb=tracks+roads, vehicle=roads only)
|
||||
1. Which entry points are valid (foot=any, 2w=tracks+roads, vehicle=roads only)
|
||||
2. The Valhalla costing profile for the network segment
|
||||
"""
|
||||
import gc
|
||||
|
|
@ -83,20 +83,20 @@ PATH_USE_VALUES = frozenset({
|
|||
MODE_TO_COSTING = {
|
||||
"auto": "auto",
|
||||
"foot": "pedestrian",
|
||||
"mtb": "bicycle",
|
||||
"atv": "auto",
|
||||
"2w": "bicycle",
|
||||
"4w": "auto",
|
||||
"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"]
|
||||
AUTO_MODE_PRIORITY = ["vehicle", "4w", "2w", "foot"]
|
||||
|
||||
# Per-endpoint travel-mode eligibility from an OSM-style "key:value" category hint.
|
||||
# Looked up exact first, then "key:*" wildcard (see _eligible_modes_from_category).
|
||||
_MODES_ALL = frozenset({"vehicle", "atv", "mtb", "foot"})
|
||||
_MODES_TRACK = frozenset({"atv", "mtb", "foot"})
|
||||
_MODES_PATH = frozenset({"mtb", "foot"})
|
||||
_MODES_ALL = frozenset({"vehicle", "4w", "2w", "foot"})
|
||||
_MODES_TRACK = frozenset({"4w", "2w", "foot"})
|
||||
_MODES_PATH = frozenset({"2w", "foot"})
|
||||
_MODES_FOOT = frozenset({"foot"})
|
||||
|
||||
CATEGORY_ELIGIBLE_MODES = {
|
||||
|
|
@ -115,9 +115,9 @@ CATEGORY_ELIGIBLE_MODES = {
|
|||
"place:village": _MODES_ALL, "place:hamlet": _MODES_ALL,
|
||||
"place:suburb": _MODES_ALL, "place:neighbourhood": _MODES_ALL,
|
||||
"railway:station": _MODES_ALL,
|
||||
# Track-like -> atv/mtb/foot
|
||||
# Track-like -> 4w/2w/foot
|
||||
"highway:track": _MODES_TRACK, "highway:trailhead": _MODES_TRACK,
|
||||
# Path-like -> mtb/foot
|
||||
# Path-like -> 2w/foot
|
||||
"highway:path": _MODES_PATH, "highway:bridleway": _MODES_PATH,
|
||||
# Foot-only
|
||||
"highway:footway": _MODES_FOOT, "highway:steps": _MODES_FOOT,
|
||||
|
|
@ -130,15 +130,15 @@ CATEGORY_ELIGIBLE_MODES = {
|
|||
}
|
||||
|
||||
# Mode to valid entry point highway classes
|
||||
# foot = any trail/track/road, mtb = tracks and roads, vehicle = roads only
|
||||
# foot = any trail/track/road, 2w = tracks and roads, vehicle = roads only
|
||||
MODE_TO_VALID_HIGHWAYS = {
|
||||
"auto": {"primary", "secondary", "tertiary", "unclassified", "residential",
|
||||
"service"},
|
||||
"foot": {"primary", "secondary", "tertiary", "unclassified", "residential",
|
||||
"service", "track", "path", "footway", "bridleway"},
|
||||
"mtb": {"primary", "secondary", "tertiary", "unclassified", "residential",
|
||||
"2w": {"primary", "secondary", "tertiary", "unclassified", "residential",
|
||||
"service", "track"},
|
||||
"atv": {"primary", "secondary", "tertiary", "unclassified", "residential",
|
||||
"4w": {"primary", "secondary", "tertiary", "unclassified", "residential",
|
||||
"service", "track"},
|
||||
"vehicle": {"primary", "secondary", "tertiary", "unclassified", "residential",
|
||||
"service"},
|
||||
|
|
@ -597,7 +597,7 @@ class OffrouteRouter:
|
|||
start_lon: float,
|
||||
end_lat: float,
|
||||
end_lon: float,
|
||||
mode: Literal["auto", "foot", "mtb", "atv", "vehicle"] = "foot",
|
||||
mode: Literal["auto", "foot", "2w", "4w", "vehicle"] = "foot",
|
||||
boundary_mode: Literal["strict", "pragmatic", "emergency"] = "pragmatic",
|
||||
start_category: Optional[str] = None,
|
||||
end_category: Optional[str] = None
|
||||
|
|
@ -614,7 +614,7 @@ class OffrouteRouter:
|
|||
Args:
|
||||
start_lat, start_lon: Starting coordinates
|
||||
end_lat, end_lon: Destination coordinates
|
||||
mode: Travel mode (foot, mtb, atv, vehicle)
|
||||
mode: Travel mode (foot, 2w, 4w, vehicle)
|
||||
boundary_mode: How to handle private land (strict, pragmatic, emergency)
|
||||
|
||||
Returns a GeoJSON FeatureCollection with route segments.
|
||||
|
|
@ -704,8 +704,8 @@ class OffrouteRouter:
|
|||
Runs the three distinct costings (auto/pedestrian/bicycle) in parallel and
|
||||
applies the per-mode snap-distance + road-class rules. snap_cache dedupes
|
||||
/locate results within a single request."""
|
||||
# auto costing -> vehicle/atv reach, bicycle -> mtb reach, pedestrian -> foot
|
||||
costing_modes = {"auto": "vehicle", "pedestrian": "foot", "bicycle": "mtb"}
|
||||
# auto costing -> vehicle/4w reach, bicycle -> 2w reach, pedestrian -> foot
|
||||
costing_modes = {"auto": "vehicle", "pedestrian": "foot", "bicycle": "2w"}
|
||||
need = [c for c in costing_modes if (lat, lon, c) not in snap_cache]
|
||||
if need:
|
||||
with ThreadPoolExecutor(max_workers=3) as ex:
|
||||
|
|
@ -727,15 +727,15 @@ class OffrouteRouter:
|
|||
(d_auto <= AUTO_SNAP_RELAXED_M and cls_auto in PAVED_HIGHWAY_CLASSES
|
||||
and self._is_terrain_flat(lat, lon)):
|
||||
modes.add("vehicle")
|
||||
# atv: near a paved road OR a track (auto costing)
|
||||
# 4w: near a paved road OR a track (auto costing)
|
||||
if d_auto <= AUTO_SNAP_RELAXED_M and (
|
||||
cls_auto in PAVED_HIGHWAY_CLASSES or use_auto in TRACK_USE_VALUES):
|
||||
modes.add("atv")
|
||||
# mtb: near a paved road OR a track/path (bicycle costing)
|
||||
modes.add("4w")
|
||||
# 2w: near a paved road OR a track/path (bicycle costing)
|
||||
if d_bike <= AUTO_SNAP_RELAXED_M and (
|
||||
cls_bike in PAVED_HIGHWAY_CLASSES
|
||||
or use_bike in (TRACK_USE_VALUES | PATH_USE_VALUES)):
|
||||
modes.add("mtb")
|
||||
modes.add("2w")
|
||||
return frozenset(modes)
|
||||
|
||||
def _route_auto(
|
||||
|
|
@ -945,7 +945,7 @@ class OffrouteRouter:
|
|||
if not entry_points:
|
||||
if mode == "vehicle":
|
||||
msg = f"No roads found within {EXPANDED_SEARCH_RADIUS_KM}km. Try a different mode."
|
||||
elif mode in ("mtb", "atv"):
|
||||
elif mode in ("2w", "4w"):
|
||||
msg = f"No tracks or roads found within {EXPANDED_SEARCH_RADIUS_KM}km. Try foot mode."
|
||||
else:
|
||||
msg = f"No trail entry points found within {EXPANDED_SEARCH_RADIUS_KM}km of start."
|
||||
|
|
@ -1025,7 +1025,7 @@ class OffrouteRouter:
|
|||
if not entry_points:
|
||||
if mode == "vehicle":
|
||||
msg = f"No roads found within {EXPANDED_SEARCH_RADIUS_KM}km of destination. Try a different mode."
|
||||
elif mode in ("mtb", "atv"):
|
||||
elif mode in ("2w", "4w"):
|
||||
msg = f"No tracks or roads found within {EXPANDED_SEARCH_RADIUS_KM}km of destination. Try foot mode."
|
||||
else:
|
||||
msg = f"No trail entry points found within {EXPANDED_SEARCH_RADIUS_KM}km of destination."
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ def test_admin_info_no_secrets_and_probes(client, monkeypatch):
|
|||
|
||||
from services.navi_offroute.router import OffrouteRouter, AUTO_MODE_PRIORITY
|
||||
|
||||
ALL_MODES = frozenset({"vehicle", "atv", "mtb", "foot"})
|
||||
ALL_MODES = frozenset({"vehicle", "4w", "2w", "foot"})
|
||||
|
||||
|
||||
def _stub_route(per_mode, calls):
|
||||
|
|
@ -284,8 +284,8 @@ def _all_ok():
|
|||
def test_eligible_modes_exact_match():
|
||||
r = object.__new__(OffrouteRouter)
|
||||
assert r._eligible_modes_from_category("highway:residential") == ALL_MODES
|
||||
assert r._eligible_modes_from_category("highway:track") == frozenset({"atv", "mtb", "foot"})
|
||||
assert r._eligible_modes_from_category("highway:path") == frozenset({"mtb", "foot"})
|
||||
assert r._eligible_modes_from_category("highway:track") == frozenset({"4w", "2w", "foot"})
|
||||
assert r._eligible_modes_from_category("highway:path") == frozenset({"2w", "foot"})
|
||||
assert r._eligible_modes_from_category("highway:footway") == frozenset({"foot"})
|
||||
|
||||
|
||||
|
|
@ -329,8 +329,8 @@ 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"},
|
||||
"4w": {"status": "error", "message": "No tracks found"},
|
||||
"2w": {"status": "error", "message": "No tracks found"},
|
||||
"foot": {"status": "ok"},
|
||||
}
|
||||
monkeypatch.setattr(OffrouteRouter, "route", _stub_route(per_mode, calls))
|
||||
|
|
@ -338,7 +338,7 @@ def test_route_auto_falls_through_to_foot(monkeypatch):
|
|||
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"]
|
||||
assert calls == ["vehicle", "4w", "2w", "foot"]
|
||||
|
||||
|
||||
def test_route_auto_all_error_returns_error(monkeypatch):
|
||||
|
|
@ -351,7 +351,7 @@ def test_route_auto_all_error_returns_error(monkeypatch):
|
|||
assert out["status"] == "error"
|
||||
assert "selected_mode" not in out
|
||||
assert out["selected_mode_set"] == sorted(ALL_MODES)
|
||||
assert calls == ["vehicle", "atv", "mtb", "foot"]
|
||||
assert calls == ["vehicle", "4w", "2w", "foot"]
|
||||
|
||||
|
||||
def test_route_auto_selected_mode_present_in_ok_response(monkeypatch):
|
||||
|
|
@ -359,15 +359,15 @@ 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"},
|
||||
"4w": {"status": "ok", "route": {"type": "FeatureCollection", "features": []}},
|
||||
"2w": {"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 out["selected_mode"] == "atv"
|
||||
assert out["selected_mode"] == "4w"
|
||||
|
||||
|
||||
# ── _route_auto with category type hints (real _eligible_modes_from_category) ──
|
||||
|
|
@ -388,9 +388,9 @@ def test_route_auto_address_to_trailhead_picks_atv(monkeypatch):
|
|||
r = object.__new__(OffrouteRouter)
|
||||
out = r._route_auto(42.0, -114.0, 42.5, -114.5, "pragmatic",
|
||||
start_category="building:house", end_category="highway:trailhead")
|
||||
assert out["selected_mode"] == "atv"
|
||||
assert out["selected_mode"] == "4w"
|
||||
assert "vehicle" not in calls
|
||||
assert out["selected_mode_set"] == sorted({"atv", "mtb", "foot"})
|
||||
assert out["selected_mode_set"] == sorted({"4w", "2w", "foot"})
|
||||
|
||||
|
||||
def test_route_auto_address_to_peak_picks_foot(monkeypatch):
|
||||
|
|
@ -436,7 +436,7 @@ def test_spatial_service_other_picks_vehicle(monkeypatch):
|
|||
r = object.__new__(OffrouteRouter)
|
||||
modes = r._spatial_eligible_modes(43.6, -116.2, {})
|
||||
assert "vehicle" in modes
|
||||
assert modes == frozenset({"vehicle", "atv", "mtb", "foot"})
|
||||
assert modes == frozenset({"vehicle", "4w", "2w", "foot"})
|
||||
|
||||
|
||||
def test_spatial_use_track_picks_atv_mtb_foot(monkeypatch):
|
||||
|
|
@ -445,7 +445,7 @@ def test_spatial_use_track_picks_atv_mtb_foot(monkeypatch):
|
|||
monkeypatch.setattr(OffrouteRouter, "_locate_on_network", _stub_locate_fixed(snap))
|
||||
r = object.__new__(OffrouteRouter)
|
||||
modes = r._spatial_eligible_modes(43.6, -116.2, {})
|
||||
assert modes == frozenset({"atv", "mtb", "foot"})
|
||||
assert modes == frozenset({"4w", "2w", "foot"})
|
||||
assert "vehicle" not in modes
|
||||
|
||||
|
||||
|
|
@ -455,7 +455,7 @@ def test_spatial_use_footway_picks_mtb_foot(monkeypatch):
|
|||
monkeypatch.setattr(OffrouteRouter, "_locate_on_network", _stub_locate_fixed(snap))
|
||||
r = object.__new__(OffrouteRouter)
|
||||
modes = r._spatial_eligible_modes(43.6, -116.2, {})
|
||||
assert modes == frozenset({"mtb", "foot"})
|
||||
assert modes == frozenset({"2w", "foot"})
|
||||
|
||||
|
||||
def test_spatial_no_class_no_use_picks_foot(monkeypatch):
|
||||
|
|
@ -635,7 +635,7 @@ def test_compute_cost_multiplier_grid_math():
|
|||
fr = _np.full((4, 4), 30, dtype=_np.uint8)
|
||||
fr[0, 0] = 80
|
||||
mult = compute_cost_multiplier_grid(
|
||||
elev, 30.0, 30.0, friction=friction, friction_raw=fr, wilderness=None, mode="mtb")
|
||||
elev, 30.0, 30.0, friction=friction, friction_raw=fr, wilderness=None, mode="2w")
|
||||
assert mult[1, 1] == 4.0 # 2.0 friction * 2.0 grass override
|
||||
assert _np.isinf(mult[0, 0]) # water impassable
|
||||
|
||||
|
|
@ -659,7 +659,7 @@ class _FakeGrid:
|
|||
|
||||
|
||||
def test_pathfind_wilderness_always_uses_foot_effort(monkeypatch):
|
||||
# Even when called with mode="mtb", the wilderness cost is computed as foot:
|
||||
# Even when called with mode="2w", the wilderness cost is computed as foot:
|
||||
# compute_cost_multiplier_grid receives mode="foot", and A* gets the foot speed
|
||||
# function (tobler=0), foot base speed (6.0), and foot trail friction.
|
||||
captured = {}
|
||||
|
|
@ -688,10 +688,10 @@ def test_pathfind_wilderness_always_uses_foot_effort(monkeypatch):
|
|||
r.wilderness_reader = None # foot is not wilderness_impassable -> not loaded anyway
|
||||
|
||||
ep = [{"lat": 44.001, "lon": -115.001, "highway_class": "track", "name": "t", "land_status": "open"}]
|
||||
out = r._pathfind_wilderness(44.0, -115.0, 44.001, -115.001, ep, "pragmatic", "start", mode="mtb")
|
||||
out = r._pathfind_wilderness(44.0, -115.0, 44.001, -115.001, ep, "pragmatic", "start", mode="2w")
|
||||
|
||||
assert out["status"] == "ok"
|
||||
assert captured["mult_mode"] == "foot" # cost grid built as foot despite mode=mtb
|
||||
assert captured["mult_mode"] == "foot" # cost grid built as foot despite mode=2w
|
||||
assert captured["speed_function_id"] == 0 # tobler (foot)
|
||||
assert captured["base_speed"] == 6.0 # foot base speed
|
||||
assert captured["lookup"][5] == 0.1 # foot road
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ const MVUM_URL = "/api/mvum"
|
|||
* Request an offroute route from the pathfinder API.
|
||||
* @param {object} start - { lat, lon }
|
||||
* @param {object} end - { lat, lon }
|
||||
* @param {string} mode - auto | foot | mtb | atv | vehicle
|
||||
* @param {string} mode - auto | foot | 2w | 4w | vehicle
|
||||
* @param {string} boundaryMode - strict | pragmatic | emergency
|
||||
* @param {string} [startCategory] - OSM "key:value" hint for the origin (Auto mode)
|
||||
* @param {string} [endCategory] - OSM "key:value" hint for the destination (Auto mode)
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ import ManeuverList from "./ManeuverList"
|
|||
const TRAVEL_MODES = [
|
||||
{ id: "auto", label: "Auto", Icon: Zap },
|
||||
{ id: "foot", label: "Foot", Icon: Footprints },
|
||||
{ id: "mtb", label: "MTB", Icon: Bike },
|
||||
{ id: "atv", label: "ATV", Icon: Car },
|
||||
{ id: "2w", label: "2W", Icon: Bike },
|
||||
{ id: "4w", label: "4W", Icon: Car },
|
||||
{ id: "vehicle", label: "Drive", Icon: Car },
|
||||
]
|
||||
|
||||
// Maps the backend's selected_mode to the chip label shown in the "Auto chose X" badge.
|
||||
const SELECTED_MODE_LABEL = { vehicle: "Drive", atv: "ATV", mtb: "MTB", foot: "Foot" }
|
||||
const SELECTED_MODE_LABEL = { vehicle: "Drive", "4w": "4W", "2w": "2W", foot: "Foot" }
|
||||
|
||||
const BOUNDARY_MODES = [
|
||||
{ id: "strict", label: "Strict", Icon: Shield, title: "Avoid barriers" },
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ function ManeuverIcon({ type }) {
|
|||
|
||||
/**
|
||||
* Add transport mode prefix to network maneuver instruction.
|
||||
* "Drive east on..." for auto, "Walk south on..." for foot, "Ride north on..." for mtb
|
||||
* "Drive east on..." for auto, "Walk south on..." for foot, "Ride north on..." for 2w
|
||||
*/
|
||||
function formatNetworkInstruction(instruction, mode) {
|
||||
if (!instruction) return ''
|
||||
|
|
@ -116,9 +116,9 @@ function formatNetworkInstruction(instruction, mode) {
|
|||
'auto': 'Drive',
|
||||
'foot': 'Walk',
|
||||
'pedestrian': 'Walk',
|
||||
'mtb': 'Ride',
|
||||
'2w': 'Ride',
|
||||
'bicycle': 'Ride',
|
||||
'atv': 'Drive',
|
||||
'4w': 'Drive',
|
||||
'vehicle': 'Drive',
|
||||
}
|
||||
const verb = modeVerbs[mode] || 'Go'
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import PlaceDetail from './PlaceDetail'
|
|||
const TRAVEL_MODES = [
|
||||
{ id: 'auto', label: 'Drive', Icon: Car },
|
||||
{ id: 'foot', label: 'Foot', Icon: Footprints },
|
||||
{ id: 'mtb', label: 'MTB', Icon: Bike },
|
||||
{ id: 'atv', label: 'ATV', Icon: Car },
|
||||
{ id: '2w', label: '2W', Icon: Bike },
|
||||
{ id: '4w', label: '4W', Icon: Car },
|
||||
{ id: 'vehicle', label: '4x4', Icon: Car },
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export const useStore = create((set, get) => ({
|
|||
routeStart: null, // { lat, lon, name }
|
||||
routeEnd: null, // { lat, lon, name }
|
||||
stops: [], // Intermediate waypoints only: [{ id, lat, lon, name }, ...]
|
||||
routeMode: "auto", // auto | foot | mtb | atv | vehicle
|
||||
routeMode: "auto", // auto | foot | 2w | 4w | vehicle
|
||||
boundaryMode: "strict", // strict | pragmatic | emergency
|
||||
routeResult: null, // Response from /api/offroute
|
||||
routeLoading: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue