5

I have a dataframe with two sets of coordinates X,Y and prevXal,prevYval and I would like to make linestrings column wise, and create an sf object of the same number of linestrings as rows in the dataframe.

Using this dataframe example, I would like to create 4 linestrings.

library(sf)
df 
            X       Y prevXval prevYval
1    -0.121  65.001    0.067   65.117
2   180.000 -50.039 -179.879  -50.156
3    -0.075  47.787    0.038   47.904
4   179.754 -73.960 -179.900  -73.979

I started with st_linestring(), but this function works down the rows.

So I tried a function to break up the points to the right input format for st_linestring().

# Create a function that creates a linestring - that extracts vectors (using the as.numeric fn) of the X,Y and prevXval, prevYval points from nx4 dataframe, then bind to a matrix 
make_line <- function(x) {rbind(as.numeric(df[x,1:2]),
                              as.numeric(df[x,3:4]))%>%
            # Convert to a linestring object
            st_linestring()

make_line(1)
LINESTRING (-0.121 65.001, 0.067 65.117)

This works for one row, but I can't seem to find an efficient way to iterate over all the rows, having also tried purrr. Any suggestions?

kimnewzealand
  • 53
  • 1
  • 4

3 Answers3

5

Here's my version of your function to create a line string from a single row:

st_segment = function(r){st_linestring(t(matrix(unlist(r), 2, 2)))}

then with my test dataset:

df = data.frame(X=runif(10),Y=runif(10), PX=runif(10), PY=runif(10))

I can create a geometry column by using sapply on that data frame:

df$geom = st_sfc(sapply(1:nrow(df), 
    function(i){st_segment(df[i,])},simplify=FALSE))

giving:

head(df)
           X          Y         PX        PY                           geom
1 0.40675125 0.18953097 0.31176190 0.8423786 LINESTRING (0.4067513 0.189...
2 0.76997740 0.05169539 0.04452896 0.6224400 LINESTRING (0.7699774 0.051...
3 0.01624562 0.55156073 0.27115739 0.2459570 LINESTRING (0.01624562 0.55...
4 0.73451115 0.74298509 0.89374478 0.4820546 LINESTRING (0.7345111 0.742...
5 0.28522902 0.93939136 0.15557674 0.7243534 LINESTRING (0.285229 0.9393...
6 0.79610820 0.59341010 0.36035310 0.9634315 LINESTRING (0.7961082 0.593...

Which I can then turn into a spatial data frame:

> df = st_sf(df)

and if I plot that I see the geometry...

Spacedman
  • 63,755
  • 5
  • 81
  • 115
4

split-apply-combine is the general pattern you're looking for, e.g.

library(sf)
df <-
tibble::tribble(~ID,        ~X,      ~Y, ~prevXval, ~prevYval,
                  1,    -0.121,  65.001,     0.067,    65.117,
                  2,   180.000, -50.039,  -179.879,   -50.156,
                  3,    -0.075,  47.787,     0.038,    47.904,
                  4,   179.754, -73.960,  -179.900,   -73.979)

rows <- split(df, seq(nrow(df)))
lines <- lapply(rows, function(row) {
    lmat <- matrix(unlist(row[2:5]), ncol = 2, byrow = TRUE)
    st_linestring(lmat)
  })
lines <- st_sfc(lines)
lines_sf <- st_sf('ID' = df$ID, 'geometry' = lines)

purrr::map can be used in place of lapply.

obrl_soil
  • 3,752
  • 15
  • 34
1

Here's a solution using purrr

library(tidyverse)
library(sf)
df <- tibble::tribble(~ID,        ~X,      ~Y, ~prevXval, ~prevYval,
                      1,    -0.121,  65.001,     0.067,    65.117,
                      2,   180.000, -50.039,  -179.879,   -50.156,
                      3,    -0.075,  47.787,     0.038,    47.904,
                      4,   179.754, -73.960,  -179.900,   -73.979)

make_line <- function(X, Y, prevXval, prevYval) {
    st_linestring(matrix(c(X, prevXval, Y, prevYval), 2, 2))
}


df %>%
    select(X, Y, prevXval, prevYval) %>% 
    pmap(make_line) %>% 
    st_as_sfc(crs = 4326) 

Output:

Geometry set for 4 features 
geometry type:  LINESTRING
dimension:      XY
bbox:           xmin: -179.9 ymin: -73.979 xmax: 180 ymax: 65.117
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
LINESTRING (-0.121 65.001, 0.067 65.117)
LINESTRING (180 -50.039, -179.879 -50.156)
LINESTRING (-0.075 47.787, 0.038 47.904)
LINESTRING (179.754 -73.96, -179.9 -73.979)

If you want to obtain a spatial dataframe, just add a couple more transformations to the chain:

df %>%
    select(X, Y, prevXval, prevYval) %>% 
    pmap(make_line) %>% 
    st_as_sfc(crs = 4326) %>% 
    {cbind(df, geom = .)} %>% 
    st_sf() 
HAVB
  • 279
  • 2
  • 9