Copernicus for Sanctuaries

Code
librarian::shelf(
  dplyr,
  DT,
  fs,
  ggplot2,
  glue,
  here,
  jsonlite,
  leaflet,
  listviewer,
  mapview,
  ncdf4,
  purrr,
  readr,
  reticulate,
  sf,
  stringr,
  terra,
  tidyr,
  quiet = T
)
options(readr.show_col_types = F)

sanctuaries_geo <- here("data/sanctuaries.geojson")
# copernicusmarine username and password
user <- "bbest1"
pass_txt <- "~/My Drive/private/data.marine.copernicus.eu_bbest1-password.txt"
dir_cm <- here("data/copernicus")
results_csv <- here("data/copernicus.csv")

Sanctuaries

We are extracting Copernicus Marine data from across National Marine Sanctuaries, borrowing polygons from the noaa-onms/climate-dashboard (see Github code).

Code
if (!file.exists(sanctuaries_geo)) {
  # source: https://github.com/noaa-onms/climate-dashboard/blob/main/data/sanctuaries.rds
  sanctuaries_rds <- here("../climate-dashboard/data/sanctuaries.rds")
  readRDS(sanctuaries_rds) |>
    filter(nms != "TBNMS") |> # exclude Great Lakes
    write_sf(sanctuaries_geo, delete_dsn = T)
}

sanctuaries <- read_sf(sanctuaries_geo)
sanctuaries |>
  st_drop_geometry() |>
  datatable()
Code
mapView(sanctuaries)

Setup for copernicusmarine in R

Code
# do once: create virtual enviroment and install copernicusmarine Python module
# virtualenv_create(envname = "CopernicusMarine")
# virtualenv_install(envname = "CopernicusMarine", packages = c("copernicusmarine"))

# TODO: check for CopernicusMarine env with copernicusmarine Python module

# use virtualenv and reticulate::import copernicusmarine Python module
use_virtualenv(virtualenv = "CopernicusMarine", required = TRUE)
cmt <- import("copernicusmarine")

# login, unless the toolbox already holds credentials (e.g. on the Mac mini)
# writes to ~/.copernicusmarine/.copernicusmarine-credentials
if (!file.exists("~/.copernicusmarine/.copernicusmarine-credentials")) {
  pass <- readLines(pass_txt)
  stopifnot(
    cmt$login(user, pass, force_overwrite = T)
  ) # py_help(cmt$login)
}
Code
get_dataset_description <- function(dataset_id) {
  cmt$describe(
    dataset_id           = dataset_id,
    disable_progress_bar = T)$json() |>
    fromJSON()
}

get_dataset_times <- function(dataset_description) {
  # dataset_description = ds_times
  services <- pluck(
    dataset_description, "products", "datasets", 1,
    "versions", 1, "parts", 1, "services", 1)
  service_names <- pluck(services, "service_name")
  i_ts <- which(service_names == "arco-time-series")

  coordinates <- pluck(services, "variables", i_ts, "coordinates")
  coord_ids   <- pluck(coordinates, 1, "coordinate_id")
  i_time      <- which(coord_ids == "time")

  time_vals <- pluck(coordinates, 1, "values", i_time) |>
    as.numeric() %>% {./1000}
  time_min <- min(time_vals) |>
    as.POSIXct(origin = "1970-01-01", tz = "UTC")
  time_max <- max(time_vals) |>
    as.POSIXct(origin = "1970-01-01", tz = "UTC")

  stopifnot(
    "get_dataset_times(): time_min is NA" = !is.na(time_min),
    "get_dataset_times(): time_max is NA" = !is.na(time_max))

  list(min = time_min, max = time_max)
}

get_last_date_in_csv <- function(results_csv) {
  if (!file.exists(results_csv))
    return(tibble(
      nms = character(), dataset = character(),
      last_time = as.Date(character())))
  read_csv(results_csv) |>
    mutate(time = as.Date(time)) |>
    group_by(nms, dataset) |>
    summarize(last_time = max(time, na.rm = TRUE), .groups = "drop")
}

Extract Global Ocean Physics Reanalysis

Product > Dataset > Variable

Variables, keep:

  • thetao: Sea water potential temperature [°C]
  • bottomT: Sea water potential temperature at sea floor [°C]
  • so: Sea water salinity [/ 103]
  • mlotst: Ocean mixed layer thickness defined by sigma theta [m]

Variables, skip:

  • siconc: Sea ice area fraction
  • sithick: Sea ice thickness [m]
  • usi: Eastward sea ice velocity [m/s]
  • vsi: Northward sea ice velocity [m/s]
  • uo: Eastward sea water velocity [m/s]
  • vo: Northward sea water velocity [m/s]
  • zos: Sea surface height above geoid [m]

Extract Global Ocean Biogeochemistry Hindcast

Variables, keep (the long records for pH, oxygen and nutrients):

  • o2: Mole concentration of dissolved molecular oxygen in sea water [mmol m-3]
  • no3, po4, si, fe: Nitrate, phosphate, silicate and dissolved iron [mmol m-3]
  • ph: Sea water pH reported on total scale
  • spco2: Surface partial pressure of carbon dioxide in sea water [Pa]
  • chl, phyc, nppv: Chlorophyll, phytoplankton carbon and net primary production

Extract Global Ocean Colour

Setup dataset parameters

Code
datasets <- list(
  # Global Ocean Physics Reanalysis - monthly
  # https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_PHY_001_030/download?dataset=cmems_mod_glo_phy_my_0.083deg_P1M-m
  "cmems_mod_glo_phy_my_0.083deg_P1M-m" = list(
    vars      = list("thetao", "bottomT", "so", "mlotst"),
    depth_rng = c(0, 0.5)),
  # Global Ocean Biogeochemistry Hindcast - monthly
  # https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_BGC_001_029/download?dataset=cmems_mod_glo_bgc_my_0.25deg_P1M-m
  "cmems_mod_glo_bgc_my_0.25deg_P1M-m" = list(
    vars      = list("chl", "fe", "no3", "nppv", "o2", "ph", "phyc", "po4", "si", "spco2"),
    depth_rng = c(0, 0.51)), # top level at 0.506 m, below the 0.5 of the others
  # Plankton, multi-sensor (4 km), monthly
  "cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M" = list(
    vars = list(
      "CHL",
      "CHL_uncertainty",
      "DIATO",
      "DIATO_uncertainty",
      "DINO",
      "DINO_uncertainty",
      "GREEN",
      "GREEN_uncertainty",
      "HAPTO",
      "HAPTO_uncertainty",
      "MICRO",
      "MICRO_uncertainty",
      "NANO",
      "NANO_uncertainty",
      "PICO",
      "PICO_uncertainty",
      "PROCHLO",
      "PROCHLO_uncertainty",
      "PROKAR",
      "PROKAR_uncertainty",
      "flags"),
    depth_rng = c(0, 0.5)),
  # Primary production, multi-sensor (4 km), monthly
  "cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M" = list(
    vars      = list("PP", "PP_uncertainty", "flags"),
    depth_rng = c(0, 0.5))
)

# query Copernicus API to set date_rng (min & max) per dataset
fmt_dt <- \(x) format(x, "%Y-%m-%dT00:00:00")
for (id in names(datasets)) {  # id = "cmems_mod_glo_phy_my_0.083deg_P1M-m"
  ds_times <- get_dataset_description(id) |> get_dataset_times()
  datasets[[id]]$date_rng <- c(fmt_dt(ds_times$min), fmt_dt(ds_times$max))
  message(glue("  {id}: {datasets[[id]]$date_rng[1]} -> {datasets[[id]]$date_rng[2]}"))
}
  cmems_mod_glo_phy_my_0.083deg_P1M-m: 1993-01-01T00:00:00 -> 2026-08-01T00:00:00
  cmems_mod_glo_bgc_my_0.25deg_P1M-m: 1993-01-01T00:00:00 -> 2026-08-01T00:00:00
  cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M: 1997-09-01T00:00:00 -> 2026-09-01T00:00:00
  cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M: 1997-09-01T00:00:00 -> 2026-09-01T00:00:00
Code
# summarize last fetched date per dataset from existing results
last_dates <- get_last_date_in_csv(results_csv)
for (id in names(datasets)) {
  last <- last_dates |>
    filter(dataset == id) |>
    pull(last_time)
  datasets[[id]]$date_last_fetched <- if (length(last) > 0)
    as.character(min(last)) else NA
}

datasets |>
  toJSON(auto_unbox = T, pretty = T) |>
  jsonedit() # view in browser

Dataset Variables

Code
vars_csv <- here("data/copernicus_variables.csv")

get_var_coord_range <- function(coord_df, coord_name) {
  if (!is.data.frame(coord_df)) return(c(NA_real_, NA_real_))
  i <- which(coord_df$coordinate_id == coord_name)
  if (length(i) == 0) return(c(NA_real_, NA_real_))
  vals <- as.numeric(coord_df$values[[i]])
  c(min(vals, na.rm = T), max(vals, na.rm = T))
}

get_dataset_vars_info <- function(dataset_id) {
  desc     <- get_dataset_description(dataset_id)
  product  <- pluck(desc, "products") |> as_tibble() |> slice(1)
  services <- pluck(
    desc, "products", "datasets", 1,
    "versions", 1, "parts", 1, "services", 1)
  i_ts     <- which(services$service_name == "arco-time-series")
  vars     <- pluck(services, "variables", i_ts)
  times    <- get_dataset_times(desc)

  tibble(
    dataset_id    = dataset_id,
    product_id    = product$product_id,
    product_title = product$title,
    time_min      = as.character(times$min),
    time_max      = as.character(times$max),
    variable      = vars$short_name,
    standard_name = vars$standard_name,
    units         = vars$units,
    depth_min     = map_dbl(vars$coordinates, \(c) get_var_coord_range(c, "depth")[1]),
    depth_max     = map_dbl(vars$coordinates, \(c) get_var_coord_range(c, "depth")[2]),
    longitude_min = map_dbl(vars$coordinates, \(c) get_var_coord_range(c, "longitude")[1]),
    longitude_max = map_dbl(vars$coordinates, \(c) get_var_coord_range(c, "longitude")[2]),
    latitude_min  = map_dbl(vars$coordinates, \(c) get_var_coord_range(c, "latitude")[1]),
    latitude_max  = map_dbl(vars$coordinates, \(c) get_var_coord_range(c, "latitude")[2])
  )
}

vars_info <- map_dfr(names(datasets), get_dataset_vars_info)
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Warning in min(vals, na.rm = T): no non-missing arguments to min; returning Inf
Warning in max(vals, na.rm = T): no non-missing arguments to max; returning
-Inf
Code
write_csv(vars_info, vars_csv)

datatable(vars_info, options = list(scrollX = TRUE))

Iterate over sanctuaries & datasets to subset

Code
last_dates <- get_last_date_in_csv(results_csv)

for (i in 1:nrow(sanctuaries)) {
  # i = 1

  d_s <- slice(sanctuaries, i)
  bb  <- d_s |>
    st_buffer(9.25 * 1000) |> # buffer by a pixel width 1/12° ~ 9.25 km at equator
    st_bbox()

  dir_out <- here(glue("{dir_cm}/{d_s$nms}"))
  dir.create(dir_out, showWarnings = F, recursive = T)
  message(glue(
    "{i}/{nrow(sanctuaries)}: {d_s$sanctuary} ({d_s$nms}) : beg ~ {format(Sys.time(), '%H:%M:%S')}"
  ))

  for (j in 1:length(datasets)) {
    # j = 1

    ds_id <- names(datasets)[j]
    ds    <- datasets[[j]]

    # check what's already processed
    last      <- last_dates |>
      filter(nms == d_s$nms, dataset == ds_id) |>
      pull(last_time)
    avail_end <- as.Date(ds$date_rng[2])

    last_msg <- if (length(last) == 1) as.character(last) else "none"
    message(glue(
      "  {j}/{length(datasets)}: {ds_id}\n",
      "    last retrieved: {last_msg}, available: {ds$date_rng[1]} to {ds$date_rng[2]}"))

    if (length(last) == 1 && last >= avail_end) {
      message(glue("    -> up to date, skipping"))
      next
    }

    # start one month after last processed, or from the beginning
    start_dt <- if (length(last) == 1) {
      format(seq.Date(last, by = "month", length.out = 2)[2],
             "%Y-%m-%dT00:00:00")
    } else {
      ds$date_rng[1]
    }

    message(glue("    -> fetching {start_dt} to {ds$date_rng[2]}"))
    
    # browser()

    nc <- cmt$subset(
      dataset_id           = ds_id,
      variables            = ds$vars,
      minimum_longitude    = bb$xmin,
      maximum_longitude    = bb$xmax,
      minimum_latitude     = bb$ymin,
      maximum_latitude     = bb$ymax,
      start_datetime       = start_dt,
      end_datetime         = ds$date_rng[2],
      minimum_depth        = ds$depth_rng[1],
      maximum_depth        = ds$depth_rng[2],
      output_directory     = dir_out,
      overwrite            = TRUE,
      service              = "timeseries",
      disable_progress_bar = TRUE)
    nc <- as.character(nc)
    message(glue("  : end ~ {format(Sys.time(), '%H:%M:%S')}"))
  }
}
1/15: Cordell Bank (CBNMS) : beg ~ 12:13:19
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:21
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:24
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:26
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:28
2/15: Channel Islands (CINMS) : beg ~ 12:13:29
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:31
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:33
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:37
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:39
3/15: Chumash Proposed Action (CPNMS) : beg ~ 12:13:39
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:41
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:44
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:46
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:48
4/15: Flower Garden Banks (FGBNMS) : beg ~ 12:13:48
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:50
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:13:53
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:57
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:13:59
5/15: Florida Keys (FKNMS) : beg ~ 12:13:59
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:01
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:04
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:07
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:09
6/15: Greater Farallones (GFNMS) : beg ~ 12:14:09
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:11
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:13
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:16
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:18
7/15: Gray's Reef (GRNMS) : beg ~ 12:14:18
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:20
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:23
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:27
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:28
8/15: Hawaiian Islands Humpback Whale (HIHWNMS) : beg ~ 12:14:28
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:30
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:34
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:37
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:39
9/15: Monterey Bay (MBNMS) : beg ~ 12:14:39
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:41
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:44
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:48
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:50
10/15: Monitor (MNMS) : beg ~ 12:14:50
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:52
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:14:54
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:14:58
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:00
11/15: American Samoa (NMSAS) : beg ~ 12:15:00
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:02
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:05
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:08
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:10
12/15: Olympic Coast (OCNMS) : beg ~ 12:15:11
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:13
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:16
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:20
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:22
13/15: Stellwagen Bank (SBNMS) : beg ~ 12:15:22
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:24
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:27
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:30
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:32
14/15: Monterey Bay - Davidson Seamount (MBNMS-david) : beg ~ 12:15:32
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:34
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:37
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:41
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:42
15/15: Monterey Bay - Mainland (MBNMS-main) : beg ~ 12:15:42
1/4: cmems_mod_glo_phy_my_0.083deg_P1M-m
last retrieved: 2026-02-01, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 2026-03-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:45
2/4: cmems_mod_glo_bgc_my_0.25deg_P1M-m
last retrieved: none, available: 1993-01-01T00:00:00 to 2026-08-01T00:00:00
    -> fetching 1993-01-01T00:00:00 to 2026-08-01T00:00:00
  : end ~ 12:15:48
3/4: cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:52
4/4: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
last retrieved: 2026-03-01, available: 1997-09-01T00:00:00 to 2026-09-01T00:00:00
    -> fetching 2026-04-01T00:00:00 to 2026-09-01T00:00:00
  : end ~ 12:15:53

