Leaflet maps are interactive maps made using the Leaflet.js JavaScript library with the help of the leaflet R package. These maps are really effective for “end-of-project” uses like presentations or in digital publications like blogs and websites. This is their most common usage, but they also provide some unique approaches to “start-of-project” tasks like data exploration.
An excellent guide for Leaflet maps already exists, so instead of re-inventing the wheel this guide will step through the basics and then cover some ecology-related uses
Leaflet maps are built with a syntax similar to the grammar of graphics approach of ggplot2. You start with an empty plot, add a base-layer on top, maybe add some more layers on top, possibly add some markers, do any customising you want, and run the code.
One obvious difference is that leaflet uses the %>% pipe from magrittr instead of the + in ggplot2, but the usage is the same.
A basic Leaflet map looks like this:
leaflet()
This is equivalent to an empty ggplot() graph.
The first thing you will normally want to do is add a basemap with the addTiles() function. By default this is the OpenStreetMap. While we’re at it, lets also use the setView() function so we’re not looking at the whole globe by default.
leaflet() %>%
addTiles() %>%
setView(lng = 144.9612, lat = -37.7964, zoom = 13)
There are also a lot of freely-available third-party layers that can be accessed with the addProviderTiles() function. Examples of all of these can be found here. Some examples:
leaflet() %>%
addProviderTiles(providers$Esri.NatGeoWorldMap) %>%
setView(lng = 15.2663, lat = 4.4419, zoom = 5)
leaflet() %>%
addProviderTiles(providers$Esri.WorldImagery) %>%
setView(lng = -77.7812, lat = 21.5218, zoom = 4)
leaflet() %>%
addProviderTiles(providers$Stamen.Toner) %>%
setView(lng = -76.6122, lat = 39.2904, zoom = 10)
There are also third-party maps that cost nothing, but require registration to get an api key. Adding these goes back to the addTiles() function. In fact, anything you want to add that isn’t one of the available provider tiles is done with addTiles().
leaflet() %>%
addTiles("http://{s}.tile.thunderforest.com/spinal-map/{z}/{x}/{y}.png?apikey=276ae9b671ed49e4a663b0edd985b2d1") %>%
setView(lng = 4.30535, lat = 50.8549541, zoom = 5)
Once you have a basemap picked out, you’re going to want to add stuff on top.
One of the most common uses for leaflet maps that you will see online is chloropleth maps. These maps shade areas based on a measurement of some statistical value like per-capita income or housing prices. These are done by adding polygons to a map using the addPolygons() function. Here, lets colour the states of the contiguous USA with a random draw (because I don’t have anything better to hand that’s simple):
mapStates <- map("state", fill = TRUE, plot = FALSE)
leaflet(data = mapStates) %>%
addTiles() %>%
addPolygons(fillColor = rainbow(50, alpha = NULL),
stroke = FALSE)
Another thing that can be added to Leaflet maps are point markers using addMarkers(). These can be as simple as icons on a map:
# Show first 20 rows from the `quakes` dataset
leaflet(data = quakes[1:20,]) %>%
addTiles() %>%
addMarkers(~long, ~lat)
To having pop-up information:
leaflet(data = quakes[1:20,]) %>%
addTiles() %>%
addMarkers(~long, ~lat,
popup = ~as.character(mag),
label = ~as.character(mag))
Or custom icons with conditional colouring:
df.20 <- quakes[1:20,]
getColor <- function(quakes) {
sapply(quakes$mag, function(mag) {
if(mag <= 4) {
"green"
} else if(mag <= 5) {
"orange"
} else {
"red"
} })
}
icons <- awesomeIcons(
icon = 'fa-flag-checkered',
iconColor = 'black',
library = 'fa',
markerColor = getColor(df.20)
)
leaflet(df.20) %>% addTiles() %>%
addAwesomeMarkers(~long, ~lat, icon=icons, label=~as.character(mag))
If you have lots of points you can even cluster them:
leaflet(quakes) %>%
addTiles() %>%
addMarkers(clusterOptions = markerClusterOptions())
Lets set up some data exploration for a species distribution model. Here we add our covariate rasters using addTile(), add our presence points with addCircleMarkers(), and make use of groups to control what can be turned on/off simultaneously, can only be shown one at a time, and what is visible at the beginning.
## [1] "CarolinaWrenPO"
## [1] "Bioclim"
## Set up map + base layers
map <- leaflet(data) %>%
addTiles(group = "OpenStreetMap") %>%
addProviderTiles(provider = 'Esri.WorldImagery',
group = 'Esri.WorldImagery')
## Set up group layers
overlay_groups <- c() # layers to "print"
hide_groups <- c() # layers to not have ticked by default
## Point colour palette
fill_pal <- colorFactor(c("#FFFFFF", "#808080", "#080808"),
domain = c('presence', 'background', 'absence'),
ordered = TRUE)
border_pal <- colorFactor(c("#F0F0F0", "#808080", "#080808"),
domain = c('presence', 'background', 'absence'),
ordered = TRUE)
## Data points
group_name <- "Presence Points" # give layer name
overlay_groups <- c(overlay_groups, group_name) # add to overlay group
map <- addCircleMarkers(map = map, # add circles to map
lng = data$longitude, # longitude values
lat = data$latitude, # latitude values
color = grey(0.4), # border colour
fillColor = "#FFFFFF", # fill colour
weight = 1,
opacity = 1,
fillOpacity = 1,
radius = 3.5,
group = group_name,
popup = paste('<b>',
paste(toupper(substr("presence", 1, 1)),
substr("presence", 2, nchar("presence")), sep=""),
'</b>',
'<br>Longitude:', data$longitude,
'<br>Latitude:', data$latitude))
## Add points legend
map <- addLegend(map = map,
pal = fill_pal,
opacity = 0.8,
values = factor(c('presence', 'absence', 'background'),
levels = c('presence', 'absence', 'background'),
ordered = TRUE),
title = 'Data points')
## Add Covariate Rasters
for(i in seq(nlayers(ras))){
### layer names
layer_name_tmp <- sprintf('names(ras)[%s]', i) # get raster layer name
layer_name <- eval(parse(text = layer_name_tmp))
overlay_groups <- c(overlay_groups, layer_name) # add layer to overlay group
### get raster colour palette
ras_pal <- colorNumeric(viridis(10), # set colour palette by viridis
domain = seq(minValue(ras[[i]]),
maxValue(ras[[i]]),
length.out = 10),
na.color = 'transparent')
### reproject ras[[i]], suppressing warnings
suppressWarnings(ext <- raster::projectExtent(ras[[i]], crs = sp::CRS('+init=epsg:3857')))
suppressWarnings(tmp_ras <- raster::projectRaster(ras[[i]], ext))
### add layer
map <- addRasterImage(map = map, # add layer to map
x = tmp_ras,
colors = ras_pal,
project = FALSE,
opacity = 0.8,
group = layer_name)
### add all bar first raster layer to hide group so default display is one raster
if(i != 1){
hide_groups <- c(hide_groups, layer_name)
}
}
## Add toggle for the layers
map <- addLayersControl(map = map, # add toggle switch for layers
position = "topleft",
baseGroups = c('OpenStreetMap',
'Esri.WorldImagery'),
overlayGroups = overlay_groups,
options = layersControlOptions(collapsed = FALSE))
map <- hideGroup(map = map, # untick all raster layers except first
hide_groups)
map
Add some more examples. Maybe something with reserve borders and polygons? The Measurement add-on for field site spacing? Transect distances?