q43·advanced

How primed for wildfire is the land around me right now?

firedroughtbiospherepublic-health Datasets: 3 45–90 min

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):

  1. 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).
  2. 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).
  3. 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.
  4. 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 4126 really means 0.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 pyhdf library 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

How a scientist answers this
Parameters
Vegetation greenness/dryness from MODIS MOD13A2 NDVI (integer × 0.0001 → NDVI, 1 km, 16-day) as a fuel-curing proxy, paired with MOD11 LST surface heat and MOD16/ECOSTRESS ET plant water stress; the danger diagnostic is a strongly negative NDVI anomaly versus a per-composite multi-year climatology.
Method
Build a per-pixel, per-16-day-composite NDVI climatology from past years, express the current composite as a standardized anomaly (z-score), and flag deeply negative anomalies as unusually cured fuel; combine with high MOD11 LST and elevated MOD16/ECOSTRESS evaporative stress for a multi-variable dryness picture.
Validation
State the climatology baseline, apply MOD13/MOD11 QA flags and drop cloud-contaminated pixels, and treat the result as a fuel-dryness proxy—cross-check against fire-weather indices (e.g. NFDRS) and agency forecasts rather than calling it a fire forecast.
In plain EnglishCompare this season's greenness to the normal range for the same time of year, and flag hillsides that are unusually dry and hot as primed-to-burn fuel.

Make it yours → Set the AOI, the 16-day composite/season, and the baseline years plus anomaly threshold in the notebook.

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

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.

editable · runs in your browser
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.