Acceso a datos de temperatura de superficie terrestre de ECOSTRESS de NASA Earthdata en la nube#

ecostress_diagram

La misión ECOsystem Spaceborne Thermal Radiometer Experiment on Space Station (ECOSTRESS) mide la temperatura de las plantas para comprender mejor cuánta agua necesitan y cómo responden al estrés. ECOSTRESS está instalado en la Estación Espacial Internacional (ISS) y recolecta datos globalmente entre las latitudes 52° N y 52° S. Fuente: NASA Earthdata.

Requisitos#

  1. Autenticación EDL (usuario/contraseña)

  2. python>=3.12

Objetivos#

Descarga un archivo remoto con (sub)selection#

  • a) Por variables

  • b) Por coordenadas geograficas

Descarga de múltiples archivos remotos#

  • A cada archivo se le aplica la subseleccion de una subregion de datos de interes antes de descargar.

Referencias#

Hook, S., & Hulley, G.(2022). ECOSTRESS Swath Land Surface Temperature and Emissivity Instantaneous L2 Global 70 m v002 [Data set]. NASA Land Processes Distributed Active Archive Center. https://doi.org/10.5067/ECOSTRESS/ECO_L2_LSTE.002

import xarray as xr
import datetime as dt
import earthaccess
import pydap
import matplotlib.pyplot as plt
import numpy as np

# import pydap-specific tools
from pydap.client import get_cmr_urls, open_url
from pydap.client import to_netcdf as dap_to_netcdf
from pydap.net import create_session

Autenticación EDL mediante earthaccess y OPeNDAP#

Puedes autenticarte mediante earthaccess como se muestra a continuacion. Debes tener una cuenta EDL válida. Hay dos estrategias para autenticarte con earthaccess:

  1. strategy="interactive". Esto pedirá tu usuario y contraseña de EDL.

  2. strategy="netrc". Usa esta opción si el notebook se ejecuta en un entorno donde se puede recuperar un .netrc con tus credenciales.

A continuación, el valor predeterminado será netrc, asumiendo que el usuario ejecutó el notebook Authenticate.ipynb. Si no es así, puedes cambiar la estrategia a "interactive".

from earthaccess.exceptions import LoginStrategyUnavailable
try:
    auth = earthaccess.login(strategy="netrc", persist=True) # you will be promted to add your EDL credentials
except LoginStrategyUnavailable:
    auth = earthaccess.login(strategy="interactive", persist=True)

# pass Token Authorization to a new Session.
my_session = create_session(auth.get_session())

Como encontrar URLs de OPeNDAP#

Usando la API CMR de NASA#

Consultamos el Common Metadata Repository (CMR por sus siglas en ingles) de la NASA para identificar archivos remotos que intersectan la siguiente área geográfica (caja delimitadora) y cubren el siguiente rango temporal:

  • -128.85 < longitud < -107.05, y 41.1 < latitud < 46.68

  • 03/01 - 04/30 (2025)

ECOSTRESS_ccid = "C2076114664-LPCLOUD"
bounding_box = [-128.847656, 41.112469, -107.050781, 46.679594]
time_range = [dt.datetime(2025, 3, 1), dt.datetime(2025, 3, 30)]

cmr_urls = get_cmr_urls(ccid=ECOSTRESS_ccid, bounding_box = bounding_box, limit=1000, time_range=time_range) # limit by default = 50

print("################################################ \n We found a total of ", len(cmr_urls), "OPeNDAP URLS!!!\n################################################")
################################################ 
 We found a total of  188 OPeNDAP URLS!!!
################################################

Acceso SOLO a metadatos con PyDAP#

Podemos acceder a metadatos producidos por OPeNDAP para identificar las variables de interés. En particular, aquellas asociadas con valores de latitud y longitud.

A continuación necesitamos solicitar los metadatos DAP4 al servidor remoto.

