Animate raster maps with ggplot and gifski
Create a GIF image from raster layers
Downloanding the data sets
The data used in this post comes from the WorldClim project, and can be retrieved here. Note that one of the authors of the project is professor Robert J. Hijmans, he is also responsible for the raster package.
Of course, some of the WorldClim project data is available through the raster package…
library(raster, quietly = TRUE)
wc_prec_series <- getData(name = "worldclim",
var = 'prec', # variable
res = 10, # spatial resolution
lon = 5, lat = 45,
download = TRUE)
wc_prec_series
## class : RasterStack
## dimensions : 900, 2160, 1944000, 12 (nrow, ncol, ncell, nlayers)
## resolution : 0.1666667, 0.1666667 (x, y)
## extent : -180, 180, -60, 90 (xmin, xmax, ymin, ymax)
## crs : +proj=longlat +datum=WGS84 +no_defs
## names : prec1, prec2, prec3, prec4, prec5, prec6, prec7, prec8, prec9, prec10, prec11, prec12
## min values : 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
## max values : 885, 736, 719, 820, 955, 1850, 2088, 1386, 904, 980, 893, 914
The getData
function, with the parameters used in the chunk above, downloads a RasterStack with 12 layers!
From the package documentation:
“The data is available at the four spatial resolutions, between 30 seconds (~1 km2) to 10 minutes (~340 km2). Each download is a “zip” file containing 12 GeoTiff (.tif) files, one for each month of the year (January is 1; December is 12)." (Fick, 2017)
Note that, for the gif at the beginning of this post, we used historical monthly weather data not available through the raster package. .
Let’s visualize one of the layers:
library(raster, quietly = TRUE)
raster::plot(wc_prec_series$prec7,
main = "Fig.1 - Average Precipitation in July from 1970 to 2000")
We can see, from the values parameter, that some regions achieved more than 1500 mm average precipitation in July (prec7).
Note that the raster package also comes with a animate
function built in:
raster::animate(wc_prec_series,
n = 1, # number of loops
main = paste("Average Precipitation in",
month.name, "from 1970 to 2000"))
The animate
function takes a RasterStack or a RasterBrick and generates a gif using each layer as frames. Even though this is a very easy to use function, it lacks more options for editing and exporting.
So, now that we have our data, we might want to choose a location of interest (loi), e.g., a country. For that, we use the rnaturalearth
package.
library(rnaturalearth, quietly = TRUE)
india <- rnaturalearth::ne_countries(country = "India", returnclass = "sf")
We choose India that has accentuated dry and wet seasons, for better contrast.
Crop to extent
The next step consists of cropping the data to the specified loi, using the shape extent as reference. To do that correctly we must assure that the raster layers and the reference map have the same Coordinate Reference System (CRS). For that, we use the sf package that already incorporate PROJ4 updates to CRS parameters. When possible, is always wiser to reproject vector data instead of rasters.
# Matching the CRS projections
require(sf, quietly = TRUE)
# Transform CRS of reference maps
india <- st_transform(india, crs = st_crs(wc_prec_series))
# Crop to extent
extent(india)
## class : Extent
## xmin : 68.17665
## xmax : 97.40256
## ymin : 7.965535
## ymax : 35.49401
india_wc_prec <- crop(wc_prec_series, india)
plot(india_wc_prec, main = "Fig.2")
Transform to tables
In order to use ggplot
, we need to transform the raster layers to tables.
require(tidyr, quietly = TRUE)
##
## Attaching package: 'tidyr'
## The following object is masked from 'package:raster':
##
## extract
rasterStack_tables <- list()
for (i in seq(india_wc_prec@data@nlayers)) {
rasterStack_tables[[i]] <- as.data.frame(india_wc_prec[[i]], xy = TRUE) %>%
drop_na()
}
A few auxiliary lists are used to indicate the directory for saving, the months of each frame, and the file names. We use the pad function from stringr
to add a zero to any one digit number. This step guarantees that the files will be in the correct order for the animation.
# Create directory
dir.create("./png")
# List of dates for labeling
dates <- month.name
# Create list of file names for writing png
library(stringr, quietly = TRUE)
files <- paste0("./png/", "prec",
stringr::str_pad(1:12, width=2, side="left", pad="0"),
".png")
One last step before plotting consists of binding the tables created from each layer in order to calculate basic summary statistics of the entire data set. Specifically, we wish to calculate the maximum precipitation value to set the legend scale correctly. Otherwise, the scale will vary accordingly to the maximum value of each month, and that gives a false contrast (see Fig.2).
tables <- data.table::rbindlist(rasterStack_tables, use.names = FALSE)
summary(tables[,3])
## prec1
## Min. : 0.00
## 1st Qu.: 6.00
## Median : 22.00
## Mean : 80.82
## 3rd Qu.: 93.00
## Max. :2088.00
Plot
We plot filling pixels values with the precipitation data on the third column of each table. The first and second columns are longitude and latitude of each pixel. Note that by adding the scale_fill_viridis_c
we are able to set the scale to a fixed maximum limit.
library(ggplot2)
library(ggspatial)
for (i in seq(rasterStack_tables)) {
ggplot() +
geom_tile(aes(x = x, y = y, fill = rasterStack_tables[[i]][,3]),
data = rasterStack_tables[[i]]) +
geom_sf(color = "azure2", fill = 'transparent', data = india) +
scale_fill_viridis_c(dates[i], direction = -1, limits = c(0, 2088.00)) + # set scale
coord_sf(expand = FALSE) +
labs(x = 'Longitude', y = 'Latitude',
title = "Climate Indices",
subtitle = 'Monthly Historical Total Precipitation (mm)',
caption = 'Source: WorldClim, 2020') +
theme(panel.grid.major = element_line(color = "lightblue",
linetype = 'dashed',
size = .125),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = NA, color = 'aliceblue'),
panel.ontop = FALSE) +
annotation_scale(location = "br", width_hint = 0.25) +
annotation_north_arrow(location = "br", which_north = "true",
height = unit(0.85, "cm"), width = unit(0.85, "cm"),
pad_x = unit(0.825, "cm"), pad_y = unit(0.825, "cm"),
style = north_arrow_fancy_orienteering)
ggsave(files[i], dpi = "screen")
}
Animate
Create the gif providing the list of plots (png files).
library(gifski)
gifski(list.files("./png/", full.names = TRUE),
"./india_wc_prec.gif", loop = TRUE, delay = .5)
Valeu!
Citations
Fick, S.E. and R.J. Hijmans, 2017. WorldClim 2: new 1km spatial resolution climate surfaces for global land areas. International Journal of Climatology 37 (12): 4302-4315.