forked from geocompx/geocompr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
09-mapping.Rmd
1075 lines (870 loc) · 67 KB
/
09-mapping.Rmd
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# (PART) Advanced methods {-}
# Making maps with R {#adv-map}
## Prerequisites {-}
- This chapter requires the following packages that we have already been using:
```{r, message = FALSE}
library(sf)
library(spData)
library(spDataLarge)
library(tidyverse)
```
- In addition it uses the following visualization packages:
```{r}
library(tmap) # for static and interactive maps
library(leaflet) # for interactive maps
library(mapview) # for interactive maps
library(shiny) # for web applications
```
## Introduction
A satisfying and important aspect of geographic research is producing and communicating the results in the form of maps.
Map making --- the art of Cartography --- is an ancient skill that involves precision, consideration of the map-reader and often an element of creativity.
Basic plotting of geographic data is straightforward in R with `plot()` (see section \@ref(basic-map)), and it is possible to create advanced maps using base R methods [@murrell_r_2016].
This chapter focuses on dedicated map-making packages, especially **tmap**, for reasons that are explained in section \@ref(static-maps).
There are a multitude of options but when learning a new skill (in this case map making), it makes sense to gain depth-of-knowledge in one package before branching out, a concept that also applies to the choice of programming language as we saw in Chapter \@ref(intro).
It is worth developing advanced map making skills not only because it is a fun activity that can produce beautiful results.
Map making also has important practical applications.
A carefully crafted map can help ensure that time spent in the analysis phases of geocomputational projects --- for example using methods covered in chapters \@ref(spatial-class) to \@ref(transform) --- are communicated effectively [@brewer_designing_2015]:
> Amateur-looking maps can undermine your audience’s ability to understand important information and weaken the presentation of a professional data investigation.
<!-- Todo: consider adding footnote saying it's good to focus on visualization early as in R4DS but that we cover it later because there's a risk of getting distracted by pretty pictures to the detriment of good analysis. -->
Maps have been used for several thousand years for a wide variety of purposes.
Historic examples include maps of buildings and land ownership in the Old Babylonian dynasty more than 3000 years ago and Ptolemy's world map in his masterpiece *Geography* nearly 2000 years ago [@talbert_ancient_2014].
However, maps have historically been out of reach for everyday people.
Modern computing has the potential to change this.
Map making skills can also help meet research and public engagement objectives.
From a research perspective clear maps are often be the best way to present the results of geocomputational research.
From policy and 'citizen science' perspectives, attractive and engaging maps can help change peoples' minds, based on the evidence.
Map making is therefore a critical part of geocomputation and its emphasis not only on describing, but also *changing* the world (see Chapter \@ref(intro)).
<!-- info about relation between efficiency and editability -->
<!-- intro to the chapter structure -->
## Static maps
Static maps are the most common type of visual output from geocomputation.
They are fixed images that can be included in printed outputs or published online.
The majority of maps contained in this book are static maps saved as `.png` files (interactive maps are covered in section \@ref(interactive-maps)).
The generic `plot()` function is often the fastest way to create static maps from vector and raster spatial objects, as demonstrated in sections \@ref(basic-map) and \@ref(basic-map-raster).
Sometimes the simplicity and speed of this approach is sufficient, especially during the development phase of a project:
when using R interactively to understand a geographic dataset, you will likely be the only person who sees them.
The base R approach is also extensible, with `plot()` offering dozens of arguments and the **grid** providing functions for low-level control of graphical outputs, --- see *R Graphics* [@murrell_r_2016], especially Chapter [14](https://www.stat.auckland.ac.nz/~paul/RG2e/chapter14.html).
The focus of this section, however, is making static 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, that 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-nutshell`](https://cran.r-project.org/web/packages/tmap/vignettes/tmap-nutshell.html) and [`tmap-modes`](https://cran.r-project.org/web/packages/tmap/vignettes/tmap-modes.html) and an academic paper on the subject [@tennekes_tmap_2018].
This section teaches how to make static maps with **tmap**, emphasizing the important aesthetic and layout options.
### tmap basics
**tmap** generates maps with sensible defaults for a wide range of spatial objects with `tm_shape()` (which accepts raster and vector objects), followed by one or more layer elements such as `tm_fill()` and `tm_dots()`.
These functions are used singularly and in combination in the code chunk below, which generates the maps presented in Figure \@ref(fig:tmshape):
```{r, eval=FALSE}
# Add fill layer to nz shape
tm_shape(nz) + tm_fill()
# Add border layer to nz shape
tm_shape(nz) + tm_borders()
# Add fill and border layers to nz shape
tm_shape(nz) + tm_fill() + tm_borders()
```
```{r tmshape, echo=FALSE, fig.cap="New Zealand's shape plotted with fill (left), border (middle) and fill *and* border (right) layers added using **tmap** functions."}
source("code/09-tmshape.R")
```
The object passed to `tm_shape()` in this case is `nz`, which represents the regions of New Zealand.
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 \@ref(fig:tmshape), 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 ``?`tmap-element``` for a list of available elements).
This 'layering' is illustrated in the right panel of Figure \@ref(fig:tmshape), which shows the result of adding a border *on top of* the fill layer.
The order in which layers are added is the order in which they are rendered.
```{block2 qtm, type = 'rmdnote'}
`qtm()` is a handy function for **q**uickly creating **t**map **m**aps (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.
```
### Map objects {#map-obj}
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 \@ref(fig:tmshape) as an object of class `tmap` (note the use of `tm_polygons()` which condenses `tm_fill() + tm_borders()` into a single function):
```{r}
map_nz = tm_shape(nz) + tm_polygons()
class(map_nz)
```
`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):
```{r, results='hide'}
map_nz1 = map_nz +
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_eleve`) representing average elevation across New Zealand (see Figure \@ref(fig:tmlayers), 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](https://en.wikipedia.org/wiki/Territorial_waters), and adds the resulting lines to an existing map object.
```{r}
nz_water = st_union(nz) %>% st_buffer(22200) %>%
st_cast(to = "LINESTRING")
map_nz2 = map_nz1 +
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 \@ref(fig:tmlayers) 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:
```{r}
map_nz3 = map_nz2 +
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 \@ref(fig:tmlayers).
```{r tmlayers, fig.cap="Maps with additional layers added to the final map of Figure 9.1."}
tmap_arrange(map_nz1, map_nz2, map_nz3)
```
Additional elements can also be added with the `+` operator.
Aesthetic settings, however, are controlled by arguments to layer functions.
### Aesthetics
The plots in the previous section demonstrate **tmap**'s default aesthetic settings.
Grey 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 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 the former, **tmap** layer functions accept aesthetic arguments that are either constant values *or* variable fields.
The 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 \@ref(fig:tmstatic).
```{r tmstatic, fig.cap="The impact of changing commonly used fill and border aesthetics to fixed values."}
ma1 = tm_shape(nz) + tm_fill(col = "red")
ma2 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3)
ma3 = tm_shape(nz) + tm_borders(col = "blue")
ma4 = tm_shape(nz) + tm_borders(lwd = 3)
ma5 = tm_shape(nz) + tm_borders(lty = 2)
ma6 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3) +
tm_borders(col = "blue", lwd = 3, lty = 2)
tmap_arrange(ma1, ma2, ma3, ma4, ma5, ma6)
```
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 \@ref(fig:tmcol)), **tmap** aesthetic arguments will not accept a numeric vector:
```{r, eval=FALSE}
plot(st_geometry(nz), col = nz$Land_area) # works
tm_shape(nz) + tm_fill(col = nz$Land_area) # fails
#> 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 \@ref(fig:tmcol)):
```{r, fig.show='hide'}
tm_shape(nz) + tm_fill(col = "Land_area")
```
```{r tmcol, fig.cap="Comparison of base (left) and tmap (right) handling of a numeric color field.", echo=FALSE, out.width="50%", fig.show='hold'}
plot(nz["Land_area"])
tm_shape(nz) + tm_fill(col = "Land_area")
```
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()` for to create superscript text):
```{r}
legend_title = expression("Area (km"^2*")")
map_nza = tm_shape(nz) +
tm_fill(col = "Land_area", title = legend_title) + tm_borders()
```
### 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 \@ref(fig:tmpal).
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` which allows you to manually set the breaks
- `n` which sets the number of bins into which numeric variables are categorized
- `palette` which defines the color scheme, for example `RdBu`
```{r, eval=FALSE}
breaks = c(0, 3, 4, 5) * 10000
tm_shape(nz) + tm_polygons(col = "Median_income")
tm_shape(nz) + tm_polygons(col = "Median_income", breaks = breaks)
tm_shape(nz) + tm_polygons(col = "Median_income", n = 10)
tm_shape(nz) + tm_polygons(col = "Median_income", palette = "RdBu")
```
```{r tmpal, fig.cap="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.", echo=FALSE}
source("code/09-tmpal.R", print.eval = TRUE)
```
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 \@ref(fig:break-styles) 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
```{r break-styles, fig.cap="Illustration of different binning methods set using the style argument in tmap.", echo=FALSE}
source("code/09-break-styles.R")
```
```{block2 break-style-note, type='rmdnote'}
Although `style` is an argument of **tmap** functions it in facts 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 \@ref(layouts) 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 \@ref(fig:colpal)), 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.^[The `col = "MAP_COLORS"` argument can be used in maps with a large number of individual polygons (for example a map of individual countries). It creates unique colors for adjacent polygons.]
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!
```{r, eval=FALSE}
tm_shape(nz) + tm_polygons("Population", palette = "Blues")
tm_shape(nz) + tm_polygons("Population", palette = "YlOrBr")
```
The last group, diverging palettes, typically range between three distinct colors (purple-white-green in Figure \@ref(fig:colpal)) 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.
```{r colpal, echo=FALSE, fig.cap="Examples of categorical, sequential and diverging palettes."}
library(RColorBrewer)
many_palette_plotter = function(color_names, n, titles){
n_colors = length(color_names)
ylim = c(0, n_colors)
par(mar = c(0, 5, 0, 0))
plot(1, 1, xlim = c(0, max(n)), ylim = ylim,
type = "n", axes = FALSE, bty = "n", xlab = "", ylab = "")
for(i in seq_len(n_colors)){
one_color = brewer.pal(n = n, name = color_names[i])
rect(xleft = 0:(n - 1), ybottom = i - 1, xright = 1:n, ytop = i - 0.2,
col = one_color, border = "light grey")
}
text(rep(-0.1, n_colors), (1: n_colors) - 0.6, labels = titles, xpd = TRUE, adj = 1)
}
many_palette_plotter(c("PRGn", "YlGn", "Set2"), 7,
titles = c("Diverging", "Sequential", "Categorical"))
```
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 spatial data visualization [@borland_rainbow_2007].
Instead [the viridis color palettes](https://cran.r-project.org/web/packages/viridis/) 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.^[See the "Color blindness simulator" options in `tmaptools::palette_explorer()`.]
### 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 \@ref(fig:na-sb)).
```{r na-sb, fig.cap="Map with additional elements - a north arrow and scale bar."}
map_nz +
tm_compass(type = "8star", position = c("left", "top")) +
tm_scale_bar(breaks = c(0, 100, 200), size = 1)
```
**tmap** also allows a wide variety of layout settings to be changed, some of which are illustrated in Figure \@ref(fig:layout1), produced using the following code (see `args(tm_layout)` or `?tm_layout` for a full list):
```{r, eval=FALSE}
map_nz + tm_layout(title = "New Zealand")
map_nz + tm_layout(scale = 5)
map_nz + tm_layout(bg.color = "lightblue")
map_nz + tm_layout(frame = FALSE)
```
```{r layout1, fig.cap="Layout options specified by (from left to right) title, scale, bg.color and frame arguments.", echo=FALSE}
source("code/09-layout1.R")
```
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 \@ref(fig:layout2) 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-greyscale).
```{r layout2, fig.cap="Illustration of selected layout options.", echo=FALSE}
# todo: add more useful settings to this plot
source("code/09-layout2.R")
```
The impact of changing the color settings listed above is illustrated in Figure \@ref(fig:layout3) (see `?tm_layout` for a full list).
```{r layout3, fig.cap="Illustration of selected color-related layout options.", echo=FALSE}
source("code/09-layout3.R")
```
<!-- Maybe reverse order and progress from the high- to the low-level control. Overall, this section reads a bit like a vignette. Is this the intention. -->
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("grey)` make more subtle changes, as illustrated in Figure \@ref(fig:tmstyles), created using code below (see `09-tmstyles.R`):
```{r, eval=FALSE}
map_nza + tm_style("bw")
map_nza + tm_style("classic")
map_nza + tm_style("cobalt")
map_nza + tm_style("col_blind")
```
```{r tmstyles, fig.cap="Selected tmap styles: bw, classic, cobalt and col_blind (from left to right).", echo=FALSE}
source("code/09-tmstyles.R")
```
```{block2 styles, type='rmdnote'}
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.
```
### Faceted maps
Faceted maps, also referred to as 'small multiples', are composed of many maps arranged side-by-side, and sometimes stacked vertically.
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 \@ref(spatial-class)).
However, facets can also represent shifting geometries such as as the evolution of a point pattern over time.
This use case of faceted plot is illustrated in Figure \@ref(fig:urban-facet).
<!-- todo: describe data type (long) and nrow vs ncol -->
```{r urban-facet, fig.cap="Faceted map showing the top 30 largest 'urban agglomerations' from 1970 to 2030 based on population projects by the United Nations.", fig.asp=0.4}
urb_1970_2030 = urban_agglomerations %>%
filter(year %in% c(1970, 1990, 2010, 2030))
tm_shape(world) + tm_polygons() +
tm_shape(urb_1970_2030) + tm_dots(size = "population_millions") +
tm_facets(by = "year", nrow = 2, free.coords = FALSE)
```
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 \@ref(animated-maps)).
### 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 \@ref(fig:insetmap1)) or bringing some non-contiguous regions closer to ease their comparison (Figure \@ref(fig:insetmap2)).
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 the 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`.
<!--# mapview::mapview(nz_height, native.crs = TRUE) or mapedit??-->
```{r}
nz_region = st_bbox(c(xmin = 1340000, xmax = 1450000,
ymin = 5130000, ymax = 5210000)) %>%
st_as_sfc() %>%
st_set_crs(st_crs(nz_height))
```
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.
```{r}
nz_height_map = tm_shape(nz_elev, bbox = tmaptools::bb(nz_region)) +
tm_raster(style = "cont", palette = "-Spectral",
auto.palette.mapping = FALSE, legend.show = FALSE) +
tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 1) +
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.
```{r}
nz_map = tm_shape(nz) + tm_polygons() +
tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 0.1) +
tm_shape(nz_region) + tm_borders(lwd = 3)
```
Finally, we combine the two maps.
A viewport from the **grid** package can be used by stating a center location (`x` and `y`) and a size (`width` and `height`) of the inset map.
```{r insetmap1, fig.cap="Inset map providing a context - location of the central part of the Southern Alps in New Zealand."}
library(grid)
nz_height_map
print(nz_map, vp = grid::viewport(0.8, 0.27, width = 0.5, height = 0.5))
```
Inset map can be save to file either by using a graphic device (see section \@ref(visual-outputs)) 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 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 type of cases (see section \@ref(reproj-geo-data) 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()`.
```{r}
us_states_map = tm_shape(us_states, projection = 2163) + tm_polygons() +
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:
```{r}
hawaii_map = tm_shape(hawaii) + tm_polygons() +
tm_layout(title = "Hawaii", frame = FALSE, bg.color = NA,
title.position = c("left", "bottom"))
alaska_map = tm_shape(alaska) + tm_polygons() +
tm_layout(title = "Alaska", frame = FALSE, bg.color = NA)
```
The final map is created by combining and arranging these three maps:
```{r insetmap2, fig.cap="Map of the United States."}
us_states_map
print(hawaii_map, vp = viewport(x = 0.4, y = 0.1, width = 0.2, height = 0.1))
print(alaska_map, vp = viewport(x = 0.15, y = 0.15, width = 0.3, height = 0.3))
```
The code presented above is very compact and allows for creation of many similar maps, however the map do not represent sizes and locations of Hawaii and Alaska well.
You can see an alternative approach in the [`vignettes/us-map.Rmd`](https://github.com/Robinlovelace/geocompr/blob/master/vignettes/us-map.Rmd) file in the book's GitHub repo, which tries to mitigate these issues.
<!-- extended info about using tm_layout to show legend in main plot and remove it in the others -->
The main goal of this section is to present how to generate and arrange inset maps.
The next step is to use the knowledge from the previous sections to improve the map style or to add another data layers.
Moreover, the same skills can be applied to combine maps and plots.
## Animated maps
Faceted maps, described in \@ref(faceted-maps), provide a way of showing how spatial relationships vary but the approach has disadvantages.
Facets become tiny when many of them are squeezed into a single plot, potentially hiding *any* spatial relationships.
Furthermore the fact that each facet is separated by space on the screen or page means that subtle differences between facets can be hard to detect.
With an increasing proportion of communication happening via digital screens, animated maps are becoming more popular.
Animated maps can even be useful for paper reports: you can always link readers to a web-page containing an animated (or interactive) version of a printed map to help make it come alive.
Figure \@ref(fig:urban-animated) is a simple example of an animated map.
Unlike the faceted plot it does not squeeze multiple maps into a single screen and allows the reader to see how the spatial distribution of the worlds most populous agglomerations evolve over time (see the book's website for the animated version).
```{r urban-animated, fig.cap="Animated map showing the top 30 largest 'urban agglomerations' from 1950 to 2030 based on population projects by the United Nations.", echo=FALSE}
knitr::include_graphics("figures/urban-animated.gif")
```
```{r, echo=FALSE, eval=FALSE}
source("code/09-urban-animation.R")
```
The animated map illustrated in Figure \@ref(fig:urban-animated) can be created using the same **tmap** techniques that generates faceted maps, demonstrated in section \@ref(faceted-maps).
There are two differences, however, related to arguments in `tm_facets()`:
- `free.coords = FALSE`, which maintains the map extent for each map iteration.
- `nrow = 1` and `ncol = 1` ensure only one facet is created per year.
These additional arguments are demonstrated in the subsequent code chunk:
```{r}
urb_anim = tm_shape(world) + tm_polygons() +
tm_shape(urban_agglomerations) + tm_dots(size = "population_millions") +
tm_facets(by = "year", free.coords = FALSE, nrow = 1, ncol = 1)
```
The resulting `urb_anim` represents a set of separate maps for each year.
The final stage is to combine them and save the result as a `.gif` file with `tmap_animation()`.
The following command creates the animation illustrated in Figure \@ref(fig:urban-animated), with a few elements missing, that we will add-in during the exercises:
```{r, eval=FALSE}
tmap_animation(urb_anim, filename = "urb_anim.gif", delay = 25)
```
Another illustration of the power of animated maps is provided in Figure \@ref(fig:animus).
This shows the development of states in United States, which first formed in the East and then incrementally to the West and finally into the interior.
Code to reproduce this map can be found in the script `09-usboundaries.R`.
```{r, echo=FALSE, eval=FALSE}
source("code/09-usboundaries.R")
source("code/09-uscolonize.R")
```
```{r animus, echo=FALSE, fig.cap="Animated map showing population growth and state formation and boundary changes in the United States, 1790-2010."}
u_animus = "https://user-images.githubusercontent.com/1825120/38543030-5794b6f0-3c9b-11e8-9da9-10ec1f3ea726.gif"
knitr::include_graphics(u_animus)
```
## Interactive maps
Static and animated maps can breathe life into geographic datasets.
Interactive maps can take reader participation to a whole new level, providing results with a life of their own that can be explored in myriad ways.
Interactivity can take many forms, including the appearance of popup messages when users click or mouse-over geographic features and maps dynamically linked to non-geographic plots.^[
A good example of such a dynamically linked visualisation is a map of Nigeria linked to a bar chart of fertility rates by Kyle Walker, published on [Twitter](https://twitter.com/kyle_e_walker/status/985948444966768641) and (for the source code) as a GitHub [Gist](https://gist.github.com/walkerke/5fe9a198a30270e2fcb8120a7fc8242a).
]
The most important type of interactivity, however, is the display of geographic data on an interactive or 'slippy' web maps.
The release of the **leaflet** package in 2015 revolutionized interactive web map creation from within R and a number of packages have built on these foundations adding new features (e.g. **leaflet.extras**) and making the creation of web maps as simple as creating static maps (e.g. **mapview** and **tmap**).
This section illustrates each approach in the opposite order.
We will explore how to make slippy maps with **tmap** (the syntax of which we have already learned), **mapview** and finally **leaflet** (which provides low level control over interactive maps).
A unique feature of **tmap** mentioned in section \@ref(static-maps) is its ability to create static and interactive maps using the same code.
Maps can be viewed interactively at any point by switching to view mode, using the command `tmap_mode("view")`.
This is demonstrated in the code below, which creates an interactive map of New Zealand based on the `tmap` object `map_nz`, created in section \@ref(map-obj), and illustrated in Figure \@ref(fig:tmview):
```{r, eval=FALSE}
tmap_mode("view")
map_nz
```
```{r tmview, fig.cap="Interactive map of New Zealand created with tmap in view mode.", echo=FALSE}
tmap_mode("view")
m_tmview = map_nz
tmap_leaflet(m_tmview)
```
Now that the interactive mode has been 'turned on', all maps produced with **tmap** will launch (another way to create interactive maps is with the `tmap_leaflet` function) .
Notable features of this interactive mode include the ability to specify the basemap using the `basemaps` argument in the function `tm_view()` (also see `?tm_basemap`), as demonstrated below (the result, a map of New Zealand with an interactive topographic basemap, is not shown):
```{r, eval=FALSE}
basemap = leaflet::providers$OpenTopoMap
map_nz +
tm_view(basemaps = basemap)
```
An impressive and little-known feature of **tmap**'s view mode is that it also works with facetted plots.
The argument `sync` in `tm_facets()` can be used in this case to produce multiple maps with syncronized zoom and pan settings, as illustrated in Figure \@ref(fig:sync) which was produced by the following code:
```{r, eval=FALSE}
world_coffee = left_join(world, coffee_data, by = "name_long")
facets = c("coffee_production_2016", "coffee_production_2017")
tm_shape(world_coffee) + tm_polygons(facets) +
tm_facets(nrow = 1, sync = TRUE)
```
```{r sync, fig.cap="Screenshot showing facetted interactive maps of global coffee producing in 2016 and 2017 in 'sync', demonstrating tmap's view mode in action.", echo=FALSE}
knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39561412-4dbba7ba-4e9d-11e8-885c-7973b351006b.png")
```
Switch **tmap** back to plotting mode with the same function:
```{r}
tmap_mode("plot")
```
If you are not proficient with **tmap**, the quickest way to create an interactive maps may be with **mapview**.
The following 'one liner' is a reliable way to interactively explore a wide range of spatial data formats:
```{r, eval=FALSE}
mapview::mapview(nz)
```
```{r mapview, fig.cap="Illustration of mapview in action.", echo=FALSE}
knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39979522-e8277398-573e-11e8-8c55-d72c6bcc58a4.png")
# mv = mapview::mapview(nz)
# mv@map
```
**mapview** has a concise syntax yet is powerful. By default, it provides some standard GIS functionality such as mouse position information, attribute queries (via pop-ups), scale bar, zoom-to-layer buttons.
It offers advanced controls including the ability to 'burst' datasets into multiple layers and the addition of multiple layers with `+` followed by the name of a geographic object. Additionlaly, it provides automatic coloring of attributes (via argument `zcol`). In essence, it can be considered a data-driven **leaflet** API (see below for more information about **leaflet**). Given that **mapview** always expects a spatial object (sf, sp, raster) as its first argument, it works well at the end of piped expressions. Consider the following example where **sf** is used to intersect lines and polygons and then visualised with **mapview**.
```{r, eval=FALSE}
trails %>%
st_transform(st_crs(franconia)) %>%
st_intersection(franconia[franconia$district == "Oberfranken", ]) %>%
st_collection_extract("LINE") %>%
mapview(color = "red", lwd = 3, layer.name = "trails") +
mapview(franconia, zcol = "district", burst = TRUE) +
breweries
```
```{r, mapview2, fig.cap="Using mapview at the end of a sf based pipe expression.", echo=FALSE, warning=FALSE}
knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39979271-5f515256-573d-11e8-9ede-e472ca007d73.png")
# commented out because interactive version not working
# mv2 = trails %>%
# st_transform(st_crs(franconia)) %>%
# st_intersection(franconia[franconia$district == "Oberfranken", ]) %>%
# st_collection_extract("LINE") %>%
# mapview(color = "red", lwd = 3, layer.name = "trails") +
# mapview(franconia, zcol = "district", burst = TRUE) +
# breweries
# mv2@map
```
One important thing to keep in mind is that **mapview** layers are added via the `+` operator (similar to **ggplot2**). This is a frequent [gotcha](https://en.wikipedia.org/wiki/Gotcha_(programming)) in piped workflows where the main binding operator is `%>%`.
For further information on **mapview** see the package's website at [r-spatial.github.io/mapview/](https://r-spatial.github.io/mapview/articles/).
There are other ways to create interactive maps with R not demonstrated here due to space constraints.
The **googleway** package, for example, provides an interactive mapping interface that is flexible and extensible with `google_map()`.
The command `google_map(key = key) %>% add_polygons(st_transform(nz, 4326))` plots an interactive map of New Zealand (it assumes a Google API key is saved as `key`).
Many other functions are provided by the package, providing an R interface to a wide range of mapping services including routing, traffic visualization and geocoding (see the [`googleway-vignette`](https://cran.r-project.org/web/packages/googleway/vignettes/googleway-vignette.html) for details).
Last but not least is **leaflet** which is the most mature and widely used interactive mapping package in R.
**leaflet** provides a relatively low level interface to the Leaflet JavaScript library and many of its arguments can be understood by reading the documentation of the original JavaScript library (see [leafletjs.com](http://leafletjs.com/reference-1.3.0.html)).
Leaflet maps are created with `leaflet()`, the result of which is a `leaflet` map object which can be piped to other **leaflet** functions.
This allows multiple map layers and control settings to be added interactively, as demonstrated in the code below which generates Figure \@ref(fig:leaflet) (see [rstudio.github.io/leaflet/](https://rstudio.github.io/leaflet/) for details).
```{r leaflet, fig.cap="The leaflet package in action, showing cycle hire points in London."}
pal = colorNumeric("RdYlBu", domain = cycle_hire$nbikes)
leaflet(data = cycle_hire) %>%
addProviderTiles(providers$Stamen.TonerLite) %>%
addCircles(col = ~pal(nbikes), opacity = 0.9) %>%
addPolygons(data = lnd, fill = FALSE) %>%
addLegend(pal = pal, values = ~nbikes) %>%
setView(lng = -0.1, 51.5, zoom = 12) %>%
addMiniMap()
```
## Mapping applications
The interactive web maps demonstrated in section \@ref(interactive-maps) can go far.
Careful selection of layers to display, base-maps and pop-ups can be used to communicate the main results of many projects involving geocomputation.
But the web mapping approach to interactivity has limitations:
- Although the map is interactive in terms of panning, zooming and clicking, the code is static, meaning the user interface is fixed.
- All map content is generally static in a web map, meaning that web maps cannot scale to handle large datasets easily.
- Additional layers of interactivity, such a graphs showing relationships between variables and 'dashboards' are difficult to create using the web-mapping approach.
Overcoming these limitations involves going beyond static web mapping and towards geospatial frameworks and map servers.
Products in this field include [GeoDjango](https://docs.djangoproject.com/en/2.0/ref/contrib/gis/) (which extends the Django web framework and is written in [Python](https://github.com/django/django)), [MapGuide](https://www.osgeo.org/projects/mapguide-open-source/) (a framework for developing web applications, largely written in [C++](https://trac.osgeo.org/mapguide/wiki/MapGuideArchitecture)) and [GeoServer](http://geoserver.org/) (a mature and powerful map server written in [Java](https://github.com/geoserver/geoserver)).
Each of these (particularly GeoServer) is scalable, enabling maps to be served to thousands of people daily --- assuming there is sufficient public interest in your maps!
The bad news is that such server-side solutions require much skilled developer time to set-up and maintain, often involving teams of people with roles such as a dedicated geospatial database administrator ([DBA](http://wiki.gis.com/wiki/index.php/Database_administrator)).
The good news is that web mapping applications can now be rapidly created using **shiny**, a package for converting R code into interactive web applications.
This is thanks to its support for interactive maps via functions such as `renderLeaflet()`, documented on the [Shiny integration](https://rstudio.github.io/leaflet/shiny.html) section of RStudio's **leaflet** website.
This section gives some context, teaches the basics of **shiny** from a web mapping perspective and culminates in a full-screen mapping application in less than 100 lines of code.
The way **shiny** works is well documented at [shiny.rstudio.com](https://shiny.rstudio.com/).
The two key elements of a **shiny** app reflect the duality common to most web application development: 'front end' (the bit the user sees) and 'back end' code.
In **shiny** apps these elements are typically created in objects named `ui` and `server` within an R script named `app.R`, that lives in an 'app folder'.
This allows web mapping applications to be represented in a single file, such as the [`coffeeApp/app.R`](https://github.com/Robinlovelace/geocompr/blob/master/coffeeApp/app.R) file in the book's GitHub repo.
```{block2 shiny, type = 'rmdnote'}
In **shiny** apps these are often split into `ui.R` (short for user interface) and `server.R` files, naming conventions used by [`shiny-server`](https://github.com/rstudio/shiny-server), a server-side Linux application for serving shiny apps on public-facing websites.
`shiny-server` also serves apps defined by a single `app.R` file in an 'app folder'.
```
Before considering large apps it is worth seeing a minimal example, named 'lifeApp', in action.^[
The word 'app' in this context refers to 'web application' and should not be confused with smartphone apps, the more common meaning of the word.
]
The code below defines and launches --- with the command `shinyApp()` --- a lifeApp, which provides an interactive slider allowing users to make countries appear with progressively lower levels of life expectancy (see Figure \@ref(fig:lifeApp)):
```{r, eval=FALSE}
ui = fluidPage(
sliderInput(inputId = "life", "Life expectancy", 49, 84, value = 80),
leafletOutput(outputId = "map")
)
server = function(input, output) {
output$map = renderLeaflet({
leaflet() %>% addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
addPolygons(data = world[world$lifeExp < input$life, ])})
}
shinyApp(ui, server)
```
```{r lifeApp, echo=FALSE, fig.cap="Screenshot showing minimal example of a web mapping application created with **shiny**."}
# knitr::include_app("http://35.233.37.196/shiny/")
knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39690606-8f9400c8-51d2-11e8-84d7-f4a66a477d2a.png")
```
The **user interface** (`ui`) of lifeApp is created by `fluidPage()`.
This contains input and output 'widgets' --- in this case, a `sliderInput()` (many other `*Input()` functions are available) and a `leafletOutput()`.
Elements added to a `fluidPage()` are arranged row-wise by default, explaining why the slider interface is placed directly above the map in Figure \@ref(fig:lifeApp) (`?column` explains how to add content column-wise).
The **server side** (`server`) is a function with `input` and `output` arguments.
`output` is a list of objects containing elements generated by `render*()` function --- `renderLeaflet()` which in this example generates `output$map`.
Inputs elements such as `input$life` referred to in the server must relate to elements that exist in the `ui` --- defined by `inputId = "life"` in the code above.
The function `shinyApp()` combines both the `ui` and `server` elements and serves the results interactively via a new R process.
When you move the slider in the map shown in Figure \@ref(fig:lifeApp), you are actually causing R code to re-run, although this is hidden from view in the user interface.
Building on this basic example and knowing where to find help (see `?shiny`), the best way forward now may be to stop reading and start programming!
The recommended next step is to open the previously mentioned [`coffeeApp/app.R`](https://github.com/Robinlovelace/geocompr/blob/master/coffeeApp/app.R) script in an IDE of choice, modify it and re-run it repeatedly.
The example contains some of the components of a web mapping application implemented in **shiny** and should 'shine' a light on how they behave (pun intended).
The `coffeeApp/app.R` script contains **shiny** functions that go beyond those demonstrated in the simple 'lifeApp' example.
These include `reactive()` and `observe()` (for creating outputs that respond to the user interface --- see `?reactive`) and `leafletProxy()` (for modifying a `leaflet` object that has already been created).
Such elements are critical to the creation of web mapping applications implemented in **shiny**.
```{block shinynote, type='rmdnote'}
There are a number of ways to run a **shiny** app.
For RStudio users the simplest way is probably to click on the 'Run App' button located in the top right of the source pane when an `app.R`, `ui.R` or `server.R` script is open.
**shiny** apps can also be initiated by using `runApp()` with the first argument being the folder containing the app code and data: `runApp("coffeeApp")` in this case (which assumes a folder named `coffeeApp` containing the `app.R` script is in your working directory).
You can also launch apps from a Unix command line with the command `Rscript -e 'shiny::runApp("coffeeApp")'`.
```
Experimenting with apps such as `coffeeApp` will build not only your knowledge of web mapping applications in R but your practical skills.
Changing the contents of `setView()`, for example, will change the starting bounding box that the user sees when the app is initiated.
Such experimentation should not be done at random, but with reference to relevant documentation, starting with `?shiny`, and motivated by a desire to solve problems such as those posed in the exercises.
**shiny** used in this way can make prototyping mapping applications faster and more accessible than ever before (deploying **shiny** apps is a separate topic beyond the scope of this chapter).
Even if your applications are eventually deployed using different technologies, **shiny** undoubtedly allows web mapping applications to be developed in relatively few lines of code (60 in the case of coffeeApp).
That does not stop shiny apps getting rather large.
The Propensity to Cycle Tool (PCT) hosted at [pct.bike](http://www.pct.bike/), for example, is a national mapping tool funded by the UK's Department for Transport.
The PCT is used by dozens of people each day and has multiple interactive elements based on more than 1000 lines of [code](https://github.com/npct/pct-shiny/blob/master/regions_www/m/server.R) [@lovelace_propensity_2017].
While such apps undoubtedly take time and effort to develop, **shiny** provides a framework for reproducible prototyping that should aid the development process.
One potential problem with the ease of developing prototypes with **shiny** is the temptation to start programming too early, before the purpose of the mapping application has been envisioned in detail.
For that reason, despite advocating **shiny**, we recommend starting with the longer established technology of a pen and paper as the first stage for interactive mapping projects.
This way your prototype web applications should be limited not by technical considerations but by your motivations and imagination.
```{r coffeeApp, echo=FALSE, fig.cap="coffeeApp, a simple web mapping application for exploring global coffee production in 2016 and 2017."}
knitr::include_app("https://bookdown.org/robinlovelace/coffeeapp/")
```
## Other mapping packages
**tmap** provides a powerful interface for creating a wide range of static maps (section \@ref(static-maps)) and also supports interactive maps (section \@ref(interactive-maps)).
But there are many other options for creating maps in R.
The aim of this section is to provide a taster of some of these and pointers for additional resources: map making is a surprisingly active area of R package development so there is more to learn than can be covered here.
The most mature option is to use `plot()` methods provided by core spatial packages **sf** and **raster**, covered in sections \@ref(basic-map) and \@ref(basic-map-raster) respectively.
What we have not mentioned in those sections was that plot methods for raster and vector objects can be combined, as illustrated in the subsequent code chunk which generates Figure \@ref(fig:nz-plot).
`plot()` has many options which can be explored by following links in the `?plot` help page and the **sf** vignette [`sf5`](https://cran.r-project.org/web/packages/sf/vignettes/sf5.html).
```{r nz-plot, fig.cap="Map of New Zealand created with plot(). The legend to the right refers to elevation (1000 m above sea level)."}
g = st_graticule(nz, lon = c(170, 175), lat = c(-45, -40, -35))
plot(nz_water, graticule = g, axes = TRUE, col = "blue")
raster::plot(nz_elev / 1000, add = TRUE)
plot(st_geometry(nz), add = TRUE)
```
Since version 2.2.2, the **tidyverse** plotting package **ggplot2** has supported `sf` objects with `geom_sf()`.
The syntax is similar to that used by **tmap**:
an initial `ggplot()` call is followed by one or more layers, that are added with `+ geom_*()`, where `*` represents a layer type such as `geom_sf()` (for sf objects) or `geom_points()` (for points).
**ggplot2** plots graticules by default.
The default settings for the graticules can be overridden using `scale_x_continuous()` and `scale_y_continuous()`.
Other notable features include the use of unquoted variable names encapsulated in `aes()` to indicate which aesthetics vary and switching data sources using the `data` argument, as demonstrated in the code chunk below which creates Figure \@ref(fig:nz-gg):
```{r nz-gg, fig.cap="Map of New Zealand created with ggplot2."}
library(ggplot2)
g1 = ggplot() + geom_sf(data = nz, aes(fill = Median_income)) +
geom_sf(data = nz_height) +
scale_x_continuous(breaks = c(170, 175))
g1
```
An advantage of **ggplot2** is that it has a strong user-community and many add-on packages.
Good additional resources can be found in the open source [ggplot2-book](https://github.com/hadley/ggplot2-book) and in the descriptions of the multitude of '**gg**packages' such as **ggrepel** and **tidygraph**.
Another benefit of maps based on **ggplot2** is that they can easily be given a level of interactivity when printed using the function `ggplotly()` from the **plotly** package.
Try `plotly::ggplotly(g1)` for example, and compare the result with other **plotly** mapping functions described at [blog.cpsievert.me](https://blog.cpsievert.me/2018/03/30/visualizing-geo-spatial-data-with-sf-and-plotly/).
```{r, eval=FALSE, echo=FALSE}
plotly::ggplotly(g1)
```
At the same time, **ggplot2** has a few drawbacks.
The `geom_sf()` function is not always able to create a desired legend to use from the spatial data ^[See https://github.com/tidyverse/ggplot2/issues/2037.].
Raster objects are also not natively supported in **ggplot2** and need to be converted into a data frame before plotting.
We have covered mapping with **sf**, **raster** and **ggplot2** packages first because these packages are highly flexible, allowing for the creation of a wide range of static maps.
<!-- Many other static mapping packages are more specific. -->
Before we cover mapping packages for plotting a specific type of map (in the next paragraph), it is worth considering alternatives to the packages already covered for general-purpose mapping (Table \@ref(tab:map-gpkg)).
```{r map-gpkg, echo=FALSE, message=FALSE}
gpkg_df = read_csv("extdata/generic_map_pkgs.csv")
map_gpkg_df = dplyr::select(gpkg_df, package, title)
map_gpkg_df$title[map_gpkg_df$package == "leaflet"] =
"Create Interactive Web Maps with Leaflet"
knitr::kable(map_gpkg_df, caption = "Selected general-purpose mapping packages, with associated metrics.")
```
Table \@ref(tab:map-gpkg) shows a range of mapping packages are available, and there are many others not listed in this table.
Of note is **cartography**, which generates a range of unusual maps including choropleth, 'proportional symbol' and 'flow' maps, each of which is documented in the vignette [`cartography`](https://cran.r-project.org/web/packages/cartography/vignettes/cartography.html).
Several R packages also allows for plotting specific map types (Table \@ref(tab:map-spkg)).
They prepare cartograms, create line maps, transform polygons into regular or hexagonal grids, and visualize complex data on grids representing geographic topologies.
```{r map-spkg, echo=FALSE, message=FALSE}
spkg_df = read_csv("extdata/specific_map_pkgs.csv")
map_spkg_df = dplyr::select(spkg_df, package, title)
knitr::kable(map_spkg_df, caption = "Selected specific-purpose mapping packages, with associated metrics.")
```
All of the aforementioned packages, however, have different approaches for data preparation and map creation.
In the next paragraph, we focus solely on the **cartogram** package.
Therefore, we suggest to read the [linemap](https://github.com/rCarto/linemap), [geogrid](https://github.com/jbaileyh/geogrid) and [geofacet](https://github.com/hafen/geofacet) documentations to learn more about them.
A cartogram is a map in which the geometry is proportionately distorted to represent a mapping variable.
Creation of this type of map is possible in R with **cartogram**, which allows for creating continuous and non-contigous area cartograms.
It is not a mapping package per se, but it allows for construction of distorted spatial objects that could be plotted using any generic mapping package.
The `cartogram()` function creates continuous area cartograms.
It accepts an `sf` object and name of the variable (column) as inputs.
Additionally, it is possible to modify the `intermax` argument - maximum number of iterations for the cartogram transformation.
For example, we could represent median income in New Zeleand's regions as a continuous cartogram (the right-hand panel of Figure \@ref(fig:cartomap1)) as follows:
```{r, fig.show='hide', message=FALSE}
library(cartogram)
nz_carto = cartogram(nz, "Median_income", itermax = 5)
tm_shape(nz_carto) + tm_polygons("Median_income")
```
```{r cartomap1, echo=FALSE, fig.cap="Comparison of regular map (left) and continuous area cartogram (right)."}
carto_map1 = tm_shape(nz) +
tm_polygons("Median_income", title = "Median income (NZD)", palette = "Greens")
carto_map2 = tm_shape(nz_carto) +
tm_polygons("Median_income", title = "Median income (NZD)", palette = "Greens")
tmap_arrange(carto_map1, carto_map2)
```
**cartogram** also offers creation of non-contiguous area cartograms with the `nc_cartogram()` function.
These cartograms are created by scaling down each region based on the provided weighting variable.
The code chunk below demonstrates creation of a non-contiguous area cartogram of US states' population (the bottom panel of Figure \@ref(fig:cartomap2)):
```{r, fig.show='hide', message=FALSE}
us_states2163 = st_transform(us_states, 2163)
us_states2163_nzcarto = nc_cartogram(us_states2163, "total_pop_15")
tm_shape(us_states2163_nzcarto) + tm_polygons("total_pop_15")
```
```{r cartomap2, echo=FALSE, fig.cap="Comparison of regular map (top) and non-continuous area cartogram (bottom)."}
carto_map3 = tm_shape(us_states2163) +
tm_polygons("total_pop_15", title = "Total population", palette = "BuPu")
carto_map4 = tm_shape(us_states2163_nzcarto) +
tm_polygons("total_pop_15", title = "Total population", palette = "BuPu")
tmap_arrange(carto_map3, carto_map4, ncol = 1)
```
## Exercises
For these exercises we will create a new object, `africa`, using the `world` and `worldbank_df` datasets from the **spData** package (see chapter \@ref(attr) to learn more about attribute operations) as follows:
```{r}
africa = world %>%
filter(continent == "Africa", !is.na(iso_a2)) %>%
left_join(worldbank_df, by = "iso_a2") %>%
select(name, subregion, gdpPercap, HDI, pop_growth) %>%
st_transform("+proj=aea +lat_1=20 +lat_2=-23 +lat_0=0 +lon_0=25")
```
We will also use `zion` and `nlcd` datasets from **spDataLarge**:
```{r, results='hide'}
zion = st_read((system.file("vector/zion.gpkg", package = "spDataLarge")))
```
1. Create a map showing the geographic distribution of the Human Development Index (`HDI`) across Africa with
-base graphics (hint: use `plot()`) and
-**tmap** (hint: use `tm_shape(africa) + ...`).
- Name two advantages of each approach
- What three other mapping packages could be used to show the same data?
- Name an advantage of each
- Bonus: create three more maps of Africa using the three packages mentioned previously
1. Extend the map of Africa created with **tmap** for the previous exercise so the legend has three bins: "High" (`HDI` above 0.7), "Medium" (`HDI` between 0.55 and 0.7) and "Low" (`HDI` below 0.55).
- Bonus: improve the map aesthetics, for example by changing the legend title, class labels and color palette.
```{r, eval=FALSE, echo=FALSE}
# >0.8 - Very high human development
# >0.7 - High human development
# >0.55 - Medium human development
# <0.55 - Low human development
sm1 = tm_shape(africa) +
tm_polygons(
col = "HDI",
title = "Human Development Index",
breaks = c(0, 0.55, 0.7, 0.8),
labels = c("Low", "Medium", "High"),
palette = "YlGn"
)
```
1. Represent `africa`'s subregions on the map.
Change the default color palette and legend title.
Next, combine this map and the map created in the previous exercise into a single plot.
```{r, eval=FALSE, echo=FALSE}
sm2 = tm_shape(africa) +
tm_polygons(col = "subregion",
title = "Subregion",
palette = "Set2")
tmap_arrange(sm2, sm1)
```
1. Create a land cover map of the Zion National Park.
- Change the default colors to match your perception of the land cover categories
- Add a scale bar and north arrow and change the position of both to improve the map's aesthetic appeal
- Bonus: Add an inset map of the Zion National Park's location in the context of the Utah state. (Hint: an object representing Utah can be subsetted from the `us_states` dataset.)
```{r, eval=FALSE, echo=FALSE}
lc_colors = c("#476ba0", "#aa0000", "#b2ada3", "#68aa63", "#a58c30",
"#c9c977", "#dbd83d", "#bad8ea")
sm3 = tm_shape(nlcd) + tm_raster(palette = lc_colors, title = "Land cover") +
tm_shape(zion) + tm_borders(lwd = 3) +
tm_scale_bar(size = 1, position = "left") +
tm_compass(type = "8star", position = c("RIGHT", "top")) +
tm_layout(legend.frame = TRUE, legend.position = c(0.6, "top"))
sm3
utah = filter(us_states, NAME == "Utah")
zion_bbox = st_as_sfc(st_bbox(nlcd))
sm3_plus = sm3 +
tm_layout(frame.lwd = 4)
im = tm_shape(utah) +
tm_polygons(lwd = 3, border.col = "black") +
tm_shape(zion_bbox) +
tm_polygons(col = "green", lwd = 1) +
tm_layout(title = "UTAH", title.size = 2, title.position = c("center", "center")) +
tm_layout(frame = FALSE, bg.color = NA)
library(grid)
print(sm3_plus, vp = grid::viewport(0.5, 0.5, width = 0.95, height = 0.95))
print(im, vp = grid::viewport(0.2, 0.4, width = 0.35, height = 0.35))
```
1. Create facet maps of countries in Eastern Africa:
- with one facet showing HDI and the other representing population growth (hint: using variables `HDI` and `pop_growth` respectively)
- with a 'small multiple' per country <!-- animated map, interactive map -->
```{r, eval=FALSE, echo=FALSE}
eastern_africa = filter(africa, subregion == "Eastern Africa")
tm_shape(eastern_africa) +
tm_polygons(col = c("HDI", "pop_growth")) +
qtm(africa, fill = NULL)
# https://github.com/mtennekes/tmap/issues/190#issuecomment-384184801
tm_shape(eastern_africa) +
tm_polygons("pop_growth", style = "jenks", palette = "BuPu") +
tm_facets(by = "name", showNA = FALSE)
```
1. Building on the previous facet map examples, create animated maps of East Africa:
- showing first the spatial distribution of HDI scores then population growth
- showing each country in order
```{r, eval=FALSE, echo=FALSE}
m = tm_shape(eastern_africa) +
tm_polygons(col = c("HDI", "pop_growth")) +
qtm(africa, fill = NULL) +
tm_facets(ncol = 1, nrow = 1)
tmap_animation(m, filename = "m.gif")
browseURL("m.gif")
m = tm_shape(eastern_africa) +
tm_polygons("pop_growth", style = "jenks", palette = "BuPu") +
tm_facets(by = "name", showNA = FALSE, ncol = 1, nrow = 1)
tmap_animation(m, filename = "m.gif")
browseURL("m.gif")
```
1. Create an interactive map of Africa:
- with **tmap**
- with **mapview**
- with **leaflet**
- bonus: for each approach add a legend (if not automatically provided) and a scale bar
1. Sketch on paper ideas for a web mapping app that could be used to make transport or land-use policies more evidence based: