Sometimes you may want to plot maps of the whole world,nthat little bluenspinning sphere thensurface of which provides a home for us all.nCreating maps of smaller areasnis covered in antutorialnI helped create called ‘Introduction to visualising spatial data in R’,nhosted with data and code on angithub repository.nThere are a range of options for plotting the world, including packages callednmaps,na function called map_data
fromnggplot2 package and rworldmap.
In this post we will use the latter two (newer) optionsnto show how maps of the entire worldncan easily be produced in R and overlaid with shortest-linenpaths called great circles. Amazingly, in each package, the geographicndata for the world and many of its subregionsnare included, saving thenneed to download and store files of unknown quality from the internet.
nn
Plotting continents and great circle lines in base graphics
nnThe first stage is to load the packages we’ll be using:
nnx <- c("rworldmap", "geosphere", "ggmap")nlapply(x, require, character.only = T)n
Let us proceed by loading an entire map of the world fromnthe rworldmap function getMap
:
s <- getMap() # load the map datanclass(s) # what type of are we dealing with?n
## [1] "SpatialPolygonsDataFrame"n## attr(,"package")n## [1] "sp"n
nrow(s) # n. polygonsn
## [1] 244n
plot(s) # the data plotted (not shown)nbbox(s) # the bounding box... of the entire worldn
## min maxn## x -180 180.00n## y -90 83.65n
The above shows that in single line of code we have loadedns
, which represents the entire world and all its countries.nThis impressive in itself,nand we can easily add further details like colour based onnthe countries’ attributes (incidentally, you can seenthe attribute data by typing s@data
).
Adding points randomly scattered over the face of the Earth
nnBut what if we want to add up points to the map ofnthe world and join them up? This can be done innthe same way as we’d add points to any R graphic.nUsing our knowledge of bbox
we can define the limitsnof random numbers (from runif
) to scatter points randomlynover the surface of the earth in terms of longitude. Note the use ofncos(abs(l))
to avoid oversampling at the poles,nwhich have a much lower surface area than the equator, pernline of longitude.
set.seed(1984)nn = 20nx <- runif(n=n, min=bbox(s)[1,1], max = bbox(s)[1,2] )nl <- seq(from = -90, to = 90, by = 0.01)ny <- sample(l, size = n, prob = cos(abs(l) * pi / 180))np <- SpatialPoints(matrix(cbind(x,y), ncol=2), proj4string=CRS("+proj=longlat +datum=WGS84"))nplot(s)npoints(p, col = "red")n
Joining the dots
nnSo how to join these randomly scattered points on the planet?nA first approximation would be to join them with straight lines.nLet’s join point 1, for example, to all others to test this method:
nnplot(s)nsegments(x0 = rep(coordinates(p[1,])[1], n), y0 = rep(coordinates(p[1,])[2], n),n x1 = coordinates(p)[,1], y1 = coordinates(p)[,2])n
(Incidentally, isn’t the use of segments
here rather clunky - any suggestionsnof a more elegant way to do this welcome.)nThe lines certainly do join up, but something doesn’t seem right in the map, right?nWell the fact that you have perfectly straight lines in the image means bendynlines over the Earth’s surface: these are not the shortest,ngreat circle lines.nTo add these great circle lines, we must use the geosphere package:
head(gcIntermediate(p[1,], p[2]), 2) # take a look at the output of the gcIntermediate functionn
## lon latn## [1,] 55.16 -37.47n## [2,] 53.16 -37.25n
plot(s)nlines(gcIntermediate(p[1,], p[2,]), col = "blue", lwd = 3)n# for loop to plot all lines going to zone 5nfor(i in 1:length(p)){n lines(gcIntermediate(p[1,], p[i,]), col = "green")n}n
Fantastic. Now we have great circle lines represented on anmap with a geographic coordinate system (CRS)n(as opposed to a projected CRS, which approximates Euclidean distance).
nnBeautifying the map
nnThe maps we created so far are not exactly beautiful.nLet’s try to make the map look a little nicer:
nnnames(s@data)n
## [1] "ScaleRank" "LabelRank" "FeatureCla" "SOVEREIGNT" n## [5] "SOV_A3" "ADM0_DIF" "LEVEL" "TYPE" n## [9] "ADMIN" "ADM0_A3" "GEOU_DIF" "GEOUNIT" n## ...n
library(rgdal)n
# s <- spTransform(s, CRSobj=CRS("+proj=robin +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"))nrcols <- terrain.colors(length(unique(s$REGION)))ns$col <- as.numeric(factor(s$REGION))npar(bg = 'lightblue')nplot(s, col = rcols[s$col], xlim = c(-180, 180))npoints(p, col = "red")nfor(i in 1:length(p)){n lines(gcIntermediate(p[5,], p[i,]), col = "black")n}n
par(bg = 'white')n
Doing it in ggplot2
nnThe ‘beautified’ map above certainly is more interesting visually, with addedncolours. But it’s difficult to call it truly beautiful. For that, as withnso many things in R plotting, we turn to ggplot2.
nns <- map_data("world")nm <- ggplot(s, aes(x=long, y=lat, group=group)) +n geom_polygon(fill="green", colour="black")nmn
When we add the lines in projected maps (i.e. with a Euclidean coordinate system)nbased solely on origins and destinations, this works fine, butnas with the previous example, generates incorrectnshortest path lines:
nn# adding linesn# for all combinations of lines, use this coden# p1 <- do.call(rbind, rep(list(coordinates(p)),n ))n# p2 <- cbind(rep(coordinates(p)[,1], each=n ), rep(coordinates(p)[,2], each=n ))n# for all lines goint to point 5:np1 <- coordinates(p[5,])[rep(1, n),]np2 <- coordinates(p)n# test plotting the linesn# ggplot() + geom_segment(aes(x = p1[,1], y = p1[,2], xend = p2[,1], yend = p2[,2]))nggplot() + geom_polygon(data = s,aes(x=long, y=lat, group=group), n fill="green", colour="black") + n geom_segment(aes(x = p1[,1], y = p1[,2], xend = p2[,1], yend = p2[,2]))n
Adding great circle lines to ggplot2 maps
nnAdding great circle lines in ggplot2 is similar, but we mustnsave all of the coordinates of the paths in advance before plotting,nbecause ggplot2 like to add all its layers in one function: youncannot iteratively add to the map using a for
loop as we didnin the base graphics example above.
To create the for loop, first create a data frame of a single line.nThe iterate for all zones and use rbind
to place one data frame onntop of the next:
paths <- gcIntermediate(p[5,], p[1,])npaths <- data.frame(paths)npaths$group <- 1nsel <- setdiff(2:length(p), 5)nfor(i in sel){n paths.tmp <- gcIntermediate(p[5,], p[i,])n paths.tmp <- data.frame(paths.tmp)n paths.tmp$group <- in paths <- rbind(paths, paths.tmp)n}n
To plot multiple paths, we can use the geom_segment
command.nBefore plotting the lines on the map, it’s sometimes best to firstnplot them on their own to ensure that everything is working.nNote the use of the command ggplot()
, which initiates annempty ggplot2 instances, ready to be filled with layers.nThis is more flexible than stating the data at the outset.
ggplot() + geom_polygon(data = s, aes(x=long, y=lat, group=group), n fill = "green", colour="black") +n geom_path(data = paths, aes(lon, lat , group = group)) +n theme(panel.background = element_rect(fill = 'lightblue'))n
Changing projection in ggplot
nnggplot2 has inbuilt map projection functionality with thenfunction coord_map
. This distorts the Euclidean axis of thenmap and allows some truly extraodinary shapes (thesentransformations can also be done in base graphics, ne.g. by using spTransform
). However,nas shown in the examples below, the library is currently buggynfor plotting polygons.
# to see the range of projections available using this method, try ?mapprojectnm <- last_plot()nm + coord_map()n
# remove fill as this clearly causes problems:nm <- ggplot() + geom_path(data = s, aes(x=long, y=lat, group=group), colour="black") +n geom_path(data = paths, aes(lon, lat , group = group)) n# m + coord_map("bicentric", lon = 0)n# m + coord_map("bonne", lat= 0)nm + coord_map("ortho", orientation=c(41, -74, 0)) # for ortho mapsn
Conclusion
nnWe’ve seen 2 ways of plotting maps of the world and overlayingn‘great circles’ lines on them. There are probably more, butnthese two options seem to work well, except withnthe bugs in ggplot2 for plotting polygons innmany map projections. The two methods are not incompatiblen(see fortify
for plotting sp objects in ggplot2)nand can be combined in many other ways.
For more information on plotting spatial data in R,nI recommend checking out R’s range ofnspatial packages.nFor an introductory tutorial on visualising spatial datanin R, you could do much worse than start withnVisualising Spatial Data in Rnby James Cheshire and myself.
nR-bloggers.com offers daily e-mail updates about R news and tutorials on topics such as: visualization (ggplot2, Boxplots, maps, animation), programming (RStudio, Sweave, LaTeX, SQL, Eclipse, git, hadoop, Web Scraping) statistics (regression, PCA, time series, trading) and more...