How primed for wildfire is the land around me right now?
Draw a rectangle, then check which collections cover it. Most NASA data is global, so a big box matches a lot.
-122.4, 38.3 → -122, 38.7 (Northern California wine country, USA)Wildfire needs dry fuel. The driest fuel is vegetation that has stopped growing and started to cure: grass that has gone from green to gold, brush that has spent its summer water. Satellites measure how green the land is from orbit, and greenness falling through a dry season is the land turning to tinder. You can watch which hillsides are drying out, even with no fire-weather station anywhere nearby.
How primed for wildfire is the land around me right now?
Wildfire needs dry fuel. The driest fuel is vegetation that has stopped growing and started to cure: grass that has gone from green to gold, brush that has spent its summer water. Satellites measure how green the land is from orbit, and greenness falling through a dry season is the land turning to tinder. You can watch which hillsides are drying out, even with no fire-weather station anywhere nearby.
Which data to use, and how
Use this dataset: MOD13A2 (short name MOD13A2), NASA’s MODIS satellite map
of vegetation greenness. It gives one snapshot every 16 days at 1 km everywhere on land, going back to
2000. The greenness number it reports is called NDVI, the Normalized Difference Vegetation Index:
a 0-to-1 score where high means green, low means bare or dried-out. It’s the easiest
starting point for fuel dryness.
Then do four steps (the runnable code further down does all of this — this is just the idea):
- Pick your land: a small latitude/longitude box around your valley, watershed, or county, plus the dates you care about (one dry season, or several years).
- Read the greenness: pull the NDVI value for each 1 km pixel. The file stores it as a big integer to
save space, so multiply by 0.0001 to get the real 0-to-1 NDVI, and throw away the “no data” pixels
(marked with the fill value
-3000, e.g. clouds). - Average over your box: take the mean NDVI across your area for that 16-day snapshot. That’s one number describing how green (or cured) your land is right now.
- Watch it fall: repeat for each snapshot through the dry season. Lower and dropping = drier, more fire-primed fuel. Compare against past years to see if it’s drier than normal.
That’s the whole method. Below, “How a scientist answers this” names the exact tools, and the Run it cell runs it live on synthetic data so you can see each step work.
What you can find out
- How green (or cured) the vegetation is right now, and every 16 days back to 2000.
- Whether fuel is drier than a normal year. Build a “normal” from past years for the same 16-day window, then plot how far this season sits below it. A deep shortfall means unusually cured, flammable vegetation.
- Where the dry patches are. At 1 km you can see which slopes, grasslands, or old burn scars have lost their green while irrigated fields stay lush.
- How fast the green is fading. The rate of NDVI decline through late summer shows fuel curing in real time, the lead-up to peak fire season.
- The heat-and-stress context. Add MOD11 LST (land surface temperature, the surface-heat side of fire danger) and MOD16 / ECOSTRESS ET (evapotranspiration, how much water the plants are losing) for the fuller danger picture.
Verified locally. For Northern California wine country (38.3–38.7 °N), the MOD13A2 NDVI field averaged 0.413 across the AOI for the 1–16 September 2024 snapshot (1,786 valid 1 km pixels). That’s moderate greenness, the signature of cured grassland mixed with still-green vineyards and oak. NDVI alone is not a fire forecast: it tells you how dry the fuel is, not whether it will burn.
What it can’t tell you
- Whether a fire will actually start or spread. NDVI is a fuel-dryness proxy, not an ignition or spread model. Predicting fire behavior needs wind, humidity, fuel-moisture readings, and dedicated fire tools (e.g. NFDRS, the US National Fire Danger Rating System).
- Live fuel moisture as a percentage. NDVI tracks moisture but isn’t a calibrated moisture gauge. The actual percent number needs field sampling or specialized retrievals.
- Fine-scale detail. At 1 km a single pixel covers a square kilometre, so it can’t see one dry ridgeline, a cleared “defensible space” around a house, or dry brush hidden under tree canopy. For that, pair with sharper imagery (Sentinel-2, Landsat).
- Sudden changes within 16 days. MOD13A2 is a 16-day composite, one blended snapshot per period, so a heat wave or wind event that cures fuel in a few days won’t show up until the next snapshot lands.
- Active fires or smoke. Those are a completely different product (VIIRS/MODIS active-fire detection and fire radiative power), not NDVI.
Gotchas to watch for
- The greenness is stored as integers, not decimals. MOD13A2 packs NDVI as a whole number to save space,
so a value like
4126really means0.4126. Always multiply by the scale factor (0.0001) before you trust it. Forget this and your “NDVI” will read in the thousands. - “No data” pixels are poison. Clouds, snow, and bad scans are written as a fill value (
-3000). If you average them in, your result is garbage. The fix: mask them out (set them to “not a number”) before taking any mean. - The files are an awkward format. MOD13A2 ships as HDF-EOS2 (HDF4) tiles in MODIS’s own “sinusoidal” map
grid. Many environments’ bundled GDAL library lacks the HDF4 driver, so it silently fails to open them. The
reliable fix used below is to read them directly with the
pyhdflibrary instead. - The grid isn’t plain lat/lon. Pixels are spaced in metres on a curved sinusoidal projection, not in degrees. To clip your lat/lon box you have to first convert each pixel’s centre from sinusoidal metres to longitude/latitude (the code does this). Skip it and you’ll select the wrong patch of ground.
- One snapshot is a moment; a trend is the story. A single low NDVI could just be a late-summer reading. Compare the same 16-day window across years to know whether this year is genuinely drier than usual.
The real-data code
The Run it cell above runs the method on synthetic data with no login. Below is the whole recipe against
the real archive: it searches MOD13A2, opens each HDF-EOS tile through the dry season,
rescales the NDVI, masks the -3000 fill, converts the sinusoidal pixel centres to lon/lat, clips your AOI,
averages each snapshot, then fits the seasonal decline and plots it. Lower / falling NDVI = drier fuel.
It needs a free Earthdata Login.
import os, re, warnings, earthaccess, numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from pyhdf.SD import SD, SDC
warnings.filterwarnings("ignore")
# load Earthdata creds from .env without `source` (passwords can break the shell)
for line in open(".env"):
m = re.match(r'\s*(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)\s*$', line)
if m: os.environ.setdefault(m.group(1), m.group(2).strip().strip('"').strip("'"))
earthaccess.login(strategy="environment") # free Earthdata Login
W, S, E, N = -122.4, 38.3, -122.0, 38.7 # your land (NorCal wine country)
# 1. Search every 16-day MOD13A2 snapshot across one dry season over the box.
grans = earthaccess.search_data(short_name="MOD13A2",
temporal=("2024-05-01", "2024-10-31"),
bounding_box=(W, S, E, N))
paths = [str(p) for p in earthaccess.download(grans, local_path="/tmp/q43")]
def aoi_mean_ndvi(hdf):
"""Open one HDF-EOS tile, rescale + mask NDVI, clip the AOI, return (date, mean, n)."""
sd = SD(hdf, SDC.READ)
sds = sd.select("1 km 16 days NDVI")
dn = sds[:].astype("float32")
fill, scale = sds.attributes()["_FillValue"], sds.attributes()["scale_factor"] # -3000, 10000
# grid corners (sinusoidal meters) from the tile metadata -> pixel-centre lon/lat
sm = sd.attributes()["StructMetadata.0"]
ulx, uly = [float(v) for v in re.search(r"UpperLeftPointMtrs=\(([^)]+)\)", sm).group(1).split(",")]
lrx, lry = [float(v) for v in re.search(r"LowerRightMtrs=\(([^)]+)\)", sm).group(1).split(",")]
ny, nx = dn.shape
R = 6371007.181 # MODIS sinusoidal sphere radius (m)
xc = ulx + (np.arange(nx) + 0.5) * (lrx - ulx) / nx
yc = uly + (np.arange(ny) + 0.5) * (lry - uly) / ny
X, Y = np.meshgrid(xc, yc)
LAT = np.degrees(Y / R)
LON = np.degrees(X / (R * np.cos(np.radians(LAT))))
box = (LON >= W) & (LON <= E) & (LAT >= S) & (LAT <= N)
ndvi = np.where(dn == fill, np.nan, dn / scale)
vals = ndvi[box]
doy = re.search(r"\.A(\d{4})(\d{3})\.", os.path.basename(hdf)) # MOD13A2.AYYYYDDD.h08v05...
date = datetime.strptime(doy.group(1) + doy.group(2), "%Y%j")
return date, float(np.nanmean(vals)), int(np.isfinite(vals).sum())
# 2. Build the seasonal series: one mean-NDVI number per 16-day snapshot.
rows = sorted((aoi_mean_ndvi(p) for p in paths), key=lambda r: r[0])
dates = [r[0] for r in rows]
greens = np.array([r[1] for r in rows])
print(f"latest snapshot {dates[-1]:%Y-%m-%d}: mean AOI NDVI = {greens[-1]:.4f} "
f"({rows[-1][2]} valid 1 km pixels)")
# 3. Fit the dry-season decline: slope of NDVI vs day-of-year (negative = curing).
t = np.array([d.timetuple().tm_yday for d in dates], dtype=float)
slope, intercept = np.polyfit(t, greens, 1)
drop = greens[0] - greens[-1]
verdict = "drying out — fuel is curing" if slope < 0 else "still greening up"
print(f"VERDICT: land is {verdict}: NDVI {greens[0]:.3f} -> {greens[-1]:.3f} "
f"(down {drop:+.3f}, slope {slope*100:+.3f} NDVI/100 days)")
# 4. Plot the seasonal curing curve with its trend line.
plt.plot(dates, greens, "o-", label="AOI mean NDVI")
plt.plot(dates, intercept + slope * t, "--", c="orange", label="dry-season trend")
plt.ylabel("NDVI (greenness)"); plt.xlabel("2024"); plt.gcf().autofmt_xdate()
plt.title("Fuel dryness: NDVI through the dry season"); plt.legend(); plt.tight_layout(); plt.show()
# Cross-check before you trust it: compare this season's curve to the same 16-day
# windows in past years (is it genuinely drier than normal?), and remember NDVI is a
# fuel-dryness proxy, not a fire forecast (see the gotchas above).
Where the data comes from
Everything here is from NASA through one free login (Earthdata Login). MOD13A2 (greenness/NDVI), MOD11 (land surface temperature), and MOD16 (evapotranspiration) all come from MODIS, the long-running instrument flying on NASA’s Terra and Aqua satellites since 2000, distributed by the LP DAAC land-data archive. ECOSTRESS, a sharper evapotranspiration source, flies on the International Space Station and comes from the same archive. No separate accounts or paid data needed.
Sources
- MOD13A2 (MODIS NDVI) product: https://lpdaac.usgs.gov/products/mod13a2v061/
- MOD11A2 (MODIS land surface temperature): https://lpdaac.usgs.gov/products/mod11a2v061/
- MOD16A2 (MODIS evapotranspiration): https://lpdaac.usgs.gov/products/mod16a2v061/
- ECOSTRESS evapotranspiration: https://ecostress.jpl.nasa.gov/
Make it yours → Set the AOI, the 16-day composite/season, and the baseline years plus anomaly threshold in the notebook.
A safe place to practise the method on MOD13A2. 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.