-
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathnutshell.qmd
More file actions
336 lines (281 loc) · 16.2 KB
/
nutshell.qmd
File metadata and controls
336 lines (281 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
```{r}
#| echo: false
source("code/before_script.R")
```
# **tmap** in a nutshell {#sec-nutshell}
The **tmap** package creates thematic maps with great flexibility.
It accepts spatial data in various formats -- shape objects (@sec-shape-objects) and next, the data can be used to create simple, quick maps (@sec-quick-maps) and more complex and expandable maps (@sec-regular-maps).
Additionally, with **tmap** it is possible to create facet maps (@sec-facets-section) and map animations (@sec-ani-section).
Finally, **tmap** has an interactive mode (@sec-map-modes) that allows users to zoom in and out, pan the map, or click on map objects to get more information -- this can be done without any additional code.
The goal of this chapter is to provide a brief overview of the main **tmap** features.
## Shape objects {#sec-shape-objects}
\index{shape objects}
\index{spatial data}
\index{tm\_shape}
As we established in @sec-geodata, spatial data comes from various file formats related to two main data models -- vector and raster.
There are also several spatial object classes in R, for example, `sf` from the **sf** package for vector data, `SpatRaster` from the **terra** package, and `stars` from **stars** for raster data and spatial data cubes.
Additionally, packages such as **sp** or **raster** offer their own classes, and this abundance of spatial object classes can be generally overwhelming.
Gladly, **tmap** can work with all of the above objects -- it treats all supported spatial data classes as so-called *shape objects*.
For example, we read the `slo_cities.gpkg` file containing several point locations representing cities and towns in Slovenia into a new object, `slo_cities`.
The `slo_cities` object is a *shape object*.
```{r}
#| message: false
library(tmap)
library(sf)
slo_cities = read_sf("data/slovenia/slo_cities.gpkg")
```
<!-- ref also to data appendix? -->
<!-- Next paragraph: to include or remove? -->
<!-- Spatial data, no matter the class, usually stores two interrelated sets of information - about the locations/geometries and their associated values/attributes. -->
<!-- Visualization of the attributes only can be done with regular plotting functions (e.g., `plot()`, `hist()`, `barplot()`) or dedicated packages, such as **ggplot2** <!--cite-->.
<!-- On the other hand, **tmap** is suitable when our goal is to visualize spatial geometries only or spatial geometries together with their attributes. -->
## Quick maps {#sec-quick-maps}
\index{quick maps}
\index{qtm}
The **tmap** package offers two distinct ways to create maps: a quick and a regular one.
The first approach, using the `qtm()` function, could be handy for data exploration.
It works even if we provide any *shape object* -- in that case, only the geometry is plotted.
@fig-qtm-1 shows a visualization of the geometries from the `slo_cities`.
```{r}
#| label: qtm1
#| eval: false
qtm(slo_cities)
```
The `qtm()` function customizes many map elements for the provided *shape object*.
For example, we can change the shapes of the points in `slo_cities`, make their sizes related to the the `"population"` argument, and add a title (@fig-qtm-2).
```{r}
#| label: qtm2
#| eval: false
qtm(slo_cities, shape = 24, size = "population", title = "Cities")
```
```{r}
#| label: fig-qtm
#| echo: false
#| layout-nrow: 2
#| fig-cap: Example of a quick map created with the `qtm()` function.
#| fig-subcap:
#| - A map with geometries only.
#| - A map with geometries and attributes.
<<qtm1>>
<<qtm2>>
```
The `qtm()` function is a quick way to create maps, but it has some limitations.
It supports only one *shape object* at a time and does not allow for much customization.
## Regular maps {#sec-regular-maps}
\index{map elements}
\index{tm\_shape}
\index{map layers}
\index{tm\_polygons}
\index{tm\_compass}
\index{tm\_scalebar}
For most applications, we recommend using the regular mapping approach.
This approach operates on many functions with the `tm_` prefix that we call one after another with the `+` operator.
We call these functions *tmap elements*.
The first element always^[Almost always...] is `tm_shape()`, which specifies the input shape object.
Next, map layers (e.g., `tm_polygons()` or `tm_symbols()`), additional map components (e.g., `tm_compass()` or `tm_scalebar()`), and overall layout options can be customized.
The last example in @sec-quick-maps can be reproduced with the regular map approach using the following code.
```{r}
#| eval: false
tm_shape(slo_cities) +
tm_symbols(shape = 24, size = "population") +
tm_title("Cities")
```
Here, we specify the input data (our *shape object*) with `tm_shape()`, visual variables (also known as *aesthetics*) of map layers with `tm_symbols()`, and the map title with `tm_title()`.
\index{map layers}
\index{visual variables}
The **tmap** package has a number of possible map layers, but the most prominent ones are `tm_polygons()`, `tm_symbols()`, `tm_lines()`, `tm_raster()`, and `tm_text()` (@sec-layers).
Overall, most visual variables of map layers can be assigned in two main ways.
First, they accept a fixed, constant value, for instance, `shape = 24`, which sets the symbols' shapes to triangles.
Second, it is also possible to provide a variable name, for example, `size = "population"`.
This plots each point with a size based on the `population` attribute from the `slo_cities` object and automatically adds a related map legend.
\index{tm\_shape}
The `tm_shape()` function and one or more of the following map layers create a *group* together.
In other words, map layers are related only to the preceding `tm_shape()` call.
One map can have several *groups*.
Let's see how many *groups* can be used by reading some additional datasets -- the `slo_elev` raster with elevation data of Slovenia, the `slo_borders` polygon with the country borders, and the `slo_railroads` lines contain a railroad network for this country.
```{r}
#| message: false
library(sf)
library(terra)
slo_borders = read_sf("data/slovenia/slo_border.gpkg")
slo_railroads = read_sf("data/slovenia/slo_railroads.gpkg")
slo_elev = rast("data/slovenia/slo_elev.tif")
```
Look at the following example and try to guess how many *groups* it has, and how many layers exist for each *group* (@fig-rmap1).
```{r}
#| label: fig-rmap1
#| warning: false
#| fig-cap: "Example of a map with four groups of map layers: an elevation layer, country borders layer, railroads layer, and cities layer."
tm_shape(slo_elev) +
tm_raster(col.scale = tm_scale(values = "geyser"),
col.legend = tm_legend(title = "Elevation (m asl)")) +
tm_shape(slo_borders) +
tm_borders() +
tm_shape(slo_railroads) +
tm_lines(lwd = "track_width",
lwd.legend = tm_legend(show = FALSE)) +
tm_shape(slo_cities) +
tm_symbols(shape = 24, size = "population",
size.legend = tm_legend(title = "Population")) +
tm_title("Slovenia") +
tm_layout(bg.color = "grey95")
```
\index{tm\_title}
\index{tm\_layout}
\index{tm\_legend}
The correct answer is four groups, all with just one layer.
Each *group* is put on top of the previous one -- **tmap** uses a layered approach.
The first *group* represents elevation data with a continuous color scale style, a color palette called `"geyser"`, and a legend title.
The second *group* shows the borders of Slovenia with the default aesthetics, while the third *group* presents the railroad network (the `slo_railroads` object), with each line's width based on the values from the `"track_width"` column, but with a legend hidden.
The last *group* is similar to our previous example with fixed symbol shapes and sizes related to the `"elevation"` attribute, but also with the legend title instead of the map title.
Additionally, we use the `tm_title()` function to add a map title and `tm_layout()` to modify the general appearance of the map.
You can also notice that we can control scales of various visual variables, such as color, size, or width, with the `tm_scale_*()` function and customize legends with the `tm_legend()` function.
\index{graticules}
\index{north arrow}
\index{scale bar}
\index{map credits}
\index{tm\_graticules}
\index{tm\_compass}
\index{map components}
Often, maps also have map components, such as graticule lines, north arrow, scale bar, or map credits (@fig-rmap2).
They help map readers understand the location or extent of the input data and provide some ancillary information.
The **tmap** package offers a set of functions for additional map elements.
The `tm_graticules()` function draws latitude and longitude graticules and adds their labels.
It also uses the layered approach, and thus, the lines are drawn either below or above the shape objects, depending on the position of this function in the code.
In our example below, `tm_graticules()` is used after all of the map groups, and that is why the graticule lines are put on the top of the spatial data.
We can also use `tm_compass()` to create a north arrow, `tm_scalebar()` to add a scale bar, and `tm_credits()` to add a text annotation representing credits or acknowledgments.
The location of all these three elements on the map is, by default, automatically determined.
It, however, can be adjusted with the `position` argument -- see an example of its use in the `tm_compass()` function below.
Moreover, it is possible to add any type of manual legend with `tm_add_legend()`.
It includes simple legends below, such as the `"Railroads"` legend element, that is only represented by a single black line and a related label, but more complex custom legends with several elements are also possible.
```{r}
#| warning: false
my_map = tm_shape(slo_elev) +
tm_raster(col.scale = tm_scale(values = "geyser"),
col.legend = tm_legend(title = "Elevation (m asl)")) +
tm_shape(slo_borders) +
tm_borders() +
tm_shape(slo_railroads) +
tm_lines(lwd = "track_width",
lwd.legend = tm_legend(show = FALSE)) +
tm_shape(slo_cities) +
tm_symbols(shape = 24, size = "population",
size.legend = tm_legend(title = "Population")) +
tm_graticules() +
tm_compass(position = c("right", "top")) +
tm_scalebar() +
tm_credits("Author, Year") +
tm_add_legend(type = "lines", col = "black", labels = "Railroads") +
tm_title("Slovenia") +
tm_layout(bg.color = "grey95")
```
Maps created with **tmap** can be saved as R objects.
This is a useful feature that allows the use of the same map in a few places in a code, modify existing **tmap** objects by adding additional *tmap elements*, or save these objects to files.
```{r}
#| label: fig-rmap2
#| message: false
#| fig-cap: "Example of a map with four groups of map layers and additional map elements, such as graticule lines, north arrow, scale bar, and text annotation. It also has a manually added legend."
my_map
```
All of these building blocks of **tmap** maps are explained in more detail in chapters from @sec-tmshape to @sec-map-layout.
## Facets {#sec-facets-section}
\index{facets}
\index{small multiples}
The **tmap** package makes the creation of facet maps, also known as small multiples, possible.
In such maps, the same data is visualized in several panels, each showing a different subset of the data.
The panels may represent different variables, time periods, or spatial extents.
The `slo_regions` object contains the administrative regions of Slovenia with their names, population density (`gdppercap`), GDP per capita (`gdppercap`), region group name (`region_group`), and other attributes.
One way to visualize this data as facets is to specify selected columns as visual variables in the map layer function (e.g., `tm_polygons()`) (@fig-sec-facets1).
```{r}
#| label: fig-sec-facets1
#| fig-cap: "A facet map with three variables visualized in separate panels."
#| fig-asp: 0.5
slo_regions = read_sf("data/slovenia/slo_regions.gpkg")
tm_shape(slo_regions) +
tm_polygons(fill = c("pop_dens", "gdppercap", "region_group"))
```
\index{tm\_facets}
Another possible approach is to use the `tm_facets()` function.
Let's illustrate it with the `slo_regions_ts` object containing the same variables as `slo_regions` but for many years (`time` column).
@fig-sec-facets2 shows a facet map with the GDP per capita, specified with `tm_polygons()`, and visualized in separate panels for each year as defined by the `tm_facets()` function.
```{r}
#| label: fig-sec-facets2
#| fig-cap: "A facet map with the GDP per capita visualized in separate panels for each year."
slo_regions_ts = read_sf("data/slovenia/slo_regions_ts.gpkg")
tm_shape(slo_regions_ts) +
tm_polygons("gdppercap") +
tm_facets(by = "time", ncol = 5)
```
Facet maps can be used for vector and raster data, their extents can be adjusted, and they can be further customized with additional labels.
This is a topic of the @sec-facets.
## Animations {#sec-ani-section}
\index{animations}
\index{tm\_animate}
@fig-sec-ani1 shows an animated map with the GDP per capita visualized for each year.
Animations in **tmap** are created similarly to the facet maps, but instead of using the `tm_facets()` function, we use the `tm_animate()` function.
It defines the variable that is used to create the animation, in this case, the `time` column from the `slo_regions_ts` object.
```{r}
#| eval: false
slo_regions_ts = read_sf("data/slovenia/slo_regions_ts.gpkg")
tm_shape(slo_regions_ts) +
tm_polygons("gdppercap") +
tm_animate(frames = "time")
```
```{r}
#| echo: false
#| message: false
#| label: fig-sec-ani1
#| fig-cap: "An animated map with the GDP per capita visualized for each year."
slo_regions_ts = read_sf("data/slovenia/slo_regions_ts.gpkg")
tma = tm_shape(slo_regions_ts) +
tm_polygons("gdppercap") +
tm_animate(frames = "time")
view_animation(tma, "ani1")
```
This function stitches together the map layers for each value of the `time` variable, creating a smooth transition between them.
We can customize its speed and if it should loop or not with the `fps` and `play` arguments, respectively.
Animations can be used for both vector and raster data and then saved as GIF or video files using the `tmap_animation()` function.
@sec-animations expands on this topic and shows how to create more complex animations, such as animated maps with facets.
## Map modes {#sec-map-modes}
\index{map modes}
\index{interactive maps}
\index{tmap\_mode}
Each map created with **tmap** can be viewed in a few map modes, including the `"plot"` and `"view"` modes.
<!-- maybe add note that more modes are possible + ref -->
The `"plot"` mode is used by default and creates static maps similar to those shown before in this chapter.
This mode supports almost all of **tmap**'s features, and it is recommended, for example, for scientific publications or printing.
The second mode, `"view"`, creates interactive maps.
They can be zoomed in and out or panned, allow for changing background tiles (*basemaps*), or click on map objects to get some additional information.
This mode has, however, some constraints and limitations compared to `"plot"`, for example, the legend cannot be fully customized, and some additional map elements are not supported.
It is important to highlight that both modes can be used with the same **tmap** code.
Therefore, there is no need to create two separate sets of code for static and interactive use.
The `tmap_mode()` function can be used to switch from one mode to the other^[Map modes can also be changed globally using `tmap_options()` or switched using `ttm()`.].
```{r}
tmap_mode("view")
```
The above line of code just changes the mode -- it does not return anything except a message.
Now, if we want to use this mode, we need to either write a new **tmap** code or provide some existing **tmap** object, such as `my_map`.
```{r}
#| eval: false
my_map
```
Our result now is the interactive map (@fig-imap1).
It shows our spatial data using aesthetics similar to @fig-rmap2 but allows us to zoom in and out or move the map.
We also can select a basemap or click on any line and point to get some related information.
```{r}
#| label: fig-imap1
#| echo: false
#| cache: false
#| message: false
#| fig-width: 9
#| fig-cap: Map from the previous figure shown using the interactive ("view") mode.
view_map(my_map, "imap1")
```
To go back to the `"plot"` mode, we need to use the `tmap_mode()` function again -- map not shown.
```{r}
#| fig-show: hide
#| message: false
tmap_mode("plot")
my_map
```
More information about the interactive `"view"` mode and how to customize its outputs is in @sec-interactive.