Leaflet For R uses the JavaScript mapping library Leaflet to let turn data straight into nifty maps. We can use this to turn query results from the UK Air Quality API into maps of ozone levels from monitoring sites. All the data comes ultimately from the DEFRA Air Information Resource.

We’ll use these libraries:

library(tidyverse)
library(config)
library(leaflet)
library(jsonlite)
library(knitr)

Let’s define some helpful functions to wrap our API calls. Note that I’m using the config package to hold the API key needed for these calls. The two calls we need here are getSite and getLatestPollutants. The former finds the nearest monitoring site given a latitude and longitude; the latter returns the latest pollutant measurements for a given site:

dw <- config::get("air-quality-api")

getSite <- function(latitude, longitude) {
  
  url <- paste("https://uk-air-quality.p.rapidapi.com/nearestsite?latitude=", latitude, "&longitude=", longitude, sep="")
  
  req <- httr::GET(url,
  httr::add_headers(
      "x-rapidapi-key"= config::get("air-quality-api")$key
    )
  )

  json <- httr::content(req, as = "text")
  fromJSON(json)
}

getLatestPollutants <- function(siteId) {
  
  url <- paste("https://uk-air-quality.p.rapidapi.com/latestpollutants?siteId=", siteId, sep="")
  
  req <- httr::GET(url,
  httr::add_headers(
      "x-rapidapi-key"= config::get("air-quality-api")$key
    )
  )

  json <- httr::content(req, as = "text")
  fromJSON(json)
}

Here is the nearest site to Exeter:

exeterSite <- getSite(50.721802, -3.533620)
exeterSite
## $siteId
## [1] "EX"
## 
## $distance
## [1] 0.2322452

Here is the nearest site to Plymouth:

plymouthSite <- getSite(50.370380, -4.142650)
plymouthSite
## $siteId
## [1] "PLYM"
## 
## $distance
## [1] 0.09003139

We can now use the siteId to get the latest pollutants for the Exeter site:

exeterPollutants <- getLatestPollutants("EX")
kable(exeterPollutants$pollutants)
time unit measurement pollutant date period
10:00 µg/m3 26.201 Nitrogen dioxide (NO2) 22/07/2020 Hourly
10:00 µg/m3 38.633 Nitrogen oxides as nitrogen dioxide (NOXasNO2) 22/07/2020 Hourly
10:00 µg/m3 53.285 Ozone (O3) 22/07/2020 Hourly
10:00 µg/m3 8.107 Nitric oxide (NO) 22/07/2020 Hourly

We can get the Plymouth data similarly:

plymouthPollutants <- getLatestPollutants("PLYM")
kable(plymouthPollutants$pollutants)
unit measurement pollutant date period time
µg/m3 (TEOM FDMS) 1.500 Volatile PM10 (Hourly measured) (Volatile PM10) 22/07/2020 Hourly 11:00
µg/m3 (TEOM FDMS) 11.100 PM10 particulate matter (Hourly measured) (PM10) 22/07/2020 Hourly 11:00
µg/m3 14.153 Nitrogen dioxide (NO2) 22/07/2020 Hourly 11:00
µg/m3 21.420 Nitrogen oxides as nitrogen dioxide (NOXasNO2) 22/07/2020 Hourly 11:00
µg/m3 (TEOM FDMS) 3.700 Volatile PM2.5 (Hourly measured) (Volatile PM2.5) 22/07/2020 Hourly 11:00
µg/m3 (TEOM FDMS) 4.700 Non-volatile PM2.5 (Hourly measured) (Non-volatile PM2.5) 22/07/2020 Hourly 11:00
µg/m3 4.740 Nitric oxide (NO) 22/07/2020 Hourly 11:00
µg/m3 55.081 Ozone (O3) 22/07/2020 Hourly 11:00
µg/m3 (TEOM FDMS) 8.400 PM2.5 particulate matter (Hourly measured) (PM2.5) 22/07/2020 Hourly 11:00
µg/m3 (TEOM FDMS) 9.600 Non-volatile PM10 (Hourly measured) (Non-volatile PM10) 22/07/2020 Hourly 11:00

Leaflet accepts a data frame of data to plot, so we can convert our data so far into a simple data frame. We only want ozone data for this example:

pollutants <- data.frame(siteId=character(), latitude=numeric(), longitude=numeric(), ozone=numeric(), stringsAsFactors=FALSE)

pollutants <- pollutants %>% 
  add_row(siteId = exeterSite$siteId, latitude = 50.721802, longitude= -3.533620, ozone=as.numeric(exeterPollutants$pollutants[which(exeterPollutants$pollutants$pollutant=="Ozone (O3)"),]$measurement)) %>%
  add_row(siteId = plymouthSite$siteId, latitude = 50.370380, longitude= -4.142650, ozone=as.numeric(plymouthPollutants$pollutants[which(plymouthPollutants$pollutants$pollutant=="Ozone (O3)"),]$measurement))

kable(pollutants)
siteId latitude longitude ozone
EX 50.72180 -3.53362 53.285
PLYM 50.37038 -4.14265 55.081

Now we can plot the ozone levels as circles proportional to their values:

options(viewer = NULL)
map <- leaflet(pollutants) %>%
  addTiles(urlTemplate = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png") %>%
  addCircles(lng = ~longitude, lat = ~latitude, weight = 10, radius = ~ozone * 100, popup = ~siteId) %>%
  setView(lng = -3.838135, lat = 50.546091, zoom = 9)
map

Here we pass leaflet the data frame of pollutant data and use addCircles to pull out each row and turn it into a circle proportional to the ozone measurement.

We can do a lot more with Leaflet. We could easily develop this further into a Shiny app to show levels of different pollutants for all sites across the UK.