-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbasemaps.Rmd
770 lines (520 loc) · 38 KB
/
basemaps.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
# Maps with basemaps
## Introduction
In this chapter, you'll learn how to use Mapbox to create interactive and static maps with basemaps.
We'll use the following libraries and data.
```{r, message=FALSE, warning=FALSE}
library(tidyverse)
library(leaflet)
library(leaflet.mapboxgl)
library(sf)
africa <- read_sf("data/countries/africa.geojson")
```
leaflet.mapboxgl is on CRAN. You can install the package from GitHub by running the following line of code in the console.
```{r, eval=FALSE}
remotes::install_github("rstudio/leaflet.mapboxgl")
```
### Basemaps
You already know how to create static maps with `geom_sf()`.
```{r, message=FALSE}
africa %>%
ggplot() +
geom_sf()
```
In the above plot, `geom_sf()` draws the boundaries represented in the `geometry` column. This map is informative, but sometimes you'll want to plot your geospatial data on top of other features. For example, say we wanted a map of Africa with country names, major cities, rivers, streets, and lakes. We could incorporate this information by adding data sources to our ggplot2 plot, but this could quickly get time-consuming, and our data would start to take up a lot of space.
Luckily, there are easier ways of adding _basemaps_ to your maps. A basemap is just a map that lies underneath the data you want to visualize. Basemaps can include any number of features. For example, a basemap could just be a satellite image, or could include features like rivers, streets, and geographic boundary names.
Here's an example basemap from Mapbox, whose basemaps and tools we'll use in this chapter.
```{r echo=FALSE, out.width="45%"}
knitr::include_graphics("images/africa.png")
```
Mapbox's basemaps are interactive, and include more detail as you zoom in. For example, if we zoom in on Senegal, we'll be able to see specific cities and some bodies of water.
```{r echo=FALSE, out.width="45%"}
knitr::include_graphics("images/senegal.png")
```
If we zoom in further on Dakar, the basemap will include roads and airports.
```{r echo=FALSE, out.width="45%"}
knitr::include_graphics("images/dakar.png")
```
Basemaps add context to your geospatial data and provide common elements you can use across different data sets. In this chapter, we'll visualize the location of cholera deaths and water pumps on top of a basemap, allowing us to see the street locations of the deaths and water pumps.
In this chapter, we'll use [Mapbox](https://www.mapbox.com/) to create both interactive and static maps that include basemaps. We will begin by using Mapbox Studio to create interactive maps and then use the Mapbox Static API to turn these interactive maps into PNG images.
### Tiles
With interactive web maps, it is usually possible to both zoom and move around the map, which means you'll only ever see a portion of the map at a time. Web maps handle this functionality by using _tiles_. Each tile contains data for a specific square sub-region of a map, and there are different sets of tiles for different zoom levels.
There are two types of tiles: raster and vector. Raster tiles store their data in pixels, like small pictures. Because they store their data in pixels, you can't easily edit aspects of the tiles. For example, imagine you had a set of raster tiles with highways colored blue, and you wanted to change the color of all highways to yellow. You would have to change each pixel that corresponded to a highway.
Vector tiles store all data in vector format. This means that they can distinguish between different _layers_, such as roads, country borders, and lakes, and you can easily manipulate these elements before rendering. If you wanted to change the color of the highways in a vector tileset, all you'd have to do is change the way that highways layer is displayed, which is fast and easy with vector data. Vector tiles also make operations like tilting possible.
Below, we'll walk you through the creation of two interactive Mapbox maps. When you're done creating each map, you'll have created a custom vector tileset with various layers and a set of rules for styling these layers.
### Mapbox
Mapbox provides infrastructure for developers to create vector-tile maps and map applications. It has a library of vector-tile basemaps with data from [OpenStreetMap](https://www.openstreetmap.org/about), an open data effort that works like Wikipedia for maps.
Mapbox has its own JavaScript library, Mapbox GL JS, that it uses to write custom interactive web maps. Instead of explaining Mapbox GL JS, we'll use Mapbox Studio, which is a web interface to Mapbox GL JS. In Studio, you can create and edit Mapbox maps and share them via URL. You can then use Mapbox's Static API to create static versions of your maps.
To introduce you to Mapbox Studio, we'll walk you through the creation of two maps that visualize John Snow's data on the 1854 London cholera epidemic. The first map will use circles to represent the number of deaths at a given location. The second will be a heatmap.
There are many features of Mapbox Studio that we won't cover here. You can read more about Studio in the [Studio Manual](https://docs.mapbox.com/studio-manual/overview/).
## Data
### Background
In late August 1854, a severe cholera epidemic broke out in the Soho neighborhood of London. As recounted by Dr. John Snow
> The most terrible outbreak of cholera which ever occurred in this kingdom, is
probably that which took place in Broad Street, Golden Square, and adjoining
streets, a few weeks ago. Within two hundred and fifty yards of the spot
where Cambridge Street joins Broad Street, there were upwards of five hundred
fatal attacks of cholera in ten days. The mortality in this limited area
probably equals any that was ever caused in this country, even by the plague;
and it was much more sudden, as the greater number of cases terminated in a
few hours. The mortality would undoubtedly have been much greater had it not
been for the flight of the population.
[@snow-1855, p. 38]
In 1854, many mistakenly believed that cholera was spread through the air. However, because the deaths were concentrated in such a small area, Snow suspected that the outbreak was due to contaminated water from a community water pump at Broad and Cambridge streets.
> On proceeding to the spot, I found that nearly all of the deaths had taken
place within a short distance of the pump. There were only ten deaths in
houses situated decidedly nearer to another street pump. In five of these
cases the families of the deceased persons informed me that they always sent
to the pump in Broad Street, as they preferred the water to that of the pump
which was nearer. In three other cases, the deceased were children who went to
school near the pump in Broad Street. Two of them were known to drink the
water; and the parents of the third think it probable that it did so.
[@snow-1855, p. 39-40]
Snow reported his findings to the authorities responsible for the community water supply, who then removed the handle from the Broad Street pump. The epidemic soon ended.
Snow visualized his data by creating a map showing the location of local water pumps and deaths from the outbreak. This map helped overturn the prevailing belief that cholera was an airborne disease. As a result, London improved its water infrastructure and developed procedures to eliminate future cholera outbreaks.
### Download data
Robin Wilson compiled Snow's data and made it available on his [website](http://blog.rtwilson.com/john-snows-cholera-data-in-more-formats/). We've formatted his data and converted the two files to GeoJSON to make them easy to upload to Mapbox. These two GeoJSON files are available in the `data` folder. Let's read them in to see what they look like.
The original data can be downloaded [here](http://rtwilson.com/downloads/SnowGIS_KML.zip).
```{r}
deaths <- read_sf("data/cholera/cholera_deaths.geojson")
pumps <- read_sf("data/cholera/cholera_pumps.geojson")
```
```{r}
deaths
```
```{r}
pumps
```
Download `cholera_deaths.geojson` and `cholera_pumps.geojson`. Next, we'll upload these files to Mapbox.
### Upload data
If you don't already have a Mapbox account, create one by going to https://account.mapbox.com/auth/signup/.
Earlier, we introduced you to the idea of tiles. We want to create a custom tileset that includes standard features like roads and location labels, but that also includes the locations of the water pumps and deaths due to the cholera outbreak. In order to create this custom tileset, we first need to upload our data and convert it into its own tileset.
* Go to [Mapbox Studio](https://studio.mapbox.com/). If you're elsewhere in Mapbox, you can click the astronaut icon in the upper-right and then click __Studio__.
* Click on __Tilesets__ in the upper-right corner.
* Click the blue button labeled __New tileset__.
* Upload `cholera_deaths.geojson` and `cholera_pumps.geojson`. You'll have to upload one at a time.
* The two tilesets should now appear in the list. Each tileset will have a random string identifier after its name. Below we will use a "*" to represent this identifier. If your tilesets did not appear, try refreshing your browser.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/tilesets.png")
```
## Circle map
You now have everything you need to create a map in Mapbox Studio. We'll create the circle map first. At the end, you should have something that looks like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-final.png")
```
You can view our completed map [here](https://api.mapbox.com/styles/v1/stanford-datalab/cjsqj3qvi3zrf1fld8qvywggs.html?fresh=true&title=true&access_token=pk.eyJ1Ijoic3RhbmZvcmQtZGF0YWxhYiIsImEiOiJjajh1aW84OXYxMWh5MndwMG9jYnhpc3ZiIn0.pSqbBonZJw667NNhHNX1Kg#16.1/51.513564/-0.135852/0).
### Create a style
In Mapbox, a _style_ is a specification for how your map will be drawn. Behind the scenes, this specification is written in JavaScript, but you can edit it from Studio. You can think of a Mapbox style like ggplot2 code for a visualization: it includes information about which data to use, specifies how to map that data to visual elements, and controls features of the visualization not related to the data (e.g., background colors, basemap elements).
We'll build our custom style by building on one of Mapbox's existing styles.
* Click on the __Styles__ tab in the upper-right corner to take you back to the Styles page.
* Click __New style__. Take a look at the different template styles that Mapbox offers. Some are better for displaying data than others. We want the cholera deaths and water pumps to be the focus of our map, so we'll use a basemap with a subtle, light background. The Light style is the best option.
* Select the Light style.
* You should now be in Mapbox Studio and should see a basemap centered on Boston.
* Name your map something informative. In the upper-left corner, you'll see the default name of your map. If you used the Light style, your map will be named _Light_. Click on this name and change it to something like _Cholera circles_.
Your screen should now look like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-name.png")
```
### Zoom in on London
The map is currently centered on Boston because the Light style's default location is Boston. Move the map to London either by manually navigating or by searching for _London_ in the search bar in the top-right corner.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-london.png")
```
### Add the deaths layer
The individual unit of a Mapbox map is a layer. You can see a list of all the layers included in the Light basemap on the left side of Studio.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-layers-list.png")
```
You can think of layers like ggplot2 geoms. Just like a geom, each layer maps data to a visual element (e.g., dots, lines, text), and you can control exactly how the data is mapped to that visual element.
You can edit an existing layer by clicking on its name. A layer editor will open. To hide the layer editor, click on the name of the layer again.
We'll need three different layers to represent the deaths and pumps data. One layer will encode the deaths data as circles whose areas represent the number of deaths in that location. Another layer will represent the pump locations as circles. A final layer will label the pumps. First, we'll add the deaths layer.
* Click the gray __Add layer__ button in the upper-left corner. Your map will immediately look different. Don't worry, this isn't because you've accidentally changed some feature. Mapbox is just highlighting all the layers without styling them.
* You are now in the layer editor for a new layer. Notice that _New layer_ now appears in the layer list on the left, and is highlighted to indicate that you're currently editing this layer.
* Notice that under _Data sources_ in the layer editor, there are _Active sources_ and _Unused sources_. Each of these sources is a tileset. Active sources are tilesets that the map currently uses to create different layers. Unused sources are available tilesets that the map does not currently use.
* If you click on the name of a tileset, you'll see all the available layers in that tileset. The tilesets included with Mapbox, like Mapbox Streets v8, each contain many different layers. Our custom tilesets (_cholera_deaths-* _ and _cholera_pumps-* _) each contain only one.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-add-layer.png")
```
* Click on your _cholera_deaths-* _ tileset from the _Unused sources_ list. Then, click on the layer titled _cholera_deaths-* _ that appears underneath.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-add-layer-deaths.png")
```
* We've now created a layer. By default, this layer will be named _cholera_deaths-* _.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-deaths-layer-added.png")
```
* Notice that you're currently in the __Select data__ tab of the layer editor. The layer editor has two tabs: __Select data__ and __Style__. In the __Select data__ tab, you can choose your data and choose the layer type. You can think of the layer type as a geom. By default, the type for our current layer is _Circle_, which happens to be what we want. For some of the layers we'll make later on, we'll need to change the type.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-select-data-tab.png")
```
* If you click on the __Style__ tab, the map's appearance will go back to normal. In the __Style__ tab, you can control the appearance of your layer. If you think of the __Select data__ tab as where you choose the geom, the __Style__ tab is where you specify the aesthetics. In the next section, you'll use the __Style__ tab to map number of deaths to circle area.
* You should now see black dots on your map that represents the locations of deaths.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-deaths-layer-style.png")
```
### Scale the dots
Right now, all the dots are the same size. We want to scale them so that the area of the circle encodes the number of deaths.
* We said that the __Style__ tab controls the aesthetic mappings. For circles, one aesthetic mapping you can control are the radii of the circles.
* To map the number of deaths to circle radii, we could select __Style across data range__ from the __Radius__ tab. However, this will linearly scale the radii of the circles by the number of deaths at that location. We actually want to scale the areas of the circles, not the radii, so we'll have to use a custom formula.
* Click __Use a formula__.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-deaths-layer-radius.png")
```
* The formula we want to use takes the form `x * sqrt(deaths)`, where `x` is a constant.
* Start adding the formula to the formula box. Use `sqrt()` for square root. Click on __Insert a data field__ and then on __deaths__ to use the `deaths` variable.
* Here's what your map should look like without `x`:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-deaths-layer-radius-formula.png")
```
* Play around with different values of the constant `x` until you're happy with the appearance of your dots. Your map should now look something like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-deaths-radius-scaled.png")
```
In this screenshot, the editing sidebar for the deaths layer is hidden. You can hide the editing sidebar by clicking on the name of the layer you're editing.
### History
If you need to, you can undo actions by clicking on __History__ in the menu on the right. Clicking the undo arrow will undo your last action. You can also click on the name of an action to revert your map further.
```{r, echo=FALSE}
# sara
knitr::include_graphics("images/screenshots/circles-history.png")
```
### Style the dots
* Make sure you're on the __Style__ tab of the _cholera_deaths-* _ layer editor.
* Use the __Color__ tab to change the color of your circles to red.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-deaths-layer-color.png")
```
* At some zoom levels, the dots are over-plotted. Change the opacity of the dots in the __Opacity__ tab. Then, add a border to the circles by adjusting the stroke width in the __Stroke width__ tab.
* When you're done, your map should look something like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-deaths-layer-styled.png")
```
### Add the pumps layer
Now, we'll add a layer to represent the pump locations.
* Again, click on __Add layer__ to create a new layer.
* Click on your _cholera_pumps-* _ tileset from the unused sources list. Then, click on the _cholera_pumps-* _ layer that appears underneath.
* Go to the __Style__ tab of the layer editor.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-pumps-layer.png")
```
* If you want to change the size of the circles, go to __Radius__ and change the value of __Circle radius__.
* You should now have a map that looks like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-pumps-layer-radius.png")
```
### Add the pumps labels layer
It would be helpful to label the dots that represents pumps. In ggplot2, you could use `geom_point()` and `geom_text()` to create a plot with labeled points. Similarly, in Mapbox you have to add two layers to create labeled points: one for the points and one for the labels. We already added the point layer, so now we just have to add the label layer.
* Add a new layer by clicking __Add layer__. Your _cholera_pumps-* _ tileset is now an active source. Click on your _cholera_pumps-* _ tileset then on the _cholera_pumps-* _ layer that appears underneath.
* By default, the layer will be named _cholera_pumps-* (1)_. Change the name of the layer to _cholera-pump-labels-* _ by clicking on the layer name towards the top of the layer editor.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-pump-labels-name.png")
```
* By default, the layer type for the _cholera-pump-labels-* _ layer is _Circle_, but we want text, not circles, for this layer. Change the layer type in the __Type__ tab to _Symbol_.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-pump-labels-type.png")
```
* Now, we need to set the labeling text. Move back to the __Style__ tab, then type _Pump_ in the text box. This will set every label to _Pump_.
* The labels are directly on top of the dots. To offset the labels, first click on __Position__. This tab contains several variables related to the positioning of the text.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-pump-labels-position.png")
```
* Use the __Text offset__ tab to adjust the offsets. You should end up with something that looks like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-final.png")
```
Your map is complete!
### Publish your map
Now, you just need to make your map visible to others by publishing.
* When you publish your map, you'll get a URL that points to your published map. Your published map will have a starting location and zoom level. Navigate to __Default map position__. If the __Lock__ option is set to unlocked (the dot is towards the left and the oval is white), it means that your published map will default to the location and zoom level of your map when you click __Publish__. You want the __Lock__ option to be unlocked. Then, move and zoom your map until you're happy.
```{r echo = FALSE}
knitr::include_graphics("images/screenshots/static-map-position.png")
```
* Publish your map by clicking the blue __Publish__ button in the upper-right corner.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/circles-publish.png")
```
* Click the __Share...__ button next to __Publish__ and copy the Share URL. This URL points to the shareable version of your map. Paste the URL into a new browser tab. Your map should open at the default location and zoom level that you set earlier. The Share URLs take the following form:
`https://api.mapbox.com/styles/v1/{username}/{style_id}.html?fresh=true&title=true&access_token={access_token}#{zoom}/{latitude}/{longitude}`
If your map does not default to the location that you set earlier, you may have to manually change your Share URL. To do so, replace your Share URLs zoom, latitude, and longitude with your desired values. Remember that you can find these values under _Map position_ in Studio.
* Once you have the correct Share URL, copy and store it somewhere. You'll need this URL for the static API later on.
## Heatmap
Now, we'll create a heatmap. Your final result should look something like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-final.png")
```
You can view the map [here](https://api.mapbox.com/styles/v1/stanford-datalab/cjsuo2k338t411fsenxjvz4aw.html?fresh=true&title=true&access_token=pk.eyJ1Ijoic3RhbmZvcmQtZGF0YWxhYiIsImEiOiJjajh1aW84OXYxMWh5MndwMG9jYnhpc3ZiIn0.pSqbBonZJw667NNhHNX1Kg#16.1/51.513564/-0.135852/0).
### Create a style
* Go back to the [Studio homepage](https://studio.mapbox.com/).
* Create a new map with a Light style basemap by clicking on __New style__ and choosing the Light style.
* Once the map has opened, change the name to something more informative, like _Cholera heatmap_.
* Navigate to London.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-name-london.png")
```
### Add the pumps layers
Mapbox can plot layers on top of each other. Each time you add a new layer, Mapbox places it on top of all other existing layers. This means that you'll typically want to add layers from the bottom up. Our heatmap has a heatmap layer underneath the two pumps layers, so ordinarily you would add the heatmap layer first, and then add the pumps layers. We'll add the pumps layers first just so we can demonstrate how to manually change the layer order.
We'll add the two pumps layers exactly as we did for the circles map.
* First, add a layer for the pump dots. Click __Add layer__, then select the _cholera_pumps-* _ layer from underneath your _cholera_pumps-* _ tileset.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-add-pumps-layer.png")
```
* If you want, change the radius of the pumps circles. You'll end up with a map that looks like this:
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-pumps-radius.png")
```
* Next, add the pump labels layer. Select the _cholera_pumps-* _ layer from your _cholera_pumps-* _ tileset that will now appear in the active sources list.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-add-pumps-labels.png")
```
* Change the name of the layer to _cholera-pump-labels-* _ and change the type to _Symbol_.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-pumps-labels-type.png")
```
* Change to the __Style__ tab to set the text and adjust the offsets. Recall that the text offset setting is under the __Position__ tab.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-pumps-labels.png")
```
### Add the deaths heatmap layer
* Add the deaths layer by selecting the _cholera_deaths-* _ layer from underneath your _cholera_deaths-* _ tileset.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-add-deaths-layer.png")
```
* Change the layer type to _Heatmap_.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/heatmap-type.png")
```
* Change to the __Style__ tab.
* Right now, the `deaths` variable is not mapped to the intensity of the heatmap. If you zoom in on your heatmap, you'll notice that each heatmap dot has the same intensity, regardless of the number of deaths at that location. To map `deaths` to intensity, we'll use the __Weight__ parameter. We'll adjust the heatmap weights similarly to how we adjusted the radii of the circles for the previous map.
* Click on __Weight__, then __Style across data range__. Select _deaths_ and click __Done__. This maps `deaths` to the heatmap weight, but the default is a uniform relationship, so the weight doesn't actually change across different values of `deaths`.
```{r echo=FALSE}
# sara
knitr::include_graphics("images/screenshots/heatmap-weight.png")
```
* Change the way the weights are scaled by adjusting the minimum and/or maximum weight.
```{r echo=FALSE}
# sara
knitr::include_graphics("images/screenshots/heatmap-change-weights.png")
```
* Your heatmap should now look something like this:
```{r echo=FALSE}
# sara
knitr::include_graphics("images/screenshots/heatmap-weights-done.png")
```
* Now, adjust the heatmap radius (the radii of the individual circles) in the __Radius__ tab. Experiment with different values.
* Your map should now look like this:
```{r echo=FALSE}
# sara
knitr::include_graphics("images/screenshots/heatmap-radius.png")
```
### Change the layer order
The heatmap currently rests on top of the pump layers, obscuring some of the pumps.
* Change the order of the layers by dragging and dropping the layer names in the list on the left. You want the _cholera_deaths-* _ to be below both of the pumps layers.
```{r, echo=FALSE}
# update
knitr::include_graphics("images/screenshots/heatmap-layers-list.png")
```
* Your map should end up looking like this:
```{r, echo=FALSE}
#
knitr::include_graphics("images/screenshots/heatmap-final.png")
```
Your heatmap map is complete!
### Publish your map
* To finalize your map, again center your map on your data, then make sure the __Lock__ option is unlocked.
* Click the blue __Publish__ button in the top-right to publish your map.
* Click on __Share...__ and then copy the Share URL. Again, paste this URL into a new browser tab to check that your map defaults to the correct location. If your map does not default to the location you set earlier, you may have to manually change the Share URL by following the steps we explained in 1.3.9.
* Copy the Share URL and store it somewhere. You'll need it for the next section.
## Static maps
You can share your interactive Mapbox map via its Share URL, but you'll sometimes want a static version to embed in documents that only support static images. In this section, you'll learn how to create static versions of your two maps using the Mapbox Static API.
### Mapbox Static API
The Static API is an interface to Mapbox's servers. You can use this interface to request data from its servers to download to your computer. In order to download your specific map, you need to create an API call. This call tells the API exactly what data you want to download. You'll need to tell the API which map to download and which location and zoom level to use in order to create a static version of an interactive map.
You create the API call by specifying parameters in a URL. Here's what the basic Mapbox Static API call looks like:
`https://api.mapbox.com/styles/v1/{username}/{style_id}/static/{longitude},{latitude},{zoom}/{width}x{height}?access_token={access_token}`
This call will create a PNG version of a map.
We'll guide you through the process of gathering each piece information. We'll store each piece of information in a variable so that we can just use `str_glue()` at the end to create the call.
### Username and access token
First, store your Mapbox username and access token in variables.
```{r}
username <- "stanford-datalab"
access_token <- Sys.getenv("MAPBOX_ACCESS_TOKEN")
```
### Style ID
Decide whether you want to first create a static version of your circles map or your heatmap. Then, find that's map's style ID. You can find the style ID in the Share URL you copied earlier.
If you don't already have your map's Share URL, you can retrieve it from the [Styles page](https://studio.mapbox.com). To do so:
* Click on __Share & use__ next to the name of your map. Then, copy the Share URL.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/styles-page-share-url.png")
```
The style ID for your map is the string that comes after your username and before the _.html_.
[https://api.mapbox.com/styles/v1/stanford-datalab/<mark>cjsqj3qvi3zrf1fld8qvywggs</mark>.html?fresh=true&title=true&access_token=pk.eyJ1Ijoic3RhbmZvcmQtZGF0YWxhYiIsImEiOiJjajh1aW84OXYxMWh5MndwMG9jYnhpc3ZiIn0.pSqbBonZJw667NNhHNX1Kg#16.1/51.513564/-0.135852/0](https://api.mapbox.com/styles/v1/stanford-datalab/cjsqj3qvi3zrf1fld8qvywggs.html?fresh=true&title=true&access_token=pk.eyJ1Ijoic3RhbmZvcmQtZGF0YWxhYiIsImEiOiJjajh1aW84OXYxMWh5MndwMG9jYnhpc3ZiIn0.pSqbBonZJw667NNhHNX1Kg#16.1/51.513564/-0.135852/0)
Store your map's style ID in a variable.
```{r}
style_id <- "cjsqj3qvi3zrf1fld8qvywggs"
```
### Longitude and latitude
Next, we need to figure which longitude and latitude to use. With the static API, the longitude and latitude indicate the center of the map.
We'll use the sf package and the `pumps` and `deaths` data to figure out the exact center of our data.
Notice that the southernmost pump is far away from the rest of the data.
```{r}
ggplot() +
geom_sf(data = deaths, color = "red") +
geom_sf(data = pumps)
```
We'll exclude this pump and focus on where most of the data lies.
First, we need to find the latitude of each pump. Then, we can use `slice_max()` to filter out the southernmost pump.
```{r}
pumps %>%
rowwise() %>%
mutate(latitude = st_coordinates(geometry)[[2]]) %>%
ungroup() %>%
slice_max(latitude, n = nrow(.) - 1) -> z3
```
Now, we'll find the bounding box of all our data. Once we have the bounding box, we can find the center of the box.
```{r, message=FALSE}
bounding_box <-
pumps %>%
rowwise() %>%
mutate(latitude = st_coordinates(geometry)[[2]]) %>%
ungroup() %>%
slice_max(latitude, n = nrow(.) - 1) %>%
st_geometry() %>%
c(deaths %>% st_geometry()) %>%
st_bbox() %>%
st_as_sfc()
```
`st_bbox()` calculates the bounding box.
`st_centroid()` finds the centroid of the box.
```{r, warning=FALSE}
centroid <-
bounding_box %>%
st_centroid()
```
We can visualize all of these geospatial operations using `geom_sf()`.
```{r}
ggplot() +
geom_sf(data = bounding_box) +
geom_sf(data = deaths) +
geom_sf(data = pumps) +
geom_sf(data = centroid, color = "red", shape = 3, size = 3)
```
Finally, we'll store the longitude and latitude of the centroid in variables so that we can use them later on in the static API call.
```{r}
longitude <- st_coordinates(centroid)[[1]]
latitude <- st_coordinates(centroid)[[2]]
```
```{r}
longitude
latitude
```
### Zoom and PNG size
Next, we need to figure out which zoom level and PNG width and height to use.
You can see the current longitude, latitude, and zoom level of your custom style in Studio by clicking on __Map position__.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/static-map-position.png")
```
Move your map to the center point you found in the previous section. Then, determine which zoom level best shows the data. Remember that we're excluding the southernmost pump. Copy this zoom level.
Now, you need to figure out how to set the height and width of your PNG so that all your data is shown at your chosen zoom level. Unfortunately, you can't see what different PNGs will look like in Studio.
The [Static API playground](https://docs.mapbox.com/help/interactive-tools/static-api-playground/) shows the results of different static API calls.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/static-api-playground.png")
```
Set the longitude and latitude to the values you found in the previous section. Set the zoom level to the value you just copied. Now, play around with different widths and heights until the PNG seems like it would include all the data (excluding the southernmost pump) and is a reasonable size.
The Static API playground doesn't let you load custom styles, so you won't be able to see the pumps and deaths data. This means you might have to move back-and-forth between the playground and Studio. In Studio, look for the features (roads, buildings, etc.) that bound the data. Then, in the playground, make sure you PNG includes those features.
You don't have to perfect your zoom level, height, and width at this stage. We'll show you how to further tweak them later on. Just try to roughly include all your data at a reasonable zoom level.
Store your chosen longitude, latitude, zoom, width, and height values in variables.
```{r, eval=FALSE}
zoom <- [your zoom]
width <- [your width]
height <- [your height]
```
```{r, echo=FALSE}
zoom <- 16.1
width <- 1200
height <- 800
```
### Create your API call
Now, you just have to add in all this information to the base call. `str_glue()` makes this easy.
```{r}
api_call <-
str_glue(
"https://api.mapbox.com/styles/v1/{username}/{style_id}/static/",
"{longitude},{latitude},{zoom}/{width}x{height}?access_token={access_token}"
)
api_call
```
If you copy and paste this link into a browser, you should see an image of your map. If you only see the basemap, and not whatever features you added, your map might be set to _Private_. If this happens, you'll need to change your map to _Public_. The easiest way to do this is to go to your [Styles homepage](https://studio.mapbox.com). Then, click on the three dots next to your map and select _Make public_.
### Download your PNG
Now, we need to download the image. Use `download.file()` to download your image as a PNG.
```{r, message=FALSE}
file_circles_map <- "images/cholera_circles_map.png"
download.file(url = api_call, destfile = file_circles_map)
```
The result should be a downloaded PNG, which you can view from RStudio or a browser. If you were able to see your PNG by pasting `api_call` into a browser, but your PNG did not download, you may need to set a different download method using the `method` argument of `download.file()`. You can see all available methods in the documentation for `download.file()`.
### Display your image
To include your image in an R Markdown document, use `knitr::include_graphics`.
```{r, out.width="100%"}
knitr::include_graphics(file_circles_map)
```
Now you can see the exact image that your API parameters produce. If you're not happy with the image, all you have to do is adjust your parameters slightly, and then rerun your code. It's difficult to adjust longitude and latitude values by hand, so we recommend adjusting the width, height, and zoom first. If you think you do need to change the longitude and latitude, you may have to go back to Studio or the static API.
Here's a function that includes all the code to make this process a bit easier. The function creates the API call, downloads the image, and displays the image using knitr. All you need to do is pass in the parameters.
```{r}
mapbox_png <- function(
username,
style_id,
longitude,
latitude,
zoom,
width,
height,
access_token,
path
) {
str_glue(
"https://api.mapbox.com/styles/v1/{username}/{style_id}/static/",
"{longitude},{latitude},{zoom}/{width}x{height}?access_token={access_token}"
) %>%
download.file(url = ., destfile = path)
knitr::include_graphics(path = path)
}
```
## leaflet.mapboxgl
The package [leaflet.mapboxgl](https://github.com/rstudio/leaflet.mapboxgl) extends the leaflet package to allow you to add Mapbox vector tiles to leaflet maps. leaflet.mapboxgl has many advantages. First, we'll use `leaflet.mapboxgl::addMapboxGL()` to display our Mapbox maps in R. Note that leaflet.mapboxgl requires RStudio version 1.2 or later to display your plots.
* You'll need a Mapbox access token and style URL. You can retrieve your public access token by going to your [Mapbox account page](https://account.mapbox.com/).
* Your style URL is different than the share URL you found earlier. To find your style URL, first go to the [Styles page](https://studio.mapbox.com/). Then, click on the three dots next to your style and copy the style URL.
```{r, echo=FALSE}
knitr::include_graphics("images/screenshots/styles-page-style-url.png")
```
* Store your style URL and access token in variables.
```{r}
style_url <- "mapbox://styles/stanford-datalab/cjsqj3qvi3zrf1fld8qvywggs"
access_token <- Sys.getenv("MAPBOX_ACCESS_TOKEN")
```
Now, we can display our map. `addMapboxGL()` adds Mapbox vector tiles to a leaflet map. You can adjust the height and width of a leaflet plot with the `height` and `width` arguments.
```{r}
leaflet(width = 950, height = 800) %>%
addMapboxGL(
accessToken = access_token,
style = style_url
)
```
Another advantage of leaflet.mapboxgl is that you can add any leaflet function to your map. We'll use this functionality to add tooltips, which you can't do in Mapbox Studio.
The function `addCircleMarkers()` adds circles to a leaflet plot. You can use the `label` argument to set the text of a tooltip.
Note that you have to set `data = deaths` inside `addCircleMarkers()` in order to reference the data.
```{r}
leaflet(width = 950, height = 800) %>%
addMapboxGL(
accessToken = access_token,
style = style_url
) %>%
addCircleMarkers(
radius = ~ 5 * sqrt(deaths),
opacity = 0,
fillOpacity = 0,
label =
case_when(
deaths$deaths == 1 ~ "1 death",
deaths$deaths > 1 ~ str_c(deaths$deaths, " deaths"),
TRUE ~ NA_character_
),
data = deaths
)
```
We already have circles encoding the number of deaths, so we've set the `opacity` and `fillOpacity` of the circle markers to 0. This makes them invisible so you can still see the layers we added in Mapbox Studio.