Sf: How do we select features with a particular geometry type?

Created on 8 Jan 2017  路  12Comments  路  Source: r-spatial/sf

To the st_cast team @mdsumner and @etiennebr : (how) can we select e.g. the one polygon feature geometry created from:

> g = st_makegrid(n=c(2,2), offset = c(0,0), cellsize = c(2,2))
> s = st_sfc(st_polygon(list(rbind(c(1,1), c(2,1),c(2,2),c(1,2),c(1,1)))))
> i = st_intersection(st_sf(a=1:4, geom = g), st_sf(b = 2, geom = s))
Warning message:
In st_intersection(st_sf(a = 1:4, geom = g), st_sf(b = 2, geom = s)) :
  attribute variables are assumed to be spatially constant throughout all geometries
> i
Simple feature collection with 4 features and 2 fields
geometry type:  GEOMETRY
dimension:      XY
bbox:           xmin: 1 ymin: 1 xmax: 2 ymax: 2
epsg (SRID):    NA
proj4string:    NA
  a b                       geometry
1 1 2 POLYGON((2 2, 2 1, 1 1, 1 2...
2 2 2           LINESTRING(2 2, 2 1)
3 3 2           LINESTRING(1 2, 2 2)
4 4 2                     POINT(2 2)

Can we do this in a pipe, by providing a select_ method, or do we need a st_select?

Most helpful comment

OK, I went with st_is:

st_is = function(x, type) UseMethod("st_is")

st_is.sf = function(x, type)
    st_is(st_geometry(x), type)

st_is.sfc = function(x, type)
    vapply(x, sf:::st_is.sfg, type, FUN.VALUE = logical(1))

st_is.sfg = function(x, type)
    class(x)[2L] %in% type

I'm using is because

  • we want to be able to check on multiple types, st_is(x, c("POINT", "MULTIPOINT"))
  • for sf objects, we check on the geometry of the complete record, where the whole record doesn't have a class of its own

All 12 comments

You mean filter_ (I make the same mistake quite often).

Here with a liberal test for either multi or single polygon:

i %>% filter(grepl("POLYGON", st_geometry_type(geometry)))
Simple feature collection with 1 feature and 2 fields
geometry type:  GEOMETRY
dimension:      XY
bbox:           xmin: 1 ymin: 1 xmax: 2 ymax: 2
epsg (SRID):    NA
proj4string:    NA
  a b                       geometry
1 1 2 POLYGON((2 2, 2 1, 1 1, 1 2...

Thanks, I meant filter indeed.

I see. Is that, like, OK enough? Similar (and better to my taste, although that doesn't mean anything):

i %>% filter(
    st_geometry_type(.)
    %in% c("POLYGON", "POINT") )

I don't have a strong opinion really, I know there's a lot of ways and mine is pretty ugly. The beauty is the evaluation going on in the general filter, possibly after mutate() etc.

Oh, but I didn't know about that use of the dot, nice!

This is a really great point. It's a neat example of interaction between sf and dplyr. I think your solution is good; I'd prefer to access a higher level interface, like st_is_geometrytype() or st_is_type() (don't know about the name).

i %>% filter(st_is_type(geometry, c("POLYGON", "POINT"))
#'  or
i %>% filter(st_is_type(. , c("POLYGON", "POINT"))

I suggest st_is:

i %>% filter(st_is(. , "POINT")
i %>% filter(st_is(. , c("POLYGON", "POINT"))

@hadley you want to chime in?

Looks good. How to make it easy to specify e.g. both multi* and single in an easy way ?
Like st_is(sfc, "*polygon") (and allow lowercase)?

both the * and the lower case don't shine in obviousness, IMO. There's also

i %>% filter(st_dimension(.) == 2)

I think you're right that you want a vector function that takes either an sf or an sfc (I personally would prefer to write filter(i, st_is(geometry, "POINT")) and returns a logical vector. That will work with dplyr and base R, and is composable in other nice ways.

I don't have strong feelings about the function name. I do think you should keep the implementation as simple as possible, i.e. something like this:

st_is <- function(x, type) UseMethod("st_is")

st_is.sf <- function(x, type) {
  st_is(x[[sf::st_geometry(x)]], type)
}

st_is.sfc <- function(x, type) {
  vapply(x, inherits, type, FUN.VALUE = logical(1))
}

That possibly suggests the name should be st_inherits()

OK, I went with st_is:

st_is = function(x, type) UseMethod("st_is")

st_is.sf = function(x, type)
    st_is(st_geometry(x), type)

st_is.sfc = function(x, type)
    vapply(x, sf:::st_is.sfg, type, FUN.VALUE = logical(1))

st_is.sfg = function(x, type)
    class(x)[2L] %in% type

I'm using is because

  • we want to be able to check on multiple types, st_is(x, c("POINT", "MULTIPOINT"))
  • for sf objects, we check on the geometry of the complete record, where the whole record doesn't have a class of its own

I think it's a mistake to not base it on inherits(), which already has the behaviour you want, and doesn't use an arbitrary constant:

inherits(Sys.Date(), c("POSIXct", "Date"))
#> [1] TRUE
inherits(Sys.time(), c("POSIXct", "Date"))
#> [1] TRUE

Thanks - you're right, I got confused.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

adrfantini picture adrfantini  路  4Comments

jsta picture jsta  路  4Comments

thiagoveloso picture thiagoveloso  路  3Comments

Nosferican picture Nosferican  路  3Comments

happyshows picture happyshows  路  3Comments