SIG EDUCA YouTube Tutoriales

Detección de agua con Sentinel-2 en Google Earth Engine

básicoLección 14 de 1810 minGoogle Earth Engine

Calcula NDWI, MNDWI y AWEInsh con Sentinel-2 para detectar agua con 10 m de resolución.

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 Sentinel-2. Con 10 m de resolución se detectan ríos, canales y caños mucho más estrechos que con Landsat.

Objetivos #

Recursos #

Los índices #

ÍndiceFórmulaSentinel-2
NDWI(Verde − NIR) / (Verde + NIR)(B3 − B8) / (B3 + B8)
MNDWI(Verde − SWIR1) / (Verde + SWIR1)(B3 − B11) / (B3 + B11)
AWEInsh4(Verde − SWIR1) − (0,25 NIR + 2,75 SWIR2)B3, B11, B8, B12

De dónde viene cada índice #

Paso 1: Preparar la imagen #

Aplicamos la máscara de nubes y tomamos la primera escena con poca nubosidad sobre el AOI.

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.

// Máscara de nubes con la banda SCL y paso a reflectancia (0 a 1)
function maskS2clouds(img) {
  var scl = img.select('SCL');
  var mask = scl.neq(3)    // sombra de nubes
    .and(scl.neq(8))       // nubes, probabilidad media
    .and(scl.neq(9))       // nubes, probabilidad alta
    .and(scl.neq(10));     // cirros
  return img.updateMask(mask).select('B.*').divide(10000);
}
// Primera imagen de Sentinel-2 con poca nubosidad sobre el AOI
var s2_image = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
  .filterDate("2023-01-01", "2023-12-01")
  .filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 10))
  .filterBounds(AOI)
  .map(maskS2clouds)
  .first()
  .clip(AOI);
print(s2_image);
// Imagen en color verdadero
Map.addLayer(s2_image, {bands: "B4,B3,B2", min: 0, max: 0.3, gamma: 1}, "Sentinel-2");
Map.centerObject(AOI, 11);

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.

// Índices para detectar agua con las bandas de Sentinel-2
var NDWI = s2_image.normalizedDifference(['B3', 'B8']).rename('NDWI');
var MNDWI = s2_image.normalizedDifference(['B3', 'B11']).rename('MNDWI');
var AWEInsh = s2_image.expression('4 * (GREEN - SWIR1) - (0.25 * NIR + 2.75 * SWIR2)', {
  'GREEN': s2_image.select('B3'),
  'SWIR1': s2_image.select('B11'),
  'SWIR2': s2_image.select('B12'),
  'NIR': s2_image.select('B8')
}).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: "sentinel_2_water_index",
  folder: "index",
  region: AOI,
  scale: 10,
  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.

// Máscara de nubes con la banda SCL y paso a reflectancia (0 a 1)
function maskS2clouds(img) {
  var scl = img.select('SCL');
  var mask = scl.neq(3)    // sombra de nubes
    .and(scl.neq(8))       // nubes, probabilidad media
    .and(scl.neq(9))       // nubes, probabilidad alta
    .and(scl.neq(10));     // cirros
  return img.updateMask(mask).select('B.*').divide(10000);
}

// Primera imagen de Sentinel-2 con poca nubosidad sobre el AOI
var s2_image = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
  .filterDate("2023-01-01", "2023-12-01")
  .filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 10))
  .filterBounds(AOI)
  .map(maskS2clouds)
  .first()
  .clip(AOI);
print(s2_image);

// Imagen en color verdadero
Map.addLayer(s2_image, {bands: "B4,B3,B2", min: 0, max: 0.3, gamma: 1}, "Sentinel-2");
Map.centerObject(AOI, 11);

// Índices para detectar agua con las bandas de Sentinel-2
var NDWI = s2_image.normalizedDifference(['B3', 'B8']).rename('NDWI');
var MNDWI = s2_image.normalizedDifference(['B3', 'B11']).rename('MNDWI');
var AWEInsh = s2_image.expression('4 * (GREEN - SWIR1) - (0.25 * NIR + 2.75 * SWIR2)', {
  'GREEN': s2_image.select('B3'),
  'SWIR1': s2_image.select('B11'),
  'SWIR2': s2_image.select('B12'),
  'NIR': s2_image.select('B8')
}).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: "sentinel_2_water_index",
  folder: "index",
  region: AOI,
  scale: 10,
  maxPixels: 1e13,
  fileFormat: "TIFF",
  formatOptions: {cloudOptimized: true}
});

Conclusiones #

Con Sentinel-2 los contornos del agua salen mucho más definidos. Ten en cuenta que MNDWI y AWEInsh usan bandas SWIR de 20 m, por lo que en los bordes verás algo menos de detalle que con NDWI.

Referencias #

© 2026 SIG EDUCA