21

Is there a way to remove holes from a polygon in R with the package sf? I would be interested in solutions that include other packages, too. Here's an example of a polygon with two holes.

library(sf)
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))
# POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1),(5 5, 5 6, 6 6, 6 5, 5 5))    

Here's the figure:

plot(pl1, col="red")

plot(pl1, col="red")

Duccio A
  • 1,402
  • 13
  • 27

4 Answers4

23

Package nngeo introduced a function to do this after this question was answered by @lbusett (and references him in the description, well done).

So you can use:

nngeo::st_remove_holes(your_sf_object)

See https://rdrr.io/cran/nngeo/man/st_remove_holes.html

AF7
  • 3,160
  • 28
  • 63
  • Great solution! Thanks – nd091680 Feb 10 '22 at 21:29
  • I've been trying to find a replacement for rgeos::gUnaryUnion and I finally figured out that I needed to use sf::aggregate() and then nngeo::st_remove_holes() to get the equivalent of what gUnaryUnion() is doing (but soon to be deprecated) – Mac471 Aug 30 '23 at 04:14
12

Following https://github.com/r-spatial/sf/issues/609#issuecomment-357426716, this could work:

library(sf)
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_geometry(st_polygon(pts))

plot(pl1)

pl2 <- st_multipolygon(lapply(pl1, function(x) x[1]))
plot(pl2)

Created on 2018-10-05 by the reprex package (v0.2.1)

lbusett
  • 5,801
  • 2
  • 24
  • 47
  • How could I apply this to `shapefiles` objects? (I mean, remove holes within a `shapefile`) – noriega Feb 26 '19 at 14:38
  • @noriega You can load the sahpefile into R with `sf::st_read("your_shapefile.shp")` and use the method suggested by Ibusett. – Duccio A Jun 13 '19 at 08:11
  • 1
    Because of the `pl1[1]`, this creates `length(pl1)` (i.e., 3) copies of the first polygon - was that intended? – jbaums Aug 16 '19 at 05:21
  • 1
    @jbaums You are right. That was a typo (now corrected). Also note that I had to add a `st_geometry` call on `pl1` for this to work (a `sfc_POLYGON` object is needed) – lbusett Aug 19 '19 at 08:05
  • Also, the function reported by @AF7 seems nice and much more general, so I'd advice users to have a look. – lbusett Aug 19 '19 at 08:06
9

sfheaders::sf_remove_holes() can also do this

sfheaders::sf_remove_holes(pl1)
POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))
SymbolixAU
  • 25,502
  • 4
  • 67
  • 139
1

Perhaps the fill_holes() function of the smoothr package might work as well.

Link: https://strimas.com/smoothr/reference/fill_holes.html

Demo:

# fill holes smaller than 1000km2
p <- jagged_polygons$geometry[5]
area_thresh <- units::set_units(1000, km^2)
p_dropped <- fill_holes(p, threshold = area_thresh)
# plot
par(mar = c(0, 0, 1, 0), mfrow = c(1, 2))
plot(p, col = "black", main = "Original")
plot(p_dropped, col = "black", main = "After fill_holes()")

enter image description here

MS Berends
  • 4,489
  • 1
  • 40
  • 53