-
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy patharrangements.qmd
More file actions
230 lines (194 loc) · 10.3 KB
/
arrangements.qmd
File metadata and controls
230 lines (194 loc) · 10.3 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
```{r arrangements-1}
#| echo: false
source("code/before_script.R")
```
# Arranging maps {#sec-arranging-maps}
\index{arranging maps}
The **tmap** package offers several tools for arranging multiple maps within a single layout.
When we want to display one main map and one or more additional maps, we can use `tm_minimap()` or `tm_inset()`.
The first function adds a small overview map to the main map using a fixed, non-customizable style (as we already showed in @sec-minimap).
More advanced arrangements can be made with the `tm_inset()` function, which adds a graphical object (including a `tmap` object) to the main map (see @sec-inset-maps below).
To create a layout with multiple maps, we have two main options.
The first one is to use the `tmap_arrange()` function.
It takes two or more `tmap` objects and arranges them in a grid layout, as you can learn in @sec-arrangements and @sec-customizing-arrangements.
The second option is to create facets with the `tm_facet()` function, which is described in @sec-facets.
The main difference is that `tmap_arrange()` combines multiple maps, often based on different data, while `tm_facet()` creates a single map with numerous panels based on the same data.
## Inset maps {#sec-inset-maps}
\index{inset maps}
\index{tm\_inset}
Inset maps are a powerful way to add additional context or details to a main map.
They are typically smaller maps that show a specific area of interest, such as a zoomed-in view of a region, or a different data layer that complements the main map.
In **tmap**, insets may be based on a bounding box, another `tmap` object, **ggplot2** objects, or image files.
To add an inset map to a main map, we can use the `tm_inset()` function.
@fig-inset-maps1 shows how to add multiple inset maps to a main map.
Each inset is defined by a bounding box using the `tmaptools::bb()` function, which takes a place name as input and returns a bounding box for that place.^[Alternatively, you can use a bounding box defined by a vector of coordinates, such as `c(xmin, ymin, xmax, ymax)` or another spatial object.]
In such cases, inset maps are added to the main map, with the same extent as the bounding box and the same style as the main map.
```{r}
#| label: fig-inset-maps1
#| fig-cap: "Inset maps based on bounding boxes."
#| message: false
library(tmap)
library(terra)
slo_elev = rast("data/slovenia/slo_elev.tif")
tm_shape(slo_elev) +
tm_raster() +
tm_inset(tmaptools::bb("Maribor")) +
tm_inset(tmaptools::bb("Ljubljana")) +
tm_inset(tmaptools::bb("Bled"))
```
The above map can be further customized by improving the color palette and adding titles to the inset maps.
We can use the `tm_title()` function to add titles to the inset maps, and the `tm_inset()` function to specify the bounding boxes for each inset map.
Then, each of these components can be grouped using the `group_id` argument and arranged using the `tm_components()` function (@sec-position-many-components).
```{r}
#| label: fig-inset-maps2
#| fig-cap: "Customized inset maps."
#| fig-width: 9
tm_shape(slo_elev) +
tm_raster(
col.scale = tm_scale_continuous(values = "geyser", midpoint = NA)
) +
tm_title("Maribor", group_id = 1) +
tm_inset(tmaptools::bb("Maribor"), group_id = 1) +
tm_title("Ljubljana", group_id = 1) +
tm_inset(tmaptools::bb("Ljubljana"), group_id = 1) +
tm_title("Bled", group_id = 2) +
tm_inset(tmaptools::bb("Bled"), group_id = 2) +
tm_components(1, position = c("right", "bottom")) +
tm_components(2, position = c("left", "top"))
```
The `tm_inset()` function can also be used to add a `tmap` object as an inset map.
In the following example, we create a `tmap` object for Slovenia's elevation.
Importantly, we set the limits of the color scale to match the limits of the elevation data of the main map and remove the legend using the `tm_legend(show = FALSE)` argument to avoid duplication of the legend in the inset map.
We also add a scale bar to the inset map using the `tm_scalebar()` function to facilitate a better understanding of the area's size shown in the inset.
```{r}
tm_slo_elev = tm_shape(slo_elev) +
tm_raster(
col.scale = tm_scale_continuous(values = "geyser", midpoint = NA,
limits = c(-100, 4000)),
col.legend = tm_legend(show = FALSE)
) +
tm_scalebar(breaks = c(0, 10, 20),
position = c("RIGHT", "BOTTOM"))
```
Now, we can create our main map and add the inset map based on the `tm_slo_elev` object (@fig-inset-maps3).
The main map in our case would be the elevation of Europe, using the same color scale as the inset map -- however, this time, we include the legend.
```{r}
#| label: fig-inset-maps3
#| fig-cap: "Inset maps based on tmap objects."
#| message: false
#| fig-asp: 1
worldelevation = rast("data/worldelevation.tif")
tm_shape(worldelevation, bbox = "Europe") +
tm_raster(
col.scale = tm_scale_continuous(values = "geyser", midpoint = NA,
limits = c(-100, 4000))
) +
tm_scalebar() +
tm_title("Slovenia", group_id = 1) +
tm_inset(tm_slo_elev, group_id = 1) +
tm_components(1, position = c("LEFT", "TOP"))
```
\index{ggplot2!inset maps}
The **ggplot2** package can also be used to create insets.
Such insets could be based on some external data or be derived from the map data itself to provide additional non-spatial information.
In the following example, we create a bar plot illustrating the number of people living in Slovenia's cities and towns.
The default **ggplot2** theme includes many elements, such as grid lines, axis ticks, and background, which are often unnecessary in inset maps and can distract from the main map.
Thus, we also remove the labeling of the axes and apply the `theme_minimal()` function to simplify the plot.
```{r}
#| message: false
library(ggplot2)
library(sf)
slo_cities = read_sf("data/slovenia/slo_cities.gpkg")
gg1 = ggplot(slo_cities, aes(place, population)) +
geom_col() +
scale_y_continuous(labels = scales::comma) +
labs(x = NULL, y = NULL) +
theme_minimal()
```
We can add the new **ggplot2** object as an inset map in the same way as we did with the `tmap` object.
@fig-inset-maps4 displays a map of Slovenia's regions, with points representing cities and towns, and then adds the **ggplot2** object as an inset map.
It allows us to see that even though there are more towns than cities in our dataset, more people live in cities than in towns.
```{r}
#| label: fig-inset-maps4
#| fig-cap: "Inset maps based on a ggplot2 object."
#| message: false
#| fig-width: 7.5
slo_regions = read_sf("data/slovenia/slo_regions.gpkg")
tm_shape(slo_regions) +
tm_polygons("gdppercap") +
tm_shape(slo_cities) +
tm_symbols(shape = "place") +
tm_labels("name", bgcol = "white") +
tm_inset(gg1, position = c("right", "bottom"))
```
<!-- image insets? -->
::: {.callout-note}
Insets are not usually needed in the interactive mode, as we can zoom in and out of the main map.
Thus, the `tm_inset()` function calls only work in plot mode.
:::
## Basic arrangements {#sec-arrangements}
\index{tmap\_arrange}
Let's see how the `tmap_arrange()` function works based on two maps of Slovenia, each based on different data sources.
The first map is based on the GDP per capita of Slovenia's regions, represented as polygons.
```{r arrangements-3}
#| message: false
library(tmap)
library(sf)
slo_regions = read_sf("data/slovenia/slo_regions.gpkg")
tm1 = tm_shape(slo_regions) +
tm_polygons("gdppercap") +
tm_title("GDP per capita in Slovenia")
```
The second map is based on the elevation of Slovenia, represented as a raster layer.
```{r arrangements-4}
#| message: false
library(tmap)
library(terra)
slo_elev = rast("data/slovenia/slo_elev.tif")
tm2 = tm_shape(slo_elev) +
tm_raster() +
tm_title("Elevation in Slovenia")
```
In both cases, we also added titles to the maps using the `tm_title()` function to clarify what each map represents, and assigned them to the objects `tm1` and `tm2`.
Now, we may arrange these two maps in a single layout using the `tmap_arrange()` function (@fig-arrangements1).
The `tmap_arrange()` function can take any number of `tmap` objects as input or even a list of `tmap` objects.
```{r}
#| label: fig-arrangements1
#| fig-cap: "Arranging two maps in one layout."
#| message: false
#| fig-asp: 0.8
tmap_arrange(tm1, tm2, ncol = 2)
```
In the example above, we used our two maps and specified the number of columns in the layout using the `ncol` argument.
We can also specify the number of rows using the `nrow` argument, the aspect ratio of the maps using the `asp` argument, and the widths and heights of the maps using the `widths` and `heights` arguments, respectively.
## Customizing arrangements {#sec-customizing-arrangements}
The `tmap_arrange()` does not align layouts of multiple maps -- you may notice that the frames of the two maps in @fig-arrangements1 are not equal in height.
To align the frames of the maps, we can use the `meta.margins` argument in the `tm_layout()` function for each map.
This argument allows us to specify the margins around the map frame available for map elements, such as titles and legends, in the order of `c(bottom, left, top, right)`.
<!-- Martijn, is the above statement correct? -->
Here, we set the bottom margin to `0.3`<!-- units? --> for both maps, which will align their frames vertically.
See the effect of this change in @fig-arrangements2.
```{r}
#| label: fig-arrangements2
#| fig-cap: "Arranging two maps in one layout with aligned frames."
#| message: false
tm1a = tm1 +
tm_layout(meta.margins = c(0.3, 0, 0, 0))
tm2a = tm2 +
tm_layout(meta.margins = c(0.3, 0, 0, 0))
tmap_arrange(tm1a, tm2a, ncol = 2)
```
You may also notice that, as we set the top margin of the second map to `0`, the titles of both maps were moved inside the map frames.
If you want to keep the titles outside, you can set the top margin of the second map to a small value, such as `0.05`.
::: {.callout-note}
The outcomes of the `tmap_arrange()` function also work in the **tmap** view mode, which allows you to explore a few maps at once interactively.
:::
<!-- widths, heights -->
<!-- any better example idea? -->
<!-- tmap_arrange(tm1a, tm2a, nrow = 2, heights = c(0.8, 0.2)) -->
<!-- possibly add a tm_add_legend example plus a ref? -->
::: {.callout-note}
The `tmap_arrange()` function call can be saved to an object, which can then be used in other functions, such as `tmap_save()`, to save the arranged maps to a file.
For more information on saving maps, see @sec-save.
:::
<!-- tmap_grob? -->