mirror of
https://github.com/zvx-echo6/recon.git
synced 2026-05-20 06:34:40 +02:00
feat(offroute): Phase O3b — trail entry index, Valhalla stitching, /api/offroute endpoint
Phase A: Trail Entry Point Index - Extract highway endpoints from idaho-latest.osm.pbf using osmium + ogr2ogr - Store 740,430 entry points in /mnt/nav/navi.db (SQLite with spatial index) - Entry points by class: service (271k), footway (152k), residential (146k), track (111k), path (26k), unclassified (16k), tertiary (9k), secondary (4k), primary (4k), bridleway (15) Phase B: Pathfinder → Valhalla Stitching (router.py) - OffrouteRouter orchestrates wilderness pathfinding + Valhalla on-network routing - Queries entry points within 50km (expanding to 100km if needed) - MCP pathfinder routes to nearest reachable entry point - Calls Valhalla pedestrian/bicycle/auto costing for on-network segment - Returns GeoJSON FeatureCollection with wilderness + network + combined segments Phase C: Flask Endpoint - POST /api/offroute with start/end coordinates, mode, boundary_mode - Returns GeoJSON route with per-segment metadata and turn-by-turn maneuvers Validated: 42.35,-114.30 → Twin Falls downtown - Wilderness: 0.5km, 9min | Network: 36km, 413min | Total: ~421min - 21 turn-by-turn instructions, segments connect at entry point Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3293cb4238
commit
1a9dfc8f8d
2 changed files with 829 additions and 0 deletions
77
lib/api.py
77
lib/api.py
|
|
@ -2722,3 +2722,80 @@ def api_auth_whoami():
|
|||
'authenticated': False,
|
||||
'username': None,
|
||||
})
|
||||
|
||||
|
||||
# ── OFFROUTE API ──
|
||||
|
||||
@app.route("/api/offroute", methods=["POST"])
|
||||
def api_offroute():
|
||||
"""
|
||||
Off-network routing from wilderness to destination.
|
||||
|
||||
Request body:
|
||||
{
|
||||
"start": [lat, lon],
|
||||
"end": [lat, lon],
|
||||
"mode": "foot" | "mtb" | "atv", (default: "foot")
|
||||
"boundary_mode": "strict" | "pragmatic" | "emergency" (default: "pragmatic")
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"status": "ok",
|
||||
"route": { GeoJSON FeatureCollection with wilderness + network segments },
|
||||
"summary": { total_distance_km, total_effort_minutes, ... }
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"status": "error", "message": "No JSON body provided"}), 400
|
||||
|
||||
# Parse coordinates
|
||||
start = data.get("start")
|
||||
end = data.get("end")
|
||||
|
||||
if not start or not end:
|
||||
return jsonify({"status": "error", "message": "Missing start or end coordinates"}), 400
|
||||
|
||||
if not isinstance(start, (list, tuple)) or len(start) != 2:
|
||||
return jsonify({"status": "error", "message": "start must be [lat, lon]"}), 400
|
||||
if not isinstance(end, (list, tuple)) or len(end) != 2:
|
||||
return jsonify({"status": "error", "message": "end must be [lat, lon]"}), 400
|
||||
|
||||
start_lat, start_lon = float(start[0]), float(start[1])
|
||||
end_lat, end_lon = float(end[0]), float(end[1])
|
||||
|
||||
# Parse options
|
||||
mode = data.get("mode", "foot")
|
||||
if mode not in ("foot", "mtb", "atv"):
|
||||
return jsonify({"status": "error", "message": "mode must be foot, mtb, or atv"}), 400
|
||||
|
||||
boundary_mode = data.get("boundary_mode", "pragmatic")
|
||||
if boundary_mode not in ("strict", "pragmatic", "emergency"):
|
||||
return jsonify({"status": "error", "message": "boundary_mode must be strict, pragmatic, or emergency"}), 400
|
||||
|
||||
# Import and run router
|
||||
from .offroute.router import OffrouteRouter
|
||||
|
||||
router = OffrouteRouter()
|
||||
try:
|
||||
result = router.route(
|
||||
start_lat=start_lat,
|
||||
start_lon=start_lon,
|
||||
end_lat=end_lat,
|
||||
end_lon=end_lon,
|
||||
mode=mode,
|
||||
boundary_mode=boundary_mode
|
||||
)
|
||||
finally:
|
||||
router.close()
|
||||
|
||||
if result.get("status") == "error":
|
||||
return jsonify(result), 400
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Offroute error")
|
||||
return jsonify({"status": "error", "message": str(e)}), 500
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue