Skip to contents

Implements methods from Weiss et al. 2018, 2020 to calculate travel time from given locations over a friction surface.

Citations:

D. J. Weiss, A. Nelson, C. A. Vargas-Ruiz, K. Gligoric, S., Bavadekar, E. Gabrilovich, A. Bertozzi-Villa, J. Rozier, H. S. Gibson, T., Shekel, C. Kamath, A. Lieber, K. Schulman, Y. Shao, V. Qarkaxhija, A. K. Nandi, S. H. Keddie, S. Rumisha, P. Amratia, R. Arambepola, E. G. Chestnutt, J. J. Millar, T. L. Symons, E. Cameron, K. E. Battle, S. Bhatt, and P. W. Gething. Global maps of travel time to healthcare facilities. (2020) Nature Medicine. https://doi.org/10.1038/s41591-020-1059-1

D. J. Weiss, A. Nelson, H.S. Gibson, W. Temperley, S. Peedell, A. Lieber, M. Hancher, E. Poyart, S. Belchior, N. Fullman, B. Mappin, U. Dalrymple, J. Rozier, T.C.D. Lucas, R.E. Howes, L.S. Tusting, S.Y. Kang, E. Cameron, D. Bisanzio, K.E. Battle, S. Bhatt, and P.W. Gething. A global map of travel time to cities to assess inequalities in accessibility in 2015. (2018). Nature. https://doi.org/10.1038/nature25181

Installation

You can install traveltime with:

install.packages("traveltime", repos = c("https://idem-lab.r-universe.dev"))

Let’s calculate some travel times

First download a friction surface –— here we are using the motorised travel time from Weiss et al. 2020. We use the function traveltime::get_friction_surface, specify the surface (type) as "motor2020", and provide the spatial extent of interest:

library(traveltime)
library(terra)
#> terra 1.7.83

friction_surface <- get_friction_surface(
    surface = "motor2020",
    extent = c(111,112,0,1)
  )
#> Checking if the following Surface-Year combinations are available to download:
#> 
#>     DATASET ID  YEAR
#>   - Explorer__2020_motorized_friction_surface:  DEFAULT
#> 
#> Loading required package: sf
#> Linking to GEOS 3.12.1, GDAL 3.9.0, PROJ 9.4.0; sf_use_s2() is FALSE
#> <GMLEnvelope>
#> ....|-- lowerCorner: 0 111
#> ....|-- upperCorner: 1 112
friction_surface
#> class       : SpatRaster 
#> dimensions  : 120, 120, 1  (nrow, ncol, nlyr)
#> resolution  : 0.008333333, 0.008333333  (x, y)
#> extent      : 111, 112, 1.387779e-17, 1  (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (EPSG:4326) 
#> source      : Explorer__2020_motorized_friction_surface_0,111,1,112.tif 
#> name        : friction_surface

Let’s have a look at that SpatRaster:

plot(friction_surface)

Now, prepare points that we would like to calculate travel time from:

from_here <- tibble::tibble(
  x = c(111.2, 111.9),
  y = c(0.2, 0.35)
)
from_here
#> # A tibble: 2 × 2
#>       x     y
#>   <dbl> <dbl>
#> 1  111.  0.2 
#> 2  112.  0.35

And calculate the travel time from our points from_here over the friction surface friction_surface using the function traveltime::calculate_travel_time:

travel_time <- calculate_travel_time(
  friction_surface = friction_surface,
  points = from_here
)
travel_time
#> class       : SpatRaster 
#> dimensions  : 120, 120, 1  (nrow, ncol, nlyr)
#> resolution  : 0.008333333, 0.008333333  (x, y)
#> extent      : 111, 112, 1.387779e-17, 1  (xmin, xmax, ymin, ymax)
#> coord. ref. :  
#> source(s)   : memory
#> name        : travel_time 
#> min value   :      0.0000 
#> max value   :    582.1882

Et voila! Here is the motorised travel time in minutes for each cell, with our points in pink.

plot(travel_time)
points(from_here, pch = 19, col = "hotpink")

