Vegetation Phenology

Vegetation phenology analysis from MODIS products via NASA EarthData. Requires NASA Earthdata credentials and pip install hyplan[phenology].

Vegetation phenology analysis for airborne remote sensing campaign planning.

Provides historical NDVI/EVI seasonality, LAI/FPAR, and phenological transition dates from MODIS products via NASA EarthData. Requires pip install hyplan[phenology] and NASA Earthdata credentials.

Supported products:

  • NDVI/EVI (MOD13A1/MYD13A1): 16-day composites at 500 m.

  • LAI/FPAR (MOD15A2H): 8-day composites at 500 m.

  • Phenology transitions (MCD12Q2): annual greenup, peak, senescence, and dormancy dates at 500 m.

fetch_phenology(polygon_file, product='ndvi', year_start=2003, year_stop=2022, satellite='terra', spatial_mode='mean', source='appeears')[source]

Fetch historical vegetation phenology data for flight planning.

Two data sources are available:

  • AppEEARS (source="appeears", default): Submits a point sample request to NASA’s AppEEARS service, which extracts time series server-side and returns only the values. Fast (minutes) but samples at polygon centroids rather than full spatial averaging.

  • Granule download (source="granules"): Downloads full MODIS HDF4 granules via earthaccess, clips to each polygon, and computes spatial statistics locally. Slower (downloads GBs of data) but provides full spatial coverage including pixel_stats mode.

Parameters

polygon_filestr

Path to a GeoJSON or shapefile with a Name column.

productstr

One of "ndvi", "evi", "lai", "fpar", "phenology". Default "ndvi".

year_startint

First year to include (default 2003).

year_stopint

Last year to include, inclusive (default 2022).

satellitestr

"terra", "aqua", or "combined" (Terra + Aqua merged). Only applies to ndvi/evi. Default "terra".

spatial_modestr

"mean" for a single spatial mean per polygon per timestep, or "pixel_stats" for mean/std/min/max/count. Default "mean". Ignored for product="phenology". "pixel_stats" requires source="granules".

sourcestr

"appeears" (default) for fast server-side extraction, or "granules" for full granule download with local processing.

Returns

pd.DataFrame

Schema depends on product:

  • ndvi/evi/lai/fpar (spatial_mode=”mean”): polygon_id, date, year, day_of_year, value

  • ndvi/evi/lai/fpar (spatial_mode=”pixel_stats”): polygon_id, date, year, day_of_year, value_mean, value_std, value_min, value_max, pixel_count

  • phenology: polygon_id, year, greenup_doy, midgreenup_doy, peak_doy, maturity_doy, midgreendown_doy, senescence_doy, dormancy_doy

Raises

HyPlanValueError

If the polygon file lacks a Name column, product is not recognized, or satellite is invalid.

HyPlanRuntimeError

If required packages are missing or authentication fails.

rtype:

DataFrame

Parameters:
  • polygon_file (str)

  • product (str)

  • year_start (int)

  • year_stop (int)

  • satellite (str)

  • spatial_mode (str)

  • source (str)

Return type:

DataFrame

fetch_phenology_spatial(polygon_file, product='ndvi', year_start=2003, year_stop=2022, satellite='terra')[source]

Compute per-pixel time-averaged vegetation index for each polygon.

Returns a dictionary mapping polygon name to an xarray DataArray with dimensions (latitude, longitude).

Return type:

dict[str, ‘xr.DataArray’]

Parameters:
  • polygon_file (str)

  • product (str)

  • year_start (int)

  • year_stop (int)

  • satellite (str)

Parameters

polygon_filestr

Path to a GeoJSON or shapefile with a Name column.

productstr

"ndvi" or "evi". Default "ndvi".

year_startint

First year (default 2003).

year_stopint

Last year (default 2022).

satellitestr

"terra", "aqua", or "combined".

Returns

dict[str, xarray.DataArray]

Polygon name to DataArray of mean vegetation index values.

summarize_phenology_by_doy(df, window=None)[source]

Compute a typical-year vegetation index profile per polygon.

Averages VI values across all years for each (polygon_id, day_of_year) pair, producing a single seasonal profile per polygon.

Return type:

DataFrame

Parameters:
  • df (DataFrame)

  • window (int | None)

Parameters

dfpd.DataFrame

DataFrame with columns polygon_id, year, day_of_year, value (as returned by fetch_phenology() with product="ndvi"/"evi"/"lai"/"fpar").

windowint or None

Optional rolling-mean window size (centered) applied to the per-polygon DOY mean. None disables smoothing.

Returns

pd.DataFrame

Columns: polygon_id, day_of_year, value_mean, value_std, value_count.

extract_phenology_stages(df)[source]

Compute mean and std of phenological transition DOYs per polygon.

Return type:

DataFrame

Parameters:

df (DataFrame)

Parameters

dfpd.DataFrame

DataFrame from fetch_phenology() with product="phenology". Must contain columns polygon_id, year, and all stage DOY columns.

Returns

pd.DataFrame

Columns: polygon_id plus {stage}_mean and {stage}_std for each transition stage.

plot_seasonal_profile(summary_df, ax=None, show_std=True, ylabel='NDVI', **kwargs)[source]

Line plot of mean vegetation index by DOY, one line per polygon.

Return type:

Axes

Parameters:
  • summary_df (DataFrame)

  • ax (Axes | None)

  • show_std (bool)

  • ylabel (str)

Parameters

summary_dfpd.DataFrame

Output of summarize_phenology_by_doy().

axplt.Axes or None

Matplotlib Axes to plot on. Created if None.

show_stdbool

If True, draw a shaded +/-1 std-dev band.

ylabelstr

Y-axis label (e.g. "NDVI", "EVI", "LAI").

**kwargs

Passed to ax.plot().

Returns

plt.Axes

plot_phenology_calendar(stages_df, ax=None)[source]

Gantt-style chart of phenological stages across months.

Each polygon gets a horizontal row with colored bars spanning from one transition to the next.

Return type:

Axes

Parameters:
  • stages_df (DataFrame)

  • ax (Axes | None)

Parameters

stages_dfpd.DataFrame

Output of extract_phenology_stages().

axplt.Axes or None

Matplotlib Axes to plot on. Created if None.

Returns

plt.Axes

plot_year_over_year_heatmap(df, polygon_id=None, ax=None, cmap='YlGn', vmin=0.0, vmax=1.0)[source]

Heatmap of vegetation index by DOY (x) and year (y).

Return type:

Axes

Parameters:
  • df (DataFrame)

  • polygon_id (str | None)

  • ax (Axes | None)

  • cmap (str)

  • vmin (float)

  • vmax (float)

Parameters

dfpd.DataFrame

DataFrame from fetch_phenology() with columns polygon_id, year, day_of_year, value.

polygon_idstr or None

Which polygon to plot. Required if multiple polygons exist. If None and only one polygon exists, uses that one.

axplt.Axes or None

Matplotlib Axes to plot on. Created if None.

cmapstr

Colormap name. Default "YlGn".

vmin, vmaxfloat

Color scale bounds.

Returns

plt.Axes

plot_cloud_phenology_combined(cloud_summary_df, phenology_summary_df, ax=None, layout='overlay')[source]

Combined cloud fraction and vegetation index seasonal plot.

Return type:

Figure | Axes

Parameters:
  • cloud_summary_df (DataFrame)

  • phenology_summary_df (DataFrame)

  • ax (Axes | None)

  • layout (str)

Parameters

cloud_summary_dfpd.DataFrame

Output of summarize_cloud_fraction_by_doy().

phenology_summary_dfpd.DataFrame

Output of summarize_phenology_by_doy().

axplt.Axes or None

For "overlay" mode, the Axes to use (created if None). Ignored for "side_by_side".

layoutstr

"overlay" (twin y-axes) or "side_by_side" (two panels).

Returns

plt.Axes or plt.Figure

plt.Axes for "overlay", plt.Figure for "side_by_side".