VoltNav

OpenStreetMap derivative database: derivation methodology

VoltNav derives several databases from OpenStreetMap data and stores them separately from the original OpenStreetMap "Database" — a routing graph (used by our turn-by-turn navigation), a non-routing feature extract (used for speed limit, bike lane, and stop sign/traffic light display), and vector map tiles.

This document makes the method of making these alterations to the original OSM Database publicly available, as required by the Open Database License (ODbL) section 4.6(b). See our Data & Attributions page for attribution and license details.

1. Routing graph

  1. Download each country/subdivision extract named in our region manifest (data/regions.json) and combine them into one file via osmium merge. Adjacent extracts intentionally duplicate objects along their shared border, so before merging we extract each file's own object-id list, find ids that appear in more than one file, deterministically assign each duplicate to exactly one "keeper" file, strip it from the others with osmium removeid, and only then merge — this avoids the duplicate-id errors osmium merge raises on directly overlapping extracts.
  2. Filter the merged file down to routing-relevant ways with osmium tags-filter, keeping any way carrying one of these keys (and its referenced nodes): highway, route, railway, amenity, man_made, public_transport, bridge. This drops buildings, landuse, and other content no routing profile reads.
  3. Tag every remaining way with the country (and, where applicable, subdivision) it falls within, by testing a representative point — the mean of the way's own resolvable node coordinates — against Natural Earth's public-domain admin-0 (country) and admin-1 (state/province) boundary polygons, via a Shapely STRtree spatial index built once over those polygons. Resolved ways are written back out with new voltnav:country/voltnav:subdivision tags; unresolved ways pass through unchanged. (This step's boundary source is Natural Earth, not OSM's own admin relations — it changes which tags a way carries, not any OSM-sourced value.)
  4. Resolve a small lookup table of legal maxspeed thresholds and bike-infrastructure legality per country/subdivision code from our own regulatory dataset (independent of OSM).
  5. Run osrm-extractosrm-partitionosrm-customize against the filtered, tagged file using one of three custom vehicle profiles. Each profile computes a routability and speed value per way from OSM's own highway, surface, cycleway, and maxspeed tags against a fixed lookup table, and hard-excludes any way whose resolved jurisdiction's legal speed threshold (step 4) it exceeds. Two of the three profiles additionally compute a custom routing weight — used only to choose between otherwise time-comparable route options, never to alter reported travel time — that further penalizes road classes lacking adjacent cycleway coverage, and roads in a jurisdiction's highest legal-speed tier.
  6. The resulting graph files are used exclusively to answer individual routing queries (an origin/destination request returns that one route's polyline and turn instructions); the graph itself is never distributed.

2. Non-routing feature extract

  1. Stream every way and node in the same regional extract with a single pass (pyosmium).
  2. For each highway=* way: parse its maxspeed tag (recognizes plain km/h values and "N mph" values, converted to km/h; anything else is treated as no data) and its cycleway infrastructure tags (highway=cycleway, and cycleway/cycleway:left/ cycleway:right/cycleway:both values), mapped onto a four-level protection scale (dedicated path > track > painted lane > shared lane).
  3. Emit one GeoJSON feature per way carrying a parsed maxspeed (with its OSM name tag) into one file, and one feature per way carrying a cycleway level into a second file.
  4. For each node tagged highway=stop or highway=traffic_signals: emit a feature carrying that node's own OSM id and, when a direction/stop:direction/ traffic_sign:direction/traffic_signals:direction tag combined with the way's own local bearing at that node resolves unambiguously, a computed compass bearing for which direction of travel the control applies to.
  5. Each output file is indexed into a coarse spatial grid (~1.1km cells, SQLite-backed) at server start — a regenerable cache of the GeoJSON above, not a further transformation of OSM content.
  6. Served only via bounded queries: the nearest tagged way to a single point (speed-limit display), and bounding-box-limited feature collections sized to one route's corridor or the rider's current map viewport — never an unbounded or whole-dataset export.

3. Vector map tiles

Vector tiles are produced from the same regional extract using Planetiler with the OpenMapTiles schema — an existing, independently documented open-source transformation, not an algorithm of our own design. VoltNav's own configuration only selects which regions are included; it does not alter the tag-to-tile-layer schema itself, which is documented at the links above.

4. Address search

Address search uses GraphHopper's pre-built Photon search index, itself a derivative of OpenStreetMap data built via Photon's own independently documented import pipeline. VoltNav does not alter this index; a thin server-side proxy filters results to the countries VoltNav has routing coverage for and reshapes the response format at query time, without altering the indexed content itself.