Як пришвидшити побудову багатокутників у R?


24

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

У мене є дані у файлі netcdf, створеному з файлу grib. Наразі я завантажив кордони країни для Канади, США та Мексики, які були доступні у файлах RData від GADM, які читаються в R як об'єкти SpatialPolygonsDataFrame.

Ось код:

# 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

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

Спасибі!


просто така ідея, чи можете ви створити індекси на полі геометрії багатокутника?
Нижче Радара

@ Burton449 Вибачте, я новачок у речах, пов’язаних із картографуванням, у тому числі полігонів, проекцій тощо ... Я не розумію вашого запитання
ialm

2
Ви можете спробувати побудувати графік на іншому пристрої, крім вікна сюжету. Оберніть функції сюжету в pdf або jpeg (із пов'язаними аргументами) та виведіть один із цих форматів. Я виявив, що це значно швидше.
Джефрі Еванс

@JeffreyEvans Нічого так. Я не вважав цього. Розміщення трьох форматних файлів у вікні сюжету зайняло приблизно 60 секунд, але складання файлу зайняло лише 14 секунд. Все ще занадто повільно для вирішення поставленого завдання, але може виявитися корисним у поєднанні з деякими методами у відповіді нижче. Спасибі!
ialm

Відповіді:


30

Я знайшов 3 способи збільшити швидкість побудови меж країни з файлів форм для Р. Я знайшов деяке натхнення та код тут і тут .

(1) Ми можемо дістати координати з файлів форм, щоб отримати довготу та широти полігонів. Тоді ми можемо помістити їх у кадр даних з першим стовпцем, що містить довготи, і другим стовпцем, що містить широти. Різні форми розділені НС.

(2) Ми можемо видалити деякі багатокутники з нашого файлу форми. Файл форми дуже-дуже детальний, але деякі з фігур - це крихітні острови, які є неважливими (для моїх сюжетів, у будь-якому разі). Ми можемо встановити мінімальний поріг площі багатокутника, щоб зберегти великі багатокутники.

(3) Ми можемо спростити геометрію наших фігур за допомогою алгоритму Дугласа-Пюкера . Краї нашої форми багатокутника можна спростити, оскільки вони дуже складні в оригінальному файлі. На щастя, є пакет rgeos, який реалізує це.

Налаштування:

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

Спосіб 1: Витягніть координати з файлів форм у кадр даних та лінії графіку

Основним недоліком є ​​те, що ми втрачаємо деяку інформацію тут у порівнянні із збереженням об'єкта як об’єкту SpatialPolygonsDataFrame, такого як проекція. Однак ми можемо перетворити його назад на об'єкт sp та додати назад інформацію про проекцію, і це все-таки швидше, ніж побудова початкових даних.

Зауважте, що цей код працює дуже повільно у вихідному файлі, оскільки існує багато форм, а отриманий кадр даних становить ~ 2 мільйони рядків.

Код:

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

Спосіб 2: Видаліть маленькі багатокутники

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

Квантили розміру полігонів для Канади:

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

Код:

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

Спосіб 3: Спростіть геометрію фігур многокутника

Ми можемо зменшити кількість вершин у наших фігурах багатокутника за допомогою gSimplifyфункції з rgeosпакету

Код:

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

Деякі орієнтири:

Я використав минуло, system.timeщоб порівняти мої графіки. Зауважте, що це саме час для накреслення країн, без контурних ліній та інших зайвих речей. Для об'єктів sp я просто використав цю plotфункцію. Для об'єктів кадру даних я використовував plotфункцію type='l'і linesфункцію.

Нанесення оригінальних багатокутників Канади, США, Мексики:

73.009 секунд

Використовуючи метод 1:

2,449 секунди

Використовуючи метод 2:

17.660 секунд

Використовуючи метод 3:

16.695 секунд

Використовуючи метод 2 + 1:

1,729 секунди

Використовуючи метод 2 + 3:

0,445 секунди

Використовуючи метод 2 + 3 + 1:

0,172 секунди

Інші зауваження:

Здається, що комбінація методів 2 + 3 дає достатню швидкість збільшення графіків багатокутників. Використання методів 2 + 3 + 1 додає проблему втрати приємних властивостей spоб’єктів, і моя головна складність полягає у застосуванні проекцій. Я щось зламав, щоб спроектувати об'єкт фрейму даних, але він працює досить повільно. Я думаю, що використання методу 2 + 3 забезпечує достатню швидкість для мене, поки я не можу вийти з-за використання методу 2 + 3 + 1.


3
+1 для написання, що, без сумніву, майбутнім читачам стане корисним.
SlowLearner

3

Кожен повинен розглянути можливість передачі пакету sf (просторові особливості) замість sp. Він значно швидший (в цьому випадку на 1/60 місце) і простіший у використанні. Ось приклад читання в SHP та побудови графіку через ggplot2.

Примітка. Вам потрібно перевстановити ggplot2 з останньої збірки на github (див. Нижче)

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

Дані GADM мають дуже високу просторову роздільну здатність берегової лінії. Якщо вам не потрібно, ви можете використовувати більш узагальнений набір даних. підходи ialm дуже цікаві, але простою альтернативою є використання даних 'wrld_simpl', які постачаються із "maptools"

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

Я хотів зберегти форми в моєму наборі даних, оскільки він містив межі регіону всередині країни (наприклад, провінції та штати). Інакше я використав би карти в пакеті даних карт!
ialm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.