Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The st_buffer() with singleSide = TRUE is inconsistent #2397

Open
Nowosad opened this issue May 14, 2024 · 9 comments
Open

The st_buffer() with singleSide = TRUE is inconsistent #2397

Nowosad opened this issue May 14, 2024 · 9 comments

Comments

@Nowosad
Copy link
Contributor

Nowosad commented May 14, 2024

Hi @edzer -- the st_buffer() with singleSide = TRUE works as expected on an example from https://postgis.net/docs/ST_Buffer.html, but differently on actual data. Is this an expected behavior?

library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.7.3, PROJ 9.2.1; sf_use_s2() is TRUE
# works as expected
p = st_as_sfc("POLYGON ((50 50, 50 150, 150 150, 150 50, 50 50))")
p_b = st_buffer(p, -20, singleSide = TRUE)
plot(p_b, col = "lightpink", border = "maroon", lwd = 6)
plot(p, border = "deepskyblue3", add = TRUE, lwd = 6)

# ?
nc = st_read(system.file("shape/nc.shp", package = "sf"))
#> Reading layer `nc' from data source 
#>   `/home/jn/R/x86_64-redhat-linux-gnu-library/4.3/sf/shape/nc.shp' 
#>   using driver `ESRI Shapefile'
#> Simple feature collection with 100 features and 14 fields
#> Geometry type: MULTIPOLYGON
#> Dimension:     XY
#> Bounding box:  xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
#> Geodetic CRS:  NAD27
nc_g = st_geometry(nc)
nc_g = st_transform(nc_g, "EPSG:9311")
nc_g1 = nc_g[1, ] |> st_cast("POLYGON")
nc_g1_b = st_buffer(nc_g1, -2000, singleSide = TRUE)

plot(nc_g1, border = "deepskyblue3", lwd = 6)
plot(nc_g1_b, col = "lightpink", add = TRUE, border = "maroon", lwd = 6)

@kadyb
Copy link
Contributor

kadyb commented May 15, 2024

Won't it be an issue related to GEOS? I get the same results in terra and geos, but strangely the first example in geos looks different (pink color is inside, not outside).

library("geos")
p = as_geos_geometry("POLYGON ((50 50, 50 150, 150 150, 150 50, 50 50))")
p_b = geos_buffer(p, -20, params = geos_buffer_params(single_sided = TRUE))
plot(p_b, col = "lightpink", border = "maroon", lwd = 6)
plot(p, border = "deepskyblue3", add = TRUE, lwd = 6)

Rplot

@edzer
Copy link
Member

edzer commented May 15, 2024

singleSide: logical; if ‘TRUE’, single-sided buffers are returned for
          linear geometries, in which case negative ‘dist’ values give
          buffers on the right-hand side, positive on the left; see
          details

So it should'nt do anything for polygons, really ;-) but if you take the polygon as a linestring you get a buffer on the right hand side, right?

p = st_as_sfc("POLYGON ((50 50, 50 150, 150 150, 150 50, 50 50))")
p_b = st_buffer(st_cast(p, "LINESTRING"), -20, singleSide = TRUE)
plot(p_b, col = "lightpink", border = "maroon", lwd = 6)
plot(p, border = "deepskyblue3", add = TRUE, lwd = 6)

@Nowosad
Copy link
Contributor Author

Nowosad commented May 15, 2024

@edzer see this:

library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.7.3, PROJ 9.2.1; sf_use_s2() is TRUE
nc = st_read(system.file("shape/nc.shp", package = "sf"))
nc_g = st_geometry(nc)
nc_g = st_transform(nc_g, "EPSG:9311")
nc_g1 = nc_g[1, ] |> st_cast("POLYGON") |> st_cast("LINESTRING")
nc_g1_b = st_buffer(nc_g1, -2000, singleSide = TRUE)

plot(nc_g1, col = "deepskyblue3", lwd = 6)
plot(nc_g1_b, col = "lightpink", add = TRUE, border = "maroon", lwd = 6)

@edzer
Copy link
Member

edzer commented May 15, 2024

It seems that the algorithm fails if the buffer self-intersects; this works:

library(sf)
# Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
nc = st_read(system.file("shape/nc.shp", package = "sf"))
# Reading layer `nc' from data source 
#   `/home/edzer/R/x86_64-pc-linux-gnu-library/4.4/sf/shape/nc.shp' 
#   using driver `ESRI Shapefile'
# Simple feature collection with 100 features and 14 fields
# Geometry type: MULTIPOLYGON
# Dimension:     XY
# Bounding box:  xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
# Geodetic CRS:  NAD27
nc_g = st_geometry(nc)
nc_g = st_transform(nc_g, "EPSG:9311")
nc_g1 = nc_g[1, ] |> st_cast("POLYGON") |> st_cast("LINESTRING")
x = nc_g1
x[[1]] = x[[1]][-(25:27),] # remove last 3 vertices
nc_g1[[1]] = structure(x[[1]], class = class(nc_g1[[1]]))
nc_g1_b = st_buffer(nc_g1, -2000, singleSide = TRUE)
plot(nc_g1, col = "deepskyblue3", lwd = 6)
plot(nc_g1_b, col = "lightpink", add = TRUE, border = "maroon", lwd = 6)