Show raster

Grabbing first netcdf file, show full metadata on variables and attributes:

Code
nms = "FKNMS"
ply <- sanctuaries |>
  filter(nms == !!nms)
# nc <- list.files(glue("data/copernicus/{nms}"), full.names = T)[1:6]
nc <- dir_info(glue("data/copernicus/{nms}")) |>
  arrange(desc(modification_time)) |>
  slice(1) |>
  pull(path)

o <- nc_open(nc)
o
File data/copernicus/FKNMS/cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M_multi-vars_83.23W-79.98W_24.23N-25.73N_2026-04-01-2026-09-01.nc (NC_FORMAT_NETCDF4):

     3 variables (excluding dimension variables):
        float PP[longitude,latitude,time]   (Contiguous storage)  
            _FillValue: -999
            valid_min: 0
            valid_max: 10000
            units: mg m-2 day-1
            standard_name: primary_productivity_of_biomass_expressed_as_carbon
            long_name: Primary Productivity - Mean of the binned pixels
            coordinates: time lat lon
            missing_value: -999
        short PP_uncertainty[longitude,latitude,time]   (Contiguous storage)  
            _FillValue: -32768
            valid_min: 0
            units: %
            valid_max: 32767
            long_name: Primary Productivity - Uncertainty estimation
            coordinates: time lat lon
            scale_factor: 0.00999999977648258
            missing_value: -32768
        byte flags[longitude,latitude,time]   (Contiguous storage)  
            standard_name: status_flag
            valid_min: 0
            valid_max: 1
            long_name: Flags
            coordinates: time lat lon

     3 dimensions:
        time  Size:6 
            unit_long: Days Since 1900-01-01
            standard_name: time
            axis: T
            long_name: Time
            units: days since 1900-01-01
            calendar: Gregorian
        latitude  Size:37 
            axis: Y
            standard_name: latitude
            units: degrees_north
            long_name: latitude
        longitude  Size:79 
            axis: X
            standard_name: longitude
            units: degrees_east
            long_name: longitude

    9 global attributes:
        Conventions: CF-1.8, ACDD-1.3
        references: http://www.globcolour.info GlobColour has been originally funded by ESA with data from ESA, NASA, NOAA and GeoEye. This version has received funding from the European Community s Seventh Framework Programme ([FP7/2007-2013]) under grant agreement n. 282723 [OSS2015 project].
        contact: servicedesk.cmems@acri-st.fr
        title: cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
        comment: average
        history: Created using software developed at ACRI-ST
        institution: ACRI
        source: surface observation
        copernicusmarine_version: 2.2.5

Using terra::rast() to read the netcdf file, show first layer:

Code
r <- rast(nc)
r
class       : SpatRaster
size        : 37, 79, 18  (nrow, ncol, nlyr)
resolution  : 0.04166667, 0.04166667  (x, y)
extent      : -83.25, -79.95833, 24.20833, 25.75  (xmin, xmax, ymin, ymax)
coord. ref. : lon/lat WGS 84 (CRS84) (OGC:CRS84)
sources     : cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M_multi-vars_83.23W-79.98W_24.23N-25.73N_2026-04-01-2026-09-01.nc
varnames    : PP (Primary Productivity - Mean of the binned pixels)
              PP_uncertainty (Primary Productivity - Uncertainty estimation)
              flags (Flags)
names       :         PP_1,         PP_2,         PP_3,         PP_4,         PP_5,         PP_6, ...
unit        : mg m-2 day-1, mg m-2 day-1, mg m-2 day-1, mg m-2 day-1, mg m-2 day-1, mg m-2 day-1, ...
time (days) : 2026-04-01 to 2026-09-01 (6 steps)
Code
i <- 1
cat(glue(
  "
  index: {i}
  name: {names(r)[i]}
  time: {time(r[[i]])}
  varname: {varnames(r[[i]])}
  longname: {longnames(r[[i]])}"
))
index: 1
name: PP_1
time: 2026-04-01
varname: PP
longname: Primary Productivity - Mean of the binned pixels
Code
# glue("{year}/{var}.tif/{time}_{depth}")

Plot the raster with the sanctuary:

Code
# plot(r[[i]])
r_1 <- r[[i]] |>
  project(leaflet:::epsg3857, method = "bilinear")
# plot(r_1)

plet(
  # r[[i]],
  r_1,
  # main  = glue("{longnames(r[[i]])}\n{names(r)[i]}\n{time(r[[i]])}"),
  main = glue("{longnames(r[[i]])}\nthetao_depth=0.5\n{time(r[[i]])}"),
  tiles = "Esri.OceanBasemap",
  fill_range = T
) |>
  addPolygons(
    data = ply,
    fillOpacity = 0.2
  )

Summarize raster to sanctuary

Code
extract_tbl <- function(r, p, stat = "mean") {
  terra::extract(r, p, fun = stat, na.rm = T) |>
    pivot_longer(!ID, names_to = "var_it") |>
    mutate(
      var = str_replace(var_it, "_[0-9]+$", ""),
      stat = !!stat,
      time = time(r)
    ) |>
    select(var, time, stat, value)
}

last_dates <- get_last_date_in_csv(results_csv)
ncs <- dir_info(dir_cm, regexp = "\\.nc$", recurse = TRUE) |> pull(path)

for (i in 1:length(ncs)) {
  # i = 1
  nc    <- ncs[i]
  nms   <- str_replace(nc, ".*/copernicus/([A-z-]+)/(.+)_multi-vars.*", "\\1")
  ds_id <- str_replace(nc, ".*/copernicus/([A-z-]+)/(.+)_multi-vars.*", "\\2")

  # parse end date from filename to check if already extracted
  nc_end <- str_extract(basename(nc), "[0-9]{4}-[0-9]{2}-[0-9]{2}\\.nc$") |>
    str_remove("\\.nc$") |> as.Date()
  last <- last_dates |>
    filter(nms == !!nms, dataset == !!ds_id) |>
    pull(last_time)
  if (length(last) == 1 && !is.na(nc_end) && nc_end <= last) {
    message(glue("{i}/{length(ncs)}: {nms}/{ds_id} already extracted through {last}, skipping"))
    next
  }

  message(glue("{i}/{length(ncs)}: nms:{nms}, ds:{ds_id}"))

  r <- rast(nc)
  # plet(r[[1]])
  p <- sanctuaries |>
    filter(nms == !!nms)

  d <- bind_rows(
    extract_tbl(r, p, "mean"),
    extract_tbl(r, p, "sd")
  ) |>
    mutate(
      nms = !!nms,
      dataset = !!ds_id
    ) |>
    relocate(nms, dataset)

  if (file.exists(results_csv)) {
    read_csv(results_csv) |>
      bind_rows(d) |>
      group_by(nms, dataset, var, time, stat) |>
      summarize(
        value = last(value),
        .groups = "drop"
      ) |>
      arrange(nms, dataset, var, time, stat) |>
      write_csv(results_csv)
  } else {
    write_csv(d, results_csv)
  }
}
1/90: nms:CBNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
2/90: CBNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
3/90: nms:CBNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
4/90: CBNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
5/90: nms:CBNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
6/90: nms:CBNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
7/90: nms:CINMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
8/90: CINMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
9/90: nms:CINMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
10/90: CINMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
11/90: nms:CINMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
12/90: nms:CINMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
13/90: nms:CPNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
14/90: CPNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
15/90: nms:CPNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
16/90: CPNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
17/90: nms:CPNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
18/90: nms:CPNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
19/90: nms:FGBNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
20/90: FGBNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
21/90: nms:FGBNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
22/90: FGBNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
23/90: nms:FGBNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
24/90: nms:FGBNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
25/90: nms:FKNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
26/90: FKNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
27/90: nms:FKNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
28/90: FKNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
29/90: nms:FKNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
30/90: nms:FKNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
31/90: nms:GFNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
32/90: GFNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
33/90: nms:GFNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
34/90: GFNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
35/90: nms:GFNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
36/90: nms:GFNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
37/90: nms:GRNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
Warning: [rast] skipped array (different geometry): /fe
skipped array (different geometry): /no3
skipped array (different geometry): /nppv
skipped array (different geometry): /o2
skipped array (different geometry): /ph
skipped array (different geometry): /phyc
skipped array (different geometry): /po4
skipped array (different geometry): /si
skipped array (different geometry): /spco2
38/90: GRNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
39/90: nms:GRNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
40/90: GRNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
41/90: nms:GRNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
42/90: nms:GRNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
43/90: nms:HIHWNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
44/90: HIHWNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
45/90: nms:HIHWNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
46/90: HIHWNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
47/90: nms:HIHWNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
48/90: nms:HIHWNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
49/90: nms:MBNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
50/90: MBNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
51/90: nms:MBNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
52/90: MBNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
53/90: nms:MBNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
54/90: nms:MBNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
55/90: nms:MBNMS-david, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
56/90: MBNMS-david/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
57/90: nms:MBNMS-david, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
58/90: MBNMS-david/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
59/90: nms:MBNMS-david, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
60/90: nms:MBNMS-david, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
61/90: nms:MBNMS-main, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
62/90: MBNMS-main/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
63/90: nms:MBNMS-main, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
64/90: MBNMS-main/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
65/90: nms:MBNMS-main, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
66/90: nms:MBNMS-main, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
67/90: nms:MNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
Warning: [rast] skipped array (different geometry): /fe
skipped array (different geometry): /no3
skipped array (different geometry): /nppv
skipped array (different geometry): /o2
skipped array (different geometry): /ph
skipped array (different geometry): /phyc
skipped array (different geometry): /po4
skipped array (different geometry): /si
skipped array (different geometry): /spco2
68/90: MNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
69/90: nms:MNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
70/90: MNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
71/90: nms:MNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
72/90: nms:MNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
73/90: nms:NMSAS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
74/90: NMSAS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
75/90: nms:NMSAS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
76/90: NMSAS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
77/90: nms:NMSAS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
78/90: nms:NMSAS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
79/90: nms:OCNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
80/90: OCNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
81/90: nms:OCNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
82/90: OCNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
83/90: nms:OCNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
84/90: nms:OCNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
85/90: nms:SBNMS, ds:cmems_mod_glo_bgc_my_0.25deg_P1M-m
86/90: SBNMS/cmems_mod_glo_phy_my_0.083deg_P1M-m already extracted through 2026-02-01, skipping
87/90: nms:SBNMS, ds:cmems_mod_glo_phy_my_0.083deg_P1M-m
88/90: SBNMS/cmems_mod_glo_phy_myint_0.083deg_P1M-m already extracted through 2024-12-01, skipping
89/90: nms:SBNMS, ds:cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
90/90: nms:SBNMS, ds:cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
Code
results_url <- results_csv |>
  str_replace(
    here(),
    "https://github.com/noaa-onms/eco-indicators/blob/main"
  )

# TODO: inspect how flags might be distributed across the dataset
# d <- read_csv(results_csv)
# d |>
#   filter(var == "flags") |> 
#   pull(value) |> 
#   table()
#                   0 0.00125156445556946 0.00180831826401446 
#               12204                 678                 678 
# 0.00196850393700787 0.00217391304347826 0.00784313725490196 
#                 678                 678                 678 
#  0.0116279069767442   0.035377456883861  0.0425050794246722 
#                 678                 678                 678 
#  0.0443678254708057  0.0465998661296397  0.0883001161535571 
#                 678                 678                 678 
#   0.107517010378544 
#                 678

# show table
read_csv(results_csv)
# A tibble: 411,456 × 6
   nms   dataset                          var   time                stat   value
   <chr> <chr>                            <chr> <dttm>              <chr>  <dbl>
 1 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-01-01 00:00:00 mean  0.493 
 2 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-01-01 00:00:00 sd    0.0826
 3 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-02-01 00:00:00 mean  0.551 
 4 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-02-01 00:00:00 sd    0.132 
 5 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-03-01 00:00:00 mean  0.219 
 6 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-03-01 00:00:00 sd    0.0882
 7 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-04-01 00:00:00 mean  0.305 
 8 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-04-01 00:00:00 sd    0.118 
 9 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-05-01 00:00:00 mean  1.09  
10 CBNMS cmems_mod_glo_bgc_my_0.25deg_P1… chl_… 1993-05-01 00:00:00 sd    0.456 
# ℹ 411,446 more rows

table of results: copernicus.csv

Generate timeseries plot per sanctuary

Code
d <- read_csv(results_csv)
# table(d$var)
# bottomT  mlotst  so_depth=0.49402499  thetao_depth=0.49402499
#   12288   12288                12288                    12288
#                 bottomT                     CHL         CHL_uncertainty                   DIATO
#                   11820                   10170                   10170                   10170
#       DIATO_uncertainty                    DINO        DINO_uncertainty                   flags
#                   10170                   10170                   10170                   10170
#                   GREEN       GREEN_uncertainty                   HAPTO       HAPTO_uncertainty
#                   10170                   10170                   10170                   10170
#                   MICRO       MICRO_uncertainty                  mlotst                    NANO
#                   10170                   10170                   11820                   10170
#        NANO_uncertainty                    PICO        PICO_uncertainty                 PROCHLO
#                   10170                   10170                   10170                   10170
#     PROCHLO_uncertainty                  PROKAR      PROKAR_uncertainty     so_depth=0.49402499
#                   10170                   10170                   10170                   11820
# thetao_depth=0.49402499
#                   11820

# table(d$dataset, useNA = "ifany")
#               cmems_mod_glo_phy_my_0.083deg_P1M-m
#                                             47280
# cmems_obs-oc_glo_bgc-plankton_my_l4-multi-4km_P1M
#                                            213570
#       cmems_obs-oc_glo_bgc-pp_my_l4-multi-4km_P1M
#                                             30510

plot_var_facets <- function(nms, data) {
  png <- glue("figures/copernicus/{nms}.png")
  data |>
    filter(
      nms == !!nms
    ) |>
    mutate(
      var = str_replace(var, "_depth=0.49402499", "_0.5m")
    ) |>
    pivot_wider(
      names_from = stat,
      values_from = value
    ) |>
    ggplot(aes(x = time, y = mean, color = var)) +
    facet_wrap(~var) +
    geom_line() +
    geom_ribbon(
      aes(ymin = mean - sd, ymax = mean + sd, fill = var),
      color = NA,
      alpha = 0.2
    ) +
    theme(legend.position = "none") +
    facet_wrap(~var, scales = "free_y") +
    labs(
      title = nms,
      x = "Time",
      y = "Value"
    )
  ggsave(png, width = 8, height = 6)
}

sapply(sanctuaries$nms, plot_var_facets, data = d)
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1448 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1502 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1448 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 3232 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1440 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1062 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1044 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
                               CBNMS                                CINMS 
      "figures/copernicus/CBNMS.png"       "figures/copernicus/CINMS.png" 
                               CPNMS                               FGBNMS 
      "figures/copernicus/CPNMS.png"      "figures/copernicus/FGBNMS.png" 
                               FKNMS                                GFNMS 
      "figures/copernicus/FKNMS.png"       "figures/copernicus/GFNMS.png" 
                               GRNMS                              HIHWNMS 
      "figures/copernicus/GRNMS.png"     "figures/copernicus/HIHWNMS.png" 
                               MBNMS                                 MNMS 
      "figures/copernicus/MBNMS.png"        "figures/copernicus/MNMS.png" 
                               NMSAS                                OCNMS 
      "figures/copernicus/NMSAS.png"       "figures/copernicus/OCNMS.png" 
                               SBNMS                          MBNMS-david 
      "figures/copernicus/SBNMS.png" "figures/copernicus/MBNMS-david.png" 
                          MBNMS-main 
 "figures/copernicus/MBNMS-main.png" 

Show timeseries per sanctuary

Show timeseries per sanctuary, without uncertainty

Code
plot_var_facets_without_uncertainty <- function(nms, data) {
  png <- glue("figures/copernicus/{nms}_without_uncertainty.png")
  data |>
    filter(
      nms == !!nms,
      !str_ends(var, "_uncertainty")
    ) |>
    mutate(
      var = str_replace(var, "_depth=0.49402499", "_0.5m")
    ) |>
    pivot_wider(
      names_from  = stat,
      values_from = value
    ) |>
    ggplot(aes(x = time, y = mean, color = var)) +
    facet_wrap(~var) +
    geom_line() +
    geom_ribbon(
      aes(ymin = mean - sd, ymax = mean + sd, fill = var),
      color = NA,
      alpha = 0.2
    ) +
    theme(legend.position = "none") +
    facet_wrap(~var, scales = "free_y") +
    labs(
      title = nms,
      x     = "Time",
      y     = "Value"
    )
  ggsave(png, width = 8, height = 6)
}

sapply(sanctuaries$nms, plot_var_facets_without_uncertainty, data = d)
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 926 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 953 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 926 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 2710 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 720 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 531 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 522 rows containing missing values or values outside the scale range
(`geom_ribbon()`).
                                                   CBNMS 
      "figures/copernicus/CBNMS_without_uncertainty.png" 
                                                   CINMS 
      "figures/copernicus/CINMS_without_uncertainty.png" 
                                                   CPNMS 
      "figures/copernicus/CPNMS_without_uncertainty.png" 
                                                  FGBNMS 
     "figures/copernicus/FGBNMS_without_uncertainty.png" 
                                                   FKNMS 
      "figures/copernicus/FKNMS_without_uncertainty.png" 
                                                   GFNMS 
      "figures/copernicus/GFNMS_without_uncertainty.png" 
                                                   GRNMS 
      "figures/copernicus/GRNMS_without_uncertainty.png" 
                                                 HIHWNMS 
    "figures/copernicus/HIHWNMS_without_uncertainty.png" 
                                                   MBNMS 
      "figures/copernicus/MBNMS_without_uncertainty.png" 
                                                    MNMS 
       "figures/copernicus/MNMS_without_uncertainty.png" 
                                                   NMSAS 
      "figures/copernicus/NMSAS_without_uncertainty.png" 
                                                   OCNMS 
      "figures/copernicus/OCNMS_without_uncertainty.png" 
                                                   SBNMS 
      "figures/copernicus/SBNMS_without_uncertainty.png" 
                                             MBNMS-david 
"figures/copernicus/MBNMS-david_without_uncertainty.png" 
                                              MBNMS-main 
 "figures/copernicus/MBNMS-main_without_uncertainty.png" 

Notes on PFT Uncertainty Variables

The *_uncertainty variables in the GlobColour plankton dataset exhibit pronounced inflections toward high values across all sanctuaries. These patterns are methodological, not ecological, and reflect the uncertainty estimation framework described below.

What the uncertainty values represent. Uncertainty is expressed as percent error bars (relative uncertainty) on each PFT chlorophyll-a concentration estimate. Values are stored as 2-byte signed integers with a scale factor of 0.01, producing a maximum representable value of 327.67 % (Copernicus Marine Environment Monitoring Service 2025a).

How uncertainty is computed. The per-pixel uncertainty is derived via error propagation combining three independent sources (Xi et al. 2021, Eq. 7):

  1. Remote-sensing reflectance (\(R_{rs}\)) input uncertainty — estimated via Monte Carlo perturbation of the \(R_{rs}\) spectrum, propagated through the EOF-regression retrieval algorithm using a look-up table (LUT) approach;
  2. Model parameter uncertainty — arising from the regression coefficients relating EOF scores to PFT concentrations;
  3. Sea surface temperature (SST) uncertainty — SST is an auxiliary predictor in the EOF-SST hybrid retrieval, and its uncertainty is propagated into the final PFT estimate.

Algorithm origin and sensor dependence. The EOF-SST hybrid algorithm was originally developed for OLCI (Sentinel-3), which has 21 spectral bands, and subsequently extended to the multi-sensor merged GlobColour product (Xi et al. 2020). Because the merged product combines sensors with different spectral configurations, uncertainty is generally higher for sensors with fewer bands (e.g., MODIS Aqua has 9 ocean-colour bands vs. OLCI’s 21).

Sensor transitions drive the inflections. The multi-sensor record combines: SeaWiFS (1997–2010), MODIS Aqua (2002–present), MERIS (2002–2012), VIIRS NPP (2012–present), and OLCI-S3A (2016–present) (Garnesson et al. 2019; Maritorena et al. 2010). Each sensor transition changes the input \(R_{rs}\) characteristics and the number of available spectral bands, producing step-changes in the propagated uncertainty even when the underlying ocean state is stable.

Low concentrations amplify relative uncertainty. PFTs that occur at very low concentrations in temperate and subpolar waters — particularly prokaryotes (PROKAR) and Prochlorococcus (PROCHLO) — yield high percent uncertainties because a small absolute error translates to a large relative error.

Reprocessing discontinuities. The November 2023 reprocessing (NASA R2022, MERIS 4th reprocessing, OLCI baseline collection 3.01) may introduce discontinuities in both concentration and uncertainty time series (Copernicus Marine Environment Monitoring Service 2025a).

