SIG EDUCA YouTube Tutoriales
Módulo de Google Earth Engine · Agua · público

Zou_2018

Módulo de Google Earth Engine que clasifica el agua abierta con la regla de Zou et al. (2018): MNDWI mayor que NDVI o que EVI, y EVI menor que 0,1.

Qué hace #

Zou y sus colaboradores (2018) usaron esta regla para mapear los cuerpos de agua de Estados Unidos entre 1984 y 2016 con todo el archivo de Landsat. La idea es simple: en el agua, el MNDWI supera a los índices de vegetación, y el EVI casi no tiene señal. Un píxel es agua si su MNDWI es mayor que su NDVI o que su EVI, y además su EVI es menor que 0,1. Esa última condición evita confundir el agua con vegetación inundada o muy húmeda. Es más sencilla que DSWE y funciona bien en lagos, embalses y ríos anchos.

Cómo cargarlo #

El módulo es público. Pega esta línea al inicio de tu script en el Code Editor y usa sus funciones con zou. delante, por ejemplo zou.ClassifyWaterZou2018(image).

var zou = require('users/sigeduca/routines:Indexes/Zou_2018');

Funciones #

FunciónRecibeDevuelve
ClassifyWaterZou2018(image)Imagen con las bandas Blue, Green, Red, Nir y Swir1, en reflectancia de 0 a 1.Imagen de una banda, waterMask: 1 donde hay agua y 0 donde no.

Escala antes de clasificar. El EVI usa una constante de 1 pensada para reflectancia de 0 a 1, y el umbral de 0,1 también. Con los valores digitales sin escalar, la regla no funciona.

Agua oscura en las costas. normalizedDifference() enmascara los píxeles con valores negativos, y en el mar o en lagos profundos la reflectancia escalada de la Colección 2 puede quedar un poco por debajo de 0. Si esas zonas te salen vacías, lleva las bandas a 0 antes con image.max(0), como hace Jones_2019R.

Ejemplo con una imagen #

Dibuja una geometría llamada AOI en el mapa y ejecuta este script. Toma una imagen de Landsat 9 con menos de 10 % de nubes sobre tu zona, la pasa a reflectancia y le aplica el módulo.

// Cargar el módulo
var zou = require('users/sigeduca/routines:Indexes/Zou_2018');

// Una imagen de Landsat 9 con poca nubosidad sobre el AOI
var img = ee.ImageCollection('LANDSAT/LC09/C02/T1_L2')
  .filterBounds(AOI)
  .filter(ee.Filter.lte('CLOUD_COVER', 10))
  .first();

// Escalar a reflectancia y usar los nombres comunes de las bandas
var bands = img.select('SR_B.').multiply(0.0000275).add(-0.2)
  .select(['SR_B2', 'SR_B3', 'SR_B4', 'SR_B5', 'SR_B6', 'SR_B7'],
          ['Blue', 'Green', 'Red', 'Nir', 'Swir1', 'Swir2']);

// Máscara de agua
var water = zou.ClassifyWaterZou2018(bands);

Map.centerObject(AOI, 11);
Map.addLayer(bands, {bands: ['Red', 'Green', 'Blue'], min: 0, max: 0.25}, 'Color verdadero');
Map.addLayer(water.selfMask(), {palette: ['00ffff']}, 'Agua (Zou, 2018)');

Ejemplo con una colección #

Para aplicar el módulo a muchas imágenes usamos map(): la función recibe cada imagen de la colección y devuelve el resultado. Dos detalles importan. Primero, cada imagen debe llegar escalada, con nombres comunes y sin nubes, y de eso se encarga prepare(). Segundo, las operaciones matemáticas borran las propiedades de la imagen, así que copiamos la fecha (system:time_start) con copyProperties() para poder graficar la serie en el tiempo.

// Cargar el módulo
var zou = require('users/sigeduca/routines:Indexes/Zou_2018');

// Prepara cada imagen: máscara de nubes, reflectancia (0 a 1) y nombres comunes
function prepare(img) {
  var qa = img.select('QA_PIXEL');
  var clear = qa.bitwiseAnd(1 << 3).eq(0)   // bit 3: nubes
    .and(qa.bitwiseAnd(1 << 4).eq(0));       // bit 4: sombras de nubes
  var bands = img.select(['SR_B2', 'SR_B3', 'SR_B4', 'SR_B5', 'SR_B6', 'SR_B7'],
                         ['Blue', 'Green', 'Red', 'Nir', 'Swir1', 'Swir2'])
    .multiply(0.0000275).add(-0.2)
    .updateMask(clear);
  // Las operaciones matemáticas borran las propiedades: recuperamos la fecha
  return ee.Image(bands.copyProperties(img, ['system:time_start']));
}

// Landsat 8 y 9 de dos años sobre el AOI
var collection = ee.ImageCollection('LANDSAT/LC09/C02/T1_L2')
  .merge(ee.ImageCollection('LANDSAT/LC08/C02/T1_L2'))
  .filterBounds(AOI)
  .filterDate('2023-01-01', '2025-01-01')
  .filter(ee.Filter.lte('CLOUD_COVER', 30))
  .map(prepare);
print('Imágenes', collection.size());

// Aplicar el módulo a cada imagen, conservando la fecha
var water = collection.map(function(img) {
  return ee.Image(zou.ClassifyWaterZou2018(img).copyProperties(img, ['system:time_start']));
});

// Frecuencia de agua: porcentaje de observaciones en que cada píxel fue agua
var frequency = water.mean().multiply(100).rename('frecuencia');
Map.centerObject(AOI, 11);
Map.addLayer(frequency.selfMask(), {min: 0, max: 100, palette: ['c6dbef', '6baed6', '08306b']}, 'Frecuencia de agua (%)');

// Fracción del AOI cubierta por agua en cada fecha
var chart = ui.Chart.image.series({
  imageCollection: water,
  region: AOI,
  reducer: ee.Reducer.mean(),
  scale: 30
}).setOptions({title: 'Fracción de agua en el AOI (Zou, 2018)', vAxis: {title: 'Fracción (0 a 1)'}, pointSize: 3});
print(chart);

Código del módulo #

No necesitas copiarlo para usarlo. Si quieres modificarlo, créalo en tu propio repositorio y cambia la ruta de require().

// Máscara de agua según Zou et al. (2018)
// https://www.pnas.org/content/pnas/suppl/2018/03/20/1719275115.DCSupplemental/pnas.1719275115.sapp.pdf

var Ndvi = function(image) {
  // índice de vegetación de diferencia normalizada (NDVI)
  var ndvi = image.normalizedDifference(['Nir', 'Red']).rename('ndvi');
  return(ndvi);
};
var Evi = function(image) {
  // índice de vegetación mejorado (EVI)
  var evi = image.expression('2.5 * (Nir - Red) / (1 + Nir + 6 * Red - 7.5 * Blue)', {
    'Nir': image.select(['Nir']),
    'Red': image.select(['Red']),
    'Blue': image.select(['Blue'])
    });
  return(evi.rename(['evi']));
};
var Mndwi = function(image) {
  // índice de agua de diferencia normalizada modificado (MNDWI)
  var mndwi = image.normalizedDifference(['Green', 'Swir1']).rename('mndwi');
  return(mndwi);
};
// Agua: MNDWI mayor que NDVI o que EVI, y EVI menor que 0,1
exports.ClassifyWaterZou2018 = function(image) {
  
  var mndwi = Mndwi(image);
  var ndvi = Ndvi(image);
  var evi = Evi(image);
  var water = (mndwi.gt(ndvi).or(mndwi.gt(evi))).and(evi.lt(0.1)); 
  
  return(water.rename(['waterMask']));
};

Referencias #

© 2026 SIG EDUCA