Sf-методы для отделения осевой линии от канала

Я надеюсь использовать метод Вороного, чтобы очертить осевую линию русла реки. Я наткнулся на несколько источников, но не смог повторить работу из-за устаревших пакетов (rgeos).

то есть https://gist.github.com/FloodHydrology/829d3846f0722610ff69d896ad65af3eОпределите линейный объект на растровой карте и верните объект линейной формы с помощью R

library(sf)
library(tibble)
library(sf)

channel <- st_as_sf(tribble(
  ~x, ~y,
  656873.7, 5553711,
  656993.1, 5553738,
  656993.6, 5553738,
  656994.1, 5553738,
  656994.6, 5553738,
  656995.1, 5553738,
  656995.7, 5553738,
  656996.2, 5553738,
  656996.7, 5553738,
  656997.2, 5553738,
  656997.7, 5553738,
  656998.2, 5553738,
  656998.7, 5553738,
  656999.2, 5553738,
  656999.7, 5553737,
  657000.2, 5553737,
  657000.6, 5553737,
  657001.0, 5553737,
  657001.5, 5553736,
  657001.9, 5553736,
  657002.3, 5553736,
  657002.6, 5553735,
  657003.0, 5553735,
  657003.3, 5553734,
  657003.6, 5553734,
  657126.8, 5553550,
  657127.0, 5553550,
  657127.3, 5553549,
  657127.5, 5553549,
  657127.7, 5553548,
  657127.9, 5553548,
  657128.1, 5553548,
  657128.2, 5553547,
  657128.3, 5553546,
  657128.4, 5553546,
  657128.4, 5553545,
  657128.4, 5553545,
  657129.8, 5553466,
  657129.8, 5553465,
  657129.7, 5553465,
  657129.7, 5553464,
  657129.6, 5553464,
  657129.5, 5553463,
  657129.4, 5553463,
  657129.2, 5553462,
  657129.0, 5553462,
  657128.8, 5553461,
  657128.6, 5553461,
  657128.3, 5553460,
  657128.1, 5553460,
  657127.8, 5553459,
  657127.4, 5553459,
  657127.1, 5553459,
  657126.7, 5553458,
  657126.4, 5553458,
  657126.0, 5553458,
  656995.2, 5553355,
  656971.7, 5553303,
  656975.2, 5553281,
  657061.9, 5553229,
  657233.8, 5553179,
  657234.3, 5553179,
  657234.8, 5553179,
  657235.3, 5553179,
  657235.9, 5553178,
  657236.3, 5553178,
  657312.0, 5553130,
  657312.5, 5553130,
  657312.9, 5553130,
  657313.3, 5553129,
  657313.7, 5553129,
  657314.1, 5553128,
  657314.5, 5553128,
  657314.8, 5553128,
  657315.1, 5553127,
  657315.4, 5553127,
  657315.6, 5553126,
  657315.9, 5553126,
  657316.1, 5553125,
  657316.2, 5553125,
  657406.2, 5552837,
  657467.5, 5552813,
  657534.9, 5552855,
  657535.4, 5552855,
  657535.9, 5552855,
  657536.3, 5552855,
  657536.8, 5552855,
  657537.3, 5552856,
  657537.8, 5552856,
  657538.3, 5552856,
  657538.8, 5552856,
  657539.4, 5552856,
  657539.9, 5552856,
  657540.4, 5552856,
  657540.9, 5552856,
  657541.4, 5552856,
  657542.0, 5552856,
  657542.5, 5552856,
  657543.0, 5552856),
  coords = c("x", "y")) %>% 
  st_combine() %>% 
  st_cast("LINESTRING") 

channel <- st_buffer(channel, 100)

st_voronoi(channel) %>% 
  st_collection_extract()

Я дошел до: Но не знаю, как использовать края объекта Вороного для получения центральных точек, а затем осевых линий.

