Aumentare la velocità di ritaglio, maschera ed estrazione di raster da molti poligoni in R?


29

Sto estraendo l'area e la percentuale di copertura di diversi tipi di uso del suolo da un raster basato su diverse migliaia di confini poligonali. Ho scoperto che la funzione di estrazione funziona molto più velocemente se eseguo l'iterazione attraverso ogni singolo poligono e ritaglio, quindi maschera il raster fino alla dimensione del particolare poligono. Tuttavia, è piuttosto lento e mi chiedo se qualcuno abbia qualche suggerimento per migliorare l'efficienza e la velocità del mio codice.

L'unica cosa che ho trovato correlata a questa è questa risposta di Roger Bivand che ha suggerito di usare GDAL.open()e GDAL.close()così come getRasterTable()e getRasterData(). Ho esaminato quelli, ma ho avuto problemi con gdal in passato e non lo conosco abbastanza bene da saperlo implementare.

Esempio riproducibile:

library(maptools)  ## For wrld_simpl
library(raster)

## Example SpatialPolygonsDataFrame
data(wrld_simpl) #polygon of world countries
bound <- wrld_simpl[1:25,] #name it this to subset to 25 countries and because my loop is set up with that variable  

## Example RasterLayer
c <- raster(nrow=2e3, ncol=2e3, crs=proj4string(wrld_simpl), xmn=-180, xmx=180, ymn=-90, ymx=90)
c[] <- 1:length(c)

#plot, so you can see it
plot(c)    
plot(bound, add=TRUE) 

Metodo più veloce finora

result <- data.frame() #empty result dataframe 

system.time(
     for (i in 1:nrow(bound)) { #this is the number of polygons to iterate through
      single <- bound[i,] #selects a single polygon
      clip1 <- crop(c, extent(single)) #crops the raster to the extent of the polygon, I do this first because it speeds the mask up
      clip2 <- mask(clip1,single) #crops the raster to the polygon boundary

      ext<-extract(clip2,single) #extracts data from the raster based on the polygon bound
      tab<-lapply(ext,table) #makes a table of the extract output
      s<-sum(tab[[1]])  #sums the table for percentage calculation
      mat<- as.data.frame(tab) 
      mat2<- as.data.frame(tab[[1]]/s) #calculates percent
      final<-cbind(single@data$NAME,mat,mat2$Freq) #combines into single dataframe
      result<-rbind(final,result)
      })

   user  system elapsed 
  39.39    0.11   39.52 

Elaborazione parallela

L'elaborazione parallela ha dimezzato il tempo dell'utente, ma ha annullato il vantaggio raddoppiando il tempo di sistema. Raster lo utilizza per la funzione di estrazione, ma sfortunatamente non per la funzione di ritaglio o maschera. Sfortunatamente, questo lascia una quantità leggermente maggiore di tempo totale trascorso a causa di "aspettare" dall'IO.

beginCluster( detectCores() -1) #use all but one core

eseguire codice su più core:

  user  system elapsed 
  23.31    0.68   42.01 

quindi termina il cluster

endCluster()

Metodo lento: il metodo alternativo per eseguire un estratto direttamente dalla funzione raster richiede molto più tempo e non sono sicuro della gestione dei dati per ottenerlo nella forma che desidero:

system.time(ext<-extract(c,bound))
   user  system elapsed 
1170.64   14.41 1186.14 

Potresti provare questo profiler del codice R ( marcodvisser.github.io/aprof/Tutorial.html ). Può dirti quali linee richiedono la maggior parte del tempo. Il link contiene anche linee guida per ridurre i tempi di elaborazione in R.
J Kelly il

Solo i miei due centesimi qui. . . ma il metodo crop / getvalues ​​non funziona quando il numero di pixel nel ritaglio è molto basso. Non sono sicuro di dove sia il limite, ma ho avuto problemi sulle colture in cui c'erano solo 1-5 pixel (non ho determinato il motivo esatto per cui (bit ancora nuovo ai pacchetti spaziali) ma scommetto che la funzione di ritaglio dipende da i confini dei pixel, quindi fatica a ritagliare i singoli pixel). L'estrazione dal pacchetto raster non presenta questo problema, ma concordato è più del doppio del tempo dell'utente e molto più del doppio del tempo del sistema. Solo un avvertimento per coloro che hanno raster a bassa risoluzione (e un in
Neal Barsch il

2
C'è un pacchetto piuttosto nuovo, velox, che ha spostato extract in C tramite il pacchetto Rcpp. Sta aumentando la velocità di circa 10 volte nelle operazioni di estrazione usando poligoni.
Jeffrey Evans,

@JeffreyEvans. Prova ora la risposta a questa domanda usando velox. Problemi con l'assegnazione di vettori estremamente grandi, tuttavia.
SeldomSeenSlim,

Risposte:


23

Sono finalmente riuscito a migliorare questa funzione. Ho scoperto che per i miei scopi, era rasterize()prima il poligono più veloce e usare getValues()invece di extract(). La rasterizzazione non è molto più veloce del codice originale per la tabulazione dei valori raster in piccoli poligoni, ma brilla quando si trattava di aree poligonali di grandi dimensioni che avevano grandi raster da ritagliare e i valori estratti. Ho anche scoperto che getValues()era molto più veloce della extract()funzione.

Ho anche capito l'elaborazione multi-core usando foreach().

Spero che ciò sia utile per altre persone che desiderano una soluzione R per l'estrazione di valori raster da una sovrapposizione di poligoni. Questo è simile al "Tabulate Intersection" di ArcGIS, che non ha funzionato bene per me, restituendo output vuoti dopo ore di elaborazione, come questo utente.

#initiate multicore cluster and load packages
library(foreach)
library(doParallel)
library(tcltk)
library(sp)
library(raster)

cores<- 7
cl <- makeCluster(cores, output="") #output should make it spit errors
registerDoParallel(cl)

Ecco la funzione:

multicore.tabulate.intersect<- function(cores, polygonlist, rasterlayer){ 
  foreach(i=1:cores, .packages= c("raster","tcltk","foreach"), .combine = rbind) %dopar% {

    mypb <- tkProgressBar(title = "R progress bar", label = "", min = 0, max = length(polygonlist[[i]]), initial = 0, width = 300) 

    foreach(j = 1:length(polygonlist[[i]]), .combine = rbind) %do% {
      final<-data.frame()
      tryCatch({ #not sure if this is necessary now that I'm using foreach, but it is useful for loops.

        single <- polygonlist[[i]][j,] #pull out individual polygon to be tabulated

        dir.create (file.path("c:/rtemp",i,j,single@data$OWNER), showWarnings = FALSE) #creates unique filepath for temp directory
        rasterOptions(tmpdir=file.path("c:/rtemp",i,j, single@data$OWNER))  #sets temp directory - this is important b/c it can fill up a hard drive if you're doing a lot of polygons

        clip1 <- crop(rasterlayer, extent(single)) #crop to extent of polygon
        clip2 <- rasterize(single, clip1, mask=TRUE) #crops to polygon edge & converts to raster
        ext <- getValues(clip2) #much faster than extract
        tab<-table(ext) #tabulates the values of the raster in the polygon

        mat<- as.data.frame(tab)
        final<-cbind(single@data$OWNER,mat) #combines it with the name of the polygon
        unlink(file.path("c:/rtemp",i,j,single@data$OWNER), recursive = TRUE,force = TRUE) #delete temporary files
        setTkProgressBar(mypb, j, title = "number complete", label = j)

      }, error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) #trycatch error so it doesn't kill the loop

      return(final)
    }  
    #close(mypb) #not sure why but closing the pb while operating causes it to return an empty final dataset... dunno why. 
  }
}

Quindi, per usarlo, regola il single@data$OWNERper adattarlo al nome della colonna del tuo poligono identificativo (indovina che avrebbe potuto essere incorporato nella funzione ...) e inserisci:

myoutput <- multicore.tabulate.intersect(cores, polygonlist, rasterlayer)

3
Il suggerimento che getValuesera molto più veloce di quello extractnon sembra valido perché se lo usi extractnon devi farlo crope rasterize(o mask). Il codice nella domanda originale fa entrambe le cose e ciò dovrebbe riguardare il doppio tempo di elaborazione.
Robert Hijmans,

l'unico modo per sapere è testando.
djas,

Che classe è la poligono qui e cosa dovrebbe fare la poligono [[i]] [, j] qui (ELI5, per favore)? Sono novizio di cose parallele, quindi non lo capisco molto bene. Non sono riuscito a ottenere la funzione per restituire nulla, fino a quando non ho cambiato in poligono elenco [[i]] [, j] in poligono elenco [, j], che sembra logico perché [, j] è il j elemento di un SpatialPolygonsDataframe, se quello è la classe corretta? Dopo averlo modificato, ho avviato il processo e alcuni output, ma c'è sicuramente ancora qualcosa che non va. (Cerco di estrarre il valore mediano in n piccoli poligoni, quindi ho cambiato anche un po 'di codice).
reima,

@RobertH Nel mio caso, il ritaglio (e il mascheramento) lo fanno funzionare circa 3 volte più velocemente. Sto usando un raster da 100 milioni di acri e i poligoni ne rappresentano una minima parte. Se non taglio al poligono, il processo procede molto più lentamente. Ecco i miei risultati: clip1 <- crop (rasterlayer, extension (single))> system.time (ext <-extract (clip1, single)) #estrazione dal sistema utente raster ritagliato scaduto 65,94 0,37 67,22> system.time (ext < -extract (rasterlayer, single)) #estrazione da un sistema utente raster da 100 milioni di acri scaduto 175,00 4,92 181,10
Luke Macaulay,

4

Accelera l'estrazione di raster (stack raster) da punto, XY o poligono

Ottima risposta Luca. Devi essere un mago R! Ecco una piccola modifica per semplificare il codice (in alcuni casi può migliorare leggermente le prestazioni). Puoi evitare alcune operazioni usando cellFromPolygon (o cellFromXY per i punti) e quindi clip e getValues.

Estrai dati poligonali o punti da pile raster ------------------------

 library(raster)  
 library(sp)   

  # create polygon for extraction
  xys= c(76.27797,28.39791,
        76.30543,28.39761,
        76.30548,28.40236,
        76.27668,28.40489)
  pt <- matrix(xys, ncol=2, byrow=TRUE)
  pt <- SpatialPolygons(list(Polygons(list(Polygon(pt)), ID="a")));
  proj4string(pt) <-"+proj=longlat +datum=WGS84 +ellps=WGS84"
  pt <- spTransform(pt, CRS("+proj=sinu +a=6371007.181 +b=6371007.181 +units=m"))
  ## Create a matrix with random data & use image()
  xy <- matrix(rnorm(4448*4448),4448,4448)
  plot(xy)

  # Turn the matrix into a raster
  NDVI_stack_h24v06 <- raster(xy)
  # Give it lat/lon coords for 36-37°E, 3-2°S
  extent(NDVI_stack_h24v06) <- c(6671703,7783703,2223852,3335852)
  # ... and assign a projection
  projection(NDVI_stack_h24v06) <- CRS("+proj=sinu +a=6371007.181 +b=6371007.181 +units=m")
  plot(NDVI_stack_h24v06)
  # create a stack of the same raster
  NDVI_stack_h24v06 = stack( mget( rep( "NDVI_stack_h24v06" , 500 ) ) )


  # Run functions on list of points
  registerDoParallel(16)
  ptm <- proc.time()
  # grab cell number
  cell = cellFromPolygon(NDVI_stack_h24v06, pt, weights=FALSE)
  # create a raster with only those cells
  r = rasterFromCells(NDVI_stack_h24v06, cell[[1]],values=F)
  result = foreach(i = 1:dim(NDVI_stack_h24v06)[3],.packages='raster',.combine=rbind,.inorder=T) %dopar% {
     #get value and store
     getValues(crop(NDVI_stack_h24v06[[i]],r))
  }
  proc.time() - ptm
  endCluster()

sistema utente trascorso 16.682 2.610 2.530

  registerDoParallel(16)
  ptm <- proc.time()
  result = foreach(i = 1:dim(NDVI_stack_h24v06)[3],.packages='raster',.inorder=T,.combine=rbind) %dopar% {
        clip1 <- crop(NDVI_stack_h24v06[[i]], extent(pt)) #crop to extent of polygon
        clip2 <- rasterize(pt, clip1, mask=TRUE) #crops to polygon edge & converts to raster
         getValues(clip2) #much faster than extract
  }
  proc.time() - ptm
  endCluster()

sistema utente scaduto 33.038 3.511 3.288


Ho seguito i due approcci e il tuo metodo è risultato leggermente più lento nel mio caso d'uso.
Luke Macaulay,

2

Se una perdita nella precisione della sovrapposizione non è estremamente importante - supponendo che sia preciso per cominciare - in genere si possono ottenere velocità di calcolo zonale molto maggiori convertendo prima i poligoni in un raster. Il rasterpacchetto contiene la zonal()funzione, che dovrebbe funzionare bene per l'attività prevista. Tuttavia, è sempre possibile sottoinsieme due matrici della stessa dimensione utilizzando l'indicizzazione standard. Se devi mantenere i poligoni e non ti dispiace il software GIS, QGIS è effettivamente più veloce nelle statistiche zonali di ArcGIS o ENVI-IDL.


2

Ho anche lottato con questo per un po 'di tempo, cercando di calcolare la quota dell'area delle classi di copertura del suolo di una mappa della griglia di ~ 300mx300m in una griglia di ~ 1kmx1km. Quest'ultimo era un file poligonale. Ho provato la soluzione multicore ma questo era ancora troppo lento per il numero di celle della griglia che avevo. Io invece:

  1. Rasterizzato la griglia 1kmx1km dando a tutte le celle della griglia un numero univoco
  2. Utilizzato allign_rasters (o gdalwarp direttamente) dal pacchetto gdalUtils con l'opzione r = "near" per aumentare la risoluzione della griglia 1kmx1km a 300mx300m, stessa proiezione ecc.
  3. Impila la mappa di copertura del suolo 300mx300m e la griglia 300mx300m dal passaggio 2, usando il pacchetto raster: stack_file <- stack (lc, grid).
  4. Crea un data.frame per combinare le mappe: df <- as.data.frame (rasterToPoints (stack_file)), che mappa i numeri di celle della griglia della mappa 1kmx1km sulla mappa di copertura del suolo 300mx300m
  5. Usa dplyr per calcolare la percentuale di celle della classe di copertura del suolo nelle celle 1kmx1km.
  6. Crea un nuovo raster sulla base del passaggio 5 collegandolo alla griglia 1kmx1km originale.

Questa procedura viene eseguita abbastanza rapidamente e senza problemi di memoria sul mio PC, quando l'ho provata su una mappa di copertura del suolo con celle della griglia> 15 mill a 300mx300m.

Suppongo che l'approccio sopra funzionerà anche se si desidera combinare un file poligonale con forme irregolari con dati raster. Forse, si potrebbero combinare i passaggi 1 e 2 rasterizzando direttamente il file poligonale su una griglia 300mx300 usando rasterize (raster probabilmente lento) o gdal_rasterize. Nel mio caso avevo anche bisogno di riproiettare, quindi ho usato gdalwarp sia per riproiettare che per disaggregare allo stesso tempo.


0

Devo affrontare questo stesso problema per estrarre i valori all'interno del poligono da un grande mosaico (50k x 50k). Il mio poligono ha solo 4 punti. Il metodo più veloce che ho trovato è fare un cropmosaico nel limite del poligono, triangolare il poligono in 2 triangoli, quindi controllare se i punti nel triangolo (l'algoritmo più veloce che ho trovato). Confronta con la extractfunzione, il tempo di esecuzione è ridotto da 20 secondi a 0,5 secondi. Tuttavia, la funzione croprichiede ancora circa 2 s per ciascun poligono.

Spiacenti, non posso fornire l'esempio riproducibile completo. I codici R di seguito non includono i file di input.

Questo metodo funziona solo per poligoni semplici.

par_dsm <- function(i, image_tif_name, field_plys2) {
    library(raster)
    image_tif <- raster(image_tif_name)
    coor <- field_plys2@polygons[[i]]@Polygons[[1]]@coords
    ext <- extent(c(min(coor[,1]), max(coor[,1]), min(coor[,2]), max(coor[,2])))

    extract2 <- function(u, v, us, vs) {
        u1 <- us[2]  - us[1]
        u2 <- us[3]  - us[2]
        u3 <- us[1]  - us[3]
        v1 <- vs[1]  - vs[2]
        v2 <- vs[2]  - vs[3]
        v3 <- vs[3]  - vs[1]
        uv1 <- vs[2] * us[1] - vs[1] * us[2]
        uv2 <- vs[3] * us[2] - vs[2] * us[3]
        uv3 <- vs[1] * us[3] - vs[3] * us[1]

        s1 <- v * u1 + u * v1 + uv1
        s2 <- v * u2 + u * v2 + uv2
        s3 <- v * u3 + u * v3 + uv3
        pos <- s1 * s2 > 0 & s2 * s3 > 0
        pos 
    }

    system.time({
        plot_rect <- crop(image_tif, ext, snap ='out')
        system.time({
        cell_idx <- cellFromXY(plot_rect, coor[seq(1,4),])
        row_idx <- rowFromCell(plot_rect, cell_idx)
        col_idx <- colFromCell(plot_rect, cell_idx)

        rect_idx <- expand.grid(lapply(rev(dim(plot_rect)[1:2]), function(x) seq(length.out = x)))

        pixel_idx1 <- extract2(
            rect_idx[,2], rect_idx[,1], 
            row_idx[c(1,2,3)], col_idx[c(1,2,3)])
        pixel_idx2 <- extract2(
            rect_idx[,2], rect_idx[,1], 
            row_idx[c(1,4,3)], col_idx[c(1,4,3)])
        pixel_idx <- pixel_idx1 | pixel_idx2
        })
    })
    mean(values(plot_rect)[pixel_idx])
}

# field_plys2: An object of polygons
# image_taf_name: file name of mosaic file
library(snowfall)
sfInit(cpus = 14, parallel = TRUE)
system.time(plot_dsm <- sfLapply(
    seq(along = field_plys2), par_dsm, image_tif_name, field_plys2))
sfStop()
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.