Search code examples
rgeospatialelevation

Estimating distance between points and its relative elevation for multiple countries and geometries, using R sf


I have multiple points (lon, lat) for a range of countries. These points are related by an ID that when connected creates a line.

data exmaple:

 aus:
 id   country  point_id      lon       lat
  1  Australia        0 130.1491 -19.57520
  1  Australia        1 129.9958 -19.48760
  1  Australia        2 129.7156 -19.25788
  1  Australia        3 129.7104 -19.20223
  2  Australia        0 129.2510 -18.59016
  2  Australia        1 129.5436 -18.30723
  3  Australia        0 137.2840 -20.06129
  3  Australia        1 137.2865 -20.04308
  3  Australia        2 137.1915 -20.00782
  3  Australia        3 137.1220 -19.97166
  3  Australia        4 137.0650 -19.91363
  3  Australia        5 136.8961 -19.85932
  4  Australia        0 136.8961 -19.85932
  4  Australia        1 136.8791 -19.88669
  4  Australia        2 136.8594 -19.91227
  4  Australia        3 136.8454 -19.92507
  4  Australia        4 136.8360 -19.92976

I managed to create the geometry following this post [1], however, I think my attempt can be automated further. I attempted using group by individually, as follow:

attempt 1 to get linestrings:

# same logic as in [1] but by group
aus_group <- aus %>% group_by(id, country) 

b_group <- aus %>% group_by(id, country) %>% select(lon, lat)
e_group <- aus %>% group_by(id, country) %>% filter(row_number()!=1) %>% select(lon, lat)
f_group <- e_group %>% group_by(id, country) %>% summarise_all(last) %>% select(country, lon, lat)
g_group <- e_group %>% group_by(id, country) %>% rbind(f_group) %>% arrange(id)

aus_group$geometry = do.call(
  "c", 
  lapply(seq(nrow(b_group)), function(i) {
    st_sfc(
      st_linestring(
        as.matrix(
          rbind(b_group[i, c("lon", "lat")], g_group[i, c("lon", "lat")])
          )
        ),
      crs = 4326
      )
    }))

dat_g_sf = st_as_sf(aus_group)
mapview(dat_g_sf, zcol = "id")

# to compare with approach in [1]
dat_g_sf %>% filter(id==1) %>% mapview(zcol = "point_id") # got same plot
# so it works 

I share this part in case there are suggestions to improve the group_by part

question:

My actual question is regarding distance estimation between points and the relative elevation between those points.

For the distance estimation, I have tried this:

attempt 2 to get distances:

