3  tmap

tmap is a popular package for making maps. It uses the concepts of a Grammar of Graphics to layer data as well as visualization rules. If you’re familiar with the ggplot2 package, this will feel similar. If you’re not familiar with ggplot2 (or not a fan), that’s ok because tmap uses layers in much the same way we just built maps using the base R plot() function.

3.1 Simple Map

First, we load the libraries we’ll need:

    #install.packages("tmap")
    library("tmap")
Warning: package 'tmap' was built under R version 4.4.3

Grammar of Graphics tools typically follow a pattern: first you indicate which data you want to work with, then you indicate the way the data should be styled. Let’s map the states data to see a basic no-frills example:

    tm_shape(states) +    #the data we want to map
    tm_polygons()         #style the data using the defaults

3.2 Multiple Layers

With tmap, if we want to add multiple layers to the map, we can think of each layer as a separate map that we stack together. For our map, we’ll first make the layer that contains the states, then we’ll add a layer that contains the lakes, then a final layer that contains the monsters.

  map_states <- tm_shape(states) +  
    tm_polygons() 

  map_lakes <- map_states +         #start with the states map
    tm_shape(lakes) +               #add the lakes data
    tm_polygons()                   #style the lakes data
  
  map_monsters <- map_lakes +       #start with the lakes map
    tm_shape(monsters) +            #add the monsters data
    tm_dots()                       #style the monsters data
  
  map_monsters                      #call the map variable to plot the map inside it

3.3 Styling

The goal of styling our data is to help it communicate better. Dots on top of state outlines are definitely difficult to understand. Let’s change the plotting arguments to make something more readable and to fit the story we want to tell.

  #create a bounding box object for the zoom area of our map
  aoi <- st_bbox(c(xmin = -96, xmax = -73, ymax = 50, ymin = 40), crs = st_crs(4326))


    map_states <- 
      tm_shape(states, 
         bbox = aoi) +                    #set the extend for the map
      
    tm_polygons(
      fill="white",                     #fill the polygons with white
      col="gray30") +                   #make the outline dark gray
    tm_layout(bg.color="lightblue3")    #set the background color with the layout options

  
    map_lakes <- map_states +         
      tm_shape(lakes) +               
      tm_polygons(
        fill = "lightblue",               #fill the polygons with light blue
        col="lightblue4"                  #make the outline a darker blue
      )                   
  
  map_monsters <- map_lakes +       
    tm_shape(monsters) +            
    tm_dots(size=.5)                    #set the size of the points              
  
  map_monsters                      

3.4 Add Text Labels

Let’s add some text labels so we know what the names of each of the monsters are.

  #create a bounding box object for the zoom area of our map
  aoi <- st_bbox(c(xmin = -96, xmax = -73, ymax = 50, ymin = 40), crs = st_crs(4326))


    map_states <- 
      tm_shape(states, 
         bbox = aoi) +                    
      
    tm_polygons(
      fill="white",                     
      col="gray30") +                   
    tm_layout(bg.color="lightblue3")    

  
    map_lakes <- map_states +         
    tm_shape(lakes) +               
    tm_polygons(
      fill = "lightblue",               
      col="lightblue4"                  
      )                   
  
  map_monsters <- map_lakes +       
    tm_shape(monsters) +            
    tm_dots(size=.5) +
    
    tm_text("Name")             #create labels, specifying the column to use
  
  map_monsters                      

Labeling of points is challenging for tmap. It doesn’t have a way to avoid label collisions (overlaps) and the placement options don’t really do much to avoid labels running over the symbols. I also haven’t found much guidance on placing the labels. Most of the tutorials avoid this problem by labeling polygons where the exact placement isn’t a concern. Like the discussion of plot(), this map would benefit from moving the labels by hand. Export a pdf and edit it in a vector illustration software like Inkscape or Adobe Illustrator.

3.5 North Arrow & Scale Bar

