Access Land Surface Temperature from NASA’s Ecostress
Requirements
- Earthdata login (EDL) credentials.
- Concept Collection ID or DOI for the relevant data product.
- Python >= 3.11.
- Mamba-forge (or conda-forge) installed on the machine.
- Familiarity with Jupyter notebooks and Jupyter Lab.
Optional:
- Store all EDL credentials in a
.netrcfile. - Basic knowledge of conda environment installation.
Objectives
To download one month of Land Surface Temperature data from an Ecostress data Collection. The spatial and temporal range is defined by the following parameters:
- Time range: 03/01/2025 – 04/30/2025.
- Spatial range: -128.8 < longitude < -107.05, and 41.1 < latitude < 46.6.
To accomplish this goal above, the tutorial will demonstrate how to:
- Authenticate (via earthaccess).
- Search for all available NASA OPeNDAP URLs for a specific NASA collection. The search will further filter by time range.
- Subset with OPeNDAP, by variable name and spatial / temporal range.
Install required python dependencies
In a terminal shell, use mamba or conda forge to install all required dependencies to run this tutorial and activate the environment to run an interactive jupyter notebook on a browser.
$ mamba create -n opendap_env -c conda-forge python=3.12 ipython jupyterlab earthaccess netCDF4
$ mamba activate opendap_env
$ pip install git+https://github.com/pydap/pydap.git
$ jupyter lab
Once in the jupyter notebook environment, import in the first cell all necessary methods that will be used to stream remote data into a local file:
import xarray as xr
import datetime as dt
import earthaccess
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
Finding OPeNDAP URLs with PyDAP
The needed parameter to search for all Land Surface Temperature data from ECOSTRESS available through OPeNDAP is
Concept Collection ID = C2076114664-LPCLOUD
Data from the above collection is a Level 2 data product, meaning SWATH data, and all remote files span different longitudes and latitudes. In this case, the necessary first step is to filter the search for all relevant data URLs by a bounding box. Later on, a further subset by coordinate values will be done by OPeNDAP.
To learn how to find the concept collection id for a specific data product, click the button below:
Below are the required parameters to search for all OPeNDAP URLs using PyDAP's get_cmr_urls:
ECOSTRESS_ccid = "C2076114664-LPCLOUD"
bounding_box = [-128.847656, 41.112469, -107.050781, 46.679594]
time_range = [dt.datetime(2025, 3, 1), dt.datetime(2025, 4, 30)]
# search for granules
cmr_urls = get_cmr_urls(ccid=ECOSTRESS_ccid, bounding_box = bounding_box, limit=1000, time_range=time_range) # limit by default = 50
EDL Authentication with earthaccess and OPeNDAP
There are various ways to authenticate with NASA, and here we will use earthaccess to retrieve a session object containing all required credentials to access data.
When using earthaccess to "login", you need to define a strategy and you have two options:
- If you already have a
.netrcfile with your EDL credentials stored in your machine, setstrategy="netrc" - If you DO NOT have a
.netrcfile with your EDL credentials, or you are not sure, do insteadstrategy="interactive"
from earthaccess.exceptions import LoginStrategyUnavailable
try:
auth = earthaccess.login(strategy="netrc", persist=True)
except LoginStrategyUnavailable:
# you will be prompted to add your EDL credentials
auth = earthaccess.login(strategy="interactive", persist=True)
# pass Token Authorization to a new Session.
my_session = session=auth.get_session()
The object my_session contains your EDL credentials, and it will be used to retrieve data from OPeNDAP. Moreover, by adding a persist=True as an argument to earthaccess.login, a .netrc is created to stored your EDL credentials in the machine for later reuse.
Use OPeNDAP to subset data by coordinate values and variable names
Subset by variable names
Below we use PyDAP to download the OPeNDAP DAP4 metadata. Pydap will create a Python representation of the dataset, including all variable names and their dimension, along with all metadata attributes associated with each variable. We will use this information to identify the variables of interest.
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
We are interested in only daylight data, with good quality information. What does good quality mean? For the purposes of this tutorial, we are interested in:
DaylightdataQAPercentCloudCover < 30%QAPercentGoodQuality > 70%
To further filter the remote data by the flags above, we ONLY download the relevant variables from each remote file
dap_to_netcdf(cmr_urls, session=my_session, output_path = output_path,
keep_variables=["/L2 LSTE Metadata/QAPercentCloudCover",
"/L2 LSTE Metadata/QAPercentGoodQuality",
"/StandardMetadata/DayNightFlag"]
)
The snippet of code above will download ONLY relevant metadata variables from ALL the remote files, and stored into individual files with a name matching the source file.
With the metadata variables already downloaded, we use the code below to identify which remote file satisfies are criteria:
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))
In our example, only 16 remote files from the initial 194 satisfy our criteria!
Finally, we download the data of interest for all the granules in the time period which satisfy our quality criteria, but not without before removing all recently downloaded data from:
$ cd $output_path
$ rm ECOv002_L2*.nc4
where $output_path should be replaced by the location of the files recently downloaded (this is necessary to avoid filename collision in the following step).
Subset by Variable Names
Finally, we are at the point where we want to download relevant data to Land Surface Temperature, along with many metadata flags. For this tutorial, we have the following variables of interest:
keep_vars = ["/StandardMetadata/EastBoundingCoordinate",
"/StandardMetadata/SouthBoundingCoordinate",
"/StandardMetadata/NorthBoundingCoordinate",
"/StandardMetadata/WestBoundingCoordinate",
"/StandardMetadata/DayNightFlag",
"/StandardMetadata/ImagePixels",
"/StandardMetadata/ImagePixelSpacing",
"/StandardMetadata/ImageLines",
"/StandardMetadata/RangeBeginningDate",
"/StandardMetadata/RangeBeginningTime",
"/StandardMetadata/RangeEndingDate",
"/StandardMetadata/RangeEndingTime",
"/L2 LSTE Metadata/QAPercentCloudCover",
"/L2 LSTE Metadata/QAPercentGoodQuality",
"/SDS/QC", "/SDS/LST","/SDS/water_mask"
]
See code in action below:

References
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
Cite this Tutorial
Jimenez-Urias, M. A. (2026). Access Land Surface Temperature Data From ECOSTRESS Via OPeNDAP. Zenodo. https://doi.org/10.5281/zenodo.19477049
@misc{jimenez_urias_2026_19477049,
author = {Jimenez-Urias, Miguel Angel},
title = {Access Land Surface Temperature Data From
ECOSTRESS Via OPeNDAP
},
month = apr,
year = 2026,
publisher = {Zenodo},
doi = {10.5281/zenodo.19477049},
url = {https://doi.org/10.5281/zenodo.19477049},
}
