vignettes/usage_and_limitations.Rmd
usage_and_limitations.Rmd
To install the package:
devtools::install_github("leonawicz/mapmate")
The mapmate
package is used for map- and globe-based data animation pre-production. Specifically, mapmate
functions are used to generate and save to disk a series of map graphics that make up a still image sequence, which can then be used in video editing and rendering software of the user’s choice. This package does not make simple animations directly within R, which can be done with packages like animation
. mapmate
is more specific to maps, hence the name, and particularly suited to 3D globe plots of the Earth.
The motivation for the package is to assist with heavy data processing and image production for large, complex data animation videos that can require a significant amount of computing resources to create. It is best suited to use on a Linux server with a significant number of CPU cores and a significant amount of RAM, but all examples presented here are simple and have no such requirements. mapmate
is all about video pre-production. Still image sequences created by R are meant to be rendered into video subsequently by dedicated video editing software. It makes the most sense to leverage the power of both programs for what they are best designed for: R for data analysis and plotting and something else for high quality video rendering.
As mentioned, there are other solutions for producing animations completely from R with only implicit external software dependencies (e.g., using the rgl
package or relying on ImageMagick convert
). The only restriction is that you have to want to create an animation that is small and simple enough for other solutions to remain realistic options. At some point the size of data or the images or the total number of frame or some other limitation may creep in and stop you if your animation is just simply too big or complex. At that point, this package has the value of being dedicated to generating still image sequences to pass to another program. If you ever make videos which require the use of video editing software, then you will know at what point it makes sense to just make still image sequences that you can drop on your timeline to finish your video project rather than trying to force R to do the entire job itself.
Nevertheless, while the strength of R for pre-production lies in its data analysis, processing and graphing capabilities, there are some limitations to how well save_map
in the mapmate
package will draw certain types of maps of certain types of data sets. The biggest limitation is in attempting to draw filled in polygons on a map using an orthographic projection; the 3D Earth globe view.
Below is a series of usage examples of save_map
applied to example data included in the package. The examples show how to make different maps based on the different arguments that can be passed to save_map
. They also show how some of the map type options available in save_map
are primarily included to reveal their current limitations in drawing maps. Points, paths and tiles tend to work well, as do contour lines for the most part, but polygons often display serious clipping issues.
This package depends heavily on ggplot2
for plotting. While it is easy enough to write a function that takes the lon/lat centroid of the visible hemisphere and returns an ordered set of lon/lat coordinates defining the circle that represents the hemisphere’s outer edge for orthographically projected data, I have not yet found a way in this context to use that circle to essentially reclip the polygons. If I could do so, then I could potentially apply the reclipping inside save_map
when ortho=TRUE
prior to the reprojection done by ggplot2::coord_map
. But not so easily, if the data are not actually projected to the orthographic projection until passed inside coord_map
. The reliance on coord_map
could cripple my ability to insert a reclipping step where I need to, but I haven’t delved into it yet since my main use of the package is not for polygons.
Lastly, since these examples are simply intended to display what kind of map outputs are made given the various arguments that can be passed to save_map
, the plot or image frame number/ID value never goes beyond 1
and save_map
is not called iteratively in any of these examples. For code related to typical map sequence generation see the introductory vignette for examples: browseVignettes(package="mapmate")
or view it here.
First some setup using the package data:
library(mapmate)
library(dplyr)
library(RColorBrewer)
pal <- rev(brewer.pal(11, "RdYlBu"))
data(annualtemps)
data(borders)
data(bathymetry)
id <- "frameID"
temps <- mutate(annualtemps, frameID = Year - min(Year) + 1) %>% filter(frameID ==
1) # subset to first frame
brdrs <- mutate(borders, frameID = 1)
bath <- mutate(bathymetry, frameID = 1)
Next for the plots. Note that the aptly named save_map
is not typically used for returning plot objects but rather for saving them to disk, hence why every call to save_map
in this set of examples must explicitly include the non-default save.plot
and return.plot
arguments.
Use ortho=TRUE
(default) or ortho=FALSE
.
save_map(temps, id = id, ortho = FALSE, col = "dodgerblue", type = "points",
save.plot = FALSE, return.plot = TRUE)
save_map(temps, id = id, col = "#FF4500", type = "points", save.plot = FALSE,
return.plot = TRUE)
The previous examples use point data. To confirm this works with points everywhere, depending on the resolution of the source data, here is another example using the bathymetry surface with type="points"
.
save_map(bath, id = id, type = "points", save.plot = FALSE, return.plot = TRUE)
type="maplines"
relies on ggplot2::geom_path
internally, which is why it is suited to drawing ordered polygon border lines and why the original borders
data frame contains the necessary additional variables for grouping and plotting order. While the prepared data frames used in this set of examples are provided by the package, typically you would generate these yourself from your data which were originally in another form such as SpatialPolygonsDataFrame
or RasterLayer
objects.
A common way to do this is by using ggplot2::fortify
or, alternatively, duplicated functionality in the broom
package. See those packages for examples or documentation. Further below I include one example “from scratch” that shows an instance of using fortify
to make the transition from a SpatialPolygonsDataFrame
to a “fortified” data frame that is ready to be ingested by ggplot
inside save_map
like the already fortified example data frames provided by mapmate
.
save_map(brdrs, id = id, type = "maplines", save.plot = FALSE, return.plot = TRUE)
While we’re here, let’s also change the view using lon
, lat
, and rotation.axis
.
save_map(brdrs, id = id, lon = -70, lat = 40, rotation.axis = 0, type = "maplines",
save.plot = FALSE, return.plot = TRUE)
In the previous examples there was no need to provide a third variable, a data variable. The plots are based on spatial locations only, whether points or paths. For gridded data, a z
variable is needed and is specified with the z.name
argument.
save_map(bath, z.name = "z", id = id, col = pal, type = "maptiles", save.plot = FALSE,
return.plot = TRUE)
type="density"
combines both 2D density and 3D contour mapping under a single heading for convenience and based on the default map appearance, but it is important to remember the distinction. The previous example using type="maptiles"
is actually a special case and matches the plot output associated with the default arguments that are relevant to type="density"
.
Default arguments include contour="none"
and density.geom="tile"
.
save_map(bath, z.name = "z", id = id, col = pal, type = "density", save.plot = FALSE,
return.plot = TRUE)
Explicit contour lines can also be overlaid with the base map type.
save_map(bath, z.name = "z", id = id, col = pal, type = "density", contour = "overlay",
save.plot = FALSE, return.plot = TRUE)
Contour lines can also be drawn without including the base layer indicated by type
.
save_map(bath, z.name = "z", id = id, col = pal, type = "density", contour = "only",
save.plot = FALSE, return.plot = TRUE)
As a brief aside, these options also work for type="points"
. Remember that these contour lines are based on the spatial density of lon/lat coordinates, unlike the previous contour lines examples that make use of the data variable.
save_map(temps, id = id, col = "red", type = "points", contour = "overlay",
save.plot = FALSE, return.plot = TRUE)
save_map(temps, id = id, col = "blue", type = "points", contour = "only", save.plot = FALSE,
return.plot = TRUE)
For type="points"
, the color col
applies to both points and contour lines, but for type="density"
col
applies to the density map. Contour line overlays will remain as black lines. The exception is when contour="only"
, in which case col
may still be a vector defining a color palette that will be applied to the contour lines in place of the underlying filled density map.
True density maps based on lon/lat locations rather than filled contours based on three variables can also be plotted by excluding z.name
. The first call below includes z.name="z"
and the second does not. As expected, these are completely different maps because they use fundamentally different data in different ways.
save_map(temps, z.name = "z", id = id, col = pal, type = "density", contour = "overlay",
save.plot = FALSE, return.plot = TRUE)
save_map(temps, id = id, col = pal, type = "density", contour = "overlay", save.plot = FALSE,
return.plot = TRUE)
Of course, this would not be interesting to do when the data frame contains gridded data at all lon/lat locations on the world grid like in bathymetry
.
save_map(bath, id = id, lon = -70, lat = 50, col = pal, type = "density", contour = "overlay",
save.plot = FALSE, return.plot = TRUE)
Relying internally on ggplot2::geom_polygon
inside save_map
can be much faster than ggplot::geom_tile
to process the data and make maps. This is an important consideration for generating long sequences of many high-resolution maps. However, the trade off is that polygons may be drawn very poorly with much clipping, the degree of the problem depending on the given data. Of course, if tiles are drawn at an extremely coarse resolution based on the input data, they still won’t look good, and also won’t take as long to draw. It is what you decide to make of it with the data you choose to map. The below examples compare the use of tiles to polygons.
save_map(temps, z.name = "z", id = id, col = pal, type = "density", contour = "overlay",
density.geom = "tile", save.plot = FALSE, return.plot = TRUE)
save_map(temps, z.name = "z", id = id, col = pal, type = "density", contour = "overlay",
density.geom = "polygon", save.plot = FALSE, return.plot = TRUE)
save_map(bath, z.name = "z", id = id, col = pal, type = "density", contour = "overlay",
density.geom = "tile", save.plot = FALSE, return.plot = TRUE)
save_map(bath, z.name = "z", id = id, col = pal, type = "density", contour = "overlay",
density.geom = "polygon", save.plot = FALSE, return.plot = TRUE)
Tiles may also interpolate where possible, but not extrapolate, which may not be desired behavior for a map; e.g., when drawing the continents and interpolating between them but still excluding the poles. It can lead to confusion about what is the underlying data. Yet polygons may still perform much worse, even in flat maps, as shown below.
save_map(temps, id = id, col = pal, type = "density", contour = "overlay", density.geom = "tile",
save.plot = FALSE, return.plot = TRUE)
save_map(temps, id = id, col = pal, type = "density", contour = "overlay", density.geom = "polygon",
save.plot = FALSE, return.plot = TRUE)
save_map(temps, id = id, col = pal, type = "density", ortho = FALSE, contour = "overlay",
density.geom = "tile", save.plot = FALSE, return.plot = TRUE)
save_map(temps, id = id, col = pal, type = "density", ortho = FALSE, contour = "overlay",
density.geom = "polygon", save.plot = FALSE, return.plot = TRUE)
This is also apparent when using the simpler options type="polygons"
and type="maptiles"
. The former relies on a SpatialPolygonsDataFrame
object as the data source and the later on a RasterLayer
. Here examples of each are compared and they are prepped from scratch using the appropriate class objects. Remember that these two map type options are essentially special cases of the density type option.
# polygons
library(rworldmap)
library(rworldxtra) # required for 'high' resolution map
library(maptools) # required for fortify to work
# also recommend installing rgeos
spdf <- joinCountryData2Map(countryExData, mapResolution = "high")
spdf@data$id <- rownames(spdf@data)
bio <- ggplot2::fortify(spdf, region = "id") %>% left_join(subset(spdf@data,
select = c(id, BIODIVERSITY)), by = "id") %>% mutate(frameID = 1) %>% rename(lon = long)
# raster layer
library(raster)
proj4 <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +to wgs84=0,0,0"
z <- "BIODIVERSITY"
# 1-degree resolution, still somewhat coarse
r <- raster(extent(-180, 180, -90, 90), nrow = 180, ncol = 360, proj4)
bio2 <- rasterize(spdf, r, field = z) %>% rasterToPoints %>% tbl_df() %>% setNames(c("lon",
"lat", z)) %>% mutate(frameID = 1)
clrs <- c("royalblue", "purple", "orange", "yellow")
save_map(bio, z.name = z, id = id, lon = -10, lat = 20, col = pal, type = "polygons",
save.plot = FALSE, return.plot = TRUE)
save_map(bio2, z.name = z, id = id, lon = -10, lat = 20, col = pal, type = "maptiles",
save.plot = FALSE, return.plot = TRUE)
In conclusion, there are a variety of methods available to draw different types of data on the globe from any arbitrary perspective. Flat maps are also an option but this is not as interesting so it is not focused on. The primary uses of save_map
among what is shown here are for plotting points, polygon borders and tiles. The rest are interesting options and can work well enough for specific use cases but are included here mainly to highlight their current limitations.
The one option not covered here (I will add this in a future update) is the type="network"
option, which is used for plotting a time series of maps/globes showing moving great circle arcs. This is my personal favorite use case, but it is also the most complex so I am yet to fold code for that into this package and incorporate great circle arc network examples into the various help documentation, vignettes and tutorials. Something more to look forward to.