Data downloads

← back to the map · model performance · about

Everything the interactive map shows is downloadable here for research use: the gridded 72-hour forecast fields, the site's own compact binary format, and the observation-vs-forecast verification statistics. Please see the citation and the as-is disclaimer at the bottom.

Gridded forecast fields

Verification statistics

summary.json
domain/region skill by metric and lead day — bias, RMSE, r, ozone-exceedance counts (90-day window, nightly)
JSON
sites.json
per-monitor 30-day pooled bias / RMSE / n for each metric
JSON

Statistics are computed by pairing AirNow observations with the archived forecast that covered each day; methodology on the verification page.

Reading the GeoTIFF (COG)

One file per pollutant per cycle. 72 bands = 72 forecast hours; each band description is the valid time in Pacific time. float32 EPSG:3857 NaN = nodata Units: µg/m³ (PM2.5), ppb (ozone).

import rasterio
ds = rasterio.open("pm25_20260712.tif")
pm = ds.read(1)                # band N = forecast hour N-1
print(ds.descriptions[0])      # e.g. "2026-07-12 05:00 PDT"
print(ds.crs, ds.bounds)

Reading the packed binaries

The viewer's own format — much smaller than the GeoTIFFs and available for every archived cycle. Each cycle directory has a manifest.json describing the grid plus gzipped .bin files:

FileLayout
pm25_<cycle>.bin.gz
o3_<cycle>.bin.gz
uint16 little-endian, hour-major, rows N→S, cols W→E. Value = stored/scale (per-species scale in the manifest). 65535 = nodata.
daily_<cycle>.bin.gz day-major, then [24-h PM2.5 mean, ozone MDA8] per day; same scaling; day list in manifest.daily.
import gzip, json, numpy as np, urllib.request as rq
base = "https://nw-air-forecast.pages.dev/data/20260712/"
man  = json.load(rq.urlopen(base + "manifest.json"))
H, W = man["grid"]["height"], man["grid"]["width"]
sp   = man["species"]["pm25"]
raw  = gzip.decompress(rq.urlopen(base + sp["bin"] + ".gz").read())
a    = np.frombuffer(raw, "<u2").reshape(-1, H, W).astype(float)
a[a == 65535] = np.nan
pm   = a / sp["scale"]         # (72, H, W) in µg/m³; hours in man["hours"]

Citing the data

AIRPACT-6 air quality forecast (Northwest Air Quality Forecast visualization), Washington State University, https://nw-air-forecast.pages.dev/ (cycle YYYYMMDD, accessed YYYY-MM-DD).

This is a research forecast provided as-is, without warranty; fields are model output, not observations. Bulk or automated downloading of many cycles is fine — files are served from a CDN — but please open a GitHub issue if you need a different format or historical data beyond what is listed here.