How to create interactive plots of GTFS data in R using Leaflet?
I want to create an interactive map that shows the public transport lines in a city. I'm trying to use Leaflet in R to do this (but I'm open to alternatives, suggestions?)
Data: The data for the transfer system is in GTFS format, organized as text files (.txt), which I read into R as a data frame. *
Problem: I can't find how to indicate the ID (variable shape_id) of each Poly line, so the plot will actually follow the route of each transit line. Instead, it connects the dots in random order.
Here's what I've tried so far without success:
# Download GTFS data of the Victoria Regional Transit System
tf <- tempfile()
td <- tempdir()
ftp.path <- "http://www.gtfs-data-exchange.com/agency/bc-transit-victoria-regional-transit-system/latest.zip"
download.file(ftp.path, tf)
# Read text file to a data frame
zipfile <- unzip( tf , exdir = td )
shape <- read.csv(zipfile[9])
# Create base map
basemap <- leaflet() %>% addTiles()
# Add transit layer
basemap %>% addPolylines(lng=shape$shape_pt_lon, lat=shape$shape_pt_lat,
fill = FALSE,
layerId =shape$shape_id)
I am glad to receive your comment on this.
*I know it is possible to import this data into GIS software such as QGIS to create a shapefile and then use readOGR to read the shapefile into R. Robin Lovelace shows how to do it . However, I am looking for a pure R solution. ;)
ps. Kyle Walker has an excellent introduction to interactive maps in R using Leaflet . Unfortunately, he doesn't cover polylines in this tutorial.
Your problem is not the method but one of the data: note that you downloaded 8 MB and the line file you are trying to load into Leaflet via Shiny is 5 MB. As a general principle, new methods with tiny datasets should always be tried first before scaling up. Here's what I did below to diagnose the problem and fix it.
Stage 1: Explore the data and subset it
pkgs <- c("leaflet", "shiny" # packages we'll use
, "maps" # to test antiquated 'maps' data type
, "maptools" # to convert 'maps' data type to Spatial* data
)
lapply(pkgs, "library", character.only = TRUE)
class(shape)
## [1] "data.frame"
head(shape)
## shape_id shape_pt_lon shape_pt_lat shape_pt_sequence
## 1 1-39-220 -123.4194 48.49065 0
## 2 1-39-220 -123.4195 48.49083 1
## 3 1-39-220 -123.4195 48.49088 2
## 4 1-39-220 -123.4196 48.49123 3
## 5 1-39-220 -123.4197 48.49160 4
## 6 1-39-220 -123.4196 48.49209 5
object.size(shape) / 1000000 # 5 MB!!!
## 5.538232 bytes
summary(shape$shape_id)
shape$shape_id <- as.character(shape$shape_id)
ids <- unique(shape$shape_id)
shape_orig <- shape
shape <- shape[shape$shape_id == ids[1],] # subset the data
Stage 2: Convert to Spatial* object
Is this like an object data.frame
on a map ?
state.map <- map("state", plot = FALSE, fill = TRUE)
str(state.map)
## List of 4
## $ x : num [1:15599] -87.5 -87.5 -87.5 -87.5 -87.6 ...
## $ y : num [1:15599] 30.4 30.4 30.4 30.3 30.3 ...
## $ range: num [1:4] -124.7 -67 25.1 49.4
## $ names: chr [1:63] "alabama" "arizona" "arkansas" "california" ...
## - attr(*, "class")= chr "map"
Yes , it's similar, so we can map2Spatial*
convert it using:
shape_map <- list(x = shape$shape_pt_lon, y = shape$shape_pt_lat)
shape_lines <- map2SpatialLines(shape_map, IDs = ids[1])
plot(shape_lines) # success - this plots a single line!
Stage 3: Join all the rows together
A for
loop will do this nicely. Note that we only use the first 10 rows. All lines used :2:length(ids)
for(i in 2:10){
shape <- shape_orig[shape_orig$shape_id == ids[i],]
shape_map <- list(x = shape$shape_pt_lon, y = shape$shape_pt_lat)
shape_temp <- map2SpatialLines(shape_map, IDs = ids[i])
shape_lines <- spRbind(shape_lines, shape_temp)
}
Stage 4: Plot
Using SpatialLines
objects makes the code a bit shorter - in this case, this will draw the first 10 lines:
leaflet() %>%
addTiles() %>%
addPolylines(data = shape_lines)
in conclusion
You need to process and manipulate the data before converting it to a Spatial* data type with the correct ID for plotting. maptools::map2Spatial*
, unique()
while a clever for
loop can do the trick.