SIG EDUCA YouTube Tutoriales

Detección de agua con Landsat 9 en Google Earth Engine

básicoLección 13 de 1810 minGoogle Earth Engine

Calcula NDWI, MNDWI y AWEInsh con Landsat 9 para detectar agua, y conoce el origen de cada índice.

Introducción #

El agua refleja poca luz en general. Devuelve algo en el azul y el verde, pero en el infrarrojo cercano y en el de onda corta absorbe casi todo. El suelo y la vegetación hacen lo contrario y reflejan mucho más en el infrarrojo. Si comparamos el verde con una banda infrarroja obtenemos un número que tiende a ser positivo sobre el agua y negativo sobre todo lo demás, y en esa idea se basan los índices espectrales para detectar agua.

En esta lección calcularemos tres de estos índices, NDWI, MNDWI y AWEInsh, con Landsat 9. Son la base para mapear inundaciones y extraer cuerpos de agua más adelante en el curso.

Objetivos #

Recursos #

Los índices #

ÍndiceFórmulaLandsat 8 y 9
NDWI(Verde − NIR) / (Verde + NIR)(B3 − B5) / (B3 + B5)
MNDWI(Verde − SWIR1) / (Verde + SWIR1)(B3 − B6) / (B3 + B6)
AWEInsh4(Verde − SWIR1) − (0,25 NIR + 2,75 SWIR2)B3, B6, B5, B7

De dónde viene cada índice #

Paso 1: Preparar la imagen #

Partimos de una escena de Landsat 9 con máscara de nubes y factor de escala.

El script usa una geometría llamada AOI. Dibújala con las herramientas de geometría del mapa en el Code Editor y renómbrala como AOI en el panel de importaciones.

// Factor de escala: convierte los valores digitales en reflectancia
function scaler(img) {
  var optical = img.select('SR_B.').multiply(0.0000275).add(-0.2).toFloat();
  var thermal = img.select('ST_B.*').multiply(0.00341802).add(149).toFloat();
  return img.addBands(optical, null, true).addBands(thermal, null, true);
}

// Máscara de nubes con la banda QA_PIXEL
function mask_clouds(img) {
  var qa = img.select('QA_PIXEL');
  var cirrus = 1 << 2;       // bit 2, cirros
  var cloud = 1 << 3;        // bit 3, nubes
  var cloudShadow = 1 << 4;  // bit 4, sombra de nubes
  var snow = 1 << 5;         // bit 5, nieve
  var mask = qa.bitwiseAnd(cirrus).eq(0)
    .and(qa.bitwiseAnd(cloud).eq(0))
    .and(qa.bitwiseAnd(cloudShadow).eq(0))
    .and(qa.bitwiseAnd(snow).eq(0));
  return img.updateMask(mask);
}
// Primera imagen de Landsat 9 con poca nubosidad sobre el AOI
var ls9_image = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2")
  .filterDate('2023-01-01', '2023-12-31')
  .filter(ee.Filter.lt("CLOUD_COVER_LAND", 10))
  .filterBounds(AOI)
  .map(mask_clouds)
  .map(scaler)
  .first()
  .clip(AOI);
print(ls9_image);
// Imagen en color verdadero
Map.addLayer(ls9_image, {bands: "SR_B4,SR_B3,SR_B2", min: 0.003, max: 0.19, gamma: 1}, "Landsat 9");
Map.centerObject(AOI, 8);

Paso 2: Calcular los índices para detectar agua #

NDWI y MNDWI son diferencias normalizadas entre dos bandas, así que basta con normalizedDifference(), que recibe primero la banda verde y después la infrarroja. AWEInsh combina cuatro bandas con coeficientes fijos, de modo que lo escribimos con expression() y asignamos un nombre a cada banda dentro de la fórmula.

// NDWI: (Verde - NIR) / (Verde + NIR)
var NDWI = ls9_image.normalizedDifference(['SR_B3', 'SR_B5']).rename('NDWI');

// MNDWI: (Verde - SWIR1) / (Verde + SWIR1)
var MNDWI = ls9_image.normalizedDifference(['SR_B3', 'SR_B6']).rename('MNDWI');

// AWEInsh: 4 * (Verde - SWIR1) - (0.25 * NIR + 2.75 * SWIR2)
var AWEInsh = ls9_image.expression('4 * (GREEN - SWIR1) - (0.25 * NIR + 2.75 * SWIR2)', {
  'GREEN': ls9_image.select('SR_B3'),
  'SWIR1': ls9_image.select('SR_B6'),
  'SWIR2': ls9_image.select('SR_B7'),
  'NIR': ls9_image.select('SR_B5')
}).rename('AWEInsh');

Paso 3: Visualizar los índices #

Usamos una paleta de −1 a 1 que va del rojo al azul. Los cuerpos de agua quedan en tonos cian y azul, y el suelo y la vegetación en rojo, amarillo o verde. NDWI y MNDWI siempre caen entre −1 y 1. AWEInsh no está acotado a ese rango, pero su umbral también es 0, así que la misma paleta separa bien el agua de lo demás.

// Simbología: rojo para lo seco, azul para el agua
var waterPalette = {min: -1, max: 1, palette: ['red', 'yellow', 'green', 'cyan', 'blue']};

Map.addLayer(NDWI, waterPalette, 'NDWI');
Map.addLayer(MNDWI, waterPalette, 'MNDWI');
Map.addLayer(AWEInsh, waterPalette, 'AWEInsh');

Paso 4: Exportar los índices #

Unimos los tres índices con addBands() para que el GeoTIFF tenga una banda por índice, en el mismo orden en que aparecen en el mapa, y lo enviamos a Google Drive desde la pestaña Tasks.

// Unir los índices y exportarlos
var index = NDWI.addBands([MNDWI, AWEInsh]).toFloat();

Export.image.toDrive({
  image: index,
  description: "landsat_9_water_index",
  folder: "index",
  region: AOI,
  scale: 30,
  maxPixels: 1e13,
  fileFormat: "TIFF",
  formatOptions: {cloudOptimized: true}
});

Código completo #

Este es el script completo. Cópialo, pégalo en el Code Editor, dibuja tu AOI y presiona Run.

// Factor de escala: convierte los valores digitales en reflectancia
function scaler(img) {
  var optical = img.select('SR_B.').multiply(0.0000275).add(-0.2).toFloat();
  var thermal = img.select('ST_B.*').multiply(0.00341802).add(149).toFloat();
  return img.addBands(optical, null, true).addBands(thermal, null, true);
}

// Máscara de nubes con la banda QA_PIXEL
function mask_clouds(img) {
  var qa = img.select('QA_PIXEL');
  var cirrus = 1 << 2;       // bit 2, cirros
  var cloud = 1 << 3;        // bit 3, nubes
  var cloudShadow = 1 << 4;  // bit 4, sombra de nubes
  var snow = 1 << 5;         // bit 5, nieve
  var mask = qa.bitwiseAnd(cirrus).eq(0)
    .and(qa.bitwiseAnd(cloud).eq(0))
    .and(qa.bitwiseAnd(cloudShadow).eq(0))
    .and(qa.bitwiseAnd(snow).eq(0));
  return img.updateMask(mask);
}

// Primera imagen de Landsat 9 con poca nubosidad sobre el AOI
var ls9_image = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2")
  .filterDate('2023-01-01', '2023-12-31')
  .filter(ee.Filter.lt("CLOUD_COVER_LAND", 10))
  .filterBounds(AOI)
  .map(mask_clouds)
  .map(scaler)
  .first()
  .clip(AOI);
print(ls9_image);

// Imagen en color verdadero
Map.addLayer(ls9_image, {bands: "SR_B4,SR_B3,SR_B2", min: 0.003, max: 0.19, gamma: 1}, "Landsat 9");
Map.centerObject(AOI, 8);

// NDWI: (Verde - NIR) / (Verde + NIR)
var NDWI = ls9_image.normalizedDifference(['SR_B3', 'SR_B5']).rename('NDWI');

// MNDWI: (Verde - SWIR1) / (Verde + SWIR1)
var MNDWI = ls9_image.normalizedDifference(['SR_B3', 'SR_B6']).rename('MNDWI');

// AWEInsh: 4 * (Verde - SWIR1) - (0.25 * NIR + 2.75 * SWIR2)
var AWEInsh = ls9_image.expression('4 * (GREEN - SWIR1) - (0.25 * NIR + 2.75 * SWIR2)', {
  'GREEN': ls9_image.select('SR_B3'),
  'SWIR1': ls9_image.select('SR_B6'),
  'SWIR2': ls9_image.select('SR_B7'),
  'NIR': ls9_image.select('SR_B5')
}).rename('AWEInsh');

// Simbología: rojo para lo seco, azul para el agua
var waterPalette = {min: -1, max: 1, palette: ['red', 'yellow', 'green', 'cyan', 'blue']};

Map.addLayer(NDWI, waterPalette, 'NDWI');
Map.addLayer(MNDWI, waterPalette, 'MNDWI');
Map.addLayer(AWEInsh, waterPalette, 'AWEInsh');

// Unir los índices y exportarlos
var index = NDWI.addBands([MNDWI, AWEInsh]).toFloat();

Export.image.toDrive({
  image: index,
  description: "landsat_9_water_index",
  folder: "index",
  region: AOI,
  scale: 30,
  maxPixels: 1e13,
  fileFormat: "TIFF",
  formatOptions: {cloudOptimized: true}
});

Conclusiones #

Los índices para detectar agua son una herramienta rápida para ubicar ríos, ciénagas y embalses. Ningún índice es perfecto en todos los paisajes, así que vale la pena comparar los tres en tu zona antes de elegir uno.

Referencias #

© 2026 SIG EDUCA