Збільшення швидкості врожаю, маски та витягу растру багатьма полігонами в R?


29

Я витягую з растру площу та відсоткове покриття різних типів землекористування на основі кількох тисяч меж полігону. Я виявив, що функція вилучення працює набагато швидше, якщо я повторюю кожен окремий багатокутник і обрізаю, а потім маскую растр до розміру конкретного багатокутника. Тим не менш, це досить повільно, і мені цікаво, чи є хтось із пропозицій щодо підвищення ефективності та швидкості мого коду.

Єдине, що я знайшов, що стосується цього, - це відповідь Роджера Біванда, який запропонував використовувати GDAL.open()та GDAL.close(), а також getRasterTable()і та getRasterData(). Я переглянув це, але в минулому виникли проблеми з gdal і не знаю цього досить добре, щоб знати, як його реалізувати.

Приклад відтворення:

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) 

Найшвидший метод поки що

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 

Паралельна обробка

Паралельна обробка скоротила час користувача вдвічі, але заперечила перевагу, подвоївши системний час. Растр використовує це для функції вилучення, але, на жаль, не для функції врожаю чи маски. На жаль, це залишає трохи більший обсяг часу, що минув через "очікування навколо" IO ".

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

запустити код на декількох ядрах:

  user  system elapsed 
  23.31    0.68   42.01 

потім закінчіть кластер

endCluster()

Повільний метод: Альтернативний метод виконання витягу безпосередньо з функції растру займає набагато більше часу, і я не впевнений в управлінні даними, щоб ввести його в потрібну форму:

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

Ви можете спробувати цей профайлер коду R ( marcodvisser.github.io/aprof/Tutorial.html ). Це може сказати вам, які рядки займають більшу частину часу. Посилання також має рекомендації щодо скорочення часу обробки в Р.
Дж. Келлі

Просто два мої центи тут. . . але метод обрізання / отримання значень не працює, коли кількість пікселів в обрізку дуже низька. Я не впевнений, де обмеження, але у мене виникли проблеми з урожаями, де було лише 1-5 пікселів (я не визначив точну причину, чому (трохи нове все ще для просторових пакетів), але я думаю, що функція врожаю залежить від межі пікселів, тому, таким чином, намагається обрізати будь-які окремі пікселі). Витяг із растрового пакету не має такої проблеми, але погоджено, що це більше ніж удвічі більше часу користувача та набагато більше ніж удвічі за системний час. Просто попередження для тих, хто має растри з низькою роздільною здатністю (і в
Ніл Барш

2
Існує дещо новий пакет, velox, який перемістив екстракт в C через пакет Rcpp. Це дає ~ 10-кратне збільшення швидкості на витяжних операціях з використанням багатокутників.
Джефрі Еванс

@JeffreyEvans. Тестування відповіді на це питання за допомогою velox зараз. Однак проблеми з цим виділяються надзвичайно великі вектори.
SeldomSeenSlim

Відповіді:


23

Нарешті я домігся вдосконалення цієї функції. Я виявив, що для моїх цілей це було швидше до rasterize()полігону спочатку і використовувати getValues()замість extract(). Растеризація не набагато швидша, ніж оригінальний код для підрахунку растрових значень у малих полігонах, але він світить, коли мова заходить про великі ділянки полігону, які мали обрізати великі растри та витягувати значення. Я також виявив, що getValues()набагато швидше, ніж extract()функція.

Я також з'ясував багатоядерну обробку, використовуючи foreach().

Я сподіваюся, що це корисно для інших людей, які хочуть рішення R для вилучення растрових значень із багатокутника. Це схоже на "Табличне перехрестя" ArcGIS, яке мало працювало для мене, повертаючи порожні виходи після годин обробки, як у цього користувача.

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

Ось функція:

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

Отже, щоб скористатись цим, відрегулюйте, single@data$OWNERщоб він відповідав імені стовпця вашого ідентифікаційного полігону (здогадуйтесь, що він міг бути вбудований у функцію ...) та введіть:

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

3
Пропозиція, яка getValuesбула набагато швидшою, ніж extractне здається дійсною, тому що якщо ви користуєтеся extract, не потрібно робити cropі rasterize(або mask). Код в оригінальному питанні робить і те, і інше, приблизно, подвійний час обробки.
Роберт Хіджманс

єдиний спосіб дізнатися - це тестування.
джас

Який клас тут є багатокутником, і що тут повинен робити полігоніст [[i]] [, j] (ELI5, будь ласка)? Я новачок у паралельних речах, тому я не дуже добре це розумію. Я не міг отримати функцію повернення нічого, доки я не змінив полігоніст [[i]] [, j] на полігоніст [, j], що здається логічним, тому що [, j] є j-м елементом SpatialPolygonsDataframe, якщо це правильний клас? Після зміни, я запустив процес і кілька результатів, але тут все одно щось не так. (Я намагаюся отримати середнє значення всередині n малих полігонів, тому я теж змінив трохи коду).
reima

@RobertH У моєму випадку обрізка (та маскування) робить її запуск приблизно в 3 рази швидше. Я використовую растр на 100 мільйонів акрів, і багатокутники - це невелика частка цього. Якщо я не обрізаю багатокутник, процес проходить набагато повільніше. Ось мої результати: clip1 <- crop (rasterlayer, масштаб (одиночний))> system.time (ext <-екстракт (clip1, одиночний)) # екстрагування з обрізаної растрової системи користувача минуло 65,94 0,37 67,22> system.time (ext < -екстракція (растровий, одиночний)) # витяг із системи користування растром на 100 мільйонів акрів минув 175,00 4,92 181,10
Люк

4

Прискорити вилучення растру (растровий стек) з точки, XY або полігону

Чудова відповідь Лука. Ви повинні бути майстром R! Ось дуже незначна настройка для спрощення коду (може дещо покращити продуктивність). Ви можете уникнути деяких операцій, використовуючи cellFromPolygon (або cellFromXY для точок), а потім відсікаючи та getValues.

Витяг даних з багатокутника або точок із растрових стеків ------------------------

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

користувальницька система пройшла 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()

система користувачів 33.038 3.511 3.288


Я застосував два підходи, і ваш метод вийшов трохи повільніше в моєму випадку використання.
Люк Маколей

2

Якщо втрата в точності накладення не є надзвичайно важливою - якщо припустити, що це точно з початку - зазвичай можна досягти набагато більших зональних швидкостей обчислення, попередньо перетворивши багатокутники в растр. rasterПакет містить zonal()функцію, яка повинна добре працювати для наміченого завдання. Однак ви завжди можете підмножити дві матриці одного розміру, використовуючи стандартну індексацію. Якщо вам потрібно підтримувати багатокутники, і ви не заперечуєте проти програмного забезпечення GIS, QGIS насправді повинен бути швидшим при зональній статистиці, ніж ArcGIS або ENVI-IDL.


2

Я також боровся з цим деякий час, намагаючись обчислити частку площі класів земельного покриву на сітці ~ 300мх300м у сітці ~ 1кмх1км. Останній являв собою файл багатокутника. Я спробував багатоядерне рішення, але це все ще було надто повільно для кількості ячеїчних сіток. Натомість я:

  1. Розподілили сітку розміром 1 кмх1 км, давши унікальним числом для всіх комірок сітки
  2. Використовуйте allign_rasters (або gdalwarp безпосередньо) з пакету gdalUtils з опцією r = "біля", щоб збільшити роздільну здатність сітки 1kmx1km до 300mx300m, тієї ж проекції тощо.
  3. Складіть карту наземного покриву 300mx300m та сітку 300mx300m з кроку 2, використовуючи растровий пакет: stack_file <- stack (lc, grid).
  4. Створіть data.frame для комбінування карт: df <- as.data.frame (rasterToPoints (stack_file)), який відображає номери комірок сітки карти 1kmx1km на карту наземного покриву 300mx300m
  5. Використовуйте dplyr для обчислення частки клітин класу наземного покриву в клітинах 1kmx1km.
  6. Створіть нову растру на основі кроку 5, пов’язавши її з початковою сіткою 1kmx1km.

Ця процедура працює досить швидко і без проблем із пам’яттю на моєму ПК, коли я спробував її на карті наземного покриття з> 15-міліметровими сітками на 300мх300м.

Я припускаю, що підхід, описаний вище, також спрацює, якщо хочеться комбінувати файл багатокутника неправильної форми з растровими даними. Можливо, можна поєднати крок 1 і 2 шляхом прямого растрування файла багатокутника до сітки 300mx300, використовуючи rasterize (растр, ймовірно, повільний) або gdal_rasterize. У моєму випадку мені потрібно було також перепроектувати, тому я використовував gdalwarp і для перепроектування, і для роз'єднання одночасно.


0

Мені доводиться стикатися з цією ж проблемою, щоб добути значення у багатокутнику з великої мозаїки (50k x 50k). Мій багатокутник має лише 4 бали. Найшвидший метод, який я знайшов, - це cropмозаїка зв'язати багатокутник, трикутник багатокутника на 2 трикутники, а потім перевірити, чи є точки в трикутнику (Найшвидший алгоритм, який я знайшов). Порівняйте з extractфункцією, час роботи скорочується з 20 с на 0,5 с. Однак функція cropвсе ж потребує приблизно 2 с для кожного багатокутника.

На жаль, не можу навести повний відтворюваний приклад. Коди R нижче не включають вхідні файли.

Цей метод працює лише для простих багатокутників.

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.