Version 0.1.1 of the package stplanr has been released on CRAN. This is a major update with many new functions and a new class definition, SpatialLinesNetwork, for route planning and network analysis using igraph.

This short post, by myself and package co-author Richard Ellison, describes how stplanr can be used for transport research with a few simple examples from the package documentation. We hope that stplanr is of use to transport researchers and practitioners worldwide and encourage contributions to the development version hosted on GitHub.

Working with origin-destination data

Origin-destination (OD) data is one of the basic data sources for understanding travel behaviour. Usually OD data in R is represented by a table containing at least the following columns:

  • Origin ID: a text string identifying the zone in which journeys originate
  • Destination ID: a test string identifying the destination zone
  • Number of trips: the rate of travel between the unique OD pair

Additional columns can provide a break-down by trip type such as by mode of travel (e.g. car) and time of day. A sample of this data (also referred to as ‘Flow data’ by some statistical organsiations) is provided in the example dataset flows, as illustrated in the Table below.


## Loading required package: sp

knitr::kable(flow[1:3,c(1, 2, 3, 13)])
Area.of.residence Area.of.workplace All On.foot
920573 E02002361 E02002361 109 59
920575 E02002361 E02002363 38 4
920578 E02002361 E02002367 10 1

To link this data to geographical space we use a dataset stored as a SpatialPointsDataFrame from the sp package in cents:


To link the flow data we can use the command od2line() to create SpatialLinesDataFrame:

odlines <- od2line(flow = flow, zones = cents)
plot(odlines, add = TRUE)

Note that the function also accepts a SpatialPolygonsDataFrame as an input by setting the line start and end point to the zone’s geographic centroid:

odlines <- od2line(flow = flow, zones = zones)
plot(odlines, add = TRUE)

To gain a basic understanding of the rate of travel in this simple travel system, we can plot the odlines with width proportional to the number of people travelling:

plot(odlines, lwd = odlines$All / mean(odlines$All) * 3, col = "red")
plot(odlines, lwd = odlines$On.foot / mean(odlines$All) * 3, col = "green", add = T)

In the resulting plot the total rate of travel is represented by the width of red lines. The proportion of people who walk is illustrated by the relationship between the width of the green and red lines. We can use this data to explore the relationship between walking and distance:

odlines <- spTransform(odlines, CRS("+init=epsg:27700"))
odlines$dist <- rgeos::gLength(odlines, byid = T)
plot(odlines$dist, odlines$On.foot / odlines$All)

# fit a model to the curve
m <- lm(On.foot / All ~ dist, odlines@data)
lines(odlines$dist, m$fitted.values)


## Call:
## lm(formula = On.foot/All ~ dist, data = odlines@data)
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.26915 -0.06987 -0.00694  0.06190  0.63195 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.365e-01  4.503e-02  11.915 8.36e-16 ***
## dist        -1.409e-04  2.501e-05  -5.633 9.64e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Residual standard error: 0.1585 on 47 degrees of freedom
## Multiple R-squared:  0.403,  Adjusted R-squared:  0.3903 
## F-statistic: 31.73 on 1 and 47 DF,  p-value: 9.638e-07

This is useful information: we can see a clear negative relationship between the distance of the trip (in metres) and the proportion who are willing to make the journey on foot.

Working with route-allocated ‘flow’ data

stplanr includes functions for allocating OD pairs to the transport network, including route_cyclestreet(), route_graphhopper() and, most rececently viaroute() which provides an R interface to the superfast OSRM routing API. This is useful because roads rarely take you directly from origin to destination, as illustrated below for the trip from Leeds to London one could take to to attend the upcoming GISRUK conference:

route <- route_cyclestreet("Leeds", "Greenwich")
tiles <- read_osm(bb(route, ext = 2))
tm_shape(tiles) +
  tm_raster() +
  tm_shape(route) +

We can allocate all of the OD pairs in odlines to the transport network using these functions. The routes_fast dataset, for example, was created using line2route() and represents the rastest route that a cyclist may take, according to the API. A sample of this dataset is illustrated below:

routes_fast$weight <- c(5, 10)
plot(routes_fast[1:2,], lwd = routes_fast$weight)

Note that there is some overlap between the two lines above. It is sometimes useful to take aggregate statistics for the attributes of overlapping lines, for example to estimate the number of people using any particular part of the transport network. This can be acheived using Barry Rowlingson’s function overline():

