Skip to content

Query the database

A build is one DuckDB file. Open it from Python, the DuckDB command line, or any tool that reads DuckDB (DBeaver, …). Every example on this page was run on the Monaco network from Your first network.

import duckdb
con = duckdb.connect("monaco.duckdb", read_only=True)
con.execute("LOAD spatial")                  # for the ST_ functions below
con.sql("SELECT count(*) FROM driving.edges").show()

Where the tables are

Each mode has its own schema: driving.edges, walking.edges, cycling.edges, and so on. Always name the schema, or switch to it with USE driving;. A tool that shows no tables is usually looking at the empty default schema main. Builds from a PBF also keep the parsed OSM data in raw. Private roads (driveways, gated streets) and, in driving, bus lanes are in driving.private_edges and the like, not in edges: you can see them but not route on them (why). Every table and column: Database schema. To see what one database holds (modes, counts, time zone, build date), run duckosm info monaco.duckdb.

The size of each network

SELECT 'driving' AS mode, count(*) AS edges, round(sum(length_m) / 1000, 1) AS km FROM driving.edges
UNION ALL SELECT 'walking', count(*), round(sum(length_m) / 1000, 1) FROM walking.edges
UNION ALL SELECT 'cycling', count(*), round(sum(length_m) / 1000, 1) FROM cycling.edges;
mode edges km
driving 2765 92.6
walking 10952 244.1
cycling 10274 245.5

Roads

-- the fastest roads
SELECT name, highway, maxspeed_kmh, round(length_m) AS length_m
FROM driving.edges WHERE maxspeed_kmh >= 70 ORDER BY maxspeed_kmh DESC, length_m DESC LIMIT 5;

-- the longest edges
SELECT name, highway, round(length_m) AS length_m FROM driving.edges ORDER BY length_m DESC LIMIT 5;

-- one-way and two-way
SELECT oneway, count(*) AS edges FROM driving.edges GROUP BY oneway;

Turns

-- where you can go next from one edge (the graph of legal turns)
SELECT e.edge_id, e.name, e.highway
FROM driving.edge_graph g JOIN driving.edges e ON e.edge_id = g.to_edge
WHERE g.from_edge = 2226047604433257818;          -- the end of Avenue Delphine
edge_id name highway
8072509973910824634 Avenue Saint-Romain residential
3664387098764709418 Avenue Delphine residential
-- junctions with the most turn restrictions
SELECT via_node, count(*) AS restrictions
FROM driving.turn_restrictions GROUP BY via_node ORDER BY restrictions DESC LIMIT 5;

Places

Coordinates are longitude/latitude (EPSG:4326). DuckDB's distance function on the sphere expects latitude first, so flip the geometry:

-- the roads nearest a point (the Casino de Monte-Carlo: lat 43.7397, lon 7.4270)
SELECT name, highway,
       round(ST_Distance_Sphere(ST_FlipCoordinates(ST_Centroid(geometry)), ST_Point(43.7397, 7.4270))) AS metres
FROM driving.edges ORDER BY metres, edge_id LIMIT 3;
name highway metres
Allées des Boulingrins service 19.0
Allées des Boulingrins residential 19.0
Avenue de Monte-Carlo residential 21.0
-- edges per H3 cell (the cell of each edge's start)
SELECT from_cell, count(*) AS edges FROM driving.edges GROUP BY from_cell ORDER BY edges DESC LIMIT 3;

Raw OpenStreetMap data

Builds from a PBF keep every OSM node, way and relation in raw, with all their tags, so you can reach things that aren't roads (areas cut from a bigger build don't have raw):

SELECT osm_id, tags['name'] AS name, tags['amenity'] AS amenity
FROM raw.nodes WHERE tags['amenity'] IN ('hospital', 'pharmacy') AND tags['name'] IS NOT NULL LIMIT 5;

Look up one OSM way

duckosm way shows all the data a build has for one OSM way (the OpenStreetMap object for a street, or a stretch of one): first its raw OSM tags and node list, then every edge built from it, in every mode, as one table.

duckosm way monaco.duckdb 4230100                   # Avenue Delphine; -m driving for one mode, --geom for WKT
duckosm way monaco.duckdb 4230100 -o way.csv        # write the table to a file (.csv / .parquet / .json)
raw way 4230100: 10 node refs
  tags: {'highway': 'residential', 'name': 'Avenue Delphine'}
  refs: [21923931, 12421715488, 21924090, 25243156, 3625098865, ...]
mode edge_id edge_ref source → target length_m maxspeed_kmh cost_s walk_type / cycle_type
cycling 2226047604433257818 4230100#1f 21923931 → 21924057 88.3 15 21.2 mixed_traffic
driving 2226047604433257818 4230100#1f 21923931 → 21924057 88.3 30 10.6
walking 2226047604433257818 4230100#1f 21923931 → 21924057 88.3 5 63.6 shared_road
cycling 3664387098764709418 4230100#1r 21924057 → 21923931 88.3 15 21.2 mixed_traffic
driving 3664387098764709418 4230100#1r 21924057 → 21923931 88.3 30 10.6
walking 3664387098764709418 4230100#1r 21924057 → 21923931 88.3 5 63.6 shared_road

(A selection of the columns; the real table has every edges column.) This two-way street became two edges, one per direction (#1f forward, #1r reverse). Each has the same edge_id in all three modes, with that mode's own speed and travel time.

from duckosm.query import way_table
way_table(con, 4230100).show()                      # the same table in Python / a notebook

Use it when a particular street looks wrong: how it was split, which edges came from it, what speed and cost each got. A negative osm_id looks up the short connector edges duckOSM adds to join dangling paths.