¿Velocidad creciente de recorte, máscara y ráster de extracción por muchos polígonos en R?

29

Extraigo el área y el porcentaje de cobertura de diferentes tipos de uso de la tierra de un ráster basado en varios miles de límites de polígonos. Descubrí que la función de extracción funciona mucho más rápido si recorro cada polígono individual y recorto y luego enmascaro el ráster hasta el tamaño del polígono en particular. Sin embargo, es bastante lento, y me pregunto si alguien tiene alguna sugerencia para mejorar la eficiencia y la velocidad de mi código.

Lo único que he encontrado relacionado con esto es esta respuesta de Roger Bivand, quien sugirió usar GDAL.open()y GDAL.close()así como getRasterTable()y getRasterData(). Los investigué, pero he tenido problemas con gdal en el pasado y no lo sé lo suficiente como para saber cómo implementarlo.

Ejemplo reproducible:

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) 

Método más rápido hasta ahora

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 

Procesamiento en paralelo

El procesamiento paralelo reduce el tiempo del usuario a la mitad, pero niega el beneficio al duplicar el tiempo del sistema. Raster usa esto para la función de extracción, pero desafortunadamente no para la función de recorte o máscara. Desafortunadamente, esto deja una cantidad ligeramente mayor de tiempo total transcurrido debido a la "espera" del "IO".

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

ejecutar código en múltiples núcleos:

  user  system elapsed 
  23.31    0.68   42.01 

luego termina el clúster

endCluster()

Método lento: el método alternativo para hacer un extracto directamente desde la función de trama tarda mucho más, y no estoy seguro acerca de la gestión de datos para obtener el formato que quiero:

system.time(ext<-extract(c,bound))
   user  system elapsed 
1170.64   14.41 1186.14 
Luke Macaulay
fuente
Puede probar este generador de perfiles de código R ( marcodvisser.github.io/aprof/Tutorial.html ). Puede decirle qué líneas toman la mayor parte del tiempo. El enlace también tiene pautas para reducir el tiempo de procesamiento en R.
J Kelly
Solo mis dos centavos aquí. . . pero el método de recorte / obtención de valores no funciona cuando el número de píxeles en el recorte es muy bajo. No estoy seguro de dónde está el límite, pero tuve problemas con los cultivos en los que solo había 1-5 píxeles (no he determinado la razón exacta por qué (poco nuevo aún para los paquetes espaciales) pero apuesto a que la función de recorte depende de los límites de píxeles, por lo que lucha por recortar píxeles individuales). El extracto del paquete ráster no tiene ese problema, pero se acordó que es más del doble del tiempo del usuario y mucho más del doble del tiempo del sistema. Sólo una advertencia a aquellos que tienen baja resolución (raster y un en
Neal Barsch
2
Hay un paquete algo nuevo, velox, que ha movido el extracto a C a través del paquete Rcpp. Está dando aumentos de ~ 10 veces en velocidad en operaciones de extracción utilizando polígonos.
Jeffrey Evans el
@JeffreyEvans. Probar la respuesta a esta pregunta usando velox ahora. Sin embargo, tener problemas con la asignación de vectores extremadamente grandes.
SeldomSeenSlim

Respuestas:

23

Finalmente pude mejorar esta función. He descubierto que para mis propósitos, era más rápida para rasterize()el polígono primera y utilizar getValues()en lugar de extract(). La rasterización no es mucho más rápida que el código original para tabular valores de trama en polígonos pequeños, pero brilla cuando se trata de áreas de polígonos grandes que tenían grandes rásteres para recortar y extraer los valores. También encontré que getValues()era mucho más rápido que la extract()función.

También descubrí el procesamiento multinúcleo usando foreach().

Espero que esto sea útil para otras personas que desean una solución R para extraer valores ráster de una superposición de polígonos. Esto es similar a la "Intersección de tabulación" de ArcGIS, que no funcionó bien para mí, devolviendo salidas vacías después de horas de procesamiento, como este usuario.

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

Aquí está la función:

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. 
  }
}

Por lo tanto, para usarlo, ajústelo single@data$OWNERpara que se ajuste al nombre de la columna de su polígono de identificación (supongo que podría haberse integrado en la función ...) y agregue:

myoutput <- multicore.tabulate.intersect(cores, polygonlist, rasterlayer)
Luke Macaulay
fuente
3
La sugerencia que getValuesfue mucho más rápida que la extractno parece válida porque si la usas extractno tienes que hacer cropy rasterize(o mask). El código en la pregunta original hace ambas cosas, y eso debería ser el doble de tiempo de procesamiento.
Robert Hijmans
la única forma de saberlo es mediante pruebas.
djas
¿Qué clase es la lista de polígonos aquí, y qué debe hacer la lista de polígonos [[i]] [, j] aquí (ELI5, por favor)? Soy novato en cosas paralelas, así que no lo entiendo muy bien. No pude obtener la función para devolver nada, hasta que cambié a polygonlist [[i]] [, j] a polygonlist [, j], lo que parece lógico porque [, j] es el elemento j de un SpatialPolygonsDataframe, si eso es la clase correcta? Después de cambiar eso, ejecuté el proceso y algunas salidas, pero definitivamente todavía hay algo mal. (Intento extraer el valor medio dentro de n polígonos pequeños, así que también cambié un poco del código).
reima
@RobertH En mi caso, el recorte (y el enmascaramiento) hace que se ejecute aproximadamente 3 veces más rápido. Estoy usando una trama de 100 millones de acres y los polígonos son una pequeña fracción de eso. Si no recorto el polígono, el proceso es mucho más lento. Aquí están mis resultados: clip1 <- crop (rasterlayer, extension (single))> system.time (ext <-extract (clip1, single)) #extracción del sistema de usuario raster recortado transcurrido 65.94 0.37 67.22> system.time (ext < -extract (rasterlayer, single)) # extracción de un sistema de usuario raster de 100 millones de acres transcurrido 175.00 4.92 181.10
Luke Macaulay
4

Acelere la extracción del ráster (pila de ráster) del punto, XY o polígono

Gran respuesta Luke. ¡Debes ser un mago R! Aquí hay un pequeño ajuste para simplificar su código (puede mejorar ligeramente el rendimiento en algunos casos). Puede evitar algunas operaciones utilizando cellFromPolygon (o cellFromXY para los puntos) y luego recortar y obtener valores.

Extraiga datos de polígonos o puntos de pilas de ráster ------------------------

 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 de usuario transcurrido 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 de usuario transcurrido 33.038 3.511 3.288

mmann1123
fuente
Ejecuté los dos enfoques y su método salió un poco más lento en mi caso de uso.
Luke Macaulay
2

Si una pérdida en la precisión de la superposición no es terriblemente importante, suponiendo que sea precisa para comenzar, normalmente se pueden lograr velocidades de cálculo zonales mucho mayores al convertir primero los polígonos en una trama. El rasterpaquete contiene la zonal()función, que debería funcionar bien para la tarea prevista. Sin embargo, siempre puede subconjugar dos matrices de la misma dimensión utilizando la indexación estándar. Si debe mantener los polígonos y no le importa el software GIS, QGIS es realmente más rápido en estadísticas zonales que ArcGIS o ENVI-IDL.

Adam Erickson
fuente
2

También luché con esto durante algún tiempo, tratando de calcular la porción de área de las clases de cobertura terrestre de un mapa de cuadrícula de ~ 300mx300m en una cuadrícula de ~ 1kmx1km. Este último era un archivo poligonal. Probé la solución multinúcleo, pero todavía era demasiado lenta para la cantidad de celdas de cuadrícula que tenía. En cambio yo:

  1. Rasterizó la cuadrícula de 1kmx1km dando a todas las celdas de la cuadrícula un número único
  2. Usé allign_rasters (o gdalwarp directamente) del paquete gdalUtils con la opción r = "near" para aumentar la resolución de la cuadrícula de 1kmx1km a 300mx300m, la misma proyección, etc.
  3. Apile el mapa de cobertura terrestre de 300mx300m y la cuadrícula de 300mx300m del paso 2, utilizando el paquete ráster: stack_file <- stack (lc, grid).
  4. Cree un data.frame para combinar los mapas: df <- as.data.frame (rasterToPoints (stack_file)), que asigna los números de celdas de la cuadrícula del mapa de 1kmx1km al mapa de cobertura del suelo 300mx300m
  5. Use dplyr para calcular la proporción de celdas de clase de cobertura del suelo en las celdas de 1kmx1km.
  6. Cree un nuevo ráster sobre la base del paso 5 al vincularlo a la cuadrícula original de 1kmx1km.

Este procedimiento se ejecuta bastante rápido y sin problemas de memoria en mi PC, cuando lo probé en un mapa de cobertura terrestre con> 15 celdas de cuadrícula de molino a 300mx300m.

Supongo que el enfoque anterior también funcionará si se desea combinar un archivo de polígono con formas irregulares con datos ráster. Quizás, uno podría combinar los pasos 1 y 2 rasterizando directamente el archivo de polígono a una cuadrícula de 300mx300 usando rasterizar (raster probablemente lento) o gdal_rasterize. En mi caso, también necesitaba volver a proyectar, así que utilicé gdalwarp para reproyectar y desagregar al mismo tiempo.

Michiel van Dijk
fuente
0

Tengo que enfrentar este mismo problema para extraer valores dentro del polígono de un gran mosaico (50k x 50k). Mi polígono solo tiene 4 puntos. El método más rápido que encontré es hacer un cropmosaico en el límite del polígono, triangular el polígono en 2 triángulos, luego verificar si los puntos en el triángulo (El algoritmo más rápido que encontré). Compare con la extractfunción, el tiempo de ejecución se reduce de 20 s a 0.5 s. Sin embargo, la función cropaún requiere aproximadamente 2 s para cada polígono.

Lo siento, no puedo proporcionar el ejemplo reproducible completo. Los códigos R a continuación no incluyen los archivos de entrada.

Este método solo funciona para polígonos simples.

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()
Usted golpear
fuente