rnet <- overline(routes_fast[1:2,], attrib = "weight", fun = sum)

Note that in the above plot the final segment to the east has a weight value that is the sum of the two overlapping lines in routes_fast[1:2,]: 5 + 10 = 15. We can verify this with Barry’s neat function

plot(rnet, lwd = rnet$weight, col = "red")
lineLabels(rnet, "weight")

## Other functions

There are many other functions designed to help transport researchers in stplanr. These include:

  • read_stats19* functions which import and format UK ‘Stats19’ road traffic casualty data
  • calc_catchment* functions for calculating transport ‘catchment areas’ using buffers around transport facilities
  • gtfs2sldf() for reading-in Google’s GTFS format into R
  • toptail* functions for removing the beginning and ends of SpatialLines objects

The use of the calc_catchment* functions can be illustrated using some simple data from Sydney showing the potential catchment of a possible separated cycle paths. First we import the data that we want to use:


## rgdal: version: 1.1-3, (SVN revision 594)
##  Geospatial Data Abstraction Library extensions to R successfully loaded
##  Loaded GDAL runtime: GDAL 1.11.2, released 2015/02/10
##  Path to GDAL shared files: /usr/share/gdal/1.11
##  Loaded PROJ.4 runtime: Rel. 4.8.0, 6 March 2012, [PJ_VERSION: 480]
##  Path to PROJ.4 shared files: (autodetected)
##  Linking to sp version: 1.2-2

data_dir <- system.file("extdata", package = "stplanr")
unzip(file.path(data_dir, ''))
unzip(file.path(data_dir, ''))
sa1income <- readOGR(".","smallsa1") # Import some population data

## OGR data source with driver: ESRI Shapefile 
## Source: ".", layer: "smallsa1"
## with 638 features
## It has 19 fields

testcycleway <- readOGR(".","testcycleway") # Import the path of the cycleways to test

## OGR data source with driver: ESRI Shapefile 
## Source: ".", layer: "testcycleway"
## with 2 features
## It has 2 fields

We can then use our population data and the path of the cycleways to estimate the population catchment for a given distance. If our population layer contains fields with multiple subsets of data for which we want to calculate the catchment area (e.g., men, women and children), we can calculate the individual catchments. For this example, we will simply use the ‘Total’ field containing the total population:

cycle_catchment <- calc_catchment(
  polygonlayer = sa1income, # The SpatialPolygonsDataFrame containing the population data
  targetlayer = testcycleway, # The Spatial* object containing the transport infrastructure of interest
  calccols = c('Total'), # The columns to summarise
  distance = 500, # The desired distance,
  projection = 'austalbers', # The projection to use for calculating the area
  dissolve = TRUE # Collapse all the population zones into a single polygon for the catchment
cycle_catchment$Total # Print the total catchment population

## [1] 23944.32

We can also plot the catchment area and the cycle paths. You will notice that in this example, there are gaps in the buffers. These gaps are because of the gaps in the population layer where Sydney harbour passes through the area. To take into account the road network and not simply straight-line distance, we can use the calc_network_catchment function.

plot(testcycleway, col="red", add=TRUE, lwd=2)

The toptail functionality is useful for removing the beginning and ends of SpatialLines, both for improving aestetchics of plots and for ensuring that lines do not overlap. This functionality is illustrated below using the routes_fast data.

proj4string(routes_fast) <- CRS("+init=epsg:4326")
rf_toptailed <- toptail(routes_fast, toptail_dist = 300)
plot(routes_fast, col = "red", lwd = 5)
plot(rf_toptailed, add = T)

The package vignette contains some further illustrations of stplanr’s functions which we plan to improve on over time. While become almost ‘industry standard’ in fields such as diverse as genetics, astronomy and epidemiology, R has received limited attention in transport planning. We believe that there is great potential for R, via new packages such as stplanr, to help solve real world transport problems such as estimating the geographical distribution of cycling potential.

The ‘sustainable’ in the package name relates to the emphasis on low-carbon modes in the package such as cycling and public transport. There is a huge amount of work to be done to plan for a transition away from fossil fuels in the sector, for health and environmental reasons. In this context we hope that software such as stplanr contributes to the evidence base needed to design better transport systems.