Composite Drought Index via GEE + COG output + climate-science plots
Project description
drought-monitoring
A Python package for computing the Composite Drought Index (CDI) over 20–30 year monitoring periods using Google Earth Engine, with in-memory dask-parallelised spatial computation, Cloud Optimized GeoTIFF export, and publication-quality climate science visualisations.
Installation
# pip
pip install drought-monitoring
# uv
uv add drought-monitoring
# with all optional dependencies (GEE + COG + plots)
pip install "drought-monitoring[all]"
Optional extras:
| Extra | Installs | Use for |
|---|---|---|
gee |
earthengine-api |
fetching GEE data |
cog |
rasterio, rioxarray |
COG export / import |
plot |
matplotlib |
visualisation |
all |
all of the above | full workflow |
Package structure
drought_monitoring/
├── core.py CDI mathematics on pd.Series
├── spatial.py pixel-wise computation on xr.DataArray (dask-parallelised)
├── gee.py GEE authentication + ERA5-Land / MODIS xee cube fetching
├── io.py Cloud Optimized GeoTIFF (COG) export and import
└── plot.py publication-quality climate science figures
tests/
└── test_core.py
pyproject.toml
README.md
Typical Jupyter notebook workflow
See workshop/drought_monitoring_TUK_workshop.ipynb for the full worked example (Turkana County, Kenya, 2000–2025).
1. Authenticate GEE
from drought_monitoring.gee import authenticate
authenticate(project="my-gee-project") # opens browser on first use
2. Define your Area of Interest
# Format: [lon_min, lat_min, lon_max, lat_max]
aoi = [34.5, 1.5, 36.5, 5.5] # Turkana County, Kenya
3. Fetch 25 years of monthly data
from drought_monitoring.gee import fetch_era5_precip, fetch_era5_temp, fetch_modis_ndvi
precip = fetch_era5_precip(aoi, start_year=2000, end_year=2025)
temp = fetch_era5_temp(aoi, start_year=2000, end_year=2025)
ndvi = fetch_modis_ndvi(aoi, start_year=2000, end_year=2025)
# Each returns a pd.Series with a monthly DatetimeIndex (312 months)
4. Compute the full CDI
from drought_monitoring import compute_all
df = compute_all(precip, temp, ndvi, window=3, weights=(0.50, 0.25, 0.25))
# pd.DataFrame with columns: PDI, TDI, VDI, CDI
5. Classify drought severity
from drought_monitoring.plot import classify_cdi
df["severity"] = classify_cdi(df["CDI"])
print(df["severity"].value_counts(normalize=True).mul(100).round(1))
6. Publication-quality plots
from drought_monitoring.plot import plot_timeseries, plot_anomaly_bars, plot_seasonal_cycle
import matplotlib.pyplot as plt
fig = plot_timeseries(
df,
title = "Composite Drought Index — Turkana County, Kenya",
subtitle = "ERA5-Land + MODIS MOD13A3 | 2000–2025",
show_components = True,
show_severity_bar = True,
)
fig.savefig("CDI_timeseries_Turkana.png", dpi=150, bbox_inches="tight")
fig2 = plot_anomaly_bars(df, column="CDI", freq="Y",
title="Annual Mean CDI Anomaly — Turkana County")
fig2.savefig("CDI_annual_Turkana.png", dpi=150, bbox_inches="tight")
fig3 = plot_seasonal_cycle(df, title="Seasonal Cycle — Turkana 2000–2025")
fig3.savefig("CDI_seasonal_Turkana.png", dpi=150, bbox_inches="tight")
7. Generate annual spatial CDI maps
from drought_monitoring.gee import yearly_drought_maps
# Streams ERA5 + MODIS via xee, computes CDI pixel-wise with dask,
# resamples to annual means — nothing written to disk
ds = yearly_drought_maps(aoi, start_year=2000, end_year=2025)
# xr.Dataset with variables: PDI, TDI, VDI, CDI | dims: (time, lat, lon)
ds["CDI"].plot(col="time", col_wrap=4, cmap="RdBu", robust=True, figsize=(18, 14))
8. Export to Cloud Optimized GeoTIFFs
import os
from drought_monitoring.io import cdi_stack_to_cog
os.makedirs("outputs", exist_ok=True)
paths = cdi_stack_to_cog(ds, output_dir="outputs/", prefix="Turkana_2000_2025")
# outputs/Turkana_2000_2025_PDI.tif (26 bands, one per year)
# outputs/Turkana_2000_2025_TDI.tif
# outputs/Turkana_2000_2025_VDI.tif
# outputs/Turkana_2000_2025_CDI.tif
9. Visualise COGs interactively
import leafmap
m = leafmap.Map(center=[3.5, 35.5], zoom=7)
m.add_cog_layer("outputs/Turkana_2000_2025_CDI.tif", name="CDI")
m
10. Run a 6-month drought forecast
from drought_monitoring.forecast import forecast_all_statistical
from drought_monitoring.plot import plot_forecast
fc = forecast_all_statistical(
precip, temp, ndvi,
n_months = 6,
weights = (0.50, 0.25, 0.25),
ci_level = 0.90,
)
# pd.DataFrame with columns: PDI, TDI, VDI, CDI, CDI_lower, CDI_upper, lead
fig_fc = plot_forecast(
history = df,
forecast = fc,
n_history = 36,
title = "Turkana CDI — 6-month Drought Outlook",
subtitle = "STL + SARIMA statistical forecast | 90% confidence band",
show_components = True,
)
fig_fc.savefig("CDI_forecast_Turkana.png", dpi=150, bbox_inches="tight")
Area-averaged time series workflow
For a single-pixel or spatially-averaged CDI time series:
from drought_monitoring.gee import fetch_era5_precip, fetch_era5_temp, fetch_modis_ndvi
from drought_monitoring import compute_all
from drought_monitoring.plot import plot_timeseries, plot_anomaly_bars
precip = fetch_era5_precip(aoi, start_year=2000, end_year=2020)
temp = fetch_era5_temp(aoi, start_year=2000, end_year=2020)
ndvi = fetch_modis_ndvi(aoi, start_year=2000, end_year=2020)
df = compute_all(precip, temp, ndvi, window=3)
# pd.DataFrame with columns: PDI, TDI, VDI, CDI
fig = plot_timeseries(df, title="CDI — Borena Region",
subtitle="ERA5-Land + MODIS MOD13A3 | 2000–2020")
fig.savefig("CDI_timeseries.png", dpi=300, bbox_inches="tight")
fig2 = plot_anomaly_bars(df, title="Annual Mean CDI Anomaly")
fig2.savefig("CDI_annual.png", dpi=300, bbox_inches="tight")
Data sources
| Variable | GEE collection | Band | Units |
|---|---|---|---|
| Precipitation | ECMWF/ERA5_LAND/MONTHLY_AGGR |
total_precipitation_sum |
mm month⁻¹ |
| Temperature | ECMWF/ERA5_LAND/MONTHLY_AGGR |
temperature_2m |
°C |
| NDVI | MODIS/061/MOD13A3 |
NDVI |
[−1, 1] |
CDI formula
Each sub-index follows Burchard-Levine et al. (2024):
DI = (μ_IP / μ_LTM) × sqrt(RL_IP / RL_LTM)
CDI = 0.50 × PDI + 0.25 × TDI + 0.25 × VDI
| Symbol | Meaning |
|---|---|
μ_IP |
Trailing rolling mean over the interest period (default 3 months) |
μ_LTM |
Long-term mean of μ_IP for that calendar month |
RL_IP |
Count of months inside the IP where the anomaly condition holds |
RL_LTM |
Long-term mean of RL_IP for that calendar month |
PDI & VDI use deficit streaks (raw < monthly LTM).
TDI uses excess streaks (raw > monthly LTM).
CDI severity classes
| CDI value | Class |
|---|---|
| < 0.50 | Extreme drought |
| 0.50 – 0.65 | Severe drought |
| 0.65 – 0.80 | Moderate drought |
| 0.80 – 0.90 | Mild drought |
| 0.90 – 1.10 | Near normal |
| 1.10 – 1.20 | Mild wet |
| 1.20 – 1.30 | Moderately wet |
| > 1.30 | Very wet |
Values < 1 indicate drought; values ≈ 1 are near-normal; values > 1 are wetter than normal.
Monitoring period
| Default | Minimum | Maximum |
|---|---|---|
| 20 years | 20 years | 30 years |
An error is raised if the requested period is outside this range.
COG structure
Each output GeoTIFF contains one band per timestep.
Band descriptions are ISO-8601 date strings (YYYY-MM or YYYY), so every
file is self-documenting and can be range-requested from cloud storage
(S3, GCS, Azure Blob) by leafmap, QGIS, or any GDAL tool.
Running tests
pytest tests/ -v
Reference
Based on: Burchard-Levine, V. et al. (2024). pyCDI: a Python implementation of the composite drought index. EO-Africa R&D, ESA. https://github.com/VicenteBurchard/pyCDI
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drought_monitoring-0.1.7.tar.gz.
File metadata
- Download URL: drought_monitoring-0.1.7.tar.gz
- Upload date:
- Size: 36.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2369040a5ae6aa057a918f32fd39b515f59994bef0198746dfaefed6300683e4
|
|
| MD5 |
b194da50c7e8f7acd904e053802f99b2
|
|
| BLAKE2b-256 |
6d3adc5af9b87dd3699d7e504ff4618997dffb5ba0e5e78e83a5efff2e560368
|
File details
Details for the file drought_monitoring-0.1.7-py3-none-any.whl.
File metadata
- Download URL: drought_monitoring-0.1.7-py3-none-any.whl
- Upload date:
- Size: 34.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff1d18e63244f3b4333cd0014af2d7a68c47f4878242990955db77abcef6d485
|
|
| MD5 |
025005968acf25c3c96af834a027ceef
|
|
| BLAKE2b-256 |
b9d942ec0d8f6f5c6944fdd474c1aca46d30d0f297e81a1da387ce3369bb6c5e
|