diff --git a/NAMESPACE b/NAMESPACE index 82f7422a7..d0253d02c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -217,6 +217,9 @@ S3method(st_difference,sfc) S3method(st_difference,sfg) S3method(st_drop_geometry,default) S3method(st_drop_geometry,sf) +S3method(st_exterior_ring,sf) +S3method(st_exterior_ring,sfc) +S3method(st_exterior_ring,sfg) S3method(st_filter,sf) S3method(st_geometry,sf) S3method(st_geometry,sfc) @@ -447,6 +450,7 @@ export(st_drivers) export(st_drop_geometry) export(st_equals) export(st_equals_exact) +export(st_exterior_ring) export(st_filter) export(st_geometry) export(st_geometry_type) diff --git a/NEWS.md b/NEWS.md index 8c5019226..37294c37a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # version 1.0-17 +* add `st_exterior_ring()` to extract exterior rings (remove holes); #2406 + * add `text.sf()`, `text.sfc()`, `points.sf()`, `points.sfc()` to annotate base plots at geometry centroids; #2399 * `st_sf()` no longer strips `tbl` or `tbl_df` class labels; #2378 diff --git a/R/geom-transformers.R b/R/geom-transformers.R index 0149fffbd..af13a68e3 100644 --- a/R/geom-transformers.R +++ b/R/geom-transformers.R @@ -1149,3 +1149,29 @@ st_line_interpolate = function(line, dist, normalized = FALSE) { st_sfc(CPL_line_interpolate(recycled[[1]], recycled[[2]], normalized), crs = st_crs(line)) } + +#' @export +#' @name geos_unary +st_exterior_ring = function(x, ...) UseMethod("st_exterior_ring") + +#' @export +st_exterior_ring.sf = function(x, ...) + st_set_geometry(x, st_exterior_ring(st_geometry(x))) + +#' @export +st_exterior_ring.sfg = function(x, ...) + st_exterior_ring(st_sfc(x))[[1]] + +#' @export +st_exterior_ring.sfc = function(x, ...) { + stopifnot(all(st_dimension(x, NA_if_empty = FALSE) == 2)) + exterior_sfg = function(x) { + if (inherits(x, "MULTIPOLYGON")) + st_multipolygon(lapply(st_cast(st_sfc(x), "POLYGON"), exterior_sfg)) + else if (inherits(x, "POLYGON")) + st_polygon(x[1]) + else + stop(paste("no exterior_ring method for objects of class", class(x)[1])) + } + st_as_sfc(lapply(x, exterior_sfg), crs = st_crs(x)) +} diff --git a/man/geos_unary.Rd b/man/geos_unary.Rd index 2d9da346e..0041b1510 100644 --- a/man/geos_unary.Rd +++ b/man/geos_unary.Rd @@ -19,6 +19,7 @@ \alias{st_reverse} \alias{st_node} \alias{st_segmentize} +\alias{st_exterior_ring} \title{Geometric unary operations on simple feature geometry sets} \usage{ st_buffer( @@ -63,6 +64,8 @@ st_reverse(x) st_node(x) st_segmentize(x, dfMaxLength, ...) + +st_exterior_ring(x, ...) } \arguments{ \item{x}{object of class \code{sfg}, \code{sfc} or \code{sf}} diff --git a/tests/sfc.R b/tests/sfc.R index 36f5f2c5a..a0e082972 100644 --- a/tests/sfc.R +++ b/tests/sfc.R @@ -340,3 +340,21 @@ out = merge(x, y, all.x=TRUE) class(out) st_as_sf(st_sfc(st_point(0:1))) + +# st_exterior_ring(): +outer = matrix(c(0,0,10,0,10,10,0,10,0,0),ncol=2, byrow=TRUE) +hole1 = matrix(c(1,1,1,2,2,2,2,1,1,1),ncol=2, byrow=TRUE) +hole2 = matrix(c(5,5,5,6,6,6,6,5,5,5),ncol=2, byrow=TRUE) +pts = list(outer, hole1, hole2) +pl1 = st_polygon(pts) +mpl1 = st_multipolygon(list(pl1,pl1+20)) + +spl1 = st_as_sfc(list(pl1),crs=4326) +smpl1 = st_as_sfc(list(mpl1),crs=4326) + +st_exterior_ring(spl1[[1]]) +st_exterior_ring(spl1) +st_exterior_ring(st_sf(a = 1, geom = spl1)) +st_exterior_ring(smpl1[[1]]) +st_exterior_ring(st_sfc(smpl1)) +st_exterior_ring(st_sf(a = 1, geom = st_sfc(smpl1))) diff --git a/tests/sfc.Rout.save b/tests/sfc.Rout.save index 755923fee..39bfa293d 100644 --- a/tests/sfc.Rout.save +++ b/tests/sfc.Rout.save @@ -1,7 +1,7 @@ -R version 4.3.3 (2024-02-29) -- "Angel Food Cake" +R version 4.4.0 (2024-04-24) -- "Puppy Cup" Copyright (C) 2024 The R Foundation for Statistical Computing -Platform: x86_64-pc-linux-gnu (64-bit) +Platform: x86_64-pc-linux-gnu R is free software and comes with ABSOLUTELY NO WARRANTY. You are welcome to redistribute it under certain conditions. @@ -1086,6 +1086,52 @@ CRS: NA x 1 POINT (0 1) > +> # st_exterior_ring(): +> outer = matrix(c(0,0,10,0,10,10,0,10,0,0),ncol=2, byrow=TRUE) +> hole1 = matrix(c(1,1,1,2,2,2,2,1,1,1),ncol=2, byrow=TRUE) +> hole2 = matrix(c(5,5,5,6,6,6,6,5,5,5),ncol=2, byrow=TRUE) +> pts = list(outer, hole1, hole2) +> pl1 = st_polygon(pts) +> mpl1 = st_multipolygon(list(pl1,pl1+20)) +> +> spl1 = st_as_sfc(list(pl1),crs=4326) +> smpl1 = st_as_sfc(list(mpl1),crs=4326) +> +> st_exterior_ring(spl1[[1]]) +POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)) +> st_exterior_ring(spl1) +Geometry set for 1 feature +Geometry type: POLYGON +Dimension: XY +Bounding box: xmin: 0 ymin: 0 xmax: 10 ymax: 10 +Geodetic CRS: WGS 84 +POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)) +> st_exterior_ring(st_sf(a = 1, geom = spl1)) +Simple feature collection with 1 feature and 1 field +Geometry type: POLYGON +Dimension: XY +Bounding box: xmin: 0 ymin: 0 xmax: 10 ymax: 10 +Geodetic CRS: WGS 84 + a geom +1 1 POLYGON ((0 0, 10 0, 10 10,... +> st_exterior_ring(smpl1[[1]]) +MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)), ((20 20, 30 20, 30 30, 20 30, 20 20))) +> st_exterior_ring(st_sfc(smpl1)) +Geometry set for 1 feature +Geometry type: MULTIPOLYGON +Dimension: XY +Bounding box: xmin: 0 ymin: 0 xmax: 30 ymax: 30 +Geodetic CRS: WGS 84 +MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)), ... +> st_exterior_ring(st_sf(a = 1, geom = st_sfc(smpl1))) +Simple feature collection with 1 feature and 1 field +Geometry type: MULTIPOLYGON +Dimension: XY +Bounding box: xmin: 0 ymin: 0 xmax: 30 ymax: 30 +Geodetic CRS: WGS 84 + a geom +1 1 MULTIPOLYGON (((0 0, 10 0, ... +> > proc.time() user system elapsed - 5.156 1.454 5.144 + 5.477 1.381 5.407