Вы уверены, что это мозаика из st_voronoi(channel) и channel является результатом вашего репрекса? Если да, то как вы это строите и какие версии sf и GEOS (sf::sf_extSoftVersion()) вы сейчас используете? Я ожидаю, что у вас получится что-то вроде этого — i.sstatic.net/w4fxB.png — где набор ребер многоугольника Вороного образует своего рода центральную линию вдоль формы ручья.

margusl 19.04.2024 15:44

Привет @margusl! Подтверждаю, что я получаю тот же результат, что и вы, при построении графика в R. Исходное изображение было сделано в QGIS, а выходные данные Вороного были неточными. Есть ли какие-нибудь идеи о том, как я могу продолжить разграничение в качестве следующего шага?

Jane 19.04.2024 17:59

Вам нужно использовать метод Вороного самостоятельно? Есть пакет centerline, который, похоже, сделает это за вас. Быстрый тест ваших данных выглядит довольно хорошо. github.com/atsyplenkov/centerline

mrhellmann 19.04.2024 19:58
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Вот упрощенный подход, просто чтобы проиллюстрировать общий рабочий процесс, хотя для реальной задачи пакет centerline, предложенный в комментариях, может сэкономить вам массу времени, и вы, вероятно, также получите лучшие результаты.

Пригодность тесселяции Вороного для обнаружения осевой линии сильно зависит от плотности вершин формы и распределения вершин, поэтому здесь я просто добавляю больше точек, в первом примере с st_line_sample() (равномерное распределение, удобное для визуализации, может пропустить некоторые критические точки на изгибах ), а во втором примере — с st_segmentize() (вероятно, вносит некоторые артефакты на концах центральной линии).

Поскольку нас интересуют края Ворного и нас не особо волнуют полигоны, мы можем пойти st_voronoi(... , bOnlyEdges = TRUE)

Из получившихся ребер нас интересуют только те, которые находятся внутри многоугольника channel, поэтому мы можем использовать пространственный фильтр, чтобы удалить большую часть (но, скорее всего, не весь) беспорядка. Отсюда мы можем думать об этом как о задаче о графе: осевая линия — это самая длинная геодезическая, диаметр графа.

tidygraph/igarph и sf аккуратно собраны в пакете sfnetworks, который позволяет нам удобно конвертировать наш sf объект оставшихся ребер Вороного в граф. И после обработки графа мы можем преобразовать его обратно в sf.

library(sf)
#> Linking to GEOS 3.11.2, GDAL 3.6.2, PROJ 9.2.0; sf_use_s2() is TRUE
library(dplyr)
library(sfnetworks)
library(tidygraph)
library(ggplot2)

# uniform point distribution along polygon line 
# for simplification & visualization
edge_points <-
  channel %>% 
  st_cast("LINESTRING") %>% 
  st_line_sample(density = 0.02) %>% 
  st_union()
edge_points
#> Geometry set for 1 feature 
#> Geometry type: MULTIPOINT
#> Dimension:     XY
#> Bounding box:  xmin: 656774.6 ymin: 5552713 xmax: 657642.6 ymax: 5553838
#> CRS:           NA
#> MULTIPOINT ((656930.3 5553621), (656880.9 55536...

# we can set bOnlyEdges to get edges instead of polygons
vor_edges <- 
  st_voronoi(edge_points, bOnlyEdges = TRUE) %>% 
  # from single MULTILINESTRING to a set of LINESTRINGs
  st_cast("LINESTRING")
vor_edges
#> Geometry set for 183 features 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 655649.7 ymin: 5551588 xmax: 658767.4 ymax: 5554963
#> CRS:           NA
#> First 5 geometries:
#> LINESTRING (655649.7 5554589, 656873.7 5553711)
#> LINESTRING (656873.7 5553711, 656873.7 5553711)
#> LINESTRING (656873.7 5553711, 656873.7 5553711)
#> LINESTRING (656873.7 5553711, 655649.7 5553857)
#> LINESTRING (656873.7 5553711, 656873.6 5553711)

# keep only edges within channel
edge_within <- 
  vor_edges %>% 
  # for convenience of st_filter we need to convert sfc to sf
  st_sf() %>% 
  st_filter(channel, .predicate = st_within) 
