Ideam_Logo

Global Forecast System - GFS


Introducción

En este cuadernillo (Notebook) aprenderemos:

  1. Introducción al GFS

  2. Uso del servidor Thredds

  3. Consulta de datos del GFS a escala de 0.5 grados en resolución usando siphon

  4. Visualización de variables pronosticadas

Prerequisitos

Conceptos

Importancia

Notas

Introducción a Xarray

Necesario

Lectura de datos multidimensionales

Introducción a Datetime

Necesario

Entender estampas de tiempo

Introducción a NetCDF

Útil

Entender la metadata de los datos

  • Tiempo de aprendizaje: 30 minutos


Librerías

Importamos las librerías que vamos a utilizar en este cuadernillo.

from datetime import datetime, timedelta

import cartopy.crs as ccrs
import cartopy.feature as cf
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
import xarray as xr
from pandas import to_datetime
from siphon.catalog import TDSCatalog

1. GFS

De acuerdo con la documentación oficial “El Sistema de Pronóstico Global (GFS) es un modelo de pronóstico del tiempo de los Centros Nacionales de Predicción Ambiental (NCEP) que genera datos para docenas de variables atmosféricas y de suelo-suelo, incluidas temperaturas, vientos, precipitaciones, humedad del suelo y concentración de ozono atmosférico.”

Ozone_GFS Reticula_GFS

Fuente: https://www.ncei.noaa.gov/

  • Sistema de Pronóstico Global

  • Utilizado comunmente como condiciones iniciales para otros modelos de pronóstico

  • El modelo se ejecuta 4 veces al día (00z, 06z, 12z y 18z)

  • Prósticos de hasta 16 días. 120 primeras horas son a escala horaria, luego cada 3 horas

  • Modelo tiene 127 capas en la vertical

  • Utiliza un esquema de esfera cúbica de volumen finito (FV3)

  • Resolución/dominio horizontal de 0.5, 1.0 y 2.5 grados

2. Acceso a datos del GFS

El acceso a los datos del GFS se puede realizar mediante la descarga directa de los datos utilizando el repositorio de datos del NCEI (National Centers for Environmental Information), usando el repositorio de datos de AWS, o usando el servidor Thredds de la NCAR. Para efectos prácticos del curso, utilizaremos la librería siphon que nos permite realizar consultas al servidor Thredds usando una API en Python.

Para consultar el modelo GFS con resolución espacial de 0.25 grados utilizamos el módulo TDSCatalog y pasamos el link correspondiente que se puede obtener acá.

gfs_url = "http://thredds.ucar.edu/thredds/catalog/grib/NCEP/GFS/Global_0p25deg/catalog.xml?dataset=grib/NCEP/GFS/Global_0p25deg/Best"
best_gfs = TDSCatalog(gfs_url)
best_gfs
Best GFS Quarter Degree Forecast Time Series

Para acceder a este conjunto de datos usando Xarray.Dataset simplemente creamos un acceso remoto de la siguiente manera

ds_gfs = best_gfs.datasets[0].remote_access(use_xarray=True)
print(f"size: {ds_gfs.nbytes / (1024 ** 3)} GB")
display(ds_gfs)
0.3.0
size: 542.7065934985876 GB
<xarray.Dataset>
Dimensions:                                                                     (
                                                                                 time2: 344,
                                                                                 : 2,
                                                                                 time3: 188,
                                                                                 pressure_difference_layer: 1,
                                                                                 depth_below_surface_layer: 4,
                                                                                 ...
                                                                                 height_above_ground: 1,
                                                                                 height_above_ground5: 2,
                                                                                 height_above_ground3: 3,
                                                                                 height_above_ground2: 7,
                                                                                 sigma: 1,
                                                                                 hybrid: 1)
Coordinates: (12/31)
  * lat                                                                         (lat) float32 ...
  * lon                                                                         (lon) float32 ...
  * time                                                                        (time) datetime64[ns] ...
    reftime                                                                     (time) datetime64[ns] ...
  * time1                                                                       (time1) datetime64[ns] ...
    reftime1                                                                    (time1) datetime64[ns] ...
    ...                                                                          ...
  * isobaric1                                                                   (isobaric1) float32 ...
  * height_above_ground4                                                        (height_above_ground4) float32 ...
  * height_above_ground5                                                        (height_above_ground5) float32 ...
  * sigma                                                                       (sigma) float32 ...
  * hybrid                                                                      (hybrid) float32 ...
  * hybrid1                                                                     (hybrid1) float32 ...
Dimensions without coordinates: 
Data variables: (12/180)
    LatLon_Projection                                                           int32 ...
    time2_bounds                                                                (time2, ) datetime64[ns] ...
    time3_bounds                                                                (time3, ) datetime64[ns] ...
    pressure_difference_layer_bounds                                            (pressure_difference_layer, ) float32 ...
    depth_below_surface_layer_bounds                                            (depth_below_surface_layer, ) float32 ...
    height_above_ground_layer_bounds                                            (height_above_ground_layer, ) float32 ...
    ...                                                                          ...
    Pressure_potential_vorticity_surface                                        (time, potential_vorticity_surface, lat, lon) float32 ...
    Temperature_potential_vorticity_surface                                     (time, potential_vorticity_surface, lat, lon) float32 ...
    u-component_of_wind_potential_vorticity_surface                             (time, potential_vorticity_surface, lat, lon) float32 ...
    v-component_of_wind_potential_vorticity_surface                             (time, potential_vorticity_surface, lat, lon) float32 ...
    Geopotential_height_potential_vorticity_surface                             (time, potential_vorticity_surface, lat, lon) float32 ...
    Medium_cloud_cover_middle_cloud_Mixed_intervals_Average                     (time3, lat, lon) float32 ...
Attributes:
    Originating_or_generating_Center:                                        ...
    Originating_or_generating_Subcenter:                                     ...
    GRIB_table_version:                                                      ...
    Type_of_generating_process:                                              ...
    Analysis_or_forecast_generating_process_identifier_defined_by_originating...
    file_format:                                                             ...
    Conventions:                                                             ...
    history:                                                                 ...
    featureType:                                                             ...
    _CoordSysBuilder:                                                        ...

El dato descargado desde el servidor Thredds trae un error en el formato de las coordenadas. Para evitar tropiezos cuando realicemos selección de datos más adelante, reemplazaremos las coordenadas de la siguiente manera:

# ds_gfs.sel(lat=0, method='nearest')
ds_gfs = ds_gfs.assign_coords(
    lat=("lat", np.linspace(90, -90, 721)), lon=("lon", np.linspace(0, 360, 1440))
)

Peligro

Este archivo pesa alrededor de 530 GB. No trate de cargarlo en memoria a menos que tenga la capacidad suficiente en RAM. Se recomienda hacer consultas específicas en variables, espacio y tiempo.

Para realizar consultas a variables de interes podemos utilizar el método .sel al igual que utilizar la notación punto o de diccionarios para acceder a variables de interes. Consultemos entonces variables como la temperatura, humedad, presión entre otros.

var = [
    "Temperature_surface",
    "Pressure_surface",
    "Precipitation_rate_surface",
    "Relative_humidity_height_above_ground",
]
ds_subset = ds_gfs[var]
display(ds_subset)
<xarray.Dataset>
Dimensions:                                (time: 189, lat: 721, lon: 1440,
                                            height_above_ground4: 1)
Coordinates:
  * time                                   (time) datetime64[ns] 2023-11-05 ....
    reftime                                (time) datetime64[ns] ...
  * height_above_ground4                   (height_above_ground4) float32 2.0
  * lat                                    (lat) float64 90.0 89.75 ... -90.0
  * lon                                    (lon) float64 0.0 0.2502 ... 360.0
Data variables:
    Temperature_surface                    (time, lat, lon) float32 ...
    Pressure_surface                       (time, lat, lon) float32 ...
    Precipitation_rate_surface             (time, lat, lon) float32 ...
    Relative_humidity_height_above_ground  (time, height_above_ground4, lat, lon) float32 ...
Attributes:
    Originating_or_generating_Center:                                        ...
    Originating_or_generating_Subcenter:                                     ...
    GRIB_table_version:                                                      ...
    Type_of_generating_process:                                              ...
    Analysis_or_forecast_generating_process_identifier_defined_by_originating...
    file_format:                                                             ...
    Conventions:                                                             ...
    history:                                                                 ...
    featureType:                                                             ...
    _CoordSysBuilder:                                                        ...

Por alguna razon los datos utilizan algunas veces la dimension time y otras veces time1. Para evitar errores podemos controlar eso de la siguiente manera

ds_subset.coords
Coordinates:
  * time                  (time) datetime64[ns] 2023-11-05 ... 2023-11-28T12:...
    reftime               (time) datetime64[ns] ...
  * height_above_ground4  (height_above_ground4) float32 2.0
  * lat                   (lat) float64 90.0 89.75 89.5 ... -89.5 -89.75 -90.0
  * lon                   (lon) float64 0.0 0.2502 0.5003 ... 359.5 359.7 360.0
try:
    ds_subset.Temperature_surface.time1
except AttributeError as e:
    print(e)
    ds_subset = ds_subset.rename(time="time1")
'DataArray' object has no attribute 'time1'
ds_subset.coords
Coordinates:
  * time1                 (time1) datetime64[ns] 2023-11-05 ... 2023-11-28T12...
    reftime               (time1) datetime64[ns] ...
  * height_above_ground4  (height_above_ground4) float32 2.0
  * lat                   (lat) float64 90.0 89.75 89.5 ... -89.5 -89.75 -90.0
  * lon                   (lon) float64 0.0 0.2502 0.5003 ... 359.5 359.7 360.0

3. Salidas gráficas a partir de los datos de GFS

Como ya sabemos, una vez tenemos nuestros datos en un objeto Xarray.Dataset podemos generar gráficos de manera rápida. Por ejemplo, podemos ver la temperatura pronósticada para el día de mañana

current_dateTime = datetime.now()
tomorrow = current_dateTime + timedelta(days=1)
tomorrow
datetime.datetime(2023, 11, 13, 20, 40, 22, 594141)

Formateamos la fecha a un string para hacer el filtrado en nuestro Dataset

fecha = f"{tomorrow:%Y-%m-%d}"
fecha
'2023-11-13'

Ahora generamos nuestra salida gráfica de la temperatura usando matplotlib

fig, ax = plt.subplots(figsize=(8, 4))
ds_subset["Temperature_surface"].sel(time1=fecha, method="nearest").plot(
    cmap="Spectral_r", ax=ax
)
<matplotlib.collections.QuadMesh at 0x7f9cc6665dd0>
../../_images/58b4c233095195a67a7d691936ab76393f21b806ce475f3be97049ce28b39170.png

Utilizando Cartopy podemos trabajar con sistemas coordenados de referencia (CRS por sus siglas en ingles Cordinate reference Systems) y diferentes proyecciones. Como ejemplo, trabajaremos con un proyección Mollweide con centro sobre Colombia (285° en longitud aproximadamente).

fig, ax = plt.subplots(
    figsize=(6, 4),
    subplot_kw={"projection": ccrs.Mollweide(central_longitude=285)},
    dpi=150,
)

ds_subset["Temperature_surface"].sel(time1=fecha, method="nearest").plot(
    ax=ax,
    cmap="Spectral_r",
    transform=ccrs.PlateCarree(),
    cbar_kwargs={
        "label": r"$Temperatura \ [°C]$",
        "orientation": "horizontal",
        "aspect": 50,
    },
)
ax.coastlines()
gl = ax.gridlines(
    draw_labels=True, linewidth=1, color="gray", alpha=0.5, linestyle="--"
)
plt.title(f"Temperatura [°C] - {fecha}");
/usr/share/miniconda3/envs/atmoscol2023/lib/python3.11/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/110m_physical/ne_110m_coastline.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
../../_images/9e7291de238ac7e2fa3fe146f48f66b629490b0991130ca94f74786d0a7d4102.png

Podemos generar un gráfico para Colombia y sus alrededores de la siguiente manera:

ds_col = ds_subset.sel(lat=slice(20, -10), lon=slice(270, 310))
display(ds_col)
<xarray.Dataset>
Dimensions:                                (time1: 189, lat: 121, lon: 160,
                                            height_above_ground4: 1)
Coordinates:
  * time1                                  (time1) datetime64[ns] 2023-11-05 ...
    reftime                                (time1) datetime64[ns] ...
  * height_above_ground4                   (height_above_ground4) float32 2.0
  * lat                                    (lat) float64 20.0 19.75 ... -10.0
  * lon                                    (lon) float64 270.2 270.4 ... 310.0
Data variables:
    Temperature_surface                    (time1, lat, lon) float32 ...
    Pressure_surface                       (time1, lat, lon) float32 ...
    Precipitation_rate_surface             (time1, lat, lon) float32 ...
    Relative_humidity_height_above_ground  (time1, height_above_ground4, lat, lon) float32 ...
Attributes:
    Originating_or_generating_Center:                                        ...
    Originating_or_generating_Subcenter:                                     ...
    GRIB_table_version:                                                      ...
    Type_of_generating_process:                                              ...
    Analysis_or_forecast_generating_process_identifier_defined_by_originating...
    file_format:                                                             ...
    Conventions:                                                             ...
    history:                                                                 ...
    featureType:                                                             ...
    _CoordSysBuilder:                                                        ...

De igual modo podemos visualizar los datos usando contour y contourf. A continuación, podemos observar los gráficos de la temperatura, presión, precipitación y humedad relativa

# Creación del "lienzo"
fig, ax = plt.subplots(
    figsize=(7, 4),
    subplot_kw={"projection": ccrs.PlateCarree(central_longitude=285)},
    dpi=150,
)

# Grafico de variables
# (ds_col.Pressure_surface.sel(time1=f"{fecha} 12:00") / 100).plot.contour(ax=ax, transform=ccrs.PlateCarree(), cmap='viridis_r',
#                                              linewidths=0.5,
#                                             levels=np.arange(500, 1000, 100))
# (ds_col.Temperature_surface.sel(time1=f"{fecha} 12:00") - 273.15).plot.contourf(ax=ax, cmap='Spectral_r', transform=ccrs.PlateCarree(),
#                                          levels=np.arange(0, 35, 1))
ds_col.Relative_humidity_height_above_ground.sel(time1=f"{fecha} 12:00").isel(
    height_above_ground4=0
).plot.contourf(
    ax=ax, cmap="viridis", transform=ccrs.PlateCarree(), levels=np.arange(0, 101, 10)
)

# (ds_col.Precipitation_rate_surface.sel(time=f"{fecha} 12:00") * 86400).plot.contourf(ax=ax,
#                                                           transform=ccrs.PlateCarree(),
#                                                           levels=np.arange(0, 50, 1))

# Adicionamos lineas costeras y división entre países
ax.coastlines()
ax.add_feature(cf.BORDERS)

# Agregar las líneas de reticula
gl = ax.gridlines(
    draw_labels=True, linewidth=1, color="gray", alpha=0.5, linestyle="--"
)
gl.xlabels_top = False
gl.ylabels_right = False
plt.title(
    f"{to_datetime(ds_col.sel(time1=f'{fecha} 12:00').time1.values):%Y-%m-%d %H%M} UTC"
);
/usr/share/miniconda3/envs/atmoscol2023/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:451: UserWarning: The .xlabels_top attribute is deprecated. Please use .top_labels to toggle visibility instead.
  warnings.warn('The .xlabels_top attribute is deprecated. Please '
/usr/share/miniconda3/envs/atmoscol2023/lib/python3.11/site-packages/cartopy/mpl/gridliner.py:487: UserWarning: The .ylabels_right attribute is deprecated. Please use .right_labels to toggle visibility instead.
  warnings.warn('The .ylabels_right attribute is deprecated. Please '
../../_images/ce59a4ee8f95a07d116217c3118d0ea9525be42d95338f6d30c7f2f62c2d7c69.png

Podemos gener series de tiempo con los valores pronosticados para una ubicacion en particular. Utilicemos la ubicación de la Universidad del Quindio como ejemplo: lat=4.54 y lon=-75.68.

ds_uniq = ds_col.sel(lat=4.54, lon=360 - 75.0, method="nearest")
ds_uniq
<xarray.Dataset>
Dimensions:                                (time1: 189, height_above_ground4: 1)
Coordinates:
  * time1                                  (time1) datetime64[ns] 2023-11-05 ...
    reftime                                (time1) datetime64[ns] ...
  * height_above_ground4                   (height_above_ground4) float32 2.0
    lat                                    float64 4.5
    lon                                    float64 284.9
Data variables:
    Temperature_surface                    (time1) float32 ...
    Pressure_surface                       (time1) float32 ...
    Precipitation_rate_surface             (time1) float32 ...
    Relative_humidity_height_above_ground  (time1, height_above_ground4) float32 ...
Attributes:
    Originating_or_generating_Center:                                        ...
    Originating_or_generating_Subcenter:                                     ...
    GRIB_table_version:                                                      ...
    Type_of_generating_process:                                              ...
    Analysis_or_forecast_generating_process_identifier_defined_by_originating...
    file_format:                                                             ...
    Conventions:                                                             ...
    history:                                                                 ...
    featureType:                                                             ...
    _CoordSysBuilder:                                                        ...
fig, (ax, ax1, ax2, ax3) = plt.subplots(4, 1, figsize=(10, 6), sharex=True)
(ds_uniq.Pressure_surface / 100).plot(ax=ax)
ax.set_title("")
ax.set_xlabel("")
ax.set_ylabel("Press [hPa]")
(ds_uniq.Temperature_surface - 273.15).plot(ax=ax1)
ax1.set_title("")
ax1.set_xlabel("")
ax1.set_ylabel("Temp [°C]")
(ds_uniq.Precipitation_rate_surface * 86400).plot(ax=ax2)
ax2.set_title("")
ax2.set_xlabel("")
ax2.set_ylabel("RR [mmhr-1]")
ds_uniq.Relative_humidity_height_above_ground.isel(height_above_ground4=0).plot(ax=ax3)
ax3.set_title("")
ax3.set_xlabel("")
ax3.set_ylabel("HR [%]")
Text(0, 0.5, 'HR [%]')
../../_images/2bbbc1f6edf8d5b7fac986532fd9225b61a90bd737336c13bfa9e65d31a827e7.png

Otras variables disponibles en el modelo GFS pueden ser gráficadas.


Conclusiones

En este cuadernillo aprendimos a consultar datos provenientes del modelo Global Forecast System usando la librería Siphon disponibles en el servidor Thredds de la NCAR. Aprendimos a generar gráficos espaciales y series temporales de pronósticos para múltiples variables usando Xarray, Cartopy y Matplotlib.