Hydrology & Water Mapping

Water covers about 71% of Earth's surface, and knowing exactly where it is (and where it isn't) matters more than you might think. Floods, droughts, reservoir management, climate science: they all depend on accurate water maps. In this module, we'll learn to spot and outline water bodies from satellite imagery using spectral indices.

Learning objectives

  • Calculate NDWI and MNDWI for water detection.
  • Apply thresholds to delineate water bodies from index images.
  • Compare the performance of NDWI and MNDWI in different environments.
  • Calculate water surface area using pixelArea() and reduceRegion().

Why it matters

Floods are the most common and costly natural disaster on the planet. Droughts threaten food and water security for billions of people. Here's where remote sensing gets powerful: satellite-based water mapping lets us monitor reservoirs, track seasonal flooding, and measure long-term changes in water availability, all without setting foot in the field.

Key vocabulary

NDWI
Normalized Difference Water Index. Uses NIR and SWIR bands to detect vegetation water content and open water. Formula: (NIR - SWIR) / (NIR + SWIR).
MNDWI
Modified Normalized Difference Water Index. Uses Green and SWIR bands for stronger open water detection. Formula: (Green - SWIR) / (Green + SWIR).
Thresholding
Setting a cutoff value on a continuous index to create a binary classification (water vs. not water).
pixelArea()
An Earth Engine function that returns an image where each pixel's value equals its area in square meters, accounting for projection distortion.

Your First Water Map: Lake Okeechobee in 12 Lines

Lake Okeechobee is the largest freshwater lake in Florida and a critical piece of the Everglades ecosystem. Let's map it in under 15 lines of code using the Modified Normalized Difference Water Index (MNDWI) from Sentinel-2. Let's see it in action.

// Map Lake Okeechobee using MNDWI from Sentinel-2
var lake = ee.Geometry.Point([-80.83, 26.95]);

var s2 = ee.ImageCollection('COPERNICUS/S2_SR')
  .filterBounds(lake)
  .filterDate('2023-03-01', '2023-03-31')
  .sort('CLOUDY_PIXEL_PERCENTAGE')
  .first()
  .divide(10000); // scale to reflectance

// MNDWI = (Green - SWIR) / (Green + SWIR)
var mndwi = s2.normalizedDifference(['B3', 'B11']).rename('MNDWI');

// Create a binary water mask using threshold > 0
var waterMask = mndwi.gt(0);

Map.centerObject(lake, 11);
Map.addLayer(mndwi, {min: -0.5, max: 0.8, palette: ['brown', 'white', 'cyan', 'blue']}, 'MNDWI');
Map.addLayer(waterMask.selfMask(), {palette: ['0000FF']}, 'Water Mask');

What you should see

Two layers: a continuous MNDWI image where water glows bright blue and land appears brown, and a binary water mask that cleanly outlines Lake Okeechobee in solid blue. Toggle between them in the Layers panel. Congratulations, you just built a water map!

What Can We Do with a Water Map?

Think about it: flood responders need rapid maps of inundated areas to know where to send boats. Water resource managers track reservoir levels to plan irrigation and city water supply. Climate scientists measure how lakes and wetlands are shrinking or expanding over decades.

Traditional water monitoring relies on ground-based gauges and manual surveys. Both are expensive, and both only cover a tiny slice of the landscape. Satellite-based approaches give us synoptic, repeatable coverage of entire watersheds at no additional cost per observation. That's a game changer.

  • Floods: Map inundation extent within hours of satellite overpass.
  • Drought: Track shrinking reservoirs and disappearing lakes over time.
  • Water resources: Monitor irrigation storage, wetland health, and river width.
  • Climate change: Document long-term trends in surface water availability.

Two Indices, One Goal: NDWI vs. MNDWI

Both indices exploit the same physical fact: water absorbs strongly in the shortwave infrared (SWIR), creating contrast with visible bands. The key difference? Which visible band we pair with SWIR.

NDWI (Gao, 1996)

Formula: (NIR - SWIR) / (NIR + SWIR)
Sentinel-2 bands: (B8 - B11) / (B8 + B11)

NDWI was originally designed to measure vegetation water content. It works for detecting open water, but here's the catch: built-up surfaces (think rooftops and parking lots) also produce positive NDWI values. That can fool your water map.

MNDWI (Xu, 2006)

Formula: (Green - SWIR) / (Green + SWIR)
Sentinel-2 bands: (B3 - B11) / (B3 + B11)

MNDWI swaps NIR for Green, which suppresses built-up features and enhances open water contrast. For most water body mapping tasks, MNDWI outperforms NDWI. Think of it as the upgraded version.

// Compare NDWI and MNDWI side by side
var ndwi = s2.normalizedDifference(['B8', 'B11']).rename('NDWI');
var mndwi = s2.normalizedDifference(['B3', 'B11']).rename('MNDWI');

