# knitr::opts_chunk$set(cache = TRUE, class.source = "fold-show")
knitr::opts_chunk$set(cache = TRUE, eval = TRUE, warning = FALSE, message = FALSE)
piggyback::pb_upload("data-raw/training-dec-2021.html")
piggyback::pb_download_url("training-dec-2021.html")

For the online version of this training document with figures, see:

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

Introduction

The PCT is not only a web tool, it is a research and open data project that has resulted in many megabytes of valuable data on current and potential future levels of cycling for work and school trips [@lovelace_propensity_2017; @goodman_scenarios_2019]. An important feature of the tool is that not only does it provide open data, but also that it is an open source project, enabling others to build on it and deploy new scenarios. In this training session we hope you will learn how to download and use these open datasets, and generate new scenarios of change.

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 can 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].

To view a video of our previous advanced training workshop at the Cycle Active City 2021 Conference, see https://www.youtube.com/watch?v=OiLzjrBMQmU.

To see the 'marked up' contents of the vignette (with results evaluated) see here.

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

Agenda

Preliminary timings:

There will be three rooms throughout the day:

Before going into one of the breakout rooms you should write in the main room chat that you'd like support. Someone from the team should respond and you will be free to switch rooms to get support.

The guide covers:

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 have experience using R, you will learn how to:

Getting PCT data from the PCT website

In this example we will use data from North Yorkshire, a mixed region containing urban areas such as York and many rural areas. You can use the PCT, which works at the regional level, for North Yorkshire or any other region by clicking on the area you're interested in on the main map at https://www.pct.bike. If you know the URL of the region you're interested in, you can navigate straight there, in this case by typing in or clicking on the link https://www.pct.bike/m/?r=north-yorkshire.

From there you will see a map showing the region. Before you download and use PCT data, it is worth exploring it on the PCT web app.

Exercise: explore the current level and distribution of cycling:

Using 'Freeze Lines'

You can use the little-known 'Freeze Lines' functionality in the PCT's web app to identify the zone origin and destinations of trips that would use improvements in a particular place. You can do this by selecting the Fast Routes option from the Cycling Flows menu, zooming into the area of interest, and then clicking on the Freeze Lines checkbox to prevent the selected routes from moving when you zoom back out.

knitr::include_graphics("https://user-images.githubusercontent.com/1825120/130370123-5b8885de-4aed-43b4-8a49-b2f875ffff1b.png")

Downloading data from the PCT in GeoJSON form

On the PCT web app Click on the Region data tab, shown in the top of Figure \@ref(fig:clifton), just beneath the 'north' in the URL. You should see a web page like that shown in Figure \@ref(fig:downloads), which highlights the Region data table alongside the Map, Region stats, National Data, Manual, and About page links.

knitr::include_graphics("https://user-images.githubusercontent.com/1825120/130371496-bd0d22ba-c969-4634-904a-0bd0dd924516.png")

Data downloaded in this way can be imported into GIS software such as QGIS, for analysis and visualisation. However, the PCT was built in R so the best way to understand and modify the results is using R, or a similar language for data analysis. The subsequent sections demonstrate using R to access, analyse, visualise and model datasets provided by the pct package.

Getting PCT data with R

We will get the same PCT datasets as in previous sections but using the R interface. If you have not already done so, you will need to install the R packages we will use for this section (and the next) by typing and executing the following command in the R console: install.packages("pct", "sf", "dplyr", "tmap").

library(pct)
library(sf)          # key package for working with spatial vector data
library(tidyverse)   # in the tidyverse
library(tmap)        # installed alongside mapview
tmap_options(check.and.fix = TRUE) # tmap setting
sf::sf_use_s2(FALSE) # don't use the s2 geometry engine

The pct package has been developed specifically for use with PCT data. To learn more about this package, see https://itsleeds.github.io/pct/.

region_name = "north-yorkshire"
zones_all = get_pct_zones(region_name)
lines_all = get_pct_lines(region_name)
# note: the next command may take a few seconds
routes_all = get_pct_routes_fast(region_name)
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)

Getting school route network data

The PCT provides a school route network layer that can be especially important when planning cycling interventions in residential areas [@goodman_scenarios_2019]. Due to the sensitive nature of school data, we cannot make route or OD data level data available. However, the PCT provides travel to school data at zone and route network levels, as shown in Figure \@ref(fig:school1). (Note: to get this data from the PCT website you must select School travel in the Trip purpose menu before clicking on Region data.)

