Liveability Baseline · Layer 1

Madrid Transit Access

Methodology, parameters and validation for run aad4ae941323, service day 2026-07-29.

AuthorDmitry Ustimov
Issued2026-08-20
Document versionaad4ae941323 the run it describes

1  Run identity

Every figure in this document belongs to one run. A run is identified by a content hash of its inputs, its parameters and the source code that produced it, so two runs of different code cannot claim the same identity, and a re-run whose output changed cannot silently reuse an old one.

Runaad4ae941323
Vintagetermtime-2026
Service day2026-07-29
Code version9962fb32ab2f digest of the pipeline source
Git commitc8cc8c31
Specification5195dd40ff93d3c87abe7d49…
Parameter settransit_v1
Input lock88e2237af8826e6407d1946b…
Licence registerb9ab15310e45bd9e9b42722b…
Reach engineraptor_v1
Rows written6,305

The licence register hash is recorded but is deliberately not part of the run identity: correcting a licence changes what may be done with the output, never the output itself, and folding it into identity would force a full recompute to record a URL.

2  What this layer measures, and what it does not

Every figure describes scheduled service: what the operators' published timetables say runs, on one representative service day. That is a deliberate choice. Accessibility, planning and site-selection questions ask what service a location is provided with, and a schedule-based answer is stable, reproducible from public data, and recomputable by anyone who doubts it.

It does not measure whether services ran on time. No realtime feed, vehicle position or arrival observation is used anywhere in this layer. Punctuality is a different question and needs a different input. A reader who wants reliability will not find it here, and no figure in this document should be presented as if it answered that.

The unit of analysis is a 250 m cell of the INSPIRE reference grid in EPSG:3035, the statistical grid European reporting already uses, so results align with other layers and with Eurostat geographies without resampling.

Feed currency, stated plainly. 1 of 6 timetable feeds in this run had passed the end of their published validity before the service day: gtfs_metro_madrid, valid to 2026-05-27, 63 days before. The operator had not republished at the time the inputs were locked, so the most recent published timetable was used and the gap is recorded here rather than left for a reader to derive from §7.1. Where a lapsed feed still described unchanged service the effect is nil; where service changed in the interval, figures for that operator are out of date by that margin.

3  Method

3.1 Walk access

Each cell takes an analysis point, and walking times are computed over a pedestrian network derived from OpenStreetMap rather than as straight-line distance. Two quantities are published per cell: time to the nearest stop of any kind, and time to the nearest frequent stop, the second computed separately for each time window because a stop that is frequent at 08:00 need not be at 21:00.

3.2 Departures per hour

For each stop and route, scheduled departures within a window are counted and divided by the window's length. Where a feed encodes service as headways rather than as individual trips, the headway is converted arithmetically. The figure is per direction: a rider travels one way, so their service is that direction's, not the sum of both. A cell's figure aggregates the stops within walking range.

3.3 Access index (PTAL)

The access index follows Transport for London's Public Transport Accessibility Level method. Each stop within walking range contributes an equivalent doorstep frequency, EDF = 30 / TAT, where TAT is total access time — walk time plus half the average headway. Contributions are weighted and summed, so the index reads as “departures per hour effectively arriving at this doorstep”. It is reported against TfL's published band thresholds rather than a scale invented here, which is the point of using an established method: the number is comparable to a body of existing practice.

3.4 Population reach

Reach columns count residents reachable within 30, 45 and 60 minutes door to door, combining walking, waiting and in-vehicle time. Population is apportioned from the Global Human Settlement Layer's modelled residential grid.

3.5 Population, and why our figure is higher than the census

Residential population comes from the Global Human Settlement Layer, a modelled 250 m grid, apportioned to cells. It is not a census count, and it does not match one:

Modelled (GHS-POP), used for every figure here3,805,757
Registered (INE), carried as a cross-check3,332,035
Difference+14.2%

The modelled figure is the higher one, and that is the expected direction. A settlement-layer model estimates people present from built form and survey data; a municipal register counts people who have registered. Residents who have not registered, or who registered elsewhere, appear in the first and not the second.

