library(stplanr)
library(sf)
Route networks represent the network of highways, cycleways, footways
and other ways along which transport happens. You can get route network
data from OpenStreetMap (e.g. via the osmdata
R package)
and other providers or transport network data.
Unlike routes, each segment geometry in a route network can only appear once.
stplanr can be used to convert a series of routes
into a route network, using the function overline()
, as
illustrated below:
library(stplanr)
library(sf)
<- routes_fast_sf[2:6, 1]
sample_routes $value <- rep(1:3, length.out = 5)
sample_routes<- overline(sample_routes, attrib = "value")
rnet plot(sample_routes["value"], lwd = sample_routes$value, main = "Routes")
plot(rnet["value"], lwd = rnet$value, main = "Route network")
The above figure shows how overline()
breaks the routes
into segments with the same values and removes overlapping segments. It
is a form of geographic aggregation.
Route networks can be represented as a graph. Usually all segments are connected together, meaning the graph is connected. We can show that very simple network above is connected as follows:
= st_intersects(sample_routes)
touching_list = igraph::graph.adjlist(touching_list)
g ::is_connected(g)
igraph#> [1] TRUE
A more complex network may not be connected in this way, as shown in the example below:
# piggyback::pb_download_url("r_key_roads_test.Rds")
= "https://github.com/ropensci/stplanr/releases/download/0.6.0/r_key_roads_test.Rds"
u = readRDS(url(u))
rnet_disconnected = sf::st_intersects(rnet_disconnected)
touching_list = igraph::graph.adjlist(touching_list)
g ::is_connected(g)
igraph#> [1] FALSE
:::plot.sfc_LINESTRING(rnet_disconnected$geometry) sf
The elements of the network are clearly divided into groups. We can identify these groups as follows:
$group = rnet_igroup(rnet_disconnected) rnet_disconnected
An important feature of route networks is that they are
simultaneously spatial and graph entities. This duality is captured in
sfNetwork
objects, which can be created by the function
SpatialLinesNetwork()
:
<- SpatialLinesNetwork(rnet)
sln class(sln)
#> [1] "sfNetwork"
#> attr(,"package")
#> [1] "stplanr"
sln
has both spatial and graph components, with the
number of lines equal to the number graph edges:
class(sln@sl)
#> [1] "sf" "data.frame"
nrow(sln@sl)
#> [1] 8
class(sln@g)
#> [1] "igraph"
length(igraph::edge.attributes(sln@g)[["weight"]])
#> [1] 8
class(sln@nb)
#> [1] "list"
length(unique(unlist(sln@nb)))
#> [1] 8
identical(sln@sl$geometry, rnet$geometry)
#> [1] TRUE
<- sln2points(sln)
sln_nodes nrow(sln_nodes)
#> [1] 9
length(sln@nb)
#> [1] 9
<- sf::st_coordinates(rnet)
rnet_coordinates set.seed(85)
<- runif(n = 2, min = min(rnet_coordinates[, 1]), max = max(rnet_coordinates[, 1]))
x <- runif(n = 2, min = min(rnet_coordinates[, 2]), max = max(rnet_coordinates[, 2]))
y <- sf::st_crs(rnet)
crs <- sf::st_as_sf(data.frame(n = 1:2, x, y), coords = c("x", "y"), crs = crs)
xy_sf <- stplanr::find_network_nodes(sln = sln, x = x, y = y) xy_nodes
Currently not running due to issues with dev version of
dplyr
:
https://github.com/ropensci/stplanr/issues/383
# plot(rnet$geometry)
# plot(sln_nodes, add = TRUE)
# xy_path <- sum_network_routes(sln = sln, start = xy_nodes[1], end = xy_nodes[2], sumvars = "length")
# # xy_path = sum_network_links(sln = sln, start = xy_nodes[1], end = xy_nodes[2])
# plot(rnet$geometry)
# plot(xy_sf$geometry, add = TRUE)
# plot(xy_path$geometry, add = TRUE, lwd = 5)
New nodes can be added to the network, although this should be done before the graph representation is created. Imagine we want to create a point half way along the the most westerly route segment in the network, near the coordinates -1.540, 53.826:
<- c(-1.540, 53.826)
new_point_coordinates <- sf::st_sf(geometry = sf::st_sfc(sf::st_point(new_point_coordinates)), crs = crs) p
We can identify the nearest point on the network at this point and use that to split the associated linestring:
<- sln_add_node(sln = sln, p = p)
sln_new <- route_local(sln = sln_new, from = p, to = xy_sf[1, ])
route_new plot(sln_new)
plot(p, add = TRUE)
plot(route_new, lwd = 5, add = TRUE)
#> Warning in plot.sf(route_new, lwd = 5, add = TRUE): ignoring all but the first
#> attribute
Other approaches to working with route networks include: