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

Posted

技术标签:

【中文标题】R中GIS地图的自动标签放置【英文标题】:Automatic Label Placement for GIS maps in R 【发布时间】:2020-05-21 23:25:54 【问题描述】:

我正在使用sf 包(和相关包)在 R 中制作 GIS 地图以读取 shapefile,并使用ggplot2(和朋友)进行绘图。这很好用,但我找不到(自动/以编程方式)为河流和道路等特征创建标签放置的方法。这些特征通常是具有不规则形状的线串。例如,参见来自 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")

【问题讨论】:

哎呀。不,不仅仅是出于原则。我不知道您是如何绘图的,或者您已经走了多远,或者您提到的内容在 ggrepel 中对非地理数据有效。您说“这很好用”,但没有显示“这个”是什么,这将有助于查看和构建。本来可以包含一个示例——sf 和其他空间包,如 spData 运送样本数据,或者你可以制作一个小的虚拟线串对象——但现在我们只能猜测其中哪些对你的情况有帮助,这只是长期来说不是很有用 如果你不提供一个最小的可重现的例子,你基本上是在要求别人为你做一个。否则他们通常不能给出很好的答案。在这种情况下,这意味着他们需要找到一个 shapefile,弄清楚你是如何使用ggrepel,基本上重做你已经完成的工作。这会大大降低您获得有用答案的可能性。 MWE 现在包含在问题中。为反应道歉;我不想无礼,我在发帖前认真思考了如何不浪费人们的时间。在我看来,我在寻求一个概念性的答案——即,这样的工具是否存在? -- 而不是针对我的特定项目的特定答案。 酷,这是一个很好的例子,如果你让我们猜测的话,我会想出这个例子。寻找诸如工具是否存在之类的概念性东西被认为是 SO 的题外话;当问题与特定问题或项目相关时,问题会更好。澄清一下,是让标签沿着目标的线串部分倾斜,还是只是将它们放置在特征附近? @camille First:我真的很抱歉我的第一次回复。我犹豫要不要在 SO 上发帖,因为它充满了卑鄙,在为此做好准备时,我自己也成了卑鄙的人。我对此感到很糟糕,我真的很抱歉。至于手头的问题:标签不需要倾斜;在更广泛的背景下(主要是道路和河流),线串是不规则的,因此标签可能只需要在线的某处,但(重要的是)平行于线。 【参考方案1】:

我想我有一些对你有用的东西。我冒昧地将您的示例更改为更现实的东西:几条由平滑随机游走构成的随机“河流”,每条长 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")

【讨论】:

sfheaders::sf_linestring(obj = data.frame(x = x1, y = y1)) 将简化一些sf 生成代码。

以上是关于R中GIS地图的自动标签放置的主要内容,如果未能解决你的问题,请参考以下文章

在 R 中使用 GIS 数据时仅渲染地图的一部分

如何在主代理的地图上放置嵌套代理

地理位置输入提示(不显示地图)

在 Anylogic 中分配随机 GIS 位置

UCMap移动GIS & 时空地图GIS

怎么把地图碎片放到gis当中去