[GIS] Loading a png image as a map with ggplot2/ggmap

ggmapggplot2google mapsmapsr

I am trying to find a workaround for ggmap's missing support of world maps (i.e. you can't create any maps that show latitudes > 80°, due to idiosyncrasies in the mapproj package).
To a limited extend however, it seems possible to create empty world maps and save them as an image (png etc.), even if you can't use the ggmap object directly as one normally would in ggmap(get_map(...)).

That's why I'd like to load a png (ideally, one I created with ggmap) into ggplot2 and use that as a map instead. How exactly can I do that?

I am aware that you can load background images in ggplot2 (see this stackoverflow question). But I'd also like to plot points on my map, so it's important that the latitude/longitude values are mapped correctly.

Best Answer

Although it is quite a long-winded approach, I came up with a solution which allows the user to display red-green-blue 'RasterStack' objects created from the initial 'ggmap' object using ggplot. But first things first, here is what I did.

First of all, I forked the read-only mirror of ggmap hosted on GitHub and, in order to retain the original source code, created a 'develop' branch which comprises all edits that were required to get this to work. No offense intended against the package developers! The modified package version can be directly installed from within R using

## install modified 'ggmap' package
try(detach("package:ggmap", unload = TRUE), silent = TRUE)

library(devtools)
install_github("fdetsch/ggmap", ref = "develop")

## load required packages
library(ggmap)
library(raster)
library(maptools)
library(plyr)

I am quite aware that there are still plenty of non-conformities involved in the package, but for now, the installation procedure should work just fine. Next, I amended get_map, which now takes the argument location = "world", in order to download a Google Maps image of the entire globe. The thus created 'ggmap' object can simple be converted to a red-green-blue 'RasterStack' object by running the newly introduced function ggmap2raster.

## retrieve and rasterize world map
map_world <- get_map(location = "world")
rst_world <- ggmap2raster(map_world)

In order to display the red-green-blue image via ggplot, I adopted and slightly modified the algorithm implementation of rggbplot presented on GURU-GIS. The function is currently not exported, and hence the usage of ::: is required to properly create the ggplot2-based figure.

## initialize figure
p <- ggmap:::rggbplot(rst_world, npix = ncell(rst_world))

Finally, let me add some sample data to demonstrate that not only the visualization of a ggmap-based world map works, but also the subsequent insertion of e.g. points or polygons.

## create and add sample points
spt <- data.frame(lon = c(-74.0059, -43.196389, 139.683333),
                  lat = c(40.7127, -22.908333, 35.683333),
                  city = c("New York City", "Rio de Janeiro", "Tokyo"))

coordinates(spt) <- ~ lon + lat
proj4string(spt) <- "+init=epsg:4326"
spt <- spTransform(spt, CRS = CRS("+init=epsg:3857"))

p <- p + 
  geom_point(aes(x = lon, y = lat), data = data.frame(spt)) + 
  geom_text(aes(x = lon, y = lat, label = city), data = data.frame(spt), 
            vjust = 1.6, size = 3)

## add polygons; see also hadley's tutorial at
## https://github.com/hadley/ggplot2/wiki/plotting-polygon-shapefiles
spy <- getData(country = "MNG", level = 0)
spy <- spTransform(spy, CRS = CRS("+init=epsg:3857"))

spy@data$id <- rownames(spy@data)
spy_points <- fortify(spy, region = "id")
spy_df <- join(spy_points, spy@data, by = "id")

p <- p + 
  geom_polygon(aes(x = long, y = lat, group = group), data = spy_df, 
               colour = "grey25", size = 2, fill = "transparent") + 
  geom_text(aes(x = x, y = y, label = "Mongolia"), 
            data = data.frame(coordinates(rgeos::gCentroid(spy))), 
            vjust = 0.5, size = 3, fontface = "bold")

## add google maps reference
p <- p + 
  geom_text(aes(x = 11500000, y = -18000000), label = "\uA9 Google Maps", 
            size = 3, fontface = "bold")

Et voilà, here is the resulting image. Please, feel free to browse the linked source code. Any suggestions on how to improve the scripts would be highly appreciated!

## save figure
png("~/worldmap.png", width = 12, height = 12, units = "cm", res = 500)
print(p)
dev.off()

worldmap

Update:

Sorry, forgot to mention that ggmap2raster allows the user to specify an output CRS of the created 'RasterStack' object which is then passed on to projectRaster from the raster package. Hence, it is possible to display the data in EPSG:4326 (Lat/Long) rather than EPSG:3857 (Web Mercator), which would also render unnecessary the transformation of 'SpatialPoints', 'SpatialPolygons', etc. to Web Mercator. However, I strongly recommend that you stick with Google Maps native EPSG:3857 to avoid vertical stretching of the map labels (see here).