Cross-cutting helpers reused by the instrument/source subpackages (swot, snpp, geos, pace, rads, drifter, glider, …). The goal is that each source package stays thin: swath→grid regridding, NASA Earthdata/CMR plumbing, GHRSST flag decoding, atomic downloads, and quick-look plotting all live here once instead of being copy-pasted per package.

Layout

Module Purpose
common.bbox Dateline-safe BBox / parse_bbox (west south east north); re-exported from swot/pace/rads/multi_altimeters
common.footprints Shared SST/L4 footprint parquet schema, dateline-safe overlap, incremental SST scan, atomic parquet write
common.swath_regrid Level-2 swath → regular grid resampling on pyresample (lat/lon or regional cartesian; nearest/bilinear/gaussian/kriging)
common.earthdata NASA Earthdata / CMR query + granule-naming helpers (temporal normalization, antimeridian bbox split, filename/date parsing)
common.flags GHRSST l2p_flags / flag_meanings decoding helpers
common.dataset xarray dataset helpers (encoding cleanup)
common.sync shared download/sync engine: download_to_path (streaming, resume, size/checksum verify, atomic replace), earthaccess_worker, run_sync (serial/--jobs parallel, tqdm, summary), with_retries
common.download atomic, idempotent file writes for plain-HTTP downloaders
common.field_ops small numpy field helpers (FFT multiplier, finite differences, NaN handling) for spectral processing
common.plotting matplotlib quick-look map helpers (lazy cartopy)
common.log timestamped stderr logger
common.cli common_sync_parser (shared download flags) and argparse value helpers

The folder also holds DATA_PATHS.md — the reference map of every dataset root on /spray (directory layouts, file globs, sample paths, SSHA/SST variable names, longitude conventions, coverage windows). Consult it before touching a /spray path or writing a new reader.

Import convention

common is installed as a bare top-level package (and mirrored as odsl_code.common). Import shared code with the bare path:

from common.log import log
from common.earthdata import normalize_cmr_time, split_antimeridian_bbox

Scripts that may be run directly (e.g. python snpp/snpp_viirs_sst.py) rather than via an installed console entry point add a small bootstrap so the repo root is importable before the common imports:

import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
PROJECT_DIR = SCRIPT_DIR.parent
if str(PROJECT_DIR) not in sys.path:
    sys.path.insert(0, str(PROJECT_DIR))

from common.log import log  # noqa: E402

Note: from common.log import log no longer imports pyresample. from common import regrid_dataset still works via a lazy __getattr__.


common.footprints

Lookup-table helpers for colocate and the super-subsetter. Builders write a parquet once; providers query it by time and dateline-safe bbox so a query never walks every granule.

Name Purpose
FOOTPRINT_COLUMNS path, t0, t1, lon_min, lon_max, lat_min, lat_max
lon_overlap / bbox_overlaps Dateline-safe overlap. A stored lon_min=-180, lon_max=180 is a 360° span (wrapping both ends to 180° would look like a zero-length arc).
build_sst_footprint_index Incremental scan of processed SNPP/GOES granules (mtime sidecar; --rebuild forces a full scan).
write_footprint_index Atomic parquet write.

SST and AVISO builders (odsl-snpp-footprints, odsl-geos-footprints, odsl-aviso-index) call this module. See ../colocate.


common.swath_regrid

Maps Level-2 swath variables onto regular grids using pyresample geometry and resampling routines.

Supported target grids:

Grid Builder Notes
Lat/lon build_latlon_grid Regular degree grid over a bbox
Regional cartesian build_cartesian_grid Uniform x/y spacing in meters, default local azimuthal equidistant projection

Supported interpolation methods:

Method Backend Main parameters
linear / bilinear pyresample.bilinear.resample_bilinear radius_of_influence, neighbours, epsilon
gaussian pyresample.kd_tree.resample_gauss radius_of_influence, gaussian_sigma or smoothing_radius, neighbours
nearest pyresample.kd_tree.resample_nearest radius_of_influence, epsilon
kriging optional pykrige.ok.OrdinaryKriging kriging_variogram_model, kriging_variogram_parameters, kriging_n_closest_points

Public API (re-exported from common/__init__.py): RegridConfig, TargetGrid, build_cartesian_grid, build_latlon_grid, load_target_grid, regrid_dataarray, regrid_dataset, regrid_swath, save_target_grid, target_grid_to_dataset.

Install optional dependencies:

pip install pyresample
pip install pykrige   # only needed for method="kriging"

Examples

Regular lat/lon grid:

import xarray as xr
from common.swath_regrid import RegridConfig, build_latlon_grid, regrid_dataset

ds = xr.open_dataset("level2_swath.nc")
grid = build_latlon_grid(
    bbox=(-98, 18, -80, 31),
    resolution_deg=0.02,
)

mapped = regrid_dataset(
    ds,
    variables=["sst_celsius"],
    target_grid=grid,
    lon_name="lon",
    lat_name="lat",
    config=RegridConfig(
        method="gaussian",
        radius_of_influence=15_000,
        smoothing_radius=7_500,
        neighbours=8,
    ),
)
mapped.to_netcdf("sst_gulf_latlon.nc")

Regional cartesian grid with 2 km cells:

from common.swath_regrid import build_cartesian_grid, regrid_dataarray

grid = build_cartesian_grid(
    bbox=(-98, 18, -80, 31),
    resolution_m=2_000,
)