Known sensor drifts. NASA sensors exhibit documented radiometric drifts that affect the merged product. The SQO report flags these as a known limitation (Copernicus Marine Environment Monitoring Service 2025b).

Weakest retrieval skill. Prokaryotes and Prochlorococcus have the lowest retrieval skill among all PFTs (\(R^2\) = 0.42–0.51 against in-situ validation) (Copernicus Marine Environment Monitoring Service 2025b; Xi et al. 2021), meaning their uncertainty estimates are both the largest and least well-constrained.

VIIRS-SNPP post-2017 exclusions. Data from VIIRS-SNPP after 2017 have been excluded from some products due to identified spurious trends in the merged record (Xi et al. 2025).

Using PFT Data for Multivariate Ordination

Despite the uncertainty caveats above, the PFT concentration data are well-validated and suitable for multivariate analyses. Below are recommended approaches for detecting ecosystem-level shifts across sanctuaries.

  1. Use concentration variables only (exclude _uncertainty). The PFT chlorophyll-a concentrations themselves are the validated retrieval products; the _uncertainty fields are informational metadata describing retrieval confidence. They should not be included as ordination input variables alongside the concentrations.

  2. Log-transform concentrations. PFT concentrations span orders of magnitude. Log-transformation stabilizes variance and matches the scale on which the retrieval algorithm was trained and validated (Xi et al. 2021). Use \(\log_{10}(\text{Chl-}a + \epsilon)\) where \(\epsilon\) is a small constant to handle zeros.

  3. Exclude low-skill PFTs or aggregate them. Consider dropping PROKAR and PROCHLO from ordination (weakest \(R^2 < 0.55\)), or combine them into a single “prokaryote” group to improve signal-to-noise ratio.

  4. PCA / RDA on a sanctuary \(\times\) time matrix. Structure the data as: rows = sanctuary-month combinations, columns = PFT variables (CHL, DIATO, DINO, GREEN, HAPTO, MICRO, NANO, PICO) plus physical variables (thetao, so, mlotst, bottomT, PP). Redundancy analysis (RDA) can then partition biological community variation explained by environmental drivers.

  5. Use uncertainty as weights. In weighted ordination methods (e.g., weighted PCA), use \(1 / \text{uncertainty}\) as observation weights to downweight unreliable observations rather than discarding them entirely.

  6. Temporal anomaly approach. Compute anomalies relative to a monthly climatology per sanctuary to remove the dominant seasonal signal, then ordinate on the anomaly matrix to detect regime shifts and long-term trends.

  7. Non-metric MDS (NMDS). NMDS is robust to the non-normal distributions common in ocean-colour data. Use Bray-Curtis dissimilarity on the PFT concentration matrix (after log-transformation) to preserve compositional distances.

  8. Rolling window analysis. Apply ordination in sliding temporal windows (e.g., 5-year windows advancing by 1 year) to track community composition shifts over time, overlaid with environmental drivers (SST, salinity, mixed layer depth).

References

Copernicus Marine Environment Monitoring Service. 2025a. Ocean Colour Thematic Assembly Centre Product User Manual. CMEMS-OC-PUM, Issue 6.0. CMEMS.
Copernicus Marine Environment Monitoring Service. 2025b. Ocean Colour Thematic Assembly Centre Scientific Quality Overview. CMEMS-OC-SQO, Issue 6.0. CMEMS.
Garnesson, Philippe, Antoine Mangin, Odile Fanton d’Andon, Julien Demaria, and Marine Bretagnon. 2019. “The CMEMS GlobColour Chlorophyll a Product Based on Satellite Observation: Multi-Sensor Merging and Flagging Strategies.” Ocean Science 15 (3): 819–30. https://doi.org/10.5194/os-15-819-2019.
Maritorena, Stéphane, Odile Hembise Fanton d’Andon, Antoine Mangin, and David A. Siegel. 2010. “Merged Satellite Ocean Color Data Products Using a Bio-Optical Model: Characteristics, Benefits and Issues.” Remote Sensing of Environment 114 (8): 1791–804. https://doi.org/10.1016/j.rse.2010.04.002.
Xi, Hongyan, Marine Bretagnon, Ehsan Mehdipour, Julien Demaria, Antoine Mangin, and Astrid Bracher. 2025. “Consistent Long-Term Observations of Surface Phytoplankton Functional Types from Space.” State of the Planet 6-osr9: 7. https://doi.org/10.5194/sp-6-osr9-7-2025.
Xi, Hongyan, Svetlana N. Losa, Antoine Mangin, et al. 2020. “Global Retrieval of Phytoplankton Functional Types Based on Empirical Orthogonal Functions Using CMEMS GlobColour Merged Products and Further Extension to OLCI Data.” Remote Sensing of Environment 240: 111704. https://doi.org/10.1016/j.rse.2020.111704.
Xi, Hongyan, Svetlana N. Losa, Antoine Mangin, et al. 2021. “Global Chlorophyll a Concentrations of Phytoplankton Functional Types with Detailed Uncertainty Assessment Using Multisensor Ocean Color and Sea Surface Temperature Satellite Products.” Journal of Geophysical Research: Oceans 126 (5): e2020JC017127. https://doi.org/10.1029/2020JC017127.