Автоматическое размещение меток для карт ГИС в R

9

Я делаю ГИС-карты в R, используя sfпакет (и связанные с ним пакеты) для чтения в шейп-файлах и ggplot2(и друзей) для построения графиков. Это работает нормально, но я не могу найти способ (автоматически / программно) создать места размещения меток для таких объектов, как реки и дороги. Эти особенности обычно представляют собой линейные линии неправильной формы. Смотрите изображение, прикрепленное, например, из Викимедиа.

введите описание изображения здесь

ggrepelПакет хорошо работает для маркировки точек в автоматическом режиме, но это не имеет особого смысла для других географических объектов, которые не являются дискретными Lat / Long точки.

Я мог бы представить себе, делая отдельные текстовые метки для каждой функции индивидуально, но я ищу что-то более автоматизированное, если это возможно. Я понимаю, что такая автоматизация не является тривиальной проблемой, но она была решена раньше (очевидно, у ArcGIS есть способ сделать это с помощью расширения, называемого maplex, но у меня нет доступа к программному обеспечению, и я бы хотел остаться в R если возможно).

Кто-нибудь знает способ сделать это?

MWE здесь:

#MWE Linestring labeling

library(tidyverse)
library(sf)
library(ggrepel)
set.seed(120)

#pick a county from the built-in North Carolina dataset
BuncombeCounty <- st_read(system.file("shapes/", package="maptools"), "sids") %>% 
  filter(NAME == "Buncombe") 

#pick 4 random points in that county
pts_sf <- data.frame(
  x = seq(-82.3, -82.7, by=-0.1) %>% 
    sample(4),
  y = seq(35.5, 35.7, by=0.05) %>% 
    sample(4),
  placenames = c("A", "B", "C", "D")
) %>% 
  st_as_sf(coords = c("x","y")) 

#link those points into a linestring
linestring_sf <- pts_sf %>% 
  st_coordinates() %>%
  st_linestring()
  st_cast("LINESTRING") 

#plot them with labels, using geom_text_repel() from the `ggrepel` package
ggplot() +
  geom_sf(data = BuncombeCounty) +
  geom_sf(data = linestring_sf) +
  geom_label_repel(data = pts_sf,
                  stat = "sf_coordinates",
                  aes(geometry = geometry,
                      label = placenames),
                  nudge_y = 0.05,
                  label.r = 0, #don't round corners of label boxes
                  min.segment.length = 0,
                  segment.size = 0.4,
                  segment.color = "dodgerblue")

введите описание изображения здесь

invertdna
источник
8
Хлоп. Нет, не просто из принципа. Я не знаю, как вы планируете, как далеко вы продвинулись, или то, что вы упомянули, сработало в ggrepel с негеографическими данными. Вы говорите «это работает хорошо», но не показываете, что такое «это», что было бы полезно увидеть и развить. Можно было бы включить пример - sf и другие пространственные пакеты, такие как образцы данных корабля spData, или вы могли бы создать небольшой фиктивный объект строки - но сейчас мы можем только догадываться, какой из них поможет в вашей ситуации, и это просто не очень полезный длительный срок
Камилла
8
Если вы не предоставите минимальный воспроизводимый пример, вы в основном просите других сделать его для вас. В противном случае они обычно не могут дать очень хороший ответ. В этом случае это означает, что им нужно будет найти шейп-файл, выяснить, как вы используете ggrepel, в основном переделать работу, которую вы уже сделали. Это значительно снижает вероятность того, что вы получите полезный ответ.
Аксеман
3
MWE теперь включен в вопрос. Извинения за реакцию; Я не хочу быть грубым, и я много думал о том, как не тратить время на людей перед публикацией. Мне показалось, что я спрашиваю о концептуальном ответе - то есть существует ли такой инструмент? - а не ответ, специфичный для моего конкретного проекта.
Инвертна
4
Круто, теперь это хороший пример, а не тот, который я бы придумал, если бы вы оставили нас догадываться. Поиск чего-то концептуального, например, существует ли инструмент, считается не по теме для SO; вопросы гораздо лучше, когда они связаны с конкретной проблемой или проектом. Чтобы уточнить, расположены ли метки под углом вдоль части линии линии или просто для размещения их рядом с элементами?
Камилла
8
@camille First: я действительно извиняюсь за свой первый ответ. Я не решался писать в SO, потому что он полон подлости, и, готовясь к этому, я сам стал злым. Я чувствую себя ужасно об этом, и мне действительно жаль. Что касается данного вопроса: ярлыки не должны быть повернуты; в более широком контексте (в основном, дороги и реки) линии линий нерегулярны, и поэтому, вероятно, метка должна быть где-то вдоль линии, но (что важно) параллельно линии.
invertdna

Ответы:

8

Я думаю, у меня есть кое-что, что может сработать для вас. Я позволил себе сменить ваш пример на что-то более реалистичное: пару случайных «рек», сделанных сглаженными случайными прогулками, каждая длиной в 100 пунктов:

library(tidyverse)
library(sf)
library(ggrepel)

BuncombeCounty <- st_read(system.file("shapes/", package = "maptools"), "sids") %>% 
                  filter(NAME == "Buncombe")
set.seed(120)

x1 <- seq(-82.795, -82.285, length.out = 100)
y1 <- cumsum(runif(100, -.01, .01))
y1 <- predict(loess(y1 ~ x1, span = 0.1)) + 35.6

x2 <- x1 + 0.02
y2 <- cumsum(runif(100, -.01, .01))
y2 <- predict(loess(y2 ~ x2, span = 0.1)) + 35.57

river_1 <- data.frame(x = x1, y = y1)     %>% 
           st_as_sf(coords = c("x", "y")) %>%
           st_coordinates()               %>%
           st_linestring()                %>%
           st_cast("LINESTRING") 

river_2 <- data.frame(x = x2, y = y2)     %>% 
           st_as_sf(coords = c("x", "y")) %>%
           st_coordinates()               %>%
           st_linestring()                %>%
           st_cast("LINESTRING") 

Мы можем построить их согласно вашему примеру:

riverplot  <- ggplot() +
              geom_sf(data = BuncombeCounty) +
              geom_sf(data = river_1, colour = "blue", size = 2) +
              geom_sf(data = river_2, colour = "blue", size = 2)

riverplot

введите описание изображения здесь

Мое решение в основном состоит в том, чтобы извлечь точки из линейных линий и пометить их. Как и на картинке в верхней части вашего вопроса, вам может потребоваться несколько копий каждой метки по всей длине строки, поэтому, если вы хотите n меток, вы просто извлекаете n одинаково разнесенных точек.

Конечно, вы хотите иметь возможность пометить обе реки одновременно, без надписей, поэтому вам нужно будет передать несколько географических объектов в виде именованного списка.

Вот функция, которая делает все это:

linestring_labels <- function(linestrings, n)
{
  do.call(rbind, mapply(function(linestring, label)
  {
  n_points <- length(linestring)/2
  distance <- round(n_points / (n + 1))
  data.frame(x = linestring[1:n * distance],
             y = linestring[1:n * distance + n_points],
             label = rep(label, n))
  }, linestrings, names(linestrings), SIMPLIFY = FALSE)) %>%
  st_as_sf(coords = c("x","y"))
}

Поэтому, если мы поместим объекты, которые мы хотим пометить в именованном списке, вот так:

river_list <- list("River 1" = river_1, "River 2" = river_2)

Тогда мы можем сделать это:

riverplot + 
   geom_label_repel(data = linestring_labels(river_list, 3),
                    stat = "sf_coordinates",
                    aes(geometry = geometry, label = label),
                    nudge_y = 0.05,
                    label.r = 0, #don't round corners of label boxes
                    min.segment.length = 0,
                    segment.size = 0.4,
                    segment.color = "dodgerblue")

введите описание изображения здесь

Аллан Кэмерон
источник
2
sfheaders::sf_linestring(obj = data.frame(x = x1, y = y1))облегчит часть sfгенерирующего кода.
SymbolixAU