edge_within
#> Simple feature collection with 68 features and 0 fields
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 656873.6 ymin: 5552816 xmax: 657543.1 ymax: 5553731
#> CRS:           NA
#> First 10 features:
#>                          geometry
#> 1  LINESTRING (656873.7 555371...
#> 2  LINESTRING (656873.7 555371...
#> 3  LINESTRING (656873.7 555371...
#> 4  LINESTRING (656996.3 555330...
#> 5  LINESTRING (656997.3 555329...
#> 6  LINESTRING (656873.8 555371...
#> 7  LINESTRING (656873.8 555371...
#> 8  LINESTRING (656996.6 555330...
#> 9  LINESTRING (656900.8 555371...
#> 10 LINESTRING (656900.8 555371...

# to remove edges that are not part of the centre line, 
# yet are too short to intersect with channel polygon outline, 
# we can convert the remaining list of edges to a graph, 
# keep only nodes (and edges) that are part of a graph diameter (longest geodesic),  
# remove pseudo nodes (nodes with 2 incident edges) with to_spatial_smooth morpher and 
# convert resulting graph to a single LINESTRING
channel_cline <- 
  edge_within %>% 
  as_sfnetwork(directed = FALSE) %>% 
  activate(nodes) %>% 
  # get_diameter returns a list node tbl indices, so we can use it with slice
  slice(igraph::get_diameter(.) %>% as.vector()) %>% 
  convert(to_spatial_smooth) %>%
  st_as_sf(active = "edges")
channel_cline
#> Simple feature collection with 1 feature and 3 fields
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 656873.6 ymin: 5552831 xmax: 657543.1 ymax: 5553725
#> CRS:           NA
#> # A tibble: 1 × 4
#>    from    to .tidygraph_edge_index                                     geometry
#>   <int> <int> <list>                                                <LINESTRING>
#> 1     1     2 <int [63]>            (656873.6 5553711, 656873.7 5553711, 656873…


# visualize previous steps ---------------------------------------------------
# cropped variant for plot
vor_crop <- st_crop(vor_edges, st_buffer(channel, 100))

p1 <- ggplot() + 
  geom_sf(data = channel, fill = "lightblue") +
  geom_sf(data = edge_points) +
  # Calssify edges (within / not within channel polygon)
  geom_sf(data = vor_crop, aes(color = st_within(vor_crop, channel, sparse = FALSE)), show.legend = FALSE) +
  ggtitle("Vor. edges from \nunif. sampled points") +
  theme_void() 

p2 <- ggplot() +
  geom_sf(data = channel, fill = "lightblue") +
  geom_sf(data = edge_within) +
  ggtitle("Vor. edges within \nchannel poly") +
  theme_void() +
  theme(legend.position = "bottom", legend.title.position = "top")

p3 <- ggplot() +
  geom_sf(data = channel, fill = "lightblue") +
  geom_sf(data = channel_cline) +
  ggtitle("Longest geodesic\n") +
  theme_void() +
  theme(legend.position = "bottom", legend.title.position = "top")

patchwork::wrap_plots(p1, p2, p3)

Поскольку st_voronoi работает с многоугольником, мы можем просто добавить некоторую плотность вершин к краю этого канала с помощью st_segmentize(), поэтому в одном конвейере это может выглядеть примерно так:

cline_sfc <- 
  channel %>% 
  # we can add more points to the polygon outline and pass it directly to st_voronoi
  st_segmentize(dfMaxLength = 10) %>%
  st_voronoi(bOnlyEdges = TRUE) %>% 
  st_cast("LINESTRING") %>% 
  st_sf() %>% 
  st_filter(channel, .predicate = st_within) %>%
  as_sfnetwork(directed = FALSE) %>% 
  activate(nodes) %>% 
  slice(igraph::get_diameter(.) %>% as.vector()) %>% 
  # there's a good chance that there are few artefacts at the beginning and end,
  # naive but perhaps good enough approach would be just to remove few nodes
  # from both ends of the diameter list:
  # slice(igraph::get_diameter(.) %>% as.vector() %>% head(-2) %>% tail(-2)) %>% 
  convert(to_spatial_smooth) %>%
  st_as_sf(active = "edges") %>%
  st_geometry() 
  
cline_sfc
#> Geometry set for 1 feature 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 656873.7 ymin: 5552826 xmax: 657553.8 ymax: 5553729
#> CRS:           NA
#> LINESTRING (656873.7 5553711, 656873.7 5553711,...

ggplot() +
  geom_sf(data = channel, fill = "lightblue") +
  geom_sf(data = cline_sfc) +
  ggtitle("cline_sfc") +
  theme_void() 

Форма ввода:

channel <- st_as_sf(tribble(
  ~x, ~y,
  656873.7, 5553711,
  656993.1, 5553738,
  656993.6, 5553738,
  656994.1, 5553738,
  656994.6, 5553738,
  656995.1, 5553738,
  656995.7, 5553738,
  656996.2, 5553738,
  656996.7, 5553738,
  656997.2, 5553738,
  656997.7, 5553738,
  656998.2, 5553738,
  656998.7, 5553738,
  656999.2, 5553738,
  656999.7, 5553737,
  657000.2, 5553737,
  657000.6, 5553737,
  657001.0, 5553737,
  657001.5, 5553736,
  657001.9, 5553736,
  657002.3, 5553736,
  657002.6, 5553735,
  657003.0, 5553735,
  657003.3, 5553734,
  657003.6, 5553734,
  657126.8, 5553550,
  657127.0, 5553550,
  657127.3, 5553549,
  657127.5, 5553549,
  657127.7, 5553548,
  657127.9, 5553548,
  657128.1, 5553548,
  657128.2, 5553547,
  657128.3, 5553546,
  657128.4, 5553546,
  657128.4, 5553545,
  657128.4, 5553545,
  657129.8, 5553466,
  657129.8, 5553465,
  657129.7, 5553465,
  657129.7, 5553464,
  657129.6, 5553464,
  657129.5, 5553463,
  657129.4, 5553463,
  657129.2, 5553462,
  657129.0, 5553462,
  657128.8, 5553461,
  657128.6, 5553461,
  657128.3, 5553460,
  657128.1, 5553460,
  657127.8, 5553459,
  657127.4, 5553459,
  657127.1, 5553459,
  657126.7, 5553458,
  657126.4, 5553458,
  657126.0, 5553458,
  656995.2, 5553355,
  656971.7, 5553303,
  656975.2, 5553281,
  657061.9, 5553229,
  657233.8, 5553179,
  657234.3, 5553179,
  657234.8, 5553179,
  657235.3, 5553179,
  657235.9, 5553178,
  657236.3, 5553178,
  657312.0, 5553130,
  657312.5, 5553130,
  657312.9, 5553130,
  657313.3, 5553129,
  657313.7, 5553129,
  657314.1, 5553128,
  657314.5, 5553128,
  657314.8, 5553128,
  657315.1, 5553127,
  657315.4, 5553127,
  657315.6, 5553126,
  657315.9, 5553126,
  657316.1, 5553125,
  657316.2, 5553125,
  657406.2, 5552837,
  657467.5, 5552813,
  657534.9, 5552855,
  657535.4, 5552855,
  657535.9, 5552855,
  657536.3, 5552855,
  657536.8, 5552855,
  657537.3, 5552856,
  657537.8, 5552856,
  657538.3, 5552856,
  657538.8, 5552856,
  657539.4, 5552856,
  657539.9, 5552856,
  657540.4, 5552856,
  657540.9, 5552856,
  657541.4, 5552856,
  657542.0, 5552856,
  657542.5, 5552856,
  657543.0, 5552856),
  coords = c("x", "y")) %>% 
  st_combine() %>% 
  st_cast("LINESTRING") 

channel <- st_buffer(channel, 100)

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