-
Notifications
You must be signed in to change notification settings - Fork 300
Description
Describe the bug
When using st_union
on two "upwards" facing polygons (i.e. in CCW order), the resulting polygon is changed so that it faces downwards (CW).
From section 6.1.11.1 of version 1.2.1 for the SF specification from https://www.ogc.org/standards/sfa
The exterior boundary LinearRing defines the “top” of the surface which is the
side of the surface from which the exterior boundary appears to traverse the
boundary in a counter clockwise direction. The interior LinearRings will
have the opposite orientation, and appear as clockwise when viewed from the “top”,
To Reproduce
# sf 1.0-9 and 1.0-10
# Plain polygon coordinates, CCW order
coord <- matrix(c(0, 1, 1, 0, 0, 0, 0, 1, 1, 0),5,2)
coord
# [,1] [,2]
# [1,] 0 0
# [2,] 1 0
# [3,] 1 1
# [4,] 0 1
# [5,] 0 0
# Create polygon
pl <- sf::st_polygon(list(coord))
# ok, still CCW:
pl
# POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))
# ok, still CCW:
sf::st_union(pl)
# POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))
# incorrect, CW:
sf::st_union(pl, pl)
# POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))
Additional context
I've been working on conversion methods between sf classes and fmesher mesh storage classes in https://github.com/inlabru-org/inlabru/ (including INLA and the coming fmesher package that will take over the geometry handling from the INLA package), and was encountering problems when testing polygons with holes as well as conversion to and from sp, and traced at least one of the issues to this st_union
inconsistency. The fmesher code handles general 3D triangles as well as a boundary segment specification that in 2D can store the complement of regular polygons (i.e. "free holes"), by using the a CCW traversion convention just as the quoted sf standard above (and sometimes without mandating closed sequences; we make use of non-closed "proto boundary" segments as well). In cases where we need to apply st_* methods in geospatial contexts we rely on ordering being handled consistently, so that we don't have to do all the special handling that was needed for sp objects with the ringDir
issues etc.
I had previously discovered that the sp
conversion method in st_as_sfc.SpatialPolygons
is broken in sf 1.0-9 for multi-polygons (in a set of disjoint polygons all but the first polygons were removed by the conversion, as the "make_valid" code removed them, presumably for believing them to be holes, and reversing the ordering of the first polygon. That bug appears to have been fixed in 1.0-10 (at least it doesn't remove polygons) so I haven't filed an Issue for that problem. The related issues/discussion I recall seeing about sp/sf conversion issues focused on geometries read from existing files, but since fmesher is a meshing library, we are the ones creating the geometries, so need to know that we can rely on well-defined behaviour.
Session info
> sessionInfo()
R version 4.2.2 (2022-10-31)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 22.04.1 LTS
Matrix products: default
BLAS: /home/flindgre/local/R-4.2.2/lib/R/lib/libRblas.so
LAPACK: /home/flindgre/local/R-4.2.2/lib/R/lib/libRlapack.so
locale:
[1] LC_CTYPE=en_GB.UTF-8 LC_NUMERIC=C LC_TIME=en_GB.UTF-8 LC_COLLATE=en_GB.UTF-8
[5] LC_MONETARY=en_GB.UTF-8 LC_MESSAGES=en_GB.UTF-8 LC_PAPER=en_GB.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] sf_1.0-10 reprex_2.0.2 usethis_2.1.6
loaded via a namespace (and not attached):
[1] Rcpp_1.0.10 rstudioapi_0.14 knitr_1.42 magrittr_2.0.3 units_0.8-1 tidyselect_1.2.0
[7] prompt_1.0.1 here_1.0.1 R6_2.5.1 rlang_1.0.6 fansi_1.0.4 dplyr_1.1.0
[13] tools_4.2.2 grid_4.2.2 xfun_0.37 utf8_1.2.3 KernSmooth_2.23-20 cli_3.6.0
[19] e1071_1.7-13 DBI_1.1.3 withr_2.5.0 class_7.3-21 rprojroot_2.0.3 tibble_3.1.8
[25] lifecycle_1.0.3 purrr_1.0.1 vctrs_0.5.2 fs_1.6.0 glue_1.6.2 proxy_0.4-27
[31] compiler_4.2.2 pillar_1.8.1 generics_0.1.3 classInt_0.4-8 pkgconfig_2.0.3
> sf::sf_extSoftVersion()
GEOS GDAL proj.4 GDAL_with_GEOS USE_PROJ_H PROJ
"3.10.2" "3.4.1" "8.2.1" "true" "true" "8.2.1"