ventuari.dev

Tutoriales / Elevación

Descargar un DEM gratis: cinco DEM de 30 m comparados con el láser GEDI

Los modelos digitales de elevación globales de 30 m se descargan gratis, pero no miden lo mismo: casi todos incluyen la altura de árboles y edificios. Comparamos Copernicus, SRTM, NASADEM, ALOS y FABDEM con el suelo medido por el láser GEDI en 7.194 puntos de Colombia y Venezuela. Aquí verás cuánto se equivoca cada uno, cuál conviene según el uso y cómo descargarlo.

Por VentuariPublicado el 17 min de lectura

Corte de un paisaje con bosque y un edificio. Una línea discontinua sigue las copas y el techo (modelo de superficie) y otra continua sigue el suelo (modelo del terreno).

Qué es un DEM y para qué sirve

Un DEM (modelo digital de elevación, también llamado MDE) es una imagen en la que cada píxel guarda una altura en metros. Los más usados entre los globales, gratuitos y con píxeles de 30 m son cinco: Copernicus GLO-30, SRTM, NASADEM, ALOS AW3D30 y FABDEM. Lo que los diferencia no es dónde se descargan, sino qué miden.

Con un DEM se calculan pendientes, cuencas y redes de drenaje, zonas que se inundarían si sube el agua, curvas de nivel o el perfil de un camino. El tutorial de cuerpos de agua con MNDWI, por ejemplo, usa la pendiente del SRTM para descartar sombras de montaña que el índice confunde con agua.

Superficie o terreno: la diferencia que más importa

Hay dos tipos de modelo. Un DSM (modelo digital de superficie) mide lo primero que encuentra desde arriba: copas de los árboles, techos y puentes. Un DTM (modelo digital del terreno) mide el suelo, sin lo que tiene encima.

Casi todos los DEM globales gratuitos son DSM. En una ciudad, un edificio de diez pisos aparece como una loma de unos 30 metros. En la Amazonía, el bosque eleva el terreno en la altura de sus árboles.

Para un mapa de pendientes general apenas importa. Para hidrología o inundaciones sí: en el modelo, el agua choca contra árboles y edificios que en la realidad no la detienen.

El SRTM se suele presentar como un caso intermedio, porque su radar entra en parte en el follaje. En nuestra medición no se notó: en el bosque quedó 6,6 m por encima del suelo, más que Copernicus. FABDEM es la excepción: parte de Copernicus GLO-30 y quita bosques y edificios con aprendizaje automático (Hawker y colaboradores, 2022). En el bosque quedó a 0,95 m del suelo.

Comparación de los DEM gratuitos de 30 m

Datos del catálogo de Google Earth Engine y de la documentación de cada fuente:

DEMDatos deMideCoberturaLicencia
Copernicus GLO-302010–2015 (radar TanDEM-X)SuperficieGlobalGratuita (licencia de Copernicus)
SRTM v3Febrero de 2000 (radar)SuperficieDe 56° S a 60° NLibre para cualquier fin (NASA)
NASADEMFebrero de 2000 (SRTM reprocesado)SuperficieDe 56° S a 60° NLibre para cualquier fin (NASA)
ALOS AW3D302006–2011 (imágenes ópticas en estéreo)SuperficieGlobalGratuita con los términos de JAXA
FABDEMBasado en Copernicus GLO-30Terreno (sin bosques ni edificios)GlobalNo comercial (CC BY-NC-SA 4.0)

Los cinco tienen píxeles de 30 m, pero eso no significa que vean cualquier detalle de ese tamaño. El ruido de la medición y lo que hay sobre el terreno pesan más que el tamaño del píxel.

La fecha también cuenta. El SRTM y el NASADEM muestran el relieve de febrero de 2000: no incluyen embalses, minas ni grandes obras posteriores. El NASADEM es el mismo SRTM reprocesado, con menos vacíos y más preciso, así que entre los dos conviene el NASADEM. En nuestra medición quedó, en mediana, a 0,77 m del suelo, frente a 2,60 m del SRTM.

Cuál elegir según tu proyecto

No hay un DEM mejor para todo. Esta tabla parte del uso:

Si necesitas…UsaPor qué
Pendientes, relieve o curvas de nivel en zonas abiertasCopernicus GLO-30Es el más reciente. En cultivos y herbazales quedó a menos de 35 cm del suelo (medición)
Inundaciones, drenaje o cuencas en zonas con bosque o ciudadFABDEM, si el uso no es comercialEn el bosque quedó a 0,95 m del suelo; los demás, de 4 a 7 m por encima
Lo mismo, en un proyecto comercialCopernicus GLO-30, con cuidadoEn el bosque quedó 5,7 m por encima del suelo: revisa esas zonas
Comparar con el relieve del año 2000NASADEMSu fecha es exacta y mejora al SRTM: en los Llanos de Apure, 0,38 m frente a 5,30 m
Un segundo DEM para contrastarALOS AW3D30Sale de imágenes ópticas, no de radar, así que sus errores son distintos

Si tu país publica un DEM nacional hecho con LiDAR o con fotografías aéreas, suele tener más detalle que cualquiera de estos. Los globales sirven cuando no lo hay o cuando necesitas el mismo dato en varios países, por ejemplo en una cuenca compartida. En Colombia, el IGAC publica modelos del terreno de 2 a 10 m por municipio: lo explicamos en DEM de Colombia.

Cinco DEM contra el láser GEDI en seis paisajes de Colombia y Venezuela

Medimos el error de los cinco DEM en 7.194 puntos donde el láser GEDI vio el suelo entre 2019 y 2025. Son seis zonas: los Andes, los Llanos y la Amazonía en Venezuela, y los Andes, el Caribe y los Llanos en Colombia. El error es la altura del DEM menos la del suelo: si es positivo, el DEM queda por encima.

FABDEM fue el más cercano al suelo, con una mediana de 0,20 m. En terreno abierto, Copernicus quedó casi igual. En el bosque, los cuatro modelos de superficie quedaron entre 4 y 7 m por encima del suelo.

Gráfico de barras con la mediana del error de cinco DEM frente al suelo de GEDI. En bosque: Copernicus +5,67 m, FABDEM +0,95, NASADEM +4,26, SRTM +6,58 y ALOS +6,07. En herbazal, cultivo y zona construida, todos quedan entre −0,80 y +1,94 m.
Mediana del error de cada DEM por cobertura de ESA WorldCover, en metros. Datos del script de esta página, ejecutado el 16 de octubre de 2026.

La mediana dice cuánto queda el DEM por encima o por debajo del suelo. El NMAD mide cuánto se dispersa el error: es como una desviación estándar, pero menos sensible a los valores extremos. Por cobertura, en metros (mediana / NMAD; en negrita, el de menor NMAD):

Cobertura (disparos)CopernicusFABDEMNASADEMSRTMALOS
Bosque (2.376)5,67 / 7,010,95 / 4,864,26 / 7,006,58 / 7,446,07 / 6,86
Herbazal (2.001)0,33 / 1,590,23 / 1,57−0,10 / 3,361,94 / 4,341,87 / 3,15
Cultivo (732)−0,08 / 0,51−0,06 / 0,57−0,80 / 1,670,26 / 2,290,18 / 1,79
Construido (1.015)0,87 / 1,80−0,77 / 1,84−0,13 / 2,721,66 / 3,041,93 / 2,30
Arbustal (568)1,80 / 7,291,21 / 7,051,30 / 7,872,24 / 8,652,09 / 7,23
Suelo desnudo (502)2,12 / 7,271,82 / 7,171,18 / 8,841,62 / 9,002,10 / 6,80

Por pendiente del terreno, en metros (mediana / NMAD; en negrita, el de menor NMAD):

Pendiente (disparos)CopernicusFABDEMNASADEMSRTMALOS
0–5° (3.522)0,35 / 1,200,01 / 1,00−0,14 / 2,401,62 / 3,291,71 / 2,45
5–15° (1.332)2,75 / 4,750,15 / 4,172,06 / 5,293,47 / 5,893,34 / 4,71
15–30° (1.276)5,32 / 8,392,45 / 8,224,41 / 10,005,16 / 9,785,22 / 8,55
Más de 30° (1.064)7,63 / 13,084,97 / 12,736,02 / 14,867,51 / 14,167,26 / 13,14

En el bosque, solo FABDEM llega al suelo

Bajo los árboles, Copernicus quedó 5,7 m por encima del suelo, el SRTM 6,6 m y ALOS 6,1 m. El NASADEM, 4,3 m. FABDEM, que quita la vegetación, se quedó en 0,95 m. Para calcular cuencas o zonas inundables bajo bosque, esa diferencia decide por dónde corre el agua del modelo.

En cultivos y pastos, Copernicus y FABDEM aciertan por centímetros

En los cultivos, los dos quedaron a menos de 10 cm del suelo, con un NMAD de medio metro. En los herbazales, a 33 y 23 cm. En terreno plano, de 0 a 5° de pendiente, su NMAD fue de 1,2 y 1,0 m, frente a 3,3 m del SRTM. Para relieve en zonas abiertas, Copernicus basta y no tiene la restricción comercial de FABDEM.

El NASADEM corrige al SRTM

Los dos salen del mismo radar de febrero de 2000, pero el NASADEM está reprocesado. En todos los puntos, su mediana fue de 0,77 m y la del SRTM de 2,60 m. La diferencia más grande apareció en los Llanos de Apure, un terreno plano: el SRTM quedó 5,30 m por encima del suelo y el NASADEM, 0,38 m.

En laderas de más de 30°, ningún DEM sirve para el detalle

Con pendientes fuertes, el NMAD subió a entre 12,7 y 14,9 m en los cinco DEM. Parte de ese error es del propio GEDI: cada disparo cubre un círculo de unos 25 m, y en una ladera el suelo cambia de altura dentro de ese círculo. Para curvas de nivel o taludes en montaña, conviene un DEM nacional hecho con LiDAR o con fotografías aéreas.

El paisaje pesa más que el DEM elegido

En el Caribe y los Llanos colombianos, los cinco DEM quedaron a 1,32 m del suelo o menos, en mediana. En los Andes, entre 1 y 5 m. En la Amazonía, los modelos de superficie quedaron entre 10 y 12 m por encima. En el valle del Sinú, donde seguimos las inundaciones de 2026, FABDEM quedó a 0,13 m del suelo. Mediana por zona, en metros:

Zona (disparos)CopernicusFABDEMNASADEMSRTMALOS
Caribe, Córdoba, Colombia (1.532)0,470,13−0,191,291,32
Llanos, Meta, Colombia (1.200)0,27−0,020,40−0,280,58
Llanos, Apure, Venezuela (1.036)0,780,260,385,303,38
Andes, Mérida, Venezuela (1.857)2,641,041,163,172,78
Andes, Caldas, Colombia (1.161)4,211,993,803,494,92
Amazonía, Amazonas, Venezuela (408)11,36−5,4710,1611,6611,48

