q35·intermediate

When a dust storm blows in, how bad is the air — and who's downwind?

atmosphereair-qualitypublic-health Datasets: 3 30–60 min

Dust is fine soil lifted into the sky by wind, most of it from deserts like the Sahara. When a big outbreak happens, a brown river of it pours off the land and rides the winds for thousands of kilometres, blocking the sun and filling the air people breathe with tiny grit. One NASA dataset maps, hour by hour, how thick that dust is overhead and how much of it is down at ground level. So you can follow one storm as it travels and see who lies in its path.

When a dust storm blows in, how bad is the air — and who’s downwind?

Dust is fine soil lifted into the sky by wind, most of it from deserts like the Sahara. When a big outbreak happens, a brown river of it pours off the land and rides the winds for thousands of kilometres, blocking the sun and filling the air people breathe with tiny grit. One NASA dataset maps, hour by hour, how thick that dust is overhead and how much of it is down at ground level. So you can follow one storm as it travels and see who lies in its path.

Which data to use, and how

Use this dataset: MERRA-2 aerosols (short name M2T1NXAER). It’s a reanalysis, a NASA model that blends satellites and weather data into one gap-free map of the atmosphere, given as an hourly global file going back to 1980. The hourly, no-gaps quality is what makes it the easiest place to start. You don’t have to chase clouds or missing pixels. You pick an hour and read the dust off the grid.

Then do four steps (the runnable code further down does all of this; this is just the idea):

  1. Pick your spot and your storm. A small latitude/longitude box, and the days the dust blew through. The default box sits on the tropical Atlantic off West Africa, where Saharan dust crosses the ocean.
  2. Read how thick the dust is overhead. The field DUEXTTAU is the dust optical depth: a plain number where higher means more dust blocking the sun (above ~1 already means a heavily hazed sky).
  3. Read how much dust is down at breathing level. The field DUSMASS25 is surface dust in the PM2.5 size range, the tiny particles that reach deep into lungs. It comes in kg per cubic metre; multiply by 1,000,000,000 to get the µg/m³ that air-quality limits use.
  4. Follow the plume and see who’s downwind. Step through the hourly files to watch the dust move, then lay a population map (WorldPop) over its path to estimate how many people sit under it.

That’s the whole method. Below, “How a scientist answers this” names the exact tools, and the Run it cell does it live on synthetic data so you can see each step work.

What you can find out

  • How thick the dust is overhead right now, or any past hour back to 1980. DUEXTTAU, the dust-only optical depth (unitless; higher means more dust blocking the sun).
  • How much dust is down at breathing level. DUSMASS25 (the lung-reaching PM2.5-size fraction) and DUSMASS (total dust), once you convert to µg/m³.
  • Where the plume is heading, hour by hour. Step through the hourly files and watch the bulk of the dust drift west across the ocean with the trade winds.
  • Roughly how many people sit downwind. Overlay WorldPop population on the plume’s path to estimate exposed people along a coastline or city.
  • Whether satellites saw the same thing. Cross-check the model against MODIS aerosol (MOD04), which measures haze directly from the imagery.

A real result to check against: for the tropical Atlantic off West Africa (12–22 °N) during the June 2020 “Godzilla” Saharan plume, DUEXTTAU hit a peak of 3.48 (area mean 1.71) at 17:30 UTC on 18 Jun 2020, an extraordinarily opaque sky. The surface dust field DUSMASS25 peaked around 341 µg/m³ in the same box. That’s many times any healthy daily air-quality limit, had it been over a populated coast.

What it can’t tell you

  • The exact dust a single person breathed. This is a ~50 km model grid, not a street-level sensor. Local hills, indoor air, and the weather on your block all shift the real number.
  • Health outcomes. Heavy dust raises breathing and heart risk, but tying a plume to actual ER visits needs hospital or clinic records. Satellites can’t see illness.
  • Dust vs. everything else. DUEXTTAU is dust-only, but real air also carries smoke, sea salt, and factory soot, which change what “bad air” means in any given place.
  • Anything smaller than ~50 km. One neighbourhood, a single valley, or a small near-source dust gust is finer than the grid. For that, pair it with ground monitors or sharper imagery.
  • Tomorrow’s dust. This is a reanalysis, a careful reconstruction of what already happened, not a forecast. For “will it be dusty tomorrow,” use an operational dust forecast model instead.

Gotchas to watch for

  • It’s a model, not a camera. A reanalysis is the best blend of observations and physics, not a direct photo. It’s good for where and how thick the dust was, but treat the exact numbers as very good estimates, not measurements. The fix: confirm big events against real satellite haze (MODIS MOD04).
  • Watch the units on surface dust. DUSMASS25 and DUSMASS come in kg/m³, a tiny number. Forget to multiply by 1e9 (→ µg/m³) and your “dust” will look like nothing. Always convert before you compare to an air-quality limit.
  • Slice your box before you read. Each hourly file is a whole-globe grid. Opening it lazily and cutting to your bounding box first (with .sel(...)) keeps you from pulling the entire planet into memory for one coastline.
  • Hours are in UTC. The timestamps are global clock time (UTC), not local time. When you say “a storm peaked at 17:30,” that’s UTC. Shift it to local time before talking about someone’s day.

The real-data code

The Run it cell above runs the method on synthetic data (no login). Below is the whole recipe against the real archive: search the MERRA-2 hourly files, open them lazily, slice the box, read the dust fields, follow the plume hour by hour, and plot the peak hour. It needs a free Earthdata Login.

Verified locally. M2T1NXAER is a global hourly NetCDF of MERRA-2 aerosol diagnostics; open one granule lazily and slice your bounding box before reading. DUEXTTAU is unitless dust optical depth; DUSMASS25 is surface PM2.5-dust in kg m⁻³ (× 1e9 → µg m⁻³).

import earthaccess, xarray as xr, numpy as np
import matplotlib.pyplot as plt

earthaccess.login(strategy="netrc")          # free Earthdata Login

W, S, E, N = -30, 12, -18, 22                 # tropical Atlantic off West Africa (W,S,E,N)

# 1. Search the hourly MERRA-2 aerosol files spanning the June 2020 "Godzilla" plume.
grans = earthaccess.search_data(short_name="M2T1NXAER",
                                temporal=("2020-06-18", "2020-06-22"),
                                bounding_box=(W, S, E, N))
print(f"{len(grans)} daily granules (each holds 24 hourly steps)")

# 2. Open lazily and slice the bounding box BEFORE reading (each file is whole-globe).
#    open_mfdataset stitches the daily files into one continuous hourly time axis.
ds  = xr.open_mfdataset(earthaccess.open(grans), combine="by_coords")
sub = ds.sel(lon=slice(W, E), lat=slice(S, N))

# 3. Read the two dust fields and area-average each hour to follow the plume.
aod = sub["DUEXTTAU"]                          # dust optical depth (unitless)
pm  = sub["DUSMASS25"] * 1e9                   # surface PM2.5 dust: kg/m³ -> µg/m³

aod_hourly = aod.mean(dim=["lat", "lon"]).compute()   # box-mean AOD per hour
pm_hourly  = pm.mean(dim=["lat", "lon"]).compute()    # box-mean surface dust per hour

# 4. Find the worst hour and report it (the answer).
ipeak = int(aod_hourly.argmax())
tpeak = np.datetime_as_string(aod_hourly.time.values[ipeak], unit="m")
print(f"Worst hour: {tpeak} UTC")
print(f"  dust AOD  peak {float(aod.max()):.2f}, box-mean {float(aod_hourly[ipeak]):.2f}")
print(f"  surface PM2.5-dust peak {float(pm.max()):.0f} µg/m³,",
      f"box-mean {float(pm_hourly[ipeak]):.0f} µg/m³")
verdict = "heavily hazed (AOD>1)" if float(aod_hourly.max()) > 1 else "moderate"
print(f"VERDICT: a {verdict} Saharan plume crossed the box; "
      f"surface dust hit {float(pm_hourly.max()):.0f} µg/m³ — far above any healthy limit.")

# 5. Plot: the hourly box-mean dust optical depth, plus a map of the peak hour.
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
ax1.plot(aod_hourly.time, aod_hourly, color="saddlebrown")
ax1.axvline(aod_hourly.time.values[ipeak], ls="--", c="k", label="peak hour")
ax1.set_ylabel("box-mean dust AOD"); ax1.set_xlabel("UTC"); ax1.legend()
aod.isel(time=ipeak).plot(ax=ax2, cmap="YlOrBr")    # dust map at the worst hour
ax2.set_title(f"DUEXTTAU @ {tpeak} UTC")
plt.tight_layout(); plt.show()

# To turn "how thick" into "who's downwind": overlay a WorldPop population grid on the
# plume's path. And confirm the event against MODIS MOD04 satellite haze — it's a model,
# not a camera (see the gotchas: trust where/how-thick, treat exact numbers as estimates).

Where the data comes from

The dust maps (M2T1NXAER, MERRA-2 aerosols) come from NASA’s GES DISC archive through one free login (Earthdata Login). The optional cross-checks are separate: MODIS aerosol imagery is also NASA, while WorldPop population grids come from an outside research group and are free to download. You stack them together to turn “how thick is the dust” into “how many people are under it.”

Sources

How a scientist answers this
Parameters
Hourly MERRA-2 aerosol diagnostics (M2T1NXAER, global, 1980–): dust aerosol optical depth at 550 nm (`DUEXTTAU`, unitless) overhead and surface dust mass (`DUSMASS25` for PM2.5-size, `DUSMASS` total, kg m⁻³; ×1e9 for µg m⁻³) at breathing level; MODIS AOD (MOD04) for independent satellite confirmation and WorldPop for population downwind.
Method
Extract the hourly `DUEXTTAU` and `DUSMASS25` fields over the plume box to follow the storm in time (peak and area-mean), map the plume's advance hour by hour, and overlay WorldPop to estimate population under surface-dust concentrations exceeding health limits; an AOD > ~1 indicates heavily hazed skies.
Validation
Cross-check MERRA-2 dust AOD against independent MODIS MOD04 satellite AOD (clear-sky only), recall MERRA-2 is a reanalysis (model + assimilation) so surface PM2.5 is modeled not measured, and compare against ground PM monitors where available before drawing health conclusions.
In plain EnglishFollow the dust hour by hour — how thick it is overhead and how much is down where people breathe — and lay a population map underneath to see who's downwind in the worst of it.

Make it yours → Set the plume bounding box, the storm dates/hours, and the surface-dust health threshold in the notebook for your event.

🧪 Virtual Mock Lab stand-in data · runs in your browser · no login

A safe place to practise the method on M2T1NXAER. The code is the real earthaccess workflow, but we feed it stand-in data shaped like the real product — so you can run it, change the numbers, and break it without an account. When you want the real data, use the recipe under “The real-data code” above, or download the notebook below.

editable · runs in your browser

Datasets used

Recipe ready · not yet run The recipe and the Virtual Mock Lab above are ready to use. We haven't yet run this question end-to-end on real data, so it has no verified answer yet — only the questions we've actually computed carry one. That's the honest line between a method and a result.