Title: | Tidy Geospatial Networks |
---|---|
Description: | Provides a tidy approach to spatial network analysis, in the form of classes and functions that enable a seamless interaction between the network analysis package 'tidygraph' and the spatial analysis package 'sf'. |
Authors: | Lucas van der Meer [aut, cre] , Lorena Abad [aut] , Andrea Gilardi [aut] , Robin Lovelace [aut] |
Maintainer: | Lucas van der Meer <[email protected]> |
License: | Apache License (>= 2) |
Version: | 0.6.5 |
Built: | 2024-12-06 18:43:01 UTC |
Source: | CRAN |
Convert a given object into an object of class sfnetwork
.
If an object can be read by as_tbl_graph
and the
nodes can be read by st_as_sf
, it is automatically
supported.
as_sfnetwork(x, ...) ## Default S3 method: as_sfnetwork(x, ...) ## S3 method for class 'sf' as_sfnetwork(x, ...) ## S3 method for class 'linnet' as_sfnetwork(x, ...) ## S3 method for class 'psp' as_sfnetwork(x, ...) ## S3 method for class 'sfc' as_sfnetwork(x, ...) ## S3 method for class 'sfNetwork' as_sfnetwork(x, ...) ## S3 method for class 'sfnetwork' as_sfnetwork(x, ...) ## S3 method for class 'tbl_graph' as_sfnetwork(x, ...)
as_sfnetwork(x, ...) ## Default S3 method: as_sfnetwork(x, ...) ## S3 method for class 'sf' as_sfnetwork(x, ...) ## S3 method for class 'linnet' as_sfnetwork(x, ...) ## S3 method for class 'psp' as_sfnetwork(x, ...) ## S3 method for class 'sfc' as_sfnetwork(x, ...) ## S3 method for class 'sfNetwork' as_sfnetwork(x, ...) ## S3 method for class 'sfnetwork' as_sfnetwork(x, ...) ## S3 method for class 'tbl_graph' as_sfnetwork(x, ...)
x |
Object to be converted into an |
... |
Arguments passed on to the |
An object of class sfnetwork
.
as_sfnetwork(sf)
: Only sf objects with either exclusively geometries
of type LINESTRING
or exclusively geometries of type POINT
are
supported. For lines, is assumed that the given features form the edges.
Nodes are created at the endpoints of the lines. Endpoints which are shared
between multiple edges become a single node. For points, it is assumed that
the given features geometries form the nodes. They will be connected by
edges sequentially. Hence, point 1 to point 2, point 2 to point 3, etc.
# From an sf object. library(sf, quietly = TRUE) # With LINESTRING geometries. as_sfnetwork(roxel) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(st_geometry(roxel)) plot(as_sfnetwork(roxel)) par(oldpar) # With POINT geometries. p1 = st_point(c(7, 51)) p2 = st_point(c(7, 52)) p3 = st_point(c(8, 52)) points = st_as_sf(st_sfc(p1, p2, p3)) as_sfnetwork(points) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(st_geometry(points)) plot(as_sfnetwork(points)) par(oldpar) # From a linnet object. if (require(spatstat.geom, quietly = TRUE)) { as_sfnetwork(simplenet) } # From a psp object. if (require(spatstat.geom, quietly = TRUE)) { set.seed(42) test_psp = psp(runif(10), runif(10), runif(10), runif(10), window=owin()) as_sfnetwork(test_psp) }
# From an sf object. library(sf, quietly = TRUE) # With LINESTRING geometries. as_sfnetwork(roxel) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(st_geometry(roxel)) plot(as_sfnetwork(roxel)) par(oldpar) # With POINT geometries. p1 = st_point(c(7, 51)) p2 = st_point(c(7, 52)) p3 = st_point(c(8, 52)) points = st_as_sf(st_sfc(p1, p2, p3)) as_sfnetwork(points) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(st_geometry(points)) plot(as_sfnetwork(points)) par(oldpar) # From a linnet object. if (require(spatstat.geom, quietly = TRUE)) { as_sfnetwork(simplenet) } # From a psp object. if (require(spatstat.geom, quietly = TRUE)) { set.seed(42) test_psp = psp(runif(10), runif(10), runif(10), runif(10), window=owin()) as_sfnetwork(test_psp) }
The sfnetwork method for as_tibble
is conceptually
different. Whenever a geometry list column is present, it will by default
return what we call a 'spatial tibble'. With that we mean an object of
class c('sf', 'tbl_df')
instead of an object of class
'tbl_df'
. This little conceptual trick is essential for how
tidyverse functions handle sfnetwork
objects, i.e. always
using the corresponding sf
method if present. When using
as_tibble
on sfnetwork
objects directly
as a user, you can disable this behaviour by setting spatial = FALSE
.
## S3 method for class 'sfnetwork' as_tibble(x, active = NULL, spatial = TRUE, ...)
## S3 method for class 'sfnetwork' as_tibble(x, active = NULL, spatial = TRUE, ...)
x |
An object of class |
active |
Which network element (i.e. nodes or edges) to activate before
extracting. If |
spatial |
Should the extracted tibble be a 'spatial tibble', i.e. an
object of class |
... |
Arguments passed on to |
The active element of the network as an object of class
tibble
.
library(tibble, quietly = TRUE) net = as_sfnetwork(roxel) # Extract the active network element as a spatial tibble. as_tibble(net) # Extract any network element as a spatial tibble. as_tibble(net, "edges") # Extract the active network element as a regular tibble. as_tibble(net, spatial = FALSE)
library(tibble, quietly = TRUE) net = as_sfnetwork(roxel) # Extract the active network element as a spatial tibble. as_tibble(net) # Extract any network element as a spatial tibble. as_tibble(net, "edges") # Extract the active network element as a regular tibble. as_tibble(net, spatial = FALSE)
A method to convert an object of class sfnetwork
into
linnet
format and enhance the
interoperability between sfnetworks
and spatstat
. Use
this method without the .sfnetwork suffix and after loading the
spatstat
package.
as.linnet.sfnetwork(X, ...)
as.linnet.sfnetwork(X, ...)
X |
An object of class |
... |
Arguments passed to |
An object of class linnet
.
as_sfnetwork
to convert objects of class
linnet
into objects of class
sfnetwork
.
Plot the geometries of an object of class sfnetwork
automatically as a ggplot
object. Use this method
without the .sfnetwork suffix and after loading the ggplot2
package.
autoplot.sfnetwork(object, ...)
autoplot.sfnetwork(object, ...)
object |
An object of class |
... |
Ignored. |
See autoplot
.
An object of class ggplot
.
Check if an object is a sfnetwork
is.sfnetwork(x)
is.sfnetwork(x)
x |
Object to be checked. |
TRUE
if the given object is an object of class
sfnetwork
, FALSE
otherwise.
library(tidygraph, quietly = TRUE, warn.conflicts = FALSE) net = as_sfnetwork(roxel) is.sfnetwork(net) is.sfnetwork(as_tbl_graph(net))
library(tidygraph, quietly = TRUE, warn.conflicts = FALSE) net = as_sfnetwork(roxel) is.sfnetwork(net) is.sfnetwork(as_tbl_graph(net))
These functions allow to query specific coordinate values from the geometries of the nodes.
node_X() node_Y() node_Z() node_M()
node_X() node_Y() node_Z() node_M()
Just as with all query functions in tidygraph, these functions
are meant to be called inside tidygraph verbs such as
mutate
or filter
, where
the network that is currently being worked on is known and thus not needed
as an argument to the function. If you want to use an algorithm outside of
the tidygraph framework you can use with_graph
to
set the context temporarily while the algorithm is being evaluated.
A numeric vector of the same length as the number of nodes in the network.
If a requested coordinate value is not available for a node, NA
will be returned.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(roxel) # Use query function in a filter call. filtered = net %>% activate("nodes") %>% filter(node_X() > 7.54) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net, col = "grey") plot(filtered, col = "red", add = TRUE) par(oldpar) # Use query function in a mutate call. net %>% activate("nodes") %>% mutate(X = node_X(), Y = node_Y())
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(roxel) # Use query function in a filter call. filtered = net %>% activate("nodes") %>% filter(node_X() > 7.54) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net, col = "grey") plot(filtered, col = "red", add = TRUE) par(oldpar) # Use query function in a mutate call. net %>% activate("nodes") %>% mutate(X = node_X(), Y = node_Y())
Plot the geometries of an object of class sfnetwork
.
## S3 method for class 'sfnetwork' plot(x, draw_lines = TRUE, ...)
## S3 method for class 'sfnetwork' plot(x, draw_lines = TRUE, ...)
x |
Object of class |
draw_lines |
If the edges of the network are spatially implicit, should
straight lines be drawn between connected nodes? Defaults to |
... |
Arguments passed on to |
This is a basic plotting functionality. For more advanced plotting,
it is recommended to extract the nodes and edges from the network, and plot
them separately with one of the many available spatial plotting functions
as can be found in sf
, tmap
, ggplot2
, ggspatial
,
and others.
This is a plot method and therefore has no visible return value.
oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,1)) net = as_sfnetwork(roxel) plot(net) # When lines are spatially implicit. par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(roxel, edges_as_lines = FALSE) plot(net) plot(net, draw_lines = FALSE) # Changing default settings. par(mar = c(1,1,1,1), mfrow = c(1,1)) plot(net, col = 'blue', pch = 18, lwd = 1, cex = 2) # Add grid and axis par(mar = c(2.5,2.5,1,1)) plot(net, graticule = TRUE, axes = TRUE) par(oldpar)
oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,1)) net = as_sfnetwork(roxel) plot(net) # When lines are spatially implicit. par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(roxel, edges_as_lines = FALSE) plot(net) plot(net, draw_lines = FALSE) # Changing default settings. par(mar = c(1,1,1,1), mfrow = c(1,1)) plot(net, col = 'blue', pch = 18, lwd = 1, cex = 2) # Add grid and axis par(mar = c(2.5,2.5,1,1)) plot(net, graticule = TRUE, axes = TRUE) par(oldpar)
A dataset containing the road network (roads, bikelanes, footpaths, etc.) of Roxel, a neighborhood in the city of Münster, Germany. The data are taken from OpenStreetMap, querying by key = 'highway'. The topology is cleaned with the v.clean tool in GRASS GIS.
roxel
roxel
An object of class sf
with LINESTRING
geometries, containing 851 features and three columns:
the name of the road, if it exists
the type of the road, e.g. cycleway
the geometry list column
s2 methods for sfnetworks
as_s2_geography.sfnetwork(x, ...)
as_s2_geography.sfnetwork(x, ...)
x |
An object of class |
... |
Arguments passed on the corresponding |
sf
methods for sfnetwork
objects.
## S3 method for class 'sfnetwork' st_as_sf(x, active = NULL, ...) ## S3 method for class 'sfnetwork' st_as_s2(x, active = NULL, ...) ## S3 method for class 'sfnetwork' st_geometry(obj, active = NULL, ...) ## S3 replacement method for class 'sfnetwork' st_geometry(x) <- value ## S3 method for class 'sfnetwork' st_drop_geometry(x, ...) ## S3 method for class 'sfnetwork' st_bbox(obj, active = NULL, ...) ## S3 method for class 'sfnetwork' st_coordinates(x, active = NULL, ...) ## S3 method for class 'sfnetwork' st_is(x, ...) ## S3 method for class 'sfnetwork' st_is_valid(x, ...) ## S3 method for class 'sfnetwork' st_crs(x, ...) ## S3 replacement method for class 'sfnetwork' st_crs(x) <- value ## S3 method for class 'sfnetwork' st_precision(x) ## S3 method for class 'sfnetwork' st_set_precision(x, precision) ## S3 method for class 'sfnetwork' st_shift_longitude(x, ...) ## S3 method for class 'sfnetwork' st_transform(x, ...) ## S3 method for class 'sfnetwork' st_wrap_dateline(x, ...) ## S3 method for class 'sfnetwork' st_normalize(x, ...) ## S3 method for class 'sfnetwork' st_zm(x, ...) ## S3 method for class 'sfnetwork' st_m_range(obj, active = NULL, ...) ## S3 method for class 'sfnetwork' st_z_range(obj, active = NULL, ...) ## S3 method for class 'sfnetwork' st_agr(x, active = NULL, ...) ## S3 replacement method for class 'sfnetwork' st_agr(x) <- value ## S3 method for class 'sfnetwork' st_reverse(x, ...) ## S3 method for class 'sfnetwork' st_simplify(x, ...) ## S3 method for class 'sfnetwork' st_join(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_join(x, y, ...) ## S3 method for class 'sfnetwork' st_filter(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_filter(x, y, ...) ## S3 method for class 'sfnetwork' st_crop(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_crop(x, y, ...) ## S3 method for class 'sfnetwork' st_difference(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_difference(x, y, ...) ## S3 method for class 'sfnetwork' st_intersection(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_intersection(x, y, ...) ## S3 method for class 'sfnetwork' st_intersects(x, y, ...) ## S3 method for class 'sfnetwork' st_sample(x, ...) ## S3 method for class 'sfnetwork' st_nearest_points(x, y, ...) ## S3 method for class 'sfnetwork' st_area(x, ...)
## S3 method for class 'sfnetwork' st_as_sf(x, active = NULL, ...) ## S3 method for class 'sfnetwork' st_as_s2(x, active = NULL, ...) ## S3 method for class 'sfnetwork' st_geometry(obj, active = NULL, ...) ## S3 replacement method for class 'sfnetwork' st_geometry(x) <- value ## S3 method for class 'sfnetwork' st_drop_geometry(x, ...) ## S3 method for class 'sfnetwork' st_bbox(obj, active = NULL, ...) ## S3 method for class 'sfnetwork' st_coordinates(x, active = NULL, ...) ## S3 method for class 'sfnetwork' st_is(x, ...) ## S3 method for class 'sfnetwork' st_is_valid(x, ...) ## S3 method for class 'sfnetwork' st_crs(x, ...) ## S3 replacement method for class 'sfnetwork' st_crs(x) <- value ## S3 method for class 'sfnetwork' st_precision(x) ## S3 method for class 'sfnetwork' st_set_precision(x, precision) ## S3 method for class 'sfnetwork' st_shift_longitude(x, ...) ## S3 method for class 'sfnetwork' st_transform(x, ...) ## S3 method for class 'sfnetwork' st_wrap_dateline(x, ...) ## S3 method for class 'sfnetwork' st_normalize(x, ...) ## S3 method for class 'sfnetwork' st_zm(x, ...) ## S3 method for class 'sfnetwork' st_m_range(obj, active = NULL, ...) ## S3 method for class 'sfnetwork' st_z_range(obj, active = NULL, ...) ## S3 method for class 'sfnetwork' st_agr(x, active = NULL, ...) ## S3 replacement method for class 'sfnetwork' st_agr(x) <- value ## S3 method for class 'sfnetwork' st_reverse(x, ...) ## S3 method for class 'sfnetwork' st_simplify(x, ...) ## S3 method for class 'sfnetwork' st_join(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_join(x, y, ...) ## S3 method for class 'sfnetwork' st_filter(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_filter(x, y, ...) ## S3 method for class 'sfnetwork' st_crop(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_crop(x, y, ...) ## S3 method for class 'sfnetwork' st_difference(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_difference(x, y, ...) ## S3 method for class 'sfnetwork' st_intersection(x, y, ...) ## S3 method for class 'morphed_sfnetwork' st_intersection(x, y, ...) ## S3 method for class 'sfnetwork' st_intersects(x, y, ...) ## S3 method for class 'sfnetwork' st_sample(x, ...) ## S3 method for class 'sfnetwork' st_nearest_points(x, y, ...) ## S3 method for class 'sfnetwork' st_area(x, ...)
x |
An object of class |
active |
Which network element (i.e. nodes or edges) to activate before
extracting. If |
... |
Arguments passed on the corresponding |
obj |
An object of class |
value |
The value to be assigned. See the documentation of the corresponding sf function for details. |
precision |
The precision to be assigned. See
|
y |
An object of class |
See the sf
documentation.
The sfnetwork
method for st_as_sf
returns
the active element of the network as object of class sf
.
The sfnetwork
and morphed_sfnetwork
methods for
st_join
, st_filter
,
st_intersection
, st_difference
,
st_crop
and the setter functions
return an object of class sfnetwork
and morphed_sfnetwork
respectively. All other
methods return the same type of objects as their corresponding sf function.
See the sf
documentation for details.
library(sf, quietly = TRUE) net = as_sfnetwork(roxel) # Extract the active network element. st_as_sf(net) # Extract any network element. st_as_sf(net, "edges") # Get geometry of the active network element. st_geometry(net) # Get geometry of any network element. st_geometry(net, "edges") # Get bbox of the active network element. st_bbox(net) # Get CRS of the network. st_crs(net) # Get agr factor of the active network element. st_agr(net) # Get agr factor of any network element. st_agr(net, "edges") # Spatial join applied to the active network element. net = st_transform(net, 3035) codes = st_as_sf(st_make_grid(net, n = c(2, 2))) codes$post_code = as.character(seq(1000, 1000 + nrow(codes) * 10 - 10, 10)) joined = st_join(net, codes, join = st_intersects) joined oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, col = "grey") plot(codes, col = NA, border = "red", lty = 4, lwd = 4, add = TRUE) text(st_coordinates(st_centroid(st_geometry(codes))), codes$post_code) plot(st_geometry(joined, "edges")) plot(st_as_sf(joined, "nodes"), pch = 20, add = TRUE) par(oldpar) # Spatial filter applied to the active network element. p1 = st_point(c(4151358, 3208045)) p2 = st_point(c(4151340, 3207520)) p3 = st_point(c(4151756, 3207506)) p4 = st_point(c(4151774, 3208031)) poly = st_multipoint(c(p1, p2, p3, p4)) %>% st_cast('POLYGON') %>% st_sfc(crs = 3035) %>% st_as_sf() filtered = st_filter(net, poly, .pred = st_intersects) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, col = "grey") plot(poly, border = "red", lty = 4, lwd = 4, add = TRUE) plot(filtered) par(oldpar)
library(sf, quietly = TRUE) net = as_sfnetwork(roxel) # Extract the active network element. st_as_sf(net) # Extract any network element. st_as_sf(net, "edges") # Get geometry of the active network element. st_geometry(net) # Get geometry of any network element. st_geometry(net, "edges") # Get bbox of the active network element. st_bbox(net) # Get CRS of the network. st_crs(net) # Get agr factor of the active network element. st_agr(net) # Get agr factor of any network element. st_agr(net, "edges") # Spatial join applied to the active network element. net = st_transform(net, 3035) codes = st_as_sf(st_make_grid(net, n = c(2, 2))) codes$post_code = as.character(seq(1000, 1000 + nrow(codes) * 10 - 10, 10)) joined = st_join(net, codes, join = st_intersects) joined oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, col = "grey") plot(codes, col = NA, border = "red", lty = 4, lwd = 4, add = TRUE) text(st_coordinates(st_centroid(st_geometry(codes))), codes$post_code) plot(st_geometry(joined, "edges")) plot(st_as_sf(joined, "nodes"), pch = 20, add = TRUE) par(oldpar) # Spatial filter applied to the active network element. p1 = st_point(c(4151358, 3208045)) p2 = st_point(c(4151340, 3207520)) p3 = st_point(c(4151756, 3207506)) p4 = st_point(c(4151774, 3208031)) poly = st_multipoint(c(p1, p2, p3, p4)) %>% st_cast('POLYGON') %>% st_sfc(crs = 3035) %>% st_as_sf() filtered = st_filter(net, poly, .pred = st_intersects) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, col = "grey") plot(poly, border = "red", lty = 4, lwd = 4, add = TRUE) plot(filtered) par(oldpar)
Query sf attributes from the active element of a sfnetwork
sf_attr(x, name, active = NULL)
sf_attr(x, name, active = NULL)
x |
An object of class |
name |
Name of the attribute to query. Either |
active |
Which network element (i.e. nodes or edges) to activate before
extracting. If |
sf attributes include sf_column
(the name of the sf column)
and agr
(the attribute-geometry-relationships).
The value of the attribute matched, or NULL
if no exact
match is found.
net = as_sfnetwork(roxel) sf_attr(net, "agr", active = "edges") sf_attr(net, "sf_column", active = "nodes")
net = as_sfnetwork(roxel) sf_attr(net, "agr", active = "edges") sf_attr(net, "sf_column", active = "nodes")
sfnetwork
is a tidy data structure for geospatial networks. It
extends the tbl_graph
data structure for
relational data into the domain of geospatial networks, with nodes and
edges embedded in geographical space, and offers smooth integration with
sf
for spatial data analysis.
sfnetwork( nodes, edges = NULL, directed = TRUE, node_key = "name", edges_as_lines = NULL, length_as_weight = FALSE, force = FALSE, message = TRUE, ... )
sfnetwork( nodes, edges = NULL, directed = TRUE, node_key = "name", edges_as_lines = NULL, length_as_weight = FALSE, force = FALSE, message = TRUE, ... )
nodes |
The nodes of the network. Should be an object of class
|
edges |
The edges of the network. May be an object of class
|
directed |
Should the constructed network be directed? Defaults to
|
node_key |
The name of the column in the nodes table that character
represented |
edges_as_lines |
Should the edges be spatially explicit, i.e. have
|
length_as_weight |
Should the length of the edges be stored in a column
named |
force |
Should network validity checks be skipped? Defaults to
|
message |
Should informational messages (those messages that are
neither warnings nor errors) be printed when constructing the network?
Defaults to |
... |
Arguments passed on to |
An object of class sfnetwork
.
library(sf, quietly = TRUE) ## Create sfnetwork from sf objects p1 = st_point(c(7, 51)) p2 = st_point(c(7, 52)) p3 = st_point(c(8, 52)) nodes = st_as_sf(st_sfc(p1, p2, p3, crs = 4326)) e1 = st_cast(st_union(p1, p2), "LINESTRING") e2 = st_cast(st_union(p1, p3), "LINESTRING") e3 = st_cast(st_union(p3, p2), "LINESTRING") edges = st_as_sf(st_sfc(e1, e2, e3, crs = 4326)) edges$from = c(1, 1, 3) edges$to = c(2, 3, 2) # Default. sfnetwork(nodes, edges) # Undirected network. sfnetwork(nodes, edges, directed = FALSE) # Using character encoded from and to columns. nodes$name = c("city", "village", "farm") edges$from = c("city", "city", "farm") edges$to = c("village", "farm", "village") sfnetwork(nodes, edges, node_key = "name") # Spatially implicit edges. sfnetwork(nodes, edges, edges_as_lines = FALSE) # Store edge lenghts in a weight column. sfnetwork(nodes, edges, length_as_weight = TRUE) # Adjust the number of features printed by active and inactive components oldoptions = options(sfn_max_print_active = 1, sfn_max_print_inactive = 2) sfnetwork(nodes, edges) options(oldoptions)
library(sf, quietly = TRUE) ## Create sfnetwork from sf objects p1 = st_point(c(7, 51)) p2 = st_point(c(7, 52)) p3 = st_point(c(8, 52)) nodes = st_as_sf(st_sfc(p1, p2, p3, crs = 4326)) e1 = st_cast(st_union(p1, p2), "LINESTRING") e2 = st_cast(st_union(p1, p3), "LINESTRING") e3 = st_cast(st_union(p3, p2), "LINESTRING") edges = st_as_sf(st_sfc(e1, e2, e3, crs = 4326)) edges$from = c(1, 1, 3) edges$to = c(2, 3, 2) # Default. sfnetwork(nodes, edges) # Undirected network. sfnetwork(nodes, edges, directed = FALSE) # Using character encoded from and to columns. nodes$name = c("city", "village", "farm") edges$from = c("city", "city", "farm") edges$to = c("village", "farm", "village") sfnetwork(nodes, edges, node_key = "name") # Spatially implicit edges. sfnetwork(nodes, edges, edges_as_lines = FALSE) # Store edge lenghts in a weight column. sfnetwork(nodes, edges, length_as_weight = TRUE) # Adjust the number of features printed by active and inactive components oldoptions = options(sfn_max_print_active = 1, sfn_max_print_inactive = 2) sfnetwork(nodes, edges) options(oldoptions)
These functions are a collection of specific spatial edge measures, that
form a spatial extension to edge measures in
tidygraph
.
edge_azimuth(degrees = FALSE) edge_circuity(Inf_as_NaN = FALSE) edge_length() edge_displacement()
edge_azimuth(degrees = FALSE) edge_circuity(Inf_as_NaN = FALSE) edge_length() edge_displacement()
degrees |
Should the angle be returned in degrees instead of radians?
Defaults to |
Inf_as_NaN |
Should the circuity values of loop edges be stored as
|
Just as with all query functions in tidygraph, spatial edge
measures are meant to be called inside tidygraph verbs such as
mutate
or filter
, where
the network that is currently being worked on is known and thus not needed
as an argument to the function. If you want to use an algorithm outside of
the tidygraph framework you can use with_graph
to
set the context temporarily while the algorithm is being evaluated.
A numeric vector of the same length as the number of edges in the graph.
edge_azimuth()
: The angle in radians between a straight
line from the edge startpoint pointing north, and the straight line from
the edge startpoint and the edge endpoint. Calculated with
st_geod_azimuth
. Requires a geographic CRS.
edge_circuity()
: The ratio of the length of an edge
linestring geometry versus the straight-line distance between its boundary
nodes, as described in Giacomin &
Levinson, 2015. DOI: 10.1068/b130131p.
edge_length()
: The length of an edge linestring geometry
as calculated by st_length
.
edge_displacement()
: The straight-line distance between the two
boundary nodes of an edge, as calculated by st_distance
.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) net = as_sfnetwork(roxel) net %>% activate("edges") %>% mutate(azimuth = edge_azimuth()) net %>% activate("edges") %>% mutate(azimuth = edge_azimuth(degrees = TRUE)) net %>% activate("edges") %>% mutate(circuity = edge_circuity()) net %>% activate("edges") %>% mutate(length = edge_length()) net %>% activate("edges") %>% mutate(displacement = edge_displacement())
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) net = as_sfnetwork(roxel) net %>% activate("edges") %>% mutate(azimuth = edge_azimuth()) net %>% activate("edges") %>% mutate(azimuth = edge_azimuth(degrees = TRUE)) net %>% activate("edges") %>% mutate(circuity = edge_circuity()) net %>% activate("edges") %>% mutate(length = edge_length()) net %>% activate("edges") %>% mutate(displacement = edge_displacement())
These functions allow to interpret spatial relations between edges and
other geospatial features directly inside filter
and mutate
calls. All functions return a logical
vector of the same length as the number of edges in the network. Element i
in that vector is TRUE
whenever any(predicate(x[i], y[j]))
is
TRUE
. Hence, in the case of using edge_intersects
, element i
in the returned vector is TRUE
when edge i intersects with any of
the features given in y.
edge_intersects(y, ...) edge_is_disjoint(y, ...) edge_touches(y, ...) edge_crosses(y, ...) edge_is_within(y, ...) edge_contains(y, ...) edge_contains_properly(y, ...) edge_overlaps(y, ...) edge_equals(y, ...) edge_covers(y, ...) edge_is_covered_by(y, ...) edge_is_within_distance(y, ...)
edge_intersects(y, ...) edge_is_disjoint(y, ...) edge_touches(y, ...) edge_crosses(y, ...) edge_is_within(y, ...) edge_contains(y, ...) edge_contains_properly(y, ...) edge_overlaps(y, ...) edge_equals(y, ...) edge_covers(y, ...) edge_is_covered_by(y, ...) edge_is_within_distance(y, ...)
y |
The geospatial features to test the edges against, either as an
object of class |
... |
Arguments passed on to the corresponding spatial predicate
function of sf. See |
See geos_binary_pred
for details on each spatial
predicate. Just as with all query functions in tidygraph, these functions
are meant to be called inside tidygraph verbs such as
mutate
or filter
, where
the network that is currently being worked on is known and thus not needed
as an argument to the function. If you want to use an algorithm outside of
the tidygraph framework you can use with_graph
to
set the context temporarily while the algorithm is being evaluated.
A logical vector of the same length as the number of edges in the network.
Note that edge_is_within_distance
is a wrapper around the
st_is_within_distance
predicate from sf. Hence, it is based on
'as-the-crow-flies' distance, and not on distances over the network.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(roxel) %>% st_transform(3035) # Create a geometry to test against. p1 = st_point(c(4151358, 3208045)) p2 = st_point(c(4151340, 3207520)) p3 = st_point(c(4151756, 3207506)) p4 = st_point(c(4151774, 3208031)) poly = st_multipoint(c(p1, p2, p3, p4)) %>% st_cast('POLYGON') %>% st_sfc(crs = 3035) # Use predicate query function in a filter call. intersects = net %>% activate(edges) %>% filter(edge_intersects(poly)) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(st_geometry(net, "edges")) plot(st_geometry(intersects, "edges"), col = "red", lwd = 2, add = TRUE) par(oldpar) # Use predicate query function in a mutate call. net %>% activate(edges) %>% mutate(disjoint = edge_is_disjoint(poly)) %>% select(disjoint)
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(roxel) %>% st_transform(3035) # Create a geometry to test against. p1 = st_point(c(4151358, 3208045)) p2 = st_point(c(4151340, 3207520)) p3 = st_point(c(4151756, 3207506)) p4 = st_point(c(4151774, 3208031)) poly = st_multipoint(c(p1, p2, p3, p4)) %>% st_cast('POLYGON') %>% st_sfc(crs = 3035) # Use predicate query function in a filter call. intersects = net %>% activate(edges) %>% filter(edge_intersects(poly)) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(st_geometry(net, "edges")) plot(st_geometry(intersects, "edges"), col = "red", lwd = 2, add = TRUE) par(oldpar) # Use predicate query function in a mutate call. net %>% activate(edges) %>% mutate(disjoint = edge_is_disjoint(poly)) %>% select(disjoint)
Spatial morphers form spatial add-ons to the set of
morphers
provided by tidygraph
. These
functions are not meant to be called directly. They should either be passed
into morph
to create a temporary alternative
representation of the input network. Such an alternative representation is a
list of one or more network objects. Single elements of that list can be
extracted directly as a new network by passing the morpher to
convert
instead, to make the changes lasting rather
than temporary. Alternatively, if the morphed state contains multiple
elements, all of them can be extracted together inside a
tbl_df
by passing the morpher to
crystallise
.
to_spatial_contracted( x, ..., simplify = FALSE, summarise_attributes = "ignore", store_original_data = FALSE ) to_spatial_directed(x) to_spatial_explicit(x, ...) to_spatial_neighborhood(x, node, threshold, weights = NULL, from = TRUE, ...) to_spatial_shortest_paths(x, ...) to_spatial_simple( x, remove_multiple = TRUE, remove_loops = TRUE, summarise_attributes = "first", store_original_data = FALSE ) to_spatial_smooth( x, protect = NULL, summarise_attributes = "ignore", require_equal = FALSE, store_original_data = FALSE ) to_spatial_subdivision(x) to_spatial_subset(x, ..., subset_by = NULL) to_spatial_transformed(x, ...)
to_spatial_contracted( x, ..., simplify = FALSE, summarise_attributes = "ignore", store_original_data = FALSE ) to_spatial_directed(x) to_spatial_explicit(x, ...) to_spatial_neighborhood(x, node, threshold, weights = NULL, from = TRUE, ...) to_spatial_shortest_paths(x, ...) to_spatial_simple( x, remove_multiple = TRUE, remove_loops = TRUE, summarise_attributes = "first", store_original_data = FALSE ) to_spatial_smooth( x, protect = NULL, summarise_attributes = "ignore", require_equal = FALSE, store_original_data = FALSE ) to_spatial_subdivision(x) to_spatial_subset(x, ..., subset_by = NULL) to_spatial_transformed(x, ...)
x |
An object of class |
... |
Arguments to be passed on to other functions. See the description of each morpher for details. |
simplify |
Should the network be simplified after contraction? This
means that multiple edges and loop edges will be removed. Multiple edges
are introduced by contraction when there are several connections between
the same groups of nodes. Loop edges are introduced by contraction when
there are connections within a group. Note however that setting this to
|
summarise_attributes |
Whenever multiple features (i.e. nodes and/or
edges) are merged into a single feature during morphing, how should their
attributes be combined? Several options are possible, see
|
store_original_data |
Whenever multiple features (i.e. nodes and/or
edges) are merged into a single feature during morphing, should the data of
the original features be stored as an attribute of the new feature, in a
column named |
node |
The geospatial point for which the neighborhood will be
calculated. Can be an integer, referring to the index of the node for which
the neighborhood will be calculated. Can also be an object of class
|
threshold |
The threshold distance to be used. Only nodes within the threshold distance from the reference node will be included in the neighborhood. Should be a numeric value in the same units as the weight values used for distance calculation. |
weights |
The edge weights used to calculate distances on the network.
Can be a numeric vector giving edge weights, or a column name referring to
an attribute column in the edges table containing those weights. If set to
|
from |
Should distances be calculated from the reference node towards
the other nodes? Defaults to |
remove_multiple |
Should multiple edges be merged into one. Defaults
to |
remove_loops |
Should loop edges be removed. Defaults to |
protect |
Nodes to be protected from being removed, no matter if they
are a pseudo node or not. Can be given as a numeric vector containing node
indices or a character vector containing node names. Can also be a set of
geospatial features as object of class |
require_equal |
Should nodes only be removed when the attribute values
of their incident edges are equal? Defaults to |
subset_by |
Whether to create subgraphs based on nodes or edges. |
It also possible to create your own morphers. See the documentation
of morph
for the requirements for custom morphers.
Either a morphed_sfnetwork
, which is a list of one or more
sfnetwork
objects, or a morphed_tbl_graph
, which is a
list of one or more tbl_graph
objects. See the
description of each morpher for details.
to_spatial_contracted()
: Combine groups of nodes into a single node per
group. ...
is forwarded to group_by
to
create the groups. The centroid of the group of nodes will be used as
geometry of the contracted node. If edge are spatially explicit, edge
geometries are updated accordingly such that the valid spatial network
structure is preserved. Returns a morphed_sfnetwork
containing a
single element of class sfnetwork
.
to_spatial_directed()
: Make a network directed in the direction given
by the linestring geometries of the edges. Differs from
to_directed
, which makes a network directed based
on the node indices given in the from
and to
columns. In
undirected networks these indices may not correspond with the endpoints of
the linestring geometries. Returns a morphed_sfnetwork
containing a
single element of class sfnetwork
. This morpher requires edges
to be spatially explicit. If not, use to_directed
.
to_spatial_explicit()
: Create linestring geometries between source
and target nodes of edges. If the edges data can be directly converted to
an object of class sf
using st_as_sf
,
extra arguments can be provided as ...
and will be forwarded to
st_as_sf
internally. Otherwise, straight lines will be
drawn between the source and target node of each edge. Returns a
morphed_sfnetwork
containing a single element of class
sfnetwork
.
to_spatial_neighborhood()
: Limit a network to the spatial neighborhood of
a specific node. ...
is forwarded to
node_distance_from
(if from
is TRUE
)
or node_distance_to
(if from
is
FALSE
). Returns a morphed_sfnetwork
containing a single
element of class sfnetwork
.
to_spatial_shortest_paths()
: Limit a network to those nodes and edges that
are part of the shortest path between two nodes. ...
is evaluated in
the same manner as st_network_paths
with
type = 'shortest'
. Returns a morphed_sfnetwork
that may
contain multiple elements of class sfnetwork
, depending on
the number of requested paths. When unmorphing only the first instance of
both the node and edge data will be used, as the the same node and/or edge
can be present in multiple paths.
to_spatial_simple()
: Remove loop edges and/or merges multiple edges
into a single edge. Multiple edges are edges that have the same source and
target nodes (in directed networks) or edges that are incident to the same
nodes (in undirected networks). When merging them into a single edge, the
geometry of the first edge is preserved. The order of the edges can be
influenced by calling arrange
before simplifying.
Returns a morphed_sfnetwork
containing a single element of class
sfnetwork
.
to_spatial_smooth()
: Construct a smoothed version of the network by
iteratively removing pseudo nodes, while preserving the connectivity of the
network. In the case of directed networks, pseudo nodes are those nodes that
have only one incoming and one outgoing edge. In undirected networks, pseudo
nodes are those nodes that have two incident edges. Equality of attribute
values among the two edges can be defined as an additional requirement by
setting the require_equal
parameter. Connectivity of the
network is preserved by concatenating the incident edges of each removed
pseudo node. Returns a morphed_sfnetwork
containing a single element
of class sfnetwork
.
to_spatial_subdivision()
: Construct a subdivision of the network by
subdividing edges at each interior point that is equal to any
other interior or boundary point in the edges table. Interior points in this
sense are those points that are included in their linestring geometry
feature but are not endpoints of it, while boundary points are the endpoints
of the linestrings. The network is reconstructed after subdivision such that
edges are connected at the points of subdivision. Returns a
morphed_sfnetwork
containing a single element of class
sfnetwork
. This morpher requires edges to be spatially
explicit and nodes to be spatially unique (i.e. not more than one node at
the same spatial location).
to_spatial_subset()
: Subset the network by applying a spatial
filter, i.e. a filter on the geometry column based on a spatial predicate.
...
is evaluated in the same manner as st_filter
.
Returns a morphed_sfnetwork
containing a single element of class
sfnetwork
. For filters on an attribute column, use
to_subgraph
.
to_spatial_transformed()
: Transform the geospatial coordinates of the
network into a different coordinate reference system. ...
is
evaluated in the same manner as st_transform
.
Returns a morphed_sfnetwork
containing a single element of class
sfnetwork
.
The vignette on spatial morphers.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) net = as_sfnetwork(roxel, directed = FALSE) %>% st_transform(3035) # Temporary changes with morph and unmorph. net %>% activate("edges") %>% mutate(weight = edge_length()) %>% morph(to_spatial_shortest_paths, from = 1, to = 10) %>% mutate(in_paths = TRUE) %>% unmorph() # Lasting changes with convert. net %>% activate("edges") %>% mutate(weight = edge_length()) %>% convert(to_spatial_shortest_paths, from = 1, to = 10)
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) net = as_sfnetwork(roxel, directed = FALSE) %>% st_transform(3035) # Temporary changes with morph and unmorph. net %>% activate("edges") %>% mutate(weight = edge_length()) %>% morph(to_spatial_shortest_paths, from = 1, to = 10) %>% mutate(in_paths = TRUE) %>% unmorph() # Lasting changes with convert. net %>% activate("edges") %>% mutate(weight = edge_length()) %>% convert(to_spatial_shortest_paths, from = 1, to = 10)
These functions allow to interpret spatial relations between nodes and
other geospatial features directly inside filter
and mutate
calls. All functions return a logical
vector of the same length as the number of nodes in the network. Element i
in that vector is TRUE
whenever any(predicate(x[i], y[j]))
is
TRUE
. Hence, in the case of using node_intersects
, element i
in the returned vector is TRUE
when node i intersects with any of
the features given in y.
node_intersects(y, ...) node_is_disjoint(y, ...) node_touches(y, ...) node_is_within(y, ...) node_equals(y, ...) node_is_covered_by(y, ...) node_is_within_distance(y, ...)
node_intersects(y, ...) node_is_disjoint(y, ...) node_touches(y, ...) node_is_within(y, ...) node_equals(y, ...) node_is_covered_by(y, ...) node_is_within_distance(y, ...)
y |
The geospatial features to test the nodes against, either as an
object of class |
... |
Arguments passed on to the corresponding spatial predicate
function of sf. See |
See geos_binary_pred
for details on each spatial
predicate. Just as with all query functions in tidygraph, these functions
are meant to be called inside tidygraph verbs such as
mutate
or filter
, where
the network that is currently being worked on is known and thus not needed
as an argument to the function. If you want to use an algorithm outside of
the tidygraph framework you can use with_graph
to
set the context temporarily while the algorithm is being evaluated.
A logical vector of the same length as the number of nodes in the network.
Note that node_is_within_distance
is a wrapper around the
st_is_within_distance
predicate from sf. Hence, it is based on
'as-the-crow-flies' distance, and not on distances over the network. For
distances over the network, use node_distance_to
with edge lengths as weights argument.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(roxel) %>% st_transform(3035) # Create a geometry to test against. p1 = st_point(c(4151358, 3208045)) p2 = st_point(c(4151340, 3207520)) p3 = st_point(c(4151756, 3207506)) p4 = st_point(c(4151774, 3208031)) poly = st_multipoint(c(p1, p2, p3, p4)) %>% st_cast('POLYGON') %>% st_sfc(crs = 3035) # Use predicate query function in a filter call. within = net %>% activate("nodes") %>% filter(node_is_within(poly)) disjoint = net %>% activate("nodes") %>% filter(node_is_disjoint(poly)) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net) plot(within, col = "red", add = TRUE) plot(disjoint, col = "blue", add = TRUE) par(oldpar) # Use predicate query function in a mutate call. net %>% activate("nodes") %>% mutate(within = node_is_within(poly)) %>% select(within)
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(roxel) %>% st_transform(3035) # Create a geometry to test against. p1 = st_point(c(4151358, 3208045)) p2 = st_point(c(4151340, 3207520)) p3 = st_point(c(4151756, 3207506)) p4 = st_point(c(4151774, 3208031)) poly = st_multipoint(c(p1, p2, p3, p4)) %>% st_cast('POLYGON') %>% st_sfc(crs = 3035) # Use predicate query function in a filter call. within = net %>% activate("nodes") %>% filter(node_is_within(poly)) disjoint = net %>% activate("nodes") %>% filter(node_is_disjoint(poly)) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net) plot(within, col = "red", add = TRUE) plot(disjoint, col = "blue", add = TRUE) par(oldpar) # Use predicate query function in a mutate call. net %>% activate("nodes") %>% mutate(within = node_is_within(poly)) %>% select(within)
A spatial network specific bounding box extractor, returning the combined bounding box of the nodes and edges in the network.
st_network_bbox(x, ...)
st_network_bbox(x, ...)
x |
An object of class |
... |
Arguments passed on to |
See st_bbox
for details.
The bounding box of the network as an object of class
bbox
.
library(sf) # Create a network. node1 = st_point(c(8, 51)) node2 = st_point(c(7, 51.5)) node3 = st_point(c(8, 52)) node4 = st_point(c(9, 51)) edge1 = st_sfc(st_linestring(c(node1, node2, node3))) nodes = st_as_sf(c(st_sfc(node1), st_sfc(node3), st_sfc(node4))) edges = st_as_sf(edge1) edges$from = 1 edges$to = 2 net = sfnetwork(nodes, edges) # Create bounding boxes for nodes, edges and the whole network. node_bbox = st_bbox(activate(net, "nodes")) node_bbox edge_bbox = st_bbox(activate(net, "edges")) edge_bbox net_bbox = st_network_bbox(net) net_bbox # Plot. oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, lwd = 2, cex = 4, main = "Element bounding boxes") plot(st_as_sfc(node_bbox), border = "red", lty = 2, lwd = 4, add = TRUE) plot(st_as_sfc(edge_bbox), border = "blue", lty = 2, lwd = 4, add = TRUE) plot(net, lwd = 2, cex = 4, main = "Network bounding box") plot(st_as_sfc(net_bbox), border = "red", lty = 2, lwd = 4, add = TRUE) par(oldpar)
library(sf) # Create a network. node1 = st_point(c(8, 51)) node2 = st_point(c(7, 51.5)) node3 = st_point(c(8, 52)) node4 = st_point(c(9, 51)) edge1 = st_sfc(st_linestring(c(node1, node2, node3))) nodes = st_as_sf(c(st_sfc(node1), st_sfc(node3), st_sfc(node4))) edges = st_as_sf(edge1) edges$from = 1 edges$to = 2 net = sfnetwork(nodes, edges) # Create bounding boxes for nodes, edges and the whole network. node_bbox = st_bbox(activate(net, "nodes")) node_bbox edge_bbox = st_bbox(activate(net, "edges")) edge_bbox net_bbox = st_network_bbox(net) net_bbox # Plot. oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, lwd = 2, cex = 4, main = "Element bounding boxes") plot(st_as_sfc(node_bbox), border = "red", lty = 2, lwd = 4, add = TRUE) plot(st_as_sfc(edge_bbox), border = "blue", lty = 2, lwd = 4, add = TRUE) plot(net, lwd = 2, cex = 4, main = "Network bounding box") plot(st_as_sfc(net_bbox), border = "red", lty = 2, lwd = 4, add = TRUE) par(oldpar)
Blending a point into a network is the combined process of first snapping the given point to its nearest point on its nearest edge in the network, subsequently splitting that edge at the location of the snapped point, and finally adding the snapped point as node to the network. If the location of the snapped point is already a node in the network, the attributes of the point (if any) will be joined to that node.
st_network_blend(x, y, tolerance = Inf)
st_network_blend(x, y, tolerance = Inf)
x |
An object of class |
y |
The spatial features to be blended, either as object of class
|
tolerance |
The tolerance distance to be used. Only features that are
at least as close to the network as the tolerance distance will be blended.
Should be a non-negative number preferably given as an object of class
|
There are two important details to be aware of. Firstly: when the
snap locations of multiple points are equal, only the first of these points
is blended into the network. By arranging y
before blending you can
influence which (type of) point is given priority in such cases.
Secondly: when the snap location of a point intersects with multiple edges,
it is only blended into the first of these edges. You might want to run the
to_spatial_subdivision
morpher after blending, such that
intersecting but unconnected edges get connected.
The blended network as an object of class sfnetwork
.
Due to internal rounding of rational numbers, it may occur that the intersection point between a line and a point is not evaluated as actually intersecting that line by the designated algorithm. Instead, the intersection point lies a tiny-bit away from the edge. Therefore, it is recommended to set the tolerance to a very small number (for example 1e-5) even if you only want to blend points that intersect the line.
library(sf, quietly = TRUE) # Create a network and a set of points to blend. n11 = st_point(c(0,0)) n12 = st_point(c(1,1)) e1 = st_sfc(st_linestring(c(n11, n12)), crs = 3857) n21 = n12 n22 = st_point(c(0,2)) e2 = st_sfc(st_linestring(c(n21, n22)), crs = 3857) n31 = n22 n32 = st_point(c(-1,1)) e3 = st_sfc(st_linestring(c(n31, n32)), crs = 3857) net = as_sfnetwork(c(e1,e2,e3)) pts = net %>% st_bbox() %>% st_as_sfc() %>% st_sample(10, type = "random") %>% st_set_crs(3857) %>% st_cast('POINT') # Blend points into the network. # --> By default tolerance is set to Inf # --> Meaning that all points get blended b1 = st_network_blend(net, pts) b1 # Blend points with a tolerance. tol = units::set_units(0.2, "m") b2 = st_network_blend(net, pts, tolerance = tol) b2 ## Plot results. # Initial network and points. oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,3)) plot(net, cex = 2, main = "Network + set of points") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) # Blend with no tolerance plot(b1, cex = 2, main = "Blend with tolerance = Inf") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) # Blend with tolerance. within = st_is_within_distance(pts, st_geometry(net, "edges"), tol) pts_within = pts[lengths(within) > 0] plot(b2, cex = 2, main = "Blend with tolerance = 0.2 m") plot(pts, cex = 2, col = "grey", pch = 20, add = TRUE) plot(pts_within, cex = 2, col = "red", pch = 20, add = TRUE) par(oldpar)
library(sf, quietly = TRUE) # Create a network and a set of points to blend. n11 = st_point(c(0,0)) n12 = st_point(c(1,1)) e1 = st_sfc(st_linestring(c(n11, n12)), crs = 3857) n21 = n12 n22 = st_point(c(0,2)) e2 = st_sfc(st_linestring(c(n21, n22)), crs = 3857) n31 = n22 n32 = st_point(c(-1,1)) e3 = st_sfc(st_linestring(c(n31, n32)), crs = 3857) net = as_sfnetwork(c(e1,e2,e3)) pts = net %>% st_bbox() %>% st_as_sfc() %>% st_sample(10, type = "random") %>% st_set_crs(3857) %>% st_cast('POINT') # Blend points into the network. # --> By default tolerance is set to Inf # --> Meaning that all points get blended b1 = st_network_blend(net, pts) b1 # Blend points with a tolerance. tol = units::set_units(0.2, "m") b2 = st_network_blend(net, pts, tolerance = tol) b2 ## Plot results. # Initial network and points. oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,3)) plot(net, cex = 2, main = "Network + set of points") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) # Blend with no tolerance plot(b1, cex = 2, main = "Blend with tolerance = Inf") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) # Blend with tolerance. within = st_is_within_distance(pts, st_geometry(net, "edges"), tol) pts_within = pts[lengths(within) > 0] plot(b2, cex = 2, main = "Blend with tolerance = 0.2 m") plot(pts, cex = 2, col = "grey", pch = 20, add = TRUE) plot(pts_within, cex = 2, col = "red", pch = 20, add = TRUE) par(oldpar)
Wrapper around distances
to calculate costs of
pairwise shortest paths between points in a spatial network. It allows to
provide any set of geospatial point as from
and to
arguments.
If such a geospatial point is not equal to a node in the network, it will
be snapped to its nearest node before calculating costs.
st_network_cost( x, from = igraph::V(x), to = igraph::V(x), weights = NULL, direction = "out", Inf_as_NaN = FALSE, ... )
st_network_cost( x, from = igraph::V(x), to = igraph::V(x), weights = NULL, direction = "out", Inf_as_NaN = FALSE, ... )
x |
An object of class |
from |
The (set of) geospatial point(s) from which the shortest paths
will be calculated. Can be an object of class |
to |
The (set of) geospatial point(s) to which the shortest paths will
be calculated. Can be an object of class |
weights |
The edge weights to be used in the shortest path calculation.
Can be a numeric vector giving edge weights, or a column name referring to
an attribute column in the edges table containing those weights. If set to
|
direction |
The direction of travel. Defaults to |
Inf_as_NaN |
Should the cost values of unconnected nodes be stored as
|
... |
Arguments passed on to |
Spatial features provided to the from
and/or
to
argument don't necessarily have to be points. Internally, the
nearest node to each feature is found by calling
st_nearest_feature
, so any feature with a geometry type
that is accepted by that function can be provided as from
and/or
to
argument.
When directly providing integer node indices or character node names to the
from
and/or to
argument, keep the following in mind. A node
index should correspond to a row-number of the nodes table of the network.
A node name should correspond to a value of a column in the nodes table
named name
. This column should contain character values without
duplicates.
For more details on the wrapped function from igraph
see the distances
documentation page.
An n times m numeric matrix where n is the length of the from
argument, and m is the length of the to
argument.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network with edge lengths as weights. # These weights will be used automatically in shortest paths calculation. net = as_sfnetwork(roxel, directed = FALSE) %>% st_transform(3035) %>% activate("edges") %>% mutate(weight = edge_length()) # Providing node indices. st_network_cost(net, from = c(495, 121), to = c(495, 121)) # Providing nodes as spatial points. # Points that don't equal a node will be snapped to their nearest node. p1 = st_geometry(net, "nodes")[495] + st_sfc(st_point(c(50, -50))) st_crs(p1) = st_crs(net) p2 = st_geometry(net, "nodes")[121] + st_sfc(st_point(c(-10, 100))) st_crs(p2) = st_crs(net) st_network_cost(net, from = c(p1, p2), to = c(p1, p2)) # Using another column for weights. net %>% activate("edges") %>% mutate(foo = runif(n(), min = 0, max = 1)) %>% st_network_cost(c(p1, p2), c(p1, p2), weights = "foo") # Not providing any from or to points includes all nodes by default. with_graph(net, graph_order()) # Our network has 701 nodes. cost_matrix = st_network_cost(net) dim(cost_matrix)
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network with edge lengths as weights. # These weights will be used automatically in shortest paths calculation. net = as_sfnetwork(roxel, directed = FALSE) %>% st_transform(3035) %>% activate("edges") %>% mutate(weight = edge_length()) # Providing node indices. st_network_cost(net, from = c(495, 121), to = c(495, 121)) # Providing nodes as spatial points. # Points that don't equal a node will be snapped to their nearest node. p1 = st_geometry(net, "nodes")[495] + st_sfc(st_point(c(50, -50))) st_crs(p1) = st_crs(net) p2 = st_geometry(net, "nodes")[121] + st_sfc(st_point(c(-10, 100))) st_crs(p2) = st_crs(net) st_network_cost(net, from = c(p1, p2), to = c(p1, p2)) # Using another column for weights. net %>% activate("edges") %>% mutate(foo = runif(n(), min = 0, max = 1)) %>% st_network_cost(c(p1, p2), c(p1, p2), weights = "foo") # Not providing any from or to points includes all nodes by default. with_graph(net, graph_order()) # Our network has 701 nodes. cost_matrix = st_network_cost(net) dim(cost_matrix)
A spatial network specific join function which makes a spatial full join on
the geometries of the nodes data, based on the st_equals
spatial predicate. Edge data are combined using a
bind_rows
semantic, meaning that data are matched by
column name and values are filled with NA
if missing in either of
the networks. The from
and to
columns in the edge data are
updated such that they match the new node indices of the resulting network.
st_network_join(x, y, ...)
st_network_join(x, y, ...)
x |
An object of class |
y |
An object of class |
... |
Arguments passed on to |
The joined networks as an object of class sfnetwork
.
library(sf, quietly = TRUE) node1 = st_point(c(0, 0)) node2 = st_point(c(1, 0)) node3 = st_point(c(1,1)) node4 = st_point(c(0,1)) edge1 = st_sfc(st_linestring(c(node1, node2))) edge2 = st_sfc(st_linestring(c(node2, node3))) edge3 = st_sfc(st_linestring(c(node3, node4))) net1 = as_sfnetwork(c(edge1, edge2)) net2 = as_sfnetwork(c(edge2, edge3)) joined = st_network_join(net1, net2) joined ## Plot results. oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net1, pch = 15, cex = 2, lwd = 4) plot(net2, col = "red", pch = 18, cex = 2, lty = 3, lwd = 4, add = TRUE) plot(joined, cex = 2, lwd = 4) par(oldpar)
library(sf, quietly = TRUE) node1 = st_point(c(0, 0)) node2 = st_point(c(1, 0)) node3 = st_point(c(1,1)) node4 = st_point(c(0,1)) edge1 = st_sfc(st_linestring(c(node1, node2))) edge2 = st_sfc(st_linestring(c(node2, node3))) edge3 = st_sfc(st_linestring(c(node3, node4))) net1 = as_sfnetwork(c(edge1, edge2)) net2 = as_sfnetwork(c(edge2, edge3)) joined = st_network_join(net1, net2) joined ## Plot results. oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net1, pch = 15, cex = 2, lwd = 4) plot(net2, col = "red", pch = 18, cex = 2, lty = 3, lwd = 4, add = TRUE) plot(joined, cex = 2, lwd = 4) par(oldpar)
Combined wrapper around shortest_paths
,
all_shortest_paths
and
all_simple_paths
from igraph
,
allowing to provide any geospatial point as from
argument and any
set of geospatial points as to
argument. If such a geospatial point
is not equal to a node in the network, it will be snapped to its nearest
node before calculating the shortest or simple paths.
st_network_paths( x, from, to = igraph::V(x), weights = NULL, type = "shortest", use_names = TRUE, ... )
st_network_paths( x, from, to = igraph::V(x), weights = NULL, type = "shortest", use_names = TRUE, ... )
x |
An object of class |
from |
The geospatial point from which the paths will be
calculated. Can be an object an object of class |
to |
The (set of) geospatial point(s) to which the paths will be
calculated. Can be an object of class |
weights |
The edge weights to be used in the shortest path calculation.
Can be a numeric vector giving edge weights, or a column name referring to
an attribute column in the edges table containing those weights. If set to
|
type |
Character defining which type of path calculation should be
performed. If set to |
use_names |
If a column named |
... |
Arguments passed on to the corresponding
|
Spatial features provided to the from
and/or
to
argument don't necessarily have to be points. Internally, the
nearest node to each feature is found by calling
st_nearest_feature
, so any feature with a geometry type
that is accepted by that function can be provided as from
and/or
to
argument.
When directly providing integer node indices or character node names to the
from
and/or to
argument, keep the following in mind. A node
index should correspond to a row-number of the nodes table of the network.
A node name should correspond to a value of a column in the nodes table
named name
. This column should contain character values without
duplicates.
For more details on the wrapped functions from igraph
see the shortest_paths
or
all_simple_paths
documentation pages.
An object of class tbl_df
with one row per
returned path. Depending on the setting of the type
argument,
columns can be node_paths
(a list column with for each path the
ordered indices of nodes present in that path) and edge_paths
(a list column with for each path the ordered indices of edges present in
that path). 'all_shortest'
and 'all_simple'
return only
node_paths
, while 'shortest'
returns both.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network with edge lengths as weights. # These weights will be used automatically in shortest paths calculation. net = as_sfnetwork(roxel, directed = FALSE) %>% st_transform(3035) %>% activate("edges") %>% mutate(weight = edge_length()) # Providing node indices. paths = st_network_paths(net, from = 495, to = 121) paths node_path = paths %>% slice(1) %>% pull(node_paths) %>% unlist() node_path oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net, col = "grey") plot(slice(activate(net, "nodes"), node_path), col = "red", add = TRUE) par(oldpar) # Providing nodes as spatial points. # Points that don't equal a node will be snapped to their nearest node. p1 = st_geometry(net, "nodes")[495] + st_sfc(st_point(c(50, -50))) st_crs(p1) = st_crs(net) p2 = st_geometry(net, "nodes")[121] + st_sfc(st_point(c(-10, 100))) st_crs(p2) = st_crs(net) paths = st_network_paths(net, from = p1, to = p2) paths node_path = paths %>% slice(1) %>% pull(node_paths) %>% unlist() node_path oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net, col = "grey") plot(c(p1, p2), col = "black", pch = 8, add = TRUE) plot(slice(activate(net, "nodes"), node_path), col = "red", add = TRUE) par(oldpar) # Using another column for weights. net %>% activate("edges") %>% mutate(foo = runif(n(), min = 0, max = 1)) %>% st_network_paths(p1, p2, weights = "foo") # Obtaining all simple paths between two nodes. # Beware, this function can take long when: # --> Providing a lot of 'to' nodes. # --> The network is large and dense. net = as_sfnetwork(roxel, directed = TRUE) st_network_paths(net, from = 1, to = 12, type = "all_simple") # Obtaining all shortest paths between two nodes. # Not using edge weights. # Hence, a shortest path is the paths with the least number of edges. st_network_paths(net, from = 5, to = 1, weights = NA, type = "all_shortest")
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network with edge lengths as weights. # These weights will be used automatically in shortest paths calculation. net = as_sfnetwork(roxel, directed = FALSE) %>% st_transform(3035) %>% activate("edges") %>% mutate(weight = edge_length()) # Providing node indices. paths = st_network_paths(net, from = 495, to = 121) paths node_path = paths %>% slice(1) %>% pull(node_paths) %>% unlist() node_path oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net, col = "grey") plot(slice(activate(net, "nodes"), node_path), col = "red", add = TRUE) par(oldpar) # Providing nodes as spatial points. # Points that don't equal a node will be snapped to their nearest node. p1 = st_geometry(net, "nodes")[495] + st_sfc(st_point(c(50, -50))) st_crs(p1) = st_crs(net) p2 = st_geometry(net, "nodes")[121] + st_sfc(st_point(c(-10, 100))) st_crs(p2) = st_crs(net) paths = st_network_paths(net, from = p1, to = p2) paths node_path = paths %>% slice(1) %>% pull(node_paths) %>% unlist() node_path oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) plot(net, col = "grey") plot(c(p1, p2), col = "black", pch = 8, add = TRUE) plot(slice(activate(net, "nodes"), node_path), col = "red", add = TRUE) par(oldpar) # Using another column for weights. net %>% activate("edges") %>% mutate(foo = runif(n(), min = 0, max = 1)) %>% st_network_paths(p1, p2, weights = "foo") # Obtaining all simple paths between two nodes. # Beware, this function can take long when: # --> Providing a lot of 'to' nodes. # --> The network is large and dense. net = as_sfnetwork(roxel, directed = TRUE) st_network_paths(net, from = 1, to = 12, type = "all_simple") # Obtaining all shortest paths between two nodes. # Not using edge weights. # Hence, a shortest path is the paths with the least number of edges. st_network_paths(net, from = 5, to = 1, weights = NA, type = "all_shortest")