22  openairmaps primer

An introduction to openairmaps and the leaflet package

Author

Jack Davison

Abstract
The openairmaps package has been designed to create interactive HTML air quality maps. These are useful to understand the geospatial context of openair-type analysis, and to present data in an engaging way in dashboards and websites. This page gives a broad overview of openairmaps as well as leaflet, the package on which it is built.

22.1 Installation

openairmaps is on CRAN, so can be simply downloaded using install.packages().

install.packages("openairmaps")

If you would like to access new features and bug fixes before they’re pushed to CRAN, the development version of openairmaps can be downloaded directly from GitHub.

# install.packages("pak")
pak::pak("davidcarslaw/openairmaps")

If you would like to use openairmaps in your analysis, don’t forget to load it using the library() function.

This chapter was produced using R version 4.3.1 and openairmaps version 0.8.1.

22.2 Background

As the R ecosystem developed, rmarkdown and, more recently, Quarto) emerged as capable tools for combining data analysis with document preparation. While these approaches can render typical .docx and .pdf outputs, one of their most common output formats is the HTML document. This format has many strengths, but a key one is interactivity; HTML widgets allow documents to be more informative and engaging. Numerous packages have been developed to easily develop these interactive widgets, such as plotly for plots, DT for tables, and leaflet for maps. The openairmaps package concerns itself with making leaflet maps.

Air quality data analysis — particularly as it pertains to long term monitoring data — naturally lends itself to being visualised spatially on a map. Monitoring networks are geographically distributed, and ignoring their geographical context may lead to incomplete insights at best and incorrect conclusions at worst! Furthermore, many air quality analysis tools are directional, asking questions of the data along the lines of “do elevated concentrations come from the North, South, East or West?” The natural question that follows is “well, what actually is it to the North/South/East/West that could be causing elevated concentrations?” — a map can help answer that question straightforwardly.

22.3 leaflet Primer

While openairmaps can be used without a thorough understanding of the leaflet package, some knowledge can definitely help! Figure 22.1 shows an example leaflet map. Try the following:

  • Click and drag to move the map around — you can go anywhere in the world!

  • Use your scroll wheel or the +/- options (top left) to zoom in and out.

  • Hover over the marker to see its label.

  • Click on the marker to see a popup.

  • Use the “layer control” menu (top right) to show and hide the marker, and swap between base maps.

library(leaflet)

leaflet() %>%
  addTiles(group = "OSM") %>%
  addProviderTiles("CartoDB.Positron", group = "CartoDB.Positron") %>%
  addMarkers(
    lat = 51.5034,
    lng = -0.1276,
    label = "10 Downing Street",
    popup = "This is where the UK Prime Minister lives!",
    group = "Marker"
  ) %>%
  addLayersControl(
    baseGroups = c("CartoDB.Positron", "OSM"), 
    overlayGroups = "Marker"
  )
Figure 22.1: An example of a leaflet map.

The anatomy of a leaflet map may look a little unusual, so to unpack:

  • Every leaflet map starts with the leaflet() function.

  • Different base maps can be added using addTiles() and addProviderTiles(). There are many of these base maps — some are minimalist and some are busy, some are colourful and some are black-and-white, some are plain and some are highly stylised!

  • Markers can be added using addMarkers() and other similar functions. These markers can have different features such as hover labels and popups.

  • The addLayersControl() function creates a menu which allows users to show/hide or swap between certain features of the map.

Don’t worry if you don’t fully understand all of this — the “all-in-one” functions in openairmaps deal with much of this for you! The only leaflet awareness you’ll need to use these functions is knowledge of the available base map providers, which can be accessed using leaflet::providers. These are shown in the collapsible below (the list is very long!).

leaflet::providers
$OpenStreetMap
[1] "OpenStreetMap"

$OpenStreetMap.Mapnik
[1] "OpenStreetMap.Mapnik"

$OpenStreetMap.DE
[1] "OpenStreetMap.DE"

$OpenStreetMap.CH
[1] "OpenStreetMap.CH"

$OpenStreetMap.France
[1] "OpenStreetMap.France"

$OpenStreetMap.HOT
[1] "OpenStreetMap.HOT"

$OpenStreetMap.BZH
[1] "OpenStreetMap.BZH"

$MapTilesAPI
[1] "MapTilesAPI"

$MapTilesAPI.OSMEnglish
[1] "MapTilesAPI.OSMEnglish"

$MapTilesAPI.OSMFrancais
[1] "MapTilesAPI.OSMFrancais"

$MapTilesAPI.OSMEspagnol
[1] "MapTilesAPI.OSMEspagnol"

$OpenSeaMap
[1] "OpenSeaMap"

$OPNVKarte
[1] "OPNVKarte"

$OpenTopoMap
[1] "OpenTopoMap"

$OpenRailwayMap
[1] "OpenRailwayMap"

$OpenFireMap
[1] "OpenFireMap"

$SafeCast
[1] "SafeCast"

$Stadia
[1] "Stadia"

$Stadia.AlidadeSmooth
[1] "Stadia.AlidadeSmooth"

$Stadia.AlidadeSmoothDark
[1] "Stadia.AlidadeSmoothDark"

$Stadia.OSMBright
[1] "Stadia.OSMBright"

$Stadia.Outdoors
[1] "Stadia.Outdoors"

$Stadia.StamenToner
[1] "Stadia.StamenToner"

$Stadia.StamenTonerBackground
[1] "Stadia.StamenTonerBackground"

$Stadia.StamenTonerLines
[1] "Stadia.StamenTonerLines"

$Stadia.StamenTonerLabels
[1] "Stadia.StamenTonerLabels"

$Stadia.StamenTonerLite
[1] "Stadia.StamenTonerLite"

$Stadia.StamenWatercolor
[1] "Stadia.StamenWatercolor"

$Stadia.StamenTerrain
[1] "Stadia.StamenTerrain"

$Stadia.StamenTerrainBackground
[1] "Stadia.StamenTerrainBackground"

$Stadia.StamenTerrainLabels
[1] "Stadia.StamenTerrainLabels"

$Stadia.StamenTerrainLines
[1] "Stadia.StamenTerrainLines"

$Thunderforest
[1] "Thunderforest"

$Thunderforest.OpenCycleMap
[1] "Thunderforest.OpenCycleMap"

$Thunderforest.Transport
[1] "Thunderforest.Transport"

$Thunderforest.TransportDark
[1] "Thunderforest.TransportDark"

$Thunderforest.SpinalMap
[1] "Thunderforest.SpinalMap"

$Thunderforest.Landscape
[1] "Thunderforest.Landscape"

$Thunderforest.Outdoors
[1] "Thunderforest.Outdoors"

$Thunderforest.Pioneer
[1] "Thunderforest.Pioneer"

$Thunderforest.MobileAtlas
[1] "Thunderforest.MobileAtlas"

$Thunderforest.Neighbourhood
[1] "Thunderforest.Neighbourhood"

$CyclOSM
[1] "CyclOSM"

$Jawg
[1] "Jawg"

$Jawg.Streets
[1] "Jawg.Streets"

$Jawg.Terrain
[1] "Jawg.Terrain"

$Jawg.Sunny
[1] "Jawg.Sunny"

$Jawg.Dark
[1] "Jawg.Dark"

$Jawg.Light
[1] "Jawg.Light"

$Jawg.Matrix
[1] "Jawg.Matrix"

$MapBox
[1] "MapBox"

$MapTiler
[1] "MapTiler"

$MapTiler.Streets
[1] "MapTiler.Streets"

$MapTiler.Basic
[1] "MapTiler.Basic"

$MapTiler.Bright
[1] "MapTiler.Bright"

