Use tidytransit to:
This package requires a working installation of sf.
# Once sf is installed, you can install from CRAN with:
install.packages('tidytransit')
# For the development version from Github:
# install.packages("devtools")
::install_github("r-transit/tidytransit") devtools
The summary page for the GTFS standard is a good resource for more information on the standard.
GTFS feeds contain many linked tables about published transit schedules about service schedules, trips, stops, and routes. Below is a diagram of these relationships and tables:
Source: Wikimedia, user -stk.
GTFS data come packaged as a zip file of tables in text form. The first thing tidytransit does is consolidate the reading of all those tables into a single R object, which contains a list of the tables in each feed.
Below we use the tidytransit read_gtfs
function in order to read a feed from the NYC MTA into R.
We use a feed included in the package in the example below. But note that you can read directly from the New York City Metropolitan Transit Authority, as shown in the commented code below.
You can also read from any other URL. This is useful because there are many sources for GTFS data, and often the best source is transit service providers themselves. See the next section on “Finding More GTFS Feeds” for more sources of feeds.
# nyc <- read_gtfs("http://web.mta.info/developers/data/nyct/subway/google_transit.zip")
<- system.file("extdata",
local_gtfs_path "google_transit_nyc_subway.zip",
package = "tidytransit")
<- read_gtfs(local_gtfs_path) nyc
You can use summary
to get an overview of the feed.
summary(nyc)
## tidygtfs object
## files agency, stops, routes, trips, stop_times, calendar, calendar_dates, shapes, transfers
## agency MTA New York City Transit
## service from 2018-06-24 to 2018-11-03
## uses stop_times (no frequencies)
## # routes 29
## # trips 19890
## # stop_ids 1503
## # stop_names 380
## # shapes 215
Each of the source tables for the GTFS feed is now available in the nyc gtfs
object. For example, stops:
head(nyc$stops)
## # A tibble: 6 × 10
## stop_id stop_code stop_name stop_desc stop_lat stop_lon zone_id stop_url
## <chr> <chr> <chr> <chr> <dbl> <dbl> <chr> <chr>
## 1 101 "" Van Cortlandt … "" 40.9 -73.9 "" ""
## 2 101N "" Van Cortlandt … "" 40.9 -73.9 "" ""
## 3 101S "" Van Cortlandt … "" 40.9 -73.9 "" ""
## 4 103 "" 238 St "" 40.9 -73.9 "" ""
## 5 103N "" 238 St "" 40.9 -73.9 "" ""
## 6 103S "" 238 St "" 40.9 -73.9 "" ""
## # … with 2 more variables: location_type <int>, parent_station <chr>
The tables available on each feed may vary. Below we can simply print the names of all the tables that were read in for this feed. Each of these is a table.
names(nyc)
## [1] "trips" "stop_times" "agency" "calendar"
## [5] "calendar_dates" "stops" "routes" "shapes"
## [9] "transfers" "."
When reading a feed, it is checked against the GTFS specification, and an attribute is added to the resultant object called validation_result
, which is a tibble about the files and fields in the GTFS feed and how they compare to the specification.
You can get this tibble from the metadata about the feed.
<- attr(nyc, "validation_result")
validation_result head(validation_result)
## # A tibble: 6 × 8
## file file_spec file_provided_status field field_spec field_provided_…
## <chr> <chr> <lgl> <chr> <chr> <lgl>
## 1 agency req TRUE agency_id opt TRUE
## 2 agency req TRUE agency_name req TRUE
## 3 agency req TRUE agency_url req TRUE
## 4 agency req TRUE agency_time… req TRUE
## 5 agency req TRUE agency_lang opt TRUE
## 6 agency req TRUE agency_phone opt TRUE
## # … with 2 more variables: validation_status <chr>, validation_details <chr>
You can find more feeds on the following sites
There might be other feed registries available, depending on your area of interest. Transit agencies might also provide their feeds on their on website.
MobilityData.org provide a csv file with all their feed sources on their GitHub page.
You can load it in R and browse it as a data frame. Tidytransit cannot handle GTFS realtime data, so we can remove those entries.
= read.csv("https://bit.ly/catalogs-csv")
MobilityData.csv
= MobilityData.csv %>%
MobilityData_feedlist as_tibble() %>%
filter(data_type == "gtfs")
str(MobilityData_feedlist)
## tibble [1,349 × 23] (S3: tbl_df/tbl/data.frame)
## $ mdb_source_id : int [1:1349] 1 2 3 4 5 6 7 8 9 10 ...
## $ data_type : chr [1:1349] "gtfs" "gtfs" "gtfs" "gtfs" ...
## $ entity_type : chr [1:1349] "" "" "" "" ...
## $ location.country_code : chr [1:1349] "US" "CA" "CA" "US" ...
## $ location.subdivision_name : chr [1:1349] "Maine" "Ontario" "Ontario" "Ohio" ...
## $ location.municipality : chr [1:1349] "Casco Bay" "London" "Barrie" "Athens" ...
## $ provider : chr [1:1349] "Casco Bay Lines" "London Transit Commission" "Barrie Transit" "Athens Public Transit" ...
## $ name : chr [1:1349] "" "" "" "" ...
## $ note : chr [1:1349] "" "" "" "" ...
## $ static_reference : int [1:1349] NA NA NA NA NA NA NA NA NA NA ...
## $ urls.direct_download : chr [1:1349] "http://smttracker.com/downloads/gtfs/cascobaylines-portland-me-usa.zip" "http://www.londontransit.ca/gtfsfeed/google_transit.zip" "http://www.myridebarrie.ca/gtfs/Google_transit.zip" "https://transitfeeds.com/p/athens-public-transit/298/latest/download" ...
## $ urls.authentication_type : int [1:1349] NA NA NA NA NA NA NA NA NA NA ...
## $ urls.authentication_info : chr [1:1349] "" "" "" "" ...
## $ urls.api_key_parameter_name : chr [1:1349] "" "" "" "" ...
## $ urls.latest : chr [1:1349] "https://storage.googleapis.com/storage/v1/b/mdb-latest/o/us-maine-casco-bay-lines-gtfs-1.zip?alt=media" "https://storage.googleapis.com/storage/v1/b/mdb-latest/o/ca-ontario-london-transit-commission-gtfs-2.zip?alt=media" "https://storage.googleapis.com/storage/v1/b/mdb-latest/o/ca-ontario-barrie-transit-gtfs-3.zip?alt=media" "https://storage.googleapis.com/storage/v1/b/mdb-latest/o/us-ohio-athens-public-transit-gtfs-4.zip?alt=media" ...
## $ urls.license : chr [1:1349] "" "https://www.londontransit.ca/open-data/ltcs-open-data-terms-of-use/" "" "" ...
## $ location.bounding_box.minimum_latitude : num [1:1349] 43.7 42.9 44.3 39.3 44.1 ...
## $ location.bounding_box.maximum_latitude : num [1:1349] 43.7 43.1 44.4 39.4 45 ...
## $ location.bounding_box.minimum_longitude: num [1:1349] -70.2 -81.4 -79.7 -82.1 -75.8 ...
## $ location.bounding_box.maximum_longitude: num [1:1349] -70.1 -81.1 -79.6 -82 -74.6 ...
## $ location.bounding_box.extracted_on : chr [1:1349] "2022-02-22T19:51:22+00:00" "2022-02-22T19:51:34+00:00" "2022-03-01T22:43:25+00:00" "2022-03-23T14:43:11+00:00" ...
## $ status : chr [1:1349] "" "" "" "inactive" ...
## $ features : chr [1:1349] "" "" "" "" ...
There is a URL (column urls.direct_download
) for each feed, which can be used to read the feed for a given area directly into R. For example:
<- MobilityData_feedlist %>%
gtfs_path_goldengate filter(provider == "Golden Gate Transit") %>%
pull(urls.direct_download)
= read_gtfs(gtfs_path_goldengate) gtfs_goldengate
The MobilityData feedlist contains bounding box coordinates for a feed which can be used to give a basic impression of the location of feeds. Note that some bounding boxes might be wrong.
First, let’s create bounding box polygons.
# create a bounding box polygon from min/max corner coordinates
suppressPackageStartupMessages(library(sf))
= function(lon_min, lon_max, lat_min, lat_max) {
bbox_polygon = matrix(
corner_coords c(lon_min, lat_min,
lon_min, lat_max,
lon_max, lat_max,
lon_max, lat_min,
lon_min, lat_min),ncol = 2, byrow = T
)= st_polygon(list(corner_coords))
polyg return(st_sfc(polyg, crs = 4326))
}
# create bounding box polygon (only for reasonable values)
= MobilityData_feedlist %>%
MobilityData_sf filter(!is.na(location.bounding_box.minimum_longitude)) %>%
filter(location.bounding_box.minimum_latitude > -89) %>%
group_by(mdb_source_id) %>%
mutate(geometry = bbox_polygon(location.bounding_box.minimum_longitude,
location.bounding_box.maximum_longitude,
location.bounding_box.minimum_latitude,%>%
location.bounding_box.maximum_latitude)) ungroup() %>% st_as_sf()
Now show the feeds on a map:
library(leaflet)
leaflet() %>%
addProviderTiles(provider = providers$CartoDB.Positron) %>%
addPolygons(data = MobilityData_sf, weight = 2,
fillOpacity = 0.1, label = substr(MobilityData_sf$provider, 0, 60))