5.3 Routing

We are all generally familiar with routing algorithms through the ubiquity of Google Maps. In fact, Google Maps has an API that could be used from within R to generate routes between two points, but this is not a free service. At the moment, the most accessible free service (up to a certain limit) appears to be Mapbox, with which you should make an account. A free account (without a credit card entered) will allow you to do 100,000 directions calls and 100,000 isochrone calls per month, which is more than enough for the purposes of this chapter.

Once you’ve created an account, on the Accounts page, create a new token and select all the “secret scopes”. Once created, you will have one chance to note down the access key, which you should save on your computer.

Install mapboxapi, created by the same developer who created tigris and censusapi. The last two lines should be run, with your access key inserted, but you shoudln’t include these in any knitted results so as to keep your access key secret.

devtools::install_github("walkerke/mapboxapi")

library(mapboxapi)

mb_access_token("YOUR_TOKEN_HERE", install = T)
readRenviron("~/.Renviron")

Let’s use epa_od from the last section as an input for routing, where we’ll use the centroid of each tract as the origin and destination points. There are 474 pairs in total. Below, we create two separate dataframes holding the lat/long coordinates of the origin and destination tract centroids, respectively, in the format that mapboxapi will need:

epa_od_origin <-
  epa_od %>% 
  st_set_geometry(NULL) %>% 
  select(h_tract) %>% 
  left_join(ca_tracts %>% select(h_tract = GEOID)) %>% 
  st_as_sf() %>% 
  st_centroid() %>% 
  st_coordinates()

epa_od_destination <-
  epa_od %>% 
  st_centroid() %>% 
  st_coordinates()

Now, let’s demonstrate a single route using mb_directions(). Multiple additional arguments can be provided, as described in the online documentation, but the simplest version requires just the origin/destination inputs and a profile= which can be “driving”, “driving-traffic”, “cycling”, or “walking”.

route_test <-
  mb_directions(
    origin = epa_od_origin[1, ],
    destination = epa_od_destination[1, ],
    profile = "driving-traffic"
  )

route_test %>% 
  st_as_sf() %>% 
  leaflet() %>%
  addMapboxTiles(
    style_id = "streets-v11",
    username = "mapbox"
  ) %>%
  addPolylines()

Notes:

  • addMapboxTiles() is provided through mapboxapi and can be used in the leaflet pipeline to reference publicly available basemaps from MapBox, or, as is a core feature of MapBox, basemaps you create yourself using your MapBox account. You might find these preferable to the other default options we’ve used in leaflet thus far.
  • Given the distance traversed here, you might notice that the granularity of the route is not that accurate, seeming to be connecting the dots between disparate points. However, if we switch to a shorter route (EPA to Stanford), we’ll find that the route geometry is better:
route_test <-
  mb_directions(
    origin = epa_od_origin[473, ],
    destination = epa_od_destination[473, ],
    profile = "driving-traffic"
  )

route_test %>% 
  st_as_sf() %>% 
  leaflet() %>%
  addMapboxTiles(
    style_id = "streets-v11",
    username = "mapbox"
  ) %>%
  addPolylines()

This is a limitation to keep in mind. Now, let’s wrap mb_directions() in map_dfr() to loop through our full epa_od dataframe:

epa_od_route <- 
  1:nrow(epa_od_origin) %>%
  map_dfr(function(x){
    mb_directions(
      origin = epa_od_origin[x, ],
      destination = epa_od_destination[x, ],
      profile = "driving-traffic"
    )
  })
epa_od_route %>% 
  st_as_sf() %>% 
  leaflet() %>%
  addMapboxTiles(
    style_id = "light-v9",
    username = "mapbox"
  ) %>%
  addPolylines()

From here, the output geometries, or the fields duration and distance, can be used for any number of analyses, including estimates of vehicle miles traveled (incorporating ACS data to estimate the % of workers in each O-D pair who are driving vs. using other modes).