header-includes:
- \usepackage{fancyhdr}
- \pagestyle{fancy}
- \fancyhead[CO,CE]{Propensity to Cycle Tool Training Course}
- \fancyfoot[CO,CE]{For source code and support see github.com/ITSLeeds and github.com/npct}
- \fancyfoot[LE,RO]{\thepage}
output: pdf_document
# output as .R script
knitr::purl("vignettes/pct_training.Rmd")
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  out.width = "50%",
  cache = TRUE,
  eval = FALSE
)
rbbt::bbt_update_bib(path_rmd = "vignettes/pct_training.Rmd")

Introduction

This guide supports workshops on advanced usage and development of the Propensity to Cycle Tool (PCT). Beginner and intermediate PCT events focus on using the PCT via the web application hosted at www.pct.bike and the data provided by the PCT in QGIS.

The focus here is on analysing cycling potential in the open source statistical programming language R. We use R because the PCT was developed in, and can be extended with, R code. Using open source software with a command-line interface reduces barriers to entry, enabling the development of open access transport models for more citizen-led and participatory transport planning, including integration with the A/B Street city simulation and editing software [@lovelace_open_2021].

The guide covers:

See the marked-up version of this vignette online at the following URL:

https://github.com/ITSLeeds/pct/releases/download/0.8.1/Propensity.to.Cycle.Tool.Advanced.Workshop.html

Preparation

If you are new to R, you should install R and RStudio before the course. For instructions on that, see the download links at cran.r-project.org and RStudio.com.

R is a powerful statistical programming language for data science and a wide range of other applications and, like any language, takes time to learn. To get started we recommend the following free resources:

If you want to calculate cycle routes from within R, you are recommended to sign-up for a CycleStreets API key. See here to apply and see here for instructions on creating a 'environment variable' (recommended for experienced R users only).

It may also be worth taking a read about the PCT if you're not familiar with it before the course starts.

Prior reading

In addition to computer hardware (a laptop) and software (an up-to-date R set-up and experience using R) pre-requisites, you should have read, or at least have working knowledge of the contents of, the following publications, all of which are freely available online:

Prerequisites

To ensure your computer is ready for the course, you should be able to run the following lines of R code on your computer:

install.packages("remotes")
pkgs = c(
  "cyclestreets",
  "mapview",
  "pct",
  "sf",
  "stats19",
  "stplanr",
  "tidyverse",
  "devtools"
)
remotes::install_cran(pkgs)
# remotes::install_github("ITSLeeds/pct")

To test your computer is ready to work with PCT data in R, you can also try running the code hosted at https://raw.githubusercontent.com/ITSLeeds/pct/master/inst/test-setup.R to check everything is working:

source("https://github.com/ITSLeeds/pct/raw/master/inst/test-setup.R") 

If you have any questions before the workshop, feel free to ask a question on the package's issue tracker (requires a GitHub login): https://github.com/itsleeds/pct/issues

How the PCT works and what you can use it for

# u = "https://raw.githubusercontent.com/npct/pct-team/master/flow-model/flow-diag2.png"
# f = "vignettes/flow-diag2.png"
# download.file(u, f)
# knitr::include_graphics("flow-diag2.png")
# knitr::include_graphics("https://raw.githubusercontent.com/npct/pct-team/master/flow-model/flow-diag2.png")
knitr::include_graphics("https://user-images.githubusercontent.com/1825120/130079051-378fcaf1-9cac-4768-a42e-28c287497618.png")

The PCT provides data at 4 geographic levels:

Which types of data are most appropriate to tackle each of the questions/problems you identified?

Getting and exploring PCT data

In this section you will learn about the open datasets provided by the PCT project and how to use them. While the most common use of the PCT is via the interactive web application hosted at www.pct.bike, there is much value in downloading the data, e.g. to identify existing cycling infrastructure in close proximity to routes with high potential, and to help identify roads in need of interventions from a safety perspective, using data from the constantly evolving and community-driven global geographic database OpenStreetMap (OSM) [@barrington-leigh_world_2017].

In this session, which assumes you know how to have experience using QGIS, or R, you will learn how to:

Getting PCT data

We will get PCT data at the MSOA level and plot the result in a simple map.

library(pct)
library(sf)      # key package for working with spatial vector data
library(dplyr)   # in the tidyverse
library(tmap)    # installed alongside mapview
region_name = "isle-of-wight"
zones_all = get_pct_zones(region_name, geography = "msoa")
lines_all = get_pct_lines(region_name, geography = "msoa")
routes_all = get_pct_routes_fast(region_name, geography = "msoa")
rnet_all = get_pct_rnet(region_name)
plot(zones_all$geometry)
plot(lines_all$geometry, col = "blue", add = TRUE)
plot(routes_all$geometry, col = "green", add = TRUE)
plot(rnet_all$geometry, col = "red", lwd = sqrt(rnet_all$bicycle), add = TRUE)

Visualising PCT data

At its heart, the PCT is a data visualisation tool.

tm_shape(rnet_all) +
  tm_lines(lwd = "dutch_slc", scale = 9)
# tmap_mode("plot")
tm_shape(zones_all) +
  tm_polygons("all") +
  tm_shape(rnet_all) +
  tm_lines(lwd = c("bicycle", "dutch_slc"), scale = 9)
# interactive plot
tmap_mode("view")
tm_shape(rnet_all) +
  tm_lines(lwd = "dutch_slc", scale = 9)
# basic plot
max_distance = 7
# plot(zones_all$geometry)
# plot(lines_all$geometry[lines_all$all > 500], col = "red", add = TRUE)

# create 'active' desire lines (less than 5 km)
active = lines_all %>% 
  mutate(`Percent Active` = (bicycle + foot) / all * 100) %>% 
  filter(e_dist_km < max_distance)

tm_shape(active) +
  tm_lines("Percent Active", palette = "RdYlBu", lwd = "all", scale = 9)
# Create car dependent desire lines
car_dependent = lines_all %>% 
  mutate(`Percent Drive` = (car_driver) / all * 100) %>% 
  filter(e_dist_km < max_distance)
tm_shape(car_dependent) +
  tm_lines("Percent Drive", palette = "-RdYlBu", lwd = "all", scale = 9)

Advanced: visualise the PCT data using a range of visualisation techniques. For inspiration, check out the Making maps with R chapter of Geocomputation with R.

Exploring PCT data

z = zones_all %>% 
  select(geo_code, geo_name, all, foot, bicycle, car_driver)
# the solution:
# View(z)
z_highest_cycling = z %>% 
  top_n(n = 1, wt = bicycle)
plot(z$geometry)
plot(z_highest_cycling$geometry, col = "red", add = TRUE)
# Aim: get top 5 cycle routes
l_msoa = lines_all %>% 
  select(geo_code1, geo_code2, all, foot, bicycle, car_driver, rf_avslope_perc, rf_dist_km)
# View(l)
l = l_msoa
l_top_cycling = l %>% 
  top_n(n = 5, wt = bicycle)
plot(z$geometry)
plot(st_geometry(l_top_cycling), add = TRUE, lwd = 5, col = "green")

# top 5 driving routes
l_top_driving = l %>% 
  top_n(n = 5, wt = car_driver)
plot(z$geometry)
plot(st_geometry(l_top_driving), add = TRUE, lwd = 5, col = "red")

# summary(sf::st_length(l_top_cycling))
# summary(sf::st_length(l_top_driving))

# library(tmap)
# tmap_mode("view")
# tm_shape(l_top_cycling) + tm_lines("green", lwd = 7) + tm_basemap(server = leaflet::providers$OpenStreetMap.BlackAndWhite)
# tm_shape(l_top_driving) + tm_lines("red", lwd = 7) + tm_basemap(server = leaflet::providers$OpenStreetMap.BlackAndWhite)
# at the lsoa level
l_original_lsoa = get_pct_lines("isle-of-wight", geography = "lsoa")
l_lsoa = l_original_lsoa %>% 
  select(geo_code1, geo_code2, all, bicycle, car_driver, rf_avslope_perc, rf_dist_km)
l_top_cycling = l_lsoa %>% 
  top_n(n = 10, wt = bicycle)
l_top_driving = l_lsoa %>% 
  top_n(n = 10, wt = car_driver)

plot(z$geometry)
plot(st_geometry(l_top_cycling), add = TRUE, lwd = 5, col = "green")
plot(z$geometry)
plot(st_geometry(l_top_driving), add = TRUE, lwd = 5, col = "red")
# at the lsoa level
l_top_cycling = l %>% 
  top_n(n = 300, wt = bicycle)
plot(z$geometry)
plot(st_geometry(l_top_driving), add = TRUE, lwd = l_top_cycling$bicycle / mean(l_top_cycling$bicycle), col = "green")

# top 5 driving routes
l_top_driving = l %>% 
  top_n(n = 300, wt = car_driver)
plot(z$geometry)
plot(st_geometry(l_top_driving), add = TRUE, lwd = l_top_driving$car_driver / mean(l_top_driving$car_driver), col = "red")

Modifying PCT data to identify routes/roads of interest

l_msoa$pcycle = l_msoa$bicycle / l_msoa$all * 100
# plot(l_msoa["pcycle"], lwd = l_msoa$all / mean(l_msoa$all), breaks = c(0, 5, 10, 20, 50))
rnet = get_pct_rnet("isle-of-wight")
l_less_than_10km = l %>% 
  filter(rf_dist_km < 10)
sum(l_less_than_10km$all) / sum(l$all)
plot(l_less_than_10km %>% filter(foot > 5) %>% select(foot))
plot(l_less_than_10km %>% filter(bicycle > 5) %>% select(bicycle))
plot(l_less_than_10km %>% filter(car_driver > 5) %>% select(car_driver))

This section is designed for people with experience with the PCT and cycling uptake estimates who want to learn more about how uptake models work.

PCT scenarios

l_msoa$euclidean_distance = as.numeric(sf::st_length(l_msoa))
l_msoa$pcycle_govtarget = uptake_pct_govtarget_2020(
  distance = l_msoa$rf_dist_km,
  gradient = l_msoa$rf_avslope_perc
  ) * 100 + l_msoa$pcycle
