datamatch fetches from four sources and joins any of them to your observations the same way. This is about which one to reach for, and what changes when you do.
Nothing here is downloaded when the vignette is built — the fetches are shown but not run, because each needs a network and some need several gigabytes.
The four, side by side
accessEnvDat() |
accessFVCOM() |
accessHYCOM() |
accessCCMP() |
|
|---|---|---|---|---|
| Source | Copernicus Marine | NECOFS / any FVCOM | HYCOM GOFS 3.1 | RSS CCMP v3.1 |
| Kind | global reanalysis, forecast, satellite | regional coastal model | global model | wind analysis |
| Grid | 0.083°–4 km, regular | unstructured mesh | 0.08° regular | 0.25° regular |
| Steps | monthly, daily, hourly | monthly (GOM3), hourly (GOM7) | 3-hourly | 6-hourly |
| Record | 1993– | 1978–2013, then 2025– | 1994–2024 across archives | 1993–present |
| Bottom salinity | derived | free | free | — |
| Wind stress | yes | GOM3 only | — | — |
| Subsetting | server-side | client-side | server-side | whole globe |
Two of those records are chains rather than one run, and the gaps
matter more than the endpoints. FVCOM has nothing between 2014
and 2024 — the GOM3 hindcast stops in 2013 and the GOM7
forecast archive starts in 2025, on a different mesh. HYCOM
reaches 2024 only by crossing from the reanalysis into a series of
operational experiments, which is a seam in how the values were
produced. hycom_covering(date) says which archives hold a
given day.
variable_dictionary() # Copernicus
fvcom_dictionary() # FVCOM
hycom_dictionary() # HYCOM
ccmp_dictionary() # CCMPThey share variable names on purpose
A covariate arrives in a column of the same name whichever source
supplied it, so everything downstream — matchData(),
resampling, plotting, and whatever model you fit — works unchanged:
bb <- list(xmin = -70, xmax = -66, ymin = 41, ymax = 44)
copernicus <- accessEnvDat(vars = "SST", years = 2010, months = 1:12, bounding_box = bb)
fvcom <- accessFVCOM(vars = "SST", years = 2010, months = 1:12, bounding_box = bb)
hycom <- accessHYCOM(vars = "SST", years = 2010, months = 1:12, bounding_box = bb)All three produce an SST column. They are three
different models and three different numbers. Sharing the name
makes them mechanically interchangeable, which is the point; it does not
make them scientifically interchangeable, which is why it is worth
recording which you used.
That property is useful deliberately. Fetching the same variable from two sources and comparing them is a real check on a result:
matched <- matchData(observations, copernicus) # SST
matched <- matchData(matched, hycom) # SST.matched
plot(matched$SST, matched$SST.matched)A colliding name is suffixed .matched rather than
overwriting yours, so both survive the join and the disagreement is
visible.
Which to reach for
Start with Copernicus. It has the widest variable list — physics, biogeochemistry, satellite ocean colour, wind and stress — the longest record with a forecast, and server-side subsetting. Everything else here is for something Copernicus does not do well.
Reach for FVCOM when the coast is the point. A Gulf of Maine box holding 1,742 GLORYS cells holds 6,579 GOM3 nodes, concentrated where the bathymetry is complicated. If your question is about a shelf, a bank, or a channel rather than a basin, that resolution is the reason. The hindcast ends in 2013; the GOM7 forecast archive picks up in 2025 on a different mesh, with nothing in between.
Reach for HYCOM for bottom fields, or for a second
opinion. It publishes salinity_bottom and
water_temp_bottom as real fields where Copernicus has no
bottom salinity at all, and being an independent model it makes
agreement meaningful. The reanalysis covers 1994–2015; operational
archives carry it to September 2024.
Reach for CCMP for winds over a long record. Six-hourly from 1993 to within days of the present, where the Copernicus wind is monthly from mid-1994 or hourly only from 2007. But it carries no stress.
Bottom salinity, four ways
BOTS is the clearest case of the same name costing
different amounts:
# Copernicus reanalysis: derived. Fetches ~50 depth levels to keep the deepest
# wet one, so it must be fetched alone, and reports the depth it used.
bots <- accessEnvDat(vars = "BOTS", years = 2010, months = 1:12, bounding_box = bb)
bots$BOTS_depth
# Copernicus forecast: published outright as `sob`, no derivation.
accessEnvDat(vars = c("BOTT", "BOTS"), years = 2026, months = 7,
bounding_box = bb, mode = "forecast")
# FVCOM: the deepest sigma layer is the sea floor everywhere. Free.
accessFVCOM(vars = c("BOTT", "BOTS"), years = 2010, months = 1:12, bounding_box = bb)
# HYCOM: `salinity_bottom` is its own field. Free.
accessHYCOM(vars = c("BOTT", "BOTS"), years = 2010, months = 1:12, bounding_box = bb)Only the first is expensive, and only the first returns
BOTS_depth — because only the first had to choose a level.
The deepest wet model level is not the sea floor, and in deep water sits
well above it, so the depth is reported rather than left to assume.
Sub-daily data, and means that are not means
All four publish below daily somewhere, and none of them publishes a daily mean:
| Source | Native step | frequency |
|---|---|---|
| Copernicus wind | hourly | "hourly" |
FVCOM GOM7
|
hourly |
"hourly", or "daily" for a snapshot |
| HYCOM | 3-hourly |
"3hourly", or "daily" for a snapshot |
| CCMP | 6-hourly |
"6hourly", or "daily" for a snapshot |
Where a source has no mean, none is invented.
frequency = "daily" on HYCOM and CCMP takes one
snapshot at a chosen hour — an instant, not an average.
A real mean is upscale_time()’s job, which keeps the
aggregation visible and the choice of summary yours:
steps <- accessCCMP(vars = c("UWND", "VWND"), frequency = "6hourly",
dates = "2010-06-15", bounding_box = bb)
mean_wind <- upscale_time(steps, to = "day")
peak_wind <- upscale_time(steps, to = "day", method = "max")The coverage check knows each source’s own step, so a complete CCMP day scores 4/4 rather than 4/24.
One caution that is easy to miss: a mean of the components is
not a mean speed. Averaging UWND and
VWND over a day and taking the magnitude gives the net
displacement of air; averaging WSPD gives how hard it blew.
On a day the wind reversed, the first is near zero and the second is
not.
Longitude, and one trap handled for you
Pass bounding_box with longitudes negative
west to every function here. CCMP is stored on a 0–360 grid and
is converted on the way in and back on the way out, so its results
overlay the others without adjustment. You should never have to think
about it — but if you fetch CCMP by hand elsewhere, that is the
difference between the Gulf of Maine and the Indian Ocean.
Putting several together
The pattern is the same as chaining two Copernicus products: each call adds columns and leaves the row count alone.
matched <- matchData(observations, accessEnvDat(vars = c("SST", "MLD"),
years = 2010, months = 1:12,
bounding_box = bb))
matched <- matchData(matched, accessHYCOM(vars = "BOTS", years = 2010,
months = 1:12, bounding_box = bb))
matched <- matchData(matched, accessCCMP(vars = "WSPD", years = 2010,
months = 1:12, bounding_box = bb))
matched <- attach_bathymetry(matched, fetch_bathymetry(bounding_box = bb),
c("DEPTH", "SLOPE"))
matched <- attach_climate_index(matched, "NAO")
colSums(is.na(sf::st_drop_geometry(matched)))Each source fails differently at the edges of its record — FVCOM
after 2013, HYCOM after 2015, LCR after 2014 — so that last
line is worth running before fitting anything.
Citing what you used
Every source here is somebody else’s work, and the obligation travels with the data rather than with this package:
fvcom_archives()$GOM3$reference
hycom_archives()$GLBv53X$reference
ccmp_versions()$`v03.1`$reference
index_dictionary() # carries the climate index referencesThe README’s References section lists the Copernicus product DOIs. Cite whichever you actually fetched from.
Satellite or model?
Copernicus serves chlorophyll and primary production from two very different sources, and both are available:
| Name | Source | Resolution | Trade-off |
|---|---|---|---|
CHL, PP
|
Copernicus-GlobColour, satellite | 4 km | Observed, finer — but surface-only and gappy under persistent cloud |
CHL_MODEL, NPP_MODEL
|
Biogeochemistry reanalysis | 0.25° | Gap-free and depth-resolved — but simulated, and coarser |
The plain names default to satellite, since the values are observed rather than simulated. Switch to the model versions where cloud gaps would matter more than resolution.
One caution: satellite PP and model
NPP_MODEL are not the same quantity.
PP is depth-integrated (mg/m2/day), NPP_MODEL
volumetric (mg/m3/day). Substituting one for the other is a units error,
not a resolution difference.
Phytoplankton functional types come from the same satellite plankton
dataset as CHL, so they can be fetched together:
env <- accessEnvDat(
vars = c("CHL", "DIATO", "DINO"), # one request, one dataset
years = 2003:2017, months = 1:12,
bounding_box = list(xmin = -76, xmax = -65, ymin = 35, ymax = 45)
)DIATO and DINO are diatom and dinophyte
chlorophyll — the spring-bloom species large copepods prefer, and the
later stratified-water group respectively.
Bottom salinity
BOTS pairs with BOTT, but it is not fetched
the same way, because GLORYS12V1 does not publish it.
The reanalysis has temperature at the sea floor and no salinity
counterpart. So it is derived: the full salinity column is fetched and
the deepest wet level in each cell kept.
bots <- accessEnvDat(vars = "BOTS", years = 2010:2014, months = 1:12,
bounding_box = bb)
#> BOTS is not published by this product. Deriving it from the full 'so' column
#> and keeping the deepest wet level in each cell; the depth used comes back as
#> BOTS_depth.The depth each value came from is returned as BOTS_depth
rather than left to be assumed. That matters because the deepest wet
model level is not the sea floor: level spacing coarsens with
depth, so in deep water the value can sit a long way above the bottom.
In shelf water it is within a few metres.
Two consequences, both deliberate:
-
It must be fetched on its own. The whole depth
column is a different request from the single level
SSTwants, so mixing them is an error rather than a quiet second download. Call twice and chainmatchData(). -
It costs far more. Roughly fifty levels are
downloaded over the same box to keep one, so a large box is much slower
than the same box of
SST.
In forecast mode none of this applies. The analysis-and-forecast
product publishes sea-floor salinity outright, so BOTS is
an ordinary variable there, fetches alongside BOTT, and
returns no BOTS_depth:
accessEnvDat(vars = c("BOTT", "BOTS"), years = 2026, months = 8,
bounding_box = bb, mode = "forecast")The two are the same quantity by construction but not the same number — one is the deepest level of a 50-level grid, the other Copernicus’s own diagnostic — so a record spanning both modes has a seam in it.
variable_dictionary("biogeochemical") # filter by product
variable_dictionary("wind") # or just the winds
fvcom_dictionary() # the FVCOM catalog, for accessFVCOM()
fvcom_archives() # which FVCOM archives are built in
hycom_dictionary() # the HYCOM catalog, for accessHYCOM()
hycom_archives() # which HYCOM archives can be read
hycom_covering("2019-06-15") # which of them span a given date
ccmp_dictionary() # the CCMP catalog, for accessCCMP()
ccmp_versions() # which CCMP versions can be read
erddap_dictionary() # MUR and VIIRS, for accessERDDAP()
erddap_datasets() # which ERDDAP datasets ship
variable_dataset(c("SST", "CHL")) # which dataset each comes from
as.data.frame(variable_dictionary())$description # full descriptions