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 #> although coordinates are longitude/latitude, st_intersects assumes that they are planar = igraph::graph.adjlist(touching_list) g ::is_connected(g) igraph#>  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#>  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(rnet) sln class(sln) #>  "sfNetwork" #> attr(,"package") #>  "stplanr"
sln has both spatial and graph components, with the number of lines equal to the number graph edges:
class(sln@sl) #>  "sf" "data.frame" nrow(sln@sl) #>  8 class(sln@g) #>  "igraph" length(igraph::edge.attributes(sln@g)[["weight"]]) #>  8 class(sln@nb) #>  "list" length(unique(unlist(sln@nb))) #>  8 identical(sln@sl$geometry, rnet$geometry) #>  TRUE
<- sln2points(sln) sln_nodes nrow(sln_nodes) #>  9 length(sln@nb) #>  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
# plot(rnet$geometry) # plot(sln_nodes, add = TRUE) # xy_path <- sum_network_routes(sln = sln, start = xy_nodes, end = xy_nodes, sumvars = "length") # # xy_path = sum_network_links(sln = sln, start = xy_nodes, end = xy_nodes) # 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 #> although coordinates are longitude/latitude, st_nearest_points assumes that they are planar <- 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: