Skip to content

Reading OSM data

OpenStreetMap (OSM) is the data source for the road network, cycling infrastructure, and destination points of interest. This page explains how bikescore-bna reads and prepares OSM data before the analysis begins.

What OpenStreetMap is

OpenStreetMap is a free, community-maintained geographic database. Millions of contributors around the world map roads, buildings, amenities, parks, and hundreds of other features. bikescore-bna uses OSM for:

  • Road geometry — the actual line shapes of every street, path, and cycleway
  • Road attributes — speed limits, lane counts, one-way status, cycling infra tags
  • Points of interest — schools, hospitals, grocery stores, transit stops, parks

OSM data is global and free, which means bikescore-bna can run on any city in the world without requiring proprietary data sources.

PBF files and where they come from

OSM publishes raw data as PBF (Protocol Buffer Format) files — compact binary files that can contain all features in a region. State and national extracts are available from Geofabrik. For example, the entire US state of Virginia is a single PBF file of about 500 MB.

bikescore-bna reads PBF files directly using osmium, a Python binding to the fast osmium C++ library. The parse stage makes a single pass through the file, collecting all relevant features in one sweep.

The study area and buffer

bikescore-bna analyses a city, not an entire region. Before the parse stage even runs, the input PBF has already been clipped to the city boundary by the acquire step: osmium extracts the city from the regional PBF using --strategy=complete_ways, which keeps any way that has at least one node inside the exact boundary polygon — including the complete geometry of cross-boundary roads with their outside nodes.

The parse stage reads this already city-clipped PBF. Every way it produces therefore has at least one node inside the boundary by construction.

The buffer zone itself is established by the osmium complete_ways strategy: cross-boundary roads are kept in full, providing road geometry beyond the city edge. This gives the routing graph the surrounding context it needs — a cyclist near the boundary can route through nearby roads outside the city. The segment stage later splits these cross-boundary roads at the exact boundary crossing point and removes outside dead-end chains, leaving clean in/out sub-segments.

What bikescore-bna reads from OSM

Roads (highway ways)

Any OSM way with a highway=* tag is extracted. This includes motorways, residential streets, cycleways, footways, paths, and tracks. Ways with a bicycle=* tag are also extracted even if they lack a highway tag.

For each way, bikescore-bna stores:

  • Geometry — the LineString in WGS84 (EPSG:4326)
  • Node IDs — the ordered list of OSM node IDs forming the way
  • Tag columns — speed, lanes, width, cycling infrastructure, parking, one-way, and all tags needed for the classify stage

Ways where bicycle=no AND highway=path are removed by parse — footpaths that explicitly prohibit cycling are excluded from the routing network.

Intersection nodes

Every node referenced by a highway way is stored in nodes_df for topology building in the segment stage. Additionally, nodes that carry traffic-control attributes are flagged directly from their OSM tags:

Attribute OSM condition
signalized highway=traffic_signals
stop highway=stop AND stop=all
rrfb highway=crossing AND flashing_lights ∈ {yes,button,always,sensor}
island highway=crossing AND (crossing=island OR crossing:island=yes)

These flags are used later by the stress stage to compute intersection stress (signalised intersections are lower stress to cross).

Destination POIs

bikescore-bna scores cycling access to 13 categories of destination — schools, hospitals, grocery stores, parks, and others. During the same osmium parse pass, every node and closed way (polygon) is checked against the destination type matchers.

A single OSM feature can match multiple destination types simultaneously. For example, a chemist (shop=chemist) can match both pharmacies and retail. The parse stage collects all matches without breaking — exclusions and deduplication are applied later in the destinations stage.

Configuration

The parse stage is configured through BNAConfig:

from bikescore_bna.config import BNAConfig

config = BNAConfig.with_defaults()
# Override buffer distance (default 2680m)
config.max_trip_distance = 3000

# Add a custom destination type to the parser
from bikescore_bna.destinations import DestinationType, OsmMatcher
config.destinations.register(DestinationType(
    name="libraries",
    display_name="Public Libraries",
    node_matchers=[OsmMatcher({"amenity": "library"})],
    area_matchers=[OsmMatcher({"amenity": "library"})],
    clustering_tolerance_m=100,
    scoring_category="recreation",
))

Implementation

OSM parsing is implemented in bikescore-bna/stages/parse.py using a single osmium.SimpleHandler subclass.

Relationship to brokenspoke-analyzer

The SQL scripts this stage replaces — and any points where the output intentionally differs — are catalogued in the Differences from brokenspoke-analyzer section.