zones_school = get_pct_zones(region = region_name, purpose = "school")
rnet_school = get_pct_rnet(region = region_name, purpose = "school")

As we will see in Section \@ref(joining), combining school and commute network data can result in a more comprehensive network.

zones_school$dutch_slc[is.na(zones_school$dutch_slc)] = 1.5
qtm(zones_school, "dutch_slc", fill.palette = "-viridis")
plot(rnet_school["dutch_slc"])

Exercise: Explore the datasets you have downloaded. Use functions such as plot() or qtm() to visualise these datasets, and try out different colour schemes

Modelling change

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 and how to generate new scenarios of change. Reproducible and open R code will be used to demonstrate the concepts so knowledge of R or other programming languages is recommended but not essential, as there will be conceptual exercises covering the factors linked to mode shift. In it you will:

The section builds on the previous section and assumes you have installed and loaded the necessary packages, and that you have read-in data on a region of your choice with the following commands:

region_name = "north-yorkshire"
zones_all = get_pct_zones(region_name)
lines_all = get_pct_lines(region_name)
# note: the next command may take a few seconds
routes_all = get_pct_routes_fast(region_name)
rnet_all = get_pct_rnet(region_name)

PCT scenarios

One of the benefits of the PCT is its ability to generate scenarios that model where people might cycle in future. Several cycling uptake scenarios are included on the PCT website. We also have R functions for these scenarios. For example, the PCT's 'Government Target' scenario allows us to calculate the cycling uptake that would be required to correspond to a scenario in which we meet the government's aim to double cycling levels by 2025, using a 2013 baseline.

The following code chunk uses the R function uptake_pct_govtarget_2020() (from the pct package) to recreate this 'Government Target' scenario.

You can do this with base R functions as follows:

lines_all$pcycle = lines_all$bicycle / lines_all$all

However, many people, especially people new to R, find the tidyverse syntax more intuitive. The same result from the previous line of code can be achieved as follows:

lines_all = lines_all %>% mutate(pcycle = bicycle / all)

That can also be written on two lines for clarity as follows:

lines_all = lines_all %>% 
  mutate(pcycle = bicycle / all)

You can see the first rows of data in this updated object by printing it as follows:

lines_all

It's worth spending a moment to interpret the output: it tells us first that this a geographic object with a LINESTRING geometry and a bounding box in Northern Europe (UK). It contains just over 10k rows and 143 columns.

A basic data manipulation task is to select columns, do this using tidyverse syntax as follows and pipe the output to the summarise function to find out the mean and standard deviation of the 'pcycle' column we have created as follows:

lines_all %>%
  sf::st_drop_geometry() %>%
  select(pcycle) %>% 
  summarise(mean(pcycle), sd(pcycle))

That tells that the the average value of pcycle is 6%. Note: that is not the same as the overall % cycling in the region, which can be calculated with base R functions as follows:

sum(lines_all$bicycle) / sum(lines_all$all)

Use whichever R code style you find most appropriate; in this section we will tend to use the tidyverse style. See the books Reproducible Road Safety Research with R, Geocomputation with R and R for Data Science for more for developing your R code writing skills.

lines_all = lines_all %>% 
  mutate(
    euclidean_distance = as.numeric(sf::st_length(lines_all)),
    uptake = uptake_pct_govtarget_2020(rf_dist_km, rf_avslope_perc),
    pcycle_govtarget = uptake + pcycle
  )
# Base R way:
lines_all$euclidean_distance = as.numeric(sf::st_length(lines_all))
lines_all$pcycle_govtarget = uptake_pct_govtarget_2020(
  distance = lines_all$rf_dist_km,
  gradient = lines_all$rf_avslope_perc
  ) + lines_all$pcycle

Exercise: Generate a 'Go Dutch' scenario for North Yorkshire using the function uptake_pct_godutch(): (Hint: the process is very similar to that used to generate the 'Government Target' scenario)

lines_all = lines_all %>% 
  mutate(
    euclidean_distance = as.numeric(sf::st_length(lines_all)),
    uptake = uptake_pct_godutch_2020(rf_dist_km, rf_avslope_perc),
    pcycle_dutch = uptake + pcycle
  )