Lo que esta medición no puede decir

  • En el Amazonas venezolano, FABDEM quedó 5,5 m por debajo de GEDI. Con estos datos no se puede saber quién falla: el láser puede no llegar al suelo bajo un bosque tan denso, o FABDEM puede quitar de más al eliminar los árboles.
  • La muestra pesa igual cada cobertura. Tomamos hasta 400 disparos por cobertura en cada zona, así que un promedio general no representa la superficie de cada paisaje. Por eso las tablas van por cobertura y por pendiente.
  • GEDI también tiene error, y no mide en la misma fecha. Sus disparos son de 2019 a 2025; los DEM, de 2000 a 2015. Quitamos los píxeles con pérdida de bosque, pero no otros cambios, como obras, minas o ríos que se movieron.

Cómo lo medimos: GEDI, el geoide y el script

GEDI es un láser de la NASA instalado en la Estación Espacial Internacional. Registra la altura del suelo incluso bajo la vegetación. Usamos su producto L2A del catálogo de Earth Engine, solo con los disparos nocturnos de mejor calidad: quality_flag 1, sin avisos de degradación ni de sesgo y con sensitivity de 0,95 o más.

En cada zona tomamos hasta 400 disparos por cobertura de ESA WorldCover, sin agua ni humedales. Quitamos los píxeles con pérdida de bosque entre 2001 y 2025 según Hansen. También descartamos los puntos donde la mediana de los cinco errores pasaba de 50 m: ahí GEDI no vio el suelo.

Sin el geoide, el error puede ser de 27 m

GEDI mide la altura sobre el elipsoide WGS84, una superficie matemática. Los DEM la dan sobre el geoide, que sigue el nivel medio del mar: EGM2008 en Copernicus y FABDEM, y EGM96 en el SRTM, el NASADEM y ALOS. Para compararlos, cada DEM se pasa al elipsoide sumando la altura de su geoide: h = H + N.

En la región, N va de −27 a +27 m. Sin esa corrección, el «error» medido sería sobre todo la distancia entre las dos superficies:

CiudadEGM2008EGM96
Cali+27,4 m+27,0 m
Medellín+24,0 m+23,5 m
Bogotá+23,0 m+21,6 m
Leticia+18,1 m+18,5 m
Montería+2,3 m+3,5 m
Mérida−0,3 m−0,2 m
Barranquilla−7,9 m−9,0 m
Maracaibo−14,4 m−14,0 m
Caracas−17,5 m−17,8 m
Ciudad Guayana−26,8 m−27,0 m

Earth Engine no tiene ningún geoide en su catálogo. Subimos las rejillas oficiales de la NGA de Estados Unidos, que distribuye el CDN de PROJ, como assets públicos que el script usa directamente.

El script: cinco DEM contra GEDI

Calcula el error de los cinco DEM en las seis zonas y muestra cuatro tablas: por país, por zona, por cobertura y por pendiente. Para medir otra zona, cambia los rectángulos de ZONAS.

Pulsa Run. Si las tablas superan los cinco minutos de las consultas interactivas, pulsa RUN en la tarea de la pestaña Tasks: el CSV guarda cada punto con sus errores.

dem_vs_gedi_colombia_venezuela.js
// Accuracy of five free 30 m DEMs against the GEDI lidar in Colombia and Venezuela
// Ventuari · https://ventuari.dev/tutoriales/descargar-dem-gratis/
//
// Compares Copernicus GLO-30, FABDEM, NASADEM, SRTM and ALOS AW3D30 with the
// ground elevation measured by GEDI in six landscapes: the Andes, the Llanos
// and the Amazon in Venezuela, and the Andes, the Caribbean and the Llanos in
// Colombia. Error = DEM − GEDI, both as heights above the WGS84 ellipsoid.
// Positive: the DEM sits above the ground (tree canopies, roofs).
//
// The interactive tables can time out (5-minute limit). The CSV export always
// finishes: click RUN on the "dem_vs_gedi_colombia_venezuela" task.

// ── 1. Geoids ──────────────────────────────────────────────────────────────
// GEDI measures heights above the ellipsoid; the DEMs, above a geoid.
// Copernicus and FABDEM use EGM2008; NASADEM, SRTM and ALOS use EGM96.
// Each DEM goes to the ellipsoid with ITS geoid: h = H + N.
var EGM96 = ee.Image('projects/earthengine-legacy/assets/users/ejvalero/EGM96-GEOIDE').resample('bilinear');
var EGM2008 = ee.Image('projects/earthengine-legacy/assets/users/ejvalero/EGM2008-GEOIDE').resample('bilinear');

// ── 2. Study areas (about 44 × 44 km each) ─────────────────────────────────
var ZONAS = [
  {pais: 'Venezuela', nombre: 'VE · Andes (Mérida)', rect: [-71.35, 8.40, -70.95, 8.80]},
  {pais: 'Venezuela', nombre: 'VE · Llanos (Apure)', rect: [-67.90, 7.55, -67.50, 7.95]},
  {pais: 'Venezuela', nombre: 'VE · Amazonía (Amazonas)', rect: [-66.40, 3.30, -66.00, 3.70]},
  {pais: 'Colombia', nombre: 'CO · Andes (Caldas)', rect: [-75.65, 4.90, -75.25, 5.30]},
  {pais: 'Colombia', nombre: 'CO · Caribe (Córdoba)', rect: [-76.05, 8.55, -75.65, 8.95]},
  {pais: 'Colombia', nombre: 'CO · Llanos (Meta)', rect: [-72.95, 3.90, -72.55, 4.30]}
];
var region = ee.FeatureCollection(ZONAS.map(function (z) {
  return ee.Feature(ee.Geometry.Rectangle(z.rect), {zona: z.nombre});
}));

// ── 3. Reference: ground measured by GEDI (L2A, 2019–2025) ─────────────────
var gediCol = ee.ImageCollection('LARSE/GEDI/GEDI02_A_002_MONTHLY')
  .filterBounds(region);
var gedi = gediCol.map(function (im) {
  var bueno = im.select('quality_flag').eq(1)
    .and(im.select('degrade_flag').eq(0))
    .and(im.select('elevation_bias_flag').eq(0))
    .and(im.select('sensitivity').gte(0.95))   // sees the ground under dense canopies
    .and(im.select('solar_elevation').lt(0));   // night shots only: less noise
  return im.select('elev_lowestmode').updateMask(bueno);
}).mosaic()
  .setDefaultProjection(gediCol.first().projection())   // sample on the GEDI grid (25 m)
  .rename('gedi');

// ── 4. The five DEMs, as heights above the ellipsoid ───────────────────────
// Bilinear interpolation on each image BEFORE mosaicking them.
function mosaico(id, banda) {
  return ee.ImageCollection(id).filterBounds(region)
    .map(function (im) { return im.select([banda]).resample('bilinear'); })
    .mosaic();
}
var DEMS = ['copernicus', 'fabdem', 'nasadem', 'srtm', 'alos'];
var dems = ee.Image.cat([
  mosaico('COPERNICUS/DEM/GLO30_2024_1', 'DEM').add(EGM2008),
  mosaico('projects/sat-io/open-datasets/FABDEM', 0).add(EGM2008),
  ee.Image('NASA/NASADEM_HGT/001').select('elevation').resample('bilinear').add(EGM96),
  ee.Image('USGS/SRTMGL1_003').select('elevation').resample('bilinear').add(EGM96),
  mosaico('JAXA/ALOS/AW3D30/V4_1', 'DSM').add(EGM96)
]).rename(DEMS);

// ── 5. Slope, land cover and change ────────────────────────────────────────
var cop = ee.ImageCollection('COPERNICUS/DEM/GLO30_2024_1').filterBounds(region).select('DEM');
var pendiente = ee.Terrain.slope(cop.mosaic().setDefaultProjection(cop.first().projection()))
  .rename('pendiente');
var cobertura = ee.ImageCollection('ESA/WorldCover/v200').first().select('Map').rename('clase');
var CLASES = [10, 20, 30, 40, 50, 60];   // no water, snow, wetlands or mangroves
var NOMBRES = ee.Dictionary({
  '10': 'Bosque', '20': 'Arbustal', '30': 'Herbazal',
  '40': 'Cultivo', '50': 'Construido', '60': 'Suelo desnudo'
});
// No forest loss 2001–2025: the terrain did not change between the DEM and GEDI.
// In Earth Engine, lossyear is masked where there was no loss: unmask(0) first,
// or eq(0) would drop exactly the pixels we want to keep.
var sinPerdida = ee.Image('UMD/hansen/global_forest_change_2025_v1_13')
  .select('lossyear').unmask(0).eq(0);

var pila = gedi.addBands(dems).addBands(pendiente).addBands(cobertura)
  .updateMask(sinPerdida)
  .updateMask(cobertura.remap(CLASES, [1, 1, 1, 1, 1, 1], 0));

// ── 6. Sample: up to 400 shots per land cover class in each area ───────────
var COLS = DEMS.map(function (d) { return 'e_' + d; });
var CUAD = DEMS.map(function (d) { return 'e2_' + d; });

var puntos = ee.FeatureCollection(ZONAS.map(function (z) {
  return pila.stratifiedSample({
    numPoints: 400,
    classBand: 'clase',
    region: ee.Geometry.Rectangle(z.rect),
    seed: 1,
    geometries: true,
    tileScale: 4
  }).map(function (p) { return p.set({zona: z.nombre, pais: z.pais}); });
})).flatten().map(function (p) {
  var g = ee.Number(p.get('gedi'));
  var errores = DEMS.map(function (d) { return ee.Number(p.get(d)).subtract(g); });
  var s = ee.Number(p.get('pendiente'));
  var tramo = ee.Algorithms.If(s.lt(5), '0–5°',
    ee.Algorithms.If(s.lt(15), '5–15°',
      ee.Algorithms.If(s.lt(30), '15–30°', 'más de 30°')));
  return p.set(ee.Dictionary.fromLists(COLS, errores))
    .set(ee.Dictionary.fromLists(CUAD, errores.map(function (e) { return e.pow(2); })))
    .set({
      cobertura: NOMBRES.get(ee.Number(p.get('clase')).int().format('%d')),
      tramo_pendiente: tramo,
      // If the median of the five errors exceeds 50 m, GEDI did not see the ground
      mediana_errores: ee.List(errores).reduce(ee.Reducer.median())
    });
}).filter(ee.Filter.lt('mediana_errores', 50))
  .filter(ee.Filter.gt('mediana_errores', -50));

