🌐 View data in browser · 💻 Data access (code) · 📦 Data access (AWS Open Data) · 📦 Data access (HYCOM.org)
One of the HYCOM stores in ocean-icechunks/hycom, which says what they have in common. This page is about this dataset.
The HYCOM GOFS 3.1 global reanalysis
(GLBv0.08 expt_53.X, 41-layer HYCOM + NCODA, 1/12°, 3-hourly, 1994–2015) as one
Icechunk store that opens in about a second as a single
(time, depth, lat, lon) datacube:
https://data.source.coop/ocean-icechunks/hycom/hycom-gofs-3pt1-reanalysis
This is a virtual store. It holds about 85 MB of Zarr metadata and 10,451,265 byte-range
references; the 306 TB of arrays stay where they are, in 63,341 NetCDF files in the
HYCOM bucket on AWS Open Data
(s3://hycom-gofs-3pt1-reanalysis, us-west-2, managed by COAPS). Every read of a science
value goes to that bucket, so the store works only as long as the bucket does. No data was
copied, rewritten or changed.
⚠️ Read this first: the data will not draw unless you disable CORS
The viewer loads, lists the variables and draws the map graticule, and then stops — because the science arrays are not in the store. They are in the HYCOM bucket on AWS, which serves byte ranges happily to a script but has no CORS configuration (checked 2026-09-19), so your browser refuses to hand those bytes to the page. This is a rule browsers enforce on the page's behalf; nothing the viewer or this repository can contain will waive it. Only the bucket's owner can change it, and the store would not need rebuilding if they did.
With that in place, the viewer streams chunks straight from the source files — no install, no account, no download:
Each link opens at the first time step, at the surface; the other variables, the time steps
and the depth levels are controls in the viewer. Everything after # is a URL fragment,
which the host never sees, so one viewer build serves any store. Drag the globe and the
address bar updates — copy it to share the exact view you are looking at.
The viewer is gridlook, a WebGL globe for
cloud-hosted Zarr and Icechunk stores, published alongside the data at
hycom/viewer/. Every
frame is one whole 29 MB level fetched from us-west-2, so it is not quick: treat it as a
look, not an analysis. With an extension enabled, a 16-step test copy of this store was seen
to render on 2026-09-18; this full store uses the same viewer build and layout.
Python ≥ 3.12 (Icechunk 2.x requires it). No credentials are needed for anything.
pip install "icechunk>=2.2" "xarray>=2026.7" "zarr>=3.4" "dask[array]"
icechunk 1.x will not work.
icechunk.http_storagedoes not exist in icechunk 1.x (it arrived in 2.0), andicechunk.credentials.HttpAccessarrived in 2.1. Every icechunk 2.x release needs Python 3.12 or newer, so on an older Pythonpip install icechunkquietly installs 1.1.x, and the code below fails withAttributeError: module 'icechunk' has no attribute 'http_storage'. Check what you have:python -c "import icechunk; print(icechunk.__version__)".
chunks=None above is deliberate, and it is still lazy — nothing is read until you ask
for values. The usual chunks={} works here but is slow: it makes each 4-D variable a dask
array of 2,570,880 chunks, and dask then spends seconds and about a gigabyte on every
operation, however small the selection. Select what you want first and hand dask only that:
Do not skip the .chunk(...) on a large selection: without dask, everything selected is
loaded at once. The same 10-day mean, measured three ways (identical results):
The time axis is regular: every 3-hourly step is there, including the 931 for which the archive has no file. Those steps read as NaN, fetch nothing, and are marked by two coordinates that travel with every variable:
tau is the forecast hour within each daily run (0 is the 12Z analysis, 3–21 the forecast
steps that fill the day). The gaps are not random: 593 of the 931 are 12Z analyses, about
7 % of them, against 0.6 % of any other hour. A plain time mean therefore under-weights 12Z;
for anything with a daily cycle, average daily means instead, and use
resample(time="1D").count() to see which days are short.
missing-time-steps.csv lists all 931. hycom.org publishes
its own list
of 923 and says they will not be filled; all 923 are gaps here too. The other 8 are the whole
2014-12-31 run, between experiments 53.8 and 53.9, which is absent from hycom.org's own
server as well.
Measured from a JupyterHub in us-west-2, the same region as the HYCOM bucket, with
async.concurrency = 64. Metadata comes from Source Cooperative and data from the HYCOM
bucket, so elsewhere the shapes are the same and the numbers larger.
Any selection inside a level fetches the whole 29 MB level. The source files are uncompressed and unchunked, and a level is the smallest contiguous piece (3251 is prime, so it cannot even be split into latitude bands). Maps, sections, profiles and area means over days to months are the good cases. A point time series over the whole record is the bad one: a single level at a single point is 1.85 TB of reads. No setting changes that; only rewriting the data would.
Values decode to float64, where the source NetCDF gives float32, because Zarr attributes
are JSON and cannot say "float32". A decoded level is 117 MB. Use .astype("float32") to
halve that, or xr.open_zarr(..., mask_and_scale=False) for the raw int16.
Global Ocean Forecasting System (GOFS) 3.1 reanalysis by the U.S. Naval Research Laboratory,
Ocean Dynamics and Prediction Branch: HYCOM with NCODA data assimilation, run on the 1/12°
GLBb0.08 grid and interpolated by NRL to the GLBv0.08 grid (0.08° in longitude; 0.08° in
latitude between 40°S and 40°N and 0.04° poleward) and to 40 fixed depth levels. Surface
forcing is NCEP CFSR and CFSv2. Each day is one run: the 12Z analysis (tau = 0) and the
3-hourly steps to 09Z the next day. The reanalysis is a sequence of ten experiments,
53.0 to 53.9, recorded per time step in the experiment coordinate:
Known problems, from hycom.org: unrealistic deep-water formation in the Ryukyu Trench in the western Pacific (and possibly elsewhere), which led to occasional instabilities over the whole water column; and noisy sea surface height and layer interfaces in the Philippine Sea. Read their description before using either region.
All nine science variables are stored as the source's big-endian int16 with
scale_factor = 0.001, an add_offset of 20 for temperature and salinity, and
_FillValue = -30000 over land and below the sea floor.
hycom-icechunk-sc.ipynb with
hycom_virtual.py; dependency floors in
requirements.txt.
hycom-smoke-test-local.ipynb is the same build on 29 files
into a temporary local repository — it needs no credentials and runs in 90 seconds. The
build notebook also needs icechunk_utils.py, from the repository root, for Source
Cooperative write credentials; reading and validating need nothing.
The source files are NetCDF 64-bit offset (NetCDF-3): uncompressed and unchunked, each 4-D variable one 1.17 GB byte range. Because nothing is compressed, a depth level is a contiguous 29 MB slice of that range, so the store references one chunk per level without rewriting anything. The idea of sub-chunking this archive is Rich Signell's (hycom-kerchunk, write-up).
No file is parsed. Each file's header is read with one 67 kB ranged GET and the
references are computed from it, which is why 63,341 files of 4.8 GB take a 75-second scan
and two minutes of writes. Before anything is written, every file must pass the same checks:
one record, the same schema as every other file, byte-identical coordinates, a size equal to
what its header implies, and an in-file time and tau equal to those in its filename. All
63,341 passed.
Offsets come from each file's own header, never from a template. The archive has two
header layouts, 40 bytes apart: 35,902 files spell four standard names …_at_bottom and
27,439 do not. The two are mixed within experiments 53.2–53.7, so neither the experiment nor
the date predicts the layout. References built from one file's offsets put the data of the
other kind 20 grid cells out of place.
The store is written as a skeleton (coordinates, and science arrays holding no references)
followed by one commit per year, so its history is its build log:
icechunk.Repository.open(...).ancestry(branch="main").
Metadata only — no array value is touched — to make the store CF-1.11 compliant. The IOOS
compliance checker passes cf:1.11 on an export of it.
v1 = snapshot VBMJX9KNE9BN8GTG5100, built 2026-09-18 with icechunk 2.2.2,
virtualizarr 2.7.3, zarr 3.4.0, xarray 2026.7.0, Python 3.12.source-files.csv.gz is the source manifest: every file referenced,
with its size, experiment, forecast hour and header length.tau and experiment reconcile
exactly with the bucket listing; surf_el holds exactly 63,341 references; and raw int16
read through the references is identical to netCDF4-C's own byte-range reader for 30
files — the first and last, both sides of all nine experiment boundaries, the longest gap,
and eight at random — across all nine variables. The executed notebook shows the run.The archive is static (1994–2015, no updates), so there is no update pipeline.
Code. The notebooks and hycom_virtual.py are released under
Apache-2.0. You are free to
use, copy, modify, and redistribute them, including commercially. If you use them in
published work, in a presentation, or in another repository, please give attribution:
Holmes, E.E. and Signell, R. (2026). HYCOM as virtual Icechunk stores. ocean-icechunks/hycom. https://github.com/ocean-icechunks/hycom
Rich Signell is named because this work rests on his: he showed that the uncompressed NetCDF-3 files of this archive can be given virtual chunks, one per depth level, without rewriting them. None of his code is used here, but the approach is his. Please cite it too:
Signell, R. (2024). Using Kerchunk with uncompressed NetCDF 64-bit offset files: Cloud-optimized access to HYCOM Ocean Model output on AWS Open Data. Pangeo (Medium). https://medium.com/pangeo/using-kerchunk-with-uncompressed-netcdf-64-bit-offset-files-cloud-optimized-access-to-hycom-ocean-9008ba6d0d67
Signell, R. (2024). hycom-kerchunk. https://github.com/rsignell/hycom-kerchunk
Data. The data is not ours, and this store contains none of it — only references to files in the HYCOM bucket. The AWS Open Data registry states "There are no restrictions on the use of this data." hycom.org recommends this acknowledgement in any publication using HYCOM data:
Funding for the development of HYCOM has been provided by the National Ocean Partnership Program and the Office of Naval Research. Data assimilative products using HYCOM are funded by the U.S. Navy. Computer time was made available by the DoD High Performance Computing Modernization Program. The output is publicly available at https://hycom.org.
The data is a demonstration product of the HYCOM Consortium, provided as is.
To look at the data anyway, install a CORS-disabling browser extension (search your browser's extension store for "CORS unblock" or "Allow CORS"), enable it, and reload the viewer. Such an extension switches off a real security protection for the sites you enable it on, so turn it back off when you are done — or use a separate browser profile for it.
The code path below has no such problem: this affects browsers only.
sea_water_salinity_at_sea_floor |
| 1e-3 |
water_u_bottom | time, lat, lon | eastward_sea_water_velocity_at_sea_floor | m/s |
water_v_bottom | time, lat, lon | northward_sea_water_velocity_at_sea_floor | m/s |
tau (coordinate) | time | forecast_period | hours |
experiment (coordinate) | time | — (CF flag values 0, 530…539) | — |
standard |
| CF's current name for the same calendar |
temperature, time | — | units_metadata | CF-1.11 recommendations |
| global | Conventions = "CF-1.6 NAVO_netcdf_v1.1" | CF-1.11, plus title, experiment_id, references, source_data, comment; the original kept as source_Conventions | the source files name no experiment, grid or version anywhere but the filename |
| added | — | experiment coordinate; tau extended over the missing steps as NaN | provenance per time step, and a way to find the gaps without reading data |