•  Icon
  •  Icon
  •  Icon
  •  Icon

Access Along-track, Sea Surface Elevation from NASA’s SWOT Mission

Access Along-track, Sea Surface Elevation from NASA’s SWOT Mission
OPeNDAP enables parallel access to swath data from SWOT, selecting only the data of interest, subsetting close to the data so only the data of interest if downloaded.
Geospatial data icon About the Data
The dataset accessed in this tutorial is freely available, and the SWOT Level 2 Nadir Altimeter Geophysical Data Record (GDR) with Waveforms dataset produced by the Surface Water and Ocean Topography (SWOT) mission provides sea surface height, significant wave height and wind speed measurements from the Poseidon-3C nadir altimeter, a Jason-class dual frequency (Ku/C) altimeter. Source: NASA Earthdata.

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 .netrc file.
  • Basic knowledge of conda environment installation.

Objectives

To download 3 months of Sea Surface Height (SSH) and Sea Surface Temperature data in a region near Iceland over the Iceland-Faroe_Front. The spatial and temporal range is defined by the following parameters:

  • Time range: 01/09/2023 – 12/31/2023.
  • Spatial range: 20.7 < longitude < -4.3, and 60 < latitude < 65.67

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 Longitude, Latitude, and time range.
  • Subset with OPeNDAP, by variable name and spatial / temporal range.

Install required python dependencies

Terminal
$ 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:

Python
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 Sea Surface Height Swath (Level 2) data from the SWOT mission that is available through OPeNDAP is

Concept Collection ID = C2799438313-POCLOUD

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:

Python
swot_ccid = "C2799438313-POCLOUD"
time_range = [dt.datetime(2023, 9, 1), dt.datetime(2023, 12, 31)]

# select a region in the Iceland-Faroe Ridge
bbox = [-20.760731, 60.080727, -4.297294,65.675099] #[west, south, east, north]

cmr_urls = get_cmr_urls(ccid=swot_ccid, bounding_box = bbox, time_range=time_range, limit=1000) # you can incread the limit of results
cmr_urls[:6]

Jupyter Cell Output

['https://opendap.earthdata.nasa.gov/collections/C2799438313-POCLOUD/granules/SWOT_GPR_2PfP402_005_20230116_124113_20230116_133219',
 'https://opendap.earthdata.nasa.gov/collections/C2799438313-POCLOUD/granules/SWOT_GPS_2PfP402_005_20230116_124113_20230116_133219',
 'https://opendap.earthdata.nasa.gov/collections/C2799438313-POCLOUD/granules/SWOT_GPN_2PfP402_005_20230116_124113_20230116_133219',
 'https://opendap.earthdata.nasa.gov/collections/C2799438313-POCLOUD/granules/SWOT_GPN_2PfP402_016_20230116_220315_20230116_225421',
 'https://opendap.earthdata.nasa.gov/collections/C2799438313-POCLOUD/granules/SWOT_GPS_2PfP402_016_20230116_220315_20230116_225421',
 'https://opendap.earthdata.nasa.gov/collections/C2799438313-POCLOUD/granules/SWOT_GPR_2PfP402_016_20230116_220315_20230116_225421']

NOTE: The CMR search yields OPeNDAP URLs that below to two categories:

  • Standard Product
  • Expert Products.

It is important to identidy whether a CMR search for a Collection data yields uniform results or not. In this case it does not, and we neeed to further filter these. We are interested int he "Standard Product".

Python

# Identify the STANDARD Data Product (There is also the Expert data Product
swot_standard_urls = [url for url in cmr_urls if url.split('/')[-1][:8]=='SWOT_GPN']

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:

  1. If you already have a .netrc file with your EDL credentials stored in your machine, set strategy="netrc"
  2. If you DO NOT have a .netrc file with your EDL credentials, or you are not sure, do instead strategy="interactive"
Python
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

One may think that the CMR search for OPeNDAP URLs already returned the desired subset of data when we provided a bounding box in the CMR query. That is unfortunately not the case. The CMR did filter using the bounding box, and returns ALL OPeNDAP URLs containing data within the bounding box. This is, it is usually the case that an OPeNDAP returned from the CMR query contains only 1% of data within the bounding box, as opposed to all data within the bounding box.

