From 8baa6f8aed1841cff8daa5206bf5c988a260b808 Mon Sep 17 00:00:00 2001 From: Anirudh Govind Date: Sun, 11 Feb 2024 19:48:41 +0100 Subject: [PATCH] add: new merge type in st_merge_spatialunits add: plots in st_surveyspatialunits fix: logic issues in st_create_tessellations --- DESCRIPTION | 1 + R/st_create_tessellations.R | 14 ++++-- R/st_merge_spatialunits.R | 51 +++++++++++++++++-- R/st_survey_spatialunits.R | 69 +++++++++++++++++++++----- man/st_create_tessellations.Rd | 4 +- man/st_merge_spatialunits.Rd | 2 +- man/st_survey_spatialunits.Rd | 6 ++- vignettes/articles/1-Spatial-Units.Rmd | 2 +- 8 files changed, 121 insertions(+), 28 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f44f2e9..d3dabd8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,6 +14,7 @@ URL: https://github.com/anirudhgovind/multi, BugReports: https://github.com/anirudhgovind/multi/issues Imports: dplyr (>= 1.0.9), + graphics, lwgeom (>= 0.2.8), sf (>= 1.0.8), sfdep, diff --git a/R/st_create_tessellations.R b/R/st_create_tessellations.R index 54dea13..947b483 100644 --- a/R/st_create_tessellations.R +++ b/R/st_create_tessellations.R @@ -11,7 +11,7 @@ #' 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. +#' @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". @@ -38,7 +38,7 @@ st_create_tessellations <- function(x, boundary, - type = c("morphological"), + type = "morphological", segment_length = 0.5, shrink_extent = 0.4, merge_threshold = NULL, @@ -164,10 +164,14 @@ st_create_tessellations <- ) } - # Convert CRS back to original + # If geometry is long lat, convert CRS back to the original. - tessellations <- sf::st_transform(tessellations, - x_crs) + if (sf::st_is_longlat(x) == TRUE | + sf::st_is_longlat(boundary) == TRUE) { + + tessellations <- sf::st_transform(tessellations, + x_crs) + } return(tessellations) } else { diff --git a/R/st_merge_spatialunits.R b/R/st_merge_spatialunits.R index 31f8403..949351c 100644 --- a/R/st_merge_spatialunits.R +++ b/R/st_merge_spatialunits.R @@ -7,7 +7,7 @@ #' iteratively merged until this value is reached. To skip this process, #' set `merge_threshold = NULL`. #' @param merge_type string; criteria with which polygons are merged. Must be one of -#' "min_centroid_distance", "min_shared_boundary", or "max_shared_boundary". +#' "min_centroid_distance", "min_adjacent_area", "max_adjacent_area", "min_shared_boundary", or "max_shared_boundary". #' Default = "min_centroid_distance". #' @param contiguity string; one of "queen" or "rook". Default = "rook". #' @param verbose logical; if `FALSE` no status messages will be output. @@ -43,11 +43,15 @@ st_merge_spatialunits <- function(x, # Check type is one of defined defaults - if (!merge_type %in% c("min_centroid_distance", - "min_shared_boundary", - "max_shared_boundary")) { + if (!merge_type %in% c( + "min_centroid_distance", + "min_adjacent_area", + "max_adjacent_area", + "min_shared_boundary", + "max_shared_boundary" + )) { stop( - "merge_type must be one of 'min_centroid_distance', 'min_shared_boundary', or 'max_shared_boundary'." + "merge_type must be one of 'min_centroid_distance', 'min_adjacent_area', 'max_adjacent_area', 'min_shared_boundary', or 'max_shared_boundary'." ) } @@ -112,6 +116,13 @@ st_merge_spatialunits <- function(x, if (merge_type == "min_centroid_distance") { selected_unit <- min_centroid_distance(ref_unit = ref_unit, ref_unit_neighbours_geo = ref_unit_neighbours_geo) + } else if (merge_type == "min_adjacent_area" | + merge_type == "max_adjacent_area") { + selected_unit <- area_adjacency( + ref_unit = ref_unit, + ref_unit_neighbours_geo = ref_unit_neighbours_geo, + merge_type = merge_type + ) } else if (merge_type == "min_shared_boundary" | merge_type == "max_shared_boundary") { selected_unit <- shared_boundary_length( @@ -166,6 +177,36 @@ st_merge_spatialunits <- function(x, return(x) } +# Function to merge polygon with an adjacent polygon with the smallest/ largest area + +area_adjacency <- function(ref_unit, + ref_unit_neighbours_geo, + merge_type = merge_type) { + # Init Var + + areas <- NULL + + # Find areas of the polygons + areas <- sf::st_area(ref_unit_neighbours_geo) + + # If min area + if (merge_type == "min_adjacent_area") { + smallest_area <- + as.integer(ref_unit_neighbours_geo$index[which.min(areas)]) + + return(smallest_area) + } + + # If max area + if (merge_type == "max_adjacent_area") { + largest_area <- + as.integer(ref_unit_neighbours_geo$index[which.max(areas)]) + + return(largest_area) + } + +} + # Function to select polygon based on minimum centroid distance min_centroid_distance <- function(ref_unit, diff --git a/R/st_survey_spatialunits.R b/R/st_survey_spatialunits.R index 0afe547..baef165 100644 --- a/R/st_survey_spatialunits.R +++ b/R/st_survey_spatialunits.R @@ -3,7 +3,8 @@ #' @param x an `sf` object with `POLYGON` geometries representing the #' spatial units to be surveyed. #' @param trim numeric; fraction (0 to 0.5) of values to be trimmed from the lower -#' end to compute a trimmed mean +#' end to compute a trimmed mean. +#' @param viz binary; Displays a scatterplot of area distributions. #' #' @return A `data.frame` object with numeric values of min, max, mean, and #' median. @@ -15,7 +16,13 @@ #' survey_report <- st_survey_spatialunits(x = street_blocks) #' survey_report st_survey_spatialunits <- function(x, - trim = NULL) { + trim = NULL, + viz = TRUE) { + + # Init var for later + + areas <- NULL + # Check x is of type sf if (!inherits(x, "sf")) { @@ -25,7 +32,8 @@ st_survey_spatialunits <- function(x, # Check geometry types if (all(sf::st_is(x, - "POLYGON") != TRUE)) { + c("POLYGON", + "MULTIPOLYGON")) != TRUE)) { stop("x must contain `POLYGON` geometries.") } @@ -42,10 +50,11 @@ st_survey_spatialunits <- function(x, # Survey Result - output <- data.frame(category = c("Area", - "Area", - "Area", - "Area"), + output <- data.frame( + category = c("Area", + "Area", + "Area", + "Area"), description = c("Min", "Max", "Mean", @@ -67,16 +76,52 @@ st_survey_spatialunits <- function(x, dplyr::slice_tail(prop = 1 - trim) |> dplyr::summarise(value = mean(areas)) - suffix <- data.frame(category = c("Area"), - description = c(paste0("Trimmed Mean (", - trim, - ")")), - value = c(x_trim)) + suffix <- data.frame( + category = c("Area"), + description = c(paste0("Trimmed Mean (", + trim, + ")")), + value = c(x_trim) + ) output <- rbind(output, suffix) } } + + if (viz == TRUE) { + plot(x$areas, + col = "gray", + xlab = "Spatial Unit Index", + ylab = "Areas") + graphics::abline(h = c(x_min, + x_max, + x_mean, + x_median), + lty = 2) + graphics::text( + x = c(0, + nrow(x), + 0, + nrow(x)), + y = c(x_min, + x_max, + x_mean, + x_median), + labels = c("min", + "max", + "mean", + "median") + ) + if (!is.null(trim)) { + graphics::abline(h = x_trim, + col = "black") + graphics::text(x = nrow(x) / 2, + y = x_trim, + labels = "trimmed mean") + } + } + print(output, right = FALSE) } diff --git a/man/st_create_tessellations.Rd b/man/st_create_tessellations.Rd index 3cdc9de..affb626 100644 --- a/man/st_create_tessellations.Rd +++ b/man/st_create_tessellations.Rd @@ -7,7 +7,7 @@ st_create_tessellations( x, boundary, - type = c("morphological"), + type = "morphological", segment_length = 0.5, shrink_extent = 0.4, merge_threshold = NULL, @@ -37,7 +37,7 @@ 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. +\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". diff --git a/man/st_merge_spatialunits.Rd b/man/st_merge_spatialunits.Rd index 00f256a..00413b8 100644 --- a/man/st_merge_spatialunits.Rd +++ b/man/st_merge_spatialunits.Rd @@ -22,7 +22,7 @@ iteratively merged until this value is reached. To skip this process, set \code{merge_threshold = NULL}.} \item{merge_type}{string; criteria with which polygons are merged. Must be one of -"min_centroid_distance", "min_shared_boundary", or "max_shared_boundary". +"min_centroid_distance", "min_adjacent_area", "max_adjacent_area", "min_shared_boundary", or "max_shared_boundary". Default = "min_centroid_distance".} \item{contiguity}{string; one of "queen" or "rook". Default = "rook".} diff --git a/man/st_survey_spatialunits.Rd b/man/st_survey_spatialunits.Rd index aa9093e..75260d6 100644 --- a/man/st_survey_spatialunits.Rd +++ b/man/st_survey_spatialunits.Rd @@ -4,14 +4,16 @@ \alias{st_survey_spatialunits} \title{Survey a set of spatial units to get summary statistics.} \usage{ -st_survey_spatialunits(x, trim = NULL) +st_survey_spatialunits(x, trim = NULL, viz = TRUE) } \arguments{ \item{x}{an \code{sf} object with \code{POLYGON} geometries representing the spatial units to be surveyed.} \item{trim}{numeric; fraction (0 to 0.5) of values to be trimmed from the lower -end to compute a trimmed mean} +end to compute a trimmed mean.} + +\item{viz}{binary; Displays a scatterplot of area distributions.} } \value{ A \code{data.frame} object with numeric values of min, max, mean, and diff --git a/vignettes/articles/1-Spatial-Units.Rmd b/vignettes/articles/1-Spatial-Units.Rmd index 95319d8..c452df4 100644 --- a/vignettes/articles/1-Spatial-Units.Rmd +++ b/vignettes/articles/1-Spatial-Units.Rmd @@ -21,7 +21,7 @@ tmap::tmap_options(check.and.fix = TRUE, show.warnings = FALSE) ``` -`multi` simplifies the process of creating morphological spatial units like street blocks and morphological tessellation units (MTs; Fleischmann et al., 2020). This vignette presents one workflow for creating street blocks and MTs using data from OpenStreetMap (using the excellent `osmdata` package; Padgham et al., 2022) with Bangalore, India as a case. +`multi` simplifies the process of creating morphological spatial units like street blocks and morphological tessellation units (MTs; Fleischmann et al., 2020). This vignette presents one workflow for creating street blocks and MTs using data from OpenStreetMap (sourced using the excellent `osmdata` package; Padgham et al., 2022) with Bangalore, India as a case. ## Street Blocks