# base R way
lines_all$pcycle_dutch = uptake_pct_godutch_2020(
  distance = lines_all$rf_dist_km,
  gradient = lines_all$rf_avslope_perc
  ) + lines_all$pcycle
summary(lines_all$pcycle_dutch)

You can plot the data as follows:

lwd = lines_all$all / mean(lines_all$all)
breaks = c(0, 5, 10, 20, 50) / 100
lines_all %>% 
  select(pcycle) %>% 
  plot(lwd = lwd, breaks = breaks)
lwd = lines_all$all / mean(lines_all$all)
breaks = c(0, 5, 10, 20, 50) / 100
plot(lines_all["pcycle"], lwd = lwd, breaks = breaks)
plot(lines_all["pcycle_dutch"], lwd = lwd, breaks = breaks)
# cor(l_originalines_all$dutch_slc / l_originalines_all$all, lines_all$pcycle_dutch)
# cor(l_originalines_all$govtarget_slc / l_originalines_all$all, lines_all$pcycle_govtarget)
# plot(l_originalines_all$dutch_slc / l_originalines_all$all, lines_all$pcycle_dutch)

Developing new scenarios of change

Let's develop a simple model representing the government's aim, that "half of all journeys in towns and cities will be cycled or walked" by 2030. We will assume that this means that all journeys made in urban areas, as defined by the Office for National Statistics, will be made by these active modes. We only have commute data in the data we downloaded, but this is a good proxy for mode share overall.

The first stage is to identify urban areas in North Yorkshire. We use data from the House of Commons Research Briefing on City and Town Classifications to define areas based on their town/city status. The code chunk below shows the benefits of R in terms of being able to get and join data onto the route data we have been using:

# Get data on the urban_rural status of LSOA zones
urban_rural = readr::read_csv("https://researchbriefings.files.parliament.uk/documents/CBP-8322/oa-classification-csv.csv")
ggplot(urban_rural) +
  geom_bar(aes(citytownclassification)) +
  coord_flip()

# Join this with the PCT commute data that we previously downloaded
urban_rural = rename(urban_rural, geo_code = lsoa_code)
zones_all_joined = left_join(zones_all, urban_rural)
routes_all_joined = left_join(routes_all, urban_rural, by = c("geo_code1" = "geo_code"))
tm_shape(zones_all_joined) +
  tm_polygons("citytownclassification")

After the classification dataset has been joined, the proportion of trips made by walking and cycling in towns and cities across North Yorkshire can be calculated as follows.

# Select only zones for which the field `citytownclassification` contains the word "Town" or "City"
routes_towns = routes_all_joined %>% 
  filter(grepl(pattern = "Town|City", x = citytownclassification)) 
round(sum(routes_towns$foot + routes_towns$bicycle) / sum(routes_towns$all) * 100)

Currently, only around 34% of commute trips in the region's 'town' areas are made by walking and cycling (27% across all zones in North Yorkshire, and a much lower proportion in terms of distance). We explore this in more detail by looking at the relationship between trip distance and mode share for existing commuter journeys, as shown in Figure \@ref(fig:distmode) (a).

We will create a scenario representing the outcome of policies that incentivise people to replace car trips with walking and cycling. This focuses on the red boxes in Figure \@ref(fig:distmode). In this scenario, we replace 50% of car trips of less than 1 km with walking, and replace 10% of car trips of 1-2 km length with walking. Many of the remaining car trips will be replaced by cycling, with the percentages of trips that switch for each OD determined by the uptake function in the Go Dutch Scenario of the PCT. The results of this scenario are shown in Figure \@ref(fig:distmode) (b).

# Reduce the number of transport mode categories 
routes_towns_recode = routes_towns %>% 
  mutate(public_transport = train_tube + bus,
         car = car_driver + car_passenger,
         other = taxi_other + motorbike 
         ) %>% 
  dplyr::select(-car_driver, -car_passenger, -train_tube, -bus) 

# Set distance bands to use in the bar charts
routes_towns_recode$dist_bands = cut(x = routes_towns_recode$rf_dist_km, breaks = c(0, 1, 3, 6, 10, 15, 20, 30, 1000), include.lowest = TRUE)

# Set the colours to use in the bar charts
col_modes = c("#fe5f55", "grey", "#ffd166", "#90be6d", "#457b9d") 