x

Definitely an issue of GEOS, not sf.

@Nowosad
Copy link
Contributor Author

Nowosad commented May 18, 2024

I was working on preparing an issue for GEOS, and then I found out that the geos package seems to return expected result.

@edzer what could make a difference here? Do you think this is still a GEOS problem?

library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.5, PROJ 9.3.1; sf_use_s2() is TRUE
nc = read_sf(system.file("shape/nc.shp", package = "sf"))
nc_g = st_geometry(nc)
nc_g = st_transform(nc_g, "EPSG:9311")
nc_g1 = nc_g[1, ] |> st_cast("POLYGON") |> st_cast("LINESTRING")

# sf
nc_g1_b = st_buffer(nc_g1, -2000, singleSide = TRUE)
plot(nc_g1, col = "deepskyblue3", lwd = 6)
plot(nc_g1_b, col = "lightpink", add = TRUE, border = "maroon", lwd = 6)

# geos
library(geos)
p = as_geos_geometry(nc_g1)
p_b = geos_buffer(p, -2000, params = geos_buffer_params(single_sided = TRUE))
plot(p_b, col = "lightpink", border = "maroon", lwd = 6)
plot(p, col = "deepskyblue3", add = TRUE, lwd = 6)

@edzer
Copy link
Member

edzer commented May 19, 2024

A possible cause outside the package code may be that geos uses another version of GEOS (3.11.1) than sf (3.12.1). The 3.12.0 release notes mention work on single sided buffers.

@mgageo
Copy link

mgageo commented Aug 19, 2024

I have a line with self-intersects and the geos left side is good but not the sf
`osm_sf <- function() {
library(sf)
library(tidyverse)
dsn <- "https://raw.githubusercontent.com/mgageo/issues/main/cote_droit_shape_bug.geojson"
ligne <- st_read(dsn) %>%
st_transform(2154) %>%
glimpse()
cote <- ligne %>%
st_buffer(-50, singleSide = TRUE, bOnlyEdges = FALSE)
plot(cote, col = "lightpink", border = "maroon", lwd = 6, main = "sf")
plot(ligne, col = "deepskyblue3", add = TRUE, lwd = 6)
}
osm_geos <- function() {
library(sf)
library(tidyverse)
library(geos)
dsn <- "https://raw.githubusercontent.com/mgageo/issues/main/cote_droit_shape_bug.geojson"
ligne <- st_read(dsn) %>%
st_transform(2154) %>%
glimpse()
p <- as_geos_geometry(ligne)
p_b <- geos_buffer(p, -50, params = geos_buffer_params(single_sided = TRUE))

plot(p_b, col = "lightpink", border = "maroon", lwd = 6, main = "geos")
plot(p, col = "deepskyblue3", add = TRUE, lwd = 6)
}
par(mfrow = c(1, 2))
osm_geos()
osm_sf()`
v2iTG4p

@mgageo
Copy link

mgageo commented Aug 20, 2024

A more simple exampe
` library(sf)
library(tidyverse)
par(mfrow = c(2, 2))
ligne <- st_linestring(rbind(c(0,0),c(0,10),c(10,10),c(0,10),c(0,20)))
p <- as_geos_geometry(ligne)
gauche <- ligne %>%
st_buffer(2, singleSide = TRUE, bOnlyEdges = FALSE)
plot(gauche, col = "lightpink", border = "maroon", lwd = 6, main = "gauche sf")
plot(ligne, col = "deepskyblue3", add = TRUE, lwd = 6)
droit <- ligne %>%
st_buffer(-2, singleSide = TRUE, bOnlyEdges = FALSE)
plot(droit, col = "lightpink", border = "maroon", lwd = 6, main = "droit sf")
plot(ligne, col = "deepskyblue3", add = TRUE, lwd = 6)

gauche <- geos_buffer(p, 2, params = geos_buffer_params(single_sided = TRUE))
plot(gauche, col = "lightpink", border = "maroon", lwd = 6, main = "gauche geos")
plot(ligne, col = "deepskyblue3", add = TRUE, lwd = 6)
droit <- geos_buffer(p, -2, params = geos_buffer_params(single_sided = TRUE))
plot(droit, col = "lightpink", border = "maroon", lwd = 6, main = "droit geos")
plot(ligne, col = "deepskyblue3", add = TRUE, lwd = 6)
`
image

@mgageo
Copy link

mgageo commented Aug 23, 2024

postgis produces the same result as sf, geos version : 3.12.2-CAPI-1.18.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants