Posizionamento automatico delle etichette per le mappe GIS in R


9

Sto realizzando mappe GIS in R usando il sfpacchetto (e pacchetti correlati) per leggere in shapefile e ggplot2(e amici) per la stampa. Funziona bene, ma non riesco a trovare il modo (automatico / programmatico) di creare posizionamenti di etichette per elementi come fiumi e strade. Queste caratteristiche sono in genere stringhe di linea con forme irregolari. Vedi l'immagine allegata ad esempio da Wikimedia.

inserisci qui la descrizione dell'immagine

Il ggrepelpacchetto funziona bene per l'etichettatura dei punti in modo automatizzato, ma questo non ha molto senso per altre caratteristiche geografiche che non sono punti Lat / Long discreti.

Potrei immaginare di farlo posizionando le singole etichette di testo su ciascuna funzione singolarmente, ma sto cercando qualcosa di più automatizzato, se possibile. Mi rendo conto che tale automazione non è un problema banale, ma è stata risolta in precedenza (apparentemente ArcGIS ha un modo di farlo con un'estensione chiamata maplex, ma non ho accesso al software e mi piacerebbe rimanere in R se possibile).

Qualcuno sa come farlo?

MWE qui:

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

inserisci qui la descrizione dell'immagine


8
Yikes. No, non solo per principio. Non so come stai pianificando o quanto lontano sei arrivato, o cosa menzioni ha funzionato in ggrepel con dati non geografici. Dici "funziona benissimo" ma non mostrare cosa sia "questo", che sarebbe utile vedere e costruire. Sarebbe stato possibile includere un esempio — sf e altri pacchetti spaziali come spData spediscono dati di esempio, oppure potresti creare un piccolo oggetto fittizio di linestring — ma al momento possiamo solo indovinare quale di questi sarebbe d'aiuto con la tua situazione, e questo è solo a lungo termine non molto utile
camille

8
Se non fornisci un esempio riproducibile minimo, in pratica stai chiedendo agli altri di crearne uno per te. Altrimenti di solito non possono dare una risposta molto buona. In questo caso ciò significa che avrebbero bisogno di trovare uno shapefile, capire come stai usando ggrepel, in sostanza ripetere il lavoro che hai già fatto. Questo rende molto meno probabile la tua risposta utile.
Axeman

3
MWE ora incluso nella domanda. Scuse per la reazione; Non voglio essere scortese, e ho pensato molto a come non perdere tempo delle persone prima di postare. Mi sembrava che stavo chiedendo una risposta concettuale - vale a dire, esiste un tale strumento? - piuttosto che una risposta specifica per il mio progetto specifico.
invertdna

4
Bene, questo ora è un buon esempio e non quello che mi sarei inventato se ci avessi lasciato indovinare. Cercare qualcosa di concettuale come l'esistenza di uno strumento è considerato fuori tema per SO; le domande sono molto migliori quando sono legate a un problema o progetto specifico. Per chiarire, avere le etichette angolate lungo la parte lineare dell'obiettivo o semplicemente posizionarle vicino alle caratteristiche?
Camille,

8
@camille First: mi scuso davvero per la mia prima risposta. Ho esitato a pubblicare post su SO perché è pieno di meschinità e, preparandomi per questo, sono diventato io stesso il cattivo. Mi sento malissimo, e mi dispiace davvero. Per quanto riguarda la domanda a portata di mano: le etichette non devono essere angolate; nel contesto più ampio (strade e fiumi, principalmente), i linestring sono irregolari, e quindi probabilmente l'etichetta deve essere solo da qualche parte lungo la linea, ma (soprattutto) parallelamente alla linea.
invertdna

Risposte:


8

Penso di avere qualcosa che potrebbe funzionare per te. Mi sono preso la libertà di cambiare il tuo esempio in qualcosa di un po 'più realistico: un paio di "fiumi" casuali realizzati con passeggiate casuali levigate, ognuna lunga 100 punti:

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

Possiamo tracciarli secondo il tuo esempio:

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

inserisci qui la descrizione dell'immagine

La mia soluzione è fondamentalmente quella di estrarre punti dai linestring ed etichettarli. Come l'immagine nella parte superiore della tua domanda, potresti volere più copie di ogni etichetta lungo la lunghezza del linestring, quindi se vuoi n etichette devi solo estrarre n punti equidistanti.

Ovviamente, vuoi essere in grado di etichettare entrambi i fiumi contemporaneamente senza lo scontro delle etichette, quindi dovrai essere in grado di passare più caratteristiche geografiche come un elenco di nomi.

Ecco una funzione che fa tutto ciò:

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

Quindi, se mettiamo gli oggetti che vogliamo etichettare in un elenco di nomi come questo:

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

Quindi possiamo farlo:

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

inserisci qui la descrizione dell'immagine


2
sfheaders::sf_linestring(obj = data.frame(x = x1, y = y1))faciliterà parte del sfcodice di generazione.
SymbolixAU il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.