q26·intermediate

Is the air in my city getting more polluted — and how many people are breathing the worst of it?

atmospherepublic-healthurbanair-quality Datasets: 4 20–45 min
Find the data for your area

Draw a rectangle to pick your area of interest, then see what NASA data covers it (live, here in your browser) or download a ready-to-run notebook with your AOI pre-filled. The notebook runs in any Python environment — it needs a free Earthdata Login to fetch the data.

Current AOI: 30.9, 29.7 → 31.6, 30.4 (Greater Cairo, Egypt)

Is the air in my city getting more polluted — and how many people are breathing the worst of it?

Nitrogen dioxide (NO₂) is the fingerprint of combustion — traffic, power plants, industry. The TROPOMI instrument on Sentinel-5P measures it from orbit, and NASA serves a tidy monthly gridded version. Pair it with free population data and you can answer two questions at once: is my city’s air getting worse over the years, and how many people live where it’s worst.

This is a multi-agency question: the NO₂ is NASA/ESA, but the “who breathes it” answer comes from free non-NASA population and boundary layers joined on top.

What you can answer

  • Is NO₂ trending up or down over my city — stack the monthly L3 grids across 2018→now and fit a trend (verified for Greater Cairo: +26% from Jan 2019 to Jan 2024)
  • Which parts of the metro are worst — map the NO₂ grid to find the high-NO₂ corridor (downtown, ring roads, industrial edge)
  • How many people live in the dirty-air zone — overlay free WorldPop population and count the people inside the high-NO₂ pixels (verified: ~22.9M in the default Cairo box, worst around district Qasr Al-Nile)
  • Did a lockdown / new highway / new plant change things — the monthly cadence resolves the 2020 COVID dip and step-changes after big infrastructure
  • How my city compares to its region — the global grid lets you rank neighboring cities on the same scale

What you can NOT answer with these datasets alone

  • Street-by-street “is my block polluted” — the L3 grid is ~10 km; it captures the city and its major corridors, not individual streets. For finer detail use the L2 S5P_L2__NO2____HiR swaths (~5.5 km) or ground monitors (OpenAQ)
  • The pollution you actually inhale — TROPOMI measures the column of NO₂ above the ground, not the concentration at breathing height; surface levels depend on mixing and weather
  • Other pollutants that hurt health — NO₂ is one marker; PM2.5 (the deadliest) needs separate data (ground monitors, or aerosol optical depth as a rough proxy)
  • Exactly who is exposed (census-grade) — WorldPop is modeled population; it gives sound exposure totals, not household income, age, or health status for equity targeting
  • Blame for the trend — a rising column doesn’t say whether traffic, industry, or a power plant drove it; pair with GHSL built-up + emissions inventories to attribute

Code template (Python, cloud-direct)

Verified locally. The HAQ TROPOMI product is monthly gridded netCDF (variable Tropospheric_NO2, dims Latitude/Longitude) on NASA GES DISC — open with xarray. The population and boundary layers are free and need no login.

import earthaccess
import numpy as np
import xarray as xr

earthaccess.login(strategy="netrc")

# Greater Cairo, Egypt — a high-NO₂ megacity
aoi = (30.9, 29.7, 31.6, 30.4)              # (W, S, E, N)

def city_no2(year_month):
    """Mean tropospheric NO₂ over the AOI for one month (molec/cm²)."""
    r = earthaccess.search_data(short_name="HAQ_TROPOMI_NO2_GLOBAL_M_L3",
                                temporal=year_month, count=1)
    ds = xr.open_dataset(earthaccess.open(r[:1])[0])
    da, la, lo = ds["Tropospheric_NO2"], ds["Latitude"], ds["Longitude"]
    sub = (da.where((la >= aoi[1]) & (la <= aoi[3]), drop=True)
             .where((lo >= aoi[0]) & (lo <= aoi[2]), drop=True))
    return float(np.nanmean(sub.values))

# 1. Build a multi-year trend (one point per January)
trend = {y: city_no2((f"{y}-01-01", f"{y}-02-01")) for y in ["2019", "2021", "2024"]}
for y, v in trend.items():
    print(f"Cairo Jan {y}: {v:.2e} molec/cm^2")
change = (trend["2024"] - trend["2019"]) / trend["2019"] * 100
print(f"Change 2019->2024: {change:+.0f}%")     # verified ~ +26%

# 2. Who breathes it — free WorldPop population + geoBoundaries place names (no NASA login)
import requests, rasterio
from rasterio.windows import from_bounds
import geopandas as gpd
from shapely.geometry import Point

meta = requests.get("https://www.worldpop.org/rest/data/pop/wpic1km?iso3=EGY").json()
pop_url = next(f for f in meta["data"][-1]["files"] if f.endswith(".tif"))
open("egy_pop_1km.tif", "wb").write(requests.get(pop_url).content)

with rasterio.open("egy_pop_1km.tif") as src:
    pop = src.read(1, window=from_bounds(*aoi, transform=src.transform)).astype("float64")
    pop[pop == src.nodata] = np.nan
print(f"People in AOI: {np.nansum(pop):,.0f}")   # verified ~22.9M for this Cairo box

adm = gpd.read_file(requests.get(
    "https://www.geoboundaries.org/api/current/gbOpen/EGY/ADM2/").json()["gjDownloadURL"])
print("Central district:", adm[adm.contains(Point(31.24, 30.05))].iloc[0]["shapeName"])  # Qasr Al-Nile

# To count people in the dirty-air zone: resample the NO₂ grid onto the population grid
#   (or vice-versa), threshold the top NO₂ quantile, and sum `pop` inside it.

Expected output

  • NO₂ trend line: monthly (or per-January) tropospheric NO₂ over the city, 2018→now, showing whether the air is getting dirtier — for Cairo, a clear rising trend (~+26% 2019→2024)
  • NO₂ map: the gridded column over the metro, highlighting the high-pollution corridor
  • Exposure estimate: people living in the high-NO₂ pixels (WorldPop), with the worst district named (geoBoundaries)
  • Comparison: the same metric for neighboring cities, ranked on one scale
  • Event markers (optional): the 2020 COVID dip or a post-infrastructure step-change

Caveats

  • Column, not surface — TROPOMI sees the NO₂ column from space; ground concentration (what you breathe) depends on boundary-layer mixing and weather. Treat the map as relative and trend information, not an absolute dose.
  • ~10 km grid — the monthly L3 resolves the city and major corridors, not streets; for finer work use L2 HiR swaths or OpenAQ ground monitors.
  • Cloud and season — TROPOMI needs clear sky; winter monthly means are more complete than monsoon months. Compare like months across years, not adjacent months.
  • NO₂ ≠ overall air quality — it tracks combustion, but PM2.5 (the biggest health burden) needs separate measurement.
  • Modeled population — WorldPop is an estimate; exposure totals are order-of-magnitude sound but not a census.

Cross-agency composition

NASA GES DISC serves the TROPOMI NO₂ L3 (Sentinel-5P is an ESA mission; NASA hosts this gridded product). WorldPop (Univ. Southampton), GHSL (EU JRC), and geoBoundaries (William & Mary) are all free, non-NASA layers joined client-side — no extra login.

Sources

📚 Problem Finder KB

Not yet tracked in the KB.