$MapTiler.Pastel
[1] "MapTiler.Pastel"

$MapTiler.Positron
[1] "MapTiler.Positron"

$MapTiler.Hybrid
[1] "MapTiler.Hybrid"

$MapTiler.Toner
[1] "MapTiler.Toner"

$MapTiler.Topo
[1] "MapTiler.Topo"

$MapTiler.Voyager
[1] "MapTiler.Voyager"

$TomTom
[1] "TomTom"

$TomTom.Basic
[1] "TomTom.Basic"

$TomTom.Hybrid
[1] "TomTom.Hybrid"

$TomTom.Labels
[1] "TomTom.Labels"

$Esri
[1] "Esri"

$Esri.WorldStreetMap
[1] "Esri.WorldStreetMap"

$Esri.DeLorme
[1] "Esri.DeLorme"

$Esri.WorldTopoMap
[1] "Esri.WorldTopoMap"

$Esri.WorldImagery
[1] "Esri.WorldImagery"

$Esri.WorldTerrain
[1] "Esri.WorldTerrain"

$Esri.WorldShadedRelief
[1] "Esri.WorldShadedRelief"

$Esri.WorldPhysical
[1] "Esri.WorldPhysical"

$Esri.OceanBasemap
[1] "Esri.OceanBasemap"

$Esri.NatGeoWorldMap
[1] "Esri.NatGeoWorldMap"

$Esri.WorldGrayCanvas
[1] "Esri.WorldGrayCanvas"

$OpenWeatherMap
[1] "OpenWeatherMap"

$OpenWeatherMap.Clouds
[1] "OpenWeatherMap.Clouds"

$OpenWeatherMap.CloudsClassic
[1] "OpenWeatherMap.CloudsClassic"

$OpenWeatherMap.Precipitation
[1] "OpenWeatherMap.Precipitation"

$OpenWeatherMap.PrecipitationClassic
[1] "OpenWeatherMap.PrecipitationClassic"

$OpenWeatherMap.Rain
[1] "OpenWeatherMap.Rain"

$OpenWeatherMap.RainClassic
[1] "OpenWeatherMap.RainClassic"

$OpenWeatherMap.Pressure
[1] "OpenWeatherMap.Pressure"

$OpenWeatherMap.PressureContour
[1] "OpenWeatherMap.PressureContour"

$OpenWeatherMap.Wind
[1] "OpenWeatherMap.Wind"

$OpenWeatherMap.Temperature
[1] "OpenWeatherMap.Temperature"

$OpenWeatherMap.Snow
[1] "OpenWeatherMap.Snow"

$HERE
[1] "HERE"

$HERE.normalDay
[1] "HERE.normalDay"

$HERE.normalDayCustom
[1] "HERE.normalDayCustom"

$HERE.normalDayGrey
[1] "HERE.normalDayGrey"

$HERE.normalDayMobile
[1] "HERE.normalDayMobile"

$HERE.normalDayGreyMobile
[1] "HERE.normalDayGreyMobile"

$HERE.normalDayTransit
[1] "HERE.normalDayTransit"

$HERE.normalDayTransitMobile
[1] "HERE.normalDayTransitMobile"

$HERE.normalDayTraffic
[1] "HERE.normalDayTraffic"

$HERE.normalNight
[1] "HERE.normalNight"

$HERE.normalNightMobile
[1] "HERE.normalNightMobile"

$HERE.normalNightGrey
[1] "HERE.normalNightGrey"

$HERE.normalNightGreyMobile
[1] "HERE.normalNightGreyMobile"

$HERE.normalNightTransit
[1] "HERE.normalNightTransit"

$HERE.normalNightTransitMobile
[1] "HERE.normalNightTransitMobile"

$HERE.reducedDay
[1] "HERE.reducedDay"

$HERE.reducedNight
[1] "HERE.reducedNight"

$HERE.basicMap
[1] "HERE.basicMap"

