luukvdmeer / sfnetworks

Tidy Geospatial Networks in R
https://luukvdmeer.github.io/sfnetworks/
Other
333 stars 20 forks source link

Morpher for calculating node neighborhoods based on spatial proximity #90

Closed luukvdmeer closed 3 years ago

luukvdmeer commented 3 years ago

Is your feature request related to a problem? Please describe. A really nice morpher in tidygraph is to_local_neighborhood. For a given node, it limits the graph to the neighborhood of that node, based on a given order. E.g., order one gives all nodes that are directly connected to the root node, etc. When applied to spatial networks, this gives nice maps:

library(sfnetworks)
library(tidygraph)
#> 
#> Attaching package: 'tidygraph'
#> The following object is masked from 'package:stats':
#> 
#>     filter

net = as_sfnetwork(roxel, directed = FALSE)

plot(net)
plot(
  convert(net, to_local_neighborhood, 1, order = 10),
  col = "red",
  add = TRUE
)

Created on 2020-11-02 by the reprex package (v0.3.0)

However, these neighborhoods are always computed based on the number of steps it takes to reach other nodes, not on the sum of edge weights it takes to reach other nodes. If that is possible, the neighborhoods actually are the same as isochrones (when using travel time as weights) or isodists (when using distance as edge weights). That would be a nice spatial morpher!

Describe the solution you'd like A to_spatial_neighborhood morpher that computes neighborhoods based on edge weights. Maybe igraph already has functionality to do this.

luukvdmeer commented 3 years ago

tidygraph::to_local_neighborhood wraps around igraph::make_ego_graph. An 'ego graph' is an induced subgraph of neighbors centered at node i within a given radius. This radius normally refers to the number of steps it takes to reach a node j from node i. In the Python package networkX, however, you can change this behavior and use edge weights instead. Hence, this allows calculating isochrones and isodists. See https://networkx.org/documentation/stable//reference/generated/networkx.generators.ego.ego_graph.html.

It seems unfortunately that igraph does not have this functionality. Seems like we have to implement this ourselves.

agila5 commented 3 years ago

Hi! A (maybe naive) suggestion:

# packages
suppressPackageStartupMessages({
  library(sf)
  library(tidygraph)
  library(sfnetworks)
})

# data
roxel_sfn <- as_sfnetwork(roxel, directed = FALSE, length_as_weight = TRUE)

# Find nodes within 300m
roxel_sfn_small <- roxel_sfn %>% 
  filter(node_distance_from(1, weights = weight) <= 300)

# Plot
par(mar = rep(0, 4))
plot(roxel_sfn)
plot(roxel_sfn_small, col = "red", add = TRUE)

Created on 2020-12-06 by the reprex package (v0.3.0)

This is not exactly the ideal approach since node_distance_from() calculates all the distances at the C level and then it filters at the R level. It would be better to filter the nodes at the C level (and then apply slice() or similar approach) but this is as far as I can go.

luukvdmeer commented 3 years ago

Amazing! I did not know node_distance_from accepted edge weights. I agree it could be optimized, but really like this as a first implementation, since it is also neat and clear. And also, if you want to do large isochrone/isodists calculations on large street networks, you'll probably be better of with the graphhopper/osrm bridges either way. No need to compete with that already ;)

agila5 commented 3 years ago

Great! If you want sometimes next week I can add a PR with the new morpher.

luukvdmeer commented 3 years ago

Perfect! No time pressure ;) For the time being I will already include the workflow you presented in the new routing vignette.

luukvdmeer commented 3 years ago

The workflow is now presented in the routing vignette.

luukvdmeer commented 3 years ago

This is now part of the new release, thanks to @agila5

See this section in the morphers vignette for an example.