Colocación automática de etiquetas para mapas SIG en R

9

Estoy haciendo mapas SIG en R usando el sfpaquete (y paquetes relacionados) para leer en shapefiles, y ggplot2(y amigos) para trazar. Esto funciona bien, pero no puedo encontrar la manera (automática / programáticamente) de crear ubicaciones de etiquetas para características como ríos y carreteras. Estas características son típicamente cadenas lineales, con formas irregulares. Ver imagen adjunta, por ejemplo, de wikimedia.

ingrese la descripción de la imagen aquí

El ggrepelpaquete funciona bien para etiquetar puntos de manera automatizada, pero esto no tiene mucho sentido para otras características geográficas que no son puntos Lat / Long discretos.

Me imagino haciendo esto colocando etiquetas de texto individuales en cada función individualmente, pero estoy buscando algo más automatizado, si es posible. Me doy cuenta de que esa automatización no es un problema trivial, pero se ha resuelto antes (ArcGIS aparentemente tiene una forma de hacerlo con una extensión llamada maplex, pero no tengo acceso al software y me gustaría quedarme en R si es posible).

¿Alguien sabe de una manera de hacer esto?

MWE aquí:

#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")

ingrese la descripción de la imagen aquí

invertida
fuente
8
Yikes No, no solo por principio. No sé cómo está tramando o qué tan lejos ha llegado, o lo que menciona ha funcionado en ggrepel con datos no geográficos. Usted dice "esto funciona bien" pero no muestra qué es "esto", lo que sería útil para ver y desarrollar. Hubiera sido posible incluir un ejemplo: SF y otros paquetes espaciales como datos de muestra de envío de datos, o podría hacer un pequeño objeto ficticio de cadena lineal, pero en este momento solo podemos adivinar cuál de ellos ayudaría con su situación, y eso es solo no muy útil a largo plazo
camille
8
Si no proporciona un ejemplo reproducible mínimo, básicamente le está pidiendo a otros que lo hagan por usted. De lo contrario, generalmente no pueden dar una muy buena respuesta. En este caso, eso significa que tendrían que encontrar un archivo de forma, averiguar cómo lo está utilizando ggrepel, básicamente rehacer el trabajo que ya ha hecho. Esto hace que sea mucho menos probable que obtenga una respuesta útil.
Axeman
3
MWE ahora incluido en la pregunta. Disculpas por la reacción; No quiero ser grosero, y pensé mucho en cómo no perder el tiempo de las personas antes de publicar. Me pareció que estaba pidiendo una respuesta conceptual, es decir, ¿existe tal herramienta? - en lugar de una respuesta específica para mi proyecto en particular.
invertdna
44
Genial, este es ahora un buen ejemplo y no el que se me habría ocurrido si nos hubieras dejado adivinar. Buscar algo conceptual como si existe una herramienta se considera fuera de tema para SO; las preguntas son mucho mejores cuando están vinculadas a un problema o proyecto específico. Para aclarar, ¿tener las etiquetas en ángulo a lo largo de la cadena lineal es parte del objetivo, o simplemente colocarlas cerca de las entidades?
camille
8
@camille Primero: realmente me disculpo por mi primera respuesta. Dudé en publicar en SO porque está lleno de maldad, y al prepararme para eso, me convertí en el malo. Me siento terrible por eso, y realmente lo siento. En cuanto a la pregunta en cuestión: las etiquetas no necesitan estar en ángulo; en el contexto más amplio (carreteras y ríos, principalmente), las cadenas lineales son irregulares, por lo que probablemente la etiqueta solo debe estar en algún lugar a lo largo de la línea, pero (lo más importante) paralela a la línea.
invertdna

Respuestas:

8

Creo que tengo algo que podría funcionar para ti. Me he tomado la libertad de cambiar su ejemplo a algo un poco más realista: un par de "ríos" aleatorios hechos con caminatas aleatorias suavizadas, cada una de 100 puntos de largo:

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") 

Podemos trazarlos según su ejemplo:

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

ingrese la descripción de la imagen aquí

Mi solución es básicamente extraer puntos de las cadenas lineales y etiquetarlos. Al igual que la imagen en la parte superior de su pregunta, es posible que desee varias copias de cada etiqueta a lo largo de la cadena de líneas, por lo que si desea n etiquetas, simplemente extraiga n puntos igualmente espaciados.

Por supuesto, desea poder etiquetar ambos ríos a la vez sin que las etiquetas entren en conflicto, por lo que deberá poder pasar múltiples características geográficas como una lista con nombre.

Aquí hay una función que hace todo eso:

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"))
}

Entonces, si ponemos los objetos que queremos etiquetar en una lista con nombre como esta:

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

Entonces podemos hacer esto:

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")

ingrese la descripción de la imagen aquí

Allan Cameron
fuente
2
sfheaders::sf_linestring(obj = data.frame(x = x1, y = y1))facilitará parte del sfcódigo generador.
SymbolixAU