How can I download only the data within the bounding box? This can be done with OPeNDAP in a two stage process.

  1. Download ONLY coordinate data from each granule, to identify the slice that is needed to download only the relevant data from out desired data of interest.
  2. Use the identified slice from each OPeNDAP URL, to stream data into a local file for analysis.

Stage 1

For SWATH data, Coordinate arrays such as Latitude and Longitude are NOT the dimensions of the dataset. Typically time or Track are the dimensions of SWATH data. Moreover, Latitude is usually NOT monotonic, but Longitude is! Below we download time and Longitude from ALL identified OPeNDAP data URLs and idenfity the slice for the time dimension that yields data within out bounding box.

NOTE: We are choosing NOT to download Latitude to speed up the workflow. Latitude and Longitude arrays can be very large for the newer data products! Identifying the slice for the time dimension that produces Longitude data within our bounding box will likely produce Latitude data within the Bounding box. Any further subset can take place once data has been downloaded.

Below is the code used that

  • Subsets by Variable Name: Only time and Longitude will be downloaded.
  • Identifies the time slice: Iterates over all downloaded granules to identify the logical slice that, when applied to time, it produces Longitude data within the bounding box.
  • Cleans: Removes all downloaded data to avoid name collision in the Stage 2.
Python
# Create a PyDAP Dataset Object to Identify Variable Names
pyds = open_url(swot_standard_urls[0], protocol="dap4", session=my_session)
pyds.tree()
HDF5 Tree
.SWOT_GPN_2PfP418_005_20230201_101117_20230201_110222.nc
β”œβ”€β”€data_01
β”‚  β”œβ”€β”€c
β”‚  β”‚  β”œβ”€β”€swh_cor_ocean_net_instr_qual
β”‚  β”‚  β”œβ”€β”€sea_state_bias_mle3
β”‚  β”‚  β”œβ”€β”€sig0_ocean_numval
β”‚  β”‚  β”œβ”€β”€agc
β”‚  β”‚  β”œβ”€β”€swh_cor_ocean_net_instr
β”‚  β”‚  β”œβ”€β”€range_ocean
β”‚  β”‚  β”œβ”€β”€swh_ocean_rms
β”‚  β”‚  β”œβ”€β”€agc_rms
β”‚  β”‚  β”œβ”€β”€sig0_ocean
β”‚  β”‚  β”œβ”€β”€range_ocean_compression_qual
β”‚  β”‚  β”œβ”€β”€range_cor_ocean_net_instr_qual
β”‚  β”‚  β”œβ”€β”€sea_state_bias_adaptive
β”‚  β”‚  β”œβ”€β”€sig0_ocean_rms
β”‚  β”‚  β”œβ”€β”€sea_state_bias
β”‚  β”‚  β”œβ”€β”€range_ocean_rms
β”‚  β”‚  β”œβ”€β”€alt_state_band_status_flag
β”‚  β”‚  β”œβ”€β”€sig0_ocean_compression_qual
β”‚  β”‚  β”œβ”€β”€swh_ocean_numval
β”‚  β”‚  β”œβ”€β”€swh_ocean
β”‚  β”‚  β”œβ”€β”€sig0_cor_ocean_net_instr_qual
β”‚  β”‚  β”œβ”€β”€swh_ocean_compression_qual
β”‚  β”‚  β”œβ”€β”€sig0_cor_atm
β”‚  β”‚  β”œβ”€β”€range_cor_ocean_net_instr
β”‚  β”‚  β”œβ”€β”€alt_state_c_band_flag
β”‚  β”‚  β”œβ”€β”€range_ocean_numval
β”‚  β”‚  β”œβ”€β”€agc_numval
β”‚  β”‚  └──sig0_cor_ocean_net_instr
β”‚  β”œβ”€β”€ku
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_filtered_mle3
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_filtered_adaptive_qual
β”‚  β”‚  β”œβ”€β”€swh_cor_ocean_net_instr_qual
β”‚  β”‚  β”œβ”€β”€range_ocean_mle3_rms
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_adaptive
β”‚  β”‚  β”œβ”€β”€swh_cor_adaptive_net_instr
β”‚  β”‚  β”œβ”€β”€sea_state_bias_mle3
β”‚  β”‚  β”œβ”€β”€off_nadir_angle_wf_ocean_rms
β”‚  β”‚  β”œβ”€β”€wvf_main_class
β”‚  β”‚  β”œβ”€β”€range_cor_ocean_mle3_net_instr
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_filtered_mle3_qual
β”‚  β”‚  β”œβ”€β”€swh_ocean_mle3_compression_qual
β”‚  β”‚  β”œβ”€β”€sig0_ocean_numval
β”‚  β”‚  β”œβ”€β”€agc
β”‚  β”‚  β”œβ”€β”€swh_cor_ocean_net_instr
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_filtered
β”‚  β”‚  β”œβ”€β”€range_ocean
β”‚  β”‚  β”œβ”€β”€ssha_mle3
β”‚  β”‚  β”œβ”€β”€sig0_adaptive_rms
β”‚  β”‚  β”œβ”€β”€swh_ocean_rms
β”‚  β”‚  β”œβ”€β”€agc_rms
β”‚  β”‚  β”œβ”€β”€sig0_adaptive
β”‚  β”‚  β”œβ”€β”€sig0_ocean
β”‚  β”‚  β”œβ”€β”€range_ocean_compression_qual
β”‚  β”‚  β”œβ”€β”€sig0_ocean_mle3_numval
β”‚  β”‚  β”œβ”€β”€range_cor_ocean_net_instr_qual
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_filtered_qual
β”‚  β”‚  β”œβ”€β”€sea_state_bias_adaptive
β”‚  β”‚  β”œβ”€β”€range_adaptive_compression_qual
β”‚  β”‚  β”œβ”€β”€swh_ocean_mle3_numval
β”‚  β”‚  β”œβ”€β”€sig0_ocean_mle3
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_filtered_adaptive
β”‚  β”‚  β”œβ”€β”€off_nadir_angle_wf_ocean_numval
β”‚  β”‚  β”œβ”€β”€sig0_ocean_rms
β”‚  β”‚  β”œβ”€β”€range_cor_adaptive_net_instr
β”‚  β”‚  β”œβ”€β”€sea_state_bias
β”‚  β”‚  β”œβ”€β”€range_ocean_mle3_compression_qual
β”‚  β”‚  β”œβ”€β”€range_ocean_rms
β”‚  β”‚  β”œβ”€β”€range_adaptive
β”‚  β”‚  β”œβ”€β”€alt_state_band_status_flag
β”‚  β”‚  β”œβ”€β”€sig0_ocean_compression_qual
β”‚  β”‚  β”œβ”€β”€swh_ocean_numval
β”‚  β”‚  β”œβ”€β”€off_nadir_angle_wf_ocean
β”‚  β”‚  β”œβ”€β”€swh_adaptive_numval
β”‚  β”‚  β”œβ”€β”€swh_adaptive_compression_qual
β”‚  β”‚  β”œβ”€β”€swh_ocean
β”‚  β”‚  β”œβ”€β”€iono_cor_gim
β”‚  β”‚  β”œβ”€β”€sig0_cor_ocean_net_instr_qual
β”‚  β”‚  β”œβ”€β”€iono_cor_alt_mle3
β”‚  β”‚  β”œβ”€β”€sea_state_bias_3d_mp2
β”‚  β”‚  β”œβ”€β”€sea_state_bias_adaptive_3d_mp2
β”‚  β”‚  β”œβ”€β”€sig0_cor_adaptive_net_instr
β”‚  β”‚  β”œβ”€β”€sig0_adaptive_numval
β”‚  β”‚  β”œβ”€β”€sig0_cor_ocean_mle3_net_instr
β”‚  β”‚  β”œβ”€β”€swh_cor_ocean_mle3_net_instr
β”‚  β”‚  β”œβ”€β”€swh_ocean_compression_qual
β”‚  β”‚  β”œβ”€β”€sig0_cor_atm
β”‚  β”‚  β”œβ”€β”€off_nadir_angle_wf_ocean_compression_qual
β”‚  β”‚  β”œβ”€β”€range_ocean_mle3
β”‚  β”‚  β”œβ”€β”€range_cor_ocean_net_instr
β”‚  β”‚  β”œβ”€β”€range_ocean_mle3_numval
β”‚  β”‚  β”œβ”€β”€ssha
β”‚  β”‚  β”œβ”€β”€range_adaptive_numval
β”‚  β”‚  β”œβ”€β”€swh_adaptive_rms
β”‚  β”‚  β”œβ”€β”€sig0_adaptive_compression_qual
β”‚  β”‚  β”œβ”€β”€range_adaptive_rms
β”‚  β”‚  β”œβ”€β”€swh_adaptive
β”‚  β”‚  β”œβ”€β”€iono_cor_alt
β”‚  β”‚  β”œβ”€β”€swh_ocean_mle3_rms
β”‚  β”‚  β”œβ”€β”€swh_ocean_mle3
β”‚  β”‚  β”œβ”€β”€range_ocean_numval
β”‚  β”‚  β”œβ”€β”€sig0_ocean_mle3_compression_qual
β”‚  β”‚  β”œβ”€β”€agc_numval
β”‚  β”‚  β”œβ”€β”€sig0_ocean_mle3_rms
β”‚  β”‚  └──sig0_cor_ocean_net_instr
β”‚  β”œβ”€β”€time
β”‚  β”œβ”€β”€longitude
β”‚  β”œβ”€β”€latitude
β”‚  β”œβ”€β”€wind_speed_mod_v
β”‚  β”œβ”€β”€mean_dynamic_topography_interp_qual
β”‚  β”œβ”€β”€ocean_tide_fes_interp_qual
β”‚  β”œβ”€β”€mean_sea_surface_dtu
β”‚  β”œβ”€β”€wave_model_map_availability_flag
β”‚  β”œβ”€β”€rad_water_vapor
β”‚  β”œβ”€β”€sst
β”‚  β”œβ”€β”€rad_side_1_land_frac_340
β”‚  β”œβ”€β”€wind_speed_mod_u
β”‚  β”œβ”€β”€distance_to_coast
β”‚  β”œβ”€β”€meteo_measurement_altitude_interp_qual
β”‚  β”œβ”€β”€model_dry_tropo_cor_measurement_altitude
β”‚  β”œβ”€β”€rad_tmb_187
β”‚  β”œβ”€β”€meteo_zero_altitude_interp_qual
β”‚  β”œβ”€β”€pole_tide
β”‚  β”œβ”€β”€rad_tmb_238
β”‚  β”œβ”€β”€surface_slope_cor
β”‚  β”œβ”€β”€rad_side_2_distance_to_land
β”‚  β”œβ”€β”€rad_side_2_land_frac_238
β”‚  β”œβ”€β”€ocean_tide_non_eq
β”‚  β”œβ”€β”€rad_side_2_rain_flag
β”‚  β”œβ”€β”€rad_tb_238
β”‚  β”œβ”€β”€depth_or_elevation
β”‚  β”œβ”€β”€rad_tb_340
β”‚  β”œβ”€β”€rad_tb_187_qual
β”‚  β”œβ”€β”€rad_tb_238_qual
β”‚  β”œβ”€β”€sea_ice_concentration_interp_qual
β”‚  β”œβ”€β”€rad_side_1_distance_to_land
β”‚  β”œβ”€β”€orb_state_diode_flag
β”‚  β”œβ”€β”€rad_side_1_land_frac_187
β”‚  β”œβ”€β”€sig0_cor_atm_source
β”‚  β”œβ”€β”€rad_side_2_sea_ice_flag
β”‚  β”œβ”€β”€rad_side_2_land_frac_340
β”‚  β”œβ”€β”€alt_state_band_seq_flag
β”‚  β”œβ”€β”€rain_rate
β”‚  β”œβ”€β”€rad_wet_tropo_cor
β”‚  β”œβ”€β”€model_dry_tropo_cor_zero_altitude
β”‚  β”œβ”€β”€solid_earth_tide
β”‚  β”œβ”€β”€load_tide_got
β”‚  β”œβ”€β”€dac
β”‚  β”œβ”€β”€rad_side_1_surface_type_flag
β”‚  β”œβ”€β”€surface_classification_flag
β”‚  β”œβ”€β”€time_tai
β”‚  β”œβ”€β”€altitude
β”‚  β”œβ”€β”€internal_tide_hret_interp_qual
β”‚  β”œβ”€β”€rad_side_1_rain_flag
β”‚  β”œβ”€β”€rad_side_1_sea_ice_flag
β”‚  β”œβ”€β”€mean_wave_direction
β”‚  β”œβ”€β”€ocean_tide_eq
β”‚  β”œβ”€β”€ice_flag
β”‚  β”œβ”€β”€rad_tmb_340
β”‚  β”œβ”€β”€rad_wet_tropo_cor_interp_qual
β”‚  β”œβ”€β”€rad_tb_340_qual
β”‚  β”œβ”€β”€rad_tb_187
β”‚  β”œβ”€β”€inv_bar_cor
β”‚  β”œβ”€β”€geoid
β”‚  β”œβ”€β”€index_first_20hz_measurement
β”‚  β”œβ”€β”€ocean_tide_fes
β”‚  β”œβ”€β”€mean_dynamic_topography
β”‚  β”œβ”€β”€rad_side_2_surface_type_flag
β”‚  β”œβ”€β”€internal_tide_hret
β”‚  β”œβ”€β”€rad_side_2_land_frac_187
β”‚  β”œβ”€β”€ocean_tide_got
β”‚  β”œβ”€β”€altitude_rate
β”‚  β”œβ”€β”€rad_wind_speed
β”‚  β”œβ”€β”€wave_model_interp_qual
β”‚  β”œβ”€β”€orb_state_rest_flag
β”‚  β”œβ”€β”€angle_of_approach_to_coast
β”‚  β”œβ”€β”€meteo_map_availability_flag
β”‚  β”œβ”€β”€wind_speed_alt
β”‚  β”œβ”€β”€wind_speed_alt_adaptive
β”‚  β”œβ”€β”€load_tide_fes
β”‚  β”œβ”€β”€wind_speed_alt_mle3
β”‚  β”œβ”€β”€rain_flag
β”‚  β”œβ”€β”€mean_sea_surface_dtu_interp_qual
β”‚  β”œβ”€β”€mean_sea_surface_cnescls
β”‚  β”œβ”€β”€sea_ice_concentration
β”‚  β”œβ”€β”€mean_wave_period_t02
β”‚  β”œβ”€β”€rad_cloud_liquid_water
β”‚  β”œβ”€β”€model_wet_tropo_cor_zero_altitude
β”‚  β”œβ”€β”€ocean_tide_got_interp_qual
β”‚  β”œβ”€β”€mean_sea_surface_cnescls_interp_qual
β”‚  β”œβ”€β”€rad_side_1_land_frac_238
β”‚  β”œβ”€β”€numtotal_20hz_measurement
β”‚  └──model_wet_tropo_cor_measurement_altitude
└──data_20
   β”œβ”€β”€c
   β”‚  β”œβ”€β”€ice2_qual
   β”‚  β”œβ”€β”€range_ice2
   β”‚  β”œβ”€β”€sigmal_ice2
   β”‚  β”œβ”€β”€range_ocean
   β”‚  β”œβ”€β”€sig0_ocean
   β”‚  β”œβ”€β”€range_ocean_compression_qual
   β”‚  β”œβ”€β”€peakiness
   β”‚  β”œβ”€β”€sig0_ocean_compression_qual
   β”‚  β”œβ”€β”€swh_ocean
   β”‚  β”œβ”€β”€range_ocog
   β”‚  β”œβ”€β”€swh_ocean_compression_qual
   β”‚  β”œβ”€β”€num_iterations_ocean
   β”‚  β”œβ”€β”€mqe_ocean
   β”‚  β”œβ”€β”€slope2_ice2
   β”‚  β”œβ”€β”€mqe_ice2
   β”‚  β”œβ”€β”€sig0_ocog
   β”‚  β”œβ”€β”€sig0_ice2
   β”‚  β”œβ”€β”€slope1_ice2
   β”‚  └──sig0_leading_edge_ice2
   β”œβ”€β”€ku
   β”‚  β”œβ”€β”€seaice_qual
   β”‚  β”œβ”€β”€range_seaice
   β”‚  β”œβ”€β”€num_iterations_adaptive
   β”‚  β”œβ”€β”€ice2_qual
   β”‚  β”œβ”€β”€mqe_ocean_mle3
   β”‚  β”œβ”€β”€wvf_main_class
   β”‚  β”œβ”€β”€range_ice2
   β”‚  β”œβ”€β”€swh_ocean_mle3_compression_qual
   β”‚  β”œβ”€β”€sigmal_ice2
   β”‚  β”œβ”€β”€num_iterations_ocean_mle3
   β”‚  β”œβ”€β”€range_ocean
   β”‚  β”œβ”€β”€sig0_adaptive
   β”‚  β”œβ”€β”€sig0_ocean
   β”‚  β”œβ”€β”€range_ocean_compression_qual
   β”‚  β”œβ”€β”€mqe_adaptive
   β”‚  β”œβ”€β”€range_tfmra
   β”‚  β”œβ”€β”€range_adaptive_compression_qual
   β”‚  β”œβ”€β”€sig0_seaice
   β”‚  β”œβ”€β”€sig0_ocean_mle3
   β”‚  β”œβ”€β”€sig0_tfmra
   β”‚  β”œβ”€β”€peakiness
   β”‚  β”œβ”€β”€range_ocean_mle3_compression_qual
   β”‚  β”œβ”€β”€tfmra_qual
   β”‚  β”œβ”€β”€range_adaptive
   β”‚  β”œβ”€β”€sig0_ocean_compression_qual
   β”‚  β”œβ”€β”€off_nadir_angle_wf_ocean
   β”‚  β”œβ”€β”€swh_adaptive_compression_qual
   β”‚  β”œβ”€β”€swh_ocean
   β”‚  β”œβ”€β”€range_ocog
   β”‚  β”œβ”€β”€ocog_qual
   β”‚  β”œβ”€β”€swh_ocean_compression_qual
   β”‚  β”œβ”€β”€off_nadir_angle_wf_ocean_compression_qual
   β”‚  β”œβ”€β”€range_ocean_mle3
   β”‚  β”œβ”€β”€num_iterations_ocean
   β”‚  β”œβ”€β”€mqe_ocean
   β”‚  β”œβ”€β”€sig0_adaptive_compression_qual
   β”‚  β”œβ”€β”€swh_adaptive
   β”‚  β”œβ”€β”€slope2_ice2
   β”‚  β”œβ”€β”€mqe_ice2
   β”‚  β”œβ”€β”€swh_ocean_mle3
   β”‚  β”œβ”€β”€sig0_ocean_mle3_compression_qual
   β”‚  β”œβ”€β”€sig0_ocog
   β”‚  β”œβ”€β”€sig0_ice2
   β”‚  β”œβ”€β”€slope1_ice2
   β”‚  └──sig0_leading_edge_ice2
   β”œβ”€β”€time
   β”œβ”€β”€longitude
   β”œβ”€β”€latitude
   β”œβ”€β”€model_dry_tropo_cor_measurement_altitude
   β”œβ”€β”€surface_slope_cor
   β”œβ”€β”€distance_to_coast
   β”œβ”€β”€meteo_measurement_altitude_interp_qual
   β”œβ”€β”€index_1hz_measurement
   β”œβ”€β”€alt_state_acq_mode_flag
   β”œβ”€β”€surface_classification_flag
   β”œβ”€β”€time_tai
   β”œβ”€β”€altitude
   β”œβ”€β”€angle_of_approach_to_coast
   β”œβ”€β”€alt_state_track_trans_flag
   └──model_wet_tropo_cor_measurement_altitude

