edmt.workflow

Submodules

Package Contents

edmt.workflow.gdf_to_ee_geometry(gdf: geopandas.GeoDataFrame) → ee.Geometry
edmt.workflow.ee_to_points(image: ee.Image, scale: int = 30, num_pixels: int = 5000) → geopandas.GeoDataFrame

Sample pixel values from an Earth Engine image and return them as a GeoDataFrame.

This function extracts a uniform random subset of pixels from the input ee.Image at a specified spatial resolution. Each sampled pixel is converted to a point geometry with its corresponding band values stored as attributes. The resulting data is downloaded synchronously and formatted as a geopandas.GeoDataFrame with WGS84 (EPSG:4326) projection.

Parameters:
  • image (ee.Image) -- The input Earth Engine image to sample. Must be a valid, initialized Earth Engine image object.

  • scale (int, optional) -- The nominal scale in meters at which to sample the image. Defaults to 30. Should closely match the native resolution of the target bands for accurate value extraction.

  • num_pixels (int, optional) -- The maximum number of pixels to sample. Defaults to 5000. Earth Engine will return up to this number (or fewer if the image contains fewer valid/unmasked pixels).

Returns:

A GeoDataFrame where each row represents a sampled pixel.

Columns include the point geometry (named geometry) and one column per image band containing the sampled values. The coordinate reference system (CRS) is explicitly set to EPSG:4326.

Return type:

gpd.GeoDataFrame

Raises:

ee.EEException -- If the image is invalid, the scale is unsupported, Earth Engine computation times out, or the response payload exceeds the getInfo() limit.

Example

>>> import ee
>>> import geopandas as gpd
>>> ee.Initialize()
>>> img = ee.Image('COPERNICUS/S2_SR/20230615T123456').select(['B4', 'B8'])
>>> gdf = ee_to_points(img, scale=10, num_pixels=1000)
>>> print(gdf.head())
>>> print(gdf.crs)  # EPSG:4326
edmt.workflow.get_landcover(roi_gdf: geopandas.GeoDataFrame) → ee.Image

Retrieve the ESA land cover classification image clipped to a specified region of interest.

This function serves as a convenience wrapper around _build_landcover(), pre-configured to use the "ESA" land cover product. It extracts the primary land cover image from the configured Earth Engine collection, selects the classification band(s), and returns the result clipped to the provided ROI.

Parameters:

roi_gdf (gpd.GeoDataFrame) -- GeoDataFrame defining the region of interest. Must contain at least one valid geometry and be compatible with edmt.workflow.gdf_to_ee_geometry().

Returns:

A single-band Earth Engine image containing the ESA land cover

classification values, spatially clipped to the ROI. Pixel values correspond to the standard ESA land cover class indices.

Return type:

ee.Image

Raises:
  • ValueError -- If the "ESA" land cover configuration is missing from _SAT_CONFIG.

  • Exception -- Propagates geometry conversion, collection access, or Earth Engine clipping errors.

Example

>>> import geopandas as gpd
>>> roi = gpd.read_file("path/to/study_area.gpkg")
>>> lc_img = get_landcover(roi)
>>> # Trigger server-side computation and inspect metadata
>>> print(lc_img.bandNames().getInfo())
edmt.workflow.get_soilmoisture(roi_gdf: geopandas.GeoDataFrame) → ee.Image

Retrieve the ISRIC SoilGrids soil moisture image clipped to a specified region of interest.

Convenience wrapper around _build_soilmoisture() pre-configured for ISRIC's SoilGrids v2.0 product. Returns high-resolution static soil moisture estimates (mean 5–15 cm depth) for the ROI.

Parameters:

roi_gdf (gpd.GeoDataFrame) -- GeoDataFrame defining the region of interest. Must be compatible with edmt.workflow.gdf_to_ee_geometry().

Returns:

Single-band Earth Engine image containing SoilGrids soil moisture

values (val_5_15cm_mean), spatially clipped to the ROI. Values are in grams of water per kilogram of soil (g/kg).

Return type:

ee.Image

edmt.workflow.compute_evi_timeseries(start_date: str, end_date: str, satellite: str = 'Sentinel2', frequency: str = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, scale: int | None = None) → pandas.DataFrame
edmt.workflow.compute_lst_timeseries(start_date: str, end_date: str, satellite: str = 'MODIS', frequency: str = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, scale: int | None = None) → pandas.DataFrame
edmt.workflow.compute_ndvi_timeseries(start_date: str, end_date: str, satellite: str = 'LANDSAT8', frequency: str = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, scale: int | None = None) → pandas.DataFrame
edmt.workflow.compute_chirps_timeseries(start_date: str, end_date: str, frequency: str = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, scale: int | None = None) → pandas.DataFrame
edmt.workflow.get_lst_image(start_date: str, end_date: str, satellite: str, roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'mean') → ee.Image
edmt.workflow.get_ndvi_image(start_date: str, end_date: str, satellite: str, roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'mean') → ee.Image
edmt.workflow.get_evi_image(start_date: str, end_date: str, satellite: str, roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'mean') → ee.Image
edmt.workflow.get_chirps_image(start_date: str, end_date: str, roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'max') → ee.Image
edmt.workflow.get_fire_incident_image(start_date: str, end_date: str, roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'max', min_confidence: int = 7, zones_only: bool = True) → ee.Image
edmt.workflow.get_lst_image_collection(start_date: str, end_date: str, satellite: str, frequency: edmt.workflow.builder.Frequency = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'mean') → ee.ImageCollection
edmt.workflow.get_ndvi_image_collection(start_date: str, end_date: str, satellite: str, frequency: edmt.workflow.builder.Frequency = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'mean') → ee.ImageCollection
edmt.workflow.get_evi_image_collection(start_date: str, end_date: str, satellite: str, frequency: edmt.workflow.builder.Frequency = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'mean') → ee.ImageCollection
edmt.workflow.get_chirps_image_collection(start_date: str, end_date: str, frequency: edmt.workflow.builder.Frequency = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'max') → ee.ImageCollection
edmt.workflow.get_fire_image_collection(start_date: str, end_date: str, frequency: edmt.workflow.builder.Frequency = 'monthly', roi_gdf: geopandas.GeoDataFrame | None = None, reducer: edmt.workflow.builder.ReducerName = 'max', min_confidence: int = 7, zones_only: bool = True) → ee.ImageCollection
edmt.workflow.create_ROI(latitude: float, longitude: float, extent_km: float = 100.0, name: str = 'AOI') → geopandas.GeoDataFrame

Create a square bounding box polygon centered on a WGS84 coordinate.

Parameters:
  • latitude (float) -- Center latitude in decimal degrees (WGS84 / EPSG:4326).

  • longitude (float) -- Center longitude in decimal degrees (WGS84 / EPSG:4326).

  • extent_km (float, optional) -- Full side length of the bounding box in kilometres (default: 100). e.g. 10 → 10 km × 10 km box centered on the coordinate.

  • name (str, optional) -- Label for the polygon feature (default: "AOI").

Returns:

Single-row GeoDataFrame (EPSG:4326) with columns: name, latitude, longitude, extent_km, geometry.

Return type:

gpd.GeoDataFrame

Notes

Degree-to-metre conversion uses the WGS84 approximation:

1° latitude ≈ 111 320 m (constant) 1° longitude ≈ 111 320 × cos(lat) m (varies with latitude)

edmt.workflow.classify_ndvi_seasons(df: pandas.DataFrame, date_col: str = 'date', ndvi_col: str = 'ndvi', threshold: float = None, threshold_method: str = 'discrete', wet_label: str = 'Wet', dry_label: str = 'Dry', agg_func: str = 'mean') → pandas.DataFrame

Aggregate NDVI observations to monthly means and classify each month as a wet or dry period based on a vegetation threshold.

Parameters:
  • df (pd.DataFrame) -- Input DataFrame containing at minimum a date and NDVI column.

  • date_col (str, optional) -- Name of the date column (default: "date").

  • ndvi_col (str, optional) -- Name of the NDVI column (default: "ndvi").

  • threshold (float, optional) -- Explicit NDVI cut-off on the true -1 to +1 scale. Overrides threshold_method when supplied. Months with mean NDVI >= threshold → wet; below → dry.

  • threshold_method (str, optional) --

    Auto-threshold strategy used when threshold is None:

    "discrete" – Ecological fixed threshold from the NDVI scale

    (NDVI_WET_THRESHOLD = 0.25 by default). Recommended — anchors the result to real-world vegetation meaning regardless of data range. (default)

    "otsu" – Maximises between-class variance within the data.

    Only meaningful when the data spans a wide dynamic range (e.g. > 0.15 spread). Avoid for narrow-band data.

    "median" – Median of monthly means. "mean" – Mean of monthly means.

  • wet_label (str, optional) -- Label for wet months (default: "Wet").

  • dry_label (str, optional) -- Label for dry months (default: "Dry").

  • agg_func (str, optional) -- Aggregation function applied per month: "mean" | "median" | "max" (default: "mean").

Returns:

Monthly DataFrame ordered chronologically with columns: year, month, month_name, ndvi_mean, threshold, season, ndvi_vegetation_class.

ndvi_vegetation_class → human-readable land-cover interpretation derived from the true NDVI scale (independent of the wet/dry split).

Return type:

pd.DataFrame

Notes

The "discrete" method (default) anchors classification to the actual NDVI scale (-1 to +1). Statistical methods (otsu/median/mean) find a threshold within the observed data range, which can produce misleading results when all values are clustered in a narrow band — e.g. labelling a month as "Wet" simply because its NDVI is 0.001 above the rest, even though 0.10 is objectively bare soil by any vegetation index standard.

edmt.workflow.classify_climate_seasons(df_ndvi: pandas.DataFrame, df_rainfall: pandas.DataFrame, df_lst: pandas.DataFrame, ndvi_date_col: str = 'date', ndvi_col: str = 'ndvi', rainfall_date_col: str = 'date', rainfall_col: str = 'precipitation_mm', lst_date_col: str = 'date', lst_col: str = 'mean', weights: Tuple[float, float, float] = (0.4, 0.35, 0.25), category_labels: List[str] | None = None, rainfall_gate_mm: float = 1.0, transition_gate_mm: float = 15.0) → pandas.DataFrame

Merge monthly NDVI, rainfall, and LST data, then classify each month into one of five climate seasons using a normalised composite score.

Composite score (0 = driest, 1 = wettest)

score = w_rain × rainfall_norm
  • w_ndvi × ndvi_norm

  • w_temp × (1 − temp_norm) ← inverted: high temp → dry

Rainfall gate (applied after scoring)

Prevents non-dry labels when rainfall is negligible, regardless of what NDVI or LST suggest:

rainfall_mm < rainfall_gate_mm → forced "Dry Season" rainfall_mm < transition_gate_mm → capped at "Dry-Wet Transition"

(only if score-based label is wetter)

param df_ndvi:

16-day or finer NDVI observations (aggregated to monthly mean).

type df_ndvi:

pd.DataFrame

param df_rainfall:

Weekly or finer precipitation observations (aggregated to monthly sum).

type df_rainfall:

pd.DataFrame

param df_lst:

Monthly or finer LST observations (aggregated to monthly mean).

type df_lst:

pd.DataFrame

param ndvi_date_col:

Date column in df_ndvi (default: "date").

type ndvi_date_col:

str

param ndvi_col:

NDVI value column (default: "ndvi").

type ndvi_col:

str

param rainfall_date_col:

Date column in df_rainfall (default: "date").

type rainfall_date_col:

str

param rainfall_col:

Precipitation column (default: "precipitation_mm").

type rainfall_col:

str

param lst_date_col:

Date column in df_lst (default: "date").

type lst_date_col:

str

param lst_col:

LST value column (default: "mean").

type lst_col:

str

param weights:

Relative importance of (rainfall, ndvi, temperature). Must sum to 1.0 (default: 0.40, 0.35, 0.25).

type weights:

tuple of 3 floats

param category_labels:

Custom season names ordered driest → wettest.

type category_labels:

list of 5 str, optional

param rainfall_gate_mm:

Monthly rainfall (mm) below which a month is forced to "Dry Season", regardless of NDVI or LST (default: 1.0 mm).

type rainfall_gate_mm:

float, optional

param transition_gate_mm:

Monthly rainfall (mm) below which a month is capped at "Dry-Wet Transition" if the score would place it in a wetter category (default: 5.0 mm).

type transition_gate_mm:

float, optional

returns:

Chronologically sorted monthly DataFrame with columns: year, month, month_name, rainfall_mm, ndvi_mean, lst_mean, composite_score, season, season_source.

season_source: "score" if the label came from the composite score,

"rainfall_gate" if it was overridden.

rtype:

pd.DataFrame