Metadata-Version: 2.4
Name: drought-monitoring
Version: 0.1.5
Summary: Composite Drought Index via GEE + COG output + climate-science plots
Author: geoges
License: MIT
Project-URL: Homepage, https://github.com/geoges/drought-monitoring
Project-URL: Bug Tracker, https://github.com/geoges/drought-monitoring/issues
Keywords: drought,CDI,google-earth-engine,xarray,remote-sensing
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: dask>=2024.8.0
Requires-Dist: leafmap>=0.61.1
Requires-Dist: localtileserver>=0.10.6
Requires-Dist: numpy>=1.21
Requires-Dist: pandas>=1.3
Requires-Dist: rasterio>=1.2
Requires-Dist: scipy>=1.13.1
Requires-Dist: statsmodels>=0.14.6
Requires-Dist: xarray>=0.19
Requires-Dist: xee>=0.0.22
Provides-Extra: gee
Requires-Dist: earthengine-api>=0.1.370; extra == "gee"
Provides-Extra: cog
Requires-Dist: rasterio>=1.2; extra == "cog"
Requires-Dist: rioxarray>=0.9; extra == "cog"
Provides-Extra: plot
Requires-Dist: matplotlib>=3.5; extra == "plot"
Provides-Extra: all
Requires-Dist: earthengine-api>=0.1.370; extra == "all"
Requires-Dist: rasterio>=1.2; extra == "all"
Requires-Dist: rioxarray>=0.9; extra == "all"
Requires-Dist: matplotlib>=3.5; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: matplotlib>=3.5; extra == "dev"
Requires-Dist: rasterio>=1.2; extra == "dev"
Requires-Dist: rioxarray>=0.9; extra == "dev"

# 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.

[![PyPI version](https://img.shields.io/pypi/v/drought-monitoring)](https://pypi.org/project/drought-monitoring/)
[![Python](https://img.shields.io/pypi/pyversions/drought-monitoring)](https://pypi.org/project/drought-monitoring/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

---

## Installation

```bash
# 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](workshop/drought_monitoring_TUK_workshop.ipynb) for the full worked example (Turkana County, Kenya, 2000–2025).

### 1. Authenticate GEE

```python
from drought_monitoring.gee import authenticate
authenticate(project="my-gee-project")   # opens browser on first use
```

### 2. Define your Area of Interest

```python
# 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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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:

```python
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

```bash
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>