A more tangible example: Walking in Singapore

Let’s create a map of the walking time across the island of Singapore from the nearest MRT or LRT station.

To do this, we need:

  • a map of Singapore
  • locations of the stations

Here’s our basemap via geodata:

#install.packages("geodata")
library(geodata)
sin <- gadm(
  country = "Singapore",
  level = 0,
  path = tempdir(),
  resolution = 2
)
plot(sin)

We’re going to see how long it takes to walk home from a station, so we’ll download the walking-only friction surface this time by specifying surface = "walk2020.

We can also pass in our basemap sin, a SpatVector, directly as the extent, instead of specifying by hand as above. We’re also only interested in walking on land so we mask out areas outside of sin, that are within the extent of the raster:

library(traveltime)
library(terra)

friction_singapore <- get_friction_surface(
    surface = "walk2020",
    extent = sin
  )|> 
  mask(sin)
#> Checking if the following Surface-Year combinations are available to download:
#> 
#>     DATASET ID  YEAR
#>   - Explorer__2020_walking_only_friction_surface:  DEFAULT
#> 
#> <GMLEnvelope>
#> ....|-- lowerCorner: 1.1664 103.6091
#> ....|-- upperCorner: 1.4714 104.0858

friction_singapore
#> class       : SpatRaster 
#> dimensions  : 37, 57, 1  (nrow, ncol, nlyr)
#> resolution  : 0.008333333, 0.008333333  (x, y)
#> extent      : 103.6083, 104.0833, 1.166667, 1.475  (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (EPSG:4326) 
#> source(s)   : memory
#> varname     : Explorer__2020_walking_only_friction_surface_1.1664,103.6091,1.4714,104.0858 
#> name        : friction_surface 
#> min value   :       0.01200000 
#> max value   :       0.06192715

The the stations data set in this package contains the longitude and latitude of all LRT and MRT station exits in Singapore1.

head(stations)
#>             x        y
#> [1,] 103.9091 1.334922
#> [2,] 103.9335 1.336555
#> [3,] 103.8493 1.297699
#> [4,] 103.8508 1.299195
#> [5,] 103.9094 1.335311
#> [6,] 103.9389 1.344999

Let’s look at our data now.

Below we plot the friction surface raster friction_singapore, with the vector boundary of sin as a dashed grey line, and stations as grey points:

library(tidyterra)
#> Registered S3 method overwritten by 'tidyterra':
#>   method              from        
#>   autoplot.SpatRaster malariaAtlas
#> 
#> Attaching package: 'tidyterra'
#> The following object is masked from 'package:stats':
#> 
#>     filter
library(ggplot2)

ggplot() +
  geom_spatraster(
    data = friction_singapore
  ) +
  geom_spatvector(
    data = sin,
    fill = "transparent",
    col = "grey50",
    lty = 2
  ) +
  geom_point(
    data = stations,
    aes(
      x = x,
      y = y
    ),
    col = "grey60",
    size = 0.5
  ) +
  scale_fill_viridis_c(
    option = "A",
    na.value = "transparent",
    direction = -1
  ) +
  labs(
    fill = "Friction"
  )

OK, now we want to calculate the walking travel time in minutes across the friction surface from the nearest station exit:

travel_time_sin <- calculate_travel_time(
  friction_surface = friction_singapore,
  points = stations
)
travel_time_sin
#> class       : SpatRaster 
#> dimensions  : 37, 57, 1  (nrow, ncol, nlyr)
#> resolution  : 0.008333333, 0.008333333  (x, y)
#> extent      : 103.6083, 104.0833, 1.166667, 1.475  (xmin, xmax, ymin, ymax)
#> coord. ref. :  
#> source(s)   : memory
#> name        : travel_time 
#> min value   :           0 
#> max value   :         Inf

Et voilah — a raster of walking time from the nearest station.

contour(
  x = travel_time_sin,
  filled = TRUE,
  nlevels = 20,
  col = viridis::magma(19, direction = -1)
)