$HERE.mapLabels
[1] "HERE.mapLabels"

$HERE.trafficFlow
[1] "HERE.trafficFlow"

$HERE.carnavDayGrey
[1] "HERE.carnavDayGrey"

$HERE.hybridDay
[1] "HERE.hybridDay"

$HERE.hybridDayMobile
[1] "HERE.hybridDayMobile"

$HERE.hybridDayTransit
[1] "HERE.hybridDayTransit"

$HERE.hybridDayGrey
[1] "HERE.hybridDayGrey"

$HERE.hybridDayTraffic
[1] "HERE.hybridDayTraffic"

$HERE.pedestrianDay
[1] "HERE.pedestrianDay"

$HERE.pedestrianNight
[1] "HERE.pedestrianNight"

$HERE.satelliteDay
[1] "HERE.satelliteDay"

$HERE.terrainDay
[1] "HERE.terrainDay"

$HERE.terrainDayMobile
[1] "HERE.terrainDayMobile"

$HEREv3
[1] "HEREv3"

$HEREv3.normalDay
[1] "HEREv3.normalDay"

$HEREv3.normalDayCustom
[1] "HEREv3.normalDayCustom"

$HEREv3.normalDayGrey
[1] "HEREv3.normalDayGrey"

$HEREv3.normalDayMobile
[1] "HEREv3.normalDayMobile"

$HEREv3.normalDayGreyMobile
[1] "HEREv3.normalDayGreyMobile"

$HEREv3.normalDayTransit
[1] "HEREv3.normalDayTransit"

$HEREv3.normalDayTransitMobile
[1] "HEREv3.normalDayTransitMobile"

$HEREv3.normalNight
[1] "HEREv3.normalNight"

$HEREv3.normalNightMobile
[1] "HEREv3.normalNightMobile"

$HEREv3.normalNightGrey
[1] "HEREv3.normalNightGrey"

$HEREv3.normalNightGreyMobile
[1] "HEREv3.normalNightGreyMobile"

$HEREv3.normalNightTransit
[1] "HEREv3.normalNightTransit"

$HEREv3.normalNightTransitMobile
[1] "HEREv3.normalNightTransitMobile"

$HEREv3.reducedDay
[1] "HEREv3.reducedDay"

$HEREv3.reducedNight
[1] "HEREv3.reducedNight"

$HEREv3.basicMap
[1] "HEREv3.basicMap"

$HEREv3.mapLabels
[1] "HEREv3.mapLabels"

$HEREv3.trafficFlow
[1] "HEREv3.trafficFlow"

$HEREv3.carnavDayGrey
[1] "HEREv3.carnavDayGrey"

$HEREv3.hybridDay
[1] "HEREv3.hybridDay"

$HEREv3.hybridDayMobile
[1] "HEREv3.hybridDayMobile"

$HEREv3.hybridDayTransit
[1] "HEREv3.hybridDayTransit"

$HEREv3.hybridDayGrey
[1] "HEREv3.hybridDayGrey"

$HEREv3.pedestrianDay
[1] "HEREv3.pedestrianDay"

$HEREv3.pedestrianNight
[1] "HEREv3.pedestrianNight"

$HEREv3.satelliteDay
[1] "HEREv3.satelliteDay"

$HEREv3.terrainDay
[1] "HEREv3.terrainDay"

$HEREv3.terrainDayMobile
[1] "HEREv3.terrainDayMobile"

$FreeMapSK
[1] "FreeMapSK"

$MtbMap
[1] "MtbMap"

$CartoDB
[1] "CartoDB"

$CartoDB.Positron
[1] "CartoDB.Positron"

$CartoDB.PositronNoLabels
[1] "CartoDB.PositronNoLabels"

$CartoDB.PositronOnlyLabels
[1] "CartoDB.PositronOnlyLabels"

$CartoDB.DarkMatter
[1] "CartoDB.DarkMatter"

