Data

Data exists

Public transport operations 🚍
General Transit Feed Specification (GTFS)

Data exists

Public transport operations 🚍
General Transit Feed Specification (GTFS)

Data exists

🛣 Infrastructure + 🚍 PT routes
OpenStreetMap (OSM)

Data exists

🚍 Public transport operations
General Transit Feed Specification
(GTFS/GTFS-RT)

Total number of GTFS feeds indexed by Transitland, since 2016

🛣 Infrastructure + 🚍 PT routes
OpenStreetMap
(OSM)

Growth of public transport routes and GTFS integration in OpenStreetMap

Data exists, but…

Growth of public transport routes and GTFS integration in OpenStreetMap

Data exists, but…

… associating GTFS and OSM data requires conflation

Combination of “geographic information from overlapping sources so as to retain accurate data, minimize redundancy, and reconcile data conflict” (Longley et al. 2001)


👀 Some authors have proposed methods to combine them by using routing algorithms (Hubsch 2026; Gabriele 2026; Poletti 2016; Bast and Brosi 2018)


🔎 However, they ignore the database of already existing OSM route relations, being computational demanding and more prone to errors

R package focused on harmonising GTFS and OSM data to enable integrated public transport modeling

Public transport operations

OSM road network

GTFShift

GTFShift::osm_shapes_match_routes()

Matching of GTFS shapes, \(i\), with OSM relations, \(j\), based on geometrical correspondence using a closeness heuristic

\[C(i, j)\]

that aims to minimize:

GTFShift

GTFShift::osm_shapes_match_routes()

Matching of GTFS shapes, \(i\), with OSM relations, \(j\), based on geometrical correspondence using a closeness heuristic

\[C(i, j) = d(\text{init}_{GTFS, i}, \text{init}_{OSM, j}) + d(\text{fin}_{GTFS, i}, \text{fin}_{OSM, j})\]

that aims to minimize:

  • distance between departure and arrival stops, \(d(\text{init}_{GTFS, i}, \text{init}_{OSM, j}) + d(\text{fin}_{GTFS, i}, \text{fin}_{OSM, j})\)

GTFShift

GTFShift::osm_shapes_match_routes()

Matching of GTFS shapes, \(i\), with OSM relations, \(j\), based on geometrical correspondence using a closeness heuristic

\[C(i, j) = d(\text{init}_{GTFS, i}, \text{init}_{OSM, j}) + d(\text{fin}_{GTFS, i}, \text{fin}_{OSM, j}) + |L_{GTFS, i} - L_{OSM, j}|\]

that aims to minimize:

  • distance between departure and arrival stops, \(d(\text{init}_{GTFS, i}, \text{init}_{OSM, j}) + d(\text{fin}_{GTFS, i}, \text{fin}_{OSM, j})\)

  • length difference, \(|L_{GTFS, i} - L_{OSM, j}|\)

GTFShift

GTFShift::osm_shapes_match_routes()

Matching of GTFS shapes, \(i\), with OSM relations, \(j\), based on geometrical correspondence using a closeness heuristic

\[C(i, j) = d(\text{init}_{GTFS, i}, \text{init}_{OSM, j}) + d(\text{fin}_{GTFS, i}, \text{fin}_{OSM, j}) + |L_{GTFS, i} - L_{OSM, j}| + \frac{L_{GTFS, i}}{N_{stops, i}} \cdot |N_{stops, i} - N_{stops, j}|\]

that aims to minimize:

  • distance between departure and arrival stops, \(d(\text{init}_{GTFS, i}, \text{init}_{OSM, j}) + d(\text{fin}_{GTFS, i}, \text{fin}_{OSM, j})\)

  • length difference, \(|L_{GTFS, i} - L_{OSM, j}|\)

  • number of stops difference, \(\frac{L_{GTFS, i}}{N_{stops, i}} \cdot |N_{stops, i} - N_{stops, j}|\)

GTFShift

GTFShift::osm_shapes_match_routes()

Matching of GTFS shapes, \(i\), with OSM relations, \(j\), based on geometrical correspondence using a closeness heuristic

\[C(i, j) = d(\text{init}_{GTFS, i}, \text{init}_{OSM, j}) + d(\text{fin}_{GTFS, i}, \text{fin}_{OSM, j}) + |L_{GTFS, i} - L_{OSM, j}| + \frac{L_{GTFS, i}}{N_{stops, i}} \cdot |N_{stops, i} - N_{stops, j}|\]

Shape \(S_i\) is associated with the OSM route \(O_{j^*}\) that minimizes its value:

\[j^* = \operatorname{argmin}_{j} C(i, j)\]

Case study

Lisbon Metropolitan Area

 

 

Lisbon urban buses
263 GTFS shapes
1.235 trips at peak

 

Barreiro urban buses
60 GTFS shapes
111 trips at peak

 

Cascais urban buses
130 GTFS shapes
136 trips at peak

 

Urban and suburban buses
1.533 GTFS shapes
2.900 trips at peak

 

Case study: results


Operator Matched shapes (%) Matched shapes peak (%) Matched trips (%) Matched trips peak (%)
Carris 99.62 100 99.96 100
TCB 91.66 100 96.78 100
MobiCascais 94.62 96.51 97.27 96.61
Carris Metropolitana 98.96 99.53 99.36 99.76


High match rate, that increases when considering peak hour (08:00)

Case study: results


Operator Heuristic length (m)¹ Heuristic points (m)¹ Heuristic stops (n)¹
Carris 278.9 ± 1300.6 45.4 ± 176.7 0.38 ± 0.70
TCB 146.6 ± 202.4 84.0 ± 359.2 0.11 ± 0.31
MobiCascais 130.5 ± 447.8 161.1 ± 866.6 0.46 ± 1.01
Carris Metropolitana 314.0 ± 466.5 72.65 ± 649.3 0.40 ± 0.93


Closeness heuristic varies across operators, that reveal:

  • Poor quality of Carris GTFS shape geometries

  • MobiCascais route variants not mapped on OSM

Case study: results

Carris GTFS shape geometries improved after OSM match

Before

Case study: results

Carris GTFS shape geometries improved after OSM match

Before

After

Case study: results


Filtering by length difference <1000 m and start/end point distance <500 m

Operator Matched shapes after filtering (n) Matched shapes peak¹ (%) Matched trips peak¹ (%)
Carris 251 97.16 97.49
TCB 52 90.32 96.40
MobiCascais 117 91.86 94.49
Carris Metropolitana 1369 92.42 94.03


Match remains >90% for all operators

Case study: results

Going back to the initial example…

Before

Case study: results

Going back to the initial example…

Before

After

GTFShift

GTFShift::osm_shapes_match_routes()

🌐 Open source method that relies on open data

💯 Simple and effective method to improve GTFS shapes quality

🧐 Enhances accuracy of of trip visualisation

🤓 Relation between PT operations and infrastructure unlocks wide range of modeling possibilities

Motivation

Limit impacts of congestion
(Cesme et al. 2018)

Reducing travel time variability
(Surprenant-Legault and El-Geneidy 2011)

Increase commercial speed
(Basso et al. 2011)

Other modes can loose
quality of service
(Arasan and Vedagiri 2010)

Potential to jeopardize
public acceptance
(Batty et al. 2014)

It is crucial to underlie these interventions on data and evidence to ensure they are effective and accepted by the public

Data sources and criteria

Bus frequency 🚍
General Transit Feed Specification (GTFS)

Traffic conditions 🚗 / Passenger demand 👭
GTFS Real Time (GTFS-RT)

Number of lanes 🛣 / Network continuity 🌐
OpenStreetMap (OSM)

Data sources: GTFS-RT

Traffic conditions 🚗 / Passenger demand 👭
GTFS Real Time (GTFS-RT)

{
    "agency_id": "41",
    "bikes_allowed": true,
    "capacity_seated": 29,
    "capacity_standing": 28,
    "capacity_total": 57,
    "contactless": false,
    "id": "41|300",
    "license_plate": "AC-45-FH",
    "make": "CaetanoBus",
    "model": "e.City Gold",
    "owner": "SCOTTURB",
    "propulsion": "electricity",
    "registration_date": "20141110",
    "wheelchair_accessible": true,
    "bearing": 167,
    "block_id": "1_1325-11",
    "current_status": "INCOMING_AT",
    "event_id": "6KLCD-41|300-1612_0_2_0800_0829_0_1",
    "lat": 38.76739501953125,
    "line_id": "1612",
    "lon": -9.2985200881958,
    "pattern_id": "1612_0_2",
    "route_id": "1612_0",
    "schedule_relationship": "SCHEDULED",
    "shift_id": "TP 1+1325",
    "speed": 3.611111111111111,
    "stop_id": "172486",
    "timestamp": 1778574545,
    "trip_id": "[6KLCD]1612_0_2_0800_0829_0_1",
    "door_status": "CLOSED"
}

Methodology

Methodology: Bus frequency 🚍

Aggregate GTFS frequencies at route level and use OSM routes geometry to aggregate at road segment level

GTFShift::get_route_frequency_hourly

GTFShift::osm_shapes_match_routes

GTFShift::get_way_frequency_hourly

Methodology:
Nr of lanes 🛣 / Network continuity 🌐

Getting road network topology from OSM

osmdata::opq

GTFShift::osm_bus_lanes

Methodology: Traffic conditions 🚗

Compute average speed along route geometry for each GTFS-RT update and aggregate per OSM road segment

GTFShift::rt_collect + GTFShift::rt_average_speed

sf::st_buffer

GTFShift::rt_extend_prioritization

Methodology: Passenger demand 👭

Summing passenger demand, using aggregated values at route level (for all segments traversed by route)

Case Study

Lisbon Metropolitan Area

 

Lisbon urban buses
263 GTFS shapes
1.235 trips at peak

 

Urban and suburban buses
1.533 GTFS shapes
2.900 trips at peak

 

Reproducibility

📦 Open source R package

📊 Comprehensive framework for bus lane prioritization

🔎 Provides methods for the analysis of each individual dimension

🚀 Enables aggregated analysis with one simple method

Reproducibility

🚀 Enables aggregated analysis with one simple method

library(GTFShift)

gtfs = GTFShift::load_feed("https://gateway.carris.pt/gateway/gtfs/api/v2.8/GTFS")

osm_q = opq(bbox=sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes)))  |>
  add_osm_feature(key = "route", value = c("bus", "tram")) |>
  add_osm_feature(key = "network", value = "Carris", key_exact = TRUE)

#  Bus frequency 🚍,  Number of lanes 🛣️,  Network continuity 🌐 
lanes = GTFShift::prioritize_lanes(gtfs, osm_q)

rt_collection = read.csv("rt_collect_file.csv") |> 
  sf::st_as_sf(coords = c("vehicle.position.longitude", "vehicle.position.latitude"), crs = 4326)

#  Traffic conditions 🚗 
lanes = GTFShift::rt_extend_prioritization(
  lane_prioritization = lanes,
  rt_collection = rt_collection
)

Future work

🗺️ Keep contributing to OSM

👀 Extend methods to evaluate operational performance

🌐 Apply methodology to international case studies

📄 Get papers published

💻 Submit to cran