R中GIS地图的自动标签放置


9

我正在R中使用sf软件包(和相关软件包)读取shapefile,并ggplot2(和朋友)进行绘图,从而制作GIS地图。效果很好,但是我找不到方法(自动/编程)为河流和道路等要素创建标签位置。这些特征通常是线串,形状不规则。参见所附图片,例如来自wikimedia的图片。

在此处输入图片说明

ggrepel软件包可以自动标记点,效果很好,但是对于不是离散纬度/经度点的其他地理特征来说,这没有多大意义。

我可以想象通过在每个功能上单独放置单独的文本标签来做到这一点,但是我正在寻找更自动化的方法(如果可能)。我意识到这样的自动化不是一个小问题,但是它已经得到了解决(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")

在此处输入图片说明


8
kes 不,不仅仅是出于原则。我不知道您的绘图方式或取得的进展,或者您提到的内容在带有非地理数据的ggrepel中有效。您说“这很好”,但不显示“这”是什么,这对您进行查看和构建很有帮助。可能会包含一个示例-SF和其他空间包(例如spData)会提供示例数据,或者您可以制作一个小的虚拟线串对象-但现在我们只能猜测其中哪一个对您的情况有所帮助,仅此而已长期来看不是很有用
camille

8
如果您没有提供最小的可复制示例,则基本上是在要求其他人为您制作一个。否则,他们通常不能给出很好的答案。在这种情况下,这意味着他们将需要找到一个shapefile,弄清楚您的使用方式ggrepel,基本上是重做已经完成的工作。这使您获得有用答案的可能性大大降低。
Axeman

3
现在,MWE已包含在问题中。对此反应表示歉意;我不想变得无礼,我认真思考了如何在发布之前不浪费人们的时间。在我看来,我在寻求一个概念性的答案-即,是否存在这样的工具?-而不是针对我特定项目的答案。
invertdna

4
太好了,现在这是一个很好的例子,如果您让我们猜测的话,我将不会想到。寻找某种概念性的东西(例如是否存在工具)被认为是SO的题外话;当问题与特定问题或项目相关时,问题会更好。为了澄清,是使标签沿目标的线串部分倾斜,还是只是将其放置在要素附近?
卡米尔

8
@camille首先:我真的很抱歉我的第一个答复。我犹豫要发给SO,因为它充满了卑鄙之情,为此我振作起来,我自己也成了卑鄙的人。我对此感到可怕,真的很抱歉。关于眼前的问题:标签不必倾斜;在更广泛的上下文中(主要是道路和河流),线串是不规则的,因此标签可能只需要沿线放置,但(重要)应与线平行。
invertdna

Answers:


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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.