Which one drives the map: the modelled figure, because it is the only one available per 250 m cell — a register total is a municipal number and cannot be apportioned to a grid without inventing a distribution. Every population-weighted statement in this layer, including the headline median and the share of residents in each class, is therefore modelled. The registered total is recorded beside it in every run so the gap is measurable rather than discovered.

Where this matters: absolute headcounts carry the model's error, so "N residents are worse than 10 minutes from frequent service" should be read as an estimate. Comparisons between cells are far more robust, because the same model applies to both.

3.6 Null policy

A real zero, an absence of service and a data failure are three different states and are kept distinguishable. A cell with no nearby stop is served by nothing; a cell whose inputs failed carries an explicit reason rather than a zero. Collapsing these would make a blank cell unreadable — the reader could not tell good news from missing data.

4  Parameters and where each came from

Every parameter carries one of two labels, and the distinction is not cosmetic. A specification value was transcribed from the customer's own written specification, whose hash is recorded in §1. A plan default was chosen by us: it is changeable without code, and it is never presented as though the specification required it. A parameter that is neither is a defect, and the labels below are read from the run's own manifest, not asserted here.

6 specification-derived, 32 plan defaults.

4.1 Specification-derived

ParameterValue in this runSource
anchors.walk_freq_mat_0 800.0; at_100 400.0spec_§6
family_a_buffer_m12000spec_§5
frequent_dep_hr4spec_§3_A2
qa_conformance.window_monotonic_order—spec_§3_A4
reach_thresholds_min30, 45, 60spec_§2
stop_count_radius_m800spec_§3_A3

4.2 Plan defaults — ours, and changeable

ParameterValue in this runSource
access_walk_cap_m1200plan_default
anchors.freq_dep_hrat_0 0.0; at_100 12.0plan_default
anchors.opps_reference100000plan_default
anchors.walk_freq_minat_0 10.0; at_100 5.0plan_default
dep_hr_clipNoneplan_default
destination_buffer_m20000plan_default
dijkstra_chunk_bytes2.09715e+08plan_default
gtfs_fixes.carry_frequency_direction_id—plan_default
gtfs_fixes.prefer_stop_times_for_per_trip_frequencies—plan_default
gtfs_fixes.use_calendar_service_date—plan_default
line_paramscontained 0.8; grid_m 30.0; min_stops 5plan_default
max_snap_m400plan_default
max_transfer_neighbours_per_stop—plan_default
max_transfer_walk_m—plan_default
min_transfer_seconds—plan_default
mode_tiers—plan_default
populated_min_pop1plan_default
ptal_windowday_kind wednesday; t0 08:00:00; t1 09:00:00plan_default
qa_conformance.viewer_class_count—plan_default
qa_plausibility.dep_hr_ceiling—plan_default
qa_plausibility.feed_dep_stop_ratio_max—plan_default
qa_plausibility.feed_share_max—plan_default
qa_plausibility.frequent_share_band—plan_default
qa_plausibility.window_ordering_max_violation_share—plan_default
reach_chunk_bytes2.09715e+08plan_default
routing_network_margin_m5000plan_default
service_daywednesdayplan_default
stop_identitygrid_m 30.0; parent_station Trueplan_default
walk_speed_m_per_min80plan_default
windows.ampeakday_kind wednesday; t0 07:00:00; t1 09:00:00plan_default
windows.eveningday_kind wednesday; t0 20:00:00; t1 22:00:00plan_default
windows.sundayday_kind sunday; t0 10:00:00; t1 14:00:00plan_default

5  Validation against the operators' own figures

Internal tests check this pipeline against itself, which is the weakest kind of assurance available: a reference implementation written by the same author from the same assumptions can share a defect with the thing it validates. The comparison below is external. Every published figure comes from Metro de Madrid, EMT, Renfe, Metros Ligeros de Madrid or CRTM — recorded with its URL, a verbatim quote and the date it was retrieved, and hash-pinned so the comparison cannot be made to pass by editing what an operator said.

Comparisons41
Within the published range33
Outside it8 — 5 higher than published, 3 lower
Externally sourced32 — the rest compare against the operator's own feed, which checks our parser rather than the data

Operators publish a range — “every 7 to 10 minutes” — rather than a single figure, so a result counts as agreeing when it falls anywhere inside the published range. Bands are rounded conservatively: lower bounds up, upper bounds down, so rounding can never move one of our values into agreement.

