Remote-Sensing Phenology

NOVA Invasives

Detecting kudzu, phragmites, and other invasive species in Northern Virginia by exploiting their phenological signatures in Sentinel-2 time series — all processed on Google Earth Engine.

Study Area

5 Monitoring Sites

Click each marker to see the invasive species, GEE coordinate code, and detection signature. The dashed green box is the bounding box for the full study area.

Loading map…
Kudzu hotspotEnglish Ivy hotspotPhragmites wetlandNative forest (control)

Why It Works

How can a satellite see invasive plants?

The key is phenological asynchrony: invasive and native plants leaf out and senesce on different schedules, and that timing gap is clearly visible in satellite time series.

What is NDVI?

NDVI = (NIR − RED) / (NIR + RED)

Plant leaves absorb red light for photosynthesis and strongly reflect near-infrared. NDVI normalizes that ratio to [-1, +1] — higher values mean denser vegetation. Sentinel-2 uses B8 (NIR) and B4 (Red).

Water< 0
Bare soil0–0.2
Sparse vegetation0.2–0.5
Dense vegetation> 0.5

Detection Windows

Early Spring (Mar–Apr) · Late Autumn (Oct–Nov)

Invasive shrubs leaf out 2–3 weeks earlier and drop leaves 2–3 weeks later than native deciduous trees. During those windows, invasives show much higher NDVI than surrounding native vegetation — a detectable signal.

Early spring windowMar–Apr
Summer peakJun–Aug
Late autumn windowOct–Nov
Winter (evergreen anomaly)Dec–Feb
01_ndvi_concept.png
NDVI concept

Simulation

Phenology Curve Comparison

Synthetic data confirming the core hypothesis: the orange (early spring) and brown (late autumn) bands mark the windows where invasive vs. native NDVI differs most.

02_phenology_comparison.png
Phenology comparison

Kudzu

Pueraria montana

Anomalously high summer NDVI (>0.8)

Year-round

Autumn Olive / Bush Honeysuckle

Elaeagnus / Lonicera

Leafs out 2-3 weeks earlier than natives

Mar–Apr

Phragmites

Phragmites australis

Persistently high NIR reflectance

Late summer

English Ivy

Hedera helix

High winter NDVI (evergreen)

Dec–Feb

Real Data

NDVI Time Series — 5 Sites (Sentinel-2 measurements)

Real data from Google Earth Engine: 360 monthly observations across 2019–2024. Switch to 'Monthly phenology' for multi-year averages, or 'Full time series' for raw monthly values. Toggle sites via the legend.

Loading NDVI data…

Source: COPERNICUS/S2_SR_HARMONIZED · 500 m buffer · monthly median composite · cloud cover < 30%

Classification

Automated Phenological Classification

A rule-based classifier (no training data needed) evaluates four seasonal NDVI features per site. Rules encode ecological priors: early leaf-out, late senescence, winter evergreen, summer explosion — any one exceeding the native baseline flags the site as invasive.

Dyke Marsh — Phragmites

Native vegetation
  • All metrics close to the native control

Spring

0.388

Summer

0.659

Autumn

0.504

Winter

0.316

Great Falls — Kudzu

Native vegetation
  • All metrics close to the native control

Spring

0.250

Summer

0.573

Autumn

0.415

Winter

0.199

Huntley Meadows — Phragmites

Deciduous invasive shrub (Autumn Olive / Honeysuckle-type)
  • Early-spring + +0.16 above control (early leaf-out)
  • Late-autumn + +0.17 above control (delayed senescence)

Spring

0.525

Summer

0.847

Autumn

0.672

Winter

0.388

Native Forest (Control)

Native vegetation
  • All metrics close to the native control

Spring

0.363

Summer

0.559

Autumn

0.497

Winter

0.318

Scotts Run — Ivy

Native vegetation
  • All metrics close to the native control

Spring

0.458

Summer

0.625

Autumn

0.542

Winter

0.385

05_phenology_signatures.png
Seasonal NDVI signatures
06_classification.png
Classification summary

⚠️ Interpreting the Real-Data Results

Only Huntley Meadows was flagged as invasive in this run. Other known hotspots (kudzu at Great Falls, ivy at Scotts Run, phragmites at Dyke Marsh) were missed — likely because the 500 m buffer averages invasive patches with surrounding native vegetation. Next steps: (1) shrink the buffer to 100 m; (2) sample finer-grained locations; (3) add texture and SWIR bands.

Project Scripts

Analysis Pipeline

Four Python scripts form a complete analysis pipeline, one stage each.

01

Understand NDVI

01_explore_ndvi_concept.py

Plots the NDVI definition and simulated phenology curves for invasive vs. native plants.

Why this way: Before pulling real satellite data, make sure we know what we're measuring. No GEE account required.

Output: 01_ndvi_concept.png · 02_phenology_comparison.png · 03_study_area_map.html

02

Extract real NDVI via GEE

02_gee_ndvi_timeseries.py

Connects to Google Earth Engine and pulls monthly NDVI time series (2019–2024) for 5 sites from Sentinel-2.

Why this way: Sentinel-2 overpasses every 5 days — hundreds of scenes over 6 years. GEE parallelizes this in the cloud; nothing is downloaded locally.

Output: ndvi_timeseries_nova.csv · 04_gee_ndvi_timeseries.png

03

Download Landsat via STAC

03_download_landsat_stac.py

Pulls raw Landsat 8/9 bands via the USGS STAC API and computes seasonal NDVI maps locally.

Why this way: STAC needs no GEE account — useful fallback. Landsat 30 m is suited for regional-scale analysis.

Output: data/*.tif · outputs/ndvi_*.png

04

Phenological classification

04_phenology_analysis.py

Uses early-spring / late-autumn NDVI deltas as features in a rule-based classifier; exports JSON for the dashboard.

Why this way: This is the project goal: turn time-series signals into a yes/no invasive flag per location.

Output: phenology_features.csv · classification.json · 05/06_*.png

GEE core workflow (02_gee_ndvi_timeseries.py)
# 1. Load the Sentinel-2 dataset (lazy — nothing is downloaded yet)
collection = (
    ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
    .filterBounds(roi)              # (1) keep only scenes over NOVA
    .filterDate("2022-03-01", "2022-04-30")  # (2) early-spring window
    .filter(ee.Filter.lt("CLOUDY_PIXEL_PERCENTAGE", 30))  # (3) drop cloudy scenes
)

# 2. Compute NDVI on every image (runs in parallel on GEE servers)
def add_ndvi(image):
    return image.addBands(
        image.normalizedDifference(["B8", "B4"]).rename("NDVI")
    )

# 3. Composite + extract a single value (this is where the actual call happens)
ndvi_median = collection.map(add_ndvi).select("NDVI").median()
stats = ndvi_median.reduceRegion(
    reducer=ee.Reducer.mean(),
    geometry=roi,    # 500 m buffer
    scale=10,        # Sentinel-2 native resolution
).getInfo()          # ← only this line triggers actual GEE computation

Technology

Sentinel-2 SRLandsat 8/9NDVI Time SeriesPhenology DetectionGEE Python API

Sources: Copernicus Sentinel-2 · USGS Landsat · EDDMapS · iNaturalist

Study area: Northern Virginia (38.70°N–39.00°N, 77.00°W–77.55°W)