4
votes

I am using ggplot2 in an oceanographic context, and I would like to add a simple map of the land for reference. Here's a minimum working example in basic graphics:

library(ggplot2)
library(maps)

#Setup fake data - some points in the North Sea
n <- 100
d <- data.frame(lon=rnorm(n,5),lat=rnorm(n,55))

#Plotting with base-graphics, then overlaying map
plot(lat~lon,d,pch=16,col="red",asp=1)
map("world",add=TRUE,col="black",fill=TRUE)

Base graphics example

Note that the xlim and ylim of this plot are set by the range of the data.

Now, attempting to reproduce this in ggplot:

#And now with ggplot
g <- ggplot(d,aes(lon,lat))+geom_point(col="red",pch=16)+
     borders("world",fill="black",colour="black")
plot(g)

enter image description here

...which is technically correct - the axes are set to cover all of the points - but is not really what I'm after.

I can of course go in and set the axes manually using e.g. coord_cartesian or similar, but this has a high irritation factor - particularly when trying to produce lots of similar maps automatically. Ideally I would like to be able to overlay the borders/coastlines and tell ggplot not to consider them when determining the axes. Alternatively, I have the impression that geom_map map be the way forward, but I'm not having much success with that..

Any suggestions?

Mark

4
I'm still not sure what it is that you'd like to achieve. You would like a map that only covers the extent of your data points, right? And what do you consider "aesthetically pleasing"?maj
Thanks for the suggestions. Ideally I want the axes determined by the data - I don't consider coastlines as "data" in this context, and therefore they shouldn't influence the choice of axes. I'd prefer this to happy automatically, without having to manually adjust the coordinate limits, which the solutions below involve. (Apologies for the rather esoteric nature of the question)Mark Payne

4 Answers

2
votes

An alternative using ggmap, that would have you either manually or iteratively getting the maps for the areas you require:

library(ggmap)
northseamap <- get_map(location = "north sea", zoom=6)
g <- ggmap(northseamap) +geom_point(data = d, aes(x=lon, y=lat), col="red",pch=16)
g

This yields the following map:

enter image description here

Note that you trade setting two coordinates for one google query ('north sea'), or a pair of lat/lon points.

The same (similar) map, with a pair of points passed for the location (from the data):

northseamap2 <- get_map(location = c(lon = mean(d$lon), lat = mean(d$lat)), zoom=6)
g <- ggmap(northseamap2) +geom_point(data = d, aes(x=lon, y=lat), col="red",pch=16)
g

enter image description here

This following version is closer to the spirit of your original plot (albeit with an actual black sea):

northseamap3 <- get_map(location = c( lon = mean(d$lon), lat = mean(d$lat)),source="stamen", color="bw", zoom=6, maptype="toner")
g <- ggmap(northseamap3) +geom_point(data = d, aes(x=lon, y=lat), col="red",pch=16)
g

enter image description here

2
votes

It's definitely doable with ggplot, but you'll need some help from it's friends since you are correct in that geom_map is required to get an aesthetically pleasing result.

library(ggplot2)
library(ggthemes)
library(maps)
require(sp)
require(maptools)

# your data

n <- 100
set.seed(1492) # makes the results reproducible
d <- data.frame(lon=rnorm(n, 5),
                lat=rnorm(n, 55))

# get the map, don't plot it but `fill` gets us named 'map' polygons
world <- map("world", fill=TRUE, plot=FALSE)

# convert the 'map' to something we can work with via geom_map
IDs <- sapply(strsplit(world$names, ":"), function(x) x[1])
world <- map2SpatialPolygons(world, IDs=IDs, proj4string=CRS("+proj=longlat +datum=WGS84"))

# this does the magic for geom_map
world_map <- fortify(world)

# setup the plot
g <- ggplot(d, aes(lon, lat))

# add the map
g <- g + geom_map(data=world_map, map=world_map,
                  aes(x=long, y=lat, map_id=id), 
                  fill="black")

# add your points
g <- g + geom_point(col="red", pch=16)

# set the map limits to the extent of your actual data
# (plus a wee bit more)

g <- g + xlim(extendrange(d$lon, f=1))
g <- g + ylim(extendrange(d$lat, f=0.5))

# approximate mercator without dealing with potential
# issues with coord_map()

g <- g + coord_quickmap()

# quick, nice map theme
g <- g + theme_map()

# show the results
g

enter image description here

Depending on where you end up on the planet with your data, you may still have some issues with errant polygon lines. If so, please post a comment and I can update the answer with how to use less buggy map data then the current built-in one.

0
votes

It turns out that there has been exactly this feature (well hidden) in ggplot2 since around 2011 (!) - the annotation_map() function adds a map as a layer. So, the solution to the question would then be:

g <- ggplot(d,aes(lon,lat))+
  annotation_map(map_data("world"))+ #Add the map as a base layer before the points
  geom_point(col="red",pch=16)+
 coord_quickmap()  #Sets aspect ratio
plot(g)

enter image description here

The coord_quickmap() function is particularly helpful here to set the aspect ratio to something that looks good. It can also be used to expand the plot so that it covers the entire North Sea:

g1 <- g+ coord_quickmap(xlim=c(0,15),ylim=c(50,60))
plot(g1)

Figure with map layer and expanded coordinates

From the help file: "coord_quickmap() is a quick approximation that does preserve straight lines. It works best for smaller areas closer to the equator." For more standard projections, see the coord_map() functionality.

-2
votes
ggplot(dat,aes(x=lon, y=lat))+borders(fill="black",colour="black") +
    geom_point(col="red",pch=16) +
    coord_fixed(xlim = c(100,180),ylim=c(0,40))