https://github.com/robinlovelace/geocomp-tranport-workshop
Example data and some code for workshop in Jena
https://github.com/robinlovelace/geocomp-tranport-workshop
Last synced: about 1 month ago
JSON representation
Example data and some code for workshop in Jena
- Host: GitHub
- URL: https://github.com/robinlovelace/geocomp-tranport-workshop
- Owner: Robinlovelace
- License: gpl-3.0
- Created: 2018-04-10T14:04:53.000Z (about 7 years ago)
- Default Branch: master
- Last Pushed: 2018-04-10T21:24:07.000Z (about 7 years ago)
- Last Synced: 2025-04-03T16:12:22.701Z (about 2 months ago)
- Language: TeX
- Size: 4.27 MB
- Stars: 3
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.Rmd
- License: LICENSE
Awesome Lists containing this project
README
---
output: github_document
title: "Routing services and local routing in R: a tutorial with stplanr and dodgr"
author: "Robin Lovelace"
date: "`r Sys.Date()`"
bibliography: references.bib
always_allow_html: yes
---```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```# Prerequisites
This workshop is an extension of the transport chapter ([Chapter 7](http://geocompr.robinlovelace.net/transport.html)) in the book *Geocomputation with R* [@lovelace_geocomputation_2018].
This book is available at [geocompr.robinlovelace.net](http://geocompr.robinlovelace.net).
Reading the chapter is not a prerequisite but it's recommended reading for context before you dive-into this tutorial.
The book is description-heavy and code light.
This tutorial is vice versa.There are two ways to get the data that is part of this workshop:
- Download and unzip the repo from GitHub via `git clone` or by clicking on [`Clone or download`](https://github.com/Robinlovelace/geocomp-tranport-workshop/archive/master.zip) on the [github.com/Robinlovelace/geocomp-tranport-workshop](https://github.com/Robinlovelace/geocomp-tranport-workshop) web page
- Entering (or copy-pasting) the following commands in R```{r, eval=FALSE}
u = "https://github.com/Robinlovelace/geocomp-tranport-workshop/archive/master.zip"
download.file(u, "master.zip")
unzip("master.zip")
```From there the easiest way to change working directory, and access the project's data, is by opening the [`geocomp-tranport-workshop.Rproj`](https://github.com/Robinlovelace/geocomp-tranport-workshop/blob/master/geocomp-tranport-workshop.Rproj) project file in RStudio.
The software prerequisites are an up-to-date R version and packages that can be loaded follows:
```{r}
library(stplanr)
library(tmap)
library(sf)
library(leaflet)
library(dodgr)
```# Introduction
As described in the `stplanr-paper` vignette [@lovelace_stplanr:_2017] there are various approaches to routing available in the package.
This vignette seeks to demonstrate these approaches using a case study in the town of Jena, Germany.# Online routing
The simplest routing strategy is to use an online service such as the public facing OSRM demo routing service at http://router.project-osrm.org/ with an interactive map-based GUI at http://map.project-osrm.org/.
We can demonstrate this routing service in action by planning a route from Jena Paradies rail station to Jen Tower, a building that is "the tallest in Eastern Germany" according to [Wikipedia](https://en.wikipedia.org/wiki/JenTower).
This represents a typical journey that one may take on visiting the city for the first time:```{r}
from = "Jena Paradies"
to = "JenTower"
``````{r, eval=FALSE}
r_osrm = route_osrm(from, to)
saveRDS(r_osrm, "r_osrm.Rds")
```What just happened?
There were 3 main steps taken inside the function `route_osrm()`:- The origin and destination text strings were geo-coded
- The information was sent to the OSRM server and returned
- The data was converted into a form that can be used in RIn case this does not work on your computer (you need a recent version of stplanr) you can load it as follows:
```{r}
r_osrm = readRDS("r_osrm.Rds")
```The class of the resulting object can be queried as follows:
```{r}
class(r_osrm)
```We can verify that it contains the spatial information we need by plotting it interactively:
```{r}
leaflet() %>% addTiles() %>% addPolylines(data = r_osrm)
```We can execute the first step taken as follows:
```{r}
(from_coords = geo_code(from))
(to_coords = geo_code(to))
```We could continue to explore what happened in the function by executing the functions `viaroute()` and `viaroute2sldf()` sequentially but that is not the point of this vignette (see the source code for more information on the internals of `route_osrm` but be warned, they are not pretty)!
Other routing services can also be used, as illustrated by the `route_graphhopper()` call below:
```{r, eval=FALSE}
r_graphbike = route_graphhopper(from_coords, to_coords)
saveRDS(r_graphbike, "r_graphbike.Rds")
```Be warned: this code may not work on your computer because you need a graphhopper api key.
We can check that we have one on this computer with the following command:```{r}
Sys.getenv("GRAPHHOPPER") %>% nchar()
```If so you will have 32 characters, if not the result will have 0.
(To get a Graphhopper API key see `?route_graphhopper`.)
That is not a problem as I've saved the result, which can be loaded with:```{r}
r_graphbike = readRDS("r_graphbike.Rds")
```The advantage of the Graphhopper API is that it has three routing profiles available (and many more with their paid plan), as illustrated below (the default is bike):
```{r, eval=FALSE}
r_graphcar = route_graphhopper(from_coords, to_coords, vehicle = "car")
r_graphwalk = route_graphhopper(from_coords, to_coords, vehicle = "foot")
saveRDS(r_graphcar, "r_graphcar.Rds")
saveRDS(r_graphwalk, "r_graphwalk.Rds")
```As before, these can be loaded from the repo as follows:
```{r}
r_graphcar = readRDS("r_graphcar.Rds")
r_graphwalk = readRDS("r_graphwalk.Rds")
```We can compare these different routes, as illustrated in the figures below:
```{r routes, fig.show='hold', fig.cap="Routes for driving (left panel, with OSRM in red and Graphhopper in orange) and active transport (right panel, cycling in green, walking in blue).", out.width="50%"}
b = tmaptools::bb(r_osrm, ext = 1.2)
tm_shape(r_osrm, bbox = b) +
tm_lines(col = "red", lwd = 6) +
qtm(r_graphcar, lines.col = "orange", lines.lwd = 4)
tm_shape(r_osrm, bbox = b) +
tm_lines(col = "white", lwd = 6) +
qtm(r_graphbike, lines.col = "green", lines.lwd = 3) +
qtm(r_graphwalk, lines.col = "blue", lines.lwd = 2)
```The results show a diversity of route options.
We can make the following generalisations:- Motorised routes tend to be more circuitous
- Routes for the same mode can be very different depending on which weighting profile is used
- Walking and cycling routes tend to be similar, although walking routes tend to be more direct as they can pass through steps etc.There are some major limitations associated with online routing:
- It is slow
- It can be expensive
- It's not conducive to reproducibility - the service may change and you need an API key
- You do not have control over the weighting profiles (this can be good and bad)To overcome these limitations we can do local routing.
# Local routing
To do local routing you need a route network, e.g. as provided by the following commands:
```{r, eval=FALSE}
library(osmdata)
bb_jena = getbb("Jena")
osm_jena = opq(bbox = bb_jena) %>%
add_osm_feature("highway", "prim|second|cycle", value_exact = FALSE) %>%
osmdata_sf()
summary(osm_jena)
ways = osm_jena$osm_lines
write_sf(ways, "ways.geojson")
```To save time we've saved the result, which can be loaded as follows from the vignettes folder:
```{r}
ways = read_sf("ways.geojson")
```Let's check if this is a reasonable representation of Jena's route network:
```{r}
leaflet() %>% addTiles() %>% addPolylines(data = ways)
```Clearly it's a very simplified route network.
A more comprehensive network could be created by altering the arguments passed to `add_osm_feature`, e.g. to simply `add_osm_feature("highway")`.
We deliberately use a subset of the network for teaching.
Now, how do we find routes along it?```{r}
ways_sp = as(ways, "Spatial")
ways_sln = SpatialLinesNetwork(ways_sp)
slotNames(ways_sln)
weightfield(ways_sln)
class(ways_sln@g)
```We can find the shortest path between A and B as follows:
```{r}
from_sln = find_network_nodes(ways_sln, from_coords[1], from_coords[2])
to_sln = find_network_nodes(ways_sln, to_coords[1], to_coords[2])
r_local = sum_network_routes(ways_sln, from_sln, to_sln, "length", combinations = F)
leaflet() %>% addTiles() %>% addPolylines(data = r_local)
``````{r}
g = ways_sln@g
e = igraph::edge_betweenness(ways_sln@g)
lwd = e / mean(e)
plot(ways_sln@sl, lwd = lwd)
``````{r}
leaflet() %>% addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
addPolylines(data = ways_sln@sl, weight = lwd * 5)
```What has just happened?
Well this is a workshop that aims to teach how to learn so this is a question for you to answer.
Here are some clues:- `?igraph::edge_betweenness`
- Google "graph betweenness"
- Take a look at the paper @cooper_using_2017
- Ask the sub-question: how does this relate to routing single lines above?Anothe way to acheive a similar result is with the **dodgr** package:
```{r}
ways_dg = weight_streetnet(ways)
summary(ways_dg)
```This shows that there are 18,000+ edges just in that subset of ways in a small town. This should explain why we're not using the complete route network!
In any case, we can find the fastest route between any 2 'node' points on the graph as follows:
```{r}
verts <- dodgr_vertices(ways_dg) # the vertices or points for routing
# path between 2 arbitrarily chosen vertices:
dp = dodgr_paths(ways_dg, from = verts$id [1], to = verts$id [9000])
str(dp)
```The result is a character vector of IDs representing the shortest path, with values
mapping on to `verts$id`. These can be joined together into a spatial object
with:```{r}
path1 <- verts[match(dp[[1]][[1]], verts$id), ]
head(path1)
```The path can be visualised as follows:
```{r}
leaflet() %>% addTiles() %>% addCircles(path1$x, path1$y)
```Let's find the nodes nearest our original from and to points:
```{r}
knf = nabor::knn(cbind(verts$x, verts$y), matrix(from_coords, ncol = 2), k = 1)
knt = nabor::knn(cbind(verts$x, verts$y), matrix(to_coords, ncol = 2), k = 1)
dp = dodgr_paths(ways_dg, from = verts$id[knf$nn.idx], to = verts$id[knt$nn.idx])
path2 <- verts[match(dp[[1]][[1]], verts$id), ]
leaflet() %>% addTiles() %>% addCircles(path2$x, path2$y)
```How can we convert this into a spatial network again?
To do so we can do routing on an industrial scale, using the bicycle weighting profile as follows:```{r}
from <- sample(ways_dg$from_id, size = 100)
to <- sample(ways_dg$to_id, size = 100)
flows <- matrix(rep(1, length(from) * length(to)),
nrow = length(from))
graph_f <- dodgr_flows_aggregate(ways_dg, from, to, flows = flows,
wt_profile = "bicycle")
head(graph_f)
```The above code created a origin-destination dataset with 100 origins and 100 destinations and found the shortest path, for the bicycle road weight profile, of the 10,000 routes between them.
Imagine how long all that routing would take using an on-line routing service.
The code chunk below converts the results back into a spatial object, and plots it:```{r}
graph_undir <- merge_directed_flows (graph_f)
ways_dsf = dodgr_to_sf(net = graph_undir)
names(ways_dsf$dat)
names(ways_dsf)
lwd2 = ways_dsf$dat$flow / mean(ways_dsf$dat$flow)
plot(ways_dsf$geoms, lwd = lwd2)
```Questions for further study:
- How does **dodgr** work?
- Hint: see the [`dodgr` vignette](https://cran.r-project.org/web/packages/dodgr/vignettes/dodgr.html#6_shortest_paths) [@padgham_dodgr_]
- How can more realistic flows between origin-destination pairs be generated?
- Hint: see @simini_universal_2012.
# AcknowledgementsMany thanks to the developers of all the software used in this tutorial and Jannes Muenchow for hosting me in Jena.
For reproducibility, the package versions used for this tutorial are shown below (note the development versions are used in many cases):
```{r}
devtools::session_info()
```# References