Measures protected cycling infrastructure in 21 European cities from OpenStreetMap, year by year since 2018, with every rule and number reproducible.
Auf Deutsch: Ergebnisse und Hintergründe für Berlin im Zählwerk Berlin Datalab.
Explore the results · Methodology · Decisions · Datalab (Deutsch)
Note
All lengths are mapped km: what OpenStreetMap contributors have mapped, not what a city has built (ADR 0006). Mapping is more complete in some cities and years than in others, so a gap between two cities can be a gap in the map.
Published cycling rankings disagree, and most of them can't be re-run: some mix figures the cities submit with OSM data and publish no code, others are expert scores. velometer is a pipeline anyone can run. It produces per-city numbers with explicit definitions, and it shows why rankings disagree (definitions, denominators, boundaries) instead of hiding it.
- Classifies every side of every road in OSM into one of five tiers, from A (separated cycle
track) to E (cycle street or bus lane). The rules derive from the ECF's QECIO tag logic and live
as data in
config/classifier/; a rule change is a new classifier version. - Measures lengths in each city's projected coordinate system, never in degrees, inside two boundaries: the city proper (LAU) and its functional urban area (FUA).
- Goes back in time with yearly snapshots on 1 January from 2018, cut from OSM's full history, plus a current snapshot. Every result row records its classifier version and OSM timestamp.
- Compares its own numbers with official city figures and published rankings, which stay a separate layer and are never blended in (ADR 0001).
- Publishes a static, bilingual reader site: per-city pages with charts and tier maps, a compare view, stories, sources, and CSV and GeoJSON downloads, each carrying its attribution.
The headline metric is A+B km per 100 km of main roads (separated tracks plus shared foot and cycle paths, against the main road network without motorways; ADR 0003).
The compare view: headline metric, city proper, OSM snapshot of 3 October 2026, classifier v4.
You need uv and osmium-tool
(apt install osmium-tool).
git clone https://github.com/senzelden/velometer.git && cd velometer
uv sync
uv run velometer init # SQLite schema and the 21 cities
uv run velometer run --cities ljubljana # download, clip, classify, store
uv run velometer results # one row per cityThe run takes about a minute, most of it the Slovenia download. results then prints (abridged):
city osm_date headline ecf_qecio clean_cities A_km B_km C_km D_km E_km main_km
ljubljana 2026-10-27 72.7 41.8 20.7 205.7 36.7 0.0 101.7 1.8 333.2
headline is A+B km per 100 km of main roads; ecf_qecio and clean_cities are the same city
measured by those rankings' definitions.
A full run downloads one Geofabrik extract per country. The yearly history needs a free OSM
account for Geofabrik's full-history files (velometer auth). uv run velometer --help lists
everything else: reconciliation with external figures, time series, trust diagnostics, counters,
plan networks and the static export (velometer app export).
| Cities | Amsterdam, Barcelona, Berlin, Bordeaux, Bratislava, Budapest, København, Helsinki, Kraków, Ljubljana, Lyon, Madrid, Oslo, Paris, Praha, Stockholm, Strasbourg, Utrecht, Warszawa, Wien, Wrocław |
| Infrastructure | OpenStreetMap via Geofabrik extracts, current and full history |
| Boundaries | Eurostat GISCO, LAU 2024 and FUA |
| Population | Eurostat, INSEE (France), INE (Spain) |
| Context | Open bike-counter data (Berlin, Paris, Wien, Helsinki, Madrid, Barcelona, Oslo), Berlin's cycling plan network |
| Comparison only | Official city figures, Clean Cities 2025, Copenhagenize, ECF QECIO, BikeNetKit; cited with source and date, never redistributed |
| Basemap | Protomaps extract, served from our own host |
- Mapped, not built. A rise can be new infrastructure or new mapping. velometer diagnoses
mapping effort per country (
velometer trust) instead of correcting it away. - Flagged years. Jumps that an automatic check catches are flagged in every chart, and each flag carries a written explanation.
- Boundaries matter. The city proper and the wider urban area tell different stories; both are computed, the city proper is the default.
- Definitions are presets. "Protected" means different tier sums in different rankings; the
presets in
config/presets.yamlreproduce them side by side.
The full method, generated from the config and the database and checked for staleness, is in
docs/methodology.md. The decisions behind it are the
ADRs.
uv run ruff format --check . && uv run ruff check . && uv run pytest \
&& node --test "src/velometer/app/static/js/tests/*.test.mjs"Network tests are marked slow and deselected by default. Deployment of the static edition is in
docs/deploy.md. The images in this README are rendered from the site by
tools/readme_shots.py.
Use GitHub's "Cite this repository" button (CITATION.cff), and name the
classifier version and OSM snapshot of the figures you cite: the Cite block under each chart on
the site gives both, ready to copy.
- Software, all versions: doi.org/10.5281/zenodo.23945133
- Data, all snapshots: doi.org/10.5281/zenodo.23932324
| Part | Licence |
|---|---|
| Code and classifier rules | MIT |
| Derived data: tier lengths, metrics, CSV and GeoJSON downloads | ODbL 1.0, map data © OpenStreetMap contributors |
| Administrative boundaries | © EuroGeographics for the administrative boundaries (non-commercial use) |
| Counter data | Each publisher's licence (dl-de-zero-2.0, ODbL, CC BY 4.0, NLOD), credited on every page that shows it |
| External comparison figures | Cited with source URL and retrieval date, not relicensed |
Each licence, its credit line and where it appears are recorded in ADR 0008.