From 05bad13d26866913a2d2ca396d606f4981afa394 Mon Sep 17 00:00:00 2001 From: Anirudh Govind Date: Tue, 12 Mar 2024 06:14:23 +0100 Subject: [PATCH] add: proximity bands --- NAMESPACE | 1 + R/st_create_proximitybands.R | 115 +++++++++++++++++ R/st_create_tessellations.R | 118 +++++++++++------- _pkgdown.yml | 9 +- man/st_create_proximitybands.Rd | 62 +++++++++ .../testthat/test-st_create_proximitybands.R | 20 +++ 6 files changed, 276 insertions(+), 49 deletions(-) create mode 100644 R/st_create_proximitybands.R create mode 100644 man/st_create_proximitybands.Rd create mode 100644 tests/testthat/test-st_create_proximitybands.R diff --git a/NAMESPACE b/NAMESPACE index 6d4b244..bfe9afe 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,7 @@ # Generated by roxygen2: do not edit by hand export(add_unique_id) +export(st_create_proximitybands) export(st_create_streetblocks) export(st_create_tessellations) export(st_explode) diff --git a/R/st_create_proximitybands.R b/R/st_create_proximitybands.R new file mode 100644 index 0000000..4e6ed7f --- /dev/null +++ b/R/st_create_proximitybands.R @@ -0,0 +1,115 @@ +#' Create proximity band spatial units. +#' +#' @param x an `sf` object with `LINESTRING` geometries representing a street +#' network. +#' @param band_width numeric; width of the band as measured from the centerline +#' of the `LINESTRING` representing the street. +#' @param segment_length numeric; Length of segments street linestrings are +#' subdivided into. Default = 2. +#' @param merge_threshold numeric; value represents the smallest acceptable +#' size for a spatial unit. Contiguous units will be iteratively merged until +#' this value is reached. To skip this process, set `merge_threshold = NULL`. +#' See st_merge_spatialunits() for more details. +#' @param merge_type string; Passed on to st_merge_spatialunits(). +#' Criteria with which polygons are merged. Must be one of +#' "min_centroid_distance", "min_shared_boundary", or "max_shared_boundary". +#' Default = "min_centroid_distance". +#' See st_merge_spatialunits() for more details. +#' @param contiguity string; one of "queen" or "rook". Default = "rook". +#' @param verbose logical; if `FALSE` no status messages will be output. +#' +#' @return An `sf` object with `POLYGON` geometries representing tessellated +#' spatial units. +#' @seealso st_merge_spatialunits, [moter::motess()] +#' @references Araldi, A., & Fusco, G. (2019). From the street to the +#' metropolitan region: Pedestrian perspective in urban fabric analysis. +#' Environment and Planning B: Urban Analytics and City Science, 46(7), +#' 1243–1263. https://doi.org/10.1177/2399808319832612 +#' @export +#' +#' @examples +#' proximity_bands <- st_create_proximitybands(x = bangalore_highways, +#' verbose = FALSE, merge_threshold = NULL) +#' plot(proximity_bands) +st_create_proximitybands <- function(x, + band_width = 10, + segment_length = 2, + merge_threshold = 1015, + merge_type = "min_centroid_distance", + contiguity = "rook", + verbose = T) { + + # Turn off s2 geometry + + sf::sf_use_s2(FALSE) + + # Check polygons (if present) and linestrings data + + if (sf::st_is_longlat(x) == TRUE) { + # Keep track of original CRS + + x_crs <- sf::st_crs(x) + + # Reproject to 3857 + + x <- sf::st_transform(x, + 3857) + + } + # Process line strings + + x <- process_linestrings(x) + + x <- sf::st_sf(x) + + # Add IDs to the linestrings + + x <- add_unique_id(x) + + # Draw buffers around the street network + + x_buffer <- sf::st_buffer(x, + band_width) + + x_buffer <- sf::st_union(x_buffer) + + voronoi_polygons <- segment_and_tessellate(x = x, + segment_length = segment_length) + + # Clip vor polygons to the drawn buffer + + suppressMessages(suppressWarnings( + x_prox_band <- sf::st_intersection(voronoi_polygons, + x_buffer) + )) + + if (verbose == T) { + message(paste0(nrow(x_prox_band)," proximity bands created.")) + } + + if (is.null(merge_threshold)) { + warning( + "Small geometries (< 1 m^2) may be present. Use st_merge_spatialunits() to aggregate them." + ) + } + + if (!is.null(merge_threshold)) { + x_prox_band <- st_merge_spatialunits( + x = x_prox_band, + merge_threshold = merge_threshold, + merge_type = merge_type, + contiguity = contiguity, + verbose = verbose + ) + } + + # If geometry is long lat, convert CRS back to the original. + + if (sf::st_is_longlat(x) == TRUE) { + x_prox_band <- sf::st_transform(x_prox_band, + x_crs) + } + + return(x_prox_band) + +} diff --git a/R/st_create_tessellations.R b/R/st_create_tessellations.R index 947b483..63001df 100644 --- a/R/st_create_tessellations.R +++ b/R/st_create_tessellations.R @@ -46,14 +46,16 @@ st_create_tessellations <- contiguity = "rook", verbose = T) { + + # Init vars used further down + + temp_id <- geometry <- NULL + # Turn off s2 geometry sf::sf_use_s2(FALSE) if (type == "morphological") { - # Init vars used further down - - temp_id <- geometry <- NULL # Check data types. @@ -87,49 +89,10 @@ st_create_tessellations <- x <- sf::st_buffer(x, dist = (-1 * shrink_extent)) - # Slice into segments - - segments <- sf::st_segmentize(x, segment_length) - - # Extract points from the segments - - suppressMessages(suppressWarnings(points <- - sf::st_cast(segments, - "POINT"))) - - # Remove duplicates - - points_unique <- unique(points) - - # Draw Voronoi Polygons - - voronoi_polygons <- - sf::st_collection_extract(sf::st_voronoi(sf::st_union(points_unique))) - - # Dissolve polygons + # Segment these lines and tessellate them - voronoi_polygons <- - voronoi_polygons[unlist(sf::st_intersects(points_unique, - voronoi_polygons))] - - # Join points to the polygons - - voronoi_polygons <- sf::st_join(sf::st_sf(voronoi_polygons), - points_unique) - - # Make geometry valid - - voronoi_polygons <- sf::st_make_valid(voronoi_polygons) - - voronoi_polygons <- sf::st_buffer(voronoi_polygons, - 0) - - # Merge polygons - - suppressMessages(suppressWarnings(voronoi_polygons <- voronoi_polygons |> - dplyr::group_by(temp_id) |> - dplyr::summarise(geometry = sf::st_union(geometry)) |> - dplyr::ungroup())) + voronoi_polygons <- segment_and_tessellate(x = x, + segment_length = segment_length) # Clip to boundary @@ -180,3 +143,68 @@ st_create_tessellations <- } } + +#' Segment `LINESTRING` or `POLYGON` geometries and create Voronoi tessellations. +#' +#' @param x a `sf` object with `LINESTRING` or `POLYGON` geometries. +#' +#' @return A `sf` object with Voronoi tessellations +#' @keywords internal +#' @noRd +segment_and_tessellate <- function(x, segment_length) { + + # Init vars used further down + + temp_id <- geometry <- NULL + + # Slice into segments + + segments <- sf::st_segmentize(x, segment_length) + + # Extract points from the segments + + suppressMessages(suppressWarnings(points <- + sf::st_cast(segments, + "POINT"))) + + # Remove duplicates + + points_unique <- unique(points) + + # Draw Voronoi Polygons + + voronoi_polygons <- + sf::st_collection_extract(sf::st_voronoi(sf::st_union(points_unique))) + + # Dissolve polygons + + voronoi_polygons <- + voronoi_polygons[unlist(sf::st_intersects(points_unique, + voronoi_polygons))] + + # Join points to the polygons + + voronoi_polygons <- sf::st_join(sf::st_sf(voronoi_polygons), + points_unique) + + # Make geometry valid + + voronoi_polygons <- sf::st_make_valid(voronoi_polygons) + + voronoi_polygons <- sf::st_buffer(voronoi_polygons, + 0) + + # Merge polygons + + suppressMessages( + suppressWarnings( + voronoi_polygons <- voronoi_polygons |> + dplyr::group_by(temp_id) |> + dplyr::summarise(geometry = sf::st_union(geometry)) |> + dplyr::ungroup() + ) + ) + + return(voronoi_polygons) + +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 10d0604..7da2797 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -3,23 +3,24 @@ template: bootstrap: 5 reference: -- subtitle: Extraction +- subtitle: Extract & Process Data contents: - st_explode - st_extract_connected - st_extract_disconnected - st_extract_intersections -- subtitle: Spatial Units +- subtitle: Create Spatial Units contents: + - st_create_proximitybands - st_create_streetblocks - st_create_tessellations - - st_merge_spatialunits - - st_survey_spatialunits - subtitle: Miscellaneous contents: - add_unique_id + - st_merge_spatialunits + - st_survey_spatialunits - subtitle: Datasets contents: diff --git a/man/st_create_proximitybands.Rd b/man/st_create_proximitybands.Rd new file mode 100644 index 0000000..7babfb5 --- /dev/null +++ b/man/st_create_proximitybands.Rd @@ -0,0 +1,62 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/st_create_proximitybands.R +\name{st_create_proximitybands} +\alias{st_create_proximitybands} +\title{Create proximity band spatial units.} +\usage{ +st_create_proximitybands( + x, + band_width = 10, + segment_length = 2, + merge_threshold = 1015, + merge_type = "min_centroid_distance", + contiguity = "rook", + verbose = T +) +} +\arguments{ +\item{x}{an \code{sf} object with \code{LINESTRING} geometries representing a street +network.} + +\item{band_width}{numeric; width of the band as measured from the centerline +of the \code{LINESTRING} representing the street.} + +\item{segment_length}{numeric; Length of segments street linestrings are +subdivided into. Default = 2.} + +\item{merge_threshold}{numeric; value represents the smallest acceptable +size for a spatial unit. Contiguous units will be iteratively merged until +this value is reached. To skip this process, set \code{merge_threshold = NULL}. +See st_merge_spatialunits() for more details.} + +\item{merge_type}{string; Passed on to st_merge_spatialunits(). +Criteria with which polygons are merged. Must be one of +"min_centroid_distance", "min_shared_boundary", or "max_shared_boundary". +Default = "min_centroid_distance". +See st_merge_spatialunits() for more details.} + +\item{contiguity}{string; one of "queen" or "rook". Default = "rook".} + +\item{verbose}{logical; if \code{FALSE} no status messages will be output.} +} +\value{ +An \code{sf} object with \code{POLYGON} geometries representing tessellated +spatial units. +} +\description{ +Create proximity band spatial units. +} +\examples{ +proximity_bands <- st_create_proximitybands(x = bangalore_highways, +verbose = FALSE, merge_threshold = NULL) +plot(proximity_bands) +} +\references{ +Araldi, A., & Fusco, G. (2019). From the street to the +metropolitan region: Pedestrian perspective in urban fabric analysis. +Environment and Planning B: Urban Analytics and City Science, 46(7), +1243–1263. https://doi.org/10.1177/2399808319832612 +} +\seealso{ +st_merge_spatialunits, \code{\link[moter:motess]{moter::motess()}} +} diff --git a/tests/testthat/test-st_create_proximitybands.R b/tests/testthat/test-st_create_proximitybands.R new file mode 100644 index 0000000..0994a74 --- /dev/null +++ b/tests/testthat/test-st_create_proximitybands.R @@ -0,0 +1,20 @@ +test_that("st_create_proximitybands() fails on non `sf` objects", { + expect_error( + st_create_proximitybands( + x = 42, + merge_threshold = NULL, + verbose = FALSE + ) + ) +}) + +test_that("st_create_proximitybands() fails on non-linestring objects passed to x", + { + expect_error( + st_create_tessellations( + x = bangalore_buildings, + merge_threshold = NULL, + verbose = FALSE + ) + ) + })