A Map of our Drive to Nicaragua - Part 1: Making the Map
Constructing a Simple Map in R with KML Data and the maps Package
For some time, I wanted to take the route we drove from Colorado to Nicaragua, and animate it. I was inspired to do so from the animation of the Colorado River in the recent Colorado River drought visualization project. Since this project provided the inspiration, I mirrored their approach and created an animated an SVG. As I completed the animation, it seemed as though there are two, somewhat separate, parts to the project: (1) creating an SVG of the map in R and (2) using R, and JavaScript, to animate the driving route. As such, I have split the posts into two parts. Part 1 focuses on making the map, while Part 2 focuses on how to animate the SVG.
In Part 1, we combine KML data from Google Maps with the maps package to create an SVG that we can later animate.
Why?
As stated above, the overall goal is to animate the route we took while driving from Colorado to Nicaragua. Though, as Part 1 focuses on making the map, there are a myriad of reasons one might need to combine KML data with existing base maps in R.
How?
In a nutshell, we:
- Export the route we took from an existing Google Map to KML files.
- Read the KML files into R, and convert them to
SpatialLines
. - Create the background map.
- Transform the map and route to an appropriate projection.
- Style the map.
- Save as an SVG.
For this process, we need the following packages:
library(sp)
library(maptools)
library(magrittr) # for pipe function
library(maps)
library(rgeos) # for gSimplify
Creating SpatialLines from KML Files
After exporting the route we took from our existing Google Map, we read the KML data into R. The maptools package provides a nice way to read in the coordinates from the KML file, which is all we need for this project. In Google Maps, multiple layers are necessary to create the entire route because there is a limitation that only allows 11 way points in each layer. To export the layers, you can create one big KML file or one file for each layer. There were a few layers from Google Maps we did not need and we needed to make sure the coordinates were appended in the correct order. As such, I thought it was simpler to read in multiple KML files rather than trim one giant KML file.
The warning messages of the following code have been suppressed, but it does results in a warning that there is an “incomplete final line” in each KML file.
# kml files to read in, in the order they should be read in
kmlFiles <- paste0(c('DrivingRoute1','DrivingRoute2', 'Ferry',
paste0('DrivingRoute',3:6)),'.kml')
r1 <- do.call(rbind, lapply(kmlFiles, function(x)
as.data.frame(maptools::getKMLcoordinates(paste0('data/',x),TRUE)[[1]])))
names(r1) <- c('long','lat')
The coordinates in the KML files from Google Maps and the maps package use longitude and latitude with the WGS84 datum; this corresponds to EPSG code 4326. It took a little reading and more guess and test work to find a projection that looked decent for the U.S., Mexico, and Central America. Eventually, I selected the Lambert Conformal Conic (LCC) projection for this map. The documentation for the EPSG code lists the parameters that must be set for this projection, and I deferred to other guidance to determine these parameter values.
Both the WGS84 datum and the LCC parameter values are passed to the Coordinate Reference System (CRS
) function and assigned to defProj
and myProj
, respectively, for later use in transforming both the route and the background map to the LCC projection.
defProj <- sp::CRS('+init=epsg:4326') # default datum
# the lambert conformal conic, set based on sites recomendations
myProj <- sp::CRS('+proj=lcc +lat_1=32 +lat_2=44 +lat_0=38 +lon_0=-100 +x_0=False +y_0=False')
The Lambert Azimuth Equal Area projection centered on Mexico City, also looked nice. The code for using this projection is: sp::CRS('+proj=laea +lat_0=19 +lon_0=-99 +x_0=False +y_0=False')
Now that we know what projection system we are starting from, and going to, we convert the KML data into a SpatialLines
object, and transform it to the LCC projection.
# create spatial lines from the kml data
driveLine <- sp::Line(r1) %>% list() %>% sp::Lines(ID='drive-line') %>%
list() %>% sp::SpatialLines(proj4string = defProj) %>%
sp::spTransform(myProj) %>%
rgeos::gSimplify(tol = 500) # arbitrarily chosen tolerance
It is important to simplify the line using rgeos::gSimplify
. I think this is because the data may exist at a different precision in the different KML files. Before I added this in, the animation appeared jerky and slow in different areas.
Creating the Background Map
Now, we create the background map, using the maps package. It is not clear from the help file, but the fill = TRUE
parameter is necessary so that each of the states/countries is created as its own polygon. Without this, maptools::map2SpatialPolygons
will return an error.
After some playing around with the map, it seemed like including the entire U.S. provides the most visually appealing map, so that is what is done here. We create a map of all the states, convert them to SpatialPolygons
, and transform it to the LCC projection. Then, we follow the same steps for Mexico, and all of the countries in Central America. Finally, the two sets of polygons are combined.
# now add background map of states and countries we drove through
ss <- maps::map('state', plot = F, fill = T)
idS <- sapply(strsplit(ss$names, ':'), function(x) x[1])
ssSt <- maptools::map2SpatialPolygons(ss, IDs=idS, proj4string=defProj) %>%
sp::spTransform(myProj)
mx <- maps::map('world',c('mexico','guatemala','nicaragua','el salvador', 'honduras', 'belize',
'costa rica','panama'),plot = F, fill = T, col = 'black')
idMx <- sapply(strsplit(mx$names, ":"), function(x) x[1])
ssMx <- maptools::map2SpatialPolygons(mx, IDs=idMx, proj4string=defProj) %>%
sp::spTransform(myProj)
bgMap <- rbind(ssSt, ssMx) # rbind combines polygons for spatialPolygons
Formatting and Saving the Map
The last steps are to format the map and save as a SVG. The simple formatting is taken care of in the plot
commands. I played around with using the new svglite package, but it saves the polygons as polylines, instead of paths. They need to be paths for the animation to work, so we use the default svg
graphics device. With this setup, a separate <path>
element in the SVG is created for each state and country. The driving route is the last <path>
element in the SVG because it is the last object we plot to the SVG device. This is important to know when it comes time to annimate the SVG in Part 2.
# add the map and route to the svg
svg(filename = 'driveRoute.svg',width=8, height=8)
par(mar = rep(0,4)) # remove margins
plot(bgMap, col = 'grey15', border = 'grey50')
plot(driveLine, col = 'steelblue3', add = T, lwd = 2.75)
dev.off()
Which creates the following map:
Part 2 will describe how to animate the path from Colorado to Nicaragua, i.e., the blue line.
Final Thoughts
Nothing new here, but a couple of key points:
- The maptools package offers a convenient way to read in KML data.
- It is necessary to uniformly simplify the KML data (
rgeos::gSimplify
) so that the animation will look smooth. - The coordinates exported from Google Maps and the maps package use the WGS84 datum. This information can be conveyed to the sp package, for creating spatial objects, using
sp::CRS('+init=epsg:4326')
Find the Code
The code, data, and this write up, are available on GitHub. The steps described here are isolated in createInitialSVG.R
.
Alan Butler PROJECTS · R
R KML maps R-sp R-maps R-rgeos R-maptools