00

How Does a Computer Know If Rice Fields Are Getting Enough Water?

An interactive guide to irrigation_performance.py — a script that measures irrigation fairness using satellites, weather data, and math.

🌐

Satellites

Sentinel-2, Landsat, and ERA5-Land collect daily images and weather data over Java, Indonesia.

🍚

Rice Paddies

749 tertiary irrigation blocks in DI Klambu, Central Java — each needs water at the right time.

📊

Performance Scores

Three indices tell us: Is each block getting enough? Is water distributed fairly? Is delivery consistent?

💡
Why This Matters

Indonesia manages thousands of irrigation districts feeding millions of hectares of rice. Traditionally, performance was measured by sending field inspectors. This script replaces months of fieldwork with satellite data processed in minutes.

📦
Download Code & Data

Get the source code, Jupyter notebook, and study area data to follow along:

  • irrigation_performance.py — main analysis script
  • PUPR_IrrigationPerformance_5Oct25.ipynb — Jupyter notebook
  • data/klambu.gpkg — Klambu Irrigation Area boundaries (749 blocks)

View on GitHub →

01

The Question We're Answering

What "irrigation performance" actually means and why it's hard to measure.

Three Questions, Three Indices

Imagine you're the manager of an irrigation district with 749 rice paddies. You need to answer three questions every season:

SI
Satisfaction Index — "Is each block getting enough water?"

Compares actual water received (ETa) against what the crop needs (CWR). An SI of 0.83 means 83% of water needs are met.

CU
Uniformity (Christiansen's CU) — "Is water distributed fairly?"

Measures how evenly SI varies across blocks. CU > 0.85 means good distribution — no block is being starved while others get excess.

RI
Reliability Index — "Is delivery consistent week after week?"

Counts how many weeks each block had SI above a threshold. High RI means farmers can depend on the system.

The Satellite Shortcut

The script avoids physical measurement by using a clever proxy: if a rice plant is getting enough water, it grows well, which changes how it reflects sunlight. Satellites can see this from space.

🌈
Think of It Like a Health Checkup

A doctor doesn't measure every cell in your body. They check a few indicators (blood pressure, temperature) that correlate with overall health. This script checks satellite "vital signs" of rice paddies to assess irrigation health.

Quick Check

A district has CU = 0.60. What does this tell you?

02

Meet the Cast of Characters

The data sources, tools, and concepts that make this pipeline work.

Five Data Sources, One Script

🛰

Sentinel-2 & Landsat

Optical satellites that photograph Earth every few days. Used to detect when rice is transplanted via NDVI and NDWI peaks.

☁️

ERA5-Land

A global weather reanalysis dataset from ECMWF. Provides daily temperature, wind, radiation, and humidity to calculate ET0.

🌎

MODIS ETa

A satellite product that estimates actual evapotranspiration (ETa) — how much water crops actually used, not just how much they needed.

💻

Google Earth Engine

GEE is the cloud engine that processes all the satellite data without downloading terabytes to your laptop.

🌾

Klambu Tertiary Blocks

749 polygon boundaries representing individual irrigation units. Each block's performance is measured independently.

How They Connect

💬 Component Conversation: "Who does what?"
S2
Sentinel-2

I take photos of the fields every 5 days. I can tell you NDVI and NDWI — basically, how green and wet the crops are.

E5
ERA5-Land

I provide daily weather: temperature, wind, humidity, radiation. The script uses my data to calculate ET0 — how much water the atmosphere "wants" to pull from the ground.

MO
MODIS

I measure ETa — how much water plants actually used. If my number is lower than what they needed, the crop is stressed.

GE
Earth Engine

I'm the kitchen where all this gets cooked. Everyone sends data to me, I crunch the numbers per block, and send CSVs back to the script.

PY
irrigation_performance.py

I take all those CSVs and compute SI, Uniformity, and Reliability using pandas. Then I make charts so humans can understand the results.

?
0 / 5

Quick Check

You need to know the transplanting date for each rice block. Which data source helps?

03

The 10-Step Pipeline

How raw satellite data becomes irrigation performance scores.

Data Flow: From Space to Spreadsheet

💻
GEE Cloud
📄
CSV Files
📊
Pandas
🎨
Results
Click "Next Step" to trace the pipeline
Step 0 / 10

The Code Entry Point

CODE
def main():
    parser = argparse.ArgumentParser(...)
    parser.add_argument('--study-area', ...)
    parser.add_argument('--year', type=int)
    parser.add_argument('--output-dir', required=True)
    parser.add_argument('--local-only',
        action='store_true')
PLAIN ENGLISH

Define the main function — the starting point of the whole script.

Set up a command-line interface so the user can pass options when running the script.

Accept a GEE asset path for the irrigation district boundaries.

Accept a year number (like 2023).

Require an output folder path — where all results will be saved.

Add a flag: if present, skip all satellite downloading and only do local math on existing CSV files.

Quick Check

You already have the CSV files from a previous run. Which flag lets you skip the slow GEE download?

04

The Science: From Weather to Water Needs

How the script turns temperature and sunlight into crop water requirements.

The Penman-Monteith Equation (Simplified)

The script calculates ET0 using the Penman-Monteith equation — the FAO standard. Think of it as an "atmospheric thirst meter."

Reference Evapotranspiration ET0 = f(temperature, wind, humidity, radiation)
CODE
et0 = tmean.expression(
  '(0.408 * slope * (Rnet - 0) +'
  '(psy * (900/(tmean+273)))'
  '* ws * (es - ea)) /'
  '(slope + psy * (0.34*ws + 1))',
  {'slope': slope, 'Rnet': Rnet,
   'psy': psy, 'tmean': tmean,
   'ws': ws, 'es': es,
   'ea': ea}).rename('ET0')
PLAIN ENGLISH

Calculate ET0 using the FAO Penman-Monteith formula.

The top of the fraction: energy from sunlight (Rnet) combined with...

...the drying power of wind. es - ea is the "vapor pressure deficit" — how much drier the air is compared to saturation.

The bottom of the fraction adjusts for temperature response and wind effects.

Feed in all the weather variables from ERA5-Land.

Name the result "ET0" (in mm/day).

From ET0 to Satisfaction Index

The chain from weather to performance score has four links:

ET0
Atmosphere thirst
(mm/day)
×
Kc
Crop coefficient
(0.95 – 1.2)
=
CWR
Crop water need
(mm/day)
vs
ETa
Actual water used
(mm/day)
Satisfaction Index SI = (1.2 × ETa) / CWR

The 1.2 is an empirical correction — satellite ETa tends to be low in humid regions. Values above 1.0 are capped at 1.0 (you can't be more than 100% satisfied).

The Rice Kc Curve

Kc changes as rice grows. The script models a 110-day growth cycle:

CODE
for day in days:
    if day <= 10:
        kc = 1.05   # Initial stage
    elif day <= 40:
        kc = 1.05 + (1.15-1.05)*(day-10)/30
    elif day <= 80:
        kc = 1.15 + (1.2-1.15)*(day-40)/40
    else:
        kc = 1.2 + (0.95-1.2)*(day-80)/30
PLAIN ENGLISH

Go through each day of the 110-day rice cycle...

Days 1–10 (Initial): Seedlings just transplanted. Low water use. Kc = 1.05

Days 11–40 (Development): Plants growing fast. Kc rises from 1.05 to 1.15

Days 41–80 (Mid-season): Full canopy, heading & flowering. Peak water use. Kc = 1.15 to 1.20

Days 81–110 (Late season): Grain filling, drying down. Kc drops from 1.20 to 0.95

Quick Check

A block has ETa = 4 mm/day and CWR = 6 mm/day. What is the SI?

05

Uniformity & Reliability

Turning per-block SI into district-wide performance grades.

Christiansen's Uniformity Coefficient

Imagine 749 students taking an exam. The average score might be 80%, but if half scored 100% and half scored 60%, that's not uniform. CU captures this spread.

Uniformity CU = 1 − (∑|SIi − SImean|) / (n × SImean)
CODE
weekly_uniformity['uniformity'] = (
    1 - weekly_uniformity['std_SI']
    / weekly_uniformity['mean_SI']
).fillna(1).clip(0, 1)
PLAIN ENGLISH

Uniformity = 1 minus the coefficient of variation (std / mean).

If all blocks have identical SI, std = 0, so uniformity = 1 (perfect).

If any value is missing (NaN), treat it as perfect (1). Clamp between 0 and 1.

Reliability: Consistency Over Time

A water supply that works 9 out of 10 weeks is more reliable than one that works 5 out of 10, even if both deliver the same total water. Farmers need predictability.

CODE
reliability['reliability'] = (
    reliability['weeks_above']
    / reliability['total_weeks']
).clip(0, 1)
PLAIN ENGLISH

For each block: count weeks where SI was above the threshold (default: 50%).

Divide by total weeks in the season.

Result: fraction of weeks the block received "adequate" water. 1.0 = never missed a week.

💡
Real Results from DI Klambu

The script found: Mean SI = 0.83 (83% of water needs met), CU = 0.93 (very fair distribution), RI = 0.98 (98% of weeks adequate). All 26 weeks had CU above the 0.85 target. Klambu is a well-performing district!

Quick Check

Block A has mean SI = 0.9 with RI = 0.5. Block B has mean SI = 0.7 with RI = 1.0. Which block are farmers happier with?

06

Running It Yourself

Three modes, real commands, and what to expect.

Three Ways to Run

1
Full Pipeline

Downloads everything from GEE + processes locally. Takes 30–60 minutes. Use for first run.

python irrigation_performance.py --output-dir results/klambu_2023 --year 2023
2
Skip GEE (--skip-gee)

Initializes GEE but uses existing CSVs for processing. Use when you've already downloaded but want to recompute.

3
Local Only (--local-only)

No GEE at all. Pure pandas processing. Takes seconds. Use for re-analysis with different parameters.

python irrigation_performance.py --output-dir results/klambu_2023 --local-only

What You Get

📈

CSV Results

uniformity_block_results.csv, reliability_block_results.csv — one row per tertiary block with performance scores.

📊

Charts

SI time series, uniformity trends, reliability histograms — ready for reports and presentations.

📝

Summary Report

performance_summary.txt — overall metrics, targets met, and block counts at a glance.

🎓
Congratulations!

You now understand how irrigation_performance.py turns satellite data into irrigation performance scores. The same approach can be applied to any irrigation district in Indonesia — just change the --study-area GEE asset path.