React + TypeScript + Tailwind rewrite of the single-driver, ~10-stop route optimizer.
npm install
npm run devOpen the printed localhost URL. Click the map to add a stop, drag any pin to move one, and the route re-solves automatically (debounced ~500ms).
src/
lib/
types.ts Stop, DurationMatrix, SolveResult, ComplexityEstimate
geo.ts haversine fallback + time/distance formatting
osrm.ts OSRM /table (matrix) and /route (geometry) calls, with
graceful fallback to a haversine-based estimate
tsp.ts heldKarp() exact solver, bruteForce() reference solver,
estimateComplexity() for the growth-tracking panel
components/
RouteMap.tsx Leaflet map, draggable pins, click-to-add
RoutePanel.tsx ordered stop list with per-leg time/distance
MatrixView.tsx duration matrix heatmap
ComplexityPanel.tsx brute-force vs Held-Karp op counts as stops change
HowItWorks.tsx algorithm walkthrough + worked diagram
App.tsx state, tab switching, debounced solve pipeline
- Solver:
heldKarp()inlib/tsp.tsis exact (not a heuristic), running inO(n²·2ⁿ)time. Practical up to ~13-15 stops — the Complexity tab shows this live as you add/remove stops, andestimateComplexity()flags when you're leaving that practical range. - Real drive times:
getDurationMatrix()calls the public OSRM demo server. It has no SLA — for production, self-host OSRM with a regional.osm.pbfextract and pointOSRM_BASEinlib/osrm.tsat it. If the call fails for any reason, it transparently falls back to a haversine-based estimate and the UI badge reflects which one you're looking at. - Scaling past ~15 stops or adding multiple vehicles, time windows, or
capacity constraints: swap
heldKarp()for a call to Google OR-Tools (typically via a small Python backend - which I aim to implement as an addition to learning this) — the same distance matrix feeds either solver.