# Plot bar chart showing modal share by distance band for existing journeys 
base_results = routes_towns_recode %>%
  sf::st_drop_geometry() %>% 
  dplyr::select(dist_bands, car, other, public_transport, bicycle, foot) %>% 
  tidyr::pivot_longer(cols = matches("car|other|publ|cy|foot"), names_to = "mode") %>% 
  mutate(mode = factor(mode, levels = c("car", "other", "public_transport", "bicycle", "foot"), ordered = TRUE)) %>% 
  group_by(dist_bands, mode) %>% 
  summarise(Trips = sum(value))
g1 = ggplot(base_results) +
  geom_col(aes(dist_bands, Trips, fill = mode)) +
  scale_fill_manual(values = col_modes) + ylab("Trips")
g1

# Create the new scenario: 
# First we replace some car journeys with walking, then replace some of the
# remaining car journeys with cycling
routes_towns_active = routes_towns_recode %>% 
  mutate(
    foot_increase_proportion = case_when(
      # specifies that 50% of car journeys <1km in length will be replaced with walking
      rf_dist_km < 1 ~ 0.5, 
      # specifies that 10% of car journeys 1-2km in length will be replaced with walking
      rf_dist_km >= 1 & rf_dist_km < 2 ~ 0.1, 
      TRUE ~ 0
      ),
    # Specify the Go Dutch scenario we will use to replace remaining car trips with cycling
    bicycle_increase_proportion = uptake_pct_godutch_2020(
      distance = rf_dist_km,
      gradient = rf_avslope_perc
      ), 
    # Make the changes specified above
    car_reduction = car * foot_increase_proportion,
    car = car - car_reduction,
    foot = foot + car_reduction,
    car_reduction = car * bicycle_increase_proportion,
    car = car - car_reduction,
    bicycle = bicycle + car_reduction
    )

# Plot bar chart showing how modal share has changed in our new scenario
active_results = routes_towns_active %>%
  sf::st_drop_geometry() %>% 
  dplyr::select(dist_bands, car, other, public_transport, bicycle, foot) %>% 
  tidyr::pivot_longer(cols = matches("car|other|publ|cy|foot"), names_to = "mode") %>% 
  mutate(mode = factor(mode, levels = c("car", "other", "public_transport", "bicycle", "foot"), ordered = TRUE)) %>% 
  group_by(dist_bands, mode) %>% 
  summarise(Trips = sum(value))
g2 = ggplot(active_results) +
  geom_col(aes(dist_bands, Trips, fill = mode)) +
  scale_fill_manual(values = col_modes) + ylab("Trips")
g2

Exercise: Instead of a scenario in which all types of car journey (i.e. both car drivers and car passengers) are replaced by walking or cycling, can you create a scenario in which solely journeys by car drivers are replaced by walking or cycling? The scenario we just created applies only to urban areas - can you adapt it so that the same changes in walking and cycling uptake are applied across the whole of North Yorkshire, including both urban and rural areas?

# % active under go active scenario
round(sum(routes_towns_recode$foot + routes_towns_recode$bicycle) / sum(routes_towns_active$all) * 100)
round(sum(routes_towns_active$foot + routes_towns_active$bicycle) / sum(routes_towns_active$all) * 100)
round(sum(routes_all_joined$foot + routes_all_joined$bicycle) / sum(routes_all_joined$all) * 100)
round(
  sum(routes_towns_active$car * routes_towns_active$rf_dist_km) /
    sum(routes_towns_recode$car * routes_towns_recode$rf_dist_km) * 100
)
round(
  sum(routes_towns_active$bicycle * routes_towns_active$rf_dist_km) /
    sum(routes_towns_recode$bicycle * routes_towns_recode$rf_dist_km) * 100
)
round(
  sum(routes_towns_active$foot * routes_towns_active$rf_dist_km) /
    sum(routes_towns_recode$foot * routes_towns_recode$rf_dist_km) * 100
)

The scenario outlined above may sound ambitious, but it only just meets the government's aim for walking and cycling to account for 50% of trips in Town and Cities, at least when looking exclusively at single stage commutes in a single region. Furthermore, while the scenario represents a \~200% (3 fold) increase in the total distance travelled by active modes, it only results in a 17% reduction in car km driven in towns. The overall impact on energy use, resource consumption and emissions is much lower for the region overall, including rural areas.

