R/eliminate_slivers.R
eliminate_slivers.RdEmulates the ArcGIS Eliminate tool (the LENGTH option): small "sliver"
polygons are removed by merging each into the neighbouring polygon with
which it shares the longest border. This is the geometrically-sensible way
to clean up the slivers left behind when two polygon layers that do not
align (e.g. Landscape Units vs. Natural Resource Districts) are intersected,
and is preferable to area-thresholding crumbs away (see intersect_clean()).
eliminate_slivers(
x,
threshold,
keep = NULL,
explode = TRUE,
boundary = TRUE,
overlap = FALSE,
distance = TRUE,
minover = 0.05,
maxdist = 1,
append = TRUE
)
desliver(
x,
threshold,
keep = NULL,
explode = TRUE,
boundary = TRUE,
overlap = FALSE,
distance = TRUE,
minover = 0.05,
maxdist = 1,
append = TRUE
)an sf or SpatVector polygons object. A projected CRS is
expected, so areas and border lengths are metric; warns otherwise.
sliver-area cutoff. Either a units object (e.g.
units::set_units(1, "ha")) or a bare numeric in square metres. Polygons
with area <= threshold are candidate slivers.
optional <data-masking> predicate
evaluated against x's attribute table; features for which it is TRUE
are never treated as slivers, even when below threshold. Column names
are used directly. Defaults to NULL (slivers by area alone). See
examples.
if TRUE (the default), multipart polygons are split to
single part before sliver detection, so threshold applies to each
individual ring. When TRUE, prefer a column-based keep predicate over
an external vector, since exploding changes the number/order of features.
passed through to
terra::combineGeoms(). The defaults implement the LENGTH rule:
boundary = TRUE assigns each sliver to the keeper it shares the longest
border with; overlap = FALSE disables overlap-area precedence;
distance (within maxdist map units) and append = TRUE only affect
slivers that share no border with any keeper (the latter keeps them rather
than dropping them). combineGeoms() always uses dissolve/erase TRUE.
an object of the same class as x, with slivers eliminated. If x
has no slivers it is returned unchanged; if every feature is a sliver a
warning is issued and x is returned unchanged (so the call is safe in a
pipe).
The merge is delegated to terra::combineGeoms(); the defaults here
(boundary = TRUE, overlap = FALSE) give pure longest-shared-border
assignment. distance/maxdist only come into play for slivers that share
no edge with any keeper (e.g. they touch at a point, or sit in a small gap).
x is returned as the same class it was passed in as ("return the type
that was passed"): an sf object in yields an sf object out, a
SpatVector in yields a SpatVector out. Internally the work is done with
terra.
Slivers are merged into keepers, so the attributes of the keeper are
retained and a sliver's own attributes are discarded (as in ArcGIS
Eliminate). Use keep to protect features that fall below threshold
but should never be dissolved away.
library(sf)
#> Linking to GEOS 3.12.2, GDAL 3.11.4, PROJ 9.4.1; sf_use_s2() is TRUE
sq <- function(xmin, xmax, ymin, ymax, id) {
p <- st_polygon(list(rbind(
c(xmin, ymin), c(xmax, ymin), c(xmax, ymax), c(xmin, ymax), c(xmin, ymin)
)))
st_sf(id = id, protected = id == "B", geometry = st_sfc(p, crs = 3005))
}
polys <- rbind(
sq(0, 5, 0, 5, "A"), # 25; shares a length-5 border with the sliver
sq(0, 1, 6, 16, "B"), # 10; shares a length-1 border with the sliver
sq(0, 5, 5, 6, "S") # 5; the sliver
)
## merge polygons < 6 (map units^2) into their longest-border neighbour:
## "S" is absorbed by "A" (border length 5 > 1); "B" is untouched.
eliminate_slivers(polys, threshold = 6)
#> Simple feature collection with 2 features and 2 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 0 ymin: 0 xmax: 5 ymax: 16
#> Projected CRS: NAD83 / BC Albers
#> id protected geometry
#> 1 A FALSE POLYGON ((5 0, 0 0, 0 5, 0 ...
#> 2 B TRUE POLYGON ((0 6, 1 6, 1 16, 0...
## a units threshold works too (areas here are tiny, shown for syntax):
eliminate_slivers(polys, threshold = units::set_units(6, "m^2"))
#> Simple feature collection with 2 features and 2 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 0 ymin: 0 xmax: 5 ymax: 16
#> Projected CRS: NAD83 / BC Albers
#> id protected geometry
#> 1 A FALSE POLYGON ((5 0, 0 0, 0 5, 0 ...
#> 2 B TRUE POLYGON ((0 6, 1 6, 1 16, 0...
## protect features via a data-masking predicate on the attribute table
## ("S" is the only sliver here, but this is how you keep a small feature):
eliminate_slivers(polys, threshold = 6, keep = protected)
#> Simple feature collection with 2 features and 2 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 0 ymin: 0 xmax: 5 ymax: 16
#> Projected CRS: NAD83 / BC Albers
#> id protected geometry
#> 1 A FALSE POLYGON ((5 0, 0 0, 0 5, 0 ...
#> 2 B TRUE POLYGON ((0 6, 1 6, 1 16, 0...
eliminate_slivers(polys, threshold = 6, keep = id == "S")
#> Simple feature collection with 3 features and 2 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 0 ymin: 0 xmax: 5 ymax: 16
#> Projected CRS: NAD83 / BC Albers
#> id protected geometry
#> 1 A FALSE POLYGON ((0 0, 5 0, 5 5, 0 ...
#> 2 B TRUE POLYGON ((0 6, 1 6, 1 16, 0...
#> 3 S FALSE POLYGON ((0 5, 5 5, 5 6, 0 ...