GMNS tables, one by one¶
A reference for every table in the GMNS database that duckosm gmns writes: what a row is, how it is
made from OSM, and what each column holds. For the command and the options, see GMNS. For
the standard itself, see the GMNS specification
(release 0.97). Numbers are from Monaco, driving.
How to read this page¶
Where the tables are. One schema per mode: gmns_driving, gmns_walking, gmns_cycling. With
--combined there is also gmns_all; with --meso / --micro, meso_<mode> and micro_<mode>.
SELECT count(*) FROM gmns_driving.link; -- ATTACH the file, or open it directly
Column marks. The Spec column says where a column comes from (--csv-extensions keeps the ➕ columns in the CSVs too, as the spec's user-defined fields, with a u_ prefix: u_osm_id, u_bridge, u_turn, …; the native geom columns never go to a CSV):
| Mark | Meaning | In the CSVs (--to-csv)? |
|---|---|---|
| ✅ | a column of the GMNS standard, filled from OSM | yes |
| ∅ | a column of the standard that OSM has no data for, so it stays empty | yes, empty |
| ➕ | a duckOSM extension: not in the standard | no (the CSV has only standard columns) |
Conventions that hold in every table:
- Ids are integers.
link_idis duckOSM'sedge_id, a content hash (BIGINT, up to 19 digits). It is the same on every rebuild and the same in every mode, so you can join GMNS to your own data on it. Keep itBIGINT: as a float or a JavaScript number it loses digits.node_idis the OSM node id. - Units: lengths and widths in metres, speeds in km/h, coordinates in longitude / latitude
(EPSG:4326). The
configtable says so. - Geometry twice. Where a table has a shape, the standard column
geometryholds it as WKT text, and the extension columngeomholds the same shape as a nativeGEOMETRYyou can query with the DuckDBspatialextension (ST_*) and draw. Onlygeometrygoes to the CSVs. - One link per direction. A two-way road is two links with swapped nodes and reversed geometry; a
one-way road is one. Every link has
directed = true. - Lane numbers run from the left in the direction of travel: lane 1 is the leftmost lane, whichever side
traffic drives. (With right-hand traffic, the default, the leftmost lane is next to the road's centre line;
with
--drive-side leftthe lanes lie left of it and the leftmost is the farthest.) OSM'sturn:lanesandbicycle:laneslist lanes in the same order, so entry n is lane n.
The tables at a glance¶
| Table | Standard? | One row is | Rows | Read it for |
|---|---|---|---|---|
config |
yes | the dataset | 1 | units, CRS, spec version |
use_definition |
yes | a way of using a lane (auto, bus, …) | 3 | what allowed_uses values mean |
use_group |
yes | a named group of uses | 1 | the mode's uses |
node |
required | a junction or end of a link | 1,904 | where links meet |
link |
required | one direction of a road | 3,092 | the routable network |
geometry |
yes | the shape of a link | 3,092 | link shapes, keyed by id |
lane |
yes | one lane of a link | 3,520 | lane use, width, turns |
movement |
yes | one legal turn at a junction | 3,957 | which link may follow which, from which lanes |
lane_connector |
➕ extension | one lane-to-lane path across a junction (driving), and a footway's join to a road lane (walking) | 1,813 + 542 | drawing lanes through junctions |
lane_check |
➕ extension | something the lanes could not be given: a lane with no way in or out, a turn with no lane connection | — | what to check (in Street View) |
crossing |
➕ extension | one pedestrian crossing painted on the driving lanes | 741 | drawing zebra crossings |
lane_crossing |
➕ extension | one lane a crossing covers, and which stretch of it | 909 | painting the stripes lane by lane |
link_along |
➕ extension | one road a footpath runs along (a footpath often runs along several) | — | matching a sidewalk to its whole street |
curb_seg |
yes | a stretch of kerb with a rule | 2 | on-street parking |
signal_controller |
yes | a traffic signal | 1 | where the signals are |
location |
yes | a point along a link | 1,803 | crossings, signs, parking entrances |
zone |
yes | an area | 1 | the area's name and, when known, outline |
gmns_all |
all modes in one node + link |
one network for every mode | ||
meso_*, micro_* |
osm2gmns layout | sections, connectors, cells | simulators |
How they point at each other:
erDiagram
node ||--o{ link : "from_node_id / to_node_id"
link ||--|| geometry : "geometry_id"
link ||--o{ lane : "link_id"
link ||--o{ curb_seg : "link_id"
link ||--o{ location : "link_id"
node ||--o{ movement : "node_id"
link ||--o{ movement : "ib_link_id / ob_link_id"
movement ||--o{ lane_connector : "mvmt_id"
lane ||--o{ lane_connector : "from_lane_id / to_lane_id"
node ||--o| signal_controller : "node_id"
use_definition ||--o{ lane : "allowed_uses"
config¶
One row that says how to read the other tables. It is what a tool needs to know first.
| Column | Type | Spec | Value |
|---|---|---|---|
dataset_name |
VARCHAR | ✅ | the output file's name plus the mode, e.g. monaco_gmns_driving |
short_length |
VARCHAR | ✅ | meter: lane widths, linear references |
long_length |
VARCHAR | ✅ | meter: link lengths |
speed |
VARCHAR | ✅ | kmh |
crs |
VARCHAR | ✅ | EPSG:4326 |
geometry_field_format |
VARCHAR | ✅ | wkt |
currency |
VARCHAR | ∅ | empty (OSM has no toll prices) |
version_number |
DOUBLE | ✅ | 0.97, the GMNS release the output follows |
id_type |
VARCHAR | ✅ | integer |
use_definition¶
What each use in allowed_uses means, so a tool can tell a bus lane from a car lane.
| Column | Type | Spec | Holds |
|---|---|---|---|
use |
VARCHAR | ✅ | the name: auto, bus, bike (driving); walk (walking); bike (cycling) |
persons_per_vehicle |
DECIMAL | ✅ | auto 1, bus 25, bike 1, walk 1 |
pce |
DECIMAL | ✅ | passenger-car equivalents: auto 1.0, bus 2.0, bike 0.2, walk 0 |
special_conditions, description |
VARCHAR | ∅ | empty |
Every use that appears in link.allowed_uses, lane.allowed_uses or movement.allowed_uses is defined here,
as the standard requires.
use_group¶
A name for the uses of a mode, to keep allowed_uses lists short.
| Column | Type | Spec | Holds |
|---|---|---|---|
use_group |
VARCHAR | ✅ | the mode: driving, walking, cycling |
uses |
VARCHAR | ✅ | its use: auto, walk, bike |
description |
VARCHAR | ∅ | empty |
node¶
A point where links start or end: a junction, a dead end, or a point where a road was cut. This is one of
the two required tables, with link.
A node is made wherever two OSM ways meet or a way ends (as in the source network's nodes; a bus-only road's own nodes,
which nodes lacks, are its links' end points, with no height). A road that
loops back on itself is cut in two at its midpoint; that point is a node with a negative id, because
it is not an OSM node.
| Column | Type | Spec | Holds |
|---|---|---|---|
node_id |
BIGINT | ✅ key | the OSM node id; negative for the cut points above |
x_coord, y_coord |
DOUBLE | ✅ | longitude, latitude |
ctrl_type |
VARCHAR | ✅ | signal where the OSM node is a highway=traffic_signals; else from the stop and give-way signs on its approaches: stop_4_way (every approach stops, four or more), stop (every approach, fewer), stop_2_way (some approaches), yield; empty where there is none. See movement for which signs count |
z_coord |
DOUBLE | ✅ | metres: the node's height, when the source was given heights with duckosm elevation; empty otherwise |
zone_id |
BIGINT | ✅ | 1: every node is in the zone. (Tools such as Path4GMNS refuse nodes with no zone.) |
node_type |
VARCHAR | ✅ | OSM names, as the spec's FAQ recommends: the OSM highway value for a turning_circle, mini_roundabout or motorway_junction; else intersection (three or more neighbouring nodes), dead_end (one); empty where a road is merely cut |
name, parent_node_id |
∅ | empty | |
geom |
GEOMETRY | ➕ | the point |
link¶
The routable network: one row per direction of a road. A footpath is one link too (two, if it can be
walked both ways). In driving, a direction only buses may use is a link too, of one lane (allowed_uses bus): a bus lane
against a one-way street and a bus-only road, the source's driving.private_edges with access = 'bus'
(design). Their turns are the source
edge_graph's rows with uses = 'bus', so movements lead into and out of them, for buses only. Monaco: 18 (Boulevard Charles III's bus lane back,
1449981121#1r, is one).
| Column | Type | Spec | Holds |
|---|---|---|---|
link_id |
BIGINT | ✅ key | the edge_id: a hash of the OSM way and the two nodes; stable, same in every mode |
name |
VARCHAR | ✅ | the street name (name tag), often empty |
from_node_id, to_node_id |
BIGINT | ✅ | the two nodes, in the direction of travel |
directed |
BOOLEAN | ✅ | always true |
geometry_id |
BIGINT | ✅ | the same as link_id: the row of geometry |
geometry |
VARCHAR | ✅ | the shape, WKT LINESTRING, from from_node to to_node |
dir_flag |
INTEGER | ✅ | always 1: the shape runs from from_node to to_node |
length |
DOUBLE | ✅ | metres: the length of the shape on the ellipsoid, so it always agrees with geometry |
facility_type |
VARCHAR | ✅ | the OSM highway value as it is: residential, primary_link, footway, … |
capacity |
DOUBLE | ✅ | saturation capacity, passenger cars per hour per lane: a default per road class (motorway 2000, trunk 1800, primary 1600, secondary 1400, tertiary 1200, unclassified 1000, residential 800, living_street 300, service 300, anything else 800). Empty on links that cars cannot use, see below |
free_speed |
FLOAT | ✅ | km/h: the maxspeed tag, else a default per road class; walking 5, cycling 15 |
lanes |
INTEGER | ✅ | the number of permanent motor-vehicle lanes in this direction, from the lanes / lanes:forward / lanes:backward tags, 1 where untagged; 1 on a bus-only link (a one-way street's lanes are its own direction's). Not the number of rows in lane: the standard excludes turn pockets, bike lanes, shoulders and parking lanes. Empty on links that cars cannot use |
bike_facility |
VARCHAR | ✅ | from OSM cycleway, in the standard's words: unseparated bike lane, separated bike lane, shared lane, counter-flow bike lane, paved shoulder, none, other |
ped_facility |
VARCHAR | ✅ | from OSM sidewalk: sidewalk (either side or both: which side is lost), offstreet_path (separate), none, unknown |
allowed_uses |
VARCHAR | ✅ | the mode's use: auto, walk or bike; on a bus-only link bus, or bus,bike where bikes may use it too (the way has oneway:bicycle=no, or cycleway / cycleway:left / cycleway:right = share_busway, or the edge is in cycling) |
grade |
DOUBLE | ✅ | percent, negative downhill: 100 × (height at the end − height at the start) / length, when the source has heights (duckosm elevation). Empty on a bridge or in a tunnel (there the height is the ground below or above, not the road) and where the slope would pass 100 %, the spec's limit. A coarse elevation model makes short links noisy: see the elevation guide |
parent_link_id |
BIGINT | ✅ | for a sidewalk (an OSM way with footway=sidewalk) in the walking and cycling schemas, the road link it runs along: the nearest, roughly parallel one within 30 m, and of a two-way road the link the sidewalk is on the kerb side of (its right with right-hand traffic). Empty on roads and on a sidewalk with no road beside it |
parking, toll, jurisdiction, row_width |
∅ | empty (parking is in curb_seg) |
|
osm_id |
BIGINT | ➕ | the OSM way the link comes from: one way gives several links (one per piece and direction). A way duckOSM had to split carries a virtual negative id in its edges (edge_ref -1525349445#1f); this column is always the real way (its absolute value), and the way's tags (highway, footway, crossing, lane tags) are read from it |
edge_ref |
VARCHAR | ➕ | duckOSM's readable id of the link, <osm_id>#<n><f or r> (503633032#2f): n counts a way's pieces along it, f / r is the direction. The two directions of one two-way piece share <osm_id>#<n> (#2f and #2r), so two links with the same way and number are twins; links of different ways (503633032#2f, 503633034#3f) never are. Not a key (link_id is) |
footway |
VARCHAR | ➕ | the OSM footway tag (sidewalk, crossing, link, …), empty without raw tags: a crossing lies on the road it crosses (a reader draws it over the road) |
crossing, crossing_markings |
VARCHAR | ➕ | on a footway=crossing link, the OSM crossing (marked, zebra, uncontrolled, unmarked, traffic_signals, …) and crossing:markings (yes, zebra, no, …) tags; empty on every other link |
along_link_id, along_mode, along_gap_m, along_kind |
BIGINT, VARCHAR, DOUBLE, VARCHAR | ➕ | on a footpath link, the road it runs along: the nearest roughly parallel road of the footpath's own level (within 30 m, on its kerb side), even where that road is only in gmns_driving (link_id is the same hash in every schema); the network it is in (walking / driving); the metres between the two lines; and along_kind: sidewalk (a footway=sidewalk way) or adjacent (any other footway, path, pedestrian or cycleway link, not a link or steps, that lies within 5 m of the road's edge: a label of closeness, nothing is moved; a footway=crossing link only when it runs along the road for most of its length and is either 10 m or longer (a footpath OSM tagged by mistake) or shares a node with a matched footpath that runs along the same road (a sidewalk's crossing of a side street, which also runs along the main road)). The road is one cars can use: a road only people walk on is not one to run along. Empty on every other link. parent_link_id is the same road for a sidewalk only, and only when it is a link of this table |
bridge, tunnel, layer |
VARCHAR | ➕ | the OSM tags as they are (yes, -1, …): which links are on a bridge or in a tunnel, and the drawing order |
geom |
GEOMETRY | ➕ | the shape |
Links that cars cannot use. In the walking and cycling schemas, a link whose OSM highway is
footway, path, cycleway, steps, pedestrian, bridleway, corridor or platform has lanes and
capacity empty: both are motor-vehicle quantities, and the standard treats foot and bike links as
uncapacitated. A road that is walked or cycled on keeps its motor lanes and capacity.
-- the longest bridges
SELECT name, round(length) AS metres FROM gmns_driving.link WHERE bridge = 'yes' ORDER BY length DESC LIMIT 5;
geometry¶
The shape of every link, in its own table keyed by geometry_id: for tools that keep shapes apart from
links. It repeats link.geometry, row for row.
| Column | Type | Spec | Holds |
|---|---|---|---|
geometry_id |
BIGINT | ✅ key | the link_id |
geometry |
VARCHAR | ✅ | WKT LINESTRING |
geom |
GEOMETRY | ➕ | the same, native |
lane¶
One row per lane of a link. Without OSM lane tags the lanes are just numbered; with them, each lane knows its use, width and turns.
| Column | Type | Spec | Holds |
|---|---|---|---|
lane_id |
VARCHAR | ✅ key | <link_id>_<lane_num>, e.g. 8511077704723192952_2 |
link_id |
BIGINT | ✅ | the link |
lane_num |
BIGINT | ✅ | 1 = the leftmost lane in the direction of travel, up to the number of lanes. An on-road bike lane on the right (cycleway:right=lane) is the last; one on the LEFT of a one-way road (cycleway:left=lane) is -1, left of lane 1 (the spec numbers lanes left of the left-most through lane negative), so the motor lanes keep 1..n |
allowed_uses |
VARCHAR | ✅ | auto; bus where psv:lanes / bus:lanes says designated; bike where bicycle:lanes does, or for an on-road bike lane (see below); the one lane of a bus-only link as its link's, bus or bus,bike (driving). walk / bike in the walking / cycling schemas |
width |
DOUBLE | ✅ | metres, from width:lanes; else the class default (service 2.5, residential / living street / unclassified 3.0, others 3.25); a bus-only link's lane 3.25; a bike lane 1.5; empty on a walk lane without a width |
r_barrier, l_barrier |
VARCHAR | ∅ | empty |
turn |
VARCHAR | ➕ | the lane's turn:lanes value (left, through;right, …), empty where untagged |
geom |
GEOMETRY | ➕ | the lane's centre line, offset from the link line by the widths of the lanes before it. A two-way road's lanes sit on the traffic side; a road mapped as two one-way ways is placed as one road (no overlap). It runs to the link's nodes |
geom_cut |
GEOMETRY | ➕ | driving: the lane's line where SUMO netconvert ends it, at the edge of the junction; the lane_connector paths meet it (design). geom is the line to the node |
How many rows: the lane count, plus a lane for each extra entry of turn:lanes (a turn pocket), plus one for an
on-road bike lane. That is why a link can have lanes = 2 and three lane rows.
On-road bike lanes. Where OSM tags cycleway=lane (or cycleway:right=lane / :both) on the right of
the direction of travel (for the way's reverse direction: cycleway:left / :both), the link gets one more lane
row, allowed_uses = 'bike', the last lane, to the right of the motor lanes; its width is the
cycleway:right:width tag, 1.5 m if untagged. The GMNS FAQ says on-road bike lanes belong in the lane
table. It is not a place to turn into: movements, forks and merges use the motor lanes only, and link.lanes
stays the motor lane count. Not done for left-hand traffic (--drive-side left: the lane would be on the left
and renumber the motor lanes), for separated tracks (they are their own links in the cycling schema) or where
bicycle:lanes already marks a lane.
-- the lanes of one street, with their uses
SELECT l.name, k.link_id, k.lane_num, k.allowed_uses
FROM gmns_driving.link l JOIN gmns_driving.lane k USING (link_id)
WHERE l.name = 'Boulevard Charles III' ORDER BY k.link_id, k.lane_num;
movement¶
One row per legal turn: from this link, onto that link, at this node. It says which links may follow which, and from which lanes.
The turns are the legal ones: OSM turn restrictions are applied. Turning back onto the road's other direction (a U-turn) is kept only at a dead end, or where it is the only way into that road's other direction.
| Column | Type | Spec | Holds |
|---|---|---|---|
mvmt_id |
VARCHAR | ✅ key | <ib_link_id>-<ob_link_id> |
node_id |
BIGINT | ✅ | the junction where the turn is made: the end of the inbound link and the start of the outbound |
ib_link_id, ob_link_id |
BIGINT | ✅ | the inbound and outbound link |
type |
VARCHAR | ✅ | left, right, thru, uturn (from the change of heading at the junction), merge, diverge (the road joins or splits within 45°) |
mvmt_code |
VARCHAR | ✅ | direction of travel and turn, e.g. NBL = northbound, left (NB/EB/SB/WB + L/T/R). Empty for a U-turn: the standard's code has no U; type says it |
start_ib_lane, end_ib_lane |
INTEGER | ✅ | the inbound lanes the turn starts from, lane_num from..to |
start_ob_lane, end_ob_lane |
INTEGER | ✅ | the outbound lanes it ends in. The two ranges are the same length and match in order: the first inbound lane goes to the first outbound lane. Where the lanes of one turn do not go on side by side (one lane into two), the turn has more rows: <mvmt_id>-2, ...; a bike lane's own, <mvmt_id>-b<lane> (allowed_uses bike) |
allowed_uses |
VARCHAR | ✅ | the mode's use; into or out of a bus-only link (driving), bus, or bus,bike where every bus-only link of the turn takes bikes |
ctrl_type |
VARCHAR | ✅ | signal at a signalised junction; otherwise stop or yield where an OSM highway=stop / give_way sign applies to the inbound link (cars and bikes, not walking). A sign lies on a way node on the approach, so it counts when it is within 50 m of the end of the inbound link, before the junction, and its direction (forward, backward or both, along the way) matches the link's. Signs with no direction, or exactly on the junction node, say nothing about which approach and are left out |
geometry |
VARCHAR | ✅ | a short curve from the end of the inbound link to the start of the outbound link, WKT |
name, penalty, capacity |
∅ | empty |
A bus's turn into or out of a bus-only link (the source edge_graph's rows with uses = 'bus', the same U-turn
rule) is a movement too. Monaco: 27 such turns.
Where the lane ranges come from (design): driving and cycling, SUMO netconvert's lane
connections, given duckOSM's lanes and legal turns; walking (a footpath is one strip), the lanes in order, lane 1 into lane 1.
A turn netconvert gives no lanes keeps its row without lanes and is listed in lane_check. A range is never half
empty: both ends are set, or none.
-- from which lanes can a vehicle turn left?
SELECT i.name, k.lane_num
FROM gmns_driving.movement m
JOIN gmns_driving.link i ON i.link_id = m.ib_link_id
JOIN gmns_driving.lane k ON k.link_id = m.ib_link_id
AND k.lane_num BETWEEN m.start_ib_lane AND m.end_ib_lane
WHERE m.type = 'left';
lane_connector¶
duckOSM's own table (in gmns_driving; a footway has no turning path through a junction in OSM, its lane runs to its node, so gmns_walking has only the short footway joins), for drawing and for lane-level routing: the path from one lane to the next across
a junction. A movement from lanes 1-2 into lanes 1-2 has two connectors.
| Column | Type | Holds |
|---|---|---|
connector_id |
VARCHAR | <from_lane_id>><to_lane_id> |
mvmt_id |
VARCHAR | the movement it belongs to |
from_lane_id, to_lane_id |
VARCHAR | the two lanes |
width |
DOUBLE | metres (the lane width, 3.25 m by default; a U-turn is as wide as its lanes, never more than the gap between them) |
geom |
GEOMETRY | the path through the junction: SUMO netconvert's internal lane, from the lane's geom_cut end to the next lane's geom_cut start |
Which lane goes to which lane, and the path between them, come from SUMO netconvert, given duckOSM's lanes (driving.lane_profile)
and legal turns (design). Not in the standard, so never in the CSVs.
lane_check¶
duckOSM's own table, in gmns_driving, gmns_cycling and gmns_walking: what the lanes could not be given, listed instead of filled
in by a guess. Empty when everything maps.
| Column | Type | Holds |
|---|---|---|
kind |
VARCHAR | no way out (no movement leaves the lane: it ends before a narrowing), no way in, no lane connection (netconvert built none for a legal turn), lane count (SUMO built a link with other lanes than duckOSM's), against its arrow (a lane that goes to an exit its turn:lanes arrow does not name, though SUMO was given the arrows: a link's arrows name its exits left to right, by order, not by angle; where they fit, netconvert's OSM import connects the lanes by them, sumo._arrow_connections), arrows not matched (a link whose arrows name more or fewer directions than it has exits: a turn the arrows leave out, or arrows meant for a junction further on; SUMO is not given them) |
id |
VARCHAR | the lane_id, mvmt_id or link_id |
detail |
VARCHAR | what was found |
Most rows are OSM data to check (a lanes=2 on a one-lane road leaves a lane with no way out); fix them with osm_overrides.yaml.
Footway joins¶
When both driving and walking are exported, gmns_walking.lane_connector holds one straight connector per footway end that meets a road at a node
both links share, from the nearest road lane's end to the footway lane's end, when the two are 0.3-6 m apart (a road lane sits beside its link's line, a footway lane
on it). from_lane_id is the road lane (the same id in gmns_driving and gmns_walking), to_lane_id the footway lane, width the footway's, mvmt_id the walking
movement between the two links, or NULL. Nothing moves: the footway still runs to its OSM node. See the design.
crossing¶
duckOSM's own table (only in gmns_driving), for drawing: one pedestrian crossing painted on the road, from the OSM
footway=crossing ways and from the highway=crossing nodes that no crossing way has. The zebra is the best rectangle for the links it crosses (below), then projected on each lane. Rows exist only where the crossing lies
across a driving lane of its own level (a ground crossing over a road in a tunnel is no crossing of that road; level: the OSM layer, else bridge 1 / tunnel -1) (30 degrees or more from the lane, not winding); a crossing way and the nodes on it are one crossing, and two
on one place are one (the longer wins).
| Column | Type | Holds |
|---|---|---|
crossing_id |
VARCHAR | w<osm_id> (a crossing way; w<osm_id>#2 for its second piece, across a dual carriageway) or n<osm_id> (a node) |
source |
VARCHAR | way (its line is the zebra's) or node (a crossing node with no way: the line is square across the road, through the node) |
crossing_type |
VARCHAR | the OSM crossing tag (marked, zebra, uncontrolled, unmarked, traffic_signals, …) |
markings |
VARCHAR | the OSM crossing:markings tag (yes, zebra, no, …) |
painted |
BOOLEAN | are stripes painted: false for crossing=unmarked, crossing:markings=no, signals alone and no tag at all |
width |
DOUBLE | the zebra's width along the road, metres: the OSM width if tagged, else estimated from the width of the road it crosses (length): 0.4 × that, between 2.5 and 4 m |
length |
DOUBLE | the rectangle's size across the road, metres, from the first covered lane's outer edge to the last |
osm_id |
BIGINT | the OSM way or node |
geom |
GEOMETRY | the zebra's rectangle (a polygon): width along the road, length across it |
lane_crossing¶
duckOSM's own table (only in gmns_driving): one row per crossing for each driving lane it covers, so a zebra is painted lane
by lane and follows a curved lane. Only auto and bus (and bus,bike) lanes: a bike lane is no part of a zebra.
| Column | Type | Holds |
|---|---|---|
crossing_id |
VARCHAR | the crossing |
lane_id |
VARCHAR | the lane, or the connector_id of a lane_connector (road surface too: a zebra over the joint of two lanes is on both lanes and the connector between them) |
link_id |
BIGINT | its link (a connector's: that of the lane it leaves) |
start_lr, end_lr |
DOUBLE | the stretch of this lane the rectangle covers, metres from the lane's start: where the rectangle really overlaps the lane's own shape, so a lane that is short or ends inside the zebra still has its part (end_lr - start_lr is up to width) |
across_from, across_to |
DOUBLE | the extent of that overlap across the rectangle: metres from the rectangle's first side to the overlap's two sides. Stripes laid in that one frame stay continuous from lane to lane |
to_left |
BOOLEAN | which way across grows in this lane: true to the lane's left (in its direction of travel), false to its right |
To paint: cut the lane's geometry between start_lr and end_lr, and put in it the stripes (pitch stripe + gap from
across = 0) whose centre lies between across_from and across_to, each a strip along the lane (so parallel to it) at the lateral position that across and to_left give (a lane that is not square to the rectangle's axis: the part of it). Not in the standard, so never in the CSVs. See the
design.
link_along¶
duckOSM's own table (filled in gmns_walking, where the footpaths are; empty in the other networks): one row per footpath link for each road it runs along. A sidewalk
usually follows a street made of several edges, not one, so link's along_link_id (the road along the longest stretch) is only the first
of them. A road is in the route when the footpath runs along it for 4 m or more (its edge within 8 m of the footpath's and parallel to it, same level). Not in the
standard, so never in the CSVs.
| Column | Type | Holds |
|---|---|---|
link_id |
BIGINT | the footpath's link |
along_link_id |
BIGINT | the road's link: of gmns_walking or, along_mode driving, of gmns_driving (the same hash in every schema) |
along_mode |
VARCHAR | the network the road is in: walking or driving |
covered_m |
DOUBLE | about how many metres of the footpath run along this road (sampled every 2 m) |
gap_m |
DOUBLE | the metres between the two lines at their nearest |
curb_seg¶
A stretch of kerb with a rule. duckOSM writes on-street parking, where OSM tags parking:left /
parking:right (or parking:lane:<side>). Monaco has two.
| Column | Type | Spec | Holds |
|---|---|---|---|
curb_seg_id |
VARCHAR | ✅ key | <link_id>_<side> |
link_id |
BIGINT | ✅ | the link |
ref_node_id |
BIGINT | ✅ | the link's from_node_id: where distance 0 is |
start_lr, end_lr |
DOUBLE | ✅ | metres from ref_node_id: the whole link, 0 to its length |
regulation |
VARCHAR | ✅ | the OSM tag value, e.g. parallel |
width |
INTEGER | ✅ | empty |
signal_controller¶
Where traffic signals are. OSM has no signal timing, so there are no phases or plans: only the controllers.
| Column | Type | Spec | Holds |
|---|---|---|---|
controller_id |
VARCHAR | ✅ key | sig_<node_id> |
node_id |
BIGINT | ➕ | the signalised node |
control_type |
VARCHAR | ➕ | signal |
location¶
A point along a link: a crossing, a stop or give-way sign, a signal. It is how the standard places street furniture, and OSM has many of these (Monaco, driving: 1,521 crossings, 108 parking entrances, 100 give-way signs, 34 signals, 20 stop signs).
A row is made for each OSM node that lies on a link (within about 0.2 m of its shape) and carries one of these
tags: highway = crossing, bus_stop, give_way, stop, traffic_signals, toll_gantry,
mini_roundabout, speed_camera; traffic_calming; amenity=parking_entrance; railway=level_crossing.
A node that lies on several links (a junction) gets a row for each. A node beside the road, on no link, is
not written: that is why Monaco has no bus stops here, its stops are platform nodes next to the road. The
table needs the raw OSM tags, like lane details do: an area clipped from a bigger build has none.
| Column | Type | Spec | Holds |
|---|---|---|---|
loc_id |
VARCHAR | ✅ key | <osm_id>_<link_id> |
link_id |
BIGINT | ✅ | the link it lies on |
ref_node_id |
BIGINT | ✅ | the link's from_node_id |
lr |
DOUBLE | ✅ | metres from ref_node_id along the link's shape |
x_coord, y_coord |
DOUBLE | ✅ | longitude, latitude of the OSM node |
loc_type |
VARCHAR | ✅ | the OSM tag value: crossing, bus_stop, give_way, … (the standard recommends OSM names) |
z_coord |
DOUBLE | ✅ | metres, between the link's two end heights by distance along it (empty without heights, and on a bridge or in a tunnel) |
zone_id |
BIGINT | ✅ | 1, the zone |
gtfs_stop_id |
VARCHAR | ✅ | the GTFS stop_id, on the rows made from a GTFS feed (--gtfs, below); empty on the OpenStreetMap rows |
osm_id |
BIGINT | ➕ | the OSM node id; empty on GTFS rows |
name |
VARCHAR | ➕ | the stop's name: OSM's name tag, or the GTFS stop_name |
geom |
GEOMETRY | ➕ | the point |
Transit stops from a GTFS feed¶
The GMNS README recommends GTFS for transit and the spec has location.gtfs_stop_id to point at it. Give the feed to
duckosm gmns:
duckosm gmns monaco.duckdb --gtfs monaco_gtfs.zip # a .zip or an unzipped folder; repeat for several feeds
duckosm gmns monaco.duckdb --gtfs a.zip --gtfs b.zip --gtfs-max-m 40
Only stops.txt is needed. With stop_times.txt, trips.txt and routes.txt the stops are told apart by the
service that calls there (route_type): bus_stop, tram_stop, train_station, subway_station,
ferry_terminal, … in OpenStreetMap's words, and entrance for a station entrance (location_type 2). Stations (1)
and boarding areas (4) only group stops and are left out. Each stop becomes a location row:
- On the nearest link of each mode within 30 m (
--gtfs-max-m): in the driving schema the stops a bus calls at (a tram or train stop has no place on a road); in the walking and cycling schemas every stop, since people walk to a tram or train stop too. Of a two-way road's two links the stop is put on the one it is on the kerb side of (its right with right-hand traffic). A stop farther than that from any link of the mode is not written there (the log says how many). lris the distance along the link;x_coord,y_coordare the stop's own coordinates;gtfs_stop_idandnamecome from the feed.- An OpenStreetMap
bus_stopnode within 25 m of a GTFS bus stop is the same stop: its row is replaced by the GTFS one, which carries the id.
Example: the Monaco bus network's feed (Compagnie des Autobus de Monaco, on transport.data.gouv.fr, Licence Ouverte 2.0) has 97 stops; 95 of the 96 that a bus serves go onto a road link of Monaco (OpenStreetMap alone has none there: its bus stops are platform nodes beside the road).
zone¶
An area. duckOSM writes one zone, and every node (and every location) is in it. Its outline
is the area you built, when the database was built with a boundary (--boundary, an H3 cell, an admin area;
Tartu has one, Monaco does not); without one the zone has a name (the output file's) and no outline. A zone is
needed because GMNS tools such as Path4GMNS refuse a network whose
nodes are in no zone. lanestyle draws the outline.
| Column | Type | Spec | Holds |
|---|---|---|---|
zone_id |
BIGINT | ✅ key | 1 |
name |
VARCHAR | ✅ | the boundary's name if it has one, else the area (the output file's name without _gmns) |
boundary |
VARCHAR | ✅ | the outline as WKT POLYGON / MULTIPOLYGON; empty without a boundary |
super_zone |
VARCHAR | ∅ | empty |
geom |
GEOMETRY | ➕ | the same outline, native |
gmns_all: all modes in one¶
--combined adds a schema gmns_all with config, node, link, use_definition and use_group
only. A road used by several modes is one link (they share link_id), with allowed_uses listing
them all, e.g. auto,bike,walk. There are no lanes or movements here: they differ by mode, so they stay in
the per-mode schemas.
Meso and micro networks¶
--meso and --micro build networks for simulators. They are modelled on osm2gmns' meso and micro
networks (the same idea, and many of the same column names), but they are not its files and the standard
has no meso or micro tables. Their ids are built from the link_id, so they lead back to the road. Lengths
are in metres, measured on each link's own geometry, and every link starts and ends exactly at its nodes.
- Meso: each road becomes a section, shortened by about 6 m at both ends (at most 35 % of the road), and each legal turn a connector: a smooth curve from the end of the inbound section to the start of the outbound one.
- Micro: each lane is cut into cells of about 7 m. Cells in a row are linked; side-by-side cells of
adjacent lanes are linked by a lane change; and each lane pair of a movement is a turn link from the end
of the inbound lane to the start of the outbound lane, along the
lane_connectorcurve where there is one.
| Schema.table | Rows (Monaco) | One row is | Main columns |
|---|---|---|---|
meso_<mode>.meso_node |
6,184 | a section end | node_id (<link_id>u start, <link_id>d end), x_coord, y_coord, macro_node_id, macro_link_id, geom |
meso_<mode>.meso_link |
7,049 | a road section (meso_type = 'normal') or a turn connector ('movement') |
link_id (M<link_id> / X<ib>-<ob>), from_node_id, to_node_id, length, lanes, capacity (empty: it follows from the lanes), free_speed, facility_type, macro_link_id, movement_id, mvmt_txt_id, start_ib_lane, end_ib_lane, geometry, geom |
micro_<mode>.micro_node |
22,553 | the start of a cell of a lane | node_id (<lane_id>@<k>), x_coord, y_coord, lane_id, cell_k, macro_link_id, lane_no, geom |
micro_<mode>.micro_link |
27,123 | a cell, a lane change, or a turn (cell_type = normal, lane_change, movement) |
link_id (C<lane_id>#<k>, H…, X…), from_node_id, to_node_id, length, width, lane_no, macro_link_id, meso_link_id, geometry, geom |
Compared with osm2gmns 0.7.6 (run on the same Monaco PBF): osm2gmns names the road class link_type_name
and codes it as a number link_type, and codes cell_type as 1 / 2; we write the OSM highway in
facility_type and text in cell_type. It has macro_node_id, zone_id, activity_type, is_boundary
and a micro capacity that we do not; we have geom, meso_type, the lane ranges, width and cell_k that it
does not. Its network is also coarser (Monaco: 1,280 links against our 3,092, since it merges junctions).
What is not written, and why¶
The standard has 25 tables. duckOSM writes 12 of them (everything above except lane_connector, crossing, lane_crossing and link_along, which are
its own) and leaves out the other 13:
| Tables | Why |
|---|---|
segment, segment_lane (a lane added or dropped part-way along a link) |
not needed: an edge is cut at every node that two ways share or where a way ends, so wherever OSM's lanes changes, the edge is already cut |
link_tod, lane_tod, movement_tod, segment_tod, segment_lane_tod, time_set_definitions (time-of-day changes) |
OSM's *:conditional tags are on a fraction of one percent to 7 % of ways, and are mostly parking and loading rules, not travel lanes |
signal_timing_plan, signal_timing_phase, signal_phase_mvmt, signal_coordination, signal_detector |
OSM has no signal timing, phases or detectors; any value would be invented |
Checking the output¶
The CSVs are validated against the standard's own schemas on every test run, and duckosm gmns --check
tests that the values agree with each other (a link starts and ends on its nodes, length is its shape's,
lane numbers run 1..n, a movement turns where its inbound link ends and uses lanes that exist). See
Conformance.