Skip to content

Architecture

ridegraph is a Python package that does the heavy lifting offline and a static web page that runs the same algorithms in the browser. There is no server: CI builds everything and publishes files.

flowchart LR
    subgraph Offline["Python package (CI or your machine)"]
        OSM[(OpenStreetMap<br/>Overpass API)] --> parse[osm.py<br/>parse and prune]
        parse --> net[RoadNetwork<br/>CSR arrays]
        net --> speeds[speeds.py + traffic.py<br/>seconds per road by time of day]
        speeds --> ch[ch.py<br/>Contraction Hierarchy per preset]
        net --> cch[cch.py<br/>nested dissection + fill-in]
        net --> turns[turns.py<br/>edge-expanded graph]
        ch --> eta[eta.py + gnn.py<br/>simulated trips, trained models]
        ch --> dispatch[demand.py + dispatch.py<br/>simulated mornings]
        TfL[(TfL disruption feed)] --> live[live.py<br/>map matching]
        ch & cch & eta & dispatch & live --> export[web_export.py<br/>binary arrays + JSON]
    end
    subgraph Browser["Static site"]
        export --> engine[engine.js<br/>searches, CH, many-to-many]
        engine --> cchjs[cch.js<br/>customisation]
        engine --> alt[alternatives.js]
        engine --> tour[tour.js]
        engine --> etajs[eta.js<br/>tree walk]
        engine --> replay[dispatch.js<br/>replay]
        TfL2[(TfL feed, live)] --> livejs[live.js] --> cchjs
        engine & cchjs & alt & tour & etajs & replay --> ui[app.js + atlas.js<br/>canvas street atlas]
    end

Layers

Data. overpass.py downloads drivable roads with retries and mirror fallback. osm.py keeps the ways a car may use, splits them into one segment per pair of consecutive OSM nodes, respects one-way rules, and keeps the largest strongly connected component so every route can return.

Graph. graph.py stores the network as flat arrays and builds a compressed sparse row (CSR) graph whose edge weights are seconds. speeds.py turns road class and speed limits into free-flow speeds, and traffic.py scales them by time of day and by how sensitive each class of road is to congestion. turns.py builds the edge-expanded graph used for turn and traffic-light costs.

Search. dijkstra.py is the reference. bidirectional.py, astar.py, ch.py (Contraction Hierarchies, with many-to-many) and cch.py (Customizable CH) must all match it, and router.py hides which graph a scenario needs. alternatives.py, reach.py and tour.py build everyday tools on top.

Learning. eta.py simulates trips in a hidden world calibrated to London, extracts route features and trains ridge, MLP, gradient-boosted trees and quantile models with conformal calibration. gnn.py trains the graph neural network over every road. demand.py forecasts ride requests.

Decisions. matching.py holds the Hungarian algorithm. dispatch.py simulates a fleet under six policies on identical requests.

Integration. live.py matches TfL disruptions onto the graph, charges.py holds the Congestion Charge zone, osrm.py prepares the head-to-head with OSRM, and web_export.py writes everything the browser needs.

The browser

The export is a manifest plus gzipped, 8-byte-aligned binary arrays that the browser views directly as typed arrays, so loading London takes no parsing. engine.js ports every search; cch.js re-customises when live closures arrive; eta.js walks the exported trees; dispatch.js replays the simulation, rebuilding each leg's route with the same hierarchy. Node tests run these modules against Python on the same data and require identical answers.

Build and deploy

Workflow What it does
ci.yml Lint, format, mypy, the full test suite with coverage on Python 3.10, 3.12 and 3.13
pages.yml Fetch London, snapshot TfL, build, contract, train, simulate, export, build these docs, publish
osrm.yml Run OSRM in Docker on the same roads and speeds and compare 1,000 random trips