4

I am trying to shrink an sf polygon by a fixed distance.

Take this simple polygon:

coords <- list(rbind(c(0,0), c(2,0), c(2,2), c(1,2), c(1,1), c(0,1), c(0,0)))
p <- sf::st_polygon(x = coords)
ggplot() + geom_sf(data = p,  fill = "white", color = "black")

enter image description here

I want to make a polygon of the same shape, but with edges all exactly 0.1 units away from the original polygon. This is the target:

target.coords <- list(rbind(c(0.1,0.1), c(1.9,0.1), c(1.9,1.9), c(1.1,1.9), c(1.1,0.9), c(0.1,0.9), c(0.1,0.1)))
target <- sf::st_polygon(x = target.coords)
ggplot() + 
  geom_sf(data = p,  fill = "white", color = "black") +
  geom_sf(data = target,  fill = "green", color = "dark green")

enter image description here

I thought that sf::st_buffer() would get me there, but the outside corner doesn't quite match.

p1 <- sf::st_buffer(x = p, dist = -0.1)
ggplot() +
  geom_sf(data = p,  fill = "white", color = "black") + 
  geom_sf(data = p1, fill = "red",   color = "dark red")

enter image description here

Is there an alternative setting to sf::st_buffer() that would allow me to achieve the target polygon? Or is there an alternative function that would achieve this? The offset needs to be by a fixed distance, not by shrinking the polygon a relative amount (e.g. 90%).

Kevin
  • 491
  • 3
  • 10
  • 1
    You _could_ do an affine transformation while preserving the exact shape (= the shrinking by 90% you explicitly ruled out). But the dark red inside polygon does IMHO behave as should be expected: the round curve around the outside corner describes all points that are 1/10 of an unit distance from the original point. The inside corner of your green polygon is actually 1 sqrt(2) farther than that... – Jindra Lacko Jun 16 '21 at 16:59
  • @JindraLacko I was noticing that about the corner as well, although then I'm curious why the convex corners are square (and therefore have a distance of sqrt(2) from the white corners) but the concave one isn't – camille Jun 16 '21 at 17:08
  • This isn't specific to R, but a previous post looks at this type of algorithm in general and might help https://stackoverflow.com/q/1109536/5325862 – camille Jun 16 '21 at 17:25
  • @camille the convex points have it easy in a negative buffer; there are two sides to measure the distance from, so no problem. They would give trouble in a positive buffer scenario / which would be easy peasy with concave points. So kind of pick what you want, but you can't have it easy both ways :) – Jindra Lacko Jun 16 '21 at 17:44

1 Answers1

7

If the problem is just related to that corner, I think you can change the default behaviour of st_buffer using the arguments joinStyle and mitreLimit. For example:

# packages
library(sf)
#> Linking to GEOS 3.9.0, GDAL 3.2.1, PROJ 7.2.1
library(ggplot2)

# data
coords <- list(rbind(c(0,0), c(2,0), c(2,2), c(1,2), c(1,1), c(0,1), c(0,0)))
p <- sf::st_polygon(x = coords)
p1 <- st_buffer(x = p, dist = -0.1, joinStyle  = "MITRE", mitreLimit = 2)

# plot
ggplot() +
  geom_sf(data = p,  fill = "white", color = "black") + 
  geom_sf(data = p1, fill = "red",   color = "dark red")

# Check 
target.coords <- list(rbind(c(0.1,0.1), c(1.9,0.1), c(1.9,1.9), c(1.1,1.9), c(1.1,0.9), c(0.1,0.9), c(0.1,0.1)))
(target <- sf::st_polygon(x = target.coords))
#> POLYGON ((0.1 0.1, 1.9 0.1, 1.9 1.9, 1.1 1.9, 1.1 0.9, 0.1 0.9, 0.1 0.1))
st_equals(p1, target)
#> Sparse geometry binary predicate list of length 1, where the predicate was `equals'
#>  1: 1

Created on 2021-06-17 by the reprex package (v2.0.0)

See also here for more examples with st_buffer().

agila
  • 3,289
  • 2
  • 9
  • 20