Как измерить расстояния между несколькими координатами вдоль потока/линии потока?

Я пытаюсь измерить расстояние между эталонным датчиком/местоположением/координатами ручья и несколькими другими датчиками на ручье/реке, которые были обнаружены с помощью nhdplusTools. Я следую методу, описанному здесь. Однако блоггер вручную удаляет сегменты потока в конце (мне нужно автоматизировать этот процесс) и в итоге измеряет только расстояние между самыми восходящими и нисходящими точками.

Вот код, который я использовал до сих пор:

library(sf)
library(nhdplusTools)
library(mapview)

# custom function for snapping points to flowlines
st_snap_points <- function(x, y, namevar, max_dist = 1000) {
  
  # this evaluates the length of the data
  if (inherits(x, "sf")) n = nrow(x)
  if (inherits(x, "sfc")) n = length(x)
  
  # this part: 
  # 1. loops through every piece of data (every point)
  # 2. snaps a point to the nearest line geometries
  # 3. calculates the distance from point to line geometries
  # 4. retains only the shortest distances and generates a point at that intersection
  out = do.call(c,
                lapply(seq(n), function(i) {
                  nrst = st_nearest_points(st_geometry(x)[i], y)
                  nrst_len = st_length(nrst)
                  nrst_mn = which.min(nrst_len)
                  if (as.vector(nrst_len[nrst_mn]) > max_dist) return(st_geometry(x)[i])
                  return(st_cast(nrst[nrst_mn], "POINT")[2])
                })
  )
  # this part converts the data to a dataframe and adds a named column of your choice
  out_xy <- st_coordinates(out) %>% as.data.frame()
  out_xy <- out_xy %>% 
    mutate({{namevar}} := x[[namevar]]) %>% 
    st_as_sf(coords=c("X","Y"), crs=st_crs(x), remove=FALSE)
  
  return(out_xy)
}


# GET FLOWLINES ASSOCIATED WITH COMIDS
  # make a list defining the sourcetype and ID
  feat_list <- list(featureSource = "comid", featureID = "13167914")
  
  # get upstream flowlines
  us_flowlines <- navigate_nldi(nldi_feature = feat_list,
                                mode = "UM",
                                data_source = "flowlines")
  
  # get downstream mainstem only
  ds_flowlines <- navigate_nldi(nldi_feature = feat_list,
                                mode = "DM",
                                data_source = "flowlines")
  
  # make a list of all the comids we've identified:
  all_comids <- c(us_flowlines$UM_flowlines$nhdplus_comid, ds_flowlines$DM_flowlines$nhdplus_comid)
  
  # download all data and create a geopackage with the comid list
  gpkg <- subset_nhdplus(comids = as.integer(all_comids),
                         simplified = TRUE,
                         overwrite = TRUE,
                         output_file = "13167914_nhdplus.gpkg",
                         nhdplus_data = "download",
                         return_data = FALSE)
  
  # pull the flowlines back in
  streams <- read_sf("13167914_nhdplus.gpkg", "NHDFlowline_Network")

# SNAP GAGE POINTS TO FLOWLINE
  # create sf of all gage locations
  all_gages <- structure(list(location = c("sample_origin", "upstream", "downstream", 
                                           "downstream"), 
                              X = c(-83.609581641841, -83.6125472, -83.54465649, -83.52743409), 
                              Y = c(41.6614141167543, 41.65968056, 41.68865998, 41.70310398)), 
                         row.names = c(NA, 4L), class = "data.frame") %>% 
    sf::st_as_sf(coords = c("X", "Y"), crs = 4326)
  
  
  # project all_gages and streams
  all_gages_proj <- sf::st_transform(all_gages, crs = 26910)
  streams_proj <- sf::st_transform(streams, crs = 26910)
  
  # snap points to the flowlines using a 500 m buffer
  gages_snapped <- st_snap_points(all_gages_proj, streams_proj, namevar = "location", max_dist = 500)
  
  # view map
  mapview(gages_snapped, col.regions = "orange", layer.name = "Snapped Gages") +
    mapview(streams_proj, color = "steelblue", layer.name = "Flowlines")

Затем блоггер использует следующий код, чтобы разделить поток на сегменты. Но опять же, они вручную удаляют сегменты потока (мне нужно автоматизировать этот процесс) и измеряют только расстояние между точками вверх и вниз по течению (я хочу измерить расстояние между эталонным датчиком/местоположением, называемым «sample_origin» в объекте all_gages, и остальные расходомеры).

library(lwgeom)
  
# MEASURE DISTANCE BETWEEN POINTS  
  # Split stream segments between points
  # Create a 1 m buffer around snapped point
  gages_snapped_buff <- st_buffer(gages_snapped, 1)
  
  # Use lwgeom::st_split to split stream segments
  segs <- st_collection_extract(lwgeom::st_split(streams_proj, gages_snapped_buff), "LINESTRING") %>% 
    tibble::rownames_to_column(var = "rowid") %>% 
    mutate(rowid=as.integer(rowid))
  
  # Calculate river distances
  segs_dist <- segs %>% 
    # drop the "loose ends" on either extent (upstream or downstream) of first/last gage
    # here they manually provided rowids from the example
    # filter(!rowid %in% c(232, 100, 66, 62, 63)) %>% 
    mutate(seg_len_m = units::drop_units(units::set_units(st_length(.), "m")),
           seg_len_km = seg_len_m/1000) %>% 
    arrange(desc(hydroseq)) %>% 
    mutate(total_len_km = cumsum(seg_len_km)) %>% 
    # filter to just cols of interest
    select(rowid, comid, gnis_id:reachcode, streamorde, hydroseq, seg_len_km, total_len_km, geom)

Как изменить этот окончательный фрагмент кода, чтобы найти расстояния между эталонным датчиком/местоположением, называемым «sample_origin» в объекте all_gages, и другими датчиками потока?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот рабочий процесс, который я использую в этих случаях:

  1. Объедините смежные строки, используя sf::st_line_merge(), чтобы создать одну строку.
  2. «Привязка» указывает на строку, используя комбинацию lwgeom::st_endpoint() и sf::st_nearest_points().
  3. Сместите привязанные точки по обе стороны от линии, используя geosphere::bearing() и spatialEco::bearing.distance(), затем используйте полученные точки для создания «лезвий» для разделения линии.
  4. Используйте лезвия, чтобы разделить леску и удалить «свободные концы», которые всегда будут первым и последним рядом.
  5. Присвойте значения результату, используя подмножество версий точек привязки, и вычислите совокупную длину.
library(nhdplusTools)
library(dplyr)
library(tidyr)
library(sf)
library(lwgeom)
library(geosphere)
library(spatialEco)
library(ggplot2)

# Load flowlines (previously created from your code example)
streams <- read_sf("13167914_nhdplus.gpkg", "NHDFlowline_Network")

# Create single linestring of streams object
streams1 <- st_transform(streams, crs = 4326) %>%
  st_combine() %>%
  st_line_merge() %>%
  st_as_sf()

# Create sf of gage locations
all_gages <- structure(list(location = c("sample_origin", "upstream", "downstream", 
                                         "downstream"), 
                            X = c(-83.609581641841, -83.6125472, -83.54465649, -83.52743409), 
                            Y = c(41.6614141167543, 41.65968056, 41.68865998, 41.70310398)), 
                       row.names = c(NA, 4L), class = "data.frame") %>% 
  st_as_sf(coords = c("X", "Y"), crs = 4326)

# Create sf of nearest points of all_gages on streams1 (replaces st_snap_points())
nearest <- all_gages %>% 
  mutate(rowid1 = 1:n(),
         nearest = st_endpoint(st_nearest_points(., streams1)),
         azimuth1 = bearing(st_coordinates(geometry),
                            st_coordinates(nearest)),
         azimuth2 = (azimuth1 + 180) %% 360) %>%
  st_set_geometry("nearest")

# Filter nearest to remove sample_origin (for joining attritubes to final result)
nearest1 <- filter(nearest, location != "sample_origin") %>%
  mutate(rowid1 = 1:n()) %>%
  select(rowid1, location)

