Automatische Etikettenplatzierung für GIS-Karten in R.

9

Ich erstelle GIS-Karten in R mit dem sfPaket (und verwandten Paketen) zum Einlesen von Shapefiles und ggplot2(und Freunden) zum Plotten. Dies funktioniert einwandfrei, aber ich kann keine Möglichkeit finden, (automatisch / programmgesteuert) Etikettenplatzierungen für Funktionen wie Flüsse und Straßen zu erstellen. Diese Merkmale sind typischerweise Linestrings mit unregelmäßigen Formen. Siehe Bild zum Beispiel aus Wikimedia.

Geben Sie hier die Bildbeschreibung ein

Das ggrepelPaket eignet sich gut zum automatisierten Beschriften von Punkten. Dies ist jedoch für andere geografische Merkmale, die keine diskreten Lat / Long-Punkte sind, wenig sinnvoll.

Ich könnte mir vorstellen, dies zu tun, indem ich einzelne Textbeschriftungen für jedes Feature einzeln platziere, aber ich suche nach etwas Automatisierterem, wenn möglich. Mir ist klar, dass eine solche Automatisierung kein triviales Problem ist, aber sie wurde bereits zuvor gelöst (ArcGIS hat anscheinend eine Möglichkeit, dies mit einer Erweiterung namens Maplex zu tun, aber ich habe keinen Zugriff auf die Software und möchte dabei bleiben R wenn möglich).

Kennt jemand einen Weg, dies zu tun?

MWE hier:

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

Geben Sie hier die Bildbeschreibung ein

invertdna
quelle
8
Huch. Nein, nicht nur aus Prinzip. Ich weiß nicht, wie Sie planen oder wie weit Sie gekommen sind oder was Sie erwähnt haben, hat in ggrepel mit nicht geografischen Daten funktioniert. Sie sagen "das funktioniert gut", zeigen aber nicht, was "das" ist, was hilfreich wäre, um es zu sehen und darauf aufzubauen. Es wäre möglich gewesen, ein Beispiel aufzunehmen - sf und andere räumliche Pakete wie spData-Schiffsbeispieldaten, oder Sie könnten ein kleines Dummy-Linestring-Objekt erstellen -, aber im Moment können wir nur raten, welches davon in Ihrer Situation helfen würde, und das ist gerecht langfristig nicht sehr nützlich
camille
8
Wenn Sie kein minimal reproduzierbares Beispiel liefern, bitten Sie im Grunde andere, eines für Sie zu erstellen. Ansonsten können sie normalerweise keine sehr gute Antwort geben. In diesem Fall bedeutet dies, dass sie ein Shapefile finden müssen, herausfinden müssen, wie Sie es verwenden ggrepel, und im Grunde die Arbeit wiederholen müssen, die Sie bereits erledigt haben. Dies macht es viel weniger wahrscheinlich, dass Sie eine nützliche Antwort erhalten.
Axeman
3
MWE jetzt in die Frage aufgenommen. Entschuldigung für die Reaktion; Ich möchte nicht unhöflich sein, und ich habe mir überlegt, wie ich die Zeit der Leute vor dem Posten nicht verschwenden kann. Es schien mir, als würde ich nach einer konzeptionellen Antwort fragen - dh gibt es ein solches Werkzeug? - anstatt eine Antwort speziell für mein spezielles Projekt.
Invertdna
4
Cool, das ist jetzt ein gutes Beispiel und nicht das, das ich mir ausgedacht hätte, wenn Sie uns geraten hätten. Die Suche nach etwas Konzeptionellem wie der Frage, ob ein Tool vorhanden ist, wird für SO als nicht thematisch betrachtet. Fragen sind viel besser, wenn sie an ein bestimmtes Problem oder Projekt gebunden sind. Sind die Beschriftungen zur Verdeutlichung entlang des linearen Teils des Ziels abgewinkelt oder nur in der Nähe der Merkmale zu platzieren?
Camille
8
@camille Zuerst: Ich entschuldige mich wirklich für meine erste Antwort. Ich habe gezögert, auf SO zu posten, weil es voller Gemeinheit ist, und als ich mich darauf gefasst machte, wurde ich selbst der Mittlere. Das tut mir schrecklich und es tut mir wirklich leid. Zur Frage: Die Etiketten müssen nicht abgewinkelt sein. Im breiteren Kontext (hauptsächlich Straßen und Flüsse) sind die Linien unregelmäßig, und daher muss sich das Etikett wahrscheinlich nur irgendwo entlang der Linie befinden, aber (wichtig) parallel zur Linie.
Invertdna

Antworten:

8

Ich glaube, ich habe etwas, das für Sie funktionieren könnte. Ich habe mir erlaubt, Ihr Beispiel in etwas Realistischeres zu ändern: ein paar zufällige "Flüsse", die mit geglätteten zufälligen Spaziergängen von jeweils 100 Punkten Länge erstellt wurden:

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

Wir können sie gemäß Ihrem Beispiel zeichnen:

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

Geben Sie hier die Bildbeschreibung ein

Meine Lösung besteht im Wesentlichen darin, Punkte aus den Linestrings zu extrahieren und sie zu kennzeichnen. Wie das Bild oben in Ihrer Frage möchten Sie möglicherweise mehrere Kopien jedes Etiketts entlang der Länge des Linienstreifens. Wenn Sie also n Etiketten möchten, extrahieren Sie einfach n Punkte mit gleichem Abstand.

Natürlich möchten Sie in der Lage sein, beide Flüsse gleichzeitig zu kennzeichnen, ohne dass die Beschriftungen zusammenstoßen. Daher müssen Sie in der Lage sein, mehrere geografische Merkmale als benannte Liste zu übergeben.

Hier ist eine Funktion, die all das macht:

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

Wenn wir also die Objekte, die wir beschriften möchten, in eine benannte Liste wie folgt einfügen:

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

Dann können wir das tun:

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

Geben Sie hier die Bildbeschreibung ein

Allan Cameron
quelle
2
sfheaders::sf_linestring(obj = data.frame(x = x1, y = y1))erleichtert einen Teil des sfgenerierenden Codes.
SymbolixAU