8.2 Static maps

Static maps are the most common type of visual output from geocomputation.Fixed images for printed outputs, common formats for static maps include .png and .pdf, for raster and vector outputs, respectively (interactive maps are covered in Section 8.4).Initially static maps were the only type of map that R could produce.Things have advanced greatly since sp was released (see Pebesma and Bivand 2005).Many new techniques for map making have been developed since then.However, a decade later static plotting was still the emphasis of geographic data visualisation in R (Cheshire and Lovelace 2015).

Despite the innovation of interactive mapping in R, static maps are still the foundation of mapping in R.The generic plot() function is often the fastest way to create static maps from vector and raster spatial objects, as shown in Sections 2.2.3 and 2.3.2.Sometimes simplicity and speed are priorities, especially during the development phase of a project, and this is where plot() excels.The base R approach is also extensible, with plot() offering dozens of arguments.Another low-level approach is the grid package, which provides functions for low-level control of graphical outputs, — see R Graphics(Murrell 2016), especially Chapter 14.The focus of this section, however, is making static maps with tmap.

Why tmap?It is a powerful and flexible map-making package with sensible defaults.It has a concise syntax that allows for the creation of attractive maps with minimal code, which will be familiar to ggplot2 users.Furthermore, tmap has a unique capability to generate static and interactive maps using the same code via tmap_mode().It accepts a wider range of spatial classes (including raster objects) than alternatives such as ggplot2, as documented in vignettes tmap-getstarted and tmap-changes-v2 and an academic paper on the subject (Tennekes 2018).This section teaches how to make static maps with tmap, emphasizing the important aesthetic and layout options.

8.2.1 tmap basics

Like ggplot2, tmap is based on the idea of a ‘grammar of graphics’ (Wilkinson and Wills 2005).This involves a separation between the input data and the aesthetics (how data are visualised): each input dataset can be ‘mapped’ in a range of different ways including location on the map (defined by data’s geometry), color, and other visual variables.The basic building block is tm_shape() (which defines input data, raster and vector objects), followed by one or more layer elements such as tm_fill() and tm_dots().This layering is demonstrated in the chunk below, which generates the maps presented in Figure 8.1:

  1. # Add fill layer to nz shape
  2. tm_shape(nz) +
  3. tm_fill()
  4. # Add border layer to nz shape
  5. tm_shape(nz) +
  6. tm_borders()
  7. # Add fill and border layers to nz shape
  8. tm_shape(nz) +
  9. tm_fill() +
  10. tm_borders()

New Zealand's shape plotted with fill (left), border (middle) and fill and border (right) layers added using tmap functions.
Figure 8.1: New Zealand’s shape plotted with fill (left), border (middle) and fill and border (right) layers added using tmap functions.

The object passed to tm_shape() in this case is nz, an sf object representing the regions of New Zealand (see Section 2.2.1 for more on sf objects).Layers are added to represent nz visually, with tm_fill() and tm_borders() creating shaded areas (left panel) and border outlines (middle panel) in Figure 8.1, respectively.

This is an intuitive approach to map making:the common task of adding new layers is undertaken by the addition operator +, followed by tm().The asterisk () refers to a wide range of layer types which have self-explanatory names including fill, borders (demonstrated above), bubbles, text and raster (see help("tmap-element" for a full list).This layering is illustrated in the right panel of Figure 8.1, the result of adding a border _on top of the fill layer.

qtm() is a handy function for quickly creating tmap maps (hence the snappy name).It is concise and provides a good default visualization in many cases:qtm(nz), for example, is equivalent to tm_shape(nz) + tm_fill() + tm_borders().Further, layers can be added concisely using multiple qtm() calls, such as qtm(nz) + qtm(nz_height).The disadvantage is that it makes aesthetics of individual layers harder to control, explaining why we avoid teaching it in this chapter.

8.2.2 Map objects

A useful feature of tmap is its ability to store objects representing maps.The code chunk below demonstrates this by saving the last plot in Figure 8.1 as an object of class tmap (note the use of tm_polygons() which condenses tm_fill() + tm_borders() into a single function):

  1. map_nz = tm_shape(nz) + tm_polygons()
  2. class(map_nz)
  3. #> [1] "tmap"

map_nz can be plotted later, for example by adding additional layers (as shown below) or simply running map_nz in the console, which is equivalent to print(map_nz).

New shapes can be added with + tm_shape(new_obj).In this case new_obj represents a new spatial object to be plotted on top of preceding layers.When a new shape is added in this way, all subsequent aesthetic functions refer to it, until another new shape is added.This syntax allows the creation of maps with multiple shapes and layers, as illustrated in the next code chunk which uses the function tm_raster() to plot a raster layer (with alpha set to make the layer semi-transparent):

  1. map_nz1 = map_nz +
  2. tm_shape(nz_elev) + tm_raster(alpha = 0.7)

Building on the previously created map_nz object, the preceding code creates a new map object map_nz1 that contains another shape (nz_elev) representing average elevation across New Zealand (see Figure 8.2, left).More shapes and layers can be added, as illustrated in the code chunk below which creates nz_water, representing New Zealand’s territorial waters, and adds the resulting lines to an existing map object.

  1. nz_water = st_union(nz) %>% st_buffer(22200) %>%
  2. st_cast(to = "LINESTRING")
  3. map_nz2 = map_nz1 +
  4. tm_shape(nz_water) + tm_lines()

There is no limit to the number of layers or shapes that can be added to tmap objects.The same shape can even be used multiple times.The final map illustrated in Figure 8.2 is created by adding a layer representing high points (stored in the object nz_height) onto the previously created map_nz2 object with tm_dots() (see ?tm_dots and ?tm_bubbles for details on tmap’s point plotting functions).The resulting map, which has four layers, is illustrated in the right-hand panel of Figure 8.2:

  1. map_nz3 = map_nz2 +
  2. tm_shape(nz_height) + tm_dots()

A useful and little known feature of tmap is that multiple map objects can be arranged in a single ‘metaplot’ with tmap_arrange().This is demonstrated in the code chunk below which plots map_nz1 to map_nz3, resulting in Figure 8.2.

  1. tmap_arrange(map_nz1, map_nz2, map_nz3)

Maps with additional layers added to the final map of Figure 8.1.
Figure 8.2: Maps with additional layers added to the final map of Figure 8.1.

More elements can also be added with the + operator.Aesthetic settings, however, are controlled by arguments to layer functions.

8.2.3 Aesthetics

The plots in the previous section demonstrate tmap’s default aesthetic settings.Gray shades are used for tm_fill() and tm_bubbles() layers and a continuous black line is used to represent lines created with tm_lines().Of course, these default values and other aesthetics can be overridden.The purpose of this section is to show how.

There are two main types of map aesthetics: those that change with the data and those that are constant.Unlike ggplot2, which uses the helper function aes() to represent variable aesthetics, tmap accepts aesthetic arguments that are either variable fields (based on column names) or constant values.36The most commonly used aesthetics for fill and border layers include color, transparency, line width and line type, set with col, alpha, lwd, and lty arguments, respectively.The impact of setting these with fixed values is illustrated in Figure 8.3.

  1. ma1 = tm_shape(nz) + tm_fill(col = "red")
  2. ma2 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3)
  3. ma3 = tm_shape(nz) + tm_borders(col = "blue")
  4. ma4 = tm_shape(nz) + tm_borders(lwd = 3)
  5. ma5 = tm_shape(nz) + tm_borders(lty = 2)
  6. ma6 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3) +
  7. tm_borders(col = "blue", lwd = 3, lty = 2)
  8. tmap_arrange(ma1, ma2, ma3, ma4, ma5, ma6)

The impact of changing commonly used fill and border aesthetics to fixed values.
Figure 8.3: The impact of changing commonly used fill and border aesthetics to fixed values.

Like base R plots, arguments defining aesthetics can also receive values that vary.Unlike the base R code below (which generates the left panel in Figure 8.4), tmap aesthetic arguments will not accept a numeric vector:

  1. plot(st_geometry(nz), col = nz$Land_area) # works
  2. tm_shape(nz) + tm_fill(col = nz$Land_area) # fails
  3. #> Error: Fill argument neither colors nor valid variable name(s)

Instead col (and other aesthetics that can vary such as lwd for line layers and size for point layers) requires a character string naming an attribute associated with the geometry to be plotted.Thus, one would achieve the desired result as follows (plotted in the right-hand panel of Figure 8.4):

  1. tm_shape(nz) + tm_fill(col = "Land_area")

Comparison of base (left) and tmap (right) handling of a numeric color field.Comparison of base (left) and tmap (right) handling of a numeric color field.
Figure 8.4: Comparison of base (left) and tmap (right) handling of a numeric color field.

An important argument in functions defining aesthetic layers such as tm_fill() is title, which sets the title of the associated legend.The following code chunk demonstrates this functionality by providing a more attractive name than the variable name Land_area (note the use of expression() to create superscript text):

  1. legend_title = expression("Area (km"^2*")")
  2. map_nza = tm_shape(nz) +
  3. tm_fill(col = "Land_area", title = legend_title) + tm_borders()

8.2.4 Color settings

Color settings are an important part of map design.They can have a major impact on how spatial variability is portrayed as illustrated in Figure 8.5.This shows four ways of coloring regions in New Zealand depending on median income, from left to right (and demonstrated in the code chunk below):

  • The default setting uses ‘pretty’ breaks, described in the next paragraph.
  • breaks allows you to manually set the breaks.
  • n sets the number of bins into which numeric variables are categorized.
  • palette defines the color scheme, for example BuGn.
  1. tm_shape(nz) + tm_polygons(col = "Median_income")
  2. breaks = c(0, 3, 4, 5) * 10000
  3. tm_shape(nz) + tm_polygons(col = "Median_income", breaks = breaks)
  4. tm_shape(nz) + tm_polygons(col = "Median_income", n = 10)
  5. tm_shape(nz) + tm_polygons(col = "Median_income", palette = "BuGn")

Illustration of settings that affect color settings. The results show (from left to right): default settings, manual breaks, n breaks, and the impact of changing the palette.
Figure 8.5: Illustration of settings that affect color settings. The results show (from left to right): default settings, manual breaks, n breaks, and the impact of changing the palette.

Another way to change color settings is by altering color break (or bin) settings.In addition to manually setting breaks tmap allows users to specify algorithms to automatically create breaks with the style argument.Six of the most useful break styles are illustrated in Figure 8.6 and described in the bullet points below:

  • style = pretty, the default setting, rounds breaks into whole numbers where possible and spaces them evenly.
  • style = equal divides input values into bins of equal range, and is appropriate for variables with a uniform distribution (not recommended for variables with a skewed distribution as the resulting map may end-up having little color diversity).
  • style = quantile ensures the same number of observations fall into each category (with the potential down side that bin ranges can vary widely).
  • style = jenks identifies groups of similar values in the data and maximizes the differences between categories.
  • style = cont (and order) present a large number of colors over continuous color field, and are particularly suited for continuous rasters (order can help visualize skewed distributions).
  • style = cat was designed to represent categorical values and assures that each category receives a unique color.
    Illustration of different binning methods set using the style argument in tmap.
    Figure 8.6: Illustration of different binning methods set using the style argument in tmap.

Although style is an argument of tmap functions, in fact it originates as an argument in classInt::classIntervals() — see the help page of this function for details.

Palettes define the color ranges associated with the bins and determined by the breaks, n, and style arguments described above.The default color palette is specified in tm_layout() (see Section 8.2.5 to learn more); however, it could be quickly changed using the palette argument.It expects a vector of colors or a new color palette name, which can be selected interactively with tmaptools::palette_explorer().You can add a - as prefix to reverse the palette order.

There are three main groups of color palettes: categorical, sequential and diverging (Figure 8.7), and each of them serves a different purpose.Categorical palettes consist of easily distinguishable colors and are most appropriate for categorical data without any particular order such as state names or land cover classes.Colors should be intuitive: rivers should be blue, for example, and pastures green.Avoid too many categories: maps with large legends and many colors can be uninterpretable.37

The second group is sequential palettes.These follow a gradient, for example from light to dark colors (light colors tend to represent lower values), and are appropriate for continuous (numeric) variables.Sequential palettes can be single (Blues go from light to dark blue, for example) or multi-color/hue (YlOrBr is gradient from light yellow to brown via orange, for example), as demonstrated in the code chunk below — output not shown, run the code yourself to see the results!

  1. tm_shape(nz) + tm_polygons("Population", palette = "Blues")
  2. tm_shape(nz) + tm_polygons("Population", palette = "YlOrBr")

The last group, diverging palettes, typically range between three distinct colors (purple-white-green in Figure 8.7) and are usually created by joining two single-color sequential palettes with the darker colors at each end.Their main purpose is to visualize the difference from an important reference point, e.g., a certain temperature, the median household income or the mean probability for a drought event.The reference point’s value can be adjusted in tmap using the midpoint argument.

Examples of categorical, sequential and diverging palettes.
Figure 8.7: Examples of categorical, sequential and diverging palettes.

There are two important principles for consideration when working with colors: perceptibility and accessibility.Firstly, colors on maps should match our perception.This means that certain colors are viewed through our experience and also cultural lenses.For example, green colors usually represent vegetation or lowlands and blue is connected with water or cool.Color palettes should also be easy to understand to effectively convey information.It should be clear which values are lower and which are higher, and colors should change gradually.This property is not preserved in the rainbow color palette; therefore, we suggest avoiding it in geographic data visualization (Borland and Taylor II 2007).Instead, the viridis color palettes, also available in tmap, can be used.Secondly, changes in colors should be accessible to the largest number of people.Therefore, it is important to use colorblind friendly palettes as often as possible.38

8.2.5 Layouts

The map layout refers to the combination of all map elements into a cohesive map.Map elements include among others the objects to be mapped, the title, the scale bar, margins and aspect ratios, while the color settings covered in the previous section relate to the palette and break-points used to affect how the map looks.Both may result in subtle changes that can have an equally large impact on the impression left by your maps.

Additional elements such as north arrows and scale bars have their own functions - tm_compass() and tm_scale_bar() (Figure 8.8).

  1. map_nz +
  2. tm_compass(type = "8star", position = c("left", "top")) +
  3. tm_scale_bar(breaks = c(0, 100, 200), size = 1)

Map with additional elements - a north arrow and scale bar.
Figure 8.8: Map with additional elements - a north arrow and scale bar.

tmap also allows a wide variety of layout settings to be changed, some of which are illustrated in Figure 8.9, produced using the following code (see args(tm_layout) or ?tm_layout for a full list):

  1. map_nz + tm_layout(title = "New Zealand")
  2. map_nz + tm_layout(scale = 5)
  3. map_nz + tm_layout(bg.color = "lightblue")
  4. map_nz + tm_layout(frame = FALSE)

Layout options specified by (from left to right) title, scale, bg.color and frame arguments.
Figure 8.9: Layout options specified by (from left to right) title, scale, bg.color and frame arguments.

The other arguments in tm_layout() provide control over many more aspects of the map in relation to the canvas on which it is placed.Some useful layout settings are listed below (see Figure 8.10 for illustrations of a selection of these):

  • Frame width (frame.lwd) and an option to allow double lines (frame.double.line).
  • Margin settings including outer.margin and inner.margin.
  • Font settings controlled by fontface and fontfamily.
  • Legend settings including binary options such as legend.show (whether or not to show the legend) legend.only (omit the map) and legend.outside (should the legend go outside the map?), as well as multiple choice settings such as legend.position.
  • Default colors of aesthetic layers (aes.color), map attributes such as the frame (attr.color).
  • Color settings controlling sepia.intensity (how yellowy the map looks) and saturation (a color-grayscale).
    Illustration of selected layout options.
    Figure 8.10: Illustration of selected layout options.

The impact of changing the color settings listed above is illustrated in Figure 8.11 (see ?tm_layout for a full list).

Illustration of selected color-related layout options.
Figure 8.11: Illustration of selected color-related layout options.

Beyond the low-level control over layouts and colors, tmap also offers high-level styles, using the tm_style() function (representing the second meaning of ‘style’ in the package).Some styles such as tm_style("cobalt") result in stylized maps, while others such as tm_style("gray") make more subtle changes, as illustrated in Figure 8.12, created using code below (see 08-tmstyles.R):

  1. map_nza + tm_style("bw")
  2. map_nza + tm_style("classic")
  3. map_nza + tm_style("cobalt")
  4. map_nza + tm_style("col_blind")

Selected tmap styles: bw, classic, cobalt and col_blind (from left to right).
Figure 8.12: Selected tmap styles: bw, classic, cobalt and col_blind (from left to right).

A preview of predefined styles can be generated by executing tmap_style_catalogue().This creates a folder called tmap_style_previews containing nine images.Each image, from tm_style_albatross.png to tm_style_white.png, shows a faceted map of the world in the corresponding style.Note: tmap_style_catalogue() takes some time to run.

8.2.6 Faceted maps

Faceted maps, also referred to as ‘small multiples’, are composed of many maps arranged side-by-side, and sometimes stacked vertically (Meulemans et al. 2017).Facets enable the visualization of how spatial relationships change with respect to another variable, such as time.The changing populations of settlements, for example, can be represented in a faceted map with each panel representing the population at a particular moment in time.The time dimension could be represented via another aesthetic such as color.However, this risks cluttering the map because it will involve multiple overlapping points (cities do not tend to move over time!).

Typically all individual facets in a faceted map contain the same geometry data repeated multiple times, once for each column in the attribute data (this is the default plotting method for sf objects, see Chapter 2).However, facets can also represent shifting geometries such as the evolution of a point pattern over time.This use case of faceted plot is illustrated in Figure 8.13.

  1. urb_1970_2030 = urban_agglomerations %>%
  2. filter(year %in% c(1970, 1990, 2010, 2030))
  3. tm_shape(world) + tm_polygons() +
  4. tm_shape(urb_1970_2030) + tm_symbols(col = "black", border.col = "white",
  5. size = "population_millions") +
  6. tm_facets(by = "year", nrow = 2, free.coords = FALSE)

Faceted map showing the top 30 largest urban agglomerations from 1970 to 2030 based on population projects by the United Nations.
Figure 8.13: Faceted map showing the top 30 largest urban agglomerations from 1970 to 2030 based on population projects by the United Nations.

The preceding code chunk demonstrates key features of faceted maps created with tmap:

  • Shapes that do not have a facet variable are repeated (the countries in world in this case).
  • The by argument which varies depending on a variable (year in this case).
  • nrow/ncol setting specifying the number of rows and columns that facets should be arranged into.
  • The free.coords-parameter specifying if each map has its own bounding box.
    In addition to their utility for showing changing spatial relationships, faceted maps are also useful as the foundation for animated maps (see Section 8.3).

8.2.7 Inset maps

An inset map is a smaller map rendered within or next to the main map.It could serve many different purposes, including providing a context (Figure 8.14) or bringing some non-contiguous regions closer to ease their comparison (Figure 8.15).They could be also used to focus on a smaller area in more detail or to cover the same area as the map, but representing a different topic.

In the example below, we create a map of the central part of New Zealand’s Southern Alps.Our inset map will show where the main map is in relation to the whole New Zealand.The first step is to define the area of interest, which can be done by creating a new spatial object, nz_region.

  1. nz_region = st_bbox(c(xmin = 1340000, xmax = 1450000,
  2. ymin = 5130000, ymax = 5210000),
  3. crs = st_crs(nz_height)) %>%
  4. st_as_sfc()

In the second step, we create a base map showing the New Zealand’s Southern Alps area.This is a place where the most important message is stated.

  1. nz_height_map = tm_shape(nz_elev, bbox = nz_region) +
  2. tm_raster(style = "cont", palette = "YlGn", legend.show = TRUE) +
  3. tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 1) +
  4. tm_scale_bar(position = c("left", "bottom"))

The third step consists of the inset map creation.It gives a context and helps to locate the area of interest.Importantly, this map needs to clearly indicate the location of the main map, for example by stating its borders.

  1. nz_map = tm_shape(nz) + tm_polygons() +
  2. tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 0.1) +
  3. tm_shape(nz_region) + tm_borders(lwd = 3)

Finally, we combine the two maps using the function viewport() from the grid package, the first arguments of which specify the center location (x and y) and a size (width and height) of the inset map.

  1. library(grid)
  2. nz_height_map
  3. print(nz_map, vp = viewport(0.8, 0.27, width = 0.5, height = 0.5))

Inset map providing a context - location of the central part of the Southern Alps in New Zealand.
Figure 8.14: Inset map providing a context - location of the central part of the Southern Alps in New Zealand.

Inset map can be saved to file either by using a graphic device (see Section 7.8) or the tmap_save() function and its arguments - insets_tm and insets_vp.

Inset maps are also used to create one map of non-contiguous areas.Probably, the most often used example is a map of the United States, which consists of the contiguous United States, Hawaii and Alaska.It is very important to find the best projection for each individual inset in these types of cases (see Chapter 6 to learn more).We can use US National Atlas Equal Area for the map of the contiguous United States by putting its EPSG code in the projection argument of tm_shape().

  1. us_states_map = tm_shape(us_states, projection = 2163) + tm_polygons() +
  2. tm_layout(frame = FALSE)

The rest of our objects, hawaii and alaska, already have proper projections; therefore, we just need to create two separate maps:

  1. hawaii_map = tm_shape(hawaii) + tm_polygons() +
  2. tm_layout(title = "Hawaii", frame = FALSE, bg.color = NA,
  3. title.position = c("LEFT", "BOTTOM"))
  4. alaska_map = tm_shape(alaska) + tm_polygons() +
  5. tm_layout(title = "Alaska", frame = FALSE, bg.color = NA)

The final map is created by combining and arranging these three maps:

  1. us_states_map
  2. print(hawaii_map, vp = grid::viewport(0.35, 0.1, width = 0.2, height = 0.1))
  3. print(alaska_map, vp = grid::viewport(0.15, 0.15, width = 0.3, height = 0.3))

Map of the United States.
Figure 8.15: Map of the United States.

The code presented above is compact and can be used as the basis for other inset maps but the results, in Figure 8.15, provide a poor representation of the locations of Hawaii and Alaska.For a more in-depth approach, see the us-map vignette from the geocompkg.