# Offset modified all_gages points by ~100mm using azimuth
offset_p1 <- data.frame(bearing.distance(st_coordinates(nearest)[,1], 
                                         st_coordinates(nearest)[,2], 
                                         0.1/111320, 
                                         nearest$azimuth1),
                        rowid1 = 1:nrow(nearest),
                        azimuth1 = nearest$azimuth1) %>%
  st_as_sf(coords = c("X", "Y"), crs = 4326) %>%
  rename(geometry1 = geometry)

offset_p2 <- data.frame(bearing.distance(st_coordinates(nearest)[,1], 
                                         st_coordinates(nearest)[,2], 
                                         0.1/111320, 
                                         nearest$azimuth2),
                        rowid1 = 1:nrow(nearest),
                        azimuth2 = nearest$azimuth2) %>%
  st_as_sf(coords = c("X", "Y"), crs = 4326) %>%
  rename(geometry2 = geometry)

# Create linestring blades to split stream1
blades <- left_join(offset_p1, data.frame(offset_p2), by = "rowid1") %>%
  select(rowid1, geometry1, geometry2) %>%
  data.frame() %>%
  pivot_longer(starts_with("geo"),
               values_to = "geometry") %>%
  data.frame() %>%
  st_as_sf() %>%
  summarise(geometry = st_combine(geometry), .by = c(rowid1)) %>%
  st_cast("LINESTRING") %>%
  left_join(select(st_drop_geometry(nearest), rowid1, location), by = "rowid1")

# Split streams1 using blades, drop "loose ends",
# assign location values using nearest1,
# calculate individual and cumulative length, project
segs <- st_collection_extract(st_split(streams1, blades), "LINESTRING") %>%
  slice(2:(n()-1)) %>%
  mutate(rowid = 1:n(),
         seg_len_km = as.numeric(st_length(.) / 1000),
         rowid1 = st_nearest_feature(., nearest1)) %>%
  left_join(data.frame(nearest1), by = "rowid1") %>%
  group_by(location) %>%
  arrange(if_else(location == "downstream", rowid, desc(rowid)), .by_group = TRUE) %>%
  mutate(cumu_len_km = cumsum(seg_len_km)) %>%
  ungroup() %>%
  st_transform(26910) %>%
  select(rowid, location, seg_len_km, cumu_len_km)

segs
# Simple feature collection with 3 features and 4 fields
# Geometry type: LINESTRING
# Dimension:     XY
# Bounding box:  xmin: 3790633 ymin: 5432731 xmax: 3795232 ymax: 5441401
# Projected CRS: NAD83 / UTM zone 10N
# # A tibble: 3 × 5
#   rowid location   seg_len_km cumu_len_km                                 x
#   <int> <chr>           <dbl>       <dbl>                  <LINESTRING [m]>
# 1     2 downstream      7.18        7.18  (3790785 5433050, 3790812 543309…
# 2     3 downstream      2.37        9.55  (3794443 5439086, 3794501 543914…
# 3     1 upstream        0.314       0.314 (3790633 5432731, 3790672 543277…


# For illustrative purposes only
nearest2 <- left_join(nearest1, data.frame(segs), by = join_by(rowid1 == rowid))

# Plot
ggplot() +
  geom_sf(data = st_transform(streams1, 26910), colour = "grey", linewidth = .2) +
  geom_sf(data = segs, 
          aes(colour = location),
          linewidth = 1) +
  geom_sf(data = st_transform(nearest, 26910), aes(shape = location), size = 1.3) +
  geom_sf_text(data = nearest2,
               aes(label = paste0(round(cumu_len_km, 2), "km")),
               size = 3,
               nudge_x = -1500) +
  theme(panel.background = element_blank(),
        legend.position = "inside", legend.position.inside =  c(0.25, 0.8),
        legend.title = element_blank(),
        legend.spacing.y = unit(-3, "mm"))

@L Тайрон - так близко, спасибо! Есть ли способ получить отдельные расстояния от sample_origin и двух разных расположений ниже по течению? В этом случае сегменты приведут к созданию df с 3 строками. Как и сейчас, я не уверен, находится ли нисходящее расстояние до конечного местоположения или до первого.

D Kincaid 22.07.2024 18:14

@L Тайрон - спасибо! Совершенство

D Kincaid 23.07.2024 03:56

Другие вопросы по теме