Status: in development, as of 8 Nov 2026. Not deployed for public use. Historical speeds in the builder (--routing-predicted-speeds) are done, and the router uses them for date_time types 0 (current) and 1 (depart at); types 2 (arrive by) and 3 (invariant) return error 199 for now. Live speeds and closures are supplied by the program that runs the router. No hosted service collects or serves live data yet.
On Berlin, responses are byte-identical to Valhalla 3.9.1 on 50/50 requests with historical speeds, 30/30 with live speeds and 5/5 with closures; see Status and roadmap.
Where speeds come from
The router picks one speed per road edge, in this order (the same order as Valhalla 3.9.1):
| Source | Where it lives | Used when |
|---|---|---|
| Live speed | An overlay beside the .glx file | The request has a date_time and the overlay has the edge |
| Historical speed | The routing part (predicted speeds) | The request has a date_time and the edge has a profile |
| Constrained flow | The routing part (edge field) | Daytime (07:00 to 19:00), or no date_time |
| Free flow | The routing part (edge field) | Night, or no date_time |
| Edge speed | The routing part (edge field) | Otherwise: from OpenStreetMap tags and road class |
A live speed counts fully at the start of the route and fades out over the first hour of travel time. A request without a date_time never uses live or historical speeds.
Historical speeds: the CSV format
Pass a directory to the builder:
geolith --output-format routing --osm-pbf berlin.osm.pbf \
--routing-output berlin-routing.glx \
--routing-predicted-speeds speeds/
The directory holds one CSV file per graph tile, named by its tile path, for example speeds/2/000/753/554.csv. Each row describes one directed edge of that tile:
edge_graph_id,free_flow_kph,constrained_kph,encoded_dct2/753554/120,52,38,AbC...==| Field | Meaning |
|---|---|
edge_graph_id | The edge, as level/tile/index. Only the index is read; the tile comes from the file name. |
free_flow_kph | Night speed in km/h. 0 means not given. Capped at 140. |
constrained_kph | Daytime speed in km/h. 0 means not given. Capped at 140. |
encoded_dct | Optional. The week of speeds in 2016 five-minute buckets, compressed to 200 DCT-II coefficients, stored as big-endian 16-bit integers and base64 encoded (400 bytes). |
The format and its parsing are the reference tool's (valhalla_add_predicted_traffic), and geolith's output tiles are byte-identical to that tool's output on the same tiles and CSV. Data that already targets Valhalla works unchanged. Identical profiles are stored once per tile. Each tile with profiles grows by 4 bytes per edge plus 400 bytes per distinct profile.
Edge ids change with every graph build. Providers usually key speeds by OpenStreetMap way and map them to edges for each build.
Live data
Today the router takes live speeds and closures from the program that runs it, as overlay records beside the tile bytes. The hosted overlay is planned: live speeds, closures and incidents will live in an overlay beside the .glx file, never inside it, so the file stays immutable and cacheable. The design:
- Private per account. A customer's probe data (for example a fleet's driver pings) is used only for that account. Sharing into a common traffic layer is an explicit opt-in.
- Short-lived. Each overlay record carries an expiry. Live speeds are 5-minute aggregates per edge, published only when enough distinct vehicles drove the edge. Driver ids are not stored, and raw pings are not kept.
- Keyed to one graph build. When a new routing file goes live, overlays for the old build stop matching.
- Read like tiles. The router library takes the overlay records from the program that runs it, like tile bytes, so the same code runs in a Worker and in tests.
The full design, including the ingestion API and storage, is in the routing repository at docs/live-data.md. Search plans the same per-account overlay pattern for custom places.