// ── 7. Bias, median, NMAD and RMSE of each DEM ─────────────────────────────
var MEDIANA = ee.Reducer.median(null, null, 100000);   // exact up to 100 000 values

function resumen(sub, grupo) {
  var medias = ee.List(sub.reduceColumns(ee.Reducer.mean().repeat(10), COLS.concat(CUAD)).get('mean'));
  var medianas = ee.List(sub.reduceColumns(MEDIANA.repeat(5), COLS).get('median'));
  var DESV = DEMS.map(function (d) { return 'd_' + d; });
  var desvios = sub.map(function (p) {
    return p.set(ee.Dictionary.fromLists(DESV, COLS.map(function (c, i) {
      return ee.Number(p.get(c)).subtract(medianas.get(i)).abs();
    })));
  });
  var mad = ee.List(desvios.reduceColumns(MEDIANA.repeat(5), DESV).get('median'));
  var r = function (x) { return ee.Number(x).multiply(100).round().divide(100); };
  return ee.FeatureCollection(DEMS.map(function (d, i) {
    return ee.Feature(null, {
      fila: ee.String(grupo).cat(' · ').cat(d),
      n: sub.size(),
      sesgo_m: r(medias.get(i)),
      mediana_m: r(medianas.get(i)),
      nmad_m: r(ee.Number(mad.get(i)).multiply(1.4826)),
      rmse_m: r(ee.Number(medias.get(i + 5)).sqrt())
    });
  }));
}

function tabla(campo) {
  var grupos = puntos.aggregate_array(campo).distinct().sort();
  return ee.FeatureCollection(grupos.map(function (g) {
    return resumen(puntos.filter(ee.Filter.eq(campo, g)), g);
  })).flatten();
}

function mostrar(fc, titulo) {
  print(ui.Chart.feature.byFeature({
    features: fc, xProperty: 'fila',
    yProperties: ['n', 'sesgo_m', 'mediana_m', 'nmad_m', 'rmse_m']
  }).setChartType('Table').setOptions({title: titulo}));
}

mostrar(tabla('pais'), 'Por país');
mostrar(tabla('zona'), 'Por zona');
mostrar(tabla('cobertura'), 'Por cobertura (ESA WorldCover)');
mostrar(tabla('tramo_pendiente'), 'Por pendiente');

// ── 8. Points to check outside Earth Engine ────────────────────────────────
Export.table.toDrive({
  collection: puntos,
  description: 'dem_vs_gedi_colombia_venezuela',
  fileFormat: 'CSV',
  selectors: ['pais', 'zona', 'cobertura', 'tramo_pendiente', 'pendiente', 'gedi']
    .concat(DEMS).concat(COLS).concat(['.geo'])
});

Map.addLayer(region.style({color: 'ff5a1f', fillColor: '00000000', width: 2}), {}, 'Zonas');
Map.centerObject(region, 5);

Lanza primero la exportación del CSV y después imprime las tablas.

dem_vs_gedi_colombia_venezuela.py
"""
Accuracy of five free 30 m DEMs against the GEDI lidar in Colombia and Venezuela
Ventuari · https://ventuari.dev/tutoriales/descargar-dem-gratis/

Compares Copernicus GLO-30, FABDEM, NASADEM, SRTM and ALOS AW3D30 with the
ground elevation measured by GEDI in six landscapes: the Andes, the Llanos
and the Amazon in Venezuela, and the Andes, the Caribbean and the Llanos in
Colombia. Error = DEM − GEDI, both as heights above the WGS84 ellipsoid.
Positive: the DEM sits above the ground (tree canopies, roofs).

The summary tables can time out (5-minute limit for interactive requests).
The CSV export always finishes and holds every sampled shot.
"""

import ee
import geemap

ee.Authenticate()
ee.Initialize(project='tu-proyecto-gcp')


# 1. Geoids
# GEDI measures heights above the ellipsoid; the DEMs, above a geoid.
# Copernicus and FABDEM use EGM2008; NASADEM, SRTM and ALOS use EGM96.
# Each DEM goes to the ellipsoid with ITS geoid: h = H + N.
EGM96 = ee.Image('projects/earthengine-legacy/assets/users/ejvalero/EGM96-GEOIDE').resample('bilinear')
EGM2008 = ee.Image('projects/earthengine-legacy/assets/users/ejvalero/EGM2008-GEOIDE').resample('bilinear')


# 2. Study areas (about 44 × 44 km each)
ZONAS = [
    {'pais': 'Venezuela', 'nombre': 'VE · Andes (Mérida)', 'rect': [-71.35, 8.40, -70.95, 8.80]},
    {'pais': 'Venezuela', 'nombre': 'VE · Llanos (Apure)', 'rect': [-67.90, 7.55, -67.50, 7.95]},
    {'pais': 'Venezuela', 'nombre': 'VE · Amazonía (Amazonas)', 'rect': [-66.40, 3.30, -66.00, 3.70]},
    {'pais': 'Colombia', 'nombre': 'CO · Andes (Caldas)', 'rect': [-75.65, 4.90, -75.25, 5.30]},
    {'pais': 'Colombia', 'nombre': 'CO · Caribe (Córdoba)', 'rect': [-76.05, 8.55, -75.65, 8.95]},
    {'pais': 'Colombia', 'nombre': 'CO · Llanos (Meta)', 'rect': [-72.95, 3.90, -72.55, 4.30]},
]
region = ee.FeatureCollection([
    ee.Feature(ee.Geometry.Rectangle(z['rect']), {'zona': z['nombre']}) for z in ZONAS
])


# 3. Reference: ground measured by GEDI (L2A, 2019–2025)
gedi_col = ee.ImageCollection('LARSE/GEDI/GEDI02_A_002_MONTHLY').filterBounds(region)


def calidad_gedi(im):
    bueno = (
        im.select('quality_flag').eq(1)
        .And(im.select('degrade_flag').eq(0))
        .And(im.select('elevation_bias_flag').eq(0))
        .And(im.select('sensitivity').gte(0.95))   # sees the ground under dense canopies
        .And(im.select('solar_elevation').lt(0))   # night shots only: less noise
    )
    return im.select('elev_lowestmode').updateMask(bueno)


gedi = (
    gedi_col.map(calidad_gedi).mosaic()
    .setDefaultProjection(gedi_col.first().projection())   # sample on the GEDI grid (25 m)
    .rename('gedi')
)


# 4. The five DEMs, as heights above the ellipsoid
# Bilinear interpolation on each image BEFORE mosaicking them.
def mosaico(id_, banda):
    return (
        ee.ImageCollection(id_).filterBounds(region)
        .map(lambda im: im.select([banda]).resample('bilinear'))
        .mosaic()
    )


DEMS = ['copernicus', 'fabdem', 'nasadem', 'srtm', 'alos']
dems = ee.Image.cat([
    mosaico('COPERNICUS/DEM/GLO30_2024_1', 'DEM').add(EGM2008),
    mosaico('projects/sat-io/open-datasets/FABDEM', 0).add(EGM2008),
    ee.Image('NASA/NASADEM_HGT/001').select('elevation').resample('bilinear').add(EGM96),
    ee.Image('USGS/SRTMGL1_003').select('elevation').resample('bilinear').add(EGM96),
    mosaico('JAXA/ALOS/AW3D30/V4_1', 'DSM').add(EGM96),
]).rename(DEMS)


# 5. Slope, land cover and change
cop = ee.ImageCollection('COPERNICUS/DEM/GLO30_2024_1').filterBounds(region).select('DEM')
pendiente = ee.Terrain.slope(
    cop.mosaic().setDefaultProjection(cop.first().projection())
).rename('pendiente')
cobertura = ee.ImageCollection('ESA/WorldCover/v200').first().select('Map').rename('clase')
CLASES = [10, 20, 30, 40, 50, 60]   # no water, snow, wetlands or mangroves
NOMBRES = ee.Dictionary({
    '10': 'Bosque', '20': 'Arbustal', '30': 'Herbazal',
    '40': 'Cultivo', '50': 'Construido', '60': 'Suelo desnudo',
})
# No forest loss 2001–2025: the terrain did not change between the DEM and GEDI.
# In Earth Engine, lossyear is masked where there was no loss: unmask(0) first,
# or eq(0) would drop exactly the pixels we want to keep.
sin_perdida = (
    ee.Image('UMD/hansen/global_forest_change_2025_v1_13')
    .select('lossyear').unmask(0).eq(0)
)

pila = (
    gedi.addBands(dems).addBands(pendiente).addBands(cobertura)
    .updateMask(sin_perdida)
    .updateMask(cobertura.remap(CLASES, [1] * len(CLASES), 0))
)


# 6. Sample: up to 400 shots per land cover class in each area
COLS = ['e_' + d for d in DEMS]
CUAD = ['e2_' + d for d in DEMS]


def muestra_zona(z):
    return pila.stratifiedSample(
        numPoints=400,
        classBand='clase',
        region=ee.Geometry.Rectangle(z['rect']),
        seed=1,
        geometries=True,
        tileScale=4,
    ).map(lambda p: p.set({'zona': z['nombre'], 'pais': z['pais']}))


def errores_punto(p):
    g = ee.Number(p.get('gedi'))
    errores = [ee.Number(p.get(d)).subtract(g) for d in DEMS]
    s = ee.Number(p.get('pendiente'))
    tramo = ee.Algorithms.If(s.lt(5), '0–5°',
            ee.Algorithms.If(s.lt(15), '5–15°',
            ee.Algorithms.If(s.lt(30), '15–30°', 'más de 30°')))
    return (
        p.set(ee.Dictionary.fromLists(COLS, errores))
        .set(ee.Dictionary.fromLists(CUAD, [e.pow(2) for e in errores]))
        .set({
            'cobertura': NOMBRES.get(ee.Number(p.get('clase')).int().format('%d')),
            'tramo_pendiente': tramo,
            # If the median of the five errors exceeds 50 m, GEDI did not see the ground
            'mediana_errores': ee.List(errores).reduce(ee.Reducer.median()),
        })
    )


puntos = (
    ee.FeatureCollection([muestra_zona(z) for z in ZONAS]).flatten()
    .map(errores_punto)
    .filter(ee.Filter.lt('mediana_errores', 50))
    .filter(ee.Filter.gt('mediana_errores', -50))
)


# 7. Bias, median, NMAD and RMSE of each DEM
MEDIANA = ee.Reducer.median(None, None, 100000)   # exact up to 100 000 values
DESV = ['d_' + d for d in DEMS]


