AirNow quickstart — PM2.5 over a city¶
AirNow publishes reference-grade hourly air-quality monitor observations for the US and Canada. This notebook pulls PM2.5 for the Los Angeles basin over one day and plots it by station.
The earthlens AirNow backend is tabular: download() returns a long-format
pandas.DataFrame, one row per measurement.
Free API key. AirNow's
/aq/data/endpoint needs a freeAPI_KEY(register at https://docs.airnowapi.org/account/request/). Set it asAIRNOW_API_KEY. The live download cell below is skipped when the key is absent, so the notebook still runs clean end-to-end (e.g. underpytest --nbval-lax).
Setup¶
%matplotlib inline
import os
import matplotlib.pyplot as plt
from earthlens import EarthLens
from earthlens.airnow import Catalog
The pollutant catalog¶
variables names pollutants, resolved to AirNow parameters codes via the catalog.
This cell needs no network.
sorted(Catalog().pollutants), Catalog().codes_for(["pm25", "o3"])
(['co', 'no2', 'o3', 'pm10', 'pm25', 'so2'], ['PM25', 'OZONE'])
Request parameters¶
bbox_lat = [34.0, 34.3] # Los Angeles basin
bbox_lon = [-118.5, -118.1]
start, end = "2026-01-01", "2026-01-01"
Build the request & download¶
AirNow authenticates at construction time, so both building the client and
downloading run inside the key check — the notebook stays runnable without a key
(e.g. under pytest --nbval-lax), it just skips the live block.
df = None
if os.environ.get("AIRNOW_API_KEY"):
client = EarthLens(
data_source="airnow",
variables=["pm25"],
start=start,
end=end,
lat_lim=bbox_lat,
lon_lim=bbox_lon,
data_type="B", # both concentration and AQI
path="out/airnow",
)
df = client.download(progress_bar=False)
print(df.shape)
else:
print("set AIRNOW_API_KEY to run the live build + download cell")
set AIRNOW_API_KEY to run the live build + download cell
Inspect the first rows¶
if df is not None:
display(df.head())
Plot hourly PM2.5 by station¶
if df is not None and not df.empty:
pm25 = df[df["parameter"] == "PM2.5"]
fig, ax = plt.subplots(figsize=(10, 4))
for site, group in pm25.groupby("site_name"):
ax.plot(group["datetime_utc"], group["value"], marker=".", label=str(site))
ax.set_ylabel("PM2.5 (UG/M3)")
ax.set_xlabel("time (UTC)")
ax.set_title("Hourly PM2.5 by AirNow site — Los Angeles")
ax.legend(title="site", fontsize=7)
fig.autofmt_xdate()
plt.show()