My goal is to generate an object of neighbourhood relationships between spatial lines in a road network. If my data were spatial polygons I could use spdep::poly2nb to do this, but I'm having trouble working out how to do this for spatial lines.
In the reprex below I try using igraph::as_adjacency_matrix create an adjacency matrix and then convert that to a neighbours list object using spdep::mat2listw. But is this the correct way to go?
Once I have the Neighours list I also want to label with the road_id attribute.
library(sfnetworks)
library(sf)
library(spdep)
library(igraph)
net <- as_sfnetwork(roxel, directed = FALSE) %>%
activate("edges") %>%
mutate(road_id = row_number()+1000)
# Make adjacency matrix
B_net <- igraph::as_adjacency_matrix(net, edges = TRUE, attr = names)
# Make neighbour list
nb_net <- mat2listw(B_net)$neighbours
# Can't use row.names in mat2listw because how do I set row.names in igraph::as_adjacency_matrix
EDIT: New approach using methof in this sfnetworks issue https://github.com/luukvdmeer/sfnetworks/issues/10 gives a neighbour list.
net_sf <- st_as_sf(net)
net_neigh <- st_touches(net_sf)
# define ad-hoc function to translate sgbp into nb (as documented in
# https://r-spatial.github.io/spdep/articles/nb_sf.html#creating-neighbours-using-sf-objects)
as.nb.sgbp <- function(x) {
attrs <- attributes(x)
x <- lapply(x, function(i) { if(length(i) == 0L) 0L else i } )
attributes(x) <- attrs
class(x) <- "nb"
x
}
net_nb <- as.nb.sgbp(net_neigh)
net_lw <- nb2listw(net_nb)
IMO there are a few approaches for creating a neighborhood list object for spatial lines similar to the output of
spdep::poly2nb. Unfortunately there are also a few assumptions (analogous to the queen parameter ofspdep::poly2nb) that I will try to explain here.First we load some packages and data
The first solution is based on the
sf::st_touchesfunction:The output is an
sgbpobject that summarizes the relationships between the spatial lines according to atouchespredicate. For example the first line of the output means that the first line ofroxelobject touches the lines 101, 146, 493, 577 and 742. Thestplanr::geo_projectedfunction is used to apply a geometric binary predicate function without reprojecting the data (otherwise we would have get some warning message).The
touchespredicate is defined in the corresponding help page (?sf::st_touches) and also here. Briefly, thesf::st_touchesfunction matches theLINESTRINGgeometries that share one common point when that point lies in the union of their boundaries (i.e. the first and last point of the line). For example, the following lines intersect each other but they are not touching since the common point do not lie in their boundaries.Another, slightly different, solution is created using the
sf::st_relatefunction:The output is (again) an
sgbpobject that summarizes the relationships between the spatial lines according to a "relate" predicate with pattern"FF*FT****". That type of pattern is used to matchLINESTRINGSif and only if they share at least one point in their boundaries, their interiors are not intersecting and there is no shared point between interiors and boundaries. For example the following two lines are touching but they are not related according to the pattern"FF*FT****".You can check more details about
sf::st_relatefunction and how to build patterns in the help page ofsf::st_relate. We can understand why this distinction betweenst_touchesandst_relateis important when we compare the output of the two predicates.We can see that they are almost identical but for a few cases (see, for example, the 6th row). We can understand the situation even better if we plot the corresponding lines:
We can see that the 6th line is touching all the other lines but it's not "related" to line 728 according to the pattern
"FF*FT****". This implies that if you create the neighbours list starting fromst_relate, then you are missing (at least) one existing link. I explicitly chose that type of pattern since the default algorithm used bysfnetworksfor inferring a graph structure from ansfobject is very similar (maybe identical) to that type of pattern. We can check this fact creating theline graphassociated to the graph created byas_sfnetwork:and test their equality:
I had to use the
uniquefunction since theigraphoutput may return duplicated indexes when there are multipled edges between two vertices, i.e.Anyway, we can see that the output is identical to
roxel_relate, which means that we cannot always safely usest_relateormake_line_graphfunctions without particular considerations on the underlying street network object. I don't want to add too many details here since the examples are too complicated, but we cannot either always use thest_touchesfunction for inferring the neighbour list object (but if you want to read a little bit more on this topic I recently wrote a paper on this topic, which is currently under review).Anyway, the summary of that paper is that I think we can safely use
st_touchesorst_relatefunctions to generate the neighbour list only after transforming theroxelobject using thestplanr::rnet_breakup_verticesfunction. (Actually there are still some really minor differences between the two approaches, but I still have to properly figure out how to solve them. For the moment I would use st_touches).Then, if you want, you can transform the result into an nb object:
We can also use the "line-graph" option:
Created on 2020-06-01 by the reprex package (v0.3.0)
I know that I wrote a lot of (maybe unnecessary) information but I think they are useful to properly give context about the suggested solution. If it's not clear please add more questions.