2017-04-27

Contents

  • Background
  • Using stplanr
  • Future plans

Background

Why stplanr

  • Contracted to create the Propensity to Cycle Tool (PCT)
  • Best way to understand stplanr is via a live demo, at www.pct.bike
  • Needed functions to work with origin-destination data and routing services

From a transport planner's perspective

Common computational tasks in transportation planning:

  • Access to and processing of data on transport infrastructure and behaviour
  • Analysis and visualisation of origin-destination flow data
  • Allocation of flows to the transport (road) network
  • Development of models to estimate transport behaviour
  • The calculation of 'catchment areas' affected by transport infrastructure

Transparency in transport planning

Transport planning is notoriously reliant on 'black boxes' and the same applies to scientific research into transport systems [@Waddell2002].

stplanr seeks to address these issues.

Using stplanr

Installing and loading stplanr

stplanr lives here: https://github.com/ropensci/stplanr

Package can be installed from CRAN or GitHub (see the package's README for details), it can be loaded in with library():

install.packages("stplanr") # stable CRAN version
# devtools::install_github("ropensci/stplanr") # dev version
library(stplanr) # also loads spatial package
## Loading required package: sp
  • Dev version requires rtools on Windows

Why host on ROpenSci?

Example of benefits of peer review

Use @importFrom whenever possible. Right now you have import(openxlsx) and import(sp) in your NAMESPACE file. Just import the functions you need. Same for other pkg deps.

Tests: Pleae write tests to cover at least all the major functions before we accept. Use testthat::skip_on_cran() for any tests that do web requests, so that CRAN tests don't fail in case a service is temporarily down

Addition of better API key handling

I think token's can be a bit easier for the user. Right now you have e.g.,

if (!Sys.getenv('CYCLESTREET') == "") {
    cckey <- Sys.getenv('CYCLESTREET')
}
if(is.null(cckey)){
    stop("You must have a CycleStreets.net api key saved as 'cckey'")
}

Importance of documentation: test datasets

cents SpatialPointsDataFrame.of.home.locations.for.flow.analysis.
destination_zones example destinations data
destinations example destinations data
flow data frame of commuter flows
flow_dests data frame of invented commuter flows with destinations in a different layer than the origins
flowlines SpatialLinesDataFrame of commuter flows
routes_fast SpatialLinesDataFrame of commuter flows on the travel network
routes_slow SpatialLinesDataFrame of commuter flows on the travel network
zones SpatialPolygonsDataFrame of home locations for flow analysis.

Example data: 'Flow' or OD data

data("flow", package = "stplanr")
head(flow[c(1:3, 12)])
##        Area.of.residence Area.of.workplace All Bicycle
## 920573         E02002361         E02002361 109       2
## 920575         E02002361         E02002363  38       0
## 920578         E02002361         E02002367  10       0
## 920582         E02002361         E02002371  44       3
## 920587         E02002361         E02002377  34       0
## 920591         E02002361         E02002382   7       0

Centroids data

data("cents", package = "stplanr")
as.data.frame(cents[1:3,-c(3,4)])
##       geo_code  MSOA11NM coords.x1 coords.x2
## 1708 E02002384 Leeds 055 -1.546463  53.80952
## 1712 E02002382 Leeds 053 -1.511861  53.81161
## 1805 E02002393 Leeds 064 -1.524205  53.80410

Creating a single desire line

flow_single_line = flow[4,] # select only the first line
desire_line_single = od2line(flow = flow_single_line, zones = cents)
plot(cents)
plot(desire_line_single, add = T)

What just happened?

  • We selected a single 'od-pair' (flow[4,])
  • The function od2line() matched the cents matching the lines and created a line (the hard bit)
  • How? Check the source code!
od2line
## function (flow, zones, destinations = NULL, zone_code = names(zones)[1], 
##     origin_code = names(flow)[1], dest_code = names(flow)[2], 
##     zone_code_d = NA, silent = TRUE) 
## {
##     l <- vector("list", nrow(flow))
##     if (is.null(destinations)) {
##         if (!silent) {
##             message(paste("Matching", zone_code, "in the zones to", 
##                 origin_code, "and", dest_code, "for origins and destinations respectively"))
##         }
##         for (i in 1:nrow(flow)) {
##             from <- zones@data[[zone_code]] %in% flow[[origin_code]][i]
##             if (sum(from) == 0) 
##                 warning(paste0("No match for line ", i))
##             to <- zones@data[[zone_code]] %in% flow[[dest_code]][i]
##             if (sum(to) == 0 & sum(from) == 1) 
##                 warning(paste0("No match for line ", i))
##             x <- sp::coordinates(zones[from, ])
##             y <- sp::coordinates(zones[to, ])
##             l[[i]] <- sp::Lines(list(sp::Line(rbind(x, y))), 
##                 as.character(i))
##         }
##     }
##     else {
##         if (is.na(zone_code_d)) {
##             zone_code_d <- names(destinations)[1]
##         }
##         if (!silent) {
##             message(paste("Matching", zone_code, "in the zones and", 
##                 zone_code_d, "in the destinations,\nto", origin_code, 
##                 "and", dest_code, "for origins and destinations respectively"))
##         }
##         for (i in 1:nrow(flow)) {
##             from <- zones@data[[zone_code]] %in% flow[[origin_code]][i]
##             if (sum(from) == 0) 
##                 warning(paste0("No match for line ", i))
##             to <- destinations@data[[zone_code_d]] %in% flow[[dest_code]][i]
##             if (sum(to) == 0 & sum(from) == 1) 
##                 warning(paste0("No match for line ", i))
##             x <- sp::coordinates(zones[from, ])
##             y <- sp::coordinates(destinations[to, ])
##             l[[i]] <- sp::Lines(list(sp::Line(rbind(x, y))), 
##                 as.character(i))
##         }
##     }
##     l <- sp::SpatialLines(l)
##     l <- sp::SpatialLinesDataFrame(l, data = flow, match.ID = FALSE)
##     sp::proj4string(l) <- sp::proj4string(zones)
##     l
## }
## <environment: namespace:stplanr>

A hard-coded version:

o = cents[cents$geo_code %in% flow$Area.of.residence[4],]
d = cents[cents$geo_code %in% flow$Area.of.workplace[4],]
l_single = Lines(list(Line(rbind(o@coords, d@coords))), ID = 1)
l_sp = SpatialLines(LinesList = list(l_single))

Visualising the result

plot(cents)
points(o, cex = 5)
points(d, cex = 5)
flow[4, 1:3]
##        Area.of.residence Area.of.workplace All
## 920582         E02002361         E02002371  44
plot(l_sp, add = TRUE)

Creating 'desire lines' for all flows

l <- od2line(flow = flow, zones = cents)
# remove lines with no length
l <- l[!l$Area.of.residence == l$Area.of.workplace,]
plot(l, lwd = l$All / 10)

Allocating flows to the transport network

stplanr has various functions for route allocation:

route_cyclestreet() # UK, cycling
route_graphhopper() # worldwide, any mode
route_transportapi_public() # UK, public transport
viaroute() # worldwide, any mode

Test

library(leaflet)
leaflet() %>% addTiles() %>% addPolylines(data = r)

Routing many lines

routes_fast = line2route(l = l)
plot(l)
plot(routes_fast, add = T, col = "red")

Future plans

Routing functions

  • Add more services (e.g. OpenTripPlanner)
  • Integrate interface
  • Add support for OSM data download and analysis
  • Via interface to osmdatar

Applications

  • Work with practitioners to make it more useful for them (e.g. ITP)
  • Link with industry (e.g. ITP)
  • Make more international
  • A global propensity to cycle tool?
  • Better handling of GPS data

Links and getting involved

Exercises

  • Work through the vignette provided by:
vignette("introducing-stplanr")
  • Create a single desire line for the 12th flow without using stplanr code (stplanr solution below):
od2line(flow = flow[12,], zones = cents)
  • Visualise the flowlines object using tmap with different colours and widths
  • Create a 'oneway' version of the flowlines and visualise these (tricky)
  • Create a sf version of the flowlines object (advanced)