In the context of the government's aim of fully decarbonising the economy by 2050, the analysis above suggests that more stringent measures focussing on long distance trips, which account for the majority of emissions, may be needed. However, it is still useful to see where there is greatest potential for car trips to be replaced by walking and cycling, as shown in Figure \@ref(fig:shortcar).

rnet_shortcar = stplanr::overline(routes_towns_active, "car_reduction")
plot(rnet_shortcar)
plot(rnet_all["dutch_slc"])

Converting PCT scenarios A/B Street simulations

The following section is based on the pct_to_abstr vignette in the abstr package, which can be installed as follows:

remotes::install_cran("abstr")

A/B Street can simulate large areas such as the North Yorkshire case study area used in this demonstration, but it works best on most computers for smaller areas and small cities. For the purposes of this example, we will create a smaller case study area, before generating A/B Street scenarios, we will constrain the case study area to York. The definition of the study area will be supplied by the local authority bounday, which is also available from the PCT.

zones_york = zones_all %>% filter(lad_name == "York")

We will get OSM data for the whole region with the osmextract package (warning, this next line of code may take some time, you can skip it as the data has be pre-processed):

q = "SELECT * FROM multipolygons WHERE building is not null"
buildings_nyorks = osmextract::oe_get("north yorkshire", query = q)

The buildings within a 5 km radius of central York and travel corridor going due North can be returned as follows using the zonebuilder package:

# york_centre = tmaptools::geocode_OSM("york", as.sf = TRUE)
# york_3km = stplanr::geo_buffer(york_centre, dist = 3000)
# buildings_york = buildings_nyorks[zones_york, , op = sf::st_within] # buildings in york
buildings_york = buildings_nyorks[york_3km, , op = sf::st_within] 
york_clockboard = zonebuilder::zb_zone("york", n_circles = 3)
york_north = york_clockboard %>% 
  filter(circle_id == 1 | segment_id == 12)
plot(york_clockboard$geometry)
plot(york_north, add = TRUE)
buildings_york = buildings_nyorks[york_north, , op = sf::st_within] 

These have been pre-saved and can be downloaded from GitHub as follows:

sf::write_sf(buildings_york, "buildings_york_north.geojson")
piggyback::pb_upload("buildings_york_north.geojson")
piggyback::pb_download_url("buildings_york_north.geojson")
u = "https://github.com/ITSLeeds/pct/releases/download/v0.9.4/buildings_york_north.geojson"
buildings_york = sf::read_sf(u)

Note: if you want to look at what type of buildings are in the input dataset, the following code should help:

buildings_nyorks %>%
  sf::st_drop_geometry() %>% 
  group_by(building) %>% 
  count(building) %>% 
  arrange(desc(n))

Next, we'll keep only OD pairs associated with zones whose centoids lie within the 5km central area:

centroids = sf::st_centroid(zones_york)
centroids_central = centroids[york_north, ]
zones_central = zones_york %>% filter(geo_code %in% centroids_central$geo_code)

We can then subset the OD pairs, represented as routes in the routes_towns_active object, as follows:

routes_york = routes_towns_active %>% 
  filter(geo_code1 %in% zones_central$geo_code) %>% 
  filter(geo_code2 %in% zones_central$geo_code)

Let's do some sanity checks to see what the input data looks like before creating scenarios for A/B Street:

sum(routes_york$all) # 30k commuters
zones_central %>% select(foot) %>% plot(reset = FALSE)
plot(routes_york$geometry, add = TRUE, col = "white", lwd = 3)
plot(buildings_york$geometry, add = TRUE)
od_york_abstr = routes_york %>% 
  transmute(
    o = geo_code1,
    d = geo_code2,
    All = foot + bicycle + public_transport + car,
    Walk = foot,
    Bike = bicycle,
    Transit = public_transport,
    Drive = car
    ) %>% 
  sf::st_drop_geometry()

After renaming the key columns in the previous command, we can covert the OD data into a new form, with a single desire line for each trip:

output_sf = abstr::ab_scenario(
  od = od_york_abstr,
  zones = zones_york,
  origin_buildings = buildings_york,
  destination_buildings = buildings_york,
  pop_var = 3,
  time_fun = ab_time_normal,
  output = "sf"
)
# plot(output_sf$geometry)

We can then convert the result to a list format and save the data as a json file:

