Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

r - Convert output from sf::st_within to vector

Im trying to use the sf package in R to see if sf object is within another sf object with the st_within function. My issue is with the output of this function which is sparse geometry binary predicate - sgbp and I need a vector as an output so that I can use the dplyr package afterwards for filtering. Here is a simplified example:

# object 1: I will test if it is inside object 2
df <- data.frame(lon = c(2.5, 3, 3.5), lat = c(2.5, 3, 3.5), var = 1) %>% 
st_as_sf(coords = c("lon", "lat"), dim = "XY") %>% st_set_crs(4326) %>%
  summarise(var = sum(var), do_union = F) %>% st_cast("LINESTRING")

# object 2: I will test if it contains object 1
box <- data.frame(lon = c(2, 4, 4, 2, 2), lat = c(2, 2, 4, 4,2), var = 1) %>%
  st_as_sf(coords = c("lon", "lat"), dim = "XY") %>% st_set_crs(4326) %>% 
  summarise(var = sum(var), do_union = F) %>% st_cast("POLYGON")

# test 1
df$indicator <- st_within(df$geometry, box$geometry) # gives geometric binary predicate on pairs of sf sets which cannot be used 
df <- df %>% filter(indicator == 1)

This gives Error: Column indicator must be a 1d atomic vector or a list.

I tried solving this problem below:

# test 2
df$indicator <- st_within(df$geometry, box$geometry, sparse = F) %>% 
  diag() # gives matrix that I convert with diag() into vector
df <- df %>% filter(indicator == FALSE)

This works, it removes the row that contains TRUE values but the process of making a matrix is very slow for my calculations since my real data contains many observations. Is there a way to make the output of st_within a character vector, or maybe a way to convert sgbp to a character vector compatible with dplyr without making a matrix?

like image 764
adl Avatar asked Mar 15 '18 08:03

adl


People also ask

What is SF in R?

sf: Simple Features for R Support for simple features, a standardized way to encode spatial vector data. Binds to 'GDAL' for reading and writing data, to 'GEOS' for geometrical operations, and to 'PROJ' for projection conversions and datum transformations.

What is SF class in R?

sf: objects with simple features frame row, consisting of attributes and geometry. in blue a single simple feature geometry (an object of class sfg ) in red a simple feature list-column (an object of class sfc , which is a column in the data. frame )

What is SFC polygon?

1 Solarflarecoin is 0.000127 Polygon. So, you've converted 1 Solarflarecoin to 0.000127 Polygon. We used 7904.162 International Currency Exchange Rate.


3 Answers

Here is how you can get a logical vector from sparse geometry binary predicate:

df$indicator <- st_within(df, box) %>% lengths > 0

or to subset without creating a new variable:

df <- df[st_within(df, box) %>% lengths > 0,]

I cannot test on your large dataset unfortunately but please let me know if it is faster than matrix approach.

like image 165
sebdalgarno Avatar answered Oct 19 '22 16:10

sebdalgarno


The result of is_within is in truth a list column, so you can work your way out of this by "unlisting" it. Something like this would work:

library(dplyr)
library(sf)

# object 1: I will test if it is inside object 2 - to make this more interesting
# I added a second not-contained line
df <- data.frame(lon = c(2.5, 3, 3.5), lat = c(2.5, 3, 3.5), var = 1) %>% 
  st_as_sf(coords = c("lon", "lat"), dim = "XY") %>% st_set_crs(4326) %>%
  summarise(var = sum(var), do_union = F) %>% st_cast("LINESTRING")

df2 <- data.frame(lon = c(4.5, 5, 6), lat = c(4.5, 5, 6), var = 2) %>% 
  st_as_sf(coords = c("lon", "lat"), dim = "XY") %>% st_set_crs(4326) %>%
  summarise(var = sum(var), do_union = F) %>% st_cast("LINESTRING")
df3 <- rbind(df, df2)

# object 2: I will test if it contains object 1
box <- data.frame(lon = c(2, 4, 4, 2, 2), lat = c(2, 2, 4, 4,2), var = 1) %>%
  st_as_sf(coords = c("lon", "lat"), dim = "XY") %>% st_set_crs(4326) %>% 
  summarise(var = sum(var), do_union = F) %>% st_cast("POLYGON")

plot(df3) 
plot(st_geometry(box), add = TRUE)

# see if the lines are within the box and build a data frame with results
is_within <- st_within(df3$geometry, box$geometry) %>% 
  lapply(FUN = function(x) data.frame(ind = length(x))) %>% 
  bind_rows()

# add the "indicator" to df3
df3 <- dplyr::mutate(df3, indicator = is_within$ind) 
df3
#> Simple feature collection with 2 features and 2 fields
#> geometry type:  LINESTRING
#> dimension:      XY
#> bbox:           xmin: 2.5 ymin: 2.5 xmax: 6 ymax: 6
#> epsg (SRID):    4326
#> proj4string:    +proj=longlat +datum=WGS84 +no_defs
#>   var indicator                       geometry
#> 1   3         1 LINESTRING (2.5 2.5, 3 3, 3...
#> 2   6         0 LINESTRING (4.5 4.5, 5 5, 6 6)

HTH

Created on 2018-03-15 by the reprex package (v0.2.0).

like image 33
lbusett Avatar answered Oct 19 '22 16:10

lbusett


Instead of using the st_within function directly, try using a spatial join. Check out the following example how st_joins works

library(sf)
library(tidyverse)

lines <-
data.frame(id=gl(3,2), x=c(-3,2,6,11,7,10), y=c(-1,6,-5,-9,10,5)) %>%
  st_as_sf(coords=c("x","y"), remove=F) %>% 
  group_by(id) %>% 
  summarise() %>%
  st_cast("LINESTRING")

yta10 <-
    st_point(c(0, 0)) %>%
    st_buffer(dist = 10) %>%
    st_sfc() %>%
    st_sf(yta = "10m")

With a left join all lines are kept, but you can see which of them that are located inside the polygon

lines %>% st_join(yta10, left=TRUE)

An inner join (left = FALSE) only keeps the ones inside

lines %>% st_join(yta10, left=FALSE)

The latter can also be obtained by

lines[yta10,]
like image 26
Hans Gardfjell Avatar answered Oct 19 '22 14:10

Hans Gardfjell