$CartoDB.DarkMatterNoLabels
[1] "CartoDB.DarkMatterNoLabels"

$CartoDB.DarkMatterOnlyLabels
[1] "CartoDB.DarkMatterOnlyLabels"

$CartoDB.Voyager
[1] "CartoDB.Voyager"

$CartoDB.VoyagerNoLabels
[1] "CartoDB.VoyagerNoLabels"

$CartoDB.VoyagerOnlyLabels
[1] "CartoDB.VoyagerOnlyLabels"

$CartoDB.VoyagerLabelsUnder
[1] "CartoDB.VoyagerLabelsUnder"

$HikeBike
[1] "HikeBike"

$HikeBike.HikeBike
[1] "HikeBike.HikeBike"

$HikeBike.HillShading
[1] "HikeBike.HillShading"

$BasemapAT
[1] "BasemapAT"

$BasemapAT.basemap
[1] "BasemapAT.basemap"

$BasemapAT.grau
[1] "BasemapAT.grau"

$BasemapAT.overlay
[1] "BasemapAT.overlay"

$BasemapAT.terrain
[1] "BasemapAT.terrain"

$BasemapAT.surface
[1] "BasemapAT.surface"

$BasemapAT.highdpi
[1] "BasemapAT.highdpi"

$BasemapAT.orthofoto
[1] "BasemapAT.orthofoto"

$nlmaps
[1] "nlmaps"

$nlmaps.standaard
[1] "nlmaps.standaard"

$nlmaps.pastel
[1] "nlmaps.pastel"

$nlmaps.grijs
[1] "nlmaps.grijs"

$nlmaps.water
[1] "nlmaps.water"

$nlmaps.luchtfoto
[1] "nlmaps.luchtfoto"

$NASAGIBS
[1] "NASAGIBS"

$NASAGIBS.ModisTerraTrueColorCR
[1] "NASAGIBS.ModisTerraTrueColorCR"

$NASAGIBS.ModisTerraBands367CR
[1] "NASAGIBS.ModisTerraBands367CR"

$NASAGIBS.ViirsEarthAtNight2012
[1] "NASAGIBS.ViirsEarthAtNight2012"

$NASAGIBS.ModisTerraLSTDay
[1] "NASAGIBS.ModisTerraLSTDay"

$NASAGIBS.ModisTerraSnowCover
[1] "NASAGIBS.ModisTerraSnowCover"

$NASAGIBS.ModisTerraAOD
[1] "NASAGIBS.ModisTerraAOD"

$NASAGIBS.ModisTerraChlorophyll
[1] "NASAGIBS.ModisTerraChlorophyll"

$NLS
[1] "NLS"

$JusticeMap
[1] "JusticeMap"

$JusticeMap.income
[1] "JusticeMap.income"

$JusticeMap.americanIndian
[1] "JusticeMap.americanIndian"

$JusticeMap.asian
[1] "JusticeMap.asian"

$JusticeMap.black
[1] "JusticeMap.black"

$JusticeMap.hispanic
[1] "JusticeMap.hispanic"

$JusticeMap.multi
[1] "JusticeMap.multi"

$JusticeMap.nonWhite
[1] "JusticeMap.nonWhite"

$JusticeMap.white
[1] "JusticeMap.white"

$JusticeMap.plurality
[1] "JusticeMap.plurality"

$GeoportailFrance
[1] "GeoportailFrance"

$GeoportailFrance.plan
[1] "GeoportailFrance.plan"

$GeoportailFrance.parcels
[1] "GeoportailFrance.parcels"

$GeoportailFrance.orthos
[1] "GeoportailFrance.orthos"

$OneMapSG
[1] "OneMapSG"

$OneMapSG.Default
[1] "OneMapSG.Default"

$OneMapSG.Night
[1] "OneMapSG.Night"

$OneMapSG.Original
[1] "OneMapSG.Original"