def resumen(sub, grupo):
    medias = ee.List(sub.reduceColumns(ee.Reducer.mean().repeat(10), COLS + CUAD).get('mean'))
    medianas = ee.List(sub.reduceColumns(MEDIANA.repeat(5), COLS).get('median'))
    desvios = sub.map(lambda p: p.set(ee.Dictionary.fromLists(DESV, [
        ee.Number(p.get(c)).subtract(medianas.get(i)).abs() for i, c in enumerate(COLS)
    ])))
    mad = ee.List(desvios.reduceColumns(MEDIANA.repeat(5), DESV).get('median'))

    def r(x):
        return ee.Number(x).multiply(100).round().divide(100)

    return ee.FeatureCollection([
        ee.Feature(None, {
            'fila': ee.String(grupo).cat(' · ').cat(d),
            'n': sub.size(),
            'sesgo_m': r(medias.get(i)),
            'mediana_m': r(medianas.get(i)),
            'nmad_m': r(ee.Number(mad.get(i)).multiply(1.4826)),
            'rmse_m': r(ee.Number(medias.get(i + 5)).sqrt()),
        }) for i, d in enumerate(DEMS)
    ])


def tabla(campo):
    grupos = puntos.aggregate_array(campo).distinct().sort()
    return ee.FeatureCollection(grupos.map(
        lambda g: resumen(puntos.filter(ee.Filter.eq(campo, g)), g)
    )).flatten()


def mostrar(fc, titulo):
    print('\n' + titulo)
    print(f"{'fila':36} {'n':>6} {'sesgo_m':>8} {'mediana_m':>10} {'nmad_m':>7} {'rmse_m':>7}")
    for f in fc.getInfo()['features']:
        p = f['properties']
        print(f"{p['fila']:36} {p['n']:>6} {p['sesgo_m']:>8} {p['mediana_m']:>10} {p['nmad_m']:>7} {p['rmse_m']:>7}")


# 8. Points to check outside Earth Engine (start it first: it always finishes)
tarea = ee.batch.Export.table.toDrive(
    collection=puntos,
    description='dem_vs_gedi_colombia_venezuela',
    fileFormat='CSV',
    selectors=['pais', 'zona', 'cobertura', 'tramo_pendiente', 'pendiente', 'gedi']
    + DEMS + COLS + ['.geo'],
)
tarea.start()
print('Export started:', tarea.status()['state'])

mostrar(tabla('pais'), 'Por país')
mostrar(tabla('zona'), 'Por zona')
mostrar(tabla('cobertura'), 'Por cobertura (ESA WorldCover)')
mostrar(tabla('tramo_pendiente'), 'Por pendiente')

m = geemap.Map()
m.add_basemap('SATELLITE')
m.addLayer(region.style(color='ff5a1f', fillColor='00000000', width=2), {}, 'Zonas')
m.centerObject(region, 5)
m

FABDEM está en el catálogo comunitario de Earth Engine con su licencia no comercial: el script sirve para medir, no para usarlo en un proyecto comercial.

El «DEM de ALOS PALSAR de 12,5 m» no es lo que parece

Muchas guías recomiendan un «DEM de ALOS PALSAR de 12,5 m» por su supuesto detalle. Ese archivo viene dentro de los productos de radar ALOS PALSAR corregidos del terreno (RTC) que distribuye ASF, y no es un DEM medido por ALOS: es el modelo que se usó para hacer la corrección. Fuera de Estados Unidos, ese modelo es el SRTM de 30 m.

Los 12,5 m son el tamaño de píxel de la imagen de radar, no la resolución del relieve, como explican la documentación de ASF y la guía del producto. En la práctica no tiene más detalle que el SRTM. Descargar el Copernicus GLO-30 o el NASADEM es más sencillo y da al menos lo mismo.

Cómo descargarlo

Desde un portal

Todos piden crear una cuenta gratuita:

  • OpenTopography reúne Copernicus GLO-30, SRTM, NASADEM y ALOS AW3D30 en un solo sitio. Eliges la zona en el mapa y descargas un GeoTIFF recortado.
  • Copernicus Data Space es la fuente oficial del Copernicus DEM.
  • USGS EarthExplorer tiene el SRTM por teselas.
  • JAXA distribuye el ALOS AW3D30.
  • FABDEM se descarga desde el repositorio enlazado en su artículo original o se usa en Google Earth Engine.

Con Google Earth Engine

Si trabajas con Google Earth Engine, no hace falta descargar nada: cada DEM está en el catálogo y se recorta a tu zona en el mismo script. Las colecciones (ImageCollection) se unen con .mosaic() antes de recortar.

DEMID en Earth EngineTipoBanda de altura
Copernicus GLO-30COPERNICUS/DEM/GLO30_2024_1ImageCollectionDEM
SRTM v3USGS/SRTMGL1_003Imageelevation
NASADEMNASA/NASADEM_HGT/001Imageelevation
ALOS AW3D30JAXA/ALOS/AW3D30/V4_1ImageCollectionDSM
FABDEM (catálogo comunitario)projects/sat-io/open-datasets/FABDEMImageCollectionLa primera banda

Ojo con Copernicus: la colección COPERNICUS/DEM/GLO30 que aparece en muchos scripts está marcada como obsoleta en el catálogo. La vigente es COPERNICUS/DEM/GLO30_2024_1. FABDEM no está en el catálogo oficial sino en el catálogo comunitario de Earth Engine, con la misma licencia no comercial.

Si necesitas el relieve de tu zona ya procesado, con pendientes, cuencas o zonas que se inundarían, mira el servicio de mapas satelitales.

Lo que la IA suele hacer mal

Si le preguntas a un asistente de IA qué DEM descargar, estas respuestas son frecuentes y fallan:

  1. Recomienda el «DEM de ALOS PALSAR de 12,5 m» como el más detallado. Es el SRTM de 30 m entregado con píxeles de 12,5 m. No aporta más detalle del relieve.
  2. Usa COPERNICUS/DEM/GLO30 en Earth Engine. Esa colección está marcada como obsoleta. La vigente es COPERNICUS/DEM/GLO30_2024_1.
  3. Trata un modelo de superficie como si fuera el terreno. Calcula cuencas o zonas inundables con Copernicus o SRTM en zonas de bosque o ciudad sin advertir que los árboles y los edificios cuentan como relieve.
  4. Propone FABDEM para un proyecto comercial. Su licencia, CC BY-NC-SA 4.0, no permite el uso comercial.
  5. Compara el DEM con GEDI o con un GPS sin pasarlos a la misma superficie. GEDI y el GPS dan la altura sobre el elipsoide; los DEM, sobre el geoide. En Colombia y Venezuela la diferencia va de −27 a +27 m.
  6. Filtra la pérdida de bosque con lossyear.eq(0). En Earth Engine, lossyear está vacía donde no hubo pérdida, así que eq(0) borra justo los píxeles sin cambios. Primero va unmask(0).
  7. Da por hecho que el geoide está en el catálogo de Earth Engine. No está, ni en el catálogo comunitario. Hay que subir las rejillas de EGM96 y EGM2008 como assets.

Preguntas frecuentes

¿Cuál es el mejor DEM gratuito?

Depende del terreno y del uso. Contra el láser GEDI, FABDEM fue el más cercano al suelo, sobre todo en el bosque, pero no se puede usar en proyectos comerciales. En zonas abiertas, Copernicus GLO-30 quedó casi igual, a menos de 35 cm del suelo en cultivos y herbazales.

¿Qué diferencia hay entre un DSM y un DTM?

Un DSM (modelo digital de superficie) incluye lo que hay sobre el suelo, como árboles y edificios. Un DTM (modelo digital del terreno) mide solo el suelo. Copernicus GLO-30 y ALOS AW3D30 son DSM; FABDEM es un DTM.

¿El DEM de ALOS PALSAR de 12,5 m tiene más detalle que el SRTM?

No. Es el modelo que se usó para corregir el terreno de las imágenes de radar, que fuera de Estados Unidos es el SRTM de 30 m, entregado con píxeles de 12,5 m. No aporta más detalle del relieve.

¿Qué precisión tiene un DEM de 30 m?

Depende más del terreno que del DEM. Contra el láser GEDI, en terreno plano el error típico (NMAD) fue de 1 a 3,3 m, y en laderas de más de 30°, de 12,7 a 14,9 m. En el bosque, los modelos de superficie quedaron de 4 a 7 m por encima del suelo.

Fuentes

  1. Copernicus DEM GLO-30 (2024_1): Global 30m Digital Elevation ModelGoogle Earth Engine, catálogo de datosCopernicus GLO-30: modelo de superficie, fechas de los datos y licencia.
  2. NASA SRTM Digital Elevation 30mGoogle Earth Engine, catálogo de datosSRTM: fecha, cobertura y relleno de vacíos.
  3. NASADEM: NASA 30m Digital Elevation ModelGoogle Earth Engine, catálogo de datosNASADEM: en qué mejora al SRTM.
  4. ALOS DSM: Global 30m v4.1Google Earth Engine, catálogo de datosALOS AW3D30: sensor, fechas y tipo de modelo.
  5. A 30 m global map of elevation with forests and buildings removedHawker, L. y colaboradores (2022). Environmental Research LettersCómo se quitan bosques y edificios en FABDEM.
  6. ALOS PALSAR ProductsAlaska Satellite Facility (ASF), documentaciónQué DEM incluyen los productos ALOS PALSAR corregidos del terreno.
  7. ASF Radiometrically Terrain Corrected ALOS PALSAR products (guía del producto, v1.2)Alaska Satellite Facility (ASF)El tamaño de píxel de 12,5 m no es la resolución del DEM.
  8. GEDI L2A Raster Canopy Top Height (Version 2)Google Earth Engine, catálogo de datosAltura del suelo (elev_lowestmode) sobre el elipsoide y las banderas de calidad de cada disparo.
  9. PROJ Datumgrid CDNOSGeo PROJ, rejillas de la NGARejillas de los geoides EGM96 (us_nga_egm96_15.tif) y EGM2008 (us_nga_egm08_25.tif).
  10. Hansen Global Forest Change v1.13 (2000-2025)Google Earth Engine, catálogo de datosAño de pérdida de bosque (lossyear), para quitar los puntos donde el terreno cambió.
  11. ESA WorldCover 10m v200Google Earth Engine, catálogo de datosCoberturas usadas para agrupar los errores.

Sigue aprendiendo

¿Vas a usar el DEM para separar el agua de las sombras de montaña? Mira cómo en el tutorial de cuerpos de agua con MNDWI.

¿Necesitas pendientes, cuencas o zonas inundables de tu territorio?

Escribe a contacto@ventuari.dev con la zona y el uso que le darás, y recibirás una propuesta con alcance y precio.