# Speeding up plotting of polygons in R

I want to plot the country borders of North America over a raster image depicting some variable and then overlay contours on top of the plot using R. I have been successful in doing this using base graphics and lattice, but it seems that the plotting process is much too slow! I have not done this in ggplot2 yet, but I doubt that it will fare better in terms of speed.

I have the data in a netcdf file created from a grib file. For now, I downloaded the country borders for Canada, USA and Mexico, which were available in RData files from GADM which read into R as SpatialPolygonsDataFrame objects.

Here is some code:

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

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

### USING LATTICE
library(rasterVis)

# Some settings for our themes
myTheme <- RdBuTheme()
myTheme\$axis.line\$col<-"transparent"
myTheme2 <- myTheme
myTheme2\$regions\$col <- 'transparent'

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

Is there a way to speed up the plotting of the polygons?

On the system that I am working on, the plotting takes several minutes. I want to eventually make a function that will easily generate a number of these plots for inspection, and I reckon I will be plotting many of these maps, so I want to increase the speed of the plots!

I found 3 ways to increase the speed of plotting the country borders from shape files for R. I found some inspiration and code from here and here.

(1) We can extract the coordinates from the shape files to get the longitude and latitudes of the polygons. Then we can put them into a data frame with the first column containing the longitudes and the second column containing latitudes. The different shapes are separated by NAs.

(2) We can remove some polygons from our shape file. The shape file is very, very detailed, but some of the shapes are tiny islands that are unimportant (for my plots, anyways). We can set a minimum polygon area threshold to keep the bigger polygons.

(3) We can simplify the geometry of our shapes using the Douglas-Peuker algorithm. The edges of our polygon shapes can be simplified, as they are very intricate in the original file. Fortunately, there is a package, `rgeos`, that implements this.

Set up:

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

``````

Method 1: Extract the coordinates from the the shape files into a data frame and plot lines

The major disadvantage is that we lose some information here when compared to keeping the object as a SpatialPolygonsDataFrame object, such as the projection. However, we can turn it back into an sp object and add back the projection information, and it is still faster than plotting the original data.

Note that this code runs very slowly on the original file because there are a lot of shapes, and the resulting data frame is ~2 million rows long.

Code:

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

Method 2: Remove small polygons

There are many small islands that are not very important. If you check some of the quantiles of the areas for the polygons, we see that many of them are miniscule. For the Canada plot, I went down from plotting over a thousand polygons to just hundreds of polygons.

Quantiles for the size of polygons for Canada:

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

Code:

``````# Get the main polygons, will determine by area.
removeSmallPolys <- 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)
}
``````

Method 3: Simplify the geometry of the polygon shapes

We can reduce the number of vertices in our polygon shapes using the `gSimplify` function from the `rgeos` package

Code:

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

Some benchmarks:

I used elapsed `system.time` to benchmark my plotting times. Note that these are just the times for plotting the countries, without the contour lines and other extra things. For the sp objects, I just used the `plot` function. For the data frame objects, I used the `plot` function with `type='l'` and the `lines` function.

Plotting the original Canada, USA, Mexico polygons:

73.009 seconds

Using Method 1:

2.449 seconds

Using Method 2:

17.660 seconds

Using Method 3:

16.695 seconds

Using Method 2 + 1:

1.729 seconds

Using Method 2 + 3:

0.445 seconds

Using Method 2 + 3 + 1:

0.172 seconds

Other remarks:

It seems that the combination of methods 2 + 3 gives sufficient speed ups to the plotting of polygons. Using methods 2 + 3 + 1 adds the problem of losing the nice properties of `sp` objects, and my main difficulty is applying projections. I hacked something together to project a data frame object, but it runs rather slow. I think using method 2 + 3 provides sufficient speed ups for me until I can get the kinks out of using method 2 + 3 + 1.

Everyone should look into transfering over to sf (spatial features) package instead of sp. It is significantly faster (1/60th in this case) and easier to use. Here is an example of reading in a shp and plotting via ggplot2.

Note: You need to reinstall ggplot2 from the most recent build on github (see below)

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

td <- file.path(tempdir(), "rgdal_examples"); dir.create(td)
st_write(st_as_sf(can),file.path(td,'can.shp'))

ptm <- proc.time()
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()
``````library(maptools)
{"ft_lang_label":"__label__en","ft_lang_prob":0.6098894,"math_prob":0.9553347,"size":3191,"snap":"2023-40-2023-50","text_gpt3_token_len":995,"char_repetition_ratio":0.10103545,"word_repetition_ratio":0.0,"special_character_ratio":0.3183955,"punctuation_ratio":0.18930636,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9972177,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2023-10-03T20:59:02Z\",\"WARC-Record-ID\":\"<urn:uuid:7c341a95-b70a-4f2a-b67b-1fdc6bcc9ed6>\",\"Content-Length\":\"184502\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:cc9896c8-c321-48b8-acf3-8a80d3c7d8b4>\",\"WARC-Concurrent-To\":\"<urn:uuid:44bf16cd-8824-41e0-beb0-86e35b9578b2>\",\"WARC-IP-Address\":\"104.18.11.86\",\"WARC-Target-URI\":\"https://gis.stackexchange.com/questions/62292/speeding-up-plotting-of-polygons-in-r/62405\",\"WARC-Payload-Digest\":\"sha1:NIS2SORAXEHZBEZBEGYHE5YNABML7VKF\",\"WARC-Block-Digest\":\"sha1:BO6DIBZTJO3R7OF2AV7H552GA6Z7LU36\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2023/CC-MAIN-2023-40/CC-MAIN-2023-40_segments_1695233511220.71_warc_CC-MAIN-20231003192425-20231003222425-00516.warc.gz\"}"}