Adding scale bar to ggplot map
Asked Answered
P

5

30

I've produced this map in ggplot2:

library(maptools); library(ggplot2)
data(wrld_simpl)
world <- fortify(wrld_simpl)
worldUk <- subset(world, id=="GBR")
ggplot() +
geom_path(aes(long, lat, group=group), data=worldUk, color="black", fill=NA) +
  coord_equal()

enter image description here

Using photoshop, I've added a scale bar to the map. How can I add a similar scale bar using ggplot2? This post adds a scale bar, but the bar doesn't refer to distance: scale bar and north arrow on map-ggplot2

Phagy answered 17/6, 2013 at 15:17 Comment(4)
Have you tested this: #15487051 ?Ulrica
Yes - see link at bottom of questionPhagy
Just an observation - you want to be very careful putting scale bars on plots that cover a large range of latitude. Depending on the projection you choose, the scale bar may only be valid in one part of the image. So it's not as easy as just adding a scale bar...Hobbs
Found this function from Osmo Salomaa and it worked for me (with UTM CRS). groups.google.com/forum/#!topic/ggplot2/klHWR0VrCYQGader
B
19

There is a package called ggsn, which allows you to customize the scale bar and north arrow.

ggplot() +
  geom_path(aes(long, lat, group=group), data=worldUk, color="black", fill=NA) +
  coord_equal() +
  ggsn::scalebar(worldUk, dist = 100, st.size=3, height=0.01, dd2km = TRUE, model = 'WGS84')

enter image description here

Bothwell answered 1/7, 2017 at 9:32 Comment(1)
Package ‘ggsn’ was removed from the CRAN repository on 2023-10-16 as requires archived package 'maptools'.Dump
B
19

Another option is annotation_scale from the ggspatial package. Some more hints on this blog post.

library(ggplot2)
library(maptools)
data(wrld_simpl)

world_sf <- sf::st_as_sf(wrld_simpl)
worldUk <- subset(world_sf, NAME == 'United Kingdom')

Plot the layer and add the scale bar.

library(ggspatial)

ggplot() +
  geom_sf(data = worldUk) +
  annotation_scale()

enter image description here

Blackhead answered 15/7, 2020 at 17:44 Comment(1)
Thanks! I don't know why ggspatial::annotation_scale worked, but it definitely solved my problem. Before I was trying to put a scalebar with ggsn::scalebar, but always got this error: Error in maptools::gcDestination(lon = x, lat = y, bearing = 90 * direction, : dist not numeric In addition: Warning messages: 1: In min(data$long) : no non-missing arguments to min; returning Inf etc, etc... any idea of what could be wrong?Limoges
M
3

I found this answer on http://editerna.free.fr/wp/?p=76 and I found it simple and worked perfectly so I must share it!

It is simple, first paste the code below into your console (no need for editing)

 library(maps) 
 library(maptools)  
 library(ggplot2)  
 library(grid)  
 #Then, we need a function to get the scale bar coordinates:

 #
 # Result #
 #--------#
 # Return a list whose elements are :
 #   - rectangle : a data.frame containing the coordinates to draw the first rectangle ;
 #   - rectangle2 : a data.frame containing the coordinates to draw the second rectangle ;
 #   - legend : a data.frame containing the coordinates of the legend texts, and the texts as well.
 #
 # Arguments : #
 #-------------#
 # lon, lat : longitude and latitude of the bottom left point of the first rectangle to draw ;
 # distanceLon : length of each rectangle ;
 # distanceLat : width of each rectangle ;
 # distanceLegend : distance between rectangles and legend texts ;
 # dist.units : units of distance "km" (kilometers) (default), "nm" (nautical miles), "mi" (statute miles). createScaleBar <-
 function(lon,lat,distanceLon,distanceLat,distanceLegend, dist.units =
 "km"){
     # First rectangle
     bottomRight <- gcDestination(lon = lon, lat = lat, bearing = 90, dist = distanceLon, dist.units = dist.units, model = "WGS84")

     topLeft <- gcDestination(lon = lon, lat = lat, bearing = 0, dist = distanceLat, dist.units = dist.units, model = "WGS84")
     rectangle <- cbind(lon=c(lon, lon, bottomRight[1,"long"], bottomRight[1,"long"], lon),
     lat = c(lat, topLeft[1,"lat"], topLeft[1,"lat"],lat, lat))
     rectangle <- data.frame(rectangle, stringsAsFactors = FALSE)

     # Second rectangle t right of the first rectangle
     bottomRight2 <- gcDestination(lon = lon, lat = lat, bearing = 90, dist = distanceLon*2, dist.units = dist.units, model = "WGS84")
     rectangle2 <- cbind(lon = c(bottomRight[1,"long"], bottomRight[1,"long"], bottomRight2[1,"long"], bottomRight2[1,"long"],
 bottomRight[1,"long"]),
     lat=c(lat, topLeft[1,"lat"], topLeft[1,"lat"], lat, lat))
     rectangle2 <- data.frame(rectangle2, stringsAsFactors = FALSE)

     # Now let's deal with the text
     onTop <- gcDestination(lon = lon, lat = lat, bearing = 0, dist = distanceLegend, dist.units = dist.units, model = "WGS84")
     onTop2 <- onTop3 <- onTop
     onTop2[1,"long"] <- bottomRight[1,"long"]
     onTop3[1,"long"] <- bottomRight2[1,"long"]

     legend <- rbind(onTop, onTop2, onTop3)
     legend <- data.frame(cbind(legend, text = c(0, distanceLon, distanceLon*2)), stringsAsFactors = FALSE, row.names = NULL)
     return(list(rectangle = rectangle, rectangle2 = rectangle2, legend = legend)) } We also need a function to obtain the coordinates of the North arrow:

 #
 # Result #
 #--------#
 # Returns a list containing :
 #   - res : coordinates to draw an arrow ;
 #   - coordinates of the middle of the arrow (where the "N" will be plotted).
 #
 # Arguments : #
 #-------------#
 # scaleBar : result of createScaleBar() ;
 # length : desired length of the arrow ;
 # distance : distance between legend rectangles and the bottom of the arrow ;
 # dist.units : units of distance "km" (kilometers) (default), "nm" (nautical miles), "mi" (statute miles). createOrientationArrow <-
 function(scaleBar, length, distance = 1, dist.units = "km"){
     lon <- scaleBar$rectangle2[1,1]
     lat <- scaleBar$rectangle2[1,2]

     # Bottom point of the arrow
     begPoint <- gcDestination(lon = lon, lat = lat, bearing = 0, dist = distance, dist.units = dist.units, model = "WGS84")
     lon <- begPoint[1,"long"]
     lat <- begPoint[1,"lat"]

     # Let us create the endpoint
     onTop <- gcDestination(lon = lon, lat = lat, bearing = 0, dist = length, dist.units = dist.units, model = "WGS84")

     leftArrow <- gcDestination(lon = onTop[1,"long"], lat = onTop[1,"lat"], bearing = 225, dist = length/5, dist.units =
 dist.units, model = "WGS84")

     rightArrow <- gcDestination(lon = onTop[1,"long"], lat = onTop[1,"lat"], bearing = 135, dist = length/5, dist.units =
 dist.units, model = "WGS84")

     res <- rbind(
             cbind(x = lon, y = lat, xend = onTop[1,"long"], yend = onTop[1,"lat"]),
             cbind(x = leftArrow[1,"long"], y = leftArrow[1,"lat"], xend = onTop[1,"long"], yend = onTop[1,"lat"]),
             cbind(x = rightArrow[1,"long"], y = rightArrow[1,"lat"], xend = onTop[1,"long"], yend = onTop[1,"lat"]))

     res <- as.data.frame(res, stringsAsFactors = FALSE)

     # Coordinates from which "N" will be plotted
     coordsN <- cbind(x = lon, y = (lat + onTop[1,"lat"])/2)

     return(list(res = res, coordsN = coordsN)) } The last function enables the user to draw the elements:

 #
 # Result #
 #--------#
 # This function enables to draw a scale bar on a ggplot object, and optionally an orientation arrow #
 # Arguments : #
 #-------------#
 # lon, lat : longitude and latitude of the bottom left point of the first rectangle to draw ;
 # distanceLon : length of each rectangle ;
 # distanceLat : width of each rectangle ;
 # distanceLegend : distance between rectangles and legend texts ;
 # dist.units : units of distance "km" (kilometers) (by default), "nm" (nautical miles), "mi" (statute miles) ;
 # rec.fill, rec2.fill : filling colour of the rectangles (default to white, and black, resp.);
 # rec.colour, rec2.colour : colour of the rectangles (default to black for both);
 # legend.colour : legend colour (default to black);
 # legend.size : legend size (default to 3);
 # orientation : (boolean) if TRUE (default), adds an orientation arrow to the plot ;
 # arrow.length : length of the arrow (default to 500 km) ;
 # arrow.distance : distance between the scale bar and the bottom of the arrow (default to 300 km) ;
 # arrow.North.size : size of the "N" letter (default to 6). scaleBar <- function(lon, lat, distanceLon, distanceLat, distanceLegend,
 dist.unit = "km", rec.fill = "white", rec.colour = "black", rec2.fill
 = "black", rec2.colour = "black", legend.colour = "black", legend.size = 3, orientation = TRUE, arrow.length = 500, arrow.distance = 300, arrow.North.size = 6){
     laScaleBar <- createScaleBar(lon = lon, lat = lat, distanceLon = distanceLon, distanceLat = distanceLat, distanceLegend =
 distanceLegend, dist.unit = dist.unit)
     # First rectangle
     rectangle1 <- geom_polygon(data = laScaleBar$rectangle, aes(x = lon, y = lat), fill = rec.fill, colour = rec.colour)

     # Second rectangle
     rectangle2 <- geom_polygon(data = laScaleBar$rectangle2, aes(x = lon, y = lat), fill = rec2.fill, colour = rec2.colour)

     # Legend
     scaleBarLegend <- annotate("text", label = paste(laScaleBar$legend[,"text"], dist.unit, sep=""), x =
 laScaleBar$legend[,"long"], y = laScaleBar$legend[,"lat"], size =
 legend.size, colour = legend.colour)

     res <- list(rectangle1, rectangle2, scaleBarLegend)

     if(orientation){# Add an arrow pointing North
         coordsArrow <- createOrientationArrow(scaleBar = laScaleBar, length = arrow.length, distance = arrow.distance, dist.unit =
 dist.unit)
         arrow <- list(geom_segment(data = coordsArrow$res, aes(x = x, y = y, xend = xend, yend = yend)), annotate("text", label = "N", x =
 coordsArrow$coordsN[1,"x"], y = coordsArrow$coordsN[1,"y"], size =
 arrow.North.size, colour = "black"))
         res <- c(res, arrow)
     }
     return(res) }

