| Title: | Explore and Analyse General Transit Feed Specification (GTFS) Files with a Focus on Urban Mobility |
|---|---|
| Description: | A bundle of methods to harmonize GTFS and OSM data, enabling the integration and exploration of different layers of transit data, starting with the planned operations (GTFS), but also the infrastructure topology (OSM) and real-time information (GTFS-RT). |
| Authors: | Gonçalo F. Matos [aut, cre] (ORCID: <https://orcid.org/0009-0001-3489-1732>), Rosa Félix [aut] (ORCID: <https://orcid.org/0000-0002-5642-6006>), Miguel Relvas Pires [ctb] |
| Maintainer: | Gonçalo F. Matos <[email protected]> |
| License: | GPL |
| Version: | 1.1.0 |
| Built: | 2026-10-09 16:27:48 UTC |
| Source: | https://github.com/u-shift/gtfshift |
Get next business Wednesday
calendar_nextBusinessWednesday(start_date = Sys.Date(), country_code = "PT")calendar_nextBusinessWednesday(start_date = Sys.Date(), country_code = "PT")
start_date |
String (Default |
country_code |
String (Default PT). Country code in the format |
Find the next Wednesday that is not a holiday. When country is given, public holidays are considered, using Nager.Date API.
Date. The next business Wednesday date.
# Example of Portuguese holiday (10/06/2026) ignored GTFShift::calendar_nextBusinessWednesday(start_date = "2026-06-09", country_code="PT") # Example of Hong Kong holiday (01/07/2026) ignored GTFShift::calendar_nextBusinessWednesday(start_date = "2026-06-30", country_code="HK")# Example of Portuguese holiday (10/06/2026) ignored GTFShift::calendar_nextBusinessWednesday(start_date = "2026-06-09", country_code="PT") # Example of Hong Kong holiday (01/07/2026) ignored GTFShift::calendar_nextBusinessWednesday(start_date = "2026-06-30", country_code="HK")
Classify bus frequency level of service based on HCM
classify_frequency_los(frequencies, frequency_col = "frequency")classify_frequency_los(frequencies, frequency_col = "frequency")
frequencies |
data.frame. Data frame with frequency information. |
frequency_col |
String (Default "frequency"). Name of the column with frequency values. |
Classifies bus frequency level of service (LOS) based on the Highway Capacity Manual (HCM) 2000 guidelines on "Service Frequency LOS for Urban Scheduled Transit Service" (Exhibit 27-1).
Refer to vignette("classify") for more details on this classification.
data.frame. Input data frame with an additional column frequency_los indicating the LOS classification.
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Get route frequency frequency_analysis <- GTFShift::get_route_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) # Compute LOS frequency_los = GTFShift::classify_frequency_los(frequency_analysis) frequency_los |> sf::st_drop_geometry() |> dplyr::select(route_id, frequency_los)# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Get route frequency frequency_analysis <- GTFShift::get_route_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) # Compute LOS frequency_los = GTFShift::classify_frequency_los(frequency_analysis) frequency_los |> sf::st_drop_geometry() |> dplyr::select(route_id, frequency_los)
Create calendar.txt from calendar_dates.txt
create_calendar(gtfs)create_calendar(gtfs)
gtfs |
tidygtfs. GTFS feed. |
When calendar_dates.txt declares all service dates, calendar.txt becomes optional in the
GTFS feed specification.
However, to perform some operations, this table might be necessary.
This method allows to create a calendar.txt table, based on the calendar_dates.txt.
It performs an approximation, considering, for each service_id, the
minimum and maximum dates and setting each week day to true if it has any date that matches that date. The results
might not be 100
data.frame. Table for calendar.txt.
gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) head(gtfs$calendar_dates |> dplyr::filter(exception_type == 1)) gtfs_calendar <- GTFShift::create_calendar(gtfs) gtfs_calendargtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) head(gtfs$calendar_dates |> dplyr::filter(exception_type == 1)) gtfs_calendar <- GTFShift::create_calendar(gtfs) gtfs_calendar
Build shapes from simple feature object
create_shapes_from_sf( sf_shapes, gtfs, metric_crs = 3857, shape_dist_traveled = FALSE )create_shapes_from_sf( sf_shapes, gtfs, metric_crs = 3857, shape_dist_traveled = FALSE )
sf_shapes |
sf object associating |
gtfs |
tidygtfs. GTFS feed. |
metric_crs |
numeric (Default 3857). EPSG code for a metric CRS used when computing distances (passed to |
shape_dist_traveled |
Boolean (Default FALSE). If TRUE, computes
|
This function builds the shapes.txt file from a simple feature object.
It first converts any MULTILINESTRING geometries to LINESTRING geometries using the
multiline_to_sorted_linestring, using a point guide per shape:
all ordered stops when the selected trip is circular (first and last
stop_id are equal), or the first two stops otherwise.
Then, it converts the LINESTRING geometries to a data.frame representing a GTFS shapes table using
gtfstools::convert_sf_to_shapes.
Coordinates are 4326 (WGS 84) by default, following GTFS specifications.
Optionally, when shape_dist_traveled = TRUE, it estimates cumulative
distance along each shape for all generated points and appends this as
shape_dist_traveled. This metric is computed in the units of
metric_crs, using GTFShift::project_points_along_geometry().
data.frame. A GTFS shapes table. Includes
shape_dist_traveled if shape_dist_traveled = TRUE.
gtfstools::convert_sf_to_shapes()
GTFShift::multiline_to_sorted_linestring()
GTFShift::project_points_along_geometry()
# Load sample GTFS gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) # Load TCB OSM routes sample linestring osm_routes = sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(shape_id %in% gtfs$shapes$shape_id) |> dplyr::sample_n(1) head(osm_routes) # Create shapes.txt for geometries shapes_txt <- GTFShift::create_shapes_from_sf( osm_routes, gtfs, metric_crs = 3763, # Make sure to addapt to the projection that better suits your location shape_dist_traveled = TRUE ) head(shapes_txt)# Load sample GTFS gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) # Load TCB OSM routes sample linestring osm_routes = sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(shape_id %in% gtfs$shapes$shape_id) |> dplyr::sample_n(1) head(osm_routes) # Create shapes.txt for geometries shapes_txt <- GTFShift::create_shapes_from_sf( osm_routes, gtfs, metric_crs = 3763, # Make sure to addapt to the projection that better suits your location shape_dist_traveled = TRUE ) head(shapes_txt)
Build shapes from GTFS stops data
create_shapes_from_stops(gtfs)create_shapes_from_stops(gtfs)
gtfs |
tidygtfs. GTFS feed. |
The function builds the shapes.txt file from the stop_times.txt and stops.txt files, by grouping trips with the same stop sequence and assigning them the same shape_id. The resulting shapes are a simplified version of the original ones, as they do not take into account the actual path followed by the vehicles, but only the stop sequence. This can be useful for some applications that do not require high precision in the shapes, and can be used as a fallback when the original feed does not include shapes.txt file.
tidygtfs. The GTFS feed with the shapes table defined and the trips table updated with the matching shape_id.
# Load GTFS without shapes gtfs <- tidytransit::read_gtfs( system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) summary(gtfs) # Create shapes from GTFS stops data gtfs_with_shapes <- GTFShift::create_shapes_from_stops(gtfs) head(gtfs_with_shapes$shapes) head( gtfs_with_shapes$trips |> dplyr::select(trip_id, shape_id) |> dplyr::distinct(shape_id, .keep_all = TRUE) ) summary(gtfs_with_shapes)# Load GTFS without shapes gtfs <- tidytransit::read_gtfs( system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) summary(gtfs) # Create shapes from GTFS stops data gtfs_with_shapes <- GTFShift::create_shapes_from_stops(gtfs) head(gtfs_with_shapes$shapes) head( gtfs_with_shapes$trips |> dplyr::select(trip_id, shape_id) |> dplyr::distinct(shape_id, .keep_all = TRUE) ) summary(gtfs_with_shapes)
Filter GTFS feed by agency
filter_by_agency(gtfs, id = NA, name = NA)filter_by_agency(gtfs, id = NA, name = NA)
gtfs |
tidygtfs. GTFS feed. |
id |
Integer (Optional when name). Ids of the agency. |
name |
String (Optional when id). Name of the agency. |
Allows to filter a GTFS feed for the agency, using the id, name or both. Returns empty feed it none provided.
tidygtfs. The filtered GTFS feed.
# Load sample feed with multiple agencies gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_merged_sample.zip", package = "GTFShift") ) summary(gtfs) # Filter by id gtfs_id_8 = gtfs |> GTFShift::filter_by_agency(id = "8") summary(gtfs_id_8) # Filter by name gtfs_ttsl <- gtfs |> GTFShift::filter_by_agency(name = "TTSL - Transtejo Soflusa") summary(gtfs_ttsl)# Load sample feed with multiple agencies gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_merged_sample.zip", package = "GTFShift") ) summary(gtfs) # Filter by id gtfs_id_8 = gtfs |> GTFShift::filter_by_agency(id = "8") summary(gtfs_id_8) # Filter by name gtfs_ttsl <- gtfs |> GTFShift::filter_by_agency(name = "TTSL - Transtejo Soflusa") summary(gtfs_ttsl)
Filter GTFS feed by mode
filter_by_modes(gtfs, modes = list())filter_by_modes(gtfs, modes = list())
gtfs |
tidygtfs. GTFS feed. |
modes |
Integer[]. A list with the ids of modes to consider. |
Allows to filter a GTFS feed for the type of transportation used, allowing for a more narrow analysis of multimodal files.
Refer to routes.txt route_type parameter on
GTFS documentation for more details.
tidygtfs. The filtered GTFS feed.
# Load sample feed with multiple modes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_merged_sample.zip", package = "GTFShift") ) gtfs$routes |> dplyr::select(route_id, route_type) summary(gtfs) # Filter by bus mode (ferry agency should be excluded) gtfs_bus <- gtfs |> GTFShift::filter_by_modes(modes = c(3)) gtfs_bus$routes |> dplyr::select(route_id, route_type) summary(gtfs_bus)# Load sample feed with multiple modes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_merged_sample.zip", package = "GTFShift") ) gtfs$routes |> dplyr::select(route_id, route_type) summary(gtfs) # Filter by bus mode (ferry agency should be excluded) gtfs_bus <- gtfs |> GTFShift::filter_by_modes(modes = c(3)) gtfs_bus$routes |> dplyr::select(route_id, route_type) summary(gtfs_bus)
Filter GTFS feed by route name
filter_by_route_name(gtfs, values, short_name = TRUE, exact_match = TRUE)filter_by_route_name(gtfs, values, short_name = TRUE, exact_match = TRUE)
gtfs |
tidygtfs. GTFS feed. |
values |
String[]. List of the route names to filter the feed. |
short_name |
Boolean. If TRUE, query for route_short_name, otherwise, route_long_name is considered. |
exact_match |
Boolean. If TRUE, route name is queried for an exact match, otherwise, partial match is considered. |
On a GTFS feed, the route_id rarely matches the real name of the route, that can range from numbers,
letters, words or combinations of both.
This method allows to filter the feed for the route short or long name, with a partial or exact match.
tidygtfs. The filtered GTFS feed.
# Load GTFS gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) summary(gtfs) # Filter by route gtfs_route <- GTFShift::filter_by_route_name(gtfs, c("4")) summary(gtfs_route)# Load GTFS gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) summary(gtfs) # Filter by route gtfs_route <- GTFShift::filter_by_route_name(gtfs, c("4")) summary(gtfs_route)
Get total extension of GTFS feed routes
get_network_extension( gtfs, route_identifier = "route_id", direction_wise = TRUE, unified = FALSE, date = GTFShift::calendar_nextBusinessWednesday(), use_osm_routes = NA, metric_crs = 3857 )get_network_extension( gtfs, route_identifier = "route_id", direction_wise = TRUE, unified = FALSE, date = GTFShift::calendar_nextBusinessWednesday(), use_osm_routes = NA, metric_crs = 3857 )
gtfs |
tidygtfs. GTFS feed. |
route_identifier |
String. (Default |
direction_wise |
Boolean (Default |
unified |
Boolean (Default |
date |
Date (Default |
use_osm_routes |
osmdata::opq (Default NA). If overpass query for transit network is defined, analysis is performed considering OSM route geometry, using |
metric_crs |
Integer or character (Default 3857). Projected CRS used to compute route lengths in meters. |
This method calculates the sum of the GTFS feed routes length, considering, for each, the shape of the variant with the highest frequency for the given date
(using GTFShift::get_route_frequency_hourly()).
For a detailed example, see the vignette("analyse").
Numeric. The routes extension, in meters.
GTFShift::get_route_frequency_hourly()
# Load GTFS gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift" )) # Get route extension GTFShift::get_network_extension( gtfs, metric_crs = 3763, # Make sure to addapt to the projection that better suits your location date = gtfs$calendar$start_date[1] )# Load GTFS gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift" )) # Get route extension GTFShift::get_network_extension( gtfs, metric_crs = 3763, # Make sure to addapt to the projection that better suits your location date = gtfs$calendar$start_date[1] )
Get statistics about lane prioritisation
get_prioritisation_stats( lane_prioritisation, weight = c("length", "frequency"), metric_crs = 3857 )get_prioritisation_stats( lane_prioritisation, weight = c("length", "frequency"), metric_crs = 3857 )
lane_prioritisation |
sf data.frame. Lane prioritisation. |
weight |
Character. Weight to use for weighted mean. Accepted values: "length", "frequency". |
metric_crs |
Integer or character (Default 3857). Projected CRS used to compute lengths in meters. |
List. Statistics about lane prioritisation, with the following attributes:
Total length of the prioritised network, in meters.
Total length of the bus lane segments, in meters.
Average speed of the prioritised network, in km/h.
Minimum speed of the prioritised network, in km/h.
Maximum speed of the prioritised network, in km/h.
Average number of lanes in the prioritised network.
Minimum number of lanes in the prioritised network.
Maximum number of lanes in the prioritised network.
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Prioritise lanes lane_prioritisation <- GTFShift::prioritise_lanes( gtfs, q, osm_file = osm_file, date = gtfs$calendar$start_date[1] ) # Get statistics for prioritisation stats <- GTFShift::get_prioritisation_stats(lane_prioritisation, metric_crs = 3763) data.frame(metric = names(stats), value = unlist(stats, use.names = FALSE))# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Prioritise lanes lane_prioritisation <- GTFShift::prioritise_lanes( gtfs, q, osm_file = osm_file, date = gtfs$calendar$start_date[1] ) # Get statistics for prioritisation stats <- GTFShift::get_prioritisation_stats(lane_prioritisation, metric_crs = 3763) data.frame(metric = names(stats), value = unlist(stats, use.names = FALSE))
For each route, returns the number of departures aggregated per hour and direction.
get_route_frequency_hourly( gtfs, date = GTFShift::calendar_nextBusinessWednesday(), use_osm_routes = NA, overline = FALSE )get_route_frequency_hourly( gtfs, date = GTFShift::calendar_nextBusinessWednesday(), use_osm_routes = NA, overline = FALSE )
gtfs |
tidygtfs. GTFS feed. |
date |
Date (Default |
use_osm_routes |
osmdata::opq (Default NA). If overpass query for transit network is defined, analysis is performed considering OSM route geometry, using |
overline |
Boolean (Default FALSE). If TRUE, routes are aggregated using |
This method analyses the GTFS feed for a representative day, generating for each route the number of services aggregated per hour and direction. It assumes the time of departure at the first stop as a reference for each trip geometry.
By default, it estimates the next business Wednesday, relevant for the peak hour.
The overline parameter enables the aggregation of bus routes that share common line segments, returning a sum of frequencies per road segment, using stplanr::overline2().
Optionally, using use_osm_routes parameter, it retrieves the geometries from OpenStreetMap by matching the tag gtfs:shape_id, overwriting the original GTFS shapes.txt.
This is particularly useful if the GTFS shapes do not share the same geometry. For instance, if the edges of the lines do not overlap or do not follow the same route-over-the-road – which is very common, even besides GTFS recommendation – geometries might not be aggregated correctly, causing inconsistent results.
By relying on a common road network, such as OSM, it is possible to overcome this issue and aggregate the bus routes correctly.
For a detailed example, see the vignette("analyse").
Adapted from github.com/Bondify/GTFS_in_R.
sf data.frame. Hourly route frequencies, with the following columns (the first three are only present if overline=FALSE):
The route_id attribute from routes.txt file.
The route_short_name attribute from routes.txt file.
The shape_id attribute from shapes.txt file.
The direction_id attribute from trips.txt file (if attribute present in GTFS feed).
The hour for which the frequency applies (24 hour format).
The number of services for the route that depart from the first stop for the corresponding 60 minutes period.
The route shape.
GTFShift::calendar_nextBusinessWednesday()
GTFShift::osm_shapes_to_routes()
stplanr::overline2()
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Get frequency frequency_analysis <- GTFShift::get_route_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) head(frequency_analysis |> sf::st_drop_geometry())# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Get frequency frequency_analysis <- GTFShift::get_route_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) head(frequency_analysis |> sf::st_drop_geometry())
For each stop, returns the number of departures aggregated per hour.
get_stop_frequency_hourly( gtfs, date = GTFShift::calendar_nextBusinessWednesday() )get_stop_frequency_hourly( gtfs, date = GTFShift::calendar_nextBusinessWednesday() )
gtfs |
tidygtfs. GTFS feed. |
date |
Date (Default |
This method analyses the GTFS feed for a representative day, generating for each stop the number of services aggregated per hour.
For a detailed example, see the vignette("analyse").
sf data.frame. Hourly stop frequencies, with the following columns:
The stop_id attribute from stops.txt file.
The hour for which the frequency applies (24 hour format).
The number of services provided at the stop for the corresponding 60 minutes period.
The stop coordinates.
GTFShift::calendar_nextBusinessWednesday()
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Get frequency frequency_analysis <- GTFShift::get_stop_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) head(frequency_analysis)# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Get frequency frequency_analysis <- GTFShift::get_stop_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) head(frequency_analysis)
Computes aggregated speed metrics for trips or groups from the output of
GTFShift::rt_average_speed(). Aggregates by trip, route, and day
(customizable via the by parameter), calculating commercial speed
(distance between first and last updates along geometry divided by elapsed time),
alternative commercial speed (considering the 2nd and penultimate updates to avoid
terminal wait biases), and measures of centrality and spread for speed observations.
get_trip_speed_profile( rt_speed, by = c("trip_id", "route_id", "day"), speed_col = "speed_kmh", time_col = "timestamp" )get_trip_speed_profile( rt_speed, by = c("trip_id", "route_id", "day"), speed_col = "speed_kmh", time_col = "timestamp" )
rt_speed |
data.frame or sf data.frame. The result of
|
by |
Character vector (Default |
speed_col |
Character (Default |
time_col |
Character (Default |
For each group defined by by (by default, each unique combination of
trip_id, route_id, and day), observations are ordered
chronologically by timestamp.
Let denote the ordered sequence of updates, where
is the distance along geometry (meters), is the reversed distance
along geometry (meters), and is the timestamp (seconds).
To accommodate circular geometries (where starting and terminal positions may map to the same
location on the shape), the distance traveled between two observations and ()
is calculated by considering both the normal and reversed distances and taking the maximum:
Commercial speed is calculated as the total distance traveled between the first and last updates divided by the elapsed time:
If or , commercial_speed is NA.
Alternative commercial speed (commercial_speed_alt) uses the 2nd and
penultimate () observations to eliminate potential dwell times or layovers at
the terminal stops:
If or , commercial_speed_alt is NA.
If by does not include trip_id (e.g., aggregating at route or day level),
commercial_speed and commercial_speed_alt are computed per trip and averaged
across trips within each group.
Measures of centrality and spread are calculated from all valid (non-NA, finite)
speed observations in speed_kmh for each group:
Earliest timestamp in the trip/group.
Latest timestamp in the trip/group.
Commercial speed between first and last update (km/h).
Alternative commercial speed between 2nd and penultimate update (km/h).
Arithmetic mean speed (km/h).
Median speed (km/h).
Standard deviation of speeds (km/h).
Variance of speeds (km/h)^2.
Minimum observed speed (km/h).
Maximum observed speed (km/h).
15th percentile speed (km/h).
25th percentile (1st quartile) speed (km/h).
75th percentile (3rd quartile) speed (km/h).
85th percentile speed (km/h).
Interquartile range (speed_p75 - speed_p25) (km/h).
Count of valid speed observations.
Total number of position updates in the group.
In addition, an attribute "global_summary" is attached to the returned data frame,
providing the same metrics evaluated over the entire input dataset.
data.frame. Aggregated speed profile metrics with one row per group.
GTFShift::rt_average_speed()
# Get GTFS-RT data collection and route geometries rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) rt_collection <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) |> dplyr::select(-speed) osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(route_id %in% rt_collection$route_id) |> dplyr::mutate(geom = GTFShift::multiline_to_sorted_linestring(geom, metric_crs = 3763)) # Compute speeds with rt_average_speed() speed <- GTFShift::rt_average_speed( rt_collection = rt_collection, trips_geometries = osm_routes, rt_collection_trips_geometries_match_col = "route_id", metric_crs = 3763 ) # Compute trip speed profile profile <- GTFShift::get_trip_speed_profile(speed) head(profile) # Customize aggregation (e.g. by route and day) route_profile <- GTFShift::get_trip_speed_profile(speed, by = c("route_id", "day")) head(route_profile)# Get GTFS-RT data collection and route geometries rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) rt_collection <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) |> dplyr::select(-speed) osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(route_id %in% rt_collection$route_id) |> dplyr::mutate(geom = GTFShift::multiline_to_sorted_linestring(geom, metric_crs = 3763)) # Compute speeds with rt_average_speed() speed <- GTFShift::rt_average_speed( rt_collection = rt_collection, trips_geometries = osm_routes, rt_collection_trips_geometries_match_col = "route_id", metric_crs = 3763 ) # Compute trip speed profile profile <- GTFShift::get_trip_speed_profile(speed) head(profile) # Customize aggregation (e.g. by route and day) route_profile <- GTFShift::get_trip_speed_profile(speed, by = c("route_id", "day")) head(route_profile)
For each OSM way with GTFS service, returns the number of departures aggregated per hour and direction.
get_way_frequency_hourly( gtfs, q, date = GTFShift::calendar_nextBusinessWednesday(), keep_osm_attributes = FALSE, osm_file = NULL )get_way_frequency_hourly( gtfs, q, date = GTFShift::calendar_nextBusinessWednesday(), keep_osm_attributes = FALSE, osm_file = NULL )
gtfs |
tidygtfs. GTFS feed. |
q |
osmdata::opq. Overpass query for transit network, to obtain OSM route ways, using |
date |
Date (Default |
keep_osm_attributes |
Boolean (Default FALSE). Whether to keep all OSM way attributes in the output |
osm_file |
character (Optional). Location of OSM extract file with |
This method analyses the GTFS feed for a representative day, finding for each route the corresponding OSM ways using GTFShift::osm_shapes_to_routes()
(routes not on OSM are ignored), aggregating the number of services per hour and direction for each.
For a detailed example, see the vignette("analyse").
sf data.frame. Hourly way frequencies, with the following columns:
The osm_id attribute from OSM way.
The hour for which the frequency applies (24 hour format).
The number of services for the route that depart from the first stop for the corresponding 60 minutes period.
The list of route_ids that use the way.
The list of shape_ids that use the way.
The route shape.
keep_osm_attributes = TRUE)All OSM way attributes.
GTFShift::calendar_nextBusinessWednesday()
GTFShift::osm_shapes_to_routes()
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Get frequency frequency_analysis <- GTFShift::get_way_frequency_hourly( gtfs, q, date = gtfs$calendar$start_date[1], osm_file = osm_file ) head(frequency_analysis |> sf::st_drop_geometry())# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Get frequency frequency_analysis <- GTFShift::get_way_frequency_hourly( gtfs, q, date = gtfs$calendar$start_date[1], osm_file = osm_file ) head(frequency_analysis |> sf::st_drop_geometry())
Read GTFS feed, fixing integrity errors
load_feed( path, store_path = NA, create_transfers = FALSE, transfer_distance = 300, transfer_time = 120, transfer_street_routing = FALSE, headers = NULL )load_feed( path, store_path = NA, create_transfers = FALSE, transfer_distance = 300, transfer_time = 120, transfer_street_routing = FALSE, headers = NULL )
path |
String. The location of the GTFS zip file. Either local or URL. |
store_path |
String (Optional). If provided, GTFS feed zip is stored at location. The file is overwritten if it already exists. |
create_transfers |
Boolean (Default FALSE). When true, generates |
transfer_distance |
Integer (Default 300). Upper straight-line distance limit in meters for transfers. |
transfer_time |
Integer (Default 120). Minimum time in seconds for transfers; all values below this will be replaced with this value, particularly all those defining in-place transfers where stop longitudes and latitudes remain identical. |
transfer_street_routing |
Boolean (Default FALSE). If TRUE, transfer times are calculated by routing throughout the underlying street network (downloaded automatically). |
headers |
Named list or character vector (Optional). Custom HTTP headers for credentials when accessing the GTFS zip file URL. |
In addition to loading the GTFS feed, this method validates its integrity and applies the proper corrections if it does not comply with the following validations:
stop_times.txt with empty arrival_time or departure_time, filtering rows that do not comply.
Feeds with missing shapes.txt file, generating it using GTFShift::create_shapes_from_stops().
When generating transfers, those already existing in each GTFS file are kept, extended with new ones computed based on the stops
network of the final aggregated version. This computation is executed with gtfsrouter::gtfs_transfer_table(), with
the parameters d_limit=transfer_distance, min_transfer_time=transfer_time and network_times=transfer_street_routing.
The other parameters are applied the library default values.
tidygtfs. The loaded GTFS feed.
GTFShift::create_shapes_from_stops()
tidytransit::read_gtfs()
gtfsrouter::gtfs_transfer_table()
# Simple call gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) summary(gtfs) # Simple call with missing shapes (triggering shapes creation because missing on GTFS file) gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) summary(gtfs) # With some parameters to build transfers and store to given location store_path <- withr::local_tempfile(fileext = ".zip") gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift"), create_transfers = TRUE, store_path ) head(gtfs$transfers) file.exists(store_path)# Simple call gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) summary(gtfs) # Simple call with missing shapes (triggering shapes creation because missing on GTFS file) gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) summary(gtfs) # With some parameters to build transfers and store to given location store_path <- withr::local_tempfile(fileext = ".zip") gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift"), create_transfers = TRUE, store_path ) head(gtfs$transfers) file.exists(store_path)
Convert a MULTILINESTRING to a sorted LINESTRING
multiline_to_sorted_linestring( multilinestring, points = NULL, metric_crs = 3857 )multiline_to_sorted_linestring( multilinestring, points = NULL, metric_crs = 3857 )
multilinestring |
sf object with MULTILINESTRING geometry |
points |
(Optional) collection of sorted point geometries used to guide ordering. If provided, the first point defines the initial segment and the second point (when available) is used as tie-break guidance for its orientation. Remaining points are used as iterative tie-break guidance. |
metric_crs |
Integer or character (Default 3857). Projected CRS used to compute distances and lengths during sorting. |
The function takes a MULTILINESTRING object and converts it to a LINESTRING object by sorting the linestrings and combining them in the correct order.
The algorithm is formulated as follows:
Let be the set of individual LINESTRING components.
Each component is characterized by its start point and end point .
1. Initialization
If guiding points are provided, let be the first point and
the second point (if available). The initial segment is chosen as
where is the Euclidean distance. If no points are provided, (assuming the input MULTILINESTRING is ordered).
Additionally, the orientation of is determined by comparing the distances
from its edges to the remaining segments in .
The edge that is closest to any remaining segment is designated as the end of .
If both edges are equidistant to the remaining segments, the orientation is determined
by the proximity to (if available) or by orienting away from .
2. Iterative Step
At iteration , with current segment endpoint , define
for each remaining segment :
Segments with geometry equal to are excluded. Candidate segments
minimize endpoint proximity:
Ties are broken as follows:
3. Verification and Assembly
Let be the selected candidate. If
then is removed from the remaining set and the loop restarts.
Otherwise, is oriented to connect from and
appended to the ordered sequence. When points are provided, consecutive
unvisited points are marked visited when
The ordered segments are concatenated into a single LINESTRING and
transformed back to the original CRS of multilinestring.
sfc. LINESTRING geometry object.
# Get OSM route geometries (MULTILINESTRING) osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::sample_n(1) head(osm_routes) # Convert geometry to LINESTRING osm_routes <- osm_routes |> dplyr::mutate( geom = GTFShift::multiline_to_sorted_linestring(geom, metric_crs = 3763) ) head(osm_routes)# Get OSM route geometries (MULTILINESTRING) osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::sample_n(1) head(osm_routes) # Convert geometry to LINESTRING osm_routes <- osm_routes |> dplyr::mutate( geom = GTFShift::multiline_to_sorted_linestring(geom, metric_crs = 3763) ) head(osm_routes)
Aggregate lines based on overlap with target network
network_overline( target_network, lines, attr, target_network_split = 100, fun = sum, join_dist = 10, metric_crs = 3857 )network_overline( target_network, lines, attr, target_network_split = 100, fun = sum, join_dist = 10, metric_crs = 3857 )
target_network |
sf. A spatial object representing the target network. |
lines |
sf. A spatial object representing the lines to aggregate. |
attr |
String. The attribute to aggregate the lines by. |
target_network_split |
Integer (Default 100). If not NA, network is split in segments of defined meters. |
fun |
Method (Default |
join_dist |
Integer (Default 10). Meters to consider when joining routes and network segments. |
metric_crs |
Integer or character (Default 3857). Projected CRS used to compute segment lengths and join distances in meters. |
This method allows for the lines aggregation. Given a target network, it identifies (using stplanr::rnet_join())
the segments corresponding to each line and uses them to aggregate the attribute defined in the parameters.
It provides an alternative to GTFShift::get_route_frequency_hourly() with the attribute overline=TRUE, which
creates an aggregated network based on the lines overlap. Instead, GTFShift::network_overline() finds, for each network
segment, the overlapping lines and aggregates their attr values, using fun.
sf. Spatial network object extended with aggregated values.
stplanr::rnet_join()
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4", "1")) # Load OSM network to serve as target network target_network = sf::st_read( system.file("extdata/samples", "osm_ways_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) head(target_network) # Get route frequency (and geometry) frequency_analysis <- GTFShift::get_route_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) |> dplyr::group_by(shape_id) |> dplyr::summarize(frequency = max(frequency)) head(frequency_analysis) # Aggregate frequencies based on geometry overlap using GTFShift::network_overline suppressWarnings({ overline <- GTFShift::network_overline( target_network = target_network, lines = frequency_analysis, attr = "frequency", metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) }) head(overline |> st_drop_geometry())# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4", "1")) # Load OSM network to serve as target network target_network = sf::st_read( system.file("extdata/samples", "osm_ways_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) head(target_network) # Get route frequency (and geometry) frequency_analysis <- GTFShift::get_route_frequency_hourly( gtfs, date = gtfs$calendar$start_date[1] ) |> dplyr::group_by(shape_id) |> dplyr::summarize(frequency = max(frequency)) head(frequency_analysis) # Aggregate frequencies based on geometry overlap using GTFShift::network_overline suppressWarnings({ overline <- GTFShift::network_overline( target_network = target_network, lines = frequency_analysis, attr = "frequency", metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) }) head(overline |> st_drop_geometry())
Export designated bus lanes from OpenStreetMap
osm_bus_lanes(bbox, osm_file = NULL)osm_bus_lanes(bbox, osm_file = NULL)
bbox |
bbox. Area from which to export bus lanes. |
osm_file |
character (Optional). Location of OSM extract file with |
Exports roads tagged as designated bus lanes on OpenStreetMap for given area.
sf data.frame. OSM bus lanes.
# Create bbox for Lisbon bbox <- sf::st_as_sfc(sf::st_bbox(c( xmin = -9.229836, ymin = 38.691399, xmax = -9.087387, ymax = 38.796760 ), crs = 4326)) # Use sample osmextract for Lisbon highways osm_file <- system.file( "extdata/samples", "osmextract_lisbon_highways_sample.pbf", package = "GTFShift" ) # Export bus lanes bus_lanes <- GTFShift::osm_bus_lanes(bbox, osm_file = osm_file) names(bus_lanes) head(bus_lanes |> dplyr::select(`osm:id`, name))# Create bbox for Lisbon bbox <- sf::st_as_sfc(sf::st_bbox(c( xmin = -9.229836, ymin = 38.691399, xmax = -9.087387, ymax = 38.796760 ), crs = 4326)) # Use sample osmextract for Lisbon highways osm_file <- system.file( "extdata/samples", "osmextract_lisbon_highways_sample.pbf", package = "GTFShift" ) # Export bus lanes bus_lanes <- GTFShift::osm_bus_lanes(bbox, osm_file = osm_file) names(bus_lanes) head(bus_lanes |> dplyr::select(`osm:id`, name))
Get centerlines for OSM road network
osm_centerlines( bbox = NULL, place = NULL, osm_file = NULL, use_buildings = TRUE, venv = NA )osm_centerlines( bbox = NULL, place = NULL, osm_file = NULL, use_buildings = TRUE, venv = NA )
bbox |
bbox (Optional, if place provided). Area from which to export bus lanes. |
place |
String (Optional, if bbox provided). Place from which to export bus lanes. |
osm_file |
String (Optional). Path to a local OpenStreetMap PBF file ('.pbf'). |
use_buildings |
Boolean (Default TRUE). Uses buildings from OSM as exclusion_mask for neatnet. |
venv |
String (Default creates a new one). Python environment where neatnet will run. |
Exports road network from OpenStreetMap for given area and uses Python neatnet package to compute its centerlines.
One of bbox, place, or osm_file must be provided.
Parameter use_buildings exports building footprints from OSM for better results on
the network simplification process.
This method was adapted from uscuni.org/neatnet by Miguel Relvas Pires in the scope of his master's thesis. The full code (Python) of his work is openly available at GitHub.
sf data.frame. OSM centerlines.
## Not run: # Get sample OSM extract osm_file <- system.file("extdata/samples", "relation_6384187.pbf", package = "GTFShift") network <- GTFShift::osm_centerlines( place = "Arroios, Lisboa, Portugal", osm_file = osm_file ) head(network) table(network$X_status) ## End(Not run)## Not run: # Get sample OSM extract osm_file <- system.file("extdata/samples", "relation_6384187.pbf", package = "GTFShift") network <- GTFShift::osm_centerlines( place = "Arroios, Lisboa, Portugal", osm_file = osm_file ) head(network) table(network$X_status) ## End(Not run)
Get OSM routes that match shapes, based on geometrical match
osm_shapes_match_routes( gtfs, q, geometry = TRUE, gtfs_match = "route_short_name", osm_match = "ref", gtfs_osm_match_exact = TRUE, log_file = NA, osm_file = NULL, num_cores = 1, osm_stop_order_relaxed = FALSE, osm_route_type = "bus", metric_crs = 3857 )osm_shapes_match_routes( gtfs, q, geometry = TRUE, gtfs_match = "route_short_name", osm_match = "ref", gtfs_osm_match_exact = TRUE, log_file = NA, osm_file = NULL, num_cores = 1, osm_stop_order_relaxed = FALSE, osm_route_type = "bus", metric_crs = 3857 )
gtfs |
tidygtfs. GTFS feed. |
q |
osmdata::opq. Overpass query for transit network |
geometry |
Boolean (Default TRUE). If TRUE, returns sf object with geometry, otherwise, a simple data.frame. |
gtfs_match |
String (Default route_short_name). routes.txt attribute that identifies routes. Accepted values: route_id, route_short_name, route_long_name. |
osm_match |
String (Default ref). OSM attribute that identifies routes by matching with gtfs_match. Accepted values: ref, name, gtfs:route_id. |
gtfs_osm_match_exact |
Boolean (Default TRUE). If TRUE, gtfs and route names are matched strictly. Otherwise, partial string match is considered (all words in gtfs_match must be in osm_match, ignoring case). |
log_file |
String (Optional). If provided, will log warnings to this file, in addition to the console. |
osm_file |
character (Optional). Location of OSM extract file with |
num_cores |
Integer (Default 1). Number of cores to use for parallel computation. Only supported on Unix-like systems (Linux, macOS). |
osm_stop_order_relaxed |
Boolean (Default FALSE). If TRUE, OSM routes with entry/exit stops not respecting the right order will still be matched (this may indicate OSM data integrity problems). If FALSE, these routes will be ignored. |
osm_route_type |
character (Default "bus"). OSM route type. Used to query OSM network (e.g., 'bus', 'train'). |
metric_crs |
Integer or character (Default 3857). Projected CRS used to compute shapes and routes lengths and stop-to-stop distances. |
For each route, matches its trips' shapes with OSM route relations.
The matching algorithm is formulated as follows:
Let be a GTFS route identifier.
1. Filtering and Base Data Selection:
Let be the set of candidate OSM route relations matching the identifier
(based on gtfs_match and osm_match).
If is empty, route is skipped.
Unless osm_stop_order_relaxed = TRUE, any relation in with entry/exit stops not in the correct order is discarded.
We also retrieve the set of GTFS shapes associated with route , denoted as .
2. Feature Extraction:
For each GTFS shape :
Extract the start and end coordinates of its trips' first and last stops: and .
Compute the shape's total length and the number of stop times .
For each candidate OSM route relation :
Extract the coordinates of the first and last stops/platforms: and .
Compute the relation's geometry length and the number of stop/platform nodes .
3. Closeness Metric Evaluation:
For each GTFS shape , we calculate the closeness metric for all candidate OSM routes :
where:
is the Euclidean distance.
The term represents the average distance between stops on the GTFS shape, serving as a scale factor for the difference in the number of stops.
Shape is associated with the OSM route that minimizes the closeness metric:
4. Conflict Resolution:
If multiple GTFS shapes are associated with the same OSM route , only the shape that minimizes the closeness metric is retained. The other conflicting shapes are ignored and a warning is triggered.
Be aware that the result might ignore some GTFS routes, in the following cases:
If there is no OSM route relation that matches the GTFS route identifier;
If, for a GTFS route, there is any OSM route relation that has entry/exit stops not respecting the right order (unless osm_stop_order_relaxed is set to TRUE);
If, for the same route, distinct shapes are associated to the same OSM route. In that case, only the shape that minimizes the closeness metric is retained.
If any of these errors occurs, warnings will be thrown at end of the method execution, and those GTFS route will be ignored in the results.
Nevertheless, provided there are enough OSM routes, all the GTFS shapes for each route will necessarily be associated with
an OSM one. This might generate wrong results if the topology of routes on OSM does not match the GTFS shapes for that route.
Refer to distance_diff, points_diff and stops_diff on the results table to validate the results and identify misassociations.
data.frame. Matched routes (sf if geometry=TRUE) with the following columns:
The route_id attribute from routes.txt file.
The shape_id attribute from shapes.txt file.
The osm_id attribute from OSM route relation.
The difference, in meters, between GTFS shape and OSM route lengths.
The sum of the difference, in meters, between GTFS shape and OSM route start and end points.
The difference between GTFS and OSM routes number of stops.
The route_short_name attribute from routes.txt file.
The route_long_name attribute from routes.txt file.
The ref attribute from OSM route relation.
The name attribute from OSM route relation.
The geometrical data for the OSM route relation.
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift" )) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Get OSM route geometries based on geometrical match shapes_osm_routes <- GTFShift::osm_shapes_match_routes( gtfs, q, osm_file = osm_file, metric_crs = 3763, # Make sure to addapt to the projection that better suits your location ) head(shapes_osm_routes |> dplyr::select(shape_id, osm_id, distance_diff, points_diff, stops_diff))# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift" )) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Get OSM route geometries based on geometrical match shapes_osm_routes <- GTFShift::osm_shapes_match_routes( gtfs, q, osm_file = osm_file, metric_crs = 3763, # Make sure to addapt to the projection that better suits your location ) head(shapes_osm_routes |> dplyr::select(shape_id, osm_id, distance_diff, points_diff, stops_diff))
Get OSM routes geometry considering gtfs:shape_id match
osm_shapes_to_routes( gtfs, q, ways = FALSE, ways_tags = c("lanes", "psv", "bus", "way", "parking", "name"), osm_file = NULL, osm_route_type = "bus" )osm_shapes_to_routes( gtfs, q, ways = FALSE, ways_tags = c("lanes", "psv", "bus", "way", "parking", "name"), osm_file = NULL, osm_route_type = "bus" )
gtfs |
tidygtfs. GTFS feed. |
q |
osmdata::opq. Overpass query for transit network. |
ways |
boolean (Default False). If true, relation is disaggregated in ways. |
ways_tags |
character vector (Default |
osm_file |
character (Optional). Location of OSM extract file with |
osm_route_type |
character (Default "bus"). OSM route type. Used to query OSM network (e.g., 'bus', 'train'). |
For each route, matches its trips' shapes with OSM route relations, considering the
OSM gtfs:shape_id attribute.
sf data.frame. Matched shape to route geometries with the following columns:
The shape_id attribute from shapes.txt file.
The osm_id attribute from OSM route relation.
The osm_id attribute from OSM way (if ways parameter is set to true).
Any column that matches ways_tags parameter.
The geometrical data for the OSM route relation.
Shapes that do not have a match on OSM are ignored. If that occurs, a warning is displayed during the method execution, informing about the missing geometries.
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Get OSM route geometries based on gtfs:shape_id match shapes_osm_routes <- GTFShift::osm_shapes_to_routes( gtfs, q, osm_file = osm_file ) head(shapes_osm_routes |> dplyr::select(shape_id, osm_id)) nrow(shapes_osm_routes) # Get OSM ways instead shapes_osm_ways <- GTFShift::osm_shapes_to_routes( gtfs, q, osm_file = osm_file, ways = TRUE ) head(shapes_osm_ways |> dplyr::select(way_osm_id, shape_id, osm_id)) nrow(shapes_osm_ways)# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("1", "2", "3", "4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Get OSM route geometries based on gtfs:shape_id match shapes_osm_routes <- GTFShift::osm_shapes_to_routes( gtfs, q, osm_file = osm_file ) head(shapes_osm_routes |> dplyr::select(shape_id, osm_id)) nrow(shapes_osm_routes) # Get OSM ways instead shapes_osm_ways <- GTFShift::osm_shapes_to_routes( gtfs, q, osm_file = osm_file, ways = TRUE ) head(shapes_osm_ways |> dplyr::select(way_osm_id, shape_id, osm_id)) nrow(shapes_osm_ways)
For each OSM way with GTFS service, aggregates its characteristics to assist in the bus lane implementation prioritisation
prioritise_lanes( gtfs, q, date = GTFShift::calendar_nextBusinessWednesday(), keep_osm_attributes = FALSE, osm_file = NULL )prioritise_lanes( gtfs, q, date = GTFShift::calendar_nextBusinessWednesday(), keep_osm_attributes = FALSE, osm_file = NULL )
gtfs |
tidygtfs. GTFS feed. |
q |
osmdata::opq. Overpass query for transit network, to obtain OSM route ways, using |
date |
Date (Default |
keep_osm_attributes |
Boolean (Default FALSE). Whether to keep all OSM way attributes in the output |
osm_file |
character (Optional). Location of OSM extract file with |
This method analyses the GTFS feed for a representative day, returning a data.frame with the road segments where transit routes run and for each, a set of parameters that can be used to prioritise bus lane implementations.
Its functionality is a bundle that encapsulates the logic of several methods from the package,
including GTFShift::get_way_frequency_hourly() and GTFShift::osm_bus_lanes(), that can be used separately if needed.
Mind that this method uses GTFShift::get_way_frequency_hourly() to match routes with OSM ways, which requires that the
OSM relation mapping is well defined for the transit routes. Routes that do not have an OSM match are ignored.
sf data.frame. Prioritised lanes with the following columns:
The osm_id attribute from OSM way.
The hour for which the frequency applies (24 hour format).
The number of services for the route that depart from the first stop for the corresponding 60 minutes period.
Whether the way has a bus lane.
The number of parking lanes.
The number of circulation lanes.
The number of travel directions.
The number of circulation lanes per direction.
The list of route_id that use the way.
The list of shape_id that use the way.
The route shape.
keep_osm_attributes = TRUE)All OSM way attributes.
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") lane_prioritisation <- GTFShift::prioritise_lanes( gtfs, q, osm_file = osm_file, date = gtfs$calendar$start_date[1] ) head( lane_prioritisation |> dplyr::select(way_osm_id, hour, frequency, is_bus_lane, n_lanes_circulation, routes) )# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") lane_prioritisation <- GTFShift::prioritise_lanes( gtfs, q, osm_file = osm_file, date = gtfs$calendar$start_date[1] ) head( lane_prioritisation |> dplyr::select(way_osm_id, hour, frequency, is_bus_lane, n_lanes_circulation, routes) )
Projects point geometries to the closest location along a single LINESTRING or MULTILINESTRING and estimates each projected point position as cumulative distance from the start of the line.
project_points_along_geometry( geometry, points, geometry_sample_meters = 10, metric_crs = 3857 )project_points_along_geometry( geometry, points, geometry_sample_meters = 10, metric_crs = 3857 )
geometry |
sf or sfc object with exactly one linear geometry (LINESTRING or MULTILINESTRING). |
points |
sf or sfc object with point geometries to be projected. |
geometry_sample_meters |
Numeric (Default 10). Sampling step used to discretize the line when estimating cumulative distance along geometry. |
metric_crs |
Integer or character (Default 3857). Projected CRS used to compute nearest points, line sampling, and cumulative distances. |
The function first computes nearest points from each input point to
geometry with sf::st_nearest_points(), keeping the point on the
line. Then, it samples the line at regular intervals and assigns cumulative
distance by nearest sampled location.
Distances are always computed in metric_crs units. The returned
projected points are transformed back to the original geometry CRS.
data.frame. Input points projected along geometry with four columns:
An sfc_POINT column with the projected location on the line.
Numeric distance from each input point to its projected location on the line.
Numeric cumulative distance from the line start to the projected location.
Numeric cumulative distance from the line end to the projected location.
If points is empty, returns an empty data.frame with the same columns.
# Get sample points from GTFS-RT collection rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) points <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) |> dplyr::sample_n(5) head(points |> dplyr::select(geometry)) # Get route geometry for points osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(route_id %in% points$route_id) head(osm_routes) # Project points to geometry points_projected <- GTFShift::project_points_along_geometry( geometry = osm_routes, points = points, metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) head(points_projected)# Get sample points from GTFS-RT collection rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) points <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) |> dplyr::sample_n(5) head(points |> dplyr::select(geometry)) # Get route geometry for points osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(route_id %in% points$route_id) head(osm_routes) # Project points to geometry points_projected <- GTFShift::project_points_along_geometry( geometry = osm_routes, points = points, metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) head(points_projected)
Query Mobility Database API for GTFS feeds
query_mobilitydatabase( access_token = NA, refresh_token = NA, bounding_filter_method = "partially_enclosed", limit = 10, offset = 0, country_code = NA, subdivision_name = NA, municipality = NA, bbox = NA, is_official = NA )query_mobilitydatabase( access_token = NA, refresh_token = NA, bounding_filter_method = "partially_enclosed", limit = 10, offset = 0, country_code = NA, subdivision_name = NA, municipality = NA, bbox = NA, is_official = NA )
access_token |
String (Optional when refresh_token). Access token. |
refresh_token |
String (Optional when access_token). Refresh token. |
bounding_filter_method |
String (Default partially_enclosed). Filtering method to use with the dataset_latitudes and dataset_longitudes parameters. |
limit |
Integer (Default 10). The number of items to be returned. |
offset |
Integer (Default 0). Offset of the first item to return. |
country_code |
String (Optional). Filter feeds by their exact country code. |
subdivision_name |
String (Optional). List only feeds with the specified value. Can be a partial match. |
municipality |
String (Optional). List only feeds with the specified value. Can be a partial match. Case insensitive. |
bbox |
bbox (Optional). Area from which to get GTFS feeds. Converted to API dataset_latitudes and dataset_longitudes URL parameters. |
is_official |
Boolean (Optional). If TRUE, only return official feeds. |
This method queries Mobility Database API, allowing to get a list of GTFS feeds documented at this platform.
To use it, an access or a refresh token must be provided.
It can be obtained for free at Mobility Database website.
For more details on the parameters, refer to https://mobilitydata.github.io/mobility-feed-api/SwaggerUI/index.html#/feeds/getGtfsFeeds.
Some useful columns of the returned data.frame (refer to the API documentation for a full list) are:
The name of the GTFS provider.
Tells if the feed is active, inactive or deprecated.
The GTFS feed URL. Can be used to download.
data.frame. Query results from Mobility Database.
feeds <- GTFShift::query_mobilitydatabase( refresh_token = Sys.getenv("MOBILITY_DATABASE"), country_code = "PT", is_official = TRUE ) head(feeds |> dplyr::select(id, provider, producer_url))feeds <- GTFShift::query_mobilitydatabase( refresh_token = Sys.getenv("MOBILITY_DATABASE"), country_code = "PT", is_official = TRUE ) head(feeds |> dplyr::select(id, provider, producer_url))
Projects each real-time vehicle position to its corresponding trip geometry, computes cumulative distance along the geometry, and derives segment speed between consecutive updates.
rt_average_speed( rt_collection, trips_geometries, rt_collection_trips_geometries_match_col = "trip_id", geometry_sample_meters = 10, metric_crs = 3857 )rt_average_speed( rt_collection, trips_geometries, rt_collection_trips_geometries_match_col = "trip_id", geometry_sample_meters = 10, metric_crs = 3857 )
rt_collection |
sf data.frame with GTFS-RT updates for multiple trips.
Must include at least |
trips_geometries |
sf data.frame with trip geometries. Geometry must be LINESTRING. |
rt_collection_trips_geometries_match_col |
Character (Default
|
geometry_sample_meters |
Numeric (Default 10). Sampling step used when projecting points along trip geometry and estimating cumulative distance. |
metric_crs |
Integer or character (Default 3857). Projected CRS used to compute distances and speeds. |
For each trip (grouped by trip_id), let
denote the ordered sequence of
real-time observations, where is the vehicle position and
the corresponding timestamp, with
. Each observation is projected onto the
trip geometry using GTFShift::project_points_along_geometry(), yielding
a projected point and two cumulative distances:
For each pair of consecutive observations , the elapsed time is
computed as
The distance increment is defined as the minimum of the forward and reversed cumulative-distance differences:
Trips with fewer than 2 updates are ignored with a warning. The distance increment is defined as the minimum of two alternative cumulative-distance differences:
The second term is a redundancy designed to avoid overstating movement on circular
geometries. In particular, after a vehicle completes a loop, a
forward comparison may treat two nearby physical positions as far apart in
cumulative distance if the geometry origin has been crossed.
Comparing against provides an auxiliary
distance candidate that helps avoid overstating movement in that situation.
Average speed is then estimated by
and reported in kilometers per hour as
Trips with fewer than two observations are ignored with a warning because
and are undefined in that case.
Method GTFShift::multiline_to_sorted_linestring() can be used to convert MULTILINESTRING
geometries to LINESTRING if needed.
sf data.frame. Object based on rt_collection, with added columns:
Projected point on trip geometry.
Distance from each update point to its projected location on the shape (meters).
Cumulative distance along trip geometry (meters).
Cumulative distance from shape end to projected location (meters).
Elapsed time since previous update (seconds).
Distance increment since previous update (meters).
Estimated speed between consecutive updates (km/h).
GTFShift::project_points_along_geometry()
GTFShift::multiline_to_sorted_linestring()
# Get GTFS-RT data collection rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) rt_collection <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) |> dplyr::select(-speed) head(rt_collection |> dplyr::select(trip_id, timestamp, geometry)) nrow(rt_collection) # Get route geometry for data collected osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(route_id %in% rt_collection$route_id) |> dplyr::mutate(geom = GTFShift::multiline_to_sorted_linestring(geom, metric_crs = 3763)) head(osm_routes) # Compute average speed (aggregated at route level) based on cumulative distance along the geometry speed <- GTFShift::rt_average_speed( rt_collection = rt_collection, trips_geometries = osm_routes, rt_collection_trips_geometries_match_col = "route_id", metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) head(speed |> dplyr::filter(!is.na(speed_kmh)) |> dplyr::select( trip_id, timestamp, speed_kmh, distance_along_geometry, distance_to_closest_on_geometry ) ) nrow(speed)# Get GTFS-RT data collection rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) rt_collection <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) |> dplyr::select(-speed) head(rt_collection |> dplyr::select(trip_id, timestamp, geometry)) nrow(rt_collection) # Get route geometry for data collected osm_routes <- sf::st_read( system.file("extdata/samples", "osm_routes_tcb.gpkg", package = "GTFShift"), quiet = TRUE ) |> dplyr::filter(route_id %in% rt_collection$route_id) |> dplyr::mutate(geom = GTFShift::multiline_to_sorted_linestring(geom, metric_crs = 3763)) head(osm_routes) # Compute average speed (aggregated at route level) based on cumulative distance along the geometry speed <- GTFShift::rt_average_speed( rt_collection = rt_collection, trips_geometries = osm_routes, rt_collection_trips_geometries_match_col = "route_id", metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) head(speed |> dplyr::filter(!is.na(speed_kmh)) |> dplyr::select( trip_id, timestamp, speed_kmh, distance_along_geometry, distance_to_closest_on_geometry ) ) nrow(speed)
Collect GTFS-RT data from a JSON feed at regular intervals
rt_collect_json( gtfs_rt_url, destination_file, header_key = "header", entity_key = "entity", fields_collect = c("id", "vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude", "vehicle.position.speed", "vehicle.timestamp", "vehicle.current_status", "vehicle.current_stop_sequence", "vehicle.stop_id"), scrape_interval = 60, log_file = NA, headers = NULL )rt_collect_json( gtfs_rt_url, destination_file, header_key = "header", entity_key = "entity", fields_collect = c("id", "vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude", "vehicle.position.speed", "vehicle.timestamp", "vehicle.current_status", "vehicle.current_stop_sequence", "vehicle.stop_id"), scrape_interval = 60, log_file = NA, headers = NULL )
gtfs_rt_url |
String. URL of the GTFS-RT feed in JSON format. |
destination_file |
String. File to save the downloaded GTFS-RT data. Content is appended in each iteration. |
header_key |
String (Default "header"). Key in the JSON corresponding to the feed header. Set to NA if not present. |
entity_key |
String (Default "entity"). Key in the JSON corresponding to the feed entities. Set to NA if response is a flat list. Use "." for nested keys. |
fields_collect |
Character vector. Fields to extract from each entity in the feed. Use "." for nested keys. |
scrape_interval |
Integer (Default 60). Interval in seconds between each download. Negative to run only once. |
log_file |
String (Optional). Path to a log file to save download logs. |
headers |
Named list or character vector (Optional). Custom HTTP headers for credentials when accessing the GTFS-RT feed URL. |
Downloads GTFS-RT data from the specified URL at regular intervals and saves them to the destination file.
This function will run indefinitely until manually stopped (CTRL + C).
String. The location of the file where data was collected.
# Create file destination_file <- withr::local_tempfile(fileext = ".csv") # Collect data GTFShift::rt_collect_json( gtfs_rt_url = "https://go.tmlmobilidade.pt/hub/api/v1/realtime/vehicles/positions/gtfs", entity_key = "data.entity", destination_file = destination_file, scrape_interval = -1 # Negative to run only once ) # Read data collection <- read.csv(destination_file) names(collection) head( collection |> dplyr::select("vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude") )# Create file destination_file <- withr::local_tempfile(fileext = ".csv") # Collect data GTFShift::rt_collect_json( gtfs_rt_url = "https://go.tmlmobilidade.pt/hub/api/v1/realtime/vehicles/positions/gtfs", entity_key = "data.entity", destination_file = destination_file, scrape_interval = -1 # Negative to run only once ) # Read data collection <- read.csv(destination_file) names(collection) head( collection |> dplyr::select("vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude") )
Collect GTFS-RT data from a Protocol Buffers feed at regular intervals
rt_collect_protobuf( gtfs_rt_url, destination_file, fields_collect = c("id", "vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude", "vehicle.position.speed", "vehicle.timestamp", "vehicle.current_status", "vehicle.current_stop_sequence", "vehicle.stop_id"), scrape_interval = 60, log_file = NA, headers = NULL )rt_collect_protobuf( gtfs_rt_url, destination_file, fields_collect = c("id", "vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude", "vehicle.position.speed", "vehicle.timestamp", "vehicle.current_status", "vehicle.current_stop_sequence", "vehicle.stop_id"), scrape_interval = 60, log_file = NA, headers = NULL )
gtfs_rt_url |
String. URL of the Protocol Buffers GTFS-RT feed. |
destination_file |
String. File to save the downloaded GTFS-RT data. Content is appended in each iteration. |
fields_collect |
Character vector. Fields to extract from each entity in the feed. |
scrape_interval |
Integer (Default 60). Interval in seconds between each download. Negative to run only once. |
log_file |
String (Optional). Path to a log file to save download logs. |
headers |
Named list or character vector (Optional). Custom HTTP headers for credentials when accessing the GTFS-RT feed URL. |
Downloads GTFS-RT data from the specified URL at regular intervals and saves them to the destination file.
This function will run indefinitely until manually stopped (CTRL + C).
String. The location of the file where data was collected.
## Not run: # Create file destination_file <- withr::local_tempfile(fileext = ".csv") # Collect data GTFShift::rt_collect_protobuf( gtfs_rt_url = "https://go.tmlmobilidade.pt/hub/api/v1/realtime/vehicles/positions/gtfs.pb", destination_file = destination_file, scrape_interval = -1 # Negative to run only once ) # Read data collection <- read.csv(destination_file) names(collection) head( collection |> dplyr::select("vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude") ) ## End(Not run)## Not run: # Create file destination_file <- withr::local_tempfile(fileext = ".csv") # Collect data GTFShift::rt_collect_protobuf( gtfs_rt_url = "https://go.tmlmobilidade.pt/hub/api/v1/realtime/vehicles/positions/gtfs.pb", destination_file = destination_file, scrape_interval = -1 # Negative to run only once ) # Read data collection <- read.csv(destination_file) names(collection) head( collection |> dplyr::select("vehicle.trip.trip_id", "vehicle.position.latitude", "vehicle.position.longitude") ) ## End(Not run)
This function extends lane segment indicators for prioritisation with speed metrics produced with GTFS-RT data.
rt_extend_prioritisation( lane_prioritisation, rt_collection, rt_current_status = c("IN_TRANSIT_TO"), lane_buffer = 15, metric_crs = 3857 )rt_extend_prioritisation( lane_prioritisation, rt_collection, rt_current_status = c("IN_TRANSIT_TO"), lane_buffer = 15, metric_crs = 3857 )
lane_prioritisation |
sf data.frame. Result of |
rt_collection |
sf data.frame. GTFS-RT data collection. Must include |
rt_current_status |
Character vector (Default |
lane_buffer |
numeric (Default 15). Buffer distance (in meters) to create around lane segments to capture nearby GTFS-RT points. |
metric_crs |
Integer or character (Default 3857). Projected CRS used to apply lane buffer distances in meters. |
Extends the lane_prioritisation data with speed metrics calculated from the GTFS-RT data points that fall within a buffer around each lane segment.
If GTFS-RT data does not provide speed information, it can be inferred from the progression of position updates through time using GTFShift::rt_average_speed().
Refer to GTFShift::rt_collect_json() or GTFShift::rt_collect_protobuf() for details on GTFS-RT data collection.
sf data.frame. Extended lane prioritisation with the following columns:
The average speed of the vehicles on the way.
The median speed of the vehicles on the way.
The 15th percentile speed of the vehicles on the way.
The 25th percentile speed of the vehicles on the way.
The 75th percentile speed of the vehicles on the way.
The 85th percentile speed of the vehicles on the way.
The number of speed observations on the way.
# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift" )) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Prioritise lanes lane_prioritisation <- GTFShift::prioritise_lanes( gtfs, q, osm_file = osm_file, date = gtfs$calendar$start_date[1] ) # Extend with GTFS-RT data collection rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) rt_collection <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) lane_prioritisation_extended <- GTFShift::rt_extend_prioritisation( lane_prioritisation = lane_prioritisation, rt_collection = rt_collection, metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) head( lane_prioritisation_extended |> sf::st_drop_geometry() |> dplyr::filter(!is.na(speed_count)) |> dplyr::select(way_osm_id, speed_avg, speed_count) )# Subset GTFS for one route only, for demo purposes gtfs <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift" )) gtfs <- GTFShift::filter_by_route_name(gtfs, c("4")) # Build query and prepare osm extract (possible to use API as alternative) q <- osmdata::opq(bbox = sf::st_bbox(tidytransit::shapes_as_sf(gtfs$shapes))) |> osmdata::add_osm_feature(key = "route", value = "bus") |> osmdata::add_osm_feature(key = "operator", value = "Transportes Colectivos do Barreiro") osm_file <- system.file("extdata/samples", "osmextract_tcb_network.pbf", package = "GTFShift") # Prioritise lanes lane_prioritisation <- GTFShift::prioritise_lanes( gtfs, q, osm_file = osm_file, date = gtfs$calendar$start_date[1] ) # Extend with GTFS-RT data collection rt_collect_file <- system.file( "extdata/samples", "gtfs_rt_sample_tcb_4_4-CS-TERM.csv", package = "GTFShift" ) rt_collection <- read.csv(rt_collect_file) |> sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326) lane_prioritisation_extended <- GTFShift::rt_extend_prioritisation( lane_prioritisation = lane_prioritisation, rt_collection = rt_collection, metric_crs = 3763 # Make sure to addapt to the projection that better suits your location ) head( lane_prioritisation_extended |> sf::st_drop_geometry() |> dplyr::filter(!is.na(speed_count)) |> dplyr::select(way_osm_id, speed_avg, speed_count) )
Merge multiple GTFS into a single aggregated file
unify( ..., prefix = FALSE, store_path = NA, create_transfers = FALSE, transfer_distance = 300, transfer_time = 120, transfer_street_routing = FALSE )unify( ..., prefix = FALSE, store_path = NA, create_transfers = FALSE, transfer_distance = 300, transfer_time = 120, transfer_street_routing = FALSE )
... |
tidygtfs[]. List of GTFS feeds. |
prefix |
Boolean (Default FALSE). If TRUE, prefixes all tables with the agency_id, to avoid conflicts in case of same IDs in different feeds. |
store_path |
String (Optional). If provided, aggregated feed zip is stored at location. The file is overwritten if it already exists. |
create_transfers |
Boolean (Default FALSE). When true, generates transfers table, aggregating close stops, even if from different GTFS. |
transfer_distance |
Integer (Default 300). Upper straight-line distance limit in meters for transfers. |
transfer_time |
Integer (Default 120). Minimum time in seconds for transfers; all values below this will be replaced with this value, particularly all those defining in-place transfers where stop longitudes and latitudes remain identical. |
transfer_street_routing |
Boolean (Default FALSE). If TRUE, transfer times are calculated by routing throughout the underlying street network (downloaded automatically). |
Aggregates multiple feeds using gtfstools::merge_gtfs().
When generating transfers, those already existing in each GTFS file are kept, extended with new ones computed based
on the stops network of the final aggregated version.
This computation is executed with gtfsrouter::gtfs_transfer_table(),
with the parameters d_limit=transfer_distance, min_transfer_time=transfer_time and
network_times=transfer_street_routing. The other parameters are applied the library default values.
For a detailed example, see the vignette("unify").
tidygtfs. The unified GTFS feed.
gtfstools::merge_gtfs()
gtfsrouter::gtfs_transfer_table()
# Load multiple GTFS files gtfs_1 <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) summary(gtfs_1) gtfs_1$agency head(gtfs_1$trips) gtfs_2 <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) summary(gtfs_2) gtfs_2$agency head(gtfs_2$trips) # Unify them unified <- GTFShift::unify(gtfs_1, gtfs_2, prefix = TRUE) summary(unified) unified$agency head(unified$trips)# Load multiple GTFS files gtfs_1 <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_tcb_sample.zip", package = "GTFShift") ) summary(gtfs_1) gtfs_1$agency head(gtfs_1$trips) gtfs_2 <- GTFShift::load_feed(system.file("extdata/samples", "gtfs_ttsl_sample_no_shapes.zip", package = "GTFShift") ) summary(gtfs_2) gtfs_2$agency head(gtfs_2$trips) # Unify them unified <- GTFShift::unify(gtfs_1, gtfs_2, prefix = TRUE) summary(unified) unified$agency head(unified$trips)