duckOSM¶
OpenStreetMap → a routable road network in one DuckDB file, with edge IDs that survive every rebuild, clip and export.
Why duckOSM¶
Most OSM-to-network tools number their edges 0, 1, 2, …. Rebuild with fresher OSM data, cut out
a smaller area, or export to a simulator, and the numbers change, so every table keyed on them
(traffic counts, speeds, map matches, demand) has to be matched to the network again.
duckOSM's edge_id is a content hash of three ids that OpenStreetMap already gives the road
segment: the OSM way it comes from (osm_id) and the OSM nodes where it starts and ends
(source, target):
edge_id = hash(osm_id, source, target)
The order of source and target is the direction, so the two directions of a two-way street get
different ids. None of the three is a number duckOSM makes up, so the id stays the same:
-
Across rebuilds
The same OSM data gives the same ids. An id only changes if that way or its end nodes change in OSM.
-
Across clips
Build a country once, cut out a city in seconds: the city keeps the country's ids.
-
Across exports
SUMO, MATSim, GMNS, GeoPackage and networkx all use the
edge_idas their own id.
How the id is built, in detail
Quick start¶
pip install "duckosm[routing]"
curl -LO https://download.geofabrik.de/europe/monaco-latest.osm.pbf
duckosm build --pbf monaco-latest.osm.pbf --output monaco.duckdb --modes driving
import duckdb
from duckosm import route
con = duckdb.connect("monaco.duckdb", read_only=True)
q = "SELECT min(edge_id) FROM driving.edges WHERE name = ?"
a = con.execute(q, ["Boulevard du Larvotto"]).fetchone()[0]
b = con.execute(q, ["Avenue Princesse Grace"]).fetchone()[0]
r = route(con, a, b) # fastest path; weight="length" for distance
r["time_s"], r["length_m"], r["edges"] # ~114 s, ~1.5 km, the ordered edge_ids
Next: Your first network walks through it step by step, and the guides cover each task.
What you get¶
- A driving, a walking and a cycling network in one file, one schema each, split at the same
junctions so a road has the same
edge_idin every mode. OSM access tags are respected: private roads are kept for the map but never routed (details). - The graph of legal turns (
edge_graph), with OSM turn restrictions, for routing in SQL, Python or networkx (Route). - Base-map layers (
features.*: water, land use, buildings, POIs) in the same file (Base-map layers). - Maps in one offline HTML page:
duckosm vizdraws the network and the base map from the database (Draw a map);duckosm route-mapis a route planner (drive, walk, cycle, walk + drive) where you drag a start and an end and get turn-by-turn directions (try it). The pages are drawn by mapstyle. - The drawing order of the roads:
duckosm levelsstores which road is painted over which (casing and fill numbers) in the file (Store the drawing order). - Any area: a GeoJSON boundary, a place name, a box or an H3 cell;
duckosm boundaryfinds an area's border by name, andduckosm extractcuts a city out of a country build in seconds (Prepare an area). - Your own fixes for OSM errors, applied on every build (Fix OSM errors).
Exports¶
| Format | Command | How it's verified |
|---|---|---|
| SUMO | duckosm sumo |
SUMO's own netconvert assembles the network; turn restrictions become explicit connections |
| MATSim | duckosm matsim, matsim-lanes |
validated against MATSim's official DTD and XSD schemas |
| GMNS | duckosm gmns |
follows the GMNS table spec |
| GeoPackage / shapefile | duckosm export-gis |
duckosm gis-debug reads the file back through GDAL and diffs every edge_id |
| networkx | duckosm export-graph |
unit tests; GraphML or lossless gpickle |
Experimental: OpenDRIVE, Lanelet2, railML, lane-level routing, GMNS meso/micro networks, intermodal routing, elevation, admin boundaries and base-map layers. They work and are structure-tested, but haven't been checked in the target tools yet. Which export for which tool: Exports.
duckOSM is MIT-licensed and built on DuckDB and its spatial extension. It is an independent project, not affiliated with or endorsed by the DuckDB Foundation.