waiguoren
waiguoren

Reputation: 53

Replacing specific sf point geometry fails

When attempting to replace a set of points within an sf sfc column, I'm receiving a few kinds of errors. I've tried a few things using both dplyr and base methods, each of which lead to errors. The one solution I've found so far is to completely replace the sfc geometry column, which seems like a suboptimal solution.

In my particular example, I have four points, two of which share the same ID in column 'a'. I'm attempting to update the geometry for points having ID 17 from (11,12) to (12,13). For illustration, I also try making updates to geometries for ID 16, updating that from (0,1) to (5,6).

library(tidyverse)
library(sf)
#> Linking to GEOS 3.6.1, GDAL 2.2.3, PROJ 4.9.3

geom = st_sfc(st_point(c(0,1)), st_point(c(0,1)), st_point(c(11,12)),st_point(c(11,12)))
a_col = c(15,16,17,17)
sf = st_sf(a = a_col, geometry = geom)

#Approach 1. doesn't work for a== 17
sf2 <- 
  sf %>%
  mutate(geometry = case_when(a==17 ~ st_sfc(st_point(c(12,13))),
                              TRUE ~ geometry))
#> Error in mutate_impl(.data, dots): Evaluation error: must be sfc_GEOMETRY/sfc, not sfc_POINT/sfc.
#Approach 1 also doesn't work for a==16
sf2 <- 
  sf %>%
  mutate(geometry = case_when(a==16 ~ st_sfc(st_point(c(5,6))),
                              TRUE ~ geometry))
#> Error in mutate_impl(.data, dots): Evaluation error: must be sfc_GEOMETRY/sfc, not sfc_POINT/sfc.

#Approach 2. This works when column a value is unique...
sf2 <- sf
st_geometry(sf2[sf2$a ==16,]) <- st_sfc(st_point(c(5,6)))

#but fails when column a value is not unique.
sf2 <- sf
st_geometry(sf2[sf2$a ==17,]) <- st_sfc(st_point(c(12,13)))
#> Error in `st_geometry<-.sf`(`*tmp*`, value = structure(list(structure(c(12, : nrow(x) == length(value) is not TRUE

#Approach 3. could wholesale replace the geometry sfc column, but seems like there's a better way
geom2 <- geom
geom2[sf$a==17] <- st_point(c(12,13))
sf2 = st_sf(a = a_col, geometry = geom2)

As you can see, Approach 1 (dplyr::case_when and dplyr::mutate) fails-- this would be my preferred approach, as I work in the tidyverse style and have other conditions and logic to apply in this particular problem. Approach 2 (replacing at st_geometry) works when the ID is unique, but fails when it is not. Approach 3 works, but for processing time, code readability, and other reasons, would not be preferred (note: sourced from this SO question Replace geometries from a list in sf )

Any recommended solutions, especially along the lines of Approach 1 - dplyr::case_when ?

Upvotes: 2

Views: 1464

Answers (2)

Spacedman
Spacedman

Reputation: 94277

If you want to do this without loading in the entirety of the tidyverse, or even the bits of it that you really need (please don't use library(tidyverse), just get the bits you really need in future), then this variant of your last attempt works:

> st_geometry(sf2)[sf2$a==17] = st_point(c(12,13))
> sf2
Simple feature collection with 4 features and 1 field
geometry type:  POINT
dimension:      XY
bbox:           xmin: 0 ymin: 1 xmax: 11 ymax: 12
epsg (SRID):    NA
proj4string:    NA
   a      geometry
1 15   POINT (0 1)
2 16   POINT (0 1)
3 17 POINT (12 13)
4 17 POINT (12 13)

You can also think of it as a variation of your third example. st_sfc is a column, and you are trying to replace a column with a column that has fewer values, so it fails. My example here replaces values in the column, and the single value on the RHS is repeated to fill the selected elements in the geometry of the object.

This is probably the fastest, tidiest, and most R-ish way of doing this.

Upvotes: 2

see24
see24

Reputation: 1230

The issue is that case_when requires that all the results have the same class. So the error message is telling you that the result of st_sfc(st_point(c(5,6))) and geometry are not the same class. If you use ifelse instead you can just avoid the requirement that they be the same class.

sf2 <- 
  sf %>%
  mutate(geometry = ifelse(a==16, st_sfc(st_point(c(5,6))), geometry))

You can also adjust the classes of the two results using st_cast

sf2 <- 
  sf %>%
  mutate(geometry = case_when(a==16 ~ st_cast(st_sfc(st_point(c(5,6))), "GEOMETRY"), 
                              TRUE ~ st_cast(geometry, "GEOMETRY")))

Upvotes: 0

Related Questions