Mapping Air Quality
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.