# output_json = abstr::ab_json(output_sf, scenario_name = "go_active_york", time_fun = abstr::ab_time_normal(7.5)) # fails but shouldn't, right?
output_json = abstr::ab_json(output_sf, scenario_name = "go_active_york")
abstr::ab_save(output_json, f = "go_active_york.json")
system("head go_active_york.json")
piggyback::pb_upload("go_active_york.json")

Importing York into A/B Street for active travel planning

A/B Street is a suite of tools to rapidly prototype active travel schemes. There are parts dedicated to planning cycle networks and low traffic neighborhoods, but today we'll use the traffic simulator to visualize this scenario we've just generated.

Many parts of A/B Street can be used in your web browser without installation, but we'll need to install it since we're importing a new city. Follow these instructions and download the .zip file for your platform (Windows, Mac, or Linux) and then unzip it. Launch the software (on Windows and Mac, you may need to follow the instructions for skipping security warnings) and you should see the main screen:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145171484-b8c34f04-92a7-4e69-b3ea-761e714e75b2.png")

Choose "Traffic simulation sandbox." A small part of Seattle will be loaded by default. At the top-right of the screen, you can click the area name -- "Montlake and Eastlake" -- to change the map:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145171705-36b221a6-f63b-47d6-8fb5-4e187508696c.png")

At the top is an option to "Import a new city." You'll see some instructions:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145171832-44270223-d3a6-4028-9586-6e5efb82c52c.png")

Step 1 leads you to geojson.io, a handy site for drawing shapes on a map and generating GeoJSON. We need to sketch a boundary for the portion of York we wish to simulate. Use the polygon tool and select everything roughly within the ring road:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145173423-4c5468a1-fae1-4605-8db5-f5a201fae56c.png")

Select all of the JSON in the right sidebar and copy it into your system clipboard. Return to A/B Street, then check through the settings. Be sure to switch to driving on the left! You can also name the imported map "york" to find it more easily later. Leave the other settings alone, then press "Import the area from your clipboard."

You'll then get a loading screen, while A/B Street downloads OpenStreetMap data for York and transforms it into a richer representation allowing for traffic simulation. This will take a few minutes, depending on your network and computer speed. The data for York is about 115MB, and on my system, the total import time was around 3 minutes.

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145173134-3865e98b-ebd1-4808-8e1b-2697192a98d1.png")

If all goes well, you'll see York at midnight, all quiet:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145173156-85d32d77-27ce-4813-b7e8-fff962a09cae.png")

Exploring A/B Street

A/B Street has lots of functionality; it's best to just play around for a few minutes and explore it. To start, use your touchpad or mouse's scroll wheel and just zoom into the city center. Once you zoom in enough, the view will change to show detailed lanes on the streets. If you happen to know the area and something looks wrong, it may be missing data in OpenStreetMap -- get in touch if you'd like to learn how to update it!

A handy tool is to just search for cross streets. Use the search icon in the bottom right corner (or its keybinding, k) and head to Gillygate and Clarence Street:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145185679-7ec2f0bc-52e9-4aef-8e88-0b8154022744.png")

Let's see some people move around! Click on one of the intersections and "spawn some agents here." Or if you notice the hotkeys, you can just hover on the intersection and press z without having to click. Some cyclists, drivers, and pedestrians appear:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145186889-f32c55f4-e3e2-4958-9cf9-aa1f70b15275.png")

Click one of them and zoom out a bit to see where they're headed:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145186881-f9f4532f-ac18-40d1-a177-dd5ea347d83e.png")

If they're moving too fast to select, press space to pause or resume the simulation, or use the controls in the top-left. The "spawn agents here" tool just creates a few people headed somewhere random, so there's nothing particularly meaningful about this.

You might notice that Clarence Street has cycle lanes appear and disappear. Why don't we make things a little more connected? Just north of the traffic signal, there are two southbound lanes. Let's transform one into a cycle lane, just to learn how the road editor works. Click the street, then "edit lane." You'll enter the edit mode:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145186876-c7091281-8c1c-446f-97ae-4cd676162a22.png")

These controls are designed to be similar to the wonderful Streetmix tool. Play around a bit and try adding a southbound cycle lane with a small buffer:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145186873-a8b8c3a3-9833-4ef8-85f3-ad38b049c72e.png")

Great! Now that you know some of the basic controls, let's go back to that scenario file we generated previously.

Importing scenario.json files

