Quick start — Copernicus Marine via the EarthLens facade¶
This notebook walks through a minimal Copernicus Marine download via the
unified EarthLens facade. The target is OSTIA L4 SST — the smallest,
surface-only dataset in the curated catalog, so the request takes seconds and
the returned NetCDF is tens of kilobytes.
It also shows two common follow-on patterns:
- bypassing the facade with
CMEMS(...)directly; - post-processing the returned NetCDF through
pyramids.netcdf.NetCDF(the project's standard GIS reader).
The notebook reads Copernicus Marine portal credentials from
COPERNICUSMARINE_SERVICE_USERNAME and COPERNICUSMARINE_SERVICE_PASSWORD
environment variables. See
Authentication for the full
credential-source ladder.
Install¶
pip install earthlens[cmems]
If you want to follow the post-process step, also install
pyramids (already a runtime
dependency of earthlens).
Register a free portal account once at https://marine.copernicus.eu/register. On a workstation, the easiest path is one shell command:
copernicusmarine login
which prompts for username + password and writes
~/.copernicusmarine/.copernicusmarine-credentials. All subsequent
EarthLens(data_source="cmems", ...) calls — including this notebook — read
that file automatically.
import datetime as dt
import os
from pathlib import Path
from earthlens import EarthLens
from earthlens.cmems import Catalog
OUT_DIR = Path('data/cmems-quickstart')
OUT_DIR.mkdir(parents=True, exist_ok=True)
SERVICE_USERNAME = os.environ.get('COPERNICUSMARINE_SERVICE_USERNAME')
SERVICE_PASSWORD = os.environ.get('COPERNICUSMARINE_SERVICE_PASSWORD')
# OSTIA NRT is a rolling near-real-time product: pick a window ~30 days
# back from today so it stays inside the dataset's moving coverage
# bounds (a hardcoded date eventually falls off the back).
_recent = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=30)
START = _recent.strftime('%Y-%m-%d')
END = (_recent + dt.timedelta(days=6)).strftime('%Y-%m-%d')
Browse the curated catalog (no network, no auth)¶
earthlens.cmems.Catalog() parses the bundled catalog/ directory
once per process (~ms warm). Use it to discover what's curated and look
up a variable's units before issuing a download. Uncurated dataset ids
still work — pass any id that copernicusmarine.describe() recognises.
cat = Catalog()
print(f'available_datasets: {len(cat.available_datasets)}')
print(f'curated datasets: {len(cat.datasets)}')
ds = cat.get_dataset('METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2')
print('\nMETOFFICE-GLO-SST-L4-NRT-OBS-SST-V2:')
print(f' cadence: {ds.cadence}')
print(f' domain: {ds.domain}')
for name, var in sorted(ds.variables.items()):
print(f' {name}: units={var.units!r}, long_name={var.long_name!r}')
available_datasets: 1251 curated datasets: 1141 METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2: cadence: irregular domain: global analysed_sst: units='kelvin', long_name='Sea surface foundation temperature' analysis_error: units='kelvin', long_name='Sea surface foundation temperature standard error' mask: units='', long_name='' sea_ice_fraction: units='1', long_name='Sea ice area fraction'
Download — one regional 7-day OSTIA SST subset via the facade¶
The smallest meaningful CMEMS request: one variable, surface only, a 6° box off Iberia, one week. The toolbox returns a single NetCDF tens of kilobytes in size; it completes in seconds.
The facade EarthLens(data_source="cmems", ...) forwards every extra kwarg
(service_username, service_password, credentials_file, file_format,
minimum_depth, maximum_depth, overwrite) to the backend constructor
verbatim.
earthlens = EarthLens(
data_source='cmems',
start=START,
end=END,
temporal_resolution='daily',
variables={'METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2': ['analysed_sst']},
lat_lim=[30.0, 36.0],
lon_lim=[-10.0, -4.0],
path=str(OUT_DIR),
service_username=SERVICE_USERNAME,
service_password=SERVICE_PASSWORD,
)
paths = earthlens.download()
for p in paths:
print(p)
INFO - 2026-05-20T17:31:18Z - Checking if credentials are valid.
INFO - 2026-05-20T17:31:18Z - Valid credentials from input username and password.
2026-05-20 19:31:18.959 | INFO | earthlens.cmems.backend:_subset_one:458 - Requesting CMEMS subset for 'METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2' variables=['analysed_sst'] → METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2.nc
INFO - 2026-05-20T17:31:19Z - Selected dataset version: "default"
INFO - 2026-05-20T17:31:19Z - Selected dataset part: "default"
INFO - 2026-05-20T17:31:22Z - Total size of the download: 210.30 KB.
2026-05-20 19:31:22.086 | INFO | earthlens.cmems.backend:download:326 - CMEMS download summary: 1 files written to C:\gdrive\algorithms\remote-sensing\earthlens\docs\examples\cmems\data\cmems-quickstart
C:\gdrive\algorithms\remote-sensing\earthlens\docs\examples\cmems\data\cmems-quickstart\METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2.nc
Same call without the facade¶
Useful if you want to introspect the catalog row on cmems.cat, or to type-
check against the CMEMS class directly.
from earthlens.cmems import CMEMS
cmems = CMEMS(
start=START,
end=END,
temporal_resolution='daily',
variables={'METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2': ['analysed_sst']},
lat_lim=[30.0, 36.0],
lon_lim=[-10.0, -4.0],
path=str(OUT_DIR),
service_username=SERVICE_USERNAME,
service_password=SERVICE_PASSWORD,
overwrite=True,
)
paths = cmems.download()
print(paths)
INFO - 2026-05-20T17:31:22Z - Checking if credentials are valid.
INFO - 2026-05-20T17:31:22Z - Valid credentials from input username and password.
2026-05-20 19:31:22.368 | INFO | earthlens.cmems.backend:_subset_one:458 - Requesting CMEMS subset for 'METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2' variables=['analysed_sst'] → METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2.nc
INFO - 2026-05-20T17:31:23Z - Selected dataset version: "default"
INFO - 2026-05-20T17:31:23Z - Selected dataset part: "default"
INFO - 2026-05-20T17:31:24Z - Total size of the download: 210.30 KB.
2026-05-20 19:31:24.934 | INFO | earthlens.cmems.backend:download:326 - CMEMS download summary: 1 files written to C:\gdrive\algorithms\remote-sensing\earthlens\docs\examples\cmems\data\cmems-quickstart
[WindowsPath('C:/gdrive/algorithms/remote-sensing/earthlens/docs/examples/cmems/data/cmems-quickstart/METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2.nc')]
from pyramids.netcdf import NetCDF
nc = NetCDF.read_file(str(paths[0]), read_only=True)
print('variables:', list(nc.meta_data.variables.keys()))
sst = nc.meta_data.variables['analysed_sst']
print(f'analysed_sst: long_name={getattr(sst, "long_name", None)!r} unit={getattr(sst, "unit", None)!r}')
arr = nc.read_array('analysed_sst')
print(f'shape (time, lat, lon): {arr.shape}')
nc.close()
2026-05-20 17:31:25 | INFO | pyramids.base.config | Logging is configured.
variables: ['analysed_sst', 'latitude', 'longitude', 'time'] analysed_sst: long_name=None unit='kelvin' shape (time, lat, lon): (7, 120, 120)
Next steps¶
- GLORYS — thermocline time-series: the
4-D depth-axis pattern (
thetaoat fixed depths). - PISCES — chlorophyll seasonal cycle:
multi-variable subset + monthly
groupby('time.month').mean(). - Altimetry — coastal SLA signal: multi-mission L4 sea-level anomaly along a coastal box.