Penempatan Label Otomatis untuk peta GIS di R

9

Saya membuat peta GIS di R menggunakan sfpaket (dan paket terkait) untuk dibaca di shapefile, dan ggplot2(dan teman) untuk merencanakan. Ini berfungsi dengan baik, tetapi saya tidak dapat menemukan cara (secara otomatis / terprogram) membuat penempatan label untuk fitur seperti sungai dan jalan. Fitur-fitur ini biasanya linestrings, dengan bentuk tidak beraturan. Lihat gambar terlampir misalnya dari wikimedia.

masukkan deskripsi gambar di sini

The ggrepelpaket bekerja dengan baik untuk label poin dengan cara otomatis, tapi ini tidak masuk akal untuk fitur geografis lainnya yang tidak diskrit poin Lat / Long.

Saya bisa membayangkan melakukan ini dengan menempatkan label teks pada masing-masing fitur secara individual, tetapi saya sedang mencari sesuatu yang lebih otomatis, jika mungkin. Saya menyadari bahwa otomatisasi semacam itu bukan masalah sepele, tetapi sudah diselesaikan sebelumnya (ArcGIS tampaknya memiliki cara untuk melakukan ini dengan ekstensi yang disebut maplex, tetapi saya tidak memiliki akses ke perangkat lunak, dan saya ingin tetap berada di dalamnya. R jika memungkinkan).

Adakah yang tahu cara melakukan ini?

MWE di sini:

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

masukkan deskripsi gambar di sini

invertdna
sumber
8
Astaga. Tidak, bukan hanya karena prinsip. Saya tidak tahu bagaimana Anda merencanakan atau seberapa jauh Anda mendapatkan, atau apa yang Anda sebutkan telah bekerja di ggrepel dengan data non-geografis. Anda mengatakan "ini bekerja dengan baik" tetapi tidak menunjukkan apa "ini", yang akan membantu untuk dilihat dan dikembangkan. Mungkin saja untuk menyertakan contoh — sf dan paket spasial lainnya seperti data sampel pengiriman spData, atau Anda bisa membuat objek pelapis dummy kecil — tetapi saat ini kami hanya bisa menebak yang mana yang akan membantu situasi Anda, dan itu hanya jangka panjang tidak terlalu berguna
camille
8
Jika Anda tidak memberikan contoh yang dapat direproduksi minimal, Anda pada dasarnya meminta orang lain untuk membuatkannya untuk Anda. Kalau tidak, mereka biasanya tidak bisa memberikan jawaban yang sangat bagus. Dalam hal ini berarti mereka perlu menemukan shapefile, mencari tahu bagaimana Anda menggunakan ggrepel, pada dasarnya ulangi pekerjaan yang telah Anda lakukan. Ini membuat kemungkinan Anda untuk mendapat jawaban yang sangat kecil kemungkinannya.
Axeman
3
MWE sekarang termasuk dalam pertanyaan. Permintaan maaf atas reaksi; Saya tidak ingin menjadi kasar, dan saya berpikir keras tentang bagaimana tidak membuang waktu orang sebelum memposting. Sepertinya saya saya meminta jawaban konseptual - yaitu, apakah alat seperti itu ada? - Daripada jawaban khusus untuk proyek khusus saya.
invertdna
4
Keren, ini sekarang contoh yang baik dan bukan yang akan saya buat jika Anda membiarkan kami menebak. Mencari sesuatu yang konseptual seperti apakah ada alat dianggap off-topic untuk SO; pertanyaan jauh lebih baik ketika dikaitkan dengan masalah atau proyek tertentu. Untuk memperjelas, apakah label miring di sepanjang bagian linestring tujuan, atau hanya menempatkannya di dekat fitur?
camille
8
@camille First: Saya benar-benar minta maaf atas balasan pertama saya. Saya ragu-ragu untuk memposting ke SO karena penuh dengan kekejaman, dan dalam menguatkan diri untuk itu, saya menjadi yang jahat sendiri. Saya merasa tidak enak tentang itu, dan saya benar-benar minta maaf. Adapun pertanyaan yang dihadapi: label tidak perlu miring; dalam konteks yang lebih luas (jalan dan sungai, terutama), linestrings tidak teratur, dan mungkin label hanya perlu berada di suatu tempat di sepanjang garis, tetapi (penting) sejajar dengan garis.
invertdna

Jawaban:

8

Saya pikir saya memiliki sesuatu yang mungkin bekerja untuk Anda. Saya telah mengambil kebebasan untuk mengubah contoh Anda menjadi sesuatu yang sedikit lebih realistis: beberapa "sungai" acak yang dibuat dengan jalan acak yang diperhalus, masing-masing panjangnya 100 poin:

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

Kami dapat memplotnya sesuai contoh Anda:

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

masukkan deskripsi gambar di sini

Solusi saya pada dasarnya adalah mengekstraksi poin dari linestrings dan melabeli mereka. Seperti gambar di bagian atas pertanyaan Anda, Anda mungkin ingin beberapa salinan dari setiap label di sepanjang linestring, jadi jika Anda ingin n label Anda cukup mengekstrak n titik-titik yang berjarak sama.

Tentu saja, Anda ingin dapat memberi label pada kedua sungai sekaligus tanpa bertabrakan dengan label, sehingga Anda harus dapat melewati beberapa fitur geografis sebagai daftar bernama.

Berikut adalah fungsi yang melakukan semua itu:

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

Jadi jika kita meletakkan objek yang ingin kita beri label dalam daftar bernama seperti ini:

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

Maka kita bisa melakukan ini:

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

masukkan deskripsi gambar di sini

Allan Cameron
sumber
2
sfheaders::sf_linestring(obj = data.frame(x = x1, y = y1))akan memudahkan beberapa sfkode penghasil.
SymbolixAU