5.1 By mode

ModeComparedWithin range Ratio range
EMT bus107 / 100.625–1.026
Metro Ligero32 / 30.723–1.583
Metro de Madrid2824 / 280.89–1.317

5.2 Every disagreement, named

These are unresolved. They are published here rather than omitted, because a validation section that shows only agreement tells a reviewer nothing they can check.

OperatorLineWhereWindow PublishedOursRatio
EMT Madrid123Plaza de los Metales, Butarque (EMT stop 5074)ampeak3.0–5.02.50.625×
EMT Madrid85Plaza de los Metales, Butarque (EMT stop 5074)ampeak6.0–8.575.00.686×
Metros Ligeros de MadridML1Pinar de Chamartín (Metro Ligero ML1)sunday2.5–3.54.751.583×
Metro de Madrid5CANILLEJAS (Metro line 5 — single-line station)evening8.0–10.910.9091.154×
Metro de Madrid5CANILLEJAS (Metro line 5 — single-line station)sunday7.5–8.68.7981.093×
Metro de Madrid4PROSPERIDAD (Metro line 4)evening9.2–10.912.01.194×
Metro de MadridRPRINCIPE PIO (Metro line R)sunday7.1–8.09.9471.317×
EMT Madrid172Avenida Camino de Santiago (EMT stop 1841, line 172)ampeak3.6–4.63.50.854×

6  Validating the model, not only its input

§5 checks departures per hour against what operators publish — that validates the input. These two check what is built on it.

6.1 Walk times, against an independent router

Stale evidence. This evidence was measured on run 75fd42f29bfe. This document describes run aad4ae941323. The figures below are that other run's.

Every walk figure comes from our own router over OpenStreetMap footpaths. Valhalla — a separate implementation — was asked for the same origin and the same destination stop, over the same extract, at the same walking speed, so a disagreement is a router disagreement and nothing else.

Walks compared118
Median difference0.39 min
Within 1 minute84
Within 2 minutes100
Median ratio1.031

0 could not be routed by Valhalla, and 2 were excluded as routing artefacts — walks either router put beyond an hour to a bus stop, which is not a walk anyone takes. They are listed in the evidence file rather than averaged into the figures above.

6.2 Door-to-door journeys, against the operators' own planners

Thirteen journeys across Madrid were recorded from EMT's and Renfe's own planners before this pipeline produced any reach output. A corpus compiled after seeing your own answer is not evidence; this one predates ours.

GroupJourneysMedian ratio Within 25%
Unaffected by engineering works5 0.9484
Affected by the Atocha works6 1.2833

Two things this comparison is not. The planners answer “if I leave now”; we answer “typically”, as a percentile over a departure window, which makes us slower on long, infrequent journeys by construction. And the works grouping is confounded — every affected pair is also among the longest and most peripheral — so it separates neither. No pass/fail band is declared: a journey time is a point, not a published range, and choosing a tolerance after seeing the numbers is choosing one that passes.

7  Sensitivity to the parameters we chose

§4 says which parameters are ours. This says whether they matter. Each row moves ONE parameter and leaves the rest identical; every figure is the population-weighted mean absolute change, because a cell with four residents moving 300% is not a finding and one with 4,000 residents moving 5% is.

ParameterMoved byAccess index Walk to any stopDepartures/hr
walk_speed_m_per_min-10%4.30%11.11%0.00%
walk_speed_m_per_min+10%3.90%9.09%0.00%
access_walk_cap_m-10%0.00%0.00%0.38%
access_walk_cap_m+10%0.00%0.00%0.00%
frequent_dep_hr-25%0.00%0.00%5.67%
frequent_dep_hr+25%0.00%0.00%34.98%
max_snap_m-50%0.05%0.00%0.11%
stop_count_radius_m-10%0.00%0.00%0.00%
stop_count_radius_m+10%0.00%0.00%0.00%

Scope: Family A only (walk, frequency, access index, stop count). Family B (opps_*) is not swept — it would cost a full RAPTOR sweep per arm.

The largest movement, read plainly. walk_speed_m_per_min is a plan_default — a value we chose, not one the specification fixed. Its value in this run is 80.0, and moving it by -10% moves the access index by 4.30% of its value, population-weighted. A reader who thinks that value should be different should read every access-index figure in this document as carrying about that much movement per -10% of disagreement.

