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.9.9.9000 |
Built: | 2024-12-30 18:29:19 UTC |
Source: | https://github.com/luukvdmeer/sfnetworks |
A method to convert an object of class sfnetwork
into
s2_geography
format. Use this method without the
.sfnetwork suffix and after loading the s2 package.
as_s2_geography.sfnetwork(x, focused = TRUE, ...)
as_s2_geography.sfnetwork(x, focused = TRUE, ...)
x |
An object of class |
focused |
Should only features that are in focus be extracted? Defaults
to |
... |
Arguments passed on the corresponding |
An object of class s2_geography
.
Convert a given object into an object of class sfnetwork
.
as_sfnetwork(x, ...) ## Default S3 method: as_sfnetwork(x, ...) ## S3 method for class 'sf' as_sfnetwork(x, ...) ## S3 method for class 'sfc' as_sfnetwork(x, ...) ## S3 method for class 'dodgr_streetnet' as_sfnetwork(x, ...) ## S3 method for class 'linnet' as_sfnetwork(x, ...) ## S3 method for class 'psp' 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 'sfc' as_sfnetwork(x, ...) ## S3 method for class 'dodgr_streetnet' as_sfnetwork(x, ...) ## S3 method for class 'linnet' as_sfnetwork(x, ...) ## S3 method for class 'psp' 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 a |
... |
Additional arguments passed on to other functions. |
An object of class sfnetwork
.
as_sfnetwork(default)
: By default, the provided object is first converted
into a tbl_graph
using
as_tbl_graph
. Further conversion into an
sfnetwork
will work as long as the nodes can be converted to
an sf
object through st_as_sf
. Arguments
to st_as_sf
can be provided as additional arguments and
will be forwarded to st_as_sf
through the
sfnetwork
construction function.
as_sfnetwork(sf)
: Convert spatial features of class
sf
directly into a sfnetwork
.
Supported geometry types are either LINESTRING
or POINT
. In
the first case, the lines become the edges in the network, and nodes are
placed at their boundaries. Additional arguments are forwarded to
create_from_spatial_lines
. In the latter case, the points
become the nodes in the network, and are connected by edges according to a
specified method. Additional arguments are forwarded to
create_from_spatial_points
.
as_sfnetwork(sfc)
: Convert spatial geometries of class
sfc
directly into a sfnetwork
.
Supported geometry types are either LINESTRING
or POINT
. In
the first case, the lines become the edges in the network, and nodes are
placed at their boundaries. Additional arguments are forwarded to
create_from_spatial_lines
. In the latter case, the points
become the nodes in the network, and are connected by edges according to a
specified method. Additional arguments are forwarded to
create_from_spatial_points
.
as_sfnetwork(dodgr_streetnet)
: Convert a directed graph of class
dodgr_streetnet
directly into a
sfnetwork
. Additional arguments are forwarded to
dodgr_to_sfnetwork
. This requires the
dodgr
package to be installed.
as_sfnetwork(linnet)
: Convert spatial linear networks of class
linnet
directly into a
sfnetwork
. Additional arguments are forwarded to
create_from_spatial_lines
. This requires the
spatstat.geom
package
to be installed.
as_sfnetwork(psp)
: Convert spatial line segments of class
psp
directly into a sfnetwork
.
The lines become the edges in the network, and nodes are placed at their
boundary points. Additional arguments are forwarded to
create_from_spatial_lines
.
as_sfnetwork(sfNetwork)
: Convert spatial networks of class
sfNetwork
from the stplanr package directly into a
sfnetwork
. This will extract the edges as an
sf
object and re-create the network structure. Additional
arguments are forwarded to create_from_spatial_lines
.The
directness of the original network is preserved unless specified otherwise
through the directed
argument.
as_sfnetwork(tbl_graph)
: Convert graph objects of class
tbl_graph
directly into a sfnetwork
.
This will work if at least the nodes can be converted to an
sf
object through st_as_sf
. Arguments
to st_as_sf
can be provided as additional arguments and
will be forwarded to st_as_sf
through the
sfnetwork
construction function. The directness of the original
graph is preserved unless specified otherwise through the directed
argument.
# From an sf object with LINESTRING geometries. library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) as_sfnetwork(roxel) plot(st_geometry(roxel)) plot(as_sfnetwork(roxel)) # From an sf object with POINT geometries. # For more examples see ?create_from_spatial_points. as_sfnetwork(mozart) plot(st_geometry(mozart)) plot(as_sfnetwork(mozart)) par(oldpar) # From a dodgr_streetnet object. if (require(dodgr, quietly = TRUE) & require(geodist, quietly = TRUE)) { as_sfnetwork(dodgr::weight_streetnet(hampi)) } # 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 a tbl_graph with coordinate columns. library(tidygraph, quietly = TRUE) nodes = data.frame(lat = c(7, 7, 8), lon = c(51, 52, 52)) edges = data.frame(from = c(1, 1, 3), to = c(2, 3, 2)) tbl_net = tbl_graph(nodes, edges) as_sfnetwork(tbl_net, coords = c("lon", "lat"), crs = 4326)
# From an sf object with LINESTRING geometries. library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) as_sfnetwork(roxel) plot(st_geometry(roxel)) plot(as_sfnetwork(roxel)) # From an sf object with POINT geometries. # For more examples see ?create_from_spatial_points. as_sfnetwork(mozart) plot(st_geometry(mozart)) plot(as_sfnetwork(mozart)) par(oldpar) # From a dodgr_streetnet object. if (require(dodgr, quietly = TRUE) & require(geodist, quietly = TRUE)) { as_sfnetwork(dodgr::weight_streetnet(hampi)) } # 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 a tbl_graph with coordinate columns. library(tidygraph, quietly = TRUE) nodes = data.frame(lat = c(7, 7, 8), lon = c(51, 52, 52)) edges = data.frame(from = c(1, 1, 3), to = c(2, 3, 2)) tbl_net = tbl_graph(nodes, edges) as_sfnetwork(tbl_net, coords = c("lon", "lat"), crs = 4326)
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, focused = TRUE, spatial = TRUE, ...)
## S3 method for class 'sfnetwork' as_tibble(x, active = NULL, focused = TRUE, spatial = TRUE, ...)
x |
An object of class |
active |
Which network element (i.e. nodes or edges) to activate before
extracting. If |
focused |
Should only features that are in focus be extracted? Defaults
to |
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
sf
if a geometry list column is present and
spatial = TRUE
, and object of class tibble
otherwise.
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
.
These functions are the spatially aware versions of tidygraph's
bind_nodes
and bind_edges
that allow you to add rows to the nodes or edges tables in a
sfnetwork
object. As with bind_rows
columns are matched by name and filled with NA
if the column does not
exist in some instances.
bind_spatial_nodes(.data, ...) bind_spatial_edges(.data, ..., node_key = "name", force = FALSE)
bind_spatial_nodes(.data, ...) bind_spatial_edges(.data, ..., node_key = "name", force = FALSE)
.data |
An object of class |
... |
One or more objects of class |
node_key |
The name of the column in the nodes table that character
represented |
force |
Should network validity checks be skipped? Defaults to
|
An object of class sfnetwork
with added nodes or
edges.
library(sf, quietly = TRUE) library(dplyr, quietly = TRUE) net = roxel |> slice(c(1:2)) |> st_transform(3035) |> as_sfnetwork() pts = roxel |> slice(c(3:4)) |> st_transform(3035) |> st_centroid() bind_spatial_nodes(net, pts)
library(sf, quietly = TRUE) library(dplyr, quietly = TRUE) net = roxel |> slice(c(1:2)) |> st_transform(3035) |> as_sfnetwork() pts = roxel |> slice(c(3:4)) |> st_transform(3035) |> st_centroid() bind_spatial_nodes(net, pts)
Combine groups of nodes into a single node per group. The centroid such a group will be used by default as new geometry of the contracted node. If edges are spatially explicit, edge geometries are updated accordingly such that the valid spatial network structure is preserved.
contract_nodes( x, groups, simplify = TRUE, compute_centroids = TRUE, reconnect_edges = TRUE, attribute_summary = "ignore", store_original_ids = FALSE, store_original_data = FALSE )
contract_nodes( x, groups, simplify = TRUE, compute_centroids = TRUE, reconnect_edges = TRUE, attribute_summary = "ignore", store_original_ids = FALSE, store_original_data = FALSE )
x |
An object of class |
groups |
A group index for each node in x. |
simplify |
Should the network be simplified after contraction? Defaults
to |
compute_centroids |
Should the new geometry of each contracted group of
nodes be the centroid of all group members? Defaults to |
reconnect_edges |
Should the geometries of the edges be updated such
they match the new node geometries? Defaults to |
attribute_summary |
How should the attributes of contracted nodes be
summarized? There are several options, see
|
store_original_ids |
For each group of contracted nodes, should
the indices of the original nodes be stored as an attribute of the new edge,
in a column named |
store_original_data |
For each group of contracted nodes, should
the data of the original nodes be stored as an attribute of the new edge, in
a column named |
The contracted network as object of class sfnetwork
.
Create a spatial network from linestring geometries
create_from_spatial_lines( x, directed = TRUE, compute_length = FALSE, subdivide = FALSE )
create_from_spatial_lines( x, directed = TRUE, compute_length = FALSE, subdivide = FALSE )
x |
|
directed |
Should the constructed network be directed? Defaults to
|
compute_length |
Should the geographic length of the edges be stored in
a column named |
subdivide |
Should the given linestring geometries be subdivided at
locations where an interior point is equal to an interior or boundary point
in another feature? This will connect the features at those locations.
Defaults to |
It is assumed that the given linestring geometries form the edges in the network. Nodes are created at the line boundaries. Shared boundaries between multiple linestrings become the same node.
An object of class sfnetwork
.
By default sfnetworks rounds coordinates to 12 decimal places to
determine spatial equality. You can influence this behavior by explicitly
setting the precision of the linestrings using
st_set_precision
.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(roxel) net plot(st_geometry(roxel)) plot(net) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(roxel) net plot(st_geometry(roxel)) plot(net) par(oldpar)
Create a spatial network from point geometries
create_from_spatial_points( x, connections = "complete", directed = TRUE, edges_as_lines = TRUE, compute_length = FALSE, k = 1 )
create_from_spatial_points( x, connections = "complete", directed = TRUE, edges_as_lines = TRUE, compute_length = FALSE, k = 1 )
x |
|
connections |
How to connect the given point geometries to each other? Can be specified either as an adjacency matrix, or as a character describing a specific method to define the connections. See Details. |
directed |
Should the constructed network be directed? Defaults to
|
edges_as_lines |
Should the created edges be spatially explicit, i.e.
have |
compute_length |
Should the geographic length of the edges be stored in
a column named |
k |
The amount of neighbors to connect to if
|
It is assumed that the given points form the nodes in the network.
How those nodes are connected by edges depends on the connections
argument.
The connections can be specified through an adjacency matrix A, which is an
n x n matrix with n being the number of nodes, and element Aij holding a
TRUE
value if there is an edge from node i to node j, and a
FALSE
value otherwise. In the case of undirected networks, the matrix
is not tested for symmetry, and an edge will exist between node i and node j
if either element Aij or element Aji is TRUE
. Non-logical matrices
are first converted into logical matrices using as.logical
,
whenever possible.
The provided adjacency matrix may also be sparse. This can be an object of
one of the sparse matrix classes from the Matrix package, or a
list-formatted sparse matrix. This is a list with one element per node,
holding the integer indices of the nodes it is adjacent to. An example are
sgbp
objects. If the values are not integers, they are
first converted into integers using as.integer
, whenever
possible.
Alternatively, the connections can be specified by providing the name of a specific method that will create the adjacency matrix internally. Valid options are:
complete
: All nodes are directly connected to each other.
sequence
: The nodes are sequentially connected to each other,
meaning that the first node is connected to the second node, the second
node is connected to the third node, et cetera.
mst
: The nodes are connected by their spatial
minimum
spanning tree, i.e. the set of edges with the minimum total edge length
required to connect all nodes. The tree is always constructed on an
undirected network, regardless of the value of the directed
.
argument. If directed = TRUE
, each edge is duplicated and reversed
to ensure full connectivity of the network. Can also be specified as
minimum_spanning_tree
.
delaunay
: The nodes are connected by their
Delaunay
triangulation.
Requires the spdep
package to be installed, and assumes planar coordinates.
gabriel
: The nodes are connected as a
Gabriel graph.
Requires the spdep
package to be installed, and assumes planar coordinates.
rn
: The nodes are connected as a
relative
neighborhood graph. Can also be specified as relative_neighborhood
or relative_neighbourhood
.
Requires the spdep
package to be installed, and assumes planar coordinates.
knn
: Each node is connected to its k nearest neighbors, with
k
being specified through the k
argument. By default,
k = 1
, meaning that the nodes are connected as a
nearest
neighbor graph. Can also be specified as nearest_neighbors
or
nearest_neighbours
.
Requires the spdep
package to be installed.
An object of class sfnetwork
.
create_from_spatial_lines
, play_geometric
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) pts = st_transform(mozart, 3035) # Using a custom adjacency matrix adj = matrix(c(rep(TRUE, 17), rep(rep(FALSE, 17), 16)), nrow = 17) net = as_sfnetwork(pts, connections = adj) plot(net) # Using a sparse adjacency matrix from a spatial predicate dst = units::set_units(300, "m") adj = st_is_within_distance(pts, dist = dst, remove_self = TRUE) net = as_sfnetwork(pts, connections = adj, directed = FALSE) plot(net) # Using pre-defined methods cnet = as_sfnetwork(pts, connections = "complete") snet = as_sfnetwork(pts, connections = "sequence") mnet = as_sfnetwork(pts, connections = "mst") dnet = as_sfnetwork(pts, connections = "delaunay") gnet = as_sfnetwork(pts, connections = "gabriel") rnet = as_sfnetwork(pts, connections = "rn") nnet = as_sfnetwork(pts, connections = "knn") knet = as_sfnetwork(pts, connections = "knn", k = 2) par(mar = c(1,1,1,1), mfrow = c(4,2)) plot(cnet, main = "complete") plot(snet, main = "sequence") plot(mnet, main = "minimum spanning tree") plot(dnet, main = "delaunay triangulation") plot(gnet, main = "gabriel graph") plot(rnet, main = "relative neighborhood graph") plot(nnet, main = "nearest neighbor graph") plot(knet, main = "k nearest neighbor graph (k = 2)") par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) pts = st_transform(mozart, 3035) # Using a custom adjacency matrix adj = matrix(c(rep(TRUE, 17), rep(rep(FALSE, 17), 16)), nrow = 17) net = as_sfnetwork(pts, connections = adj) plot(net) # Using a sparse adjacency matrix from a spatial predicate dst = units::set_units(300, "m") adj = st_is_within_distance(pts, dist = dst, remove_self = TRUE) net = as_sfnetwork(pts, connections = adj, directed = FALSE) plot(net) # Using pre-defined methods cnet = as_sfnetwork(pts, connections = "complete") snet = as_sfnetwork(pts, connections = "sequence") mnet = as_sfnetwork(pts, connections = "mst") dnet = as_sfnetwork(pts, connections = "delaunay") gnet = as_sfnetwork(pts, connections = "gabriel") rnet = as_sfnetwork(pts, connections = "rn") nnet = as_sfnetwork(pts, connections = "knn") knet = as_sfnetwork(pts, connections = "knn", k = 2) par(mar = c(1,1,1,1), mfrow = c(4,2)) plot(cnet, main = "complete") plot(snet, main = "sequence") plot(mnet, main = "minimum spanning tree") plot(dnet, main = "delaunay triangulation") plot(gnet, main = "gabriel graph") plot(rnet, main = "relative neighborhood graph") plot(nnet, main = "nearest neighbor graph") plot(knet, main = "k nearest neighbor graph (k = 2)") par(oldpar)
Extract the node or edge data from a spatial network
node_data(x, focused = TRUE) edge_data(x, focused = TRUE, require_sf = FALSE)
node_data(x, focused = TRUE) edge_data(x, focused = TRUE, require_sf = FALSE)
x |
An object of class |
focused |
Should only features that are in focus be extracted? Defaults
to |
require_sf |
Is an |
For the nodes, always an object of class sf
. For
the edges, an object of class sf
if the edges are
spatially explicit, and an object of class tibble
if the edges are spatially implicity and require_sf = FALSE
.
net = as_sfnetwork(roxel[1:10, ]) node_data(net) edge_data(net)
net = as_sfnetwork(roxel[1:10, ]) node_data(net) edge_data(net)
Dual edge weights are two sets of edge weights, one (the actual weight) to determine the shortest path, and the other (the reported weight) to report the cost of that path.
dual_weights(reported, actual)
dual_weights(reported, actual)
reported |
The edge weights to be reported. Evaluated by
|
actual |
The actual edge weights to be used to determine shortest paths.
Evaluated by |
Dual edge weights enable dual-weighted routing. This is supported
by the dodgr
routing backend.
An object of class dual_weights
.
This function is not meant to be called directly, but used inside other functions that accept an edge query.
evaluate_edge_query(data, query)
evaluate_edge_query(data, query)
data |
An object of class |
query |
The query that defines for which edges to extract indices,
defused into a |
There are multiple ways in which edge indices can be queried in sfnetworks. The query can be formatted as follows:
As spatial features: Spatial features can be given as object of
class sf
or sfc
. The nearest edge to
each feature is found by calling st_nearest_feature
.
As edge type query function: A edge type query function defines for each edge if it is of a given type or not. Nodes that meet the criterium are queried.
As edge predicate query function: A edge predicate query function defines for each edge if a given spatial predicate applies to the spatial relation between that edge and other spatial features. Nodes that meet the criterium are queried.
As column name: The referenced column is expected to have logical values defining for each edge if it should be queried or not. Note that tidy evaluation is used and hence the column name should be unquoted.
As integers: Integers are interpreted as edge indices. A edge index corresponds to a row-number in the edges table of the network.
As characters: Characters are interpreted as edge names. A edge name corresponds to a value in a column named "name" in the the edges table of the network. Note that this column is expected to store unique names without any duplicated values.
As logicals: Logicals should define for each edge if it should be queried or not.
Queries that can not be evaluated in any of the ways described above will be
forcefully converted to integers using as.integer
.
A vector of queried edge indices.
This function is not meant to be called directly, but used inside other functions that accept a node query.
evaluate_node_query(data, query)
evaluate_node_query(data, query)
data |
An object of class |
query |
The query that defines for which nodes to extract indices,
defused into a |
There are multiple ways in which node indices can be queried in sfnetworks. The query can be formatted as follows:
As spatial features: Spatial features can be given as object of
class sf
or sfc
. The nearest node to
each feature is found by calling st_nearest_feature
.
As node type query function: A node type query function defines for each node if it is of a given type or not. Nodes that meet the criterium are queried.
As node predicate query function: A node predicate query function defines for each node if a given spatial predicate applies to the spatial relation between that node and other spatial features. Nodes that meet the criterium are queried.
As column name: The referenced column is expected to have logical values defining for each node if it should be queried or not. Note that tidy evaluation is used and hence the column name should be unquoted.
As integers: Integers are interpreted as node indices. A node index corresponds to a row-number in the nodes table of the network.
As characters: Characters are interpreted as node names. A node name corresponds to a value in a column named "name" in the the nodes table of the network. Note that this column is expected to store unique names without any duplicated values.
As logicals: Logicals should define for each node if it should be queried or not.
Queries that can not be evaluated in any of the ways described above will be
forcefully converted to integers using as.integer
.
A vector of queried node indices.
This function is not meant to be called directly, but used inside other functions that accept the specification of edge weights.
evaluate_weight_spec(data, spec)
evaluate_weight_spec(data, spec)
data |
An object of class |
spec |
The specification that defines how to compute or extract edge
weights defused into a |
There are multiple ways in which edge weights can be specified in sfnetworks. The specification can be formatted as follows:
As edge measure function: A spatial edge measure function computes a given measure for each edge, which will then be used as edge weights.
As column name: A column in the edges table of the network that contains the edge weights. Note that tidy evaluation is used and hence the column name should be unquoted.
As a numeric vector: This vector should be of the same length as the number of edges in the network, specifying for each edge what its weight is.
As dual weights: Dual weights can be specified by the
dual_weights
function. This allows to use a different set of
weights for shortest paths computation and for reporting the total cost of
those paths. Note that not every routing backend support dual-weighted
routing.
If the weight specification is NULL
or NA
, this means that no
edge weights are used. For shortest path computation, this means that the
shortest path is simply the path with the fewest number of edges.
A numeric vector of edge weights.
For backward compatibility it is currently also still possible to format the specification as a quoted column name, but this may be removed in future versions.
Also note that many shortest path algorithms require edge weights to be positive.
These functions forms a spatial extension to the
grouping
functions in tidygraph,
allowing to detect communities with spatial clustering algorithms.
group_spatial_dbscan(epsilon, min_pts = 1, use_network_distance = TRUE, ...)
group_spatial_dbscan(epsilon, min_pts = 1, use_network_distance = TRUE, ...)
epsilon |
The value of the epsilon parameter for the DBSCAN clustering algorithm, defining the radius of the neighborhood of a node. |
min_pts |
The value of the minPts parameter for the DBSCAN clustering algorithm, defining the minimum number of points in the neighborhood to be considered a core point. |
use_network_distance |
Should the distance between nodes be computed as
the distance over the network (using |
... |
Additional arguments passed on to the clustering algorithm. |
Just as with all grouping functions in tidygraph, spatial grouping
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 with the membership for each node in the network. The enumeration happens in order based on group size progressing from the largest to the smallest group.
group_spatial_dbscan()
: Uses density-based spatial clustering as
implemented in the dbscan
function of the dbscan
package. This requires the dbscan package to be installed. Each node marked
as noise will form its own cluster.
library(tidygraph, quietly = TRUE) play_geometric(10, 0.5) |> activate(nodes) |> mutate(group = group_spatial_dbscan(0.25))
library(tidygraph, quietly = TRUE) play_geometric(10, 0.5) |> activate(nodes) |> mutate(group = group_spatial_dbscan(0.25))
Extract all node or edge indices from a spatial network
node_ids(x, focused = TRUE) edge_ids(x, focused = TRUE)
node_ids(x, focused = TRUE) edge_ids(x, focused = TRUE)
x |
An object of class |
focused |
Should only the indices of features that are in focus be
extracted? Defaults to |
The indices in these objects are always integers that correspond to rownumbers in respectively the nodes or edges table.
A vector of integers.
net = as_sfnetwork(roxel[1:10, ]) node_ids(net) edge_ids(net)
net = as_sfnetwork(roxel[1:10, ]) node_ids(net) edge_ids(net)
Check if an object is a sfnetwork
is_sfnetwork(x) is.sfnetwork(x)
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))
This function converts an undirected network to a directed network following the direction given by the linestring geometries of the edges.
make_edges_directed(x)
make_edges_directed(x)
x |
An object of class |
In undirected spatial networks it is required that the boundary of edge geometries contain their incident node geometries. However, it is not required that their start point equals their specified *from* node and their end point their specified *to* node. Instead, it may be vice versa. This is because for undirected networks *from* and *to* indices are always swopped if the *to* index is lower than the *from* index. Therefore, the direction given by the *from* and *to* indices does not necessarily match the direction given by the edge geometries.
A directed network as object of class sfnetwork
.
If the network is already directed it is returned unmodified.
This function turns spatially implicit networks into spatially explicit networks by adding a geometry column to the edge data.
make_edges_explicit(x, ...)
make_edges_explicit(x, ...)
x |
An object of class |
... |
Arguments forwarded to |
An object of class sfnetwork
with spatially explicit
edges.
If the network is already spatially explicit it is returned unmodified.
This function updates edge geometries in undirected networks such that they are guaranteed to start at their specified *from* node and end at their specified *to* node.
make_edges_follow_indices(x)
make_edges_follow_indices(x)
x |
An object of class |
In undirected spatial networks it is required that the boundary of edge geometries contain their incident node geometries. However, it is not required that their start point equals their specified *from* node and their end point their specified *to* node. Instead, it may be vice versa. This is because for undirected networks *from* and *to* indices are always swopped if the *to* index is lower than the *from* index.
This function reverses edge geometries if they start at the *to* node and end at the *from* node, such that in the resulting network it is guaranteed that edge boundary points exactly match their incident node geometries. In directed networks, there will be no change.
An object of class sfnetwork
with updated edge
geometries.
This function turns spatially explicit networks into spatially implicit networks by dropping the geometry column of the edge data.
make_edges_implicit(x)
make_edges_implicit(x)
x |
An object of class |
An object of class sfnetwork
with spatially implicit
edges.
If the network is already spatially implicit it is returned unmodified.
This function creates a mixed network, meaning that some edges are directed, and some are undirected. In practice this is implemented as a directed network in which those edges that are meant to be undirected are duplicated and reversed.
make_edges_mixed(x, directed)
make_edges_mixed(x, directed)
x |
An object of class |
directed |
An integer vector of edge indices specifying those edges that should be directed. |
A mixed network as object of class sfnetwork
.
This function makes invalid edges valid by modifying either edge or node geometries such that the boundary points of edge linestring geometries always match the point geometries of the nodes that are specified as their incident nodes by the *from* and *to* columns.
make_edges_valid(x, preserve_geometries = FALSE)
make_edges_valid(x, preserve_geometries = FALSE)
x |
An object of class |
preserve_geometries |
Should the edge geometries remain unmodified?
Defaults to |
If geometries should be preserved, edges are made valid by adding
edge boundary points that do not equal their corresponding node geometry as
new nodes to the network, and updating the *from* and *to* indices to match
this newly added nodes. If FALSE
, edges are made valid by modifying
their geometries, i.e. edge boundary points that do not equal their
corresponding node geometry are replaced by that node geometry.
An object of class sfnetwork
with corrected edge
geometries.
This function works only if the edge geometries are meant to start at
their specified *from* node and end at their specified *to* node. In
undirected networks this is not necessarily the case, since edge geometries
are allowed to start at their specified *to* node and end at their specified
*from* node. Therefore, in undirected networks those edges first have to be
reversed before running this function. Use
make_edges_follow_indices
for this.
A dataset containing point locations (museums, sculptures, squares, universities, etc.) of places named after Wolfgang Amadeus Mozart in the city of Salzburg, Austria. The data are taken from OpenStreetMap. See 'data-raw/mozart.R' for code on its creation.
mozart
mozart
An object of class sf
with LINESTRING
geometries, containing 17 features and four columns:
the name of the point location
the type of location, e.g. museum, artwork, cinema, etc.
the website URL for more information about the place, if available
the geometry list column
Count the number of nodes or edges in a network
n_nodes(x, focused = FALSE) n_edges(x, focused = FALSE)
n_nodes(x, focused = FALSE) n_edges(x, focused = FALSE)
x |
An object of class |
focused |
Should only features that are in focus be counted? Defaults
to |
An integer.
net = as_sfnetwork(roxel) n_nodes(net) n_edges(net)
net = as_sfnetwork(roxel) n_nodes(net) n_edges(net)
Neighbor lists are sparse adjacency matrices in list format that specify for
each node to which other nodes it is adjacent. They occur for example in the
sf package as sgbp
objects, and are also
frequently used in the spdep package.
nb_to_sfnetwork( x, nodes, directed = TRUE, edges_as_lines = TRUE, compute_length = FALSE, force = FALSE ) sfnetwork_to_nb(x, direction = "out")
nb_to_sfnetwork( x, nodes, directed = TRUE, edges_as_lines = TRUE, compute_length = FALSE, force = FALSE ) sfnetwork_to_nb(x, direction = "out")
x |
For the conversion to sfnetwork: a neighbor list, which is a list
adjacent to. For the conversion from sfnetwork: an object of class
|
nodes |
The nodes themselves as an object of class |
directed |
Should the constructed network be directed? Defaults to
|
edges_as_lines |
Should the created edges be spatially explicit, i.e.
have |
compute_length |
Should the geographic length of the edges be stored in
a column named |
force |
Should validity checks be skipped? Defaults to |
direction |
The direction that defines if two nodes are neighbors.
Defaults to |
For the conversion to sfnetwork: An object of class
sfnetwork
. For the conversion from sfnetwork: a neighbor list,
which is a list with one element per node that holds the integer indices of
the nodes it is adjacent to.
Extract the nearest nodes or edges to given spatial features
nearest_nodes(x, y, focused = TRUE) nearest_edges(x, y, focused = TRUE)
nearest_nodes(x, y, focused = TRUE) nearest_edges(x, y, focused = TRUE)
x |
An object of class |
y |
|
focused |
Should only features that are in focus be extracted? Defaults
to |
To determine the nearest node or edge to each feature in y
the function st_nearest_feature
is used. When extracting
nearest edges, spatially explicit edges are required, i.e. the edges table
should have a geometry column.
An object of class sf
with each row containing
the nearest node or edge to the corresponding spatial features in y
.
library(sf, quietly = TRUE) net = as_sfnetwork(roxel) pts = st_sample(st_bbox(roxel), 6) nodes = nearest_nodes(net, pts) edges = nearest_edges(net, pts) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, main = "Nearest nodes") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) plot(st_geometry(nodes), cex = 2, col = "orange", pch = 20, add = TRUE) plot(net, main = "Nearest edges") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) plot(st_geometry(edges), lwd = 2, col = "orange", pch = 20, add = TRUE) par(oldpar)
library(sf, quietly = TRUE) net = as_sfnetwork(roxel) pts = st_sample(st_bbox(roxel), 6) nodes = nearest_nodes(net, pts) edges = nearest_edges(net, pts) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, main = "Nearest nodes") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) plot(st_geometry(nodes), cex = 2, col = "orange", pch = 20, add = TRUE) plot(net, main = "Nearest edges") plot(pts, cex = 2, col = "red", pch = 20, add = TRUE) plot(st_geometry(edges), lwd = 2, col = "orange", pch = 20, add = TRUE) par(oldpar)
Extract the indices of nearest nodes or edges to given spatial features
nearest_node_ids(x, y, focused = TRUE) nearest_edge_ids(x, y, focused = TRUE)
nearest_node_ids(x, y, focused = TRUE) nearest_edge_ids(x, y, focused = TRUE)
x |
An object of class |
y |
|
focused |
Should only the indices of features that are in focus be
extracted? Defaults to |
To determine the nearest node or edge to each feature in y
the function st_nearest_feature
is used. When extracting
nearest edges, spatially explicit edges are required, i.e. the edges table
should have a geometry column.
An integer vector with each element containing the index of the
nearest node or edge to the corresponding spatial features in y
.
library(sf, quietly = TRUE) net = as_sfnetwork(roxel) pts = st_sample(st_bbox(roxel), 6) nearest_node_ids(net, pts) nearest_edge_ids(net, pts)
library(sf, quietly = TRUE) net = as_sfnetwork(roxel) pts = st_sample(st_bbox(roxel), 6) nearest_node_ids(net, pts) nearest_edge_ids(net, pts)
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()) # Use query function directly. X = with_graph(net, node_X()) head(X)
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()) # Use query function directly. X = with_graph(net, node_X()) head(X)
Random spatial networks are created by randomly sampling nodes within a given area, and connecting them by edges according to a specified method.
play_geometric( n, radius, bounds = NULL, edges_as_lines = TRUE, compute_length = FALSE, ... )
play_geometric( n, radius, bounds = NULL, edges_as_lines = TRUE, compute_length = FALSE, ... )
n |
The number of nodes to be sampled. |
radius |
The radius within which nodes will be connected by an edge. |
bounds |
The spatial features within which the nodes should be sampled
as object of class |
edges_as_lines |
Should the created edges be spatially explicit, i.e.
have |
compute_length |
Should the geographic length of the edges be stored in
a column named |
... |
Additional arguments passed on to |
play_geometric()
: Creates a random geometric graph. Two nodes will be
connected by an edge if the distance between them is within the given radius.
If nodes are sampled on a unit square (i.e. when bounds = NULL
) this
radius is unitless. If bounds are given as a spatial feature, the radius is
assumed to be in meters for geographic coordinates, and in the units of the
coordinate reference system for projected coordinates. Alternatively, units
can also be specified explicitly by providing a units
object.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) # Sample 10 nodes on a unit square # Connect nodes by an edge if they are within 0.25 distance from each other net = play_geometric(10, 0.25) net plot(net) # Sample 10 nodes within a spatial bounding box # Connect nodes by an edge if they are within 1 km from each other net = play_geometric(10, units::set_units(1, "km"), bounds = st_bbox(roxel)) net plot(net) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) # Sample 10 nodes on a unit square # Connect nodes by an edge if they are within 0.25 distance from each other net = play_geometric(10, 0.25) net plot(net) # Sample 10 nodes within a spatial bounding box # Connect nodes by an edge if they are within 1 km from each other net = play_geometric(10, units::set_units(1, "km"), bounds = st_bbox(roxel)) net plot(net) par(oldpar)
Plot the geometries of an object of class sfnetwork
.
## S3 method for class 'sfnetwork' plot(x, draw_lines = TRUE, node_args = list(), edge_args = list(), ...)
## S3 method for class 'sfnetwork' plot(x, draw_lines = TRUE, node_args = list(), edge_args = list(), ...)
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 |
node_args |
A named list of arguments that will be passed on to
|
edge_args |
A named list of arguments that will be passed on to
|
... |
Arguments passed on to |
Arguments passed to ...
will be used both for plotting the
nodes and for plotting the edges. Edges are always plotted first. Arguments
specified in node_args
and edge_args
should not be specified
in ...
as well, this will result in an error.
Invisible.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,1)) net = as_sfnetwork(roxel) plot(net) # When edges are spatially implicit. # By default straight lines will be drawn between connected nodes. par(mar = c(1,1,1,1), mfrow = c(1,2)) inet = st_drop_geometry(activate(net, "edges")) plot(inet) plot(inet, draw_lines = FALSE) # Changing plot settings. par(mar = c(1,1,1,1), mfrow = c(1,1)) plot(net, main = "My network", col = "blue", pch = 18, lwd = 1, cex = 2) # Changing plot settings for nodes and edges separately. plot(net, node_args = list(col = "red"), edge_args = list(col = "blue")) # Add grid and axis par(mar = c(2.5,2.5,1,1)) plot(net, graticule = TRUE, axes = TRUE) # Plot two networks on top of each other. par(mar = c(1,1,1,1), mfrow = c(1,1)) neta = as_sfnetwork(roxel[1:10, ]) netb = as_sfnetwork(roxel[50:60, ]) plot(neta) plot(netb, node_args = list(col = "orange"), add = TRUE) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,1)) net = as_sfnetwork(roxel) plot(net) # When edges are spatially implicit. # By default straight lines will be drawn between connected nodes. par(mar = c(1,1,1,1), mfrow = c(1,2)) inet = st_drop_geometry(activate(net, "edges")) plot(inet) plot(inet, draw_lines = FALSE) # Changing plot settings. par(mar = c(1,1,1,1), mfrow = c(1,1)) plot(net, main = "My network", col = "blue", pch = 18, lwd = 1, cex = 2) # Changing plot settings for nodes and edges separately. plot(net, node_args = list(col = "red"), edge_args = list(col = "blue")) # Add grid and axis par(mar = c(2.5,2.5,1,1)) plot(net, graticule = TRUE, axes = TRUE) # Plot two networks on top of each other. par(mar = c(1,1,1,1), mfrow = c(1,1)) neta = as_sfnetwork(roxel[1:10, ]) netb = as_sfnetwork(roxel[50:60, ]) plot(neta) plot(netb, node_args = list(col = "orange"), add = 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'. See 'data-raw/roxel.R' for code on its creation.
roxel
roxel
An object of class sf
with LINESTRING
geometries, containing 1215 features and three columns:
the name of the road, if it exists
the type of the road, e.g. cycleway
the geometry list column
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 |
The value of the queried attribute.
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")
sf
methods for sfnetwork
objects.
## S3 method for class 'sfnetwork' st_as_sf(x, active = NULL, focused = TRUE, ...) ## S3 method for class 'sfnetwork' st_geometry(obj, active = NULL, focused = TRUE, ...) ## 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_as_s2(x, active = NULL, focused = TRUE, ...) ## 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_segmentize(x, ...) ## S3 method for class 'sfnetwork' st_simplify(x, ...) ## S3 method for class 'sfnetwork' st_join(x, y, ..., ignore_multiple = TRUE) ## 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, focused = TRUE, ...) ## S3 method for class 'sfnetwork' st_geometry(obj, active = NULL, focused = TRUE, ...) ## 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_as_s2(x, active = NULL, focused = TRUE, ...) ## 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_segmentize(x, ...) ## S3 method for class 'sfnetwork' st_simplify(x, ...) ## S3 method for class 'sfnetwork' st_join(x, y, ..., ignore_multiple = TRUE) ## 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 |
focused |
Should only features that are in focus be extracted? Defaults
to |
... |
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 |
ignore_multiple |
When performing a spatial join with the nodes
table, and there are multiple matches for a single node, only the first one
of them is joined into the network. But what should happen with the others?
If this argument is set to |
See the sf
documentation. The following methods
have a special behavior:
st_geometry<-
: The geometry setter requires the replacement
geometries to have the same CRS as the network. Node replacements should
all be points, while edge replacements should all be linestrings. When
replacing node geometries, the boundaries of the edge geometries are
replaced as well to preserve the valid spatial network structure. When
replacing edge geometries, new edge boundaries that do not match the
location of their specified incident node are added as new nodes to the
network.
st_transform
: No matter if applied to the nodes or edge
table, this method will update the coordinates of both tables. The same
holds for all other methods that update the way in which the coordinates
are encoded without changing their actual location, such as
st_precision
, st_normalize
, st_zm
, and others.
st_join
: When applied to the nodes table and multiple matches
exist for the same node, only the first match is joined. A warning will be
given in this case. If ignore_multiple = FALSE
, multiple mathces
are instead added as isolated nodes to the returned network.
st_intersection
, st_difference
and st_crop
:
These methods clip edge geometries when applied to the edges table. To
preserve a valid spatial network structure, clipped edge boundaries are
added as new nodes to the network.
st_reverse
: When reversing edge geometries in a directed
network, the indices in the from and to columns will be swapped as well.
st_segmentize
: When segmentizing edge geometries, the edge
boundaries are forced to remain the same such that the valid spatial
network structure is preserved. This may lead to slightly inaccurate
results.
Geometric unary operations are only supported on sfnetwork
objects if they do not change the geometry type nor the spatial location
of the original features, since that would break the valid spatial network
structure. When applying the unsupported operations, first extract the
element of interest (nodes or edges) using st_as_sf
.
The methods for st_join
,
st_filter
, st_intersection
,
st_difference
and st_crop
, as well as
the methods for all setter functions and the geometric unary operations
preserve the class of the object it is applied to, i.e. either a
sfnetwork
object or its morphed equivalent. When dropping node
geometries, an object of class tbl_graph
is
returned. All other methods return the same type of objects as their
corresponding sf function. See the sf
documentation for
details.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(roxel) # Extract the active network element as sf object. st_as_sf(net) # Extract any network element as sf object. st_as_sf(net, "edges") # Get the geometry of the active network element. st_geometry(net) # Get the geometry of any network element. st_geometry(net, "edges") # Replace the geometry of the nodes. # This will automatically update edge geometries to match the new nodes. newnet = net newnds = rep(st_centroid(st_combine(st_geometry(net))), n_nodes(net)) st_geometry(newnet) = newnds plot(net) plot(newnet) # Drop the geometries of the edges. # This returns an sfnetwork with spatially implicit edges. st_drop_geometry(activate(net, "edges")) # Drop the geometries of the nodes. # This returns a tbl_graph. st_drop_geometry(net) # Get the bounding box 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 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) plot(net, col = "grey") plot(poly, border = "red", lty = 4, lwd = 4, add = TRUE) plot(filtered) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(roxel) # Extract the active network element as sf object. st_as_sf(net) # Extract any network element as sf object. st_as_sf(net, "edges") # Get the geometry of the active network element. st_geometry(net) # Get the geometry of any network element. st_geometry(net, "edges") # Replace the geometry of the nodes. # This will automatically update edge geometries to match the new nodes. newnet = net newnds = rep(st_centroid(st_combine(st_geometry(net))), n_nodes(net)) st_geometry(newnet) = newnds plot(net) plot(newnet) # Drop the geometries of the edges. # This returns an sfnetwork with spatially implicit edges. st_drop_geometry(activate(net, "edges")) # Drop the geometries of the nodes. # This returns a tbl_graph. st_drop_geometry(net) # Get the bounding box 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 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) plot(net, col = "grey") plot(poly, border = "red", lty = 4, lwd = 4, add = TRUE) plot(filtered) par(oldpar)
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, compute_length = FALSE, length_as_weight = deprecated(), force = FALSE, message = TRUE, ... )
sfnetwork( nodes, edges = NULL, directed = TRUE, node_key = "name", edges_as_lines = NULL, compute_length = FALSE, length_as_weight = deprecated(), 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
|
compute_length |
Should the geographic length of the edges be stored in
a column named |
length_as_weight |
Deprecated, use |
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) 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 column named 'length'. sfnetwork(nodes, edges, compute_length = TRUE)
library(sf, quietly = TRUE) 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 column named 'length'. sfnetwork(nodes, edges, compute_length = TRUE)
The dodgr
package is designed for routing
on directed graphs, and is known for its fast computations of cost matrices,
shortest paths, and more. In sfnetwork, dodgr can be chosen as a routing
backend.
dodgr_to_sfnetwork(x, edges_as_lines = TRUE) sfnetwork_to_dodgr(x, weights = edge_length(), time = FALSE)
dodgr_to_sfnetwork(x, edges_as_lines = TRUE) sfnetwork_to_dodgr(x, weights = edge_length(), time = FALSE)
x |
For the conversion to sfnetwork: an object of class
|
edges_as_lines |
Should the created edges be spatially explicit, i.e.
have |
weights |
The edge weights to be stored in the dodgr streetnet.
Evaluated by |
time |
Are the provided weights time values? If |
For the conversion to sfnetwork: An object of class
sfnetwork
. For the conversion from sfnetwork: an object of
class dodgr_streetnet
.
The dodgr
package is designed for
directed graphs. If the provided sfnetwork
object is
undirected, it is made directed by duplicating and reversing each edge.
Construct a simple version of the network. A simple network is defined as a network without loop edges and multiple edges. A loop edge is an edge that starts and ends at the same node. Multiple edges are different edges between the same node pair.
simplify_network( x, remove_multiple = TRUE, remove_loops = TRUE, attribute_summary = "first", store_original_ids = FALSE, store_original_data = FALSE )
simplify_network( x, remove_multiple = TRUE, remove_loops = TRUE, attribute_summary = "first", store_original_ids = FALSE, store_original_data = FALSE )
x |
An object of class |
remove_multiple |
Should multiple edges be merged into one. Defaults
to |
remove_loops |
Should loop edges be removed. Defaults to |
attribute_summary |
How should the attributes of merged multiple
edges be summarized? There are several options, see
|
store_original_ids |
For each group of merged multiple edges, should
the indices of the original edges be stored as an attribute of the new edge,
in a column named |
store_original_data |
For each group of merged multiple edges, should
the data of the original edges be stored as an attribute of the new edge, in
a column named |
The simple network as object of class sfnetwork
.
When merging groups of multiple edges into a single edge, the geometry
of the first edge in each group is preserved. The order of the edges can be
influenced by calling arrange
before simplifying.
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.
smooth_pseudo_nodes( x, protect = NULL, require_equal = NULL, attribute_summary = "ignore", store_original_ids = FALSE, store_original_data = FALSE )
smooth_pseudo_nodes( x, protect = NULL, require_equal = NULL, attribute_summary = "ignore", store_original_ids = FALSE, store_original_data = FALSE )
x |
An object of class |
protect |
An integer vector of edge indices specifying which nodes
should be protected from being removed. Defaults to |
require_equal |
A character vector of edge column names specifying
which attributes of the incident edges of a pseudo node should be equal in
order for the pseudo node to be removed? Defaults to |
attribute_summary |
How should the attributes of concatenated edges
be summarized? There are several options, see
|
store_original_ids |
For each concatenated edge, should the indices of
the original edges be stored as an attribute of the new edge, in a column
named |
store_original_data |
For each concatenated edge, should the data of
the original edges be stored as an attribute of the new edge, in a column
named |
The smoothed network as object of class sfnetwork
.
These functions are a collection of centrality measures that are specific
for spatial networks, and form a spatial extension to
centrality measures
in tidygraph.
centrality_straightness(...)
centrality_straightness(...)
... |
Additional arguments passed on to other functions. |
Just as with all centrality 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.
centrality_straightness()
: The straightness centrality of node i is the
average ratio of Euclidean distance and network distance between node i and
all other nodes in the network. ...
is forwarded to
st_network_distance
to compute the network distance matrix.
Euclidean distances are computed using st_distance
.
library(tidygraph, quietly = TRUE) net = as_sfnetwork(roxel, directed = FALSE) net |> activate(nodes) |> mutate(sc = centrality_straightness())
library(tidygraph, quietly = TRUE) net = as_sfnetwork(roxel, directed = FALSE) net |> activate(nodes) |> mutate(sc = centrality_straightness())
These functions are a collection of edge measures in spatial networks.
edge_azimuth(degrees = FALSE) edge_circuity(Inf_as_NaN = FALSE) edge_length() edge_displacement() edge_segment_count()
edge_azimuth(degrees = FALSE) edge_circuity(Inf_as_NaN = FALSE) edge_length() edge_displacement() edge_segment_count()
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
. If edges are spatially
implicit, the straight-line distance between its boundary nodes is computed
instead, using st_distance
.
edge_displacement()
: The straight-line distance between the two
boundary nodes of an edge, as calculated by st_distance
.
edge_segment_count()
: The number of segments contained in the
linestring geometry of an edge. Segments are those parts of a linestring
geometry that do not contain any interior points.
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()) net |> activate(edges) |> mutate(n_segs = edge_segment_count())
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()) net |> activate(edges) |> mutate(n_segs = edge_segment_count())
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 the chosen spatial predicate applies
to the spatial relation between the i-th edge and any of the features 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_is_nearest(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_is_nearest(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. The function edge_is_nearest
instead wraps around
st_nearest_feature
and returns TRUE
for element i
if the i-th edge is the nearest edge to any of the features in y
.
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) # Use predicate query function directly. intersects = with_graph(net, edge_intersects(poly)) head(intersects)
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) # Use predicate query function directly. intersects = with_graph(net, edge_intersects(poly)) head(intersects)
Spatial morphers form spatial add-ons to the set of
morphers
provided by tidygraph
. These
functions change the existing structure of the network.
to_spatial_contracted( x, ..., simplify = TRUE, compute_centroids = TRUE, attribute_summary = "ignore", summarise_attributes = deprecated(), store_original_data = FALSE ) to_spatial_directed(x) to_spatial_explicit(x, ...) to_spatial_implicit(x) to_spatial_mixed(x, directed) to_spatial_neighborhood(x, node, threshold, weights = edge_length(), ...) to_spatial_reversed(x, protect = NULL) to_spatial_shortest_paths(x, ...) to_spatial_simple( x, remove_multiple = TRUE, remove_loops = TRUE, attribute_summary = "first", summarise_attributes = deprecated(), store_original_data = FALSE ) to_spatial_smooth( x, protect = NULL, require_equal = NULL, attribute_summary = "ignore", summarise_attributes = deprecated(), store_original_data = FALSE ) to_spatial_subdivision(x, protect = NULL, all = FALSE, merge = TRUE) to_spatial_subset(x, ..., subset_by = NULL) to_spatial_transformed(x, ...) to_spatial_unique(x, attribute_summary = "ignore", store_original_data = FALSE)
to_spatial_contracted( x, ..., simplify = TRUE, compute_centroids = TRUE, attribute_summary = "ignore", summarise_attributes = deprecated(), store_original_data = FALSE ) to_spatial_directed(x) to_spatial_explicit(x, ...) to_spatial_implicit(x) to_spatial_mixed(x, directed) to_spatial_neighborhood(x, node, threshold, weights = edge_length(), ...) to_spatial_reversed(x, protect = NULL) to_spatial_shortest_paths(x, ...) to_spatial_simple( x, remove_multiple = TRUE, remove_loops = TRUE, attribute_summary = "first", summarise_attributes = deprecated(), store_original_data = FALSE ) to_spatial_smooth( x, protect = NULL, require_equal = NULL, attribute_summary = "ignore", summarise_attributes = deprecated(), store_original_data = FALSE ) to_spatial_subdivision(x, protect = NULL, all = FALSE, merge = TRUE) to_spatial_subset(x, ..., subset_by = NULL) to_spatial_transformed(x, ...) to_spatial_unique(x, attribute_summary = "ignore", store_original_data = FALSE)
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? Defaults
to |
compute_centroids |
Should the new geometry of each contracted group of
nodes be the centroid of all group members? Defaults to |
attribute_summary |
Whenever groups of nodes or edges are merged
into a single feature during morphing, how should their attributes be
summarized? There are several options, see
|
summarise_attributes |
Deprecated, use |
store_original_data |
Whenever groups of nodes 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
|
directed |
Which edges should be directed? Evaluated by
|
node |
The node for which the neighborhood will be calculated.
Evaluated by |
threshold |
The threshold cost to be used. Only nodes reachable within
this threshold cost from the reference node will be included in the
neighborhood. Should be a numeric value in the same units as the given edge
weights. Alternatively, units can be specified explicitly by providing a
|
weights |
The edge weights to be used for travel cost computation.
Evaluated by |
protect |
Nodes or edges to be protected from being changed in
structure. Evaluated by |
remove_multiple |
Should multiple edges be merged into one. Defaults
to |
remove_loops |
Should loop edges be removed. Defaults to |
require_equal |
Which attributes of its incident edges should be equal
in order for a pseudo node to be removed? Evaluated as a
|
all |
Should edges be subdivided at all their interior points? If set
to |
merge |
Should multiple subdivision points at the same location be
merged into a single node, and should subdivision points at the same
location as an existing node be merged into that node? Defaults to
|
subset_by |
Whether to create subgraphs based on nodes or edges. |
Morphers are not meant to be called directly. Instead, they should
be called inside the morph
verb to change the
network structure temporarily. Depending on the chosen morpher, this results
in a list of one or more network objects. Single elements of that list can
be extracted directly as a new network by calling the morpher inside the
convert
verb instead, to make the changes lasting
rather than temporary.
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 such a group will be used by default as
geometry of the contracted node. If edges 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_implicit()
: Drop edge geometries from the network. Returns
a morphed_sfnetwork
containing a single element of class
sfnetwork
.
to_spatial_mixed()
: Construct a mixed network in which some edges
are directed, and some are undirected. In practice this is implemented as a
directed network in which those edges that are meant to be undirected are
duplicated and reversed. 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 st_network_cost
to
compute the travel cost from the specified node to all other nodes in the
network. Returns a morphed_sfnetwork
that may contain multiple
elements of class sfnetwork
, depending on the number of given
thresholds. 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 neighborhoods.
to_spatial_reversed()
: Reverse the direction of edges. 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
. 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()
: Construct a simple version of the network. A
simple network is defined as a network without loop edges and multiple
edges. A loop edge is an edge that starts and ends at the same node.
Multiple edges are different edges between the same node pair. 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 interior points. Subdividing means that a new node is
added on an edge, and the edge is split in two at that location. Interior
points are those points that shape a linestring geometry feature but are not
endpoints of it. Returns a morphed_sfnetwork
containing a single
element of class sfnetwork
. This morpher requires edges to be
spatially explicit.
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
.
to_spatial_unique()
: Merge nodes with equal geometries into a single
node. Returns a morphed_sfnetwork
containing a single element of
class sfnetwork
. By default sfnetworks rounds coordinates to
12 decimal places to determine spatial equality. You can influence this
behavior by explicitly setting the precision of the network using
st_set_precision
.
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) |> morph(to_spatial_shortest_paths, from = 1, to = 10) |> mutate(in_paths = TRUE) |> unmorph() # Lasting changes with convert. net |> activate(edges) |> 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) |> morph(to_spatial_shortest_paths, from = 1, to = 10) |> mutate(in_paths = TRUE) |> unmorph() # Lasting changes with convert. net |> activate(edges) |> 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 the chosen spatial predicate applies
to the spatial relation between the i-th node and any of the features 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_is_nearest(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_is_nearest(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. The function node_is_nearest
instead wraps around
st_nearest_feature
and returns TRUE
for element i
if the i-th node is the nearest node to any of the features in y
.
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) # Use predicate query function directly. within = with_graph(net, node_is_within(poly)) head(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) # Use predicate query function directly. within = with_graph(net, node_is_within(poly)) head(within)
These functions are a collection of node type queries that are commonly
used in spatial network analysis, and form a spatial extension to
node type queries
in tidygraph.
node_is_pseudo() node_is_dangling()
node_is_pseudo() node_is_dangling()
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, indicating if each node is of the type in question.
node_is_pseudo()
: Pseudo nodes in directed networks are those
nodes with only one incoming and one outgoing edge. In undirected networks
pseudo nodes are those nodes with only two incident edges, i.e. nodes of
degree 2.
node_is_dangling()
: Dangling nodes are nodes with only one
incident edge, i.e. nodes of degree 1.
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(mozart, "mst", directed = FALSE) # Use query function in a filter call. pseudos = net |> activate(nodes) |> filter(node_is_pseudo()) danglers = net |> activate(nodes) |> filter(node_is_dangling()) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, main = "Pseudo nodes") plot(st_geometry(pseudos), pch = 20, cex = 1.2, col = "orange", add = TRUE) plot(net, main = "Dangling nodes") plot(st_geometry(danglers), pch = 20, cex = 1.2, col = "orange", add = TRUE) par(oldpar) # Use query function in a mutate call. net |> activate(nodes) |> mutate(pseudo = node_is_pseudo(), dangling = node_is_dangling()) # Use query function directly. danglers = with_graph(net, node_is_dangling()) head(danglers)
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) # Create a network. net = as_sfnetwork(mozart, "mst", directed = FALSE) # Use query function in a filter call. pseudos = net |> activate(nodes) |> filter(node_is_pseudo()) danglers = net |> activate(nodes) |> filter(node_is_dangling()) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) plot(net, main = "Pseudo nodes") plot(st_geometry(pseudos), pch = 20, cex = 1.2, col = "orange", add = TRUE) plot(net, main = "Dangling nodes") plot(st_geometry(danglers), pch = 20, cex = 1.2, col = "orange", add = TRUE) par(oldpar) # Use query function in a mutate call. net |> activate(nodes) |> mutate(pseudo = node_is_pseudo(), dangling = node_is_dangling()) # Use query function directly. danglers = with_graph(net, node_is_dangling()) head(danglers)
Determine duplicated geometries
st_duplicated(x)
st_duplicated(x)
x |
A logical vector specifying for each feature in x
if its
geometry is equal to a previous feature in x
.
library(sf, quietly = TRUE) p1 = st_sfc(st_point(c(1, 1))) p2 = st_sfc(st_point(c(0, 0))) p3 = st_sfc(st_point(c(1, 0))) st_duplicated(c(p1, p2, p2, p3, p1))
library(sf, quietly = TRUE) p1 = st_sfc(st_point(c(1, 1))) p2 = st_sfc(st_point(c(0, 0))) p3 = st_sfc(st_point(c(1, 0))) st_duplicated(c(p1, p2, p2, p3, p1))
Geometry matching
st_match(x)
st_match(x)
x |
A numeric vector giving for each feature in x
the position of
the first feature in x
that has an equal geometry.
library(sf, quietly = TRUE) p1 = st_sfc(st_point(c(1, 1))) p2 = st_sfc(st_point(c(0, 0))) p3 = st_sfc(st_point(c(1, 0))) st_match(c(p1, p2, p2, p3, p1))
library(sf, quietly = TRUE) p1 = st_sfc(st_point(c(1, 1))) p2 = st_sfc(st_point(c(0, 0))) p3 = st_sfc(st_point(c(1, 0))) st_match(c(p1, p2, p2, p3, p1))
A spatial network specific bounding box creator, 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, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) # Create a network. n1 = st_point(c(8, 51)) n2 = st_point(c(7, 51.5)) n3 = st_point(c(8, 52)) n4 = st_point(c(9, 51)) e1 = st_sfc(st_linestring(c(n1, n2, n3))) nodes = st_as_sf(c(st_sfc(n1), st_sfc(n3), st_sfc(n4))) edges = st_as_sf(e1) 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. plot(net, lwd = 2, cex = 4, main = "Element bounding boxes") plot(st_as_sfc(node_bbox), border = "orange", lty = 2, lwd = 4, add = TRUE) plot(st_as_sfc(edge_bbox), border = "skyblue", lty = 2, lwd = 4, add = TRUE) plot(net, lwd = 2, cex = 4, main = "Network bounding box") plot(st_as_sfc(net_bbox), border = "orange", lty = 2, lwd = 4, add = TRUE) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) # Create a network. n1 = st_point(c(8, 51)) n2 = st_point(c(7, 51.5)) n3 = st_point(c(8, 52)) n4 = st_point(c(9, 51)) e1 = st_sfc(st_linestring(c(n1, n2, n3))) nodes = st_as_sf(c(st_sfc(n1), st_sfc(n3), st_sfc(n4))) edges = st_as_sf(e1) 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. plot(net, lwd = 2, cex = 4, main = "Element bounding boxes") plot(st_as_sfc(node_bbox), border = "orange", lty = 2, lwd = 4, add = TRUE) plot(st_as_sfc(edge_bbox), border = "skyblue", lty = 2, lwd = 4, add = TRUE) plot(net, lwd = 2, cex = 4, main = "Network bounding box") plot(st_as_sfc(net_bbox), border = "orange", lty = 2, lwd = 4, add = TRUE) par(oldpar)
Blending a point into a network is the combined process of first projecting the point onto its nearest point on its nearest edge in the network, then subdividing that edge at the location of the projected point, and finally adding the projected point as node to the network. If the location of the projected point is equal an existing node in the network, the attributes of the point will be joined to that node, instead of adding a new node.
st_network_blend(x, y, tolerance = Inf, ignore_duplicates = TRUE)
st_network_blend(x, y, tolerance = Inf, ignore_duplicates = TRUE)
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
|
ignore_duplicates |
If there are multiple points in |
When the projected location of a given point intersects with more
than one edge, it is only blended into the first of these edges. Edges are
not connected at blending locations. Use the spatial morpher
to_spatial_subdivision
for that.
To determine if a projected point is equal to an existing node, and to
determine if multiple projected points are equal to each other, sfnetworks
by default rounds coordinates to 12 decimal places. You can influence this
behavior by explicitly setting the precision of the network using
st_set_precision
.
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 an edge.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) # Create a spatial network. n1 = st_point(c(0, 0)) n2 = st_point(c(1, 0)) n3 = st_point(c(2, 0)) e1 = st_sfc(st_linestring(c(n1, n2)), crs = 3857) e2 = st_sfc(st_linestring(c(n2, n3)), crs = 3857) net = as_sfnetwork(c(e1, e2)) # Create spatial points to blend in. p1 = st_sfc(st_point(c(0.5, 0.1))) p2 = st_sfc(st_point(c(0.5, -0.2))) p3 = st_sfc(st_point(c(1, 0.2))) p4 = st_sfc(st_point(c(1.75, 0.2))) p5 = st_sfc(st_point(c(1.25, 0.1))) pts = st_sf(foo = letters[1:5], geometry = c(p1, p2, p3, p4, p5), crs = 3857) # Blend all points into the network. b1 = st_network_blend(net, pts) b1 plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(b1) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) # Blend points within a tolerance distance. tol = units::set_units(0.1, "m") b2 = st_network_blend(net, pts, tolerance = tol) b2 plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(b2) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) # Add points with duplicated projected location as isolated nodes. b3 = st_network_blend(net, pts, ignore_duplicates = FALSE) b3 par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) # Create a spatial network. n1 = st_point(c(0, 0)) n2 = st_point(c(1, 0)) n3 = st_point(c(2, 0)) e1 = st_sfc(st_linestring(c(n1, n2)), crs = 3857) e2 = st_sfc(st_linestring(c(n2, n3)), crs = 3857) net = as_sfnetwork(c(e1, e2)) # Create spatial points to blend in. p1 = st_sfc(st_point(c(0.5, 0.1))) p2 = st_sfc(st_point(c(0.5, -0.2))) p3 = st_sfc(st_point(c(1, 0.2))) p4 = st_sfc(st_point(c(1.75, 0.2))) p5 = st_sfc(st_point(c(1.25, 0.1))) pts = st_sf(foo = letters[1:5], geometry = c(p1, p2, p3, p4, p5), crs = 3857) # Blend all points into the network. b1 = st_network_blend(net, pts) b1 plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(b1) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) # Blend points within a tolerance distance. tol = units::set_units(0.1, "m") b2 = st_network_blend(net, pts, tolerance = tol) b2 plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(b2) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) # Add points with duplicated projected location as isolated nodes. b3 = st_network_blend(net, pts, ignore_duplicates = FALSE) b3 par(oldpar)
Compute total travel costs of shortest paths between nodes in a spatial network.
st_network_cost( x, from = node_ids(x), to = node_ids(x), weights = edge_length(), direction = "out", Inf_as_NaN = FALSE, router = getOption("sfn_default_router", "igraph"), use_names = FALSE, ... ) st_network_distance( x, from = node_ids(x), to = node_ids(x), direction = "out", Inf_as_NaN = FALSE, router = getOption("sfn_default_router", "igraph"), use_names = FALSE, ... )
st_network_cost( x, from = node_ids(x), to = node_ids(x), weights = edge_length(), direction = "out", Inf_as_NaN = FALSE, router = getOption("sfn_default_router", "igraph"), use_names = FALSE, ... ) st_network_distance( x, from = node_ids(x), to = node_ids(x), direction = "out", Inf_as_NaN = FALSE, router = getOption("sfn_default_router", "igraph"), use_names = FALSE, ... )
x |
An object of class |
from |
The nodes where the paths should start. Evaluated by
|
to |
The nodes where the paths should end. Evaluated by
|
weights |
The edge weights to be used in the shortest path calculation.
Evaluated by |
direction |
The direction of travel. Defaults to |
Inf_as_NaN |
Should the cost values of unconnected nodes be stored as
|
router |
The routing backend to use for the cost matrix computation.
Currently supported options are |
use_names |
If a column named |
... |
Additional arguments passed on to the underlying function of the chosen routing backend. See Details. |
The sfnetworks package does not implement its own routing algorithms to compute cost matrices. Instead, it relies on "routing backends", i.e. other R packages that have implemented such algorithms. Currently two different routing backends are supported.
The default is igraph
. This package supports
many-to-many cost matrix computation with the distances
function. The igraph router does not support dual-weighted routing.
The second supported routing backend is dodgr
. This
package supports many-to-many cost matrix computation with the
dodgr_dists
function. It also supports dual-weighted
routing. The dodgr package is a conditional dependency of sfnetworks. Using
the dodgr router requires the dodgr package to be installed.
The default router can be changed by setting the sfn_default_router
option.
An n times m numeric matrix where n is the length of the from
argument, and m is the length of the to
argument.
st_network_paths
, st_network_travel
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) net = as_sfnetwork(roxel, directed = FALSE) |> st_transform(3035) # Compute the network cost matrix between node pairs. # Note that geographic edge length is used as edge weights by default. st_network_cost(net, from = c(495, 121), to = c(495, 121)) # st_network_distance is a synonym for st_network_cost with default weights. st_network_distance(net, from = c(495, 121), to = c(495, 121)) # Compute the network cost matrix between spatial point features. # These are snapped to their nearest node before computing costs. 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)) # Use a node type query function to specify origins and/or destinations. st_network_cost(net, from = 499, to = node_is_connected(499)) # Use a spatial edge measure to specify edge weights. # By default edge_length() is used. st_network_cost(net, c(p1, p2), c(p1, p2), weights = edge_displacement()) # Use a column in the edges table to specify edge weights. # This uses tidy evaluation. net |> activate("edges") |> mutate(foo = runif(n(), min = 0, max = 1)) |> st_network_cost(c(p1, p2), c(p1, p2), weights = foo) # Compute the cost matrix without edge weights. # Here the cost is defined by the number of edges, ignoring space. st_network_cost(net, c(p1, p2), c(p1, p2), weights = NA) # Use the dodgr router for dual-weighted routing. paths = st_network_cost(net, from = c(p1, p2), to = c(p1, p2), weights = dual_weights(edge_segment_count(), edge_length()), router = "dodgr" ) # 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) net = as_sfnetwork(roxel, directed = FALSE) |> st_transform(3035) # Compute the network cost matrix between node pairs. # Note that geographic edge length is used as edge weights by default. st_network_cost(net, from = c(495, 121), to = c(495, 121)) # st_network_distance is a synonym for st_network_cost with default weights. st_network_distance(net, from = c(495, 121), to = c(495, 121)) # Compute the network cost matrix between spatial point features. # These are snapped to their nearest node before computing costs. 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)) # Use a node type query function to specify origins and/or destinations. st_network_cost(net, from = 499, to = node_is_connected(499)) # Use a spatial edge measure to specify edge weights. # By default edge_length() is used. st_network_cost(net, c(p1, p2), c(p1, p2), weights = edge_displacement()) # Use a column in the edges table to specify edge weights. # This uses tidy evaluation. net |> activate("edges") |> mutate(foo = runif(n(), min = 0, max = 1)) |> st_network_cost(c(p1, p2), c(p1, p2), weights = foo) # Compute the cost matrix without edge weights. # Here the cost is defined by the number of edges, ignoring space. st_network_cost(net, c(p1, p2), c(p1, p2), weights = NA) # Use the dodgr router for dual-weighted routing. paths = st_network_cost(net, from = c(p1, p2), to = c(p1, p2), weights = dual_weights(edge_segment_count(), edge_length()), router = "dodgr" ) # 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)
The faces of a spatial network are the areas bounded by edges, without any other edge crossing it. A special face is the outer face, which is the area not bounded by any set of edges.
st_network_faces(x, boundary = NULL)
st_network_faces(x, boundary = NULL)
x |
An object of class |
boundary |
The boundary used for the outer face, as an object of class
|
An object of class sfc
with POLYGON
geometries, in which each feature represents one face of the network.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) pts = st_transform(mozart, 3035) net = as_sfnetwork(pts, "delaunay") faces = st_network_faces(net) plot(faces, col = sf.colors(length(faces), categorical = TRUE)) plot(net, add = TRUE) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) pts = st_transform(mozart, 3035) net = as_sfnetwork(pts, "delaunay") faces = st_network_faces(net) plot(faces, col = sf.colors(length(faces), categorical = TRUE)) plot(net, add = TRUE) par(oldpar)
Isolines are curves along which a function has a constant value. In spatial networks, they are used to delineate areas that are reachable from a given node within a given travel cost. If the travel cost is distance, they are known as isodistances, while if the travel cost is time, they are known as isochrones. This function finds all network nodes that lie inside an isoline around a specified node.
st_network_iso( x, node, cost, weights = edge_length(), ..., delineate = TRUE, ratio = 1, allow_holes = FALSE )
st_network_iso( x, node, cost, weights = edge_length(), ..., delineate = TRUE, ratio = 1, allow_holes = FALSE )
x |
An object of class |
node |
The node around which the isolines will be drawn. Evaluated by
|
cost |
The constant cost value of the isoline. Should be a numeric
value in the same units as the given edge weights. Alternatively, units can
be specified explicitly by providing a |
weights |
The edge weights to be used in the shortest path calculation.
Evaluated by |
... |
Additional arguments passed on to |
delineate |
Should the nodes inside the isoline be delineated? If
|
ratio |
The ratio of the concave hull. Defaults to |
allow_holes |
May the concave hull have holes? Defaults to |
An object of class sf
with one row per requested
isoline. The object contains the following columns:
cost
: The constant cost value of the isoline.
geometry
: If delineate = TRUE
, the concave hull of all
nodes that lie inside the isoline. Otherwise, those nodes combined into a
single MULTIPOINT
geometry.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) center = st_centroid(st_combine(st_geometry(roxel))) net = as_sfnetwork(roxel, directed = FALSE) iso = net |> st_network_iso(node_is_nearest(center), c(1000, 500, 250)) colors = c("#fee6ce90", "#fdae6b90", "#e6550d90") plot(net) plot(st_geometry(iso), col = colors, add = TRUE) # The level of detail can be increased with the ratio argument. # This requires GEOS >= 3.11. if (compareVersion(sf_extSoftVersion()[["GEOS"]], "3.11.0") > -1) { iso = net |> st_network_iso(node_is_nearest(center), c(1000, 500, 250), ratio = 0.3) colors = c("#fee6ce90", "#fdae6b90", "#e6550d90") plot(net) plot(st_geometry(iso), col = colors, add = TRUE) } par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) center = st_centroid(st_combine(st_geometry(roxel))) net = as_sfnetwork(roxel, directed = FALSE) iso = net |> st_network_iso(node_is_nearest(center), c(1000, 500, 250)) colors = c("#fee6ce90", "#fdae6b90", "#e6550d90") plot(net) plot(st_geometry(iso), col = colors, add = TRUE) # The level of detail can be increased with the ratio argument. # This requires GEOS >= 3.11. if (compareVersion(sf_extSoftVersion()[["GEOS"]], "3.11.0") > -1) { iso = net |> st_network_iso(node_is_nearest(center), c(1000, 500, 250), ratio = 0.3) colors = c("#fee6ce90", "#fdae6b90", "#e6550d90") plot(net) plot(st_geometry(iso), col = colors, add = TRUE) } par(oldpar)
A spatial network specific join function which makes a spatial full join on
the geometries of the nodes data. 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
.
By default sfnetworks rounds coordinates to 12 decimal places to
determine spatial equality. You can influence this behavior by explicitly
setting the precision of the networks using
st_set_precision
.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) # Create two networks. n1 = st_point(c(0, 0)) n2 = st_point(c(1, 0)) n3 = st_point(c(1,1)) n4 = st_point(c(0,1)) e1 = st_sfc(st_linestring(c(n1, n2))) e2 = st_sfc(st_linestring(c(n2, n3))) e3 = st_sfc(st_linestring(c(n3, n4))) neta = as_sfnetwork(c(e1, e2)) netb = as_sfnetwork(c(e2, e3)) # Join the networks based on spatial equality of nodes. net = st_network_join(neta, netb) net # Plot. plot(neta, pch = 15, cex = 2, lwd = 4) plot(netb, col = "orange", pch = 18, cex = 2, lty = 3, lwd = 4, add = TRUE) plot(net, cex = 2, lwd = 4) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) # Create two networks. n1 = st_point(c(0, 0)) n2 = st_point(c(1, 0)) n3 = st_point(c(1,1)) n4 = st_point(c(0,1)) e1 = st_sfc(st_linestring(c(n1, n2))) e2 = st_sfc(st_linestring(c(n2, n3))) e3 = st_sfc(st_linestring(c(n3, n4))) neta = as_sfnetwork(c(e1, e2)) netb = as_sfnetwork(c(e2, e3)) # Join the networks based on spatial equality of nodes. net = st_network_join(neta, netb) net # Plot. plot(neta, pch = 15, cex = 2, lwd = 4) plot(netb, col = "orange", pch = 18, cex = 2, lty = 3, lwd = 4, add = TRUE) plot(net, cex = 2, lwd = 4) par(oldpar)
Find shortest paths between nodes in a spatial network
st_network_paths( x, from, to = node_ids(x), weights = edge_length(), all = FALSE, k = 1, direction = "out", router = getOption("sfn_default_router", "igraph"), use_names = FALSE, return_cost = TRUE, return_geometry = TRUE, ... )
st_network_paths( x, from, to = node_ids(x), weights = edge_length(), all = FALSE, k = 1, direction = "out", router = getOption("sfn_default_router", "igraph"), use_names = FALSE, return_cost = TRUE, return_geometry = TRUE, ... )
x |
An object of class |
from |
The node where the paths should start. Evaluated by
|
to |
The nodes where the paths should end. Evaluated by
|
weights |
The edge weights to be used in the shortest path calculation.
Evaluated by |
all |
Should all shortest paths be returned for each pair of nodes? If
set to |
k |
The number of paths to find. Setting this to any integer higher
than 1 returns not only the shortest path, but also the next k - 1 loopless
shortest paths, which may be longer than the shortest path. Currently, this
is only supported for one-to-one routing, meaning that both the from and to
argument should be of length 1. This argument is ignored if |
direction |
The direction of travel. Defaults to |
router |
The routing backend to use for the shortest path computation.
Currently supported options are |
use_names |
If a column named |
return_cost |
Should the total cost of each path be computed? Defaults
to |
return_geometry |
Should a linestring geometry be constructed for each
path? Defaults to |
... |
Additional arguments passed on to the underlying function of the chosen routing backend. See Details. |
The sfnetworks package does not implement its own routing algorithms to find shortest paths. Instead, it relies on "routing backends", i.e. other R packages that have implemented such algorithms. Currently two different routing backends are supported.
The default is igraph
. This package supports
one-to-many shortest path calculation with the
shortest_paths
function. Note that multiple from nodes
are not supported. If multiple from nodes are given, only the first one is
taken. The igraph router also supports the computation of all shortest path
(see the all
argument) through the
all_shortest_paths
function and of k shortest paths
(see the k
argument) through the
k_shortest_paths
function. In the latter case, only
one-to-one routing is supported, meaning that also only one to node should
be provided. The igraph router does not support dual-weighted routing.
The second supported routing backend is dodgr
. This
package supports many-to-many shortest path calculation with the
dodgr_paths
function. It also supports dual-weighted
routing. The computation of all shortest paths and k shortest paths is
currently not supported by the dodgr router. The dodgr package is a
conditional dependency of sfnetworks. Using the dodgr router requires the
dodgr package to be installed.
The default router can be changed by setting the sfn_default_router
option.
An object of class sf
with one row per requested
path. If return_geometry = FALSE
or edges are spatially implicit, a
tbl_df
is returned instead. If a requested path could
not be found, it is included in the output as an empty path.
Depending on the argument settings, the output may include the following columns:
from
: The index of the node at the start of the path.
to
: The index of the node at the end of the path.
node_path
: A vector containing the indices of all nodes on
the path, in order of visit.
edge_path
: A vector containing the indices of all edges on
the path, in order of visit.
path_found
: A boolean describing if the requested path exists.
cost
: The total cost of the path, obtained by summing the
weights of all visited edges. Included if return_cost = TRUE
.
geometry
: The geometry of the path, obtained by merging the
geometries of all visited edges. Included if return_geometry = TRUE
and the network has spatially explicit edges.
st_network_cost
, st_network_travel
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) net = as_sfnetwork(roxel, directed = FALSE) |> st_transform(3035) # Compute the shortest path between two nodes. # Note that geographic edge length is used as edge weights by default. paths = st_network_paths(net, from = 495, to = 121) paths plot(net, col = "grey") plot(st_geometry(net)[paths$from], pch = 20, cex = 2, add = TRUE) plot(st_geometry(paths), col = "orange", lwd = 3, add = TRUE) # Compute the shortest paths from one to multiple nodes. # This will return a tibble with one row per path. paths = st_network_paths(net, from = 495, to = c(121, 131, 141)) paths plot(net, col = "grey") plot(st_geometry(net)[paths$from], pch = 20, cex = 2, add = TRUE) plot(st_geometry(paths), col = "orange", lwd = 3, add = TRUE) # Compute the shortest path between two spatial point features. # These are snapped to their nearest node before finding the path. 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 plot(net, col = "grey") plot(c(p1, p2), pch = 20, cex = 2, add = TRUE) plot(st_geometry(net)[paths$from], pch = 4, cex = 2, add = TRUE) plot(st_geometry(paths), col = "orange", lwd = 3, add = TRUE) # Use a node type query function to specify destinations. st_network_paths(net, 1, node_is_adjacent(1)) # Use a spatial edge measure to specify edge weights. # By default edge_length() is used. st_network_paths(net, p1, p2, weights = edge_displacement()) # Use a column in the edges table to specify edge weights. # This uses tidy evaluation. net |> activate("edges") |> mutate(foo = runif(n(), min = 0, max = 1)) |> st_network_paths(p1, p2, weights = foo) # Compute the shortest paths without edge weights. # This is the path with the fewest number of edges, ignoring space. st_network_paths(net, p1, p2, weights = NA) # Use the dodgr router for many-to-many routing. paths = st_network_paths(net, from = c(1, 2), to = c(10, 11), router = "dodgr" ) # Use the dodgr router for dual-weighted routing. paths = st_network_paths(net, from = c(1, 2), to = c(10, 11), weights = dual_weights(edge_segment_count(), edge_length()), router = "dodgr" ) par(oldpar)
library(sf, quietly = TRUE) library(tidygraph, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) net = as_sfnetwork(roxel, directed = FALSE) |> st_transform(3035) # Compute the shortest path between two nodes. # Note that geographic edge length is used as edge weights by default. paths = st_network_paths(net, from = 495, to = 121) paths plot(net, col = "grey") plot(st_geometry(net)[paths$from], pch = 20, cex = 2, add = TRUE) plot(st_geometry(paths), col = "orange", lwd = 3, add = TRUE) # Compute the shortest paths from one to multiple nodes. # This will return a tibble with one row per path. paths = st_network_paths(net, from = 495, to = c(121, 131, 141)) paths plot(net, col = "grey") plot(st_geometry(net)[paths$from], pch = 20, cex = 2, add = TRUE) plot(st_geometry(paths), col = "orange", lwd = 3, add = TRUE) # Compute the shortest path between two spatial point features. # These are snapped to their nearest node before finding the path. 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 plot(net, col = "grey") plot(c(p1, p2), pch = 20, cex = 2, add = TRUE) plot(st_geometry(net)[paths$from], pch = 4, cex = 2, add = TRUE) plot(st_geometry(paths), col = "orange", lwd = 3, add = TRUE) # Use a node type query function to specify destinations. st_network_paths(net, 1, node_is_adjacent(1)) # Use a spatial edge measure to specify edge weights. # By default edge_length() is used. st_network_paths(net, p1, p2, weights = edge_displacement()) # Use a column in the edges table to specify edge weights. # This uses tidy evaluation. net |> activate("edges") |> mutate(foo = runif(n(), min = 0, max = 1)) |> st_network_paths(p1, p2, weights = foo) # Compute the shortest paths without edge weights. # This is the path with the fewest number of edges, ignoring space. st_network_paths(net, p1, p2, weights = NA) # Use the dodgr router for many-to-many routing. paths = st_network_paths(net, from = c(1, 2), to = c(10, 11), router = "dodgr" ) # Use the dodgr router for dual-weighted routing. paths = st_network_paths(net, from = c(1, 2), to = c(10, 11), weights = dual_weights(edge_segment_count(), edge_length()), router = "dodgr" ) par(oldpar)
Solve the travelling salesman problem by finding the shortest route through a set of nodes that visits each of those nodes once.
st_network_travel( x, nodes, weights = edge_length(), optimizer = "TSP", router = getOption("sfn_default_router", "igraph"), return_paths = TRUE, use_names = FALSE, return_cost = TRUE, return_geometry = TRUE, ... )
st_network_travel( x, nodes, weights = edge_length(), optimizer = "TSP", router = getOption("sfn_default_router", "igraph"), return_paths = TRUE, use_names = FALSE, return_cost = TRUE, return_geometry = TRUE, ... )
x |
An object of class |
nodes |
Nodes to be visited. Evaluated by
|
weights |
The edge weights to be used in the shortest path calculation.
Evaluated by |
optimizer |
The optimization backend to use for defining the optimal
visiting order of the given nodes. Currently the only supported option is
|
router |
The routing backend to use for the cost matrix computation and
the path computation. Currently supported options are |
return_paths |
After defining the optimal visiting order of nodes,
should the actual paths connecting those nodes be computed and returned?
Defaults to |
use_names |
If a column named |
return_cost |
Should the total cost of each path between two subsequent
nodes be computed? Defaults to |
return_geometry |
Should a linestring geometry be constructed for each
path between two subsequent nodes? Defaults to |
... |
Additional arguments passed on to the underlying function of the chosen optimization backend. See Details. |
The sfnetworks package does not implement its own route optimization
algorithms. Instead, it relies on "optimization backends", i.e. other R
packages that have implemented such algorithms. Currently the only supported
optimization backend to solve the travelling salesman problem is the
TSP
package, which provides the
solve_TSP
function for this task.
An input for most route optimization algorithms is the matrix containing the
travel costs between the nodes to be visited. This is computed using
st_network_cost
. The output of most route optimization
algorithms is the optimal order in which the given nodes should be visited.
To compute the actual paths that connect the nodes in that order, the
st_network_paths
function is used. Both cost matrix computation
and shortest paths computation allow to specify a "routing backend", i.e. an
R package that implements algorithms to solve those tasks. See the
documentation of the corresponding functions for details.
An object of class sf
with one row per leg of the
optimal route, containing the path of that leg.
If return_geometry = FALSE
or edges are spatially implicit, a
tbl_df
is returned instead. See the documentation of
st_network_paths
for details. If return_paths = FALSE
,
a vector of indices in visiting order is returned, with each index
specifying the position of the visited node in the from
argument.
st_network_paths
, st_network_cost
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) net = as_sfnetwork(roxel, directed = FALSE) |> st_transform(3035) # Compute the optimal route through three nodes. # Note that geographic edge length is used as edge weights by default. route = st_network_travel(net, c(1, 10, 100)) route plot(net, col = "grey") plot(st_geometry(net)[route$from], pch = 20, cex = 2, add = TRUE) plot(st_geometry(route), col = "orange", lwd = 3, add = TRUE) # Instead of returning a path we can return a vector of visiting order. st_network_travel(net, c(1, 10, 100), return_paths = FALSE) # Use spatial point features to specify the visiting locations. # These are snapped to their nearest node before finding the path. p1 = st_geometry(net, "nodes")[1] + st_sfc(st_point(c(50, -50))) p2 = st_geometry(net, "nodes")[10] + st_sfc(st_point(c(-10, 100))) p3 = st_geometry(net, "nodes")[100] + st_sfc(st_point(c(-10, 100))) pts = c(p1, p2, p3) st_crs(pts) = st_crs(net) route = st_network_travel(net, pts) route plot(net, col = "grey") plot(pts, pch = 20, cex = 2, add = TRUE) plot(st_geometry(net)[route$from], pch = 4, cex = 2, add = TRUE) plot(st_geometry(route), col = "orange", lwd = 3, add = TRUE) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) net = as_sfnetwork(roxel, directed = FALSE) |> st_transform(3035) # Compute the optimal route through three nodes. # Note that geographic edge length is used as edge weights by default. route = st_network_travel(net, c(1, 10, 100)) route plot(net, col = "grey") plot(st_geometry(net)[route$from], pch = 20, cex = 2, add = TRUE) plot(st_geometry(route), col = "orange", lwd = 3, add = TRUE) # Instead of returning a path we can return a vector of visiting order. st_network_travel(net, c(1, 10, 100), return_paths = FALSE) # Use spatial point features to specify the visiting locations. # These are snapped to their nearest node before finding the path. p1 = st_geometry(net, "nodes")[1] + st_sfc(st_point(c(50, -50))) p2 = st_geometry(net, "nodes")[10] + st_sfc(st_point(c(-10, 100))) p3 = st_geometry(net, "nodes")[100] + st_sfc(st_point(c(-10, 100))) pts = c(p1, p2, p3) st_crs(pts) = st_crs(net) route = st_network_travel(net, pts) route plot(net, col = "grey") plot(pts, pch = 20, cex = 2, add = TRUE) plot(st_geometry(net)[route$from], pch = 4, cex = 2, add = TRUE) plot(st_geometry(route), col = "orange", lwd = 3, add = TRUE) par(oldpar)
Project spatial points on a spatial network
st_project_on_network(x, network, on = "edges")
st_project_on_network(x, network, on = "edges")
x |
The spatial features to be projected, either as object of class
|
network |
An object of class |
on |
On what component of the network should the points be projected?
Setting it to |
This function uses st_nearest_feature
to find
the nearest edge or node to each feature in x
. When projecting on
edges, it then finds the nearest point on the nearest edge by calling
st_nearest_points
in a pairwise manner.
The same object as x
but with its geometries replaced by the
projections.
Due to internal rounding of rational numbers, even a point projected
on an edge may not be evaluated as actually intersecting that edge when
calling st_intersects
.
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) # Create a spatial network. n1 = st_point(c(0, 0)) n2 = st_point(c(1, 0)) n3 = st_point(c(2, 0)) e1 = st_sfc(st_linestring(c(n1, n2)), crs = 3857) e2 = st_sfc(st_linestring(c(n2, n3)), crs = 3857) net = as_sfnetwork(c(e1, e2)) # Create spatial points to project in. p1 = st_sfc(st_point(c(0.25, 0.1))) p2 = st_sfc(st_point(c(1, 0.2))) p3 = st_sfc(st_point(c(1.75, 0.15))) pts = st_sf(foo = letters[1:3], geometry = c(p1, p2, p3), crs = 3857) # Project points to the edges of the network. p1 = st_project_on_network(pts, net) plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(st_geometry(p1), pch = 4, col = "orange", add = TRUE) # Project points to the nodes of the network. p2 = st_project_on_network(pts, net, on = "nodes") plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(st_geometry(p2), pch = 4, col = "orange", add = TRUE) par(oldpar)
library(sf, quietly = TRUE) oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1)) # Create a spatial network. n1 = st_point(c(0, 0)) n2 = st_point(c(1, 0)) n3 = st_point(c(2, 0)) e1 = st_sfc(st_linestring(c(n1, n2)), crs = 3857) e2 = st_sfc(st_linestring(c(n2, n3)), crs = 3857) net = as_sfnetwork(c(e1, e2)) # Create spatial points to project in. p1 = st_sfc(st_point(c(0.25, 0.1))) p2 = st_sfc(st_point(c(1, 0.2))) p3 = st_sfc(st_point(c(1.75, 0.15))) pts = st_sf(foo = letters[1:3], geometry = c(p1, p2, p3), crs = 3857) # Project points to the edges of the network. p1 = st_project_on_network(pts, net) plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(st_geometry(p1), pch = 4, col = "orange", add = TRUE) # Project points to the nodes of the network. p2 = st_project_on_network(pts, net, on = "nodes") plot(net) plot(st_geometry(pts), pch = 20, col = "orange", add = TRUE) plot(st_geometry(p2), pch = 4, col = "orange", add = TRUE) par(oldpar)
Rounding of geometry coordinates
st_round(x, digits = 0)
st_round(x, digits = 0)
x |
|
digits |
Integer indicating the number of decimal places to be used. |
An object of class sf
or sfc
with rounded coordinates.
library(sf, quietly = TRUE) p1 = st_sfc(st_point(c(1.123, 1.123))) p2 = st_sfc(st_point(c(0.789, 0.789))) p3 = st_sfc(st_point(c(1.123, 0.789))) st_round(st_as_sf(c(p1, p2, p2, p3, p1)), digits = 1)
library(sf, quietly = TRUE) p1 = st_sfc(st_point(c(1.123, 1.123))) p2 = st_sfc(st_point(c(0.789, 0.789))) p3 = st_sfc(st_point(c(1.123, 0.789))) st_round(st_as_sf(c(p1, p2, p2, p3, p1)), digits = 1)
Construct a subdivision of the network by subdividing edges at interior points. Subdividing means that a new node is added on an edge, and the edge is split in two at that location. Interior points are those points that shape a linestring geometry feature but are not endpoints of it.
subdivide_edges(x, protect = NULL, all = FALSE, merge = TRUE)
subdivide_edges(x, protect = NULL, all = FALSE, merge = TRUE)
x |
An object of class |
protect |
An integer vector of edge indices specifying which edges
should be protected from being subdivided. Defaults to |
all |
Should edges be subdivided at all their interior points? If set
to |
merge |
Should multiple subdivision points at the same location be
merged into a single node, and should subdivision points at the same
location as an existing node be merged into that node? Defaults to
|
The subdivision of x as object of class sfnetwork
.
By default sfnetworks rounds coordinates to 12 decimal places to
determine spatial equality. You can influence this behavior by explicitly
setting the precision of the network using
st_set_precision
.
Normally tidygraph functions should work out of the box on
sfnetwork
objects, but in some cases special treatment is
needed especially for the geometry column, requiring a specific method.
## S3 method for class 'sfnetwork' as_tbl_graph(x, ...) ## S3 method for class 'sfnetwork' reroute(.data, ...) ## S3 method for class 'sfnetwork' morph(.data, ...) ## S3 method for class 'morphed_sfnetwork' unmorph(.data, ...)
## S3 method for class 'sfnetwork' as_tbl_graph(x, ...) ## S3 method for class 'sfnetwork' reroute(.data, ...) ## S3 method for class 'sfnetwork' morph(.data, ...) ## S3 method for class 'morphed_sfnetwork' unmorph(.data, ...)
x |
An object of class |
... |
Arguments passed on the corresponding |
.data |
An object of class |
See the tidygraph
documentation. The
following methods have a special behavior:
reroute
: To preserve the valid spatial network structure,
this method will replace the boundaries of edge geometries by the location
of the node those edges are rerouted to or from. Note that when the goal
is to reverse edges in a spatial network, reroute will not simply reverse
the edge geometries. In that case it is recommended to use the sfnetwork
method for st_reverse
instead.
morph
: This method checks if the morphed network still has
spatially embedded nodes. In that case a morphed_sfnetwork
is
returned. If not, a morphed_tbl_graph
is returned instead.
unmorph
: This method makes sure the geometry list column is
correctly handled during the unmorphing process.
The method for as_tbl_graph
returns an
object of class tbl_graph
. The method for
morph
returns a morphed_sfnetwork
if the
morphed network is still spatial, and a morphed_tbl_graph
otherwise.
All other methods return an object of class sfnetwork
.
Validate the structure of a sfnetwork
validate_network(x, message = TRUE)
validate_network(x, message = TRUE)
x |
An object of class |
message |
Should messages be printed during validation? Defaults to
|
A valid sfnetwork structure means that all nodes have POINT
geometries, and - when edges are spatially explicit - all edges have
LINESTRING
geometries, nodes and edges have the same coordinate
reference system and the same coordinate precision, and coordinates of
edge boundaries match coordinates of their corresponding nodes.
Nothing when the network is valid. Otherwise, an error is thrown.
Since sfnetwork
objects inherit igraph
objects, any igraph function can be called on a sfnetwork. However, if this
function returns a network, it will be an igraph object rather than a
sfnetwork object. With wrap_igraph
, such a function will
preserve the sfnetwork class, after checking if the network returned by
igraph still has a valid spatial network structure.
wrap_igraph(.data, .f, ..., .force = FALSE, .message = TRUE)
wrap_igraph(.data, .f, ..., .force = FALSE, .message = TRUE)
.data |
An object of class |
.f |
An function from the |
... |
Arguments passed on to |
.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 |
An object of class sfnetwork
.
oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(mozart, "delaunay", directed = FALSE) mst = wrap_igraph(net, igraph::mst, .message = FALSE) mst plot(net) plot(mst) par(oldpar)
oldpar = par(no.readonly = TRUE) par(mar = c(1,1,1,1), mfrow = c(1,2)) net = as_sfnetwork(mozart, "delaunay", directed = FALSE) mst = wrap_igraph(net, igraph::mst, .message = FALSE) mst plot(net) plot(mst) par(oldpar)