Closed idshklein closed 3 years ago
Good to see such a nice workflow already, using sfnetworks! Sounds like something to implement indeed. Will look into the details and the third part asap
I think with current functionalities, most notably the tidygraph::to_contracted()
morpher, we already come close. Improvements are still needed. Some issues:
Adressing these issues in a specific to_spatial_contracted
morpher would bring us a lot further!
Reprex:
suppressPackageStartupMessages({
library(sfnetworks)
library(sf)
library(tidygraph)
library(tidyverse)
library(magrittr)
library(dbscan)
})
G = roxel %>%
st_transform(3035) %>%
filter(!type %in% c("path", "footway", "track", "pedestrian", "cycleway")) %>%
as_sfnetwork()
# By setting minPts to 1 we put every node in a cluster
# Even if the node is the only member of that cluster
clusters = dbscan(st_coordinates(G), eps = 15, minPts = 1)$cluster
G = G %>%
mutate(cluster = clusters)
G
#> # A sfnetwork with 597 nodes and 641 edges
#> #
#> # CRS: EPSG:3035
#> #
#> # A directed multigraph with 16 components with spatially explicit edges
#> #
#> # Node Data: 597 x 2 (active)
#> # Geometry type: POINT
#> # Dimension: XY
#> # Bounding box: xmin: 4150707 ymin: 3206375 xmax: 4152367 ymax: 3208543
#> geometry cluster
#> <POINT [m]> <int>
#> 1 (4151491 3207923) 1
#> 2 (4151474 3207946) 2
#> 3 (4151398 3207777) 3
#> 4 (4151370 3207673) 4
#> 5 (4151408 3207539) 5
#> 6 (4151421 3207592) 6
#> # … with 591 more rows
#> #
#> # Edge Data: 641 x 5
#> # Geometry type: LINESTRING
#> # Dimension: XY
#> # Bounding box: xmin: 4150707 ymin: 3206375 xmax: 4152367 ymax: 3208543
#> from to name type geometry
#> <int> <int> <chr> <fct> <LINESTRING [m]>
#> 1 1 2 Havixbecker St… resident… (4151491 3207923, 4151474 3207946)
#> 2 3 4 Pienersallee secondary (4151398 3207777, 4151390 3207727, 4151…
#> 3 5 6 Schulte-Bernd-… resident… (4151408 3207539, 4151417 3207573, 4151…
#> # … with 638 more rows
# We can now contract nodes using the to_contracted morpher
# We use the cluster column to group the nodes, and all groups are contracted to a single node
# Edges are updated accordingly such that connectivity is preserved
G_new = G %>%
convert(to_contracted, cluster)
# When using the tidygraph morpher, the results needs some 'unpacking' to be useful
G_new
#> # A sfnetwork with 508 nodes and 553 edges
#> #
#> # CRS: EPSG:3035
#> #
#> # A directed simple graph with 16 components with spatially implicit edges
#> #
#> # Node Data: 508 x 4 (active)
#> # Geometry type: POINT
#> # Dimension: XY
#> # Bounding box: xmin: 4150716 ymin: 3206375 xmax: 4152367 ymax: 3208539
#> cluster geometry .orig_data .tidygraph_node_index
#> <int> <POINT [m]> <list> <list>
#> 1 1 (4151491 3207923) <tibble [1 × 1]> <int [1]>
#> 2 2 (4151474 3207946) <tibble [1 × 1]> <int [1]>
#> 3 3 (4151398 3207777) <tibble [1 × 1]> <int [1]>
#> 4 4 (4151370 3207673) <tibble [1 × 1]> <int [1]>
#> 5 5 (4151408 3207539) <tibble [1 × 1]> <int [1]>
#> 6 6 (4151421 3207592) <tibble [1 × 1]> <int [1]>
#> # … with 502 more rows
#> #
#> # Edge Data: 553 x 4
#> from to .tidygraph_edge_index .orig_data
#> <int> <int> <list> <list>
#> 1 1 2 <int [1]> <tibble [1 × 5]>
#> 2 1 472 <int [1]> <tibble [1 × 5]>
#> 3 2 14 <int [1]> <tibble [1 × 5]>
#> # … with 550 more rows
G_n = st_as_sf(G, "nodes")
G_e = st_as_sf(G, "edges")
G_new_n = st_as_sf(G_new, "nodes")
G_new_e = st_as_sf(do.call(rbind, pull(activate(G_new, "edges"), .orig_data)))
# Plot
p = ggplot() +
geom_sf(data = G_e, lwd = 2, color = "darkgrey") +
geom_sf(data = G_n, cex = 3) +
geom_sf(data = G_new_e, color = "red") +
geom_sf(data = G_new_n, color = "red")
p + coord_sf(xlim = c(4151510,4151540),ylim = c(3207550,3207620))
Created on 2020-12-24 by the reprex package (v0.3.0)
The spatial contraction is now implemented as a morpher in the new release, see this section in the morpher vignette for a general example, and this section in the cleaning vignette for an example focused specifically on simplifying intersections.
I prefer to stay with the contraction morpher and not implement something on top of that addressing only intersection simplification. The current structure gives a lot of freedom in choosing your own spatial clustering algorithm, and adding any other grouping variables you want to use.
The function is not super, super fast by the way. This is mainly because of st_centroid
, which seems to be a rather expensive computation. On Roxel it now runs in +/- 1.5 seconds.
Very often, osm networks (and networks in general)) contains edges that represent the different legs of a junction or roundabouts. When one comes to analyze a network, sometimes this level of detail is too much, as it fails to represent streets as a common person would define them. In order to solve this complexity, an intersection simplifying technique is commonly used. I know of intersection simplifiers in MATSim and OSMnx.
According to my understanding, the algorithm to simplify intersections is based on three main parts:
Here is a draft of parts 1 and 2:
Created on 2020-12-21 by the reprex package (v0.3.0)
Due to the fact that I do not have a deep understanding of the bolts and cogs of tidygraph and sfnetworks, I don't know how to implement the third part. Therefore, 3 questions: