Sf: spatstat <--> sf conversions

Created on 4 Jan 2020  Ā·  12Comments  Ā·  Source: r-spatial/sf

I tried to implement a number of sf -> spatstat conversions, and vice versa. A review by the spatstat team e.g. @rubak would be appreciated. I'm in particular not sure whether the observation windows for the psp objects make sense.

Here is a demo:

suppressPackageStartupMessages(library(spatstat))
suppressPackageStartupMessages(library(sf))

# png("/tmp/spa%03d.png")

p1 = st_point(0:1)
p2 = st_point(1:2)
p3 = st_point(c(-1,2))
p = st_sfc(p1, p2, p3)
as.ppp(p)
# Planar point pattern: 3 points
# window: rectangle = [-1, 1] x [1, 2] units
try(as.ppp(st_set_crs(p, 4326)))
# Error in as.ppp.sfc(st_set_crs(p, 4326)) : 
#   Only projected coordinates may be converted to spatstat class objects

sf = st_sf(geom = p)
try(as.ppp(sf))
# Planar point pattern: 3 points
# window: rectangle = [-1, 1] x [1, 2] units
sf = st_sf(a = 1:3, geom = p)
as.ppp(sf)
# Marked planar point pattern: 3 points
# marks are numeric, of storage type  ā€˜integer’
# window: rectangle = [-1, 1] x [1, 2] units
sf = st_sf(a = 1:3, b=3:1, geom = p)
as.ppp(sf) # warns
# Marked planar point pattern: 3 points
# marks are numeric, of storage type  ā€˜integer’
# window: rectangle = [-1, 1] x [1, 2] units
# Warning message:
# In as.ppp.sf(sf) : only first attribute column is used for marks

w = st_as_sfc(st_bbox(st_sfc(p1, p2)))
sf = st_sf(a = 1:3, geom = p)
(p0 = rbind(st_sf(a = 0, geom = w), sf))
# Simple feature collection with 4 features and 1 field
# geometry type:  GEOMETRY
# dimension:      XY
# bbox:           xmin: -1 ymin: 1 xmax: 1 ymax: 2
# epsg (SRID):    NA
# proj4string:    NA
#   a                           geom
# 1 0 POLYGON ((0 1, 1 1, 1 2, 0 ...
# 2 1                    POINT (0 1)
# 3 2                    POINT (1 2)
# 4 3                   POINT (-1 2)
try(as.ppp(p0)) # errors: one point outside window
# Error in `marks<-.ppp`(`*tmp*`, value = value) : 
#   number of rows of data frame != number of points
# In addition: Warning message:
# 1 point was rejected as lying outside the specified window 

w = st_as_sfc(st_bbox(p))
sf = st_sf(a = 1:3, geom = p)
(p0 = rbind(st_sf(a = 0, geom = w), sf))
# Simple feature collection with 4 features and 1 field
# geometry type:  GEOMETRY
# dimension:      XY
# bbox:           xmin: -1 ymin: 1 xmax: 1 ymax: 2
# epsg (SRID):    NA
# proj4string:    NA
#   a                           geom
# 1 0 POLYGON ((-1 1, 1 1, 1 2, -...
# 2 1                    POINT (0 1)
# 3 2                    POINT (1 2)
# 4 3                   POINT (-1 2)
as.ppp(p0)
# Marked planar point pattern: 3 points
# marks are numeric, of storage type  ā€˜double’
# window: polygonal boundary
# enclosing rectangle: [-1, 1] x [1, 2] units

library(stars)
# Loading required package: abind
tif = system.file("tif/L7_ETMs.tif", package = "stars")
s = adrop(read_stars(tif)[,,,1]) > 70
plot(s)

spa001

m = as.owin(s)
plot(m)

spa002

table(m$m)

# FALSE  TRUE 
# 39494 83354 

# as.owin.sf, as.owin.sfc_*
nc = st_read(system.file("gpkg/nc.gpkg", package="sf"))
# Reading layer `nc.gpkg' from data source `/home/edzer/R/x86_64-pc-linux-gnu-library/3.6/sf/gpkg/nc.gpkg' using driver `GPKG'
# Simple feature collection with 100 features and 14 fields
# geometry type:  MULTIPOLYGON
# dimension:      XY
# bbox:           xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
# epsg (SRID):    4267
# proj4string:    +proj=longlat +datum=NAD27 +no_defs
try(as.owin(nc)) # should be projected
# Error in as.owin.sfc_MULTIPOLYGON(st_geometry(W), ...) : 
#   Only projected coordinates may be converted to spatstat class objects
nc = st_transform(nc, 32119)
plot(as.owin(nc), col = 'grey')

spa003

plot(as.owin(st_geometry(nc)), col = 'grey')

spa004

sq = rbind(c(-1,-1), c(1, -1), c(1,1), c(-1,1), c(-1,-1))
pol = st_polygon(list(0.5 * sq, sq[5:1,] * 0.45)) # w hole
plot(as.owin(pol), col = 'grey')

spa005

plot(as.owin(st_sfc(pol)), col = 'grey')

spa006

mpol = st_multipolygon(list(
    list(sq, sq[5:1,] * 0.9),
    list(sq * 2, sq[5:1,] * 1.8)))
plot(as.owin(mpol), col = 'grey')

spa007

plot(as.owin(st_sfc(mpol)), col = 'grey')

spa008

plot(as.owin(st_sfc(pol, mpol)), col = 'grey')

spa009

plot(as.owin(st_sf(a=1:2, st_sfc(pol, mpol))), col = 'grey')

spa010

o = as.owin(st_sf(a=1:2, st_sfc(pol, mpol)))

st_as_sfc(o)[[1]]
# POLYGON ((2 2, -2 2, -2 -2, 2 -2, 2 2), (-1.8 -1.8, -1.8 1.8, 1.8 1.8, 1.8 -1.8, -1.8 -1.8))
st_as_sfc(o)[[2]]
# POLYGON ((1 1, -1 1, -1 -1, 1 -1, 1 1), (-0.9 -0.9, -0.9 0.9, 0.9 0.9, 0.9 -0.9, -0.9 -0.9))
st_as_sfc(o)[[3]]
# POLYGON ((0.5 0.5, -0.5 0.5, -0.5 -0.5, 0.5 -0.5, 0.5 0.5), (-0.45 -0.45, -0.45 0.45, 0.45 0.45, 0.45 -0.45, -0.45 -0.45))

plot(st_as_sfc(o), col = 'blue', main = 'st_as_sfc(o)')

spa011

plot(st_as_sf(o), col = 'blue', main = 'st_as_sf(o)')

spa012

data(nztrees)
qNZ <- quadratcount(nztrees, nx=4, ny=3)
ts = as.tess(qNZ)
plot(st_as_sfc(ts))

spa013

ls = st_linestring(rbind(c(0,0), c(1,1), c(2,0)))
plot(as.psp(ls))

spa014

mls = st_multilinestring(list(rbind(c(0,0), c(1,1), c(2,0)), rbind(c(3,3), c(4,2))))
plot(as.psp(mls))

spa015

plot(as.psp(st_sfc(ls)))

spa016

plot(as.psp(st_sfc(mls)))

spa017

plot(as.psp(st_sfc(ls, mls)))

spa018

All 12 comments

Note that this is done in a branch called "ppp".

This looks great @edzer! I will try to have a closer look this coming week.

EDIT:

I installed the development version of the {stars} package and it worked

end EDIT.

I am not able to replicate this code chunk:

suppressPackageStartupMessages(library(spatstat))
suppressPackageStartupMessages(library(sf)) # r-spatial/sf@ppp (ppp branch of the github repo)
library(stars)

tif = system.file("tif/L7_ETMs.tif", package = "stars")
s = adrop(read_stars(tif)[,,,1]) > 70
plot(s)
m = as.owin(s) # does not work
plot(m) # does not work (because previous line of code)

I get this error when I run m = as.owin(s): Error in as.owin(s) : could not find function "as.owin" . I installed the "ppp" branch of {sf} and the development version of {spatstat}.

I installed the ppp branch of the {sf} package; however, I am not able to run this code: st_read(dsn = system.file("gpkg/nc.gpkg", package="sf")). I get the following error:

Error in .Call("_sf_CPL_read_ogr", PACKAGE = "sf", datasource, layer,  : 
  Incorrect number of arguments (13), expecting 12 for '_sf_CPL_read_ogr'

@gueyenono for the stars problem, you need to install the github version of stars, branch master; for the nc reading problem: maybe remove sf altogether (by hand) and then reinstall it. If it persists, please give your devtools::sessions_info() output.

@gueyenono for the stars problem, you need to install the github version of stars, branch master; for the nc reading problem: maybe remove sf altogether (by hand) and then reinstall it. If it persists, please give your devtools::sessions_info() output.

Removing sf by hand and reinstalling it worked. Thank you.

I merged this branch now into master. Have you had time to look at it, @rubak ?

I have only started looking, but I have an example where a ppp with a polygonal owin with holes is mis-intepreted. Funny thing is that direct conversion of the owin works fine. Reproducible example:

suppressPackageStartupMessages(library(spatstat))
suppressPackageStartupMessages(library(sf))
W <- Window(gordon)
plot(W)

A point in a polygonal hole is correctly classified as ā€œoutsideā€ by spatstat:

inside.owin(4, -27, W)
#> [1] FALSE

Converting the owin directly to sf makes it into a single polygon with holes and
things work correctly:

W_sf <- st_as_sf(W)
W_sf
#> Simple feature collection with 1 feature and 0 fields
#> geometry type:  POLYGON
#> dimension:      XY
#> bbox:           xmin: -26.40848 ymin: -36.32095 xmax: 26.40848 ymax: 36.32095
#> epsg (SRID):    NA
#> proj4string:    NA
#>                             geom
#> 1 POLYGON ((22.0448 -14.20711...
p_sf <- st_point(matrix(c(4,-27),ncol=2))
st_contains(W_sf, p_sf) # Point not contained in polygon (correct)
#> Sparse geometry binary predicate list of length 1, where the predicate was `contains'
#>  1: (empty)

However if W is the window of a ppp (in this case empty, but same issue with points)
it is converted incorrectly:

X <- ppp(numeric(0), numeric(0), window = W)
W_sf2 <- st_as_sf(X)
W_sf2
#> Simple feature collection with 1 feature and 1 field
#> geometry type:  MULTIPOLYGON
#> dimension:      XY
#> bbox:           xmin: -26.40848 ymin: -36.32095 xmax: 26.40848 ymax: 36.32095
#> epsg (SRID):    NA
#> proj4string:    NA
#>    label                           geom
#> 1 window MULTIPOLYGON (((22.49851 -1...
st_contains(W_sf2, p_sf) # Point allegedly in polygon (wrong)
#> Sparse geometry binary predicate list of length 1, where the predicate was `contains'
#>  1: 1

Probably, the problem is due to the conversion of the polygon through edges here:

https://github.com/r-spatial/sf/blob/1e095d426487d47fc5b588f3cbd625c1047fa92e/R/spatstat.R#L1-L9

I assume the same problem is present when converting psp to sf. Maybe the conversion of owin to polygon could be inspired by maptools:
https://github.com/cran/maptools/blob/14a028b493b22969d03af5ed221b5fe992cc1b67/R/spatstat1.R#L69-L97

Just checked that the problem is the same for psp.

Now that I looked further into the code I see that the maptools code for polygons is already in st_as_sfc.owin, so we just need to use that code in st_as_sf.ppp and st_as_sf.psp, so the fix should be simple.

I also noticed there are some issues with multiple columns of marks (at least for psp -- seems OK for ppp)...

Thanks! I think st_as_sf.ppp and st_as_sf.psp now set the right window.

Should check_ring_dir (or similar) also be called in as.owin.POLYGON and as.owin.MULTIPOLYGON? Currently the following fails:

nc <- st_read(system.file("shape/nc.shp", package="sf"), quiet = TRUE)
spatstat::as.owin(nc$geometry[[1]])
#> Error in spatstat::owin(bb[c("xmin", "xmax")], bb[c("ymin", "ymax")],  : 
#>   Area of window is negative;
#>  check that all polygons were traversed in the right direction

Yes, that makes sense!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Nosferican picture Nosferican  Ā·  3Comments

kendonB picture kendonB  Ā·  3Comments

kendonB picture kendonB  Ā·  4Comments

Nowosad picture Nowosad  Ā·  3Comments

happyshows picture happyshows  Ā·  3Comments