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)
unit measurement pollutant date period time
µg/m3 10.103 Nitric oxide (NO) 21/05/2020 Hourly 13:00
µg/m3 11.093 Nitrogen dioxide (NO2) 21/05/2020 Hourly 13:00
µg/m3 26.584 Nitrogen oxides as nitrogen dioxide (NOXasNO2) 21/05/2020 Hourly 13:00
µg/m3 68.453 Ozone (O3) 21/05/2020 Hourly 13:00

We can get the Plymouth data similarly:

plymouthPollutants <- getLatestPollutants("PLYM")
kable(plymouthPollutants$pollutants)
period time unit measurement pollutant date
Hourly 14:00 µg/m3 10.710 Nitrogen dioxide (NO2) 21/05/2020
Hourly 14:00 µg/m3 (TEOM FDMS) 11.000 PM10 particulate matter (Hourly measured) (PM10) 21/05/2020
Hourly 14:00 µg/m3 14.153 Nitrogen oxides as nitrogen dioxide (NOXasNO2) 21/05/2020
Hourly 14:00 µg/m3 (TEOM FDMS) 2.000 Volatile PM2.5 (Hourly measured) (Volatile PM2.5) 21/05/2020
Hourly 14:00 µg/m3 2.245 Nitric oxide (NO) 21/05/2020
Hourly 14:00 µg/m3 (TEOM FDMS) 4.300 Volatile PM10 (Hourly measured) (Volatile PM10) 21/05/2020
Hourly 14:00 µg/m3 (TEOM FDMS) 6.700 Non-volatile PM10 (Hourly measured) (Non-volatile PM10) 21/05/2020
Hourly 14:00 µg/m3 (TEOM FDMS) 7.100 Non-volatile PM2.5 (Hourly measured) (Non-volatile PM2.5) 21/05/2020
Hourly 14:00 µg/m3 79.429 Ozone (O3) 21/05/2020
Hourly 14:00 µg/m3 (TEOM FDMS) 9.100 PM2.5 particulate matter (Hourly measured) (PM2.5) 21/05/2020

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 68.453
PLYM 50.37038 -4.14265 79.429

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.