[我正在使用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")
我认为我有一些可能对您有用的东西。我采取了将您的示例更改为更现实的方式的自由:用平滑的随机游走制作的几个随机“河”,每条长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")