[view raw Rmd]

Summary

This post introduces the POLYGON FULL geometry, new in sf 1.0-18 which appeared on CRAN Today.

Why?

Polygons delineate a two-dimensional bound area. The OGC simple feature access standard defines besides the regular POLYGON and MULTIPOLYGON (a set of polygons) also the POLYGON EMPTY and MULTIPOLYGON EMPTY, which can both be thought of as “no points”. There is however nothing polygonal about “no points”, you can read more about this in Even Rouault’s blog entry.

Although the simple feature standard does not explicitly state this, it was clearly developed for flat, planar geometries. The Earth however is round, and doing geometrical operations e.g. on the two-dimensional surface of a sphere changes many things. One of them is that the total, entire surface is also a bound area. To denote that area, since sf 1.0-18 we can now use POLYGON FULL.

How?

As of 2020, R package sf uses the R packgae s2 and the s2geometry library for computations on geometries with geodetic (unprojected) coordinates, unless it is told not to do so (and assume a flat Earth) by

library(sf)

## Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.4.0; sf_use_s2() is TRUE

sf_use_s2(FALSE)

## Spherical geometry (s2) switched off

The s2geomety library uses the concept of a POLYGON FULL, and represents it internally by POLYGON((0 -90, 0 -90)). Package sf also does this:

sf_use_s2(TRUE)

## Spherical geometry (s2) switched on

(p = st_as_sfc("POLYGON FULL", crs = 'OGC:CRS84'))

## Geometry set for 1 feature 
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: -180 ymin: -90 xmax: 180 ymax: 90
## Geodetic CRS:  WGS 84 (CRS84)

## POLYGON FULL

If s2 is switched off, this geometry is not recognized as a POLYGON FULL but instead is printed as the POLYGON((0 -90, 0 -90)):

sf_use_s2(FALSE)

## Spherical geometry (s2) switched off

p

## Geometry set for 1 feature 
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: -180 ymin: -90 xmax: 180 ymax: 90
## Geodetic CRS:  WGS 84 (CRS84)

## POLYGON ((0 -90, 0 -90))

which obviously is not a valid polygon

st_is_valid(p)

## [1] NA

and will lead to errors when used, e.g. in

st_make_valid(p)

## Error in scan(text = lst[[length(lst)]], quiet = TRUE): scan() expected 'a real', got 'IllegalArgumentException:'

## Error in (function (msg) : IllegalArgumentException: Invalid number of points in LinearRing found 2 - must be 0 or >= 3

When using s2, it works nicely:

sf_use_s2(TRUE)

## Spherical geometry (s2) switched on

st_is_full(p)

## [1] TRUE

st_is_empty(p)

## [1] FALSE

st_is_valid(p)

## [1] TRUE

pt = st_as_sfc("POINT(7 52)", crs = 'OGC:CRS84')
st_intersects(p, pt)

## Sparse geometry binary predicate list of length 1, where the predicate
## was `intersects'
##  1: 1

st_intersection(p, pt)

## Geometry set for 1 feature 
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 7 ymin: 52 xmax: 7 ymax: 52
## Geodetic CRS:  WGS 84 (CRS84)

## POINT (7 52)

st_distance(p, pt)

## Units: [m]
##      [,1]
## [1,]    0

st_area(p) |> units::set_units(km^2) # spherical approximation

## 510066073 [km^2]

st_bbox(p)

## xmin ymin xmax ymax 
## -180  -90  180   90

Examples

A more full example is described in this vignette, yielding this figure:

options(s2_oriented = TRUE) # don't change orientation from here on
co = st_as_sf(s2::s2_data_countries())
g = st_as_sfc("POLYGON FULL", crs = 'EPSG:4326')
oc = st_difference(g, st_union(co)) # oceans
b = st_buffer(st_as_sfc("POINT(-30 52)", crs = 'EPSG:4326'), 9800000) # visible half
i = st_intersection(b, oc) # visible ocean
plot(st_transform(i, "+proj=ortho +lat_0=52 +lon_0=-30"), col = 'blue')

Some more discussion leading to the current implementation is found in this sf issue. Comments are welcome in this issue!