Análisis Climático Global con Datos ERA5
Visualización avanzada de datos meteorológicos 3D usando modelos ECMWF Copernicus

Tecnologías
Descripción
Sistema completo para procesamiento y visualización de datos climáticos con:
-
Carga y procesamiento de formatos GRIB/NetCDF
-
Visualizaciones 2D/3D con proyecciones cartográficas
-
Análisis temporal de series climáticas
-
Integración con datos geoespaciales (GeoJSON)
-
Animaciones de evolución temporal
-
Modelado de temperaturas y variables atmosféricas
Detalles Técnicos
Instalación de Dependencias
Trabajar con datos climáticos requiere un stack específico que no viene por defecto en Python. cfgrib permite leer archivos en formato GRIB (el formato nativo del ECMWF), cartopy se encarga de las proyecciones cartográficas para dibujar mapas geográficamente correctos, netCDF4 lee el otro formato estándar de datos climáticos (NetCDF), y geopandas permite trabajar con capas geoespaciales vectoriales, como los límites departamentales, para cruzarlas con los datos raster de temperatura.
pip install cfgrib cartopy netCDF4 geopandas
Carga de Datos NetCDF4
Este primer script arranca con lo más simple: leer un archivo .nc (NetCDF) directamente con la librería netCDF4, extraer la variable t2m (temperatura a 2 metros de altura, la estándar para reportar “temperatura del aire”) junto con sus coordenadas de latitud y longitud, y graficarla sobre un mapa. Un detalle importante: los datos de ECMWF vienen en Kelvin, así que hay que restar 273.15 para pasarlos a Celsius antes de graficar.
Para el mapa uso cartopy con una proyección PlateCarree (la clásica proyección equirectangular, donde latitud/longitud se mapean linealmente a los ejes X/Y), y agrego capas de referencia —costas, fronteras, ríos, lagos— para que el mapa sea legible sin necesitar contexto adicional. El campo de temperatura se dibuja con pcolormesh, que arma una grilla de color a partir de los valores punto por punto, usando una paleta divergente (coolwarm) que separa visualmente temperaturas frías (azul) de cálidas (rojo).
import netCDF4 as nc
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# Ruta del archivo .nc (cambia esto por la ubicación de tu archivo)
file_path = "/content/drive/MyDrive/3c7785ed8c7cf99c2ae80fafa694aef4.nc"
# Cargar el archivo NetCDF4
dataset = nc.Dataset(file_path, mode="r")
# Obtener las variables necesarias
t2m = dataset.variables['t2m'][0, :, :]
lat = dataset.variables['latitude'][:]
lon = dataset.variables['longitude'][:]
# Convertir a Celsius
t2m_celsius = t2m - 273.15
# Crear la figura y el mapa
fig, ax = plt.subplots(figsize=(10, 5), subplot_kw={'projection': ccrs.PlateCarree()})
ax.set_extent([lon.min(), lon.max(), lat.min(), lat.max()], crs=ccrs.PlateCarree())
# Agregar características al mapa
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS, linestyle=':')
ax.add_feature(cfeature.LAND, edgecolor='black')
ax.add_feature(cfeature.LAKES, edgecolor='black')
ax.add_feature(cfeature.RIVERS, edgecolor='blue')
# Dibujar la temperatura
c = ax.pcolormesh(lon, lat, t2m_celsius, cmap='coolwarm', transform=ccrs.PlateCarree())
# Agregar barra de color
cbar = plt.colorbar(c, orientation='horizontal', pad=0.05)
cbar.set_label('2m Temperature (°C)')
# Título del gráfico
plt.title("2m Temperature from ECMWF")
# Mostrar el gráfico
plt.show()
# Cerrar el dataset
dataset.close()
Visualización con Cartopy y GRIB
Acá subo un escalón en complejidad: en vez de NetCDF, trabajo directamente con el formato GRIB (más compacto y común en meteorología operativa), usando xarray con el motor cfgrib, que permite manipular los datos como un array multidimensional con etiquetas (mucho más cómodo que trabajar con arrays de NumPy puros). La variable acá es stl2 (temperatura del suelo a 2 metros).
El cambio más notorio es la proyección: en vez de la plana PlateCarree, uso una proyección ortográfica centrada en Sudamérica (central_longitude=-60, central_latitude=-15), que simula cómo se vería el planeta visto desde el espacio en ese punto —da un efecto de globo 3D aunque siga siendo una imagen 2D. Fijo también un rango de temperatura común (vmin=-40, vmax=40) para que la escala de colores sea comparable entre distintos mapas o fechas.
import numpy as np
import xarray as xr
import cfgrib
import geopandas as gpd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import warnings
# Ignorar advertencias específicas
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=UserWarning)
# Ruta del archivo GRIB (modifica según tu ubicación)
file_path = "/content/drive/MyDrive/53ff69f95521585163052af17eefd0c1.grib"
# Ruta del archivo GeoJSON (modifica según tu ubicación)
geojson_path = "/content/drive/MyDrive/DEPARTAMENTOS_PY_CNPV2022.geojson.txt"
# Cargar el dataset GRIB
data = xr.open_dataset(file_path, engine='cfgrib')
# Extraer temperatura y coordenadas
lons = data.longitude.values
lats = data.latitude.values
temp = data.stl2.values[0] - 273.15
# Cargar el archivo GeoJSON
gdf = gpd.read_file(geojson_path)
# Crear la figura con una proyección ortográfica centrada en Sudamérica
fig = plt.figure(figsize=(12, 12))
ax = fig.add_subplot(111, projection=ccrs.Orthographic(central_longitude=-60, central_latitude=-15))
# Agregar características del mapa
ax.add_feature(cfeature.LAND, edgecolor='black')
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS, linestyle='-')
# Dibujar el límite del mapa
ax.gridlines(draw_labels=False, linestyle="--", color="gray", alpha=0.5)
# Proyectar los datos de temperatura
vmin, vmax = -40, 40
mesh = ax.imshow(temp, extent=[lons.min(), lons.max(), lats.min(), lats.max()], transform=ccrs.PlateCarree(), cmap='coolwarm', origin='upper', vmin=vmin, vmax=vmax)
# Agregar barra de colores
cbar = plt.colorbar(mesh, orientation='horizontal', pad=0.05, extend='both')
cbar.set_label('Temperature (°C)')
cbar.set_ticks(np.arange(vmin, vmax + 10, 10))
# Agregar título
plt.title('ERA5-Pressure-levels, Daily Mean Temperature at 1000 hPa\n2025-02-07 (UTC)')
# Mostrar el gráfico
plt.show()
Visualización con Cartopy y GeoJSON
Esta versión toma los mismos datos GRIB pero cambia el enfoque: en vez de la vista tipo “globo”, hago zoom sobre Sudamérica con una proyección PlateCarree acotada a la región (set_extent), y sumo una capa vectorial (GeoJSON) con los límites departamentales de Paraguay, cargada con geopandas. Esto permite superponer la grilla de temperatura con divisiones administrativas reales, algo clave si el objetivo es un análisis a nivel país o departamento en lugar de una vista puramente continental.
Es, en esencia, el mismo procesamiento de datos que el script anterior, pero pensado para un caso de uso distinto: mientras el mapa ortográfico sirve para comunicar un fenómeno a escala continental, este acercamiento con límites geográficos sirve para un análisis operativo o territorial más específico.
import numpy as np
import xarray as xr
import cfgrib
import geopandas as gpd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import warnings
# Ignorar advertencias específicas
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=UserWarning)
# Ruta del archivo GRIB (modifica según tu ubicación)
file_path = "/content/drive/MyDrive/53ff69f95521585163052af17eefd0c1.grib"
# Ruta del archivo GeoJSON (modifica según tu ubicación)
geojson_path = "/content/drive/MyDrive/DEPARTAMENTOS_PY_CNPV2022.geojson.txt"
# Cargar el dataset GRIB
data = xr.open_dataset(file_path, engine='cfgrib')
# Extraer temperatura y coordenadas
lons = data.longitude.values
lats = data.latitude.values
temp = data.stl2.values[0] - 273.15
# Cargar el archivo GeoJSON
gdf = gpd.read_file(geojson_path)
# Crear la figura con un zoom más adecuado
fig = plt.figure(figsize=(15, 10))
ax = fig.add_subplot(111, projection=ccrs.PlateCarree())
# Configurar la extensión del mapa para Sudamérica
ax.set_extent([-85, -30, -60, 15], crs=ccrs.PlateCarree())
# Agregar características del mapa
ax.add_feature(cfeature.LAND, edgecolor='black')
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS, linestyle='-')
# Agregar límites de los departamentos desde el GeoJSON
gdf.boundary.plot(ax=ax, edgecolor="black", linewidth=1.2, transform=ccrs.PlateCarree())
# Proyectar los datos con un rango de temperatura adecuado
vmin, vmax = -40, 40
mesh = ax.imshow(temp, extent=[lons.min(), lons.max(), lats.min(), lats.max()], transform=ccrs.PlateCarree(), cmap='coolwarm', origin='upper', vmin=vmin, vmax=vmax)
# Agregar barra de colores
cbar = plt.colorbar(mesh, orientation='horizontal', pad=0.05, extend='both')
cbar.set_label('Temperature (°C)')
cbar.set_ticks(np.arange(vmin, vmax + 10, 10))
# Agregar título
plt.title('ERA5-Pressure-levels, Daily Mean Temperature at 1000 hPa\n2025-02-07 (UTC)')
# Mostrar el gráfico
plt.show()