%%time
pyds = open_url(cmr_urls[0], protocol="dap4", session=my_session)
pyds.tree()
.ECOv002_L2_LSTE_37709_001_20250301T092419_0713_01.h5
├──L2 LSTE Metadata
│  ├──AncillaryNWP
│  ├──BandSpecification
│  ├──CloudMaxTemperature
│  ├──CloudMeanTemperature
│  ├──CloudMinTemperature
│  ├──CloudSDevTemperature
│  ├──Emis1GoodAvg
│  ├──Emis2GoodAvg
│  ├──Emis3GoodAvg
│  ├──Emis4GoodAvg
│  ├──Emis5GoodAvg
│  ├──LSTGoodAvg
│  ├──NWPSource
│  ├──NumberOfBands
│  ├──OrbitCorrectionPerformed
│  ├──QAPercentCloudCover
│  └──QAPercentGoodQuality
├──SDS
│  ├──Emis1
│  ├──Emis1_err
│  ├──Emis2
│  ├──Emis2_err
│  ├──Emis3
│  ├──Emis3_err
│  ├──Emis4
│  ├──Emis4_err
│  ├──Emis5
│  ├──Emis5_err
│  ├──EmisWB
│  ├──LST
│  ├──LST_err
│  ├──PWV
│  ├──QC
│  ├──cloud_mask
│  └──water_mask
└──StandardMetadata
   ├──AncillaryInputPointer
   ├──AutomaticQualityFlag
   ├──AutomaticQualityFlagExplanation
   ├──BuildID
   ├──CampaignShortName
   ├──CollectionLabel
   ├──DataFormatType
   ├──DayNightFlag
   ├──EastBoundingCoordinate
   ├──FieldOfViewObstruction
   ├──HDFVersionID
   ├──ImageLineSpacing
   ├──ImageLines
   ├──ImagePixelSpacing
   ├──ImagePixels
   ├──InputPointer
   ├──InstrumentShortName
   ├──LocalGranuleID
   ├──LongName
   ├──NorthBoundingCoordinate
   ├──PGEName
   ├──PGEVersion
   ├──PlatformLongName
   ├──PlatformShortName
   ├──PlatformType
   ├──ProcessingEnvironment
   ├──ProcessingLevelDescription
   ├──ProcessingLevelID
   ├──ProducerAgency
   ├──ProducerInstitution
   ├──ProductionDateTime
   ├──ProductionLocation
   ├──RangeBeginningDate
   ├──RangeBeginningTime
   ├──RangeEndingDate
   ├──RangeEndingTime
   ├──RegionID
   ├──SISName
   ├──SISVersion
   ├──SceneID
   ├──ShortName
   ├──SouthBoundingCoordinate
   ├──StartOrbitNumber
   ├──StopOrbitNumber
   └──WestBoundingCoordinate
CPU times: user 167 ms, sys: 21 ms, total: 188 ms
Wall time: 1.79 s

Subdividir datos#

Primero, queremos subdividir datos con base en los criterios:

  • Datos diurnos

  • QAPercentCloudCover <30%

  • y QAPercentGoodQuality> 70%

Para eso, necesitamos descargar solo 3 variables:

    ["/L2 LSTE Metadata/QAPercentCloudCover", 
     "/L2 LSTE Metadata/QAPercentGoodQuality",
     "/StandardMetadata/DayNightFlag"]
output_path = 'data/'

Descarga de datos al directorio local#

Descargaremos cada archivo remoto y los almacenaremos como un archivo individual, ya que estos no se pueden agregar.

%%time
dap_to_netcdf(cmr_urls, session=my_session, output_path = output_path, 
              keep_variables=["/L2 LSTE Metadata/QAPercentCloudCover", 
                              "/L2 LSTE Metadata/QAPercentGoodQuality",
                              "/StandardMetadata/DayNightFlag"]
             )
CPU times: user 170 ms, sys: 259 ms, total: 428 ms
Wall time: 12.4 s

Inspeccionar todos los archivos descargados#

Los datos ya se descargaron, y podemos filtrar más para identificar gránulos remotos por

  • Datos diurnos

  • QAPercentCloudCover <30%

  • y QAPercentGoodQuality> 70%

Luego actualizaremos nuestra lista de URLs desde las cuales descargar, con base en los archivos remotos que satisfacen los criterios anteriores.

NOTA: ¡Los archivos no se pueden agregar!

final_urls = []
for i in range(len(cmr_urls)):
    local_file = output_path+cmr_urls[i].split("/")[-1]+".nc4"
    dst = xr.open_datatree(local_file).load()
    if dst['L2 LSTE Metadata/QAPercentCloudCover'].values < 30 and dst["L2 LSTE Metadata/QAPercentGoodQuality"] > 70 and dst["/StandardMetadata/DayNightFlag"] == 'Day':
         final_urls.append(cmr_urls[i])

print("Total remote granules to download: ", len(final_urls))
Total remote granules to download:  15

Declara solo las variables de interes por descargar#

En este punto, es crucial eliminar cualquier dato ECOSTRESS descargado en el directorio data_output. Para eso abre una terminal y navega al directorio data.

import os
import glob

fnames = [output_path+f"{fname.split('/')[-1]}.nc4" for fname in cmr_urls]

for filename in fnames:
    try:
        os.remove(filename)
    except FileNotFoundError:
        print(f"The file '{filename}' is not in there anymore")    
# define all variables of interest that our final download will have

keep_vars = ["/StandardMetadata/EastBoundingCoordinate", 
             "/StandardMetadata/SouthBoundingCoordinate", 
             "/StandardMetadata/NorthBoundingCoordinate",
             "/StandardMetadata/WestBoundingCoordinate",
             "/StandardMetadata/DayNightFlag",
             "/StandardMetadata/ImagePixels",
             "/StandardMetadata/ImagePixelSpacing",
             "/StandardMetadata/ImageLines",
             "/StandardMetadata/RangeBeginningDate",
             "/StandardMetadata/RangeBeginningTime",
             "/StandardMetadata/RangeEndingDate",
             "/L2 LSTE Metadata/QAPercentCloudCover",
             "/L2 LSTE Metadata/QAPercentGoodQuality",
             "/SDS/QC", "/SDS/LST",
            ]

Descarga de datos#

No olvides usar ahora la lista final_urls, que apunta a nuestros gránulos de interés.

%%time
dap_to_netcdf(final_urls, session=my_session, output_path = output_path,
              keep_variables=keep_vars,
             )
CPU times: user 38.1 ms, sys: 132 ms, total: 170 ms
Wall time: 6.79 s

Inspeccionar archivos locales#

Los datos ya se descargaron.

NOTA: estos datasets no se pueden agregar.

local_file = output_path+final_urls[0].split("/")[-1]+".nc4"
dst = xr.open_datatree(local_file)
dst
<xarray.DataTree>
Group: /
├── Group: /SDS
│       Dimensions:  (dim1: 5632, dim2: 5400)
│       Dimensions without coordinates: dim1, dim2
│       Data variables:
│           LST      (dim1, dim2) float64 243MB ...
│           QC       (dim1, dim2) uint16 61MB ...
├── Group: /L2 LSTE Metadata
│       Dimensions:               (dim0: 1)
│       Dimensions without coordinates: dim0
│       Data variables:
│           QAPercentCloudCover   (dim0) int32 4B ...
│           QAPercentGoodQuality  (dim0) int32 4B ...
└── Group: /StandardMetadata
        Dimensions:                  (dim3: 1)
        Dimensions without coordinates: dim3
        Data variables:
            DayNightFlag             <U3 12B ...
            EastBoundingCoordinate   (dim3) float64 8B ...
            ImageLines               (dim3) int32 4B ...
            ImagePixelSpacing        (dim3) float64 8B ...
            ImagePixels              (dim3) int32 4B ...
            NorthBoundingCoordinate  (dim3) float64 8B ...
            RangeBeginningDate       <U10 40B ...
            RangeBeginningTime       <U15 60B ...
            RangeEndingDate          <U10 40B ...
            SouthBoundingCoordinate  (dim3) float64 8B ...
            WestBoundingCoordinate   (dim3) float64 8B ...
(dst["SDS/LST"]-273.15).plot(vmin=-9, vmax=5, cmap='nipy_spectral')
<matplotlib.collections.QuadMesh at 0x169c00380>
../_images/69a889c4f3549a0d8b5a2b3891e42076c2c21971d538d3801aedb3e82c732a25.png