Now, to plot onto a map!

First create your ggplot map, and add the scale bar as an extra layer, using the « + » sign:

 your_ggplot_object -> your_ggplot_object + scaleBar(lon = -130, lat = 26, distanceLon = 500,
 distanceLat = 100, distanceLegend = 200, dist.unit = "km")

On the example above, I call the « scaleBar » function, and I specify some values for the arguments. For instance, lon = -130, lat = 26 means I want the bottom left point of the rectangle to be at (-130,26). You need to define the other values of each argument according to what you want (there is a description of each argument in the header of the function).

Mislay answered 10/12, 2014 at 17:57 Comment(2)
This solution doesn't seem to work with ggmap(), but seems to work with ggplot(). any clues on using this with ggmap?Orthogonal
I get an error: Don't know how to automatically pick scale for object of type function. Defaulting to continuous Error in data.frame(x = c(13.15, 13.15, 13.2235616636229, 13.2235616636229, : arguments imply different number of rows: 5, 0 What can I do?Exuviae
I
2

I would suggest annotate(), which would allow you to draw segments of the proper length right in the plotting area. You could then add text above/below as a label.

Inadvertence answered 25/4, 2014 at 0:18 Comment(1)
Necessary if the scale is not distance.Astrodynamics
A
0

ggsn (mentioned by previous answers) is now deprecated. ggspatial is a modern replacement.

https://github.com/paleolimbot/ggspatial

For example:

library(ggspatial)    
plot <- ggplot()

plot +
annotation_scale(location = "tl") +
annotation_north_arrow(location = "bl", which_north = "true", style = north_arrow_minimal())
Auburta answered 18/7 at 23:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.