--- title: "Desire Lines Extended" author: "Robin Lovelace, Jakub Nowosad, Jannes Muenchow" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Desire Lines Extended} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- This vignette builds on the [transport chapter](https://geocompr.robinlovelace.net/transport.html) of [the Geocomputation with R book](https://geocompr.github.io/) by showing how to create multi-stage desire lines, from the ground-up. It depends on these packages and datasets: ```{r, message=FALSE, warning=FALSE} library(sf) library(stplanr) library(dplyr) library(spDataLarge) desire_lines = od2line(bristol_od, bristol_zones) desire_rail = top_n(desire_lines, n = 3, wt = train) ``` The first stage is to create matrices of coordinates that will subsequently be used to create matrices representing each leg: ```{r} mat_orig = as.matrix(line2df(desire_rail)[c("fx", "fy")]) mat_dest = as.matrix(line2df(desire_rail)[c("tx", "ty")]) mat_rail = st_coordinates(bristol_stations) ``` The outputs are three matrices representing the starting points of the trips, their destinations and possible intermediary points at public transport nodes (named `orig`, `dest` and `rail` respectively). But how to identify *which* intermediary points to use for each desire line? The `knn()` function from the **nabor** package (which is used internally by **stplanr** so it should already be installed) solves this problem by finding *k nearest neighbors* between two sets of coordinates. By setting the `k` parameter, one can define how many nearest neighbors should be returned. Of course, `k` cannot exceed the number of observations in the input (here: `mat_rail`). We are interested in just one nearest neighbor, namely, the closest railway station: ```{r} knn_orig = nabor::knn(mat_rail, query = mat_orig, k = 1)$nn.idx knn_dest = nabor::knn(mat_rail, query = mat_dest, k = 1)$nn.idx ``` This results not in matrices of coordinates, but row indices that can subsequently be used to subset the `mat_rail`. It is worth taking a look at the results to ensure that the process has worked properly, and to explain what has happened: ```{r} as.numeric(knn_orig) as.numeric(knn_dest) ``` The output demonstrates that each object contains three whole numbers (the number of rows in `desire_rail`) representing the rail station closest to the origin and destination of each desire line. Note that while each 'origin station' is different, the destination (station `30`) is the same for all desire lines. This is to be expected because rail travel in cities tends to converge on a single large station (in this case Bristol Temple Meads). The indices can now be used to create matrices representing the rail station of origin and destination: ```{r} mat_rail_o = mat_rail[knn_orig, ] mat_rail_d = mat_rail[knn_dest, ] ``` The final stage is to convert these matrices into meaningful geographic objects, in this case simple feature 'multilinestrings' that capture the fact that each stage is a separate line, but part of the same overall trip: ```{r} mats2line = function(mat1, mat2) { lapply(1:nrow(mat1), function(i) { rbind(mat1[i, ], mat2[i, ]) %>% st_linestring() }) %>% st_sfc() } desire_rail$leg_orig = mats2line(mat_orig, mat_rail_o) desire_rail$leg_rail = mats2line(mat_rail_o, mat_rail_d) desire_rail$leg_dest = mats2line(mat_rail_d, mat_dest) ``` ```{r, eval=FALSE, echo=FALSE} # Create equivalent geographic objects using multilinestrings: tmp = lapply(1:nrow(mat_orig), function(i) { list( st_linestring(rbind(mat_orig[i, ], mat_rail_o[i, ])), st_linestring(rbind(mat_rail_o[i, ], mat_rail_d[i, ])), st_linestring(rbind(mat_rail_d[i, ], mat_dest[i, ]))) %>% st_multilinestring }) tmp = st_sfc(tmp) plot(tmp, type = "n") lapply(tmp, function(x) plot(x[[1]], col = "red", add = TRUE)) lapply(tmp, function(x) plot(x[[2]], col = "blue", add = TRUE)) lapply(tmp, function(x) plot(x[[3]], col = "green", add = TRUE)) ``` The results are visualised below: ```{r stations, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Station nodes (red dots) used as intermediary points that convert straight desire lines with high rail usage (black) into three legs: to the origin station (red) via public transport (grey) and to the destination (a very short blue line)."} zone_cents = st_centroid(bristol_zones) zone_cents_rail = zone_cents[desire_rail,] plot(desire_rail$geometry, expandBB = c(.1, .1, .1, .1)) plot(desire_rail$leg_orig, add = TRUE, col = "red", lwd = 3) plot(desire_rail$leg_rail, add = TRUE, col = "grey", lwd = 2) plot(bristol_stations, add = TRUE, col = "red") plot(desire_rail$leg_dest, add = TRUE, col = "blue", lwd = 5) plot(zone_cents_rail, add = TRUE, col = "black") # library(tmap) # tmap_mode("plot") # qtm(bristol_stations, basemaps = "https://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=feae177da543411c9efa64160305212d", dots.col = "red", symbols.size = 2) + # tm_shape(desire_rail) + # tm_lines(col = "black", lwd = 4) + # tm_shape(legs) + # tm_lines() ```