8  Quality gate

A run records the checks that ran and what each returned. The gate distinguishes a check that passed from one that was never evaluated, because “not evaluated” reported as a pass is how an unverified number reaches a client.

Statuspass_with_unevaluated
Checks recorded51
CheckSeverityOutcome
a1_cap_not_binding_for_anchorswarnnot_evaluated
blend_ordering_stable_under_reweightwarnnot_evaluated
classification_saturationwarnpass
dep_hr_plausibility_ceilingwarnpass
external_journey_planner_spotcheckwarnnot_evaluated
feed_contribution_reconciliationwarnpass
frequent_by_route_summingwarnnot_evaluated
frequent_share_plausiblewarnpass
n_clipped_is_meaningfulwarnnot_evaluated
norm_opps_discriminateswarnnot_evaluated
opps_departure_window_stabilitywarnnot_evaluated
opps_plausibility_central_gt_peripheralwarnnot_evaluated
opps_reference_is_finalwarnnot_evaluated
ptal_band_tracks_walk_and_freqwarnnot_evaluated
stop_count_reconciles_to_networkwarnnot_evaluated
summary_digest_driftwarnnot_evaluated
window_ordering_dep_hrwarnwarn
window_ordering_walk_freqwarnnot_evaluated
a4_stop_is_a2_stophardnot_evaluated
all_destinations_on_networkhardnot_evaluated
anchors_are_not_provisionalhardpass
aoi_population_reconciles_inehardnot_evaluated
composite_rank_extremeshardnot_evaluated
declared_not_null_columns_populatedhardpass
declared_walk_cap_conformancehardpass
dep_hr_matches_published_timetablehardnot_evaluated
edge_buffer_flaggedhardnot_evaluated
every_mode_reports_departureshardpass
frequent_set_headway_verifiedhardnot_evaluated
frequent_state_partition_completehardnot_evaluated
known_corridor_mode_presenthardnot_evaluated
mode_present_implies_stop_in_catchmenthardnot_evaluated
mode_tier_score_matches_modeshardnot_evaluated
net_island_handled_not_infinitehardnot_evaluated
no_data_has_declared_causehardnot_evaluated
no_nonfinite_publishedhardpass
opps_threshold_monotonichardnot_evaluated
provenance_completenesshardnot_evaluated
ptal_basis_is_networkhardnot_evaluated
ptal_hand_calculationhardnot_evaluated
ptal_walk_consistent_with_a1hardpass
row_grain_unique_populated_cellshardnot_evaluated
schema_conformancehardnot_evaluated
seasonal_pair_unconfoundedhardnot_evaluated
service_day_within_all_feed_validityhardnot_evaluated
service_state_partition_completehardnot_evaluated
walk_any_le_walk_freqhardnot_evaluated
walk_ge_crowfly_lower_boundhardnot_evaluated
walk_monotonicity_lipschitzhardnot_evaluated
window_frequency_monotonicityhardpass
zero_iff_no_servicehardnot_evaluated

9  Sources, licences and required attribution

Each input carries a written licence position, held once in a register whose hash is recorded with the run.

1 source has no settled redistribution position: aoi_polygon. Until settled, this layer is provisional for redistribution: the figures stand, and the terms on which a third party may receive them do not. What each open position blocks is stated against that source in the table below. It is said here rather than left to be inferred from a table.

9.1 Timetable feeds in this run

FeedValidityLicence
gtfs_crtm_interurbanos2026-06-13 → 2027-07-24https://www.crtm.es/licencia-de-uso
gtfs_crtm_metro_ligero2026-06-22 → 2027-06-22https://www.crtm.es/licencia-de-uso
gtfs_crtm_urban_bus2026-06-13 → 2027-07-24https://www.crtm.es/licencia-de-uso
gtfs_emt_madrid2026-07-16 → 2026-12-31https://mobilitylabs.emtmadrid.es/sip/terms-of-use
gtfs_metro_madrid2025-05-27 → 2026-05-27
lapsed 63 days before the service day
https://www.crtm.es/licencia-de-uso
gtfs_renfe_cercanias2026-07-17 → 2026-08-15https://creativecommons.org/licenses/by/4.0/

