Come velocizzare la stampa di poligoni in R?


24

Voglio tracciare i confini del paese del Nord America su un'immagine raster che raffigura alcune variabili e quindi sovrappone i contorni sopra la trama usando R. Sono riuscito a farlo usando la grafica di base e il reticolo, ma sembra che il processo di stampa sia troppo lento! Non l'ho ancora fatto in ggplot2, ma dubito che andrà meglio in termini di velocità.

Ho i dati in un file netcdf creato da un file grib. Per ora, ho scaricato i confini del paese per Canada, USA e Messico, che erano disponibili in file RData da GADM che leggevano in R come oggetti SpatialPolygonsDataFrame.

Ecco un po 'di codice:

# Load packages
library(raster)
#library(ncdf) # If you cannot install ncdf4
library(ncdf4)

# Read in the file, get the 13th layer
# fn <- 'path_to_file'
r <- raster(fn, band=13)

# Set the projection and extent
p4 <- "+proj=lcc +lat_1=50.0 +lat_2=50.0 +units=km +x_0=32.46341 +y_0=32.46341 +lon_0=-107 +lat_0=1.0"
projection(r) <- CRS(p4)
extent(r) <- c(-5648.71, 5680.72, 1481.40, 10430.62)

# Get the country borders
# This will download the RData files to your working directory
can<-getData('GADM', country="CAN", level=1)
usa<-getData('GADM', country="USA", level=1)
mex<-getData('GADM', country="MEX", level=1)

# Project to model grid
can_p <- spTransform(can, CRS(p4))
usa_p <- spTransform(usa, CRS(p4))
mex_p <- spTransform(mex, CRS(p4))

### USING BASE GRAPHICS
par(mar=c(0,0,0,0))
# Plot the raster
bins <- 100
plot(r, axes=FALSE, box=FALSE, legend=FALSE,
     col=rev( rainbow(bins,start=0,end=1) ),
     breaks=seq(4500,6000,length.out=bins))
plot(r, legend.only=TRUE, col=rev( rainbow(bins,start=0,end=1)),
     legend.width=0.5, legend.shrink=0.75, 
     breaks=seq(4500,6000,length.out=bins),
     axis.args=list(at=seq(4500,6000,length.out=11),
                labels=seq(4500,6000,length.out=11),
                cex.axis=0.5),
     legend.args=list(text='Height (m)', side=4, font=2, 
                      line=2, cex=0.8))
# Plot the borders
# These are so slow!!
plot(can_p, add=TRUE, border='white', lwd=2)
plot(usa_p, add=TRUE, border='white', lwd=2)
plot(mex_p, add=TRUE, border='white', lwd=2)
# Add the contours
contour(r, add=TRUE, nlevel=5)

### USING LATTICE
library(rasterVis)

# Some settings for our themes
myTheme <- RdBuTheme()
myTheme$axis.line$col<-"transparent"
myTheme$add.line$alpha <- 1
myTheme2 <- myTheme
myTheme2$regions$col <- 'transparent'
myTheme2$add.text$cex <- 0.7
myTheme2$add.line$lwd <- 1
myTheme2$add.line$alpha <- 0.8

# Get JUST the contour lines
contours <- contourplot(r, margin=FALSE, scales=list(draw=FALSE),
                        par.settings=myTheme2, pretty=TRUE, key=NULL, cuts=5,
                        labels=TRUE)

# Plot the colour
levels <- levelplot(r, contour=FALSE, margin=FALSE, scales=list(draw=FALSE),
                    par.settings = myTheme, cuts=100)

# Plot!
levels +  
  layer(sp.polygons(can_p, col='green', lwd=2)) +
  layer(sp.polygons(usa_p, col='green', lwd=2)) +
  layer(sp.polygons(mex_p, col='green', lwd=2)) +
  contours

C'è un modo per accelerare la trama dei poligoni? Sul sistema su cui sto lavorando, la stampa richiede diversi minuti. Voglio infine creare una funzione che generi facilmente un certo numero di questi grafici per l'ispezione, e credo che traccerò molte di queste mappe, quindi voglio aumentare la velocità dei grafici!

Grazie!


solo un'idea del genere, puoi creare indici nel tuo campo della geometria poligonale?
Sotto il radar,

@ Burton449 Siamo spiacenti, sono nuovo delle cose relative alla mappatura in R, inclusi poligoni, proiezioni, ecc ... Non capisco la tua domanda
ialm,

2
Potresti provare a stampare su un dispositivo diverso dalla finestra della trama. Avvolgi le funzioni della trama in pdf o jpeg (con argomenti associati) e genera uno di questi formati. Ho scoperto che questo è notevolmente più veloce.
Jeffrey Evans,

@JeffreyEvans Wow, sì. Non l'ho considerato. La stampa dei tre file delle forme nella finestra della trama ha richiesto circa 60 secondi, ma la stampa su un file ha richiesto solo 14 secondi. Ancora troppo lento per l'attività in corso, ma può rivelarsi utile se combinato con alcuni dei metodi nella risposta di seguito. Grazie!
ialm

Risposte:


30

Ho trovato 3 modi per aumentare la velocità di tracciare i confini del paese dai file di forma per R. Ho trovato ispirazione e codice da qui e qui .

(1) Possiamo estrarre le coordinate dai file di forma per ottenere la longitudine e le latitudini dei poligoni. Quindi possiamo inserirli in un frame di dati con la prima colonna contenente le lunghezze e la seconda colonna contenente le latitudini. Le diverse forme sono separate da NA.

(2) Possiamo rimuovere alcuni poligoni dal nostro file di forma. Il file delle forme è molto, molto dettagliato, ma alcune delle forme sono minuscole isole che non sono importanti (per i miei grafici, comunque). Possiamo impostare una soglia minima dell'area poligonale per mantenere i poligoni più grandi.

(3) Possiamo semplificare la geometria delle nostre forme usando l' algoritmo Douglas-Peuker . I bordi delle nostre forme poligonali possono essere semplificati, in quanto sono molto intricati nel file originale. Fortunatamente, esiste un pacchetto rgeosche lo implementa.

Impostare:

# Load packages
library(rgdal)
library(raster)
library(sp)
library(rgeos)

# Load the shape files
can<-getData('GADM', country="CAN", level=0)
usa<-getData('GADM', country="USA", level=0)
mex<-getData('GADM', country="MEX", level=0)

Metodo 1: estrarre le coordinate dai file di forma in un frame di dati e linee di trama

Il principale svantaggio è che qui perdiamo alcune informazioni rispetto al mantenimento dell'oggetto come oggetto SpatialPolygonsDataFrame, come la proiezione. Tuttavia, possiamo trasformarlo nuovamente in un oggetto sp e aggiungere nuovamente le informazioni di proiezione, ed è ancora più veloce della stampa dei dati originali.

Si noti che questo codice viene eseguito molto lentamente sul file originale perché ci sono molte forme e il frame di dati risultante è lungo ~ 2 milioni di righe.

Codice:

# Convert the polygons into data frames so we can make lines
poly2df <- function(poly) {
  # Convert the polygons into data frames so we can make lines
  # Number of regions
  n_regions <- length(poly@polygons)

  # Get the coords into a data frame
  poly_df <- c()
  for(i in 1:n_regions) {
    # Number of polygons for first region
    n_poly <- length(poly@polygons[[i]]@Polygons)
    print(paste("There are",n_poly,"polygons"))
    # Create progress bar
    pb <- txtProgressBar(min = 0, max = n_poly, style = 3)
    for(j in 1:n_poly) {
      poly_df <- rbind(poly_df, NA, 
                       poly@polygons[[i]]@Polygons[[j]]@coords)
      # Update progress bar
      setTxtProgressBar(pb, j)
    }
    close(pb)
    print(paste("Finished region",i,"of",n_regions))
  }
  poly_df <- data.frame(poly_df)
  names(poly_df) <- c('lon','lat')
  return(poly_df)
}

Metodo 2: rimuovere piccoli poligoni

Ci sono molte piccole isole che non sono molto importanti. Se controlli alcuni dei quantili delle aree per i poligoni, vediamo che molti di loro sono minuscoli. Per la trama del Canada, sono passato dal complottare oltre mille poligoni a solo centinaia di poligoni.

Quantili per le dimensioni dei poligoni per il Canada:

          0%          25%          50%          75%         100% 
4.335000e-10 8.780845e-06 2.666822e-05 1.800103e-04 2.104909e+02 

Codice:

# Get the main polygons, will determine by area.
getSmallPolys <- function(poly, minarea=0.01) {
  # Get the areas
  areas <- lapply(poly@polygons, 
                  function(x) sapply(x@Polygons, function(y) y@area))

  # Quick summary of the areas
  print(quantile(unlist(areas)))

  # Which are the big polygons?
  bigpolys <- lapply(areas, function(x) which(x > minarea))
  length(unlist(bigpolys))

  # Get only the big polygons and extract them
  for(i in 1:length(bigpolys)){
    if(length(bigpolys[[i]]) >= 1 && bigpolys[[i]] >= 1){
      poly@polygons[[i]]@Polygons <- poly@polygons[[i]]@Polygons[bigpolys[[i]]]
      poly@polygons[[i]]@plotOrder <- 1:length(poly@polygons[[i]]@Polygons)
    }
  }
  return(poly)
}

Metodo 3: semplificare la geometria delle forme poligonali

Possiamo ridurre il numero di vertici nelle nostre forme poligonali usando la gSimplifyfunzione dal rgeospacchetto

Codice:

can <- getData('GADM', country="CAN", level=0)
can <- gSimplify(can, tol=0.01, topologyPreserve=TRUE)

Alcuni parametri di riferimento:

Ho usato il tempo trascorso system.timeper confrontare i miei tempi di stampa. Si noti che questi sono solo i tempi per tracciare i paesi, senza i contorni e altre cose extra. Per gli oggetti sp, ho appena usato la plotfunzione. Per gli oggetti del frame di dati, ho usato la plotfunzione con type='l'e la linesfunzione.

Tracciare i poligoni originali di Canada, USA, Messico:

73,009 secondi

Utilizzando il metodo 1:

2.449 secondi

Utilizzando il metodo 2:

17.660 secondi

Utilizzando il metodo 3:

16.695 secondi

Utilizzando il metodo 2 + 1:

1.729 secondi

Utilizzando il metodo 2 + 3:

0.445 secondi

Utilizzando il metodo 2 + 3 + 1:

0,172 secondi

Altre osservazioni:

Sembra che la combinazione dei metodi 2 + 3 dia velocità sufficienti per la stampa di poligoni. L'uso dei metodi 2 + 3 + 1 aggiunge il problema di perdere le belle proprietà degli spoggetti e la mia principale difficoltà è applicare le proiezioni. Ho hackerato qualcosa insieme per proiettare un oggetto frame di dati, ma funziona piuttosto lentamente. Penso che l'utilizzo del metodo 2 + 3 mi offra velocità sufficienti fino a quando non riesco a liberarmi del metodo 2 + 3 + 1.


3
+1 per la scrittura, che senza dubbio i futuri lettori troveranno utili.
SlowLearner,

3

Tutti dovrebbero cercare di passare al pacchetto sf (caratteristiche spaziali) invece di sp. È significativamente più veloce (1/60 in questo caso) e più facile da usare. Ecco un esempio di lettura in shp e di stampa tramite ggplot2.

Nota: è necessario reinstallare ggplot2 dal build su github più recente (vedere di seguito)

library(rgdal)
library(sp)
library(sf)
library(plyr)
devtools::install_github("tidyverse/ggplot2")
library(ggplot2)

# Load the shape files
can<-getData('GADM', country="CAN", level=0)
td <- file.path(tempdir(), "rgdal_examples"); dir.create(td)
st_write(st_as_sf(can),file.path(td,'can.shp'))


ptm <- proc.time()
  can = readOGR(dsn=td, layer="can")
  can@data$id = rownames(can@data)
  can.points = fortify(can, region="id")
  can.df = join(can.points, can@data, by="id")
  ggplot(can.df) +  geom_polygon(aes(long,lat,group=group,fill='NAME_ENGLISH'))
proc.time() - ptm

user  system elapsed 
683.344   0.980 684.51 

ptm <- proc.time()
  can2 = st_read(file.path(td,'can.shp'))  
  ggplot(can2)+geom_sf( aes(fill = 'NAME_ENGLISH' )) 
proc.time() - ptm

user  system elapsed 
11.340   0.096  11.433 

0

I dati GADM hanno una risoluzione spaziale molto elevata delle coste. Se non è necessario, è possibile utilizzare un set di dati più generalizzato. Gli approcci di ialm sono molto interessanti, ma una semplice alternativa è quella di utilizzare i dati "wrld_simpl" forniti con "maptools"

library(maptools)
data(wrld_simpl)
plot(wrld_simpl)

Volevo preservare le forme nel mio set di dati perché conteneva i confini della regione all'interno del paese (ad esempio, province e stati). Altrimenti avrei usato le mappe nel pacchetto di dati delle mappe!
ialm,
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.