sst = regrid_dataarray(
    ds["sst_celsius"],
    target_grid=grid,
    lon=ds["lon"],
    lat=ds["lat"],
    method="linear",
    radius_of_influence=8_000,
    neighbours=32,
)
sst.to_netcdf("sst_gulf_2km.nc")

Kriging:

mapped = regrid_dataarray(
    ds["sst_celsius"],
    target_grid=grid,
    lon=ds["lon"],
    lat=ds["lat"],
    config=RegridConfig(
        method="kriging",
        radius_of_influence=25_000,
        kriging_variogram_model="spherical",
        kriging_variogram_parameters={"sill": 1.0, "range": 20_000, "nugget": 0.05},
        kriging_n_closest_points=32,
        kriging_max_points=10_000,
    ),
)

pyresample expects longitude inputs in the [-180, 180) range. The helper wraps longitudes automatically before building source swath definitions.


common.earthdata

NASA Earthdata / CMR query and granule-naming helpers, free of instrument logic so every earthaccess-based source package can reuse them.

Function Description
normalize_cmr_time(value, *, is_end=False) Normalize a str/date/datetime into a CMR temporal-query string; is_end fills the day's end edge
split_antimeridian_bbox(bbox) Split a west south east north bbox that crosses the antimeridian into two CMR-safe bboxes
filename_from_url(url) Strip query string and path, returning the bare filename
date_parts(date_token) "YYYYMMDD"("YYYY", "MM", "DD") (or "unknown" triples)
safe_token(value) Sanitize a string for use in a filename/path
existing_netcdf_files(root) Index existing *.nc files under a root by basename (for idempotent skips)
from common.earthdata import normalize_cmr_time, split_antimeridian_bbox

start = normalize_cmr_time("2025-01-01")               # 2025-01-01T00:00:00Z
end   = normalize_cmr_time("2025-01-31", is_end=True)  # 2025-01-31T23:59:59Z
boxes = split_antimeridian_bbox((170, 10, -170, 20))   # two [-180,180) bboxes

common.flags

Helpers for decoding GHRSST l2p_flags and related flag metadata (used by the SST processors to derive day/night and quality fields).

Function Description
_as_list(value) Coerce a CF flag_meanings/flag_values attribute (str, bytes, scalar, iterable) into a list
_normalize_flag_name(value) Lowercase/strip a flag name and normalize -_

common.dataset

Function Description
clear_encodings(ds) Clear per-variable .encoding so decoded data can be re-written without stale scale_factor/_FillValue conflicts

common.sync

The shared download/sync engine every provider downloader builds on. Discovery stays provider-specific; the engine unifies transfer, idempotency, verification, retry, parallelism and dry-run. Work items are RemoteFile(url, dest, size?, checksum?).

Function Description
download_to_path(remote, opener, *, overwrite, verify, resume, dry_run, …) Stream a URL to .<name>.part then atomically replace dest; skip/verify existing files by size or checksum; HTTP range resume; the prior file is never lost on failure. Returns a SyncResult.
earthaccess_worker(remote, *, provider, overwrite, verify, dry_run, …) Same skip/verify/summary contract for granules fetched via earthaccess.download (transferred into a temp dir, verified, atomically moved).
run_sync(items, worker, *, jobs, …) Run a worker over every item serially or across jobs threads with a tqdm bar; returns a SyncSummary (per-status counts, exit_code).
with_retries(worker, *, attempts, …) Retry a worker with exponential backoff on FAILED.
RequestsOpener / UrllibOpener Byte openers over requests / stdlib for the plain-HTTP and HEAD-verify paths.

Shared CLI flags come from common.cli.common_sync_parser (--overwrite, --dry-run, --jobs, --verify {none,size,checksum}, --retries, --limit, …) as an argparse parent, with per-provider aliases (--out-root/--out, --max-granules/--max-files, --overwrite-downloads, --verify-downloads).

common.download

Atomic, idempotent whole-file byte writes (not URL fetches) — used for the catalog/manifest side-files of the plain-HTTP downloaders.

Function Description
write_bytes_safely(data, out_file, overwrite) Write bytes to a .part temp file then atomically rename; returns "exists" / "downloaded"; backs up any pre-existing file when overwrite=True

common.field_ops

Small, data-source-agnostic numpy helpers for spectral field processing (used by the SWOT eddy/wave geometric-separation scripts).

Function Description
k_from_lambda_km(dx_m, lambda_km) Nondimensional wavenumber for a wavelength given grid spacing
apply_fft_multiplier(u, mult) Apply a spectral multiplier: ifft2(mult * fft2(u)).real
grad_forward(u) Forward-difference gradient (gx, gy)
finite_mask(x) 1.0 where finite else 0.0
replace_nans(x, value) Copy of x with non-finite entries set to value

common.plotting

Function Description
add_map_context(ax, bbox_values) Add coastlines/land/borders/gridlines to an axis over a west south east north bbox; lazily imports cartopy and degrades to a plain lon/lat axis if it (the plot extra) is unavailable

common.log / common.cli

Function Description
log(message) Print "[<UTC ISO8601>] message" to stderr (flushed)
parse_variables(value) Parse a comma-separated CLI string into a list, or None
common_sync_parser(...) Argparse parent with --overwrite, --dry-run, --jobs, --verify, --retries, --limit