9.2 What each source permits, and the credit it requires

SourceWhat may be done with it Required attribution
aoi_polygonNo settled position. The boundary defining the study area has not been fixed: an OpenStreetMap administrative relation is in use, against the alternative of an official INE/IGN municipal boundary. No licence can be recorded until that choice is made.—
ghspop_madridCopy, redistribute, adapt and build upon for any purpose including commercial use, with attribution. No share-alike.Pesaresi, M., Schiavina, M., Politis, P., Freire, S., Krasnodębska, K., Uhl, J. H., ... Kemper, T. (2024). Advances on the Global Human Settlement Layer by joint assessment of Earth Observation and population survey data. International Journal of Digital Earth, 17(1). https://doi.org/10.1080/17538947.2024.2390454
gtfs_crtm_interurbanosCell metrics convey no source record and ship as processed data (datos explotados); conveyed feed records (the stops/lines overlay) stay viewer-only or ship under CRTM terms.Powered by CRTM — with a link to https://www.crtm.es/, noting that the data is processed (datos explotados)
gtfs_crtm_metro_ligeroCell metrics convey no source record and ship as processed data (datos explotados); conveyed feed records (the stops/lines overlay) stay viewer-only or ship under CRTM terms.Powered by CRTM — with a link to https://www.crtm.es/, noting that the data is processed (datos explotados)
gtfs_crtm_urban_busCell metrics convey no source record and ship as processed data (datos explotados); conveyed feed records (the stops/lines overlay) stay viewer-only or ship under CRTM terms.Powered by CRTM — with a link to https://www.crtm.es/, noting that the data is processed (datos explotados)
gtfs_emt_madridCommercial redistribution of derived data is permitted with attribution. No share-alike clause exists in these terms, so EMT imposes no constraint on an exclusive deliverable.Powered by EMT de Madrid — with a link to https://www.emtmadrid.es/
gtfs_metro_madridCell metrics convey no source record and ship as processed data (datos explotados); conveyed feed records (the stops/lines overlay) stay viewer-only or ship under CRTM terms.Powered by CRTM — with a link to https://www.crtm.es/, noting that the data is processed (datos explotados)
gtfs_renfe_cercaniasCommercial use, redistribution and derived works permitted with attribution. No share-alike.Renfe, CC BY 4.0
osm_castilla_la_manchaThe walking graph derived from OpenStreetMap is never conveyed: it is an intermediate that computes walk times and is not published, not in the GeoPackage and not in the viewer payload. What ships is a scalar in minutes per 250 m cell, carrying no OSM identifier, geometry or attribute. ODbL 4.4 attaches to a Derivative Database that is PUBLICLY USED, and 4.5(b) states that using the Database to create a Produced Work does not create a Derivative Database for 4.4's purposes. The shipped basemap geometry (roads.bin, buildings.bin, places.json) is a separate artefact already accepted as a Derivative Database offered onward under ODbL.© OpenStreetMap contributors
osm_castilla_y_leonThe walking graph derived from OpenStreetMap is never conveyed: it is an intermediate that computes walk times and is not published, not in the GeoPackage and not in the viewer payload. What ships is a scalar in minutes per 250 m cell, carrying no OSM identifier, geometry or attribute. ODbL 4.4 attaches to a Derivative Database that is PUBLICLY USED, and 4.5(b) states that using the Database to create a Produced Work does not create a Derivative Database for 4.4's purposes. The shipped basemap geometry (roads.bin, buildings.bin, places.json) is a separate artefact already accepted as a Derivative Database offered onward under ODbL.© OpenStreetMap contributors
osm_madridThe walking graph derived from OpenStreetMap is never conveyed: it is an intermediate that computes walk times and is not published, not in the GeoPackage and not in the viewer payload. What ships is a scalar in minutes per 250 m cell, carrying no OSM identifier, geometry or attribute. ODbL 4.4 attaches to a Derivative Database that is PUBLICLY USED, and 4.5(b) states that using the Database to create a Produced Work does not create a Derivative Database for 4.4's purposes. The shipped basemap geometry (roads.bin, buildings.bin, places.json) is a separate artefact already accepted as a Derivative Database offered onward under ODbL.© OpenStreetMap contributors

10  Known limitations