$OneMapSG.Grey
[1] "OneMapSG.Grey"

$OneMapSG.LandLot
[1] "OneMapSG.LandLot"

$USGS
[1] "USGS"

$USGS.USTopo
[1] "USGS.USTopo"

$USGS.USImagery
[1] "USGS.USImagery"

$USGS.USImageryTopo
[1] "USGS.USImageryTopo"

$WaymarkedTrails
[1] "WaymarkedTrails"

$WaymarkedTrails.hiking
[1] "WaymarkedTrails.hiking"

$WaymarkedTrails.cycling
[1] "WaymarkedTrails.cycling"

$WaymarkedTrails.mtb
[1] "WaymarkedTrails.mtb"

$WaymarkedTrails.slopes
[1] "WaymarkedTrails.slopes"

$WaymarkedTrails.riding
[1] "WaymarkedTrails.riding"

$WaymarkedTrails.skating
[1] "WaymarkedTrails.skating"

$OpenAIP
[1] "OpenAIP"

$OpenSnowMap
[1] "OpenSnowMap"

$OpenSnowMap.pistes
[1] "OpenSnowMap.pistes"

$AzureMaps
[1] "AzureMaps"

$AzureMaps.MicrosoftImagery
[1] "AzureMaps.MicrosoftImagery"

$AzureMaps.MicrosoftBaseDarkGrey
[1] "AzureMaps.MicrosoftBaseDarkGrey"

$AzureMaps.MicrosoftBaseRoad
[1] "AzureMaps.MicrosoftBaseRoad"

$AzureMaps.MicrosoftBaseHybridRoad
[1] "AzureMaps.MicrosoftBaseHybridRoad"

$AzureMaps.MicrosoftTerraMain
[1] "AzureMaps.MicrosoftTerraMain"

$AzureMaps.MicrosoftWeatherInfraredMain
[1] "AzureMaps.MicrosoftWeatherInfraredMain"

$AzureMaps.MicrosoftWeatherRadarMain
[1] "AzureMaps.MicrosoftWeatherRadarMain"

$SwissFederalGeoportal
[1] "SwissFederalGeoportal"

$SwissFederalGeoportal.NationalMapColor
[1] "SwissFederalGeoportal.NationalMapColor"

$SwissFederalGeoportal.NationalMapGrey
[1] "SwissFederalGeoportal.NationalMapGrey"

$SwissFederalGeoportal.SWISSIMAGE
[1] "SwissFederalGeoportal.SWISSIMAGE"

If you would like to use openairmaps in a more advanced way (for example, using the “marker” class of functions), you may find it useful to learn some leaflet. Its authors have written an excellent free guide to help you get started.

22.4 Functions

openairmaps currently has three main families of functions — those being network visualisation, directional analysis, and trajectory analysis — plus a handful of “utility” functions. These are summarised in Table 22.1. The functions are further divided into two categories:

  • “All-in-one” functions are most like openair functions in that they will construct a map from the ground-up. These functions provide the quickest route from data to a HTML map.

  • “Marker” functions are most like leaflet functions in that they add layers onto pre-existing leaflet maps. These are more complicated to use, but are more powerful if you are more familiar with how leaflet works.

Table 22.1: The openairmaps toolkit.
Family Description All-in-one Markers

Network Visualisation

Visualises any of the networks made available by importMeta().

networkMap()

Directional Analysis

Uses any of the openair directional analysis plots (e.g., polarPlot()) as markers, allowing them to be viewed in their geospatial context.

annulusMap()
freqMap()
percentileMap()
polarMap()
pollroseMap()
windroseMap()

addPolarMarkers()

Trajectory Analysis

Creates interactive visualisations of HYSPLIT trajectories, allowing for finer investigations when compared with their static openair counterparts.

trajMap()
trajLevelMap()

addTrajPaths()

The next few pages will discuss each of these function families in turn, starting with network visualisation.