40

I have a df with polygon ID's from a shapefile and their centre-points in a geometry column:

# A tibble: 3 x 2
     ID     geometry
  <dbl> <POINT [°]>
1     1 (117.2 31.8)
2     2 (116.4 40.1)
3     4   (117.9 26)

I want to put the latitude/longitude values into separate columns, so I do:

library(sf)
centres<- as.data.frame(st_coordinates(df))

This new 'centres' dataframe has the lat&long values, but misses the ID column. How can I preserve it, or is there another way to get the lat&long values into separate columns from the geometry column whilst keeping the ID in the same df?

dput for the dataframe is:

df <- structure(list(ID = c(1, 2, 4), 
      geometry = structure(list(structure(c(117.2, 31.8), 
      class = c("XY", "POINT", "sfg")), structure(c(116.4, 40.1), 
      class = c("XY", "POINT", "sfg")), structure(c(117.9, 26.0), 
      class = c("XY", "POINT", "sfg"))), class = c("sfc_POINT", "sfc"), 
      precision = 0, bbox = structure(c(xmin = 116.4, ymin = 26.0, xmax = 117.9, ymax = 40.1), 
      class = "bbox"), crs = structure(list(epsg = 4326L, 
      proj4string = "+proj=longlat +datum=WGS84 +no_defs"), class = "crs"), n_empty = 0L)), 
      row.names = c(NA, -3L), class = c("sf", "tbl_df", "tbl", "data.frame"), 
      sf_column = "geometry", agr = structure(c(ID = NA_integer_), 
      class = "factor", .Label = c("constant", "aggregate", "identity")))
Jorrit G
  • 509
  • 2
  • 5
  • 6

3 Answers3

45

Solution using unlist + map()

library(tidyverse)

separated_coord <- df %>%
    mutate(long = unlist(map(df$geometry,1)),
           lat = unlist(map(df$geometry,2)))

separated_coord
Beka Stiling
  • 25
  • 1
  • 7
Henry Cyranka
  • 2,970
  • 1
  • 16
  • 21
43

st_coordinates can be used with dplyr::mutate:

library(magrittr) #for the pipe
df <- df %>%
  dplyr::mutate(lon = sf::st_coordinates(.)[,1],
                lat = sf::st_coordinates(.)[,2])

df
Simple feature collection with 3 features and 3 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 116.4 ymin: 26 xmax: 117.9 ymax: 40.1
CRS:            EPSG:4326
# A tibble: 3 x 4
     ID     geometry   lon   lat
* <dbl>  <POINT [°]> <dbl> <dbl>
1     1 (117.2 31.8)  117.  31.8
2     2 (116.4 40.1)  116.  40.1
3     4   (117.9 26)  118.  26 

And if you no longer care about geometry, transform in a standard dataframe using df %>% sf::st_set_geometry(NULL) (even better, you can use st_drop_geometry() as mentioned in comments)

linog
  • 5,786
  • 3
  • 14
  • 28
  • 3
    Great solution. Maybe flip the lon and lat labels though? – Jordan May 22 '21 at 16:09
  • 2
    It appears this has been fixed since XY -> Lon / Lat. And it was edited after Jordan's comment. ("two-dimensional points refer to x and y, easting and northing, or longitude and latitude, we refer to them as XY" https://r-spatial.github.io/sf/articles/sf1.html) – Ari Anisfeld Feb 22 '22 at 05:48
  • I use `st_drop_geometry()` instead of `sf::st_set_geometry(NULL)`. Also, I think at the start of your answer you meant `st_coordinates` rather than `st_geometry`. Otherwise, good answer. – Earlien Dec 09 '22 at 02:42
8

A possible approach is to unlist it.

setNames(data.frame(df[[1]], 
                    matrix(unlist(df[2]), ncol=2, byrow=TRUE)), 
         c("ID", "lon", "lat"))

#   ID   lon  lat
# 1  1 117.2 31.8
# 2  2 116.4 40.1
# 3  4 117.9 26.0

Explanation

Data structure check with str(df) shows, that a variable - geometry - is in list format, which can be unhandy. A way to solve this is to unlist() it, transform it into a 2-column matrix, and reassemble it with the first column. With setNames() we are able to assign new column names in one step.

jay.sf
  • 60,139
  • 8
  • 53
  • 110