Python
# Declare Variable Names by their Fully Qualifying Name (FQN) similar to a filepath

Variables = [
    "/data_01/time", 
    "/data_01/longitude",
    "/data_20/time",
    "/data_20/longitude",
]


output_path = "data/" # <-------- Adapt for your case

Download coordinate data

Python
dap_to_netcdf(swot_standard_urls, 
              session=my_session, 
              keep_variables=Variables,
              output_path=output_path)

Having downloaded the data, we now identify the index values of Longitude that, for each downloaded granule, yield data within our bounding box.

Python

## get min lat/lon from 
minLon, maxLon = bbox[0], bbox[2]
slices = []

# Longitude is spans 0, 360 values. The bounding box has -180,180 format
# So an extra step to turn downloaded lon values into -180,180 format is applied below

for url in swot_standard_urls:
    filename = output_path+f"{url.split('/')[-1]}.nc4"
    dt1 = xr.open_datatree(filename).load()

    # find index /data_01/longitude
    longitude = dt1['data_01/longitude']
    longitude = longitude.where(longitude <=180,  longitude -360)
    mask = (longitude >= minLon) & (longitude <= maxLon)
    idx = np.nonzero(mask.values)[0]

    # find index /data_20/longitude
    longitude = dt1['data_20/longitude']
    longitude = longitude.where(longitude <=180,  longitude -360)
    mask = (longitude >= minLon) & (longitude <= maxLon)
    idx2 = np.nonzero(mask.values)[0]

    # create slice for each granule
    slices.append({"/data_01/time":(idx[0],idx[-1]), 
                   "/data_20/time": (idx2[0],idx2[-1])})