One big benefit of the tmap workflow is the built in option to add norh arrows and scale bars. Adding them in is super simple and if you’re happy with the defaults, it’s pretty quick. We’ll put ours in the lower left corner where they won’t cover up our monster points. Also, we’re adding these to the last layer of the map we built so that the arrow and scale bar don’t get covered up by anything else. (Note I’m taking out the labels because they are messy.)

  #create a bounding box object for the zoom area of our map
  aoi <- st_bbox(c(xmin = -96, xmax = -73, ymax = 50, ymin = 40), crs = st_crs(4326))


    map_states <- 
      tm_shape(states, 
         bbox = aoi) +                    
      
    tm_polygons(
      fill="white",                     
      col="gray30") +                   
    tm_layout(bg.color="lightblue3")    

  
    map_lakes <- map_states +         
    tm_shape(lakes) +               
    tm_polygons(
      fill = "lightblue",               
      col="lightblue4"                  
      )                   
  
  map_monsters <- map_lakes +       
    tm_shape(monsters) +            
    tm_dots(size=.5) +
    
    tm_compass(position=c("left", "bottom")) +     #add the north arrow (compass rose)
    tm_scalebar(position=c("left", "bottom"))      #add the scale bar
    
  
  map_monsters                      

4 Ready-Made Tile Services

Another benefit of the tmap workflow is the easy availability of using tile services (commonly known as “basemaps” but people use that term to mean so many other things as well). Tile services are continuous map layers that are stored in square chunks (tiles) so you only need to load the part of the world in your area of interest. One tile service that many people are familiar with is Google Maps, but open data like OpenStreetMap is also available as a tile service.

Remember that even if a tile service makes a dataset easy to use, you still need to comply with the terms of service for that specific dataset.

Let’s replace our base layers (states and lakes) with the default basemap.

  #create a bounding box object for the zoom area of our map
  aoi <- st_bbox(c(xmin = -96, xmax = -73, ymax = 50, ymin = 40), crs = st_crs(4326))


      map_monsters <-  
        tm_basemap() +                       #start with the default base map
      tm_shape(monsters, bbox = aoi) +     #move the bounding box parameter here
      tm_dots(size=.5) +
    
      tm_compass(position=c("left", "bottom")) +     
      tm_scalebar(position=c("left", "bottom"))      
    
  
  map_monsters                      

What about a base layer with some more color or labels? Let’s add the OpenStreetMap tile service.

  #create a bounding box object for the zoom area of our map
  aoi <- st_bbox(c(xmin = -96, xmax = -73, ymax = 50, ymin = 40), crs = st_crs(4326))


      map_monsters <-  
        tm_basemap("OpenStreetMap") +                       #start with the default base map
      tm_shape(monsters, bbox = aoi) +     
      tm_dots(size=.5) +
    
      tm_compass(position=c("left", "bottom")) +     
      tm_scalebar(position=c("left", "bottom"))      
    
  
  map_monsters                      

4.1 Interactive Maps

Being able to pan and zoom around a plot is a useful tool for understanding your data or the results of an analysis. tmap makes this relatively easy with the tmap_leaflet() function. Let’s plot our monster data on the default basemap. The pattern of the code should look familiar: set up the map, building up layers from the bottom, and then plot the map.

  #create a bounding box object for the zoom area of our map
  aoi <- st_bbox(c(xmin = -96, xmax = -73, ymax = 50, ymin = 40), crs = st_crs(4326))

leaflet_map =                           
  tm_shape(monsters, bbox = aoi) +      #add the monster data     
  tm_dots(size=.5)                      #style the monster data

tmap_leaflet(leaflet_map, show = TRUE)  #plot the monster data                      

Notice the source information for the basemap printed in the lower right corner of the map. If you want to publish this map, you’ll need to check the Terms of Service for this data source.

Try zooming out and panning around the world to see more lake monster locations in context. You can change your basemap like we did earlier in the workshop with a parameter like tm_basemap("OpenStreetMap") when you define your map contents.

4.2 Summary

We’ve seen that we can build a map in layers using tmap in a similar manner to the workflow we used in the base plot() workflow. tmap has more built-in features for composing maps and finer control over some aspects of maps like north arrows and scale bars. It also has built-in base map tools which makes making a map quick. But tmap still doesn’t have an easy way to deal with label placement.