PISCES — chlorophyll seasonal cycle off the Iberian upwelling¶
Demonstrates the multi-variable subset pattern: pull two PISCES
biogeochem variables (chl chlorophyll-a, o2 dissolved oxygen) over a
coastal box off Iberia for a multi-year window, group by month, and plot
the seasonal cycle.
PISCES is cmems_mod_glo_bgc_my_0.25deg_P1D-m — daily, 1/4° grid,
global. Variables are 4-D (time, depth, lat, lon); we clip to the
surface 0-10 m to keep the request small.
Setup¶
Consolidate the imports up front. pyramids provides NetCDF (reading the
downloaded file); earthlens provides the unified EarthLens entry point and
the CMEMS Catalog. xarray handles the calendar-aware monthly groupby and
matplotlib the final plot.
import os
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
from pyramids.netcdf import NetCDF
from earthlens import EarthLens
from earthlens.cmems import Catalog
The request¶
PISCES is cmems_mod_glo_bgc_my_0.25deg_P1D-m — daily, 1/4° grid, global. We
pull two biogeochem variables (chl chlorophyll-a, o2 dissolved oxygen) over
a coastal box off Iberia, and create the output directory.
OUT_DIR = Path('data/cmems-pisces')
OUT_DIR.mkdir(parents=True, exist_ok=True)
DATASET_ID = 'cmems_mod_glo_bgc_my_0.25deg_P1D-m'
VARIABLES = ['chl', 'o2']
BBOX = dict(lat_lim=[40.0, 44.0], lon_lim=[-12.0, -8.0]) # Iberian upwelling box
Inspect the dataset metadata¶
The CMEMS Catalog resolves the dataset id to its cadence, domain, and per-
variable units / long names — a quick sanity check before downloading.
ds = Catalog().get_dataset(DATASET_ID)
print(f'{DATASET_ID}: cadence={ds.cadence}, domain={ds.domain}')
for v in VARIABLES:
print(
f' {v}: units={ds.variables[v].units!r}, long_name={ds.variables[v].long_name!r}'
)
Download — three years of daily PISCES at the surface¶
Multi-year × multi-variable × surface-clipped, in one server-side subset call.
We build the EarthLens request first — variables are 4-D
(time, depth, lat, lon), so we clip to the surface 0-10 m to keep it small.
earthlens = EarthLens(
data_source='cmems',
start='2018-01-01',
end='2020-12-31',
cadence='daily',
dataset=DATASET_ID,
variables=VARIABLES,
**BBOX,
path=str(OUT_DIR),
minimum_depth=0.0,
maximum_depth=10.0,
service_username=os.environ.get('COPERNICUSMARINE_SERVICE_USERNAME'),
service_password=os.environ.get('COPERNICUSMARINE_SERVICE_PASSWORD'),
)
Run the subset download on its own line so the request build and the network call read as separate steps. It returns the list of written NetCDF paths.
paths = earthlens.download()
print(paths)
nc = NetCDF.read_file(str(paths[0]), read_only=True)
ds = xr.decode_cf(nc.to_xarray())
nc.close()
Reduce to a 12-month climatology¶
Average across (depth, lat, lon) per day, then group by calendar month across
the three years. Both arrays end up as 12-element vectors keyed on month-of-year
(1-12).
surface = ds.mean(dim=['depth', 'latitude', 'longitude'])
monthly = surface.groupby('time.month').mean()
climatology = {v: monthly[v].values for v in VARIABLES}
for v in VARIABLES:
print(f'{v}: {climatology[v].round(4)}')
Plot the seasonal cycle¶
Iberian upwelling chlorophyll peaks in spring / late summer with the wind-driven upwelling events; dissolved oxygen tracks the same pattern inversely (warmer water holds less O2).
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
months = np.arange(1, 13)
ax1.bar(months, climatology['chl'], color='tab:green')
ax1.set_xlabel('Month')
ax1.set_ylabel('chl (mg m-3)')
ax1.set_title('PISCES chlorophyll-a climatology')
ax1.set_xticks(months)
ax2.bar(months, climatology['o2'], color='tab:blue')
ax2.set_xlabel('Month')
ax2.set_ylabel('o2 (mmol m-3)')
ax2.set_title('PISCES dissolved oxygen climatology')
ax2.set_xticks(months)
fig.suptitle('Iberian upwelling box, 2018-2020 surface (0-10 m) mean')
fig.tight_layout()