# inspect the slices to the first 4 remote granules on List
print(slices[:4])
Jupyter Cell Output

[{'/data_01/time': (196, 365), '/data_20/time': (3793, 7199)},
 {'/data_01/time': (1875, 2436), '/data_20/time': (37341, 48583)},
 {'/data_01/time': (241, 465), '/data_20/time': (4581, 9082)},
 {'/data_01/time': (2031, 2471), '/data_20/time': (40501, 49316)}]

Finally we clean the downloaded data to avoid filename collisions with the data download in the next step (NOTE replace output_path with your own!)

Terminal
$ cd path_to_data_replace_here
$ rm SWOT_GPN*.nc4

Stage 2

Now we stream all the data of interest, applying the subset to each remote granule that we just calculated

Python

# Define all Variables of interest using their Fully Qualifying Name
Variables = [
    "/data_01/time", "/data_01/longitude", "/data_01/latitude", 
    "/data_01/sst", "/data_01/mean_dynamic_topography_interp_qual",
    "/data_01/surface_classification_flag", "/data_01/ice_flag",
    "/data_01/mean_dynamic_topography", "/data_01/rain_flag",
    "/data_20/time", "/data_20/longitude", "/data_20/latitude", 
    "/data_20/distance_to_coast",
]

# Stream data to local file directory
dap_to_netcdf(swot_standard_urls, 
              session=my_session, 
              keep_variables=Variables, 
              dim_slices = slices, 
              output_path=output_path)

See the code in action below!

References

SWOT. (2024). SWOT Level 2 Nadir Altimeter Geophysical Data Record with Waveforms Version 2.0 [Data set]. NASA Physical Oceanography Distributed Active Archive Center. https://doi.org/10.5067/SWOT-NALT-GDR-2.0

Cite this Tutorial

Citation
Jimenez-Urias, M. A. (2026). Access Sea Surface Height (SWATH) Data from SWOT. Zenodo. https://doi.org/10.5281/zenodo.19598977
BibTeX
@misc{jimenez_urias_2026_19598977,
  author       = {Jimenez-Urias, Miguel Angel},
  title        = {Access Sea Surface Height (SWATH) Data from SWOT},
  month        = apr,
  year         = 2026,
  publisher    = {Zenodo},
  doi          = {10.5281/zenodo.19598977},
  url          = {https://doi.org/10.5281/zenodo.19598977},
}