For this section, we'll switch to an area in Leeds, where another exercise (??) has generated https://github.com/ITSLeeds/pct/releases/download/v0.9.4/go_active_leeds.json using abstr. If you've generated scenario data for York, you can follow the same steps here, but the images shown will be for Leeds. Change the map at the top and open the Leeds city center. You can find it in the list of areas already imported; you won't need to draw a boundary yourself.

Once you have Leeds opened up, change the traffic scenario using the button at the top-right next to the map name. You'll see some options:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145189333-e6ca7fac-ad26-4a06-8a3c-21ee3ed43213.png")

These are different travel demand models to tell the simulation where people live, work, and travel. Choose the last option, "import JSON scenario," and import either the JSON file you produced for York, or https://github.com/ITSLeeds/pct/releases/download/v0.9.4/go_active_leeds.json for Leeds.

The simulation resets to midnight, and it's all quiet. Let's see when we can expect people to start moving. In the top-right panel, there's a small info icon:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145189329-c7066424-ee65-4a72-b6b1-b56daf7a4cbe.png")

It looks like abstr starts all the trips around 8:30. Use the button at the bottom to jump to the first trip. You'll return to the simulation, where you can use the top-left panel to speed up time a bit. Around 7:45, things wake up a bit:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145189325-55c6f809-0938-4ab9-b848-4877c9a1f706.png")

There are a few handy layers you can draw to find people more easily. Use the icon in the bottom-right corner or press the hotkey l. Feel free to explore these! Two useful ones are the population heatmap, just showing where people are:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145189310-01e8624d-ce50-4fb7-91a1-7b647676f31d.png")

And the delay layer, showing where vehicles are getting stuck:

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145189302-aec4b6cd-3b4b-4f40-9aca-b6bdf30081ab.png")

Zooming in a bit, it's not clear exactly what the bottleneck is -- probably this is an unrealistic volume of traffic to be pouring in from the north. Producing a realistic travel demand model is challenging!

knitr::include_graphics("https://user-images.githubusercontent.com/1664407/145189298-84e1fd1c-a398-4cc6-af78-917326353bde.png")

Joining commute and school data {#joining}

The PCT is not limited to commuter data only, it also provides a range of school data for each region in England and Wales to be downloaded with relative ease. In the example below, we add a purpose to the get_pct_rnet() function of school. This allows us to get estimates of cycling potential on the road network for school trips, commuter trips, and school and commuter trips combined. Note in the figure below that the combined route network provides a more comprehensive (yet still incomplete) overview of cycling potential in the study region.

# get pct rnet data for schools
rnet_school = get_pct_rnet(region = region_name, purpose = "school")
rnet_school = subset(rnet_school, select = -c(`cambridge_slc`)) # subset columns for bind
rnet_all = subset(rnet_all, select = -c(`ebike_slc`,`gendereq_slc`,`govnearmkt_slc`)) # subset columns for bind 

rnet_school_commute = rbind(rnet_all,rnet_school) # bind commute and schools rnet data
rnet_school_commute$duplicated_geometries = duplicated(rnet_school_commute$geometry) # find duplicated geometries
rnet_school_commute$geometry_txt = sf::st_as_text(rnet_school_commute$geometry)

rnet_combined = rnet_school_commute %>% 
  group_by(geometry_txt) %>% # group by geometry
  summarise(across(bicycle:dutch_slc, sum, na.rm = TRUE)) # and summarise route network which is not a duplicate
brks = c(0, 10, 100, 1000, 5000)
tmap_arrange(nrow = 1,
  tm_shape(rnet_all %>% arrange(dutch_slc)) + tm_lines("dutch_slc", palette = "-viridis", breaks = brks) + tm_layout(title = "Trips to work"),
  tm_shape(rnet_school %>% arrange(dutch_slc)) + tm_lines("dutch_slc", palette = "-viridis", breaks = brks) + tm_layout(title = "Trips to school"),
  tm_shape(rnet_combined %>% arrange(dutch_slc)) + tm_lines("dutch_slc", palette = "-viridis", breaks = brks) + tm_layout(title = "Trips to work and school")
)
# https://github.com/ITSLeeds/pct/issues/103#issuecomment-904990639
knitr::include_graphics("https://user-images.githubusercontent.com/1825120/130692688-65a958a9-8586-4196-982d-872d236becdb.png")

Useful links

These links may be useful when working through the exercises:

References



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