# edited aus_1 becomes aus
aus_2 <- aus %>% select(id, country, point_id, lon, lat) #to avoid error with geometry
str(aus_2)
aus_2$distance = do.call(
  "c", 
  lapply(seq(nrow(b)), function(i) {
    st_distance(
      st_sfc(st_point
# edited b becomes b_group, and g becomes g_group
             (as.matrix(rbind(b_group[i, c("lon", "lat")], g_group[i, c("lon", "lat")], by_element = TRUE)
               )
             ),
      crs = 4326
      )
    }))
Error in st_point(as.matrix(rbind(b_group[i, c("lon", "lat")], g_group[i, c("lon", "lat")]), by_element = TRUE)) : 
nrow(x) == 1 is not TRUE

I think the error might be related with the matrix creation. I appreciate any help here.

expected results: edited.

As can be seen in my attempt 2, I tried to get the length between points (by point_id), e.g., distance between point_id: 0 & 1, in id: 1; and so on; using st_distance. (I guess the sum of all distances by point_id would result on the total length estimation).

In my understanding, the distance for point_id == 0 should be zero because it is the distance between point_id: 0 and 0, while the distance between point_id: 0 and 1 is the actual distance for point_id: 1. This is the reason why in my attempt 1 and 2, I tried to create two groups of c("lon", "lat"): b_group and g_group, and then I try applying st_distance between point b and point g. Unfortunately, this did not work.

Overall: it is expected "an sf object of point geometries with the same number of rows as an input dataset, with distance to lagging or leading point (between point_id) within the same group (id) + elevation (between by point_id)" from margusl comment.

no edited:

For the relative elevation estimation, I haven’t found previous work on this using R. Suggestions regarding where to start about elevation estimation are welcome.

Also, what is the meaning of "c" in 1? How does it work in the function?


Solution

  • For elevation you'd need some data source / provider. elevatr package potentially can provide you values for point locations, for example through Amazon Web Service Terrain Tiles, but you'd probably need to evaluate those results and work out acceptable resolution / zoom levels, elevatr docs should cover this part.

    To get distances between every current and previous point, you can use element-wise distance with lag:

    library(elevatr)
    library(dplyr)
    library(sf)
    #> Linking to GEOS 3.11.2, GDAL 3.6.2, PROJ 9.2.0; sf_use_s2() is TRUE
    
    aus |>
      # tibble gets us more compact printout
      tibble() |>
      # make sure points are correctyl sorted
      arrange(country, id, point_id) |>
      # convert to sf object
      st_as_sf(coords = c("lon", "lat"), crs = "WGS84") |>
      # get elevation from Amazon Web Service Terrain Tiles
      get_elev_point(src = "aws") |> 
      # distance from previuos point to the current one, within the same id group
      mutate(dist = st_distance(geometry, lag(geometry), by_element = TRUE),
             # or switch from lag() to lead() to get distance to the next point
             # dist_to_next = st_distance(geometry, lead(geometry), by_element = TRUE),
             .by = id)
    #> Mosaicing & Projecting
    #> Note: Elevation units are in meters
    #> Simple feature collection with 17 features and 6 fields
    #> Geometry type: POINT
    #> Dimension:     XY
    #> Bounding box:  xmin: 129.251 ymin: -20.06129 xmax: 137.2865 ymax: -18.30723
    #> Geodetic CRS:  WGS 84
    #> # A tibble: 17 × 7
    #>       id country  point_id             geometry elevation elev_units   dist
    #>  * <int> <chr>       <int>          <POINT [°]>     <dbl> <chr>         [m]
    #>  1     1 Austral…        0  (130.1491 -19.5752)       382 meters        NA 
    #>  2     1 Austral…        1  (129.9958 -19.4876)       425 meters     18788.
    #>  3     1 Austral…        2 (129.7156 -19.25788)       444 meters     38941.
    #>  4     1 Austral…        3 (129.7104 -19.20223)       441 meters      6212.
    #>  5     2 Austral…        0  (129.251 -18.59016)       376 meters        NA 
    #>  6     2 Austral…        1 (129.5436 -18.30723)       398 meters     44072.
    #>  7     3 Austral…        0  (137.284 -20.06129)       229 meters        NA 
    #>  8     3 Austral…        1 (137.2865 -20.04308)       234 meters      2042.
    #>  9     3 Austral…        2 (137.1915 -20.00782)       237 meters     10671.
    #> 10     3 Austral…        3  (137.122 -19.97166)       234 meters      8301.
    #> 11     3 Austral…        4  (137.065 -19.91363)       235 meters      8783.
    #> 12     3 Austral…        5 (136.8961 -19.85932)       235 meters     18665.
    #> 13     4 Austral…        0 (136.8961 -19.85932)       235 meters        NA 
    #> 14     4 Austral…        1 (136.8791 -19.88669)       235 meters      3525.
    #> 15     4 Austral…        2 (136.8594 -19.91227)       233 meters      3512.
    #> 16     4 Austral…        3 (136.8454 -19.92507)       233 meters      2042.
    #> 17     4 Austral…        4  (136.836 -19.92976)       237 meters      1112.
    

    Dataset example:

    aus <- read.table(header = TRUE, text =
    "id   country point_id      lon       lat
      1  Australia        0 130.1491 -19.57520
      1  Australia        1 129.9958 -19.48760
      1  Australia        2 129.7156 -19.25788
      1  Australia        3 129.7104 -19.20223
      2  Australia        0 129.2510 -18.59016
      2  Australia        1 129.5436 -18.30723
      3  Australia        0 137.2840 -20.06129
      3  Australia        1 137.2865 -20.04308
      3  Australia        2 137.1915 -20.00782
      3  Australia        3 137.1220 -19.97166
      3  Australia        4 137.0650 -19.91363
      3  Australia        5 136.8961 -19.85932
      4  Australia        0 136.8961 -19.85932
      4  Australia        1 136.8791 -19.88669
      4  Australia        2 136.8594 -19.91227
      4  Australia        3 136.8454 -19.92507
      4  Australia        4 136.8360 -19.92976")
    

    Original answer - https://stackoverflow.com/revisions/78251444/1