Subset SWOT LR L2 Unsmoothed data from AVISO’s FTP Server
This notebook explains how to select and retrieve Unsmoothed (250-m) SWOT LR L2 half orbits from AVISO’s FTP Server, and subset data with a geographical area.
L2 Unsmoothed data can be explored at:
You need to have xarray
, numpy
, pydap
, threddsclient
and matplotlib
+cartopy
(for visualisation) packages installed in your Python environment for this notebook to work.
Tutorial Objectives
Download files through FTP, with selection by cycle and pass numbers
Subset data with geographical selection
Import + code
[1]:
# Install Cartopy with mamba to avoid discrepancies
# ! mamba install -q -c conda-forge cartopy
[2]:
import warnings
warnings.filterwarnings('ignore')
[3]:
import os
from getpass import getpass
import numpy as np
import ftplib
import xarray as xr
[4]:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
[5]:
def _download_file(ftp:str, filename:str, target_directory:str):
print(f"Download file: {filename}")
try:
local_filepath = os.path.join(target_directory, filename)
with open(local_filepath, 'wb') as file:
ftp.retrbinary('RETR %s' % filename, file.write)
return local_filepath
except Exception as e:
print(f"Error downloading {filename}: {e}")
def _get_last_version_filename(filenames):
versions = {int(f[-5:-3]): f for f in filenames}
return versions[max(versions.keys())]
def _select_filename(filenames, only_last):
if not only_last: return filenames
return [_get_last_version_filename(filenames)]
def ftp_download_files(ftp_path, level, variant, cycle_numbers, half_orbits, output_dir, only_last=True):
""" Download half orbits files from AVISO's FTP Server.
Args:
ftp_path
path of the FTP fileset
level
L2 or L3
variant
Basic, Expert, WindWave or Unsmoothed
cycle_numbers
list of cycles numbers
half_orbits
list of passes numbers
output_dir
output directory
only_last
if True (Default), downloads only the last version of a file if several versions exist.
Downloads all versions otherwise.
Returns:
The list of local files.
"""
# Set up FTP server details
ftpAVISO = 'ftp-access.aviso.altimetry.fr'
try:
# Logging into FTP server using provided credentials
with ftplib.FTP(ftpAVISO) as ftp:
ftp.login(username, password)
ftp.cwd(ftp_path)
print(f"Connection Established {ftp.getwelcome()}")
downloaded_files = []
for cycle in cycle_numbers:
cycle_str = '{:03d}'.format(cycle)
cycle_dir = f'cycle_{cycle_str}'
print(ftp_path+cycle_dir)
ftp.cwd(cycle_dir)
for half_orbit in half_orbits:
half_orbit_str = '{:03d}'.format(half_orbit)
pattern = f'SWOT_{level}_LR_SSH_{variant}_{cycle_str}_{half_orbit_str}'
filenames = []
try:
filenames = ftp.nlst(f'{pattern}_*')
# No version in L3 filenames
if level=="L3": only_last=False
filenames = _select_filename(filenames, only_last)
except Exception as e:
print(f"No pass {half_orbit}")
local_files = [_download_file(ftp, f, output_dir) for f in filenames]
downloaded_files += local_files
ftp.cwd('../')
return downloaded_files
except ftplib.error_perm as e:
print(f"FTP error: {e}")
except Exception as e:
print(f"Error: {e}")
def _normalized_ds(ds, lon_min, lon_max):
lon = ds.longitude.values
lon[lon < lon_min] += 360
lon[lon > lon_max] -= 360
ds.longitude.values = lon
return ds
def _subset_ds(file, group, variables, lon_range, lat_range):
swot_ds = xr.open_dataset(file, group=group)
swot_ds = swot_ds[variables]
swot_ds.load()
ds = _normalized_ds(swot_ds.copy(), -180, 180)
mask = (
(ds.longitude <= lon_range[1])
& (ds.longitude >= lon_range[0])
& (ds.latitude <= lat_range[1])
& (ds.latitude >= lat_range[0])
).compute()
swot_ds_area = swot_ds.where(mask, drop=True)
for var in list(swot_ds_area.keys()):
swot_ds_area[var].encoding = {'zlib':True, 'complevel':5}
return swot_ds_area
def _to_netcdf(ds_root, ds_left, ds_right, output_file):
""" Writes the dataset to a netcdf file """
ds_root.to_netcdf(output_file)
ds_right.to_netcdf(output_file, group='right', mode='a')
ds_left.to_netcdf(output_file, group='left', mode='a')
def _subset_grouped_ds(file, variables, lon_range, lat_range, output_dir):
print(f"Subset dataset: {file}")
ds_root = xr.open_dataset(file)
swot_ds_right = _subset_ds(file, 'right', variables, lon_range, lat_range)
swot_ds_left = _subset_ds(file, 'left', variables, lon_range, lat_range)
if swot_ds_right.sizes['num_lines'] == 0 and swot_ds_left.sizes['num_lines'] == 0:
print(f'Dataset {file} not matching geographical area.')
return None
filename = f"subset_{os.path.basename(file)}"
print(f"Store subset: {filename}")
filepath = os.path.join(output_dir, filename)
_to_netcdf(ds_root, swot_ds_left, swot_ds_right, filepath)
return filepath
def subset_files(filenames, variables, lon_range, lat_range, output_dir):
""" Subset datasets with geographical area.
Args:
filenames
the filenames of datasets to subset
variables
variables to select
lon_range
the longitude range
lat_range
the latitude range
output_dir
output directory
Returns:
The list of subsets files.
"""
return [subset_file for subset_file in [_subset_grouped_ds(f, variables, lon_range, lat_range, output_dir) for f in filenames] if subset_file is not None]
def _interpolate_coords(ds):
lon = ds.longitude
lat = ds.latitude
shape = lon.shape
lon = np.array(lon).ravel()
lat = np.array(lat).ravel()
dss = xr.Dataset({
'longitude': xr.DataArray(
data = lon,
dims = ['time']
),
'latitude': xr.DataArray(
data = lat,
dims = ['time'],
)
},)
dss_interp = dss.interpolate_na(dim="time", method="linear", fill_value="extrapolate")
ds['longitude'] = (('num_lines', 'num_pixels'), dss_interp.longitude.values.reshape(shape))
ds['latitude'] = (('num_lines', 'num_pixels'), dss_interp.latitude.values.reshape(shape))
def normalize_coordinates(ds):
""" Normalizes the coordinates of the dataset : interpolates Nan values in lon/lat, and assign lon/lat as coordinates.
Args:
ds: the dataset
Returns:
xr.Dataset: the normalized dataset
"""
_interpolate_coords(ds)
return ds.assign_coords(
{"longitude": ds.longitude, "latitude": ds.latitude}
)
Parameters
Define existing output folder to save results:
[6]:
output_dir = "downloads"
Authentication parameters
Enter your AVISO+ credentials
[ ]:
username = input("Enter username:")
[ ]:
password = getpass(f"Enter password for {username}:")
Data parameters
Variables available in unsmoothed Swot LR L2 data are:
time
time_tai
latitude
longitude
latitude_uncert
longitude_uncert
polarization_karin
ssh_karin_2
ssh_karin_2_qual
ssh_karin_uncert
sig0_karin_2
sig0_karin_2_qual
sig0_karin_uncert
total_coherence
mean_sea_surface_cnescls
miti_power_250m
miti_power_var_250m
ancillary_surface_classification_flag
Define the variables you want:
[9]:
variables = ['latitude', 'longitude', 'time', 'sig0_karin_2', 'ancillary_surface_classification_flag', 'ssh_karin_2', 'ssh_karin_2_qual']
Define a geographical area
[10]:
# California
lat_range = 35, 42
lon_range = -127, -121
localbox = [lon_range[0], lon_range[1], lat_range[0], lat_range[1]]
localbox
[10]:
[-127, -121, 35, 42]
Define the FTP filepath
[11]:
ftp_path = '/swot_products/l2_karin/l2_lr_ssh/PIC0/Unsmoothed/'
# For selecting files with a regex pattern
variant = "Unsmoothed"
level = "L2"
Define data parameters
Note
Passes matching a geographical area and period can be found using this tutorial
[12]:
# Define cycles and half_orbits numbers to download
cycle_numbers = [20]
pass_numbers = [11, 24, 39, 52, 67, 274, 289, 302, 317]
Download files through FTP
[13]:
downloaded_files = ftp_download_files(ftp_path, level, variant, cycle_numbers, pass_numbers, output_dir, only_last=True)
Connection Established 220 192.168.10.119 FTP server ready
/swot_products/l2_karin/l2_lr_ssh/PIC0/Unsmoothed/cycle_020
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_011_20240821T002444_20240821T011532_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_024_20240821T113333_20240821T122500_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_039_20240822T002515_20240822T011604_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_052_20240822T113404_20240822T122531_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_067_20240823T002546_20240823T011634_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_274_20240830T095520_20240830T104647_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_289_20240830T224702_20240830T233829_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_302_20240831T095551_20240831T104718_PIC0_01.nc
Download file: SWOT_L2_LR_SSH_Unsmoothed_020_317_20240831T224734_20240831T233901_PIC0_01.nc
[14]:
downloaded_files
[14]:
['downloads/SWOT_L2_LR_SSH_Unsmoothed_020_011_20240821T002444_20240821T011532_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_024_20240821T113333_20240821T122500_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_039_20240822T002515_20240822T011604_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_052_20240822T113404_20240822T122531_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_067_20240823T002546_20240823T011634_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_274_20240830T095520_20240830T104647_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_289_20240830T224702_20240830T233829_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_302_20240831T095551_20240831T104718_PIC0_01.nc',
'downloads/SWOT_L2_LR_SSH_Unsmoothed_020_317_20240831T224734_20240831T233901_PIC0_01.nc']
Subset data in the required geographical area
[15]:
subset_filenames = subset_files(downloaded_files, variables, lon_range, lat_range, output_dir)
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_011_20240821T002444_20240821T011532_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_011_20240821T002444_20240821T011532_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_024_20240821T113333_20240821T122500_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_024_20240821T113333_20240821T122500_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_039_20240822T002515_20240822T011604_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_039_20240822T002515_20240822T011604_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_052_20240822T113404_20240822T122531_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_052_20240822T113404_20240822T122531_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_067_20240823T002546_20240823T011634_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_067_20240823T002546_20240823T011634_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_274_20240830T095520_20240830T104647_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_274_20240830T095520_20240830T104647_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_289_20240830T224702_20240830T233829_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_289_20240830T224702_20240830T233829_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_302_20240831T095551_20240831T104718_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_302_20240831T095551_20240831T104718_PIC0_01.nc
Subset dataset: downloads/SWOT_L2_LR_SSH_Unsmoothed_020_317_20240831T224734_20240831T233901_PIC0_01.nc
Store subset: subset_SWOT_L2_LR_SSH_Unsmoothed_020_317_20240831T224734_20240831T233901_PIC0_01.nc
[16]:
subset_filenames
[16]:
['downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_011_20240821T002444_20240821T011532_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_024_20240821T113333_20240821T122500_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_039_20240822T002515_20240822T011604_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_052_20240822T113404_20240822T122531_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_067_20240823T002546_20240823T011634_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_274_20240830T095520_20240830T104647_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_289_20240830T224702_20240830T233829_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_302_20240831T095551_20240831T104718_PIC0_01.nc',
'downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_317_20240831T224734_20240831T233901_PIC0_01.nc']
Visualise data on a pass
Open a pass dataset
[25]:
subset_file = "downloads/subset_SWOT_L2_LR_SSH_Unsmoothed_020_317_20240831T224734_20240831T233901_PIC0_01.nc"
[26]:
ds_left = xr.open_dataset(subset_file, group="left")
[27]:
ds_right = xr.open_dataset(subset_file, group="right")
Interpolate coordinates to fill Nan values in latitude and longitude, and assign them as coordinates.
[28]:
ds_left = normalize_coordinates(ds_left)
ds_right = normalize_coordinates(ds_right)
Plot Sigma 0
Mask invalid data
[29]:
for dss in ds_left, ds_right:
dss["sig0_karin_2"] = dss.sig0_karin_2.where(dss.ancillary_surface_classification_flag==0)
dss["sig0_karin_2"] = dss.sig0_karin_2.where(dss.sig0_karin_2 < 1e6)
#dss["sig0_karin_2_log"] = 10*np.log10(dss["sig0_karin_2"])
Plot data
[30]:
plot_kwargs = dict(
x="longitude",
y="latitude",
cmap="gray_r",
vmin=18,
vmax=50,
)
fig, ax = plt.subplots(figsize=(21, 12), subplot_kw=dict(projection=ccrs.PlateCarree()))
ds_left.sig0_karin_2.plot.pcolormesh(ax=ax, cbar_kwargs={"shrink": 0.3}, **plot_kwargs)
ds_right.sig0_karin_2.plot.pcolormesh(ax=ax, add_colorbar=False, **plot_kwargs)
ax.gridlines(draw_labels=True)
ax.coastlines()
ax.set_extent(localbox, crs=ccrs.PlateCarree())
Plot SSHA
Mask invalid data
[31]:
for dss in ds_left, ds_right:
dss["ssh_karin_2"] = dss.ssh_karin_2.where(dss.ancillary_surface_classification_flag==0)
dss["ssh_karin_2"] = dss.ssh_karin_2.where(dss.ssh_karin_2_qual==0)
Plot data
[32]:
plot_kwargs = dict(
x="longitude",
y="latitude"
)
fig, ax = plt.subplots(figsize=(21, 12), subplot_kw=dict(projection=ccrs.PlateCarree()))
ds_left.ssh_karin_2.plot.pcolormesh(ax=ax, cbar_kwargs={"shrink": 0.3}, **plot_kwargs)
ds_right.ssh_karin_2.plot.pcolormesh(ax=ax, add_colorbar=False, **plot_kwargs)
ax.gridlines(draw_labels=True)
ax.coastlines()
ax.set_extent(localbox, crs=ccrs.PlateCarree())