l_msoa$pcycle_dutch = uptake_pct_godutch_2020(
  distance = l_msoa$rf_dist_km,
  gradient = l_msoa$rf_avslope_perc
  ) * 100 + l_msoa$pcycle
plot(l_msoa["pcycle"], lwd = l_msoa$all / mean(l_msoa$all), breaks = c(0, 5, 10, 20, 50))
plot(l_msoa["pcycle_dutch"], lwd = l_msoa$all / mean(l_msoa$all), breaks = c(0, 5, 10, 20, 50))
# cor(l_original_msoa$dutch_slc / l_original_msoa$all, l_msoa$pcycle_dutch)
# cor(l_original_msoa$govtarget_slc / l_original_msoa$all, l_msoa$pcycle_govtarget)
# plot(l_original_msoa$dutch_slc / l_original_msoa$all, l_msoa$pcycle_dutch)

Routing and route networks

A key aspect of the PCT is routing. This section demonstrates how to calculate cycling routes in R, to support evidence-based transport planning.

Routes

l_top = l_msoa %>% 
  top_n(n = 1, wt = bicycle)
library(osrm)
r_top = route(l = l_top, route_fun = osrmRoute, returnclass = "sf")
# r_top = route(l = l_top, route_fun = cyclestreets::journey)
mapview::mapview(r_top)
sf::write_sf(r_top, "r_top.geojson")
piggyback::pb_upload("r_top.geojson")
piggyback::pb_download_url("r_top.geojson")
r_top = sf::read_sf("https://github.com/ITSLeeds/pct/releases/download/0.0.1/r_top.geojson")
tm_shape(r_top) +
  tm_lines(lwd = 5)
r_cs = route(l = l_top, route_fun = cyclestreets::journey)
library(leaflet)
leaflet() %>% 
  addTiles() %>% 
  addPolylines(data = r_cs)

Route networks

route_data = sf::st_sf(wight_lines_30, geometry = wight_routes_30$geometry)
library(stplanr) # required for the overline function
rnet_walk = overline(route_data, attrib = "foot")
tm_shape(rnet_walk) +
  tm_lines(lwd = "foot", scale = 9)
rnet_school = get_pct_rnet(region = "isle-of-wight", purpose = "school")
plot(rnet_school)
# Demo PCT Analysis#
# Make a commuting quiet route network for Isle of Wight
# and combine it with the travel to school route network

# Step 1: Load Library
library(tidyverse)
library(sf)
library(pct)
library(stplanr)

# Step 2: Get Data
routes_commute = get_pct_routes_quiet(region = "isle-of-wight",
                              purpose = "commute",
                              geography = "lsoa")

lines_commute = get_pct_lines(region = "isle-of-wight",
                              purpose = "commute",
                              geography = "lsoa")

rnet_school = get_pct_rnet(region = "isle-of-wight",
                           purpose = "school",
                           geography = "lsoa")

# Step 3: Prepare Data
lines_commute = lines_commute %>%
  st_drop_geometry() %>%
  select(id, bicycle, dutch_slc)

routes_commute = routes_commute %>%
  select(id)

# Join Cycling Levels to Routes
routes_commute = left_join(routes_commute, lines_commute)
plot(routes_commute["bicycle"])

# Make a commuting Rnet
rnet_commute = overline2(routes_commute, 
                         attrib = c("bicycle","dutch_slc"))
plot(rnet_commute["bicycle"])

# Combine commuting and travel to schools
rnet_school = rnet_school %>%
  select(dutch_slc)
rnet_commute = rnet_commute %>%
  select(dutch_slc)
rnet_commute$bicycle = NULL


rnet_both = rbind(rnet_commute, rnet_school)
rnet_both = overline2(rnet_both, 
                         attrib = c("dutch_slc"))
mapview::mapview(rnet_both, at = c(50,100,200,500,1000))

Ideas for further work

# old code - create with leaflet
pal = colorBin(palette = "RdYlBu", domain = active$`Percent Active`, bins = c(0, 2, 4, 10, 15, 20, 30, 40, 90))
leaflet(data = active) %>% 
  addProviderTiles(providers$OpenStreetMap.BlackAndWhite) %>% 
  addPolylines(color = ~pal(`Percent Active`), weight = active$all / 100) %>% 
  addLegend(pal = pal, values = ~`Percent Active`)

pal = colorBin(palette = "RdYlBu", domain = car_dependent$`Percent Active`, bins = c(0, 20, 40, 60, 80, 100), reverse = TRUE)
leaflet(data = car_dependent) %>% 
  addProviderTiles(providers$OpenStreetMap.BlackAndWhite) %>% 
  addPolylines(color = ~pal(`Percent Drive`), weight = active$all / 100) %>% 
  addLegend(pal = pal, values = ~`Percent Drive`)

Useful links

These links may be useful when working through the exercises:

https://github.com/ITSLeeds/pct/releases/download/v0.9.4/training-dec-2021.html

References



ITSLeeds/pct documentation built on April 13, 2025, 5:49 p.m.