Introduction
GTFS feeds do not have a defined scope regarding its coverage of the transportation system. Some can be bounded to one agency, whereas others can aggregate several modes in the same city, or even national wise.
From the simpler to the most complex feeds, some analysis require to narrow the perspective. GTFShift provides some to help in this process.
Filters
Filter by agency
This example uses a national GTFS feed for long distance rail in Germany, retrieved from https://gtfs.de/en/feeds/de_fv/.
# Load GTFS
gtfs = load_feed("https://download.gtfs.de/germany/fv_free/latest.zip")
summary(gtfs)
#> tidygtfs object
#> files agency, routes, stop_times, trips, attributions, shapes, transfers, ., calendar, calendar_dates, feed_info, stops
#> agencies Ceske Drahy, Nederlandse Spoorwegen, Dänische Staatsbahnen ... 10 more
#> service from 2025-06-22 to 2025-07-22
#> uses stop_times (no frequencies)
#> # routes 97
#> # trips 4453
#> # stop_ids 1560
#> # stop_names 771
#> # shapes 2144
Multimodal feeds aggregate several agencies.
GTFShift::filter_by_agency()
, allows to filter by the
agency id, name, or both. You can filter by the id
and/or
name
of the agency.
# Filter by agency id
gtfs_5 = GTFShift::filter_by_agency(gtfs, id = 5)
summary(gtfs_5)
#> tidygtfs object
#> files agency, routes, stop_times, trips, attributions, shapes, transfers, ., calendar, calendar_dates, feed_info, stops
#> agencies Ceske Drahy, Nederlandse Spoorwegen, Dänische Staatsbahnen ... 10 more
#> service from 2025-06-22 to 2025-07-22
#> uses stop_times (no frequencies)
#> # routes 64
#> # trips 3557
#> # stop_ids 792
#> # stop_names 376
#> # shapes 2024
# Filter by agency name
gtfs_sncf = GTFShift::filter_by_agency(gtfs, name = "SNCF")
summary(gtfs_sncf)
#> tidygtfs object
#> files agency, routes, stop_times, trips, attributions, shapes, transfers, ., calendar, calendar_dates, feed_info, stops
#> agencies Ceske Drahy, Nederlandse Spoorwegen, Dänische Staatsbahnen ... 10 more
#> service from 2025-06-22 to 2025-07-22
#> uses stop_times (no frequencies)
#> # routes 2
#> # trips 27
#> # stop_ids 6
#> # stop_names 5
#> # shapes 6
Original GTFS
shape_agency = gtfs$trips |>
left_join(gtfs$routes, by = "route_id") |>
left_join(gtfs$agency, by = "agency_id") |>
select(shape_id, agency_id, agency_name) |>
distinct() |>
mutate(name = paste(agency_id, agency_name, sep = ": "))
shapes_sf = tidytransit::shapes_as_sf(gtfs$shapes) |>
left_join(shape_agency, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Agency")
GTFS filtered for agency id = 5
shape_agency = gtfs_5$trips |>
left_join(gtfs_5$routes, by = "route_id") |>
left_join(gtfs_5$agency, by = "agency_id") |>
select(shape_id, agency_id, agency_name) |>
distinct() |>
mutate(name = paste(agency_id, agency_name, sep = ": "))
shapes_sf = tidytransit::shapes_as_sf(gtfs_5$shapes) |>
left_join(shape_agency, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name = "Agency")
GTFS filtered for agency name = SNCF
shape_agency = gtfs_sncf$trips |>
left_join(gtfs_sncf$routes, by = "route_id") |>
left_join(gtfs_sncf$agency, by = "agency_id") |>
select(shape_id, agency_id, agency_name) |>
distinct()|>
mutate(name = paste(agency_id, agency_name, sep = ": "))
shapes_sf = tidytransit::shapes_as_sf(gtfs_sncf$shapes) |>
left_join(shape_agency, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Agency")
Filter by mode
This example uses a metropolitan wide GTFS feed for Los Angeles rail services, retrieved from https://developer.metro.net/gtfs-schedule-data/.
# Load GTFS
gtfs = GTFShift::load_feed("https://gitlab.com/LACMTA/gtfs_rail/raw/master/gtfs_rail.zip", create_transfers=FALSE)
summary(gtfs)
#> tidygtfs object
#> files agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency Metro - Los Angeles
#> service from 2025-06-27 to 2025-07-11
#> uses stop_times (no frequencies)
#> # routes 6
#> # trips 7664
#> # stop_ids 440
#> # stop_names 341
#> # shapes 14
Multimodal feeds aggregate several modes.
GTFShift::filter_by_mode()
allows to restrict the feed to
some modes only. Refer to routes.txt ‘route_type’ parameter on GTFS
documentation for more details on the modes id that should be used
as parameters.
# Filter by mode tram
gtfs_tram = GTFShift::filter_by_modes(gtfs, modes = list(0))
summary(gtfs_tram)
#> tidygtfs object
#> files agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency Metro - Los Angeles
#> service from 2025-06-27 to 2025-07-11
#> uses stop_times (no frequencies)
#> # routes 4
#> # trips 6176
#> # stop_ids 91
#> # stop_names 91
#> # shapes 8
Original GTFS
shape_route = gtfs$trips |>
left_join(gtfs$routes, by = "route_id") |>
select(shape_id, route_id, route_type) |>
distinct() |>
mutate(route_type = as.character(route_type))
shapes_sf = tidytransit::shapes_as_sf(gtfs$shapes) |>
left_join(shape_route, by = "shape_id") |>
filter(!is.na(route_type))
mapview::mapview(shapes_sf, zcol = "route_type", legend = TRUE, layer.name="Route type")
GTFS filtered for mode tram (0)
shape_route = gtfs_tram$trips |>
left_join(gtfs_tram$routes, by = "route_id") |>
select(shape_id, route_id, route_type) |>
distinct() |>
mutate(route_type = as.character(route_type))
shapes_sf = tidytransit::shapes_as_sf(gtfs_tram$shapes) |>
left_join(shape_route, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "route_type", legend = TRUE, layer.name="Route type")
Filter by route_name
This article uses a GTFS feed from the library GTFS database for Portugal as an example. Refer to the vignette(“download”) for more details.
# Get GTFS from library GTFS database for Portugal
data = read.csv(system.file("extdata", "gtfs_sources_pt.csv", package = "GTFShift"))
gtfs = GTFShift::load_feed(data[data$ID=="faro",]$URL, create_transfers=FALSE)
summary(gtfs)
#> tidygtfs object
#> files agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency Próximo - Transportes Urbanos de Faro
#> service from 2023-01-01 to 2025-12-31
#> uses stop_times (no frequencies)
#> # routes 14
#> # trips 674
#> # stop_ids 187
#> # stop_names 126
#> # shapes 29
GTFS feeds aggregate several routes.
GTFShift::filter_by_route_name()
, allows to filter the feed
for specific routes, given a partial or total match with the short or
the long name.
# Filter by short_name with exact match
gtfs_1 = GTFShift::filter_by_route_name(
gtfs,
values = list("1"),
short_name = TRUE,
exact_match = TRUE
)
summary(gtfs_1)
#> tidygtfs object
#> files agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency Próximo - Transportes Urbanos de Faro
#> service from 2023-01-02 to 2025-12-31
#> uses stop_times (no frequencies)
#> # routes 1
#> # trips 58
#> # stop_ids 23
#> # stop_names 19
#> # shapes 1
# Filter by long_name with partial match
gtfs_terminal = GTFShift::filter_by_route_name(
gtfs,
values = list("Terminal", "Rodoviário"),
short_name = FALSE,
exact_match = FALSE
)
summary(gtfs_terminal)
#> tidygtfs object
#> files agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency Próximo - Transportes Urbanos de Faro
#> service from 2023-01-01 to 2025-12-31
#> uses stop_times (no frequencies)
#> # routes 6
#> # trips 406
#> # stop_ids 103
#> # stop_names 72
#> # shapes 15
Original GTFS
route_name = gtfs$trips |>
left_join(gtfs$routes, by = "route_id") |>
select(shape_id, route_id, route_short_name, route_long_name) |>
distinct() |>
mutate(name = paste(route_short_name, route_long_name, sep = ": "))
shapes_sf = tidytransit::shapes_as_sf(gtfs$shapes) |>
left_join(route_name, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Route name")
GTFS filtered for route short name 1
(exact match)
route_name = gtfs_1$trips |>
left_join(gtfs_1$routes, by = "route_id") |>
select(shape_id, route_id, route_short_name, route_long_name) |>
distinct() |>
mutate(name = paste(route_short_name, route_long_name, sep = ": "))
shapes_sf = tidytransit::shapes_as_sf(gtfs_1$shapes) |>
left_join(route_name, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Route name")
GTFS filtered for route long name Terminal
,
Rodoviário
(partial match)
route_name = gtfs_terminal$trips |>
left_join(gtfs_terminal$routes, by = "route_id") |>
select(shape_id, route_id, route_short_name, route_long_name) |>
distinct() |>
mutate(name = paste(route_short_name, route_long_name, sep = ": "))
shapes_sf = tidytransit::shapes_as_sf(gtfs_terminal$shapes) |>
left_join(route_name, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Route name")