var waterPalette = {min: -0.5, max: 0.8, palette: ['brown', 'white', 'cyan', 'blue']};
Map.addLayer(ndwi, waterPalette, 'NDWI');
Map.addLayer(mndwi, waterPalette, 'MNDWI');

When to use which

  • Use MNDWI for mapping open water bodies (lakes, reservoirs, rivers).
  • Use NDWI when you also care about vegetation moisture stress.
  • In urban-water mixed areas, MNDWI is strongly preferred because it suppresses built-up noise.

Drawing the Line: Thresholding for Water Detection

A spectral index gives us continuous values (typically from -1 to 1), but a water map is categorical: each pixel is either water or not water. Thresholding is how we draw that line. Think of it like setting a passing grade on an exam.

The simplest threshold for MNDWI is zero. Pixels above zero get classified as water; pixels below zero become land. This works well in most cases, but we can fine-tune the threshold to reduce errors.

// Basic threshold at 0
var waterBasic = mndwi.gt(0);
Map.addLayer(waterBasic.selfMask(), {palette: ['blue']}, 'Water (threshold = 0)');

// Refined threshold at 0.2 (reduces false positives from shadows)
var waterRefined = mndwi.gt(0.2);
Map.addLayer(waterRefined.selfMask(), {palette: ['darkblue']}, 'Water (threshold = 0.2)');

Try it: Find the best threshold

Use the Inspector tool to click on water pixels and land pixels near the shoreline. What MNDWI values do you see? Try thresholds of 0, 0.1, 0.2, and 0.3. Which one best separates water from land in your area?

Threshold pitfalls

  • Too low (e.g., -0.1): Shadows and wet soil sneak into your water map.
  • Too high (e.g., 0.5): Shallow or turbid water gets excluded.
  • The "best" threshold depends on your scene. There is no universal magic number.

How Big Is That Lake? Calculating Water Area

Once we have a binary water mask, we can calculate the total water surface area. ee.Image.pixelArea() creates an image where each pixel's value equals its area in square meters. We multiply the pixel area image by our water mask, then sum the result over the region of interest. Let's see how big Lake Okeechobee really is.

// Calculate water surface area in square kilometers
var waterMask = mndwi.gt(0);

// Create an area image (each pixel = its area in m²)
var areaImage = ee.Image.pixelArea();

// Multiply by water mask so only water pixels have area values
var waterArea = areaImage.updateMask(waterMask);

// Sum the area within the region
var roi = lake.buffer(40000); // 40 km buffer around the point
var totalArea = waterArea.reduceRegion({
  reducer: ee.Reducer.sum(),
  geometry: roi,
  scale: 10,
  maxPixels: 1e9
});

// Convert m² to km²
var areaKm2 = ee.Number(totalArea.get('area')).divide(1e6);
print('Water surface area (km²):', areaKm2);

What you should see

The console should print a value near 1,700 km² for Lake Okeechobee (its area fluctuates seasonally). If your number looks way off, double-check your threshold and buffer size.

Where Spectral Water Detection Can Trick You

Spectral indices are powerful, but they're not perfect. Several common features can produce false positives (classified as water when they're not) or false negatives (water that gets missed). Don't worry, once you know these pitfalls, they're easy to watch for.

  • Cloud shadows: Dark shadows on land produce high MNDWI values and frequently get misclassified as water. Always apply cloud masking before water detection.
  • Terrain shadows: Mountain shadows in low-sun-angle imagery mimic water signatures.
  • Dark surfaces: Lava flows, asphalt, and some dark soils can look water-like to certain indices.
  • Ice and snow: Frozen water bodies may not be detected because ice has a different spectral profile than liquid water.
  • Turbid water: Sediment-laden water reflects more in the visible bands, reducing MNDWI values and potentially causing false negatives.
  • Small water bodies: Features smaller than the pixel size (10 m for Sentinel-2) simply can't be resolved.

Pro tips

  • Always cloud-mask your imagery before computing water indices.
  • Combine spectral thresholding with slope data (from a DEM) to reduce terrain shadow errors.
  • For operational water mapping, use the JRC Global Surface Water dataset (we'll cover that in the next module).

Quick self-check

  1. What is the formula for MNDWI, and which Sentinel-2 bands does it use?
  2. Why does MNDWI generally outperform NDWI for open water mapping?
  3. If your water mask includes cloud shadows, what step are you missing?
  4. What GEE function gives you each pixel's area in square meters?
  5. Name two sources of false positives in spectral water detection.

Going deeper

We've covered water detection at a foundational level here. Ready for more advanced techniques? Check out:

  • EEFA Book - Chapter A2.3: Surface Water Mapping

Next Steps