Skip to contents

Provides functionality to group lines that form naturally continuous lines in a spatial network. The algorithm implemented is based on the Continuity in Street Networks (COINS) method doi:10.1177/2399808320967680, which identifies continuous "strokes" in the network as the line strings that maximize the angles between consecutive segments.

Usage

stroke(
  edges,
  angle_threshold = 0,
  attributes = FALSE,
  flow_mode = FALSE,
  from_edge = NULL
)

Arguments

edges

An object of class sfc (or compatible), including the network edge geometries (should be of type LINESTRING).

angle_threshold

Consecutive line segments can be considered part of the same stroke if the internal angle they form is larger than angle_threshold (in degrees). It should fall in the range 0 <= angle_threshold < 180.

attributes

If TRUE, return a label for each edge, representing the groups each edge belongs to. Only possible for flow_mode = TRUE.

flow_mode

If TRUE, line segments that belong to the same edge are not split across strokes (even if they form internal angles smaller than angle_threshold).

from_edge

Only look for the continuous strokes that include the provided edges or line segments.

Value

An object of class sfc (if attributes = FALSE), a vector with the same length as edges otherwise.

Examples

library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.4.0; sf_use_s2() is TRUE

# Setup a simple network

p1 <- st_point(c(0, 3))
p2 <- st_point(c(2, 1))
p3 <- st_point(c(3, 0))
p4 <- st_point(c(1, 4))
p5 <- st_point(c(3, 2))
p6 <- st_point(c(4, 1))
p7 <- st_point(c(4, 3))
p8 <- st_point(c(5, 3))

l1 <- st_linestring(c(p1, p2, p5))
l2 <- st_linestring(c(p2, p3))
l3 <- st_linestring(c(p4, p5))
l4 <- st_linestring(c(p5, p6))
l5 <- st_linestring(c(p5, p7))
l6 <- st_linestring(c(p7, p8))

network_edges <- st_sfc(l1, l2, l3, l4, l5, l6)

# Identify strokes in the full network with default settings
stroke(network_edges)
#> Geometry set for 3 features 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 0 ymin: 0 xmax: 5 ymax: 4
#> CRS:           NA
#> LINESTRING (0 3, 2 1, 3 0)
#> LINESTRING (2 1, 3 2, 4 3, 5 3)
#> LINESTRING (1 4, 3 2, 4 1)

# Set a threshold to the angle between consecutive segments
stroke(network_edges, angle_threshold = 150)
#> Geometry set for 4 features 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 0 ymin: 0 xmax: 5 ymax: 4
#> CRS:           NA
#> LINESTRING (0 3, 2 1, 3 0)
#> LINESTRING (2 1, 3 2, 4 3)
#> LINESTRING (1 4, 3 2, 4 1)
#> LINESTRING (4 3, 5 3)

# Identify strokes in flow mode (do not break initial edges)
stroke(network_edges, flow_mode = TRUE)
#> Geometry set for 3 features 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 0 ymin: 0 xmax: 5 ymax: 4
#> CRS:           NA
#> LINESTRING (0 3, 2 1, 3 2, 4 3, 5 3)
#> LINESTRING (2 1, 3 0)
#> LINESTRING (1 4, 3 2, 4 1)

# Instead of returning stroke geometries, return stroke labels
stroke(network_edges, flow_mode = TRUE, attributes = TRUE)
#> [1] 1 2 3 3 1 1

# Identify strokes that continue one (or a subset) of edges
stroke(network_edges, from_edge = 2)
#> Geometry set for 1 feature 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 0 ymin: 0 xmax: 3 ymax: 3
#> CRS:           NA
#> LINESTRING (0 3, 2 1, 3 0)
stroke(network_edges, from_edge = c(2, 3))
#> Geometry set for 2 features 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 0 ymin: 0 xmax: 4 ymax: 4
#> CRS:           NA
#> LINESTRING (0 3, 2 1, 3 0)
#> LINESTRING (1 4, 3 2, 4 1)