Skip to content

Repository files navigation

oceansensing.github.io

Website of C4PO — the Collaboratory for Physical Oceanography at the Virginia Institute of Marine Science, live at oceansensing.org.

Built with Astro. Static output, and close to zero client-side JavaScript — the exceptions are the theme toggle, the photo shuffle and lightbox on observation pages, the asset map, and the UTC clock. The map is the one heavyweight: gliders from four national data centers, NOAA saildrones, the Argo array, animated currents at two depths, sea-surface temperature and salinity, isobaths, a detailed coastline and EEZ boundaries, and ECMWF 10 m wind and 2 m air temperature — with the color scale, its range, the isobath opacity and every layer under the reader's control. One button takes a reader's own data over the top of it — KML, KMZ, GeoJSON or a shapefile, zipped or as loose parts — with the format worked out from the bytes rather than the file name — from a file or from a pasted link, where the host allows it. The ESPC fields show the step nearest the reader's own clock rather than a fixed offset from the model run, the currents publishing the two steps that span a six-hour window so the tide in that model can be stepped through, and the map's attribution says which hour is drawn and how far from now it is. The address bar carries the whole view as you move, so the URL is a link to exactly what is on screen — with a Copy link button beside the map, since selecting a URL out of a phone's address bar is an awkward gesture. Beside it, one button saves the figure as a PNG — doubled for print, with the color bar and the credit line baked in, so a figure dropped into a talk still says where it came from.

It carries two pages. /visualization/ is the general-purpose one and /observations/hurricanes/ is framed around the storm season. They run the same engine off the same data and differ only in their preset — which layers open, and where. Adding another such page means one .astro file passing layers and home to AssetMap.

It lives in packages/ocean-map, an npm workspace package rather than a page component, so it can be used elsewhere: see its README, EMBEDDING.md for another website, and PORTING-IOS.md for the native app. Anything added to it has to keep both of those viable — BOUNDARIES.md says what that means.

A third page, /dev/visualization/, runs a sandbox fork of the same engine — packages/ocean-map-dev — against the same live data. It is where an idea gets tried before it is held to the gates the real map is held to, and it says so on the page. What is in it at the moment is a polar stereographic view of either pole, with the longitude at the top of the screen adjustable, because Web Mercator puts the pole at infinity and draws the ice at the one aspect ratio that makes it unreadable. Every push to main deploys automatically via GitHub Actions, and a scheduled run rebuilds hourly to refresh the map data; the hurricane page picks that up on its own without losing your place on the map.

There is also a seawater calculator under Data & Tools: every thermodynamic property at full TEOS-10 precision, evaluated from the Gibbs function rather than from the 75-term polynomial, with the Absolute Salinity anomaly applied from position. It runs entirely in the browser. The physics is packages/teos10 — another workspace package, DOM-free and renderer-independent, checked against the GSW reference implementation on every build.

There is also a Slocum glider data decoder reads the binary files a Slocum logs — sbd, tbd, mbd, nbd, dbd, ebd and their compressed forms — and writes CSV, netCDF, or the OceanGliders OG1.0 format. Seawater properties from the CTD are one checkbox. Nothing is uploaded.

Drop a whole deployment folder, data and its cache directory, in either order. Files from several gliders, or from one glider with a break of three days or more, come out as separate deployments in separate files.

Two plots, and either will draw anything: X, Y and color take any column, each with its own limits, as a scatter, a line or both, in any of twenty color scales. Point size and plot size are yours, and each plot saves as a PNG. They differ only in what they open on — the dive on the whole record against time, the profile on a sensor against depth.

The files and your setup are kept in the browser, so a reload comes back to where you were. Clearing the data leaves the caches and your plot setup behind, so the next segment off the same glider comes up drawn the way the last one was. The decoder itself is packages/slocum, a port of SlocumIO.jl checked against dbdreader value for value.

Off the same engine there is a glider ballast calculator: how much lead to add, what the vehicle should then read in the tank, and whether its buoyancy engine can cover the water it will fly in. The arithmetic is packages/glider-ballast. The vehicle numbers it ships are stand-ins and the page says so — replace them from your own ballast sheet.

Editing content

All content lives in Markdown and data files — you never need to touch layout code:

To add… Edit…
a publication paste its BibTeX entry into src/data/publications.bib
a presentation new entry in src/data/presentations.yaml (type: invited / conference / workshop / outreach)
a news post new .md file in src/content/news/
a person new .md file in src/content/people/
a research project new .md file in src/content/projects/
a past project set status: completed in the project's frontmatter — the Research page moves it to a "Past projects" section automatically
a dataset new entry in src/data/datasets.yaml
a software tool new entry in src/data/software.yaml
a seawater property one entry in packages/teos10/index.ts's build(), and a check in scripts/test-teos10.mjs — the label, unit and precision live with the physics so the screen, the clipboard and the CSV cannot disagree
a glider to the ballast calculator one entry in packages/glider-ballast/vehicles.ts. Set illustrative: false only with a real ballast sheet in hand — that flag is what turns off the page's caution
a derived column to the Slocum decoder one entry in packages/slocum/derive.ts, and a range check in scripts/test-slocum.mjs — derived columns are marked as such in both outputs, so a reader can always tell them from what the glider recorded
a significant observation new .md file in src/content/observations/ — add map: assets for the live asset map, or surveys: entries for dated photo panels
a CV item new entry in the matching src/data/cv/<person>/*.yaml file (grants, advising, service, …) — publications and presentations flow in automatically
a member's CV new directory src/data/cv/<person-id>/ (id matching their file in src/content/people/) with any of the section files — their page appears at /cv/<person-id>/

Site title, navigation, contact info, and the list of author names bolded on the Publications page live in src/config.ts. Colors, fonts, and spacing live in src/styles/tokens.css.

Developing locally

npm install
npm run dev      # dev server at localhost:4321
npm run build    # production build into dist/
npm run verify   # everything CI checks, in one command

npm run verify builds, type-checks, checks the docs for drift, and runs the map, multimap, calculator, decoder and clock test harnesses — over 1,400 assertions in all. CI runs the same command and refuses to deploy if it fails, so running it before you push is the quickest way to find out whether a change will publish.

The individual pieces, if you want one on its own:

npm run check          # type-check
npm run check:docs     # docs reference real scripts, real paths, the right URL
npm run test:units     # the map's renderer-independent modules, with no DOM at all
npm run test:teos10    # the TEOS-10 package against GSW, against calculus, against physics
npm run test:ballast   # the glider ballast arithmetic, against its own identities
npm run test:seawater  # seawater calculator, against the built bundle
npm run test:ballast-page # glider ballast calculator, against the built bundle
npm run test:slocum    # the Slocum decoder, against dbdreader's recorded answers
npm run test:slocum-page # Slocum decoder page, against the built bundle
npm run test:prose     # built pages keep the spaces Astro likes to eat
npm run test:schema    # every published data file against the contract in schema.ts
npm run test:contrast  # map colors stay visible, and every conceded clash is named
npm run test:map       # asset map, against the built bundle
npm run test:multimap  # two maps on one page stay out of each other's way
npm run test:clock     # UTC clock, against the built bundle

The test harnesses read from dist/, so build first or they test stale code.

Refreshing map data by hand — CI does all of this on every deploy, so you only need it locally when working on the map:

npm run data           # storms, gliders (four regional ERDDAPs), USVs, Argo floats
npm run data:currents  # global + regional current grids, surface and 60 m
npm run data:tiles     # the 1/12° current tiles (~92 MB per depth, per forecast hour built)
npm run data:fields      # global + regional sea-surface temperature and salinity grids
npm run data:wind        # ECMWF 10 m wind — the one pipeline with a Python dependency
npm run data:field-tiles # native-resolution Navy field tiles (OISST needs none — its regions already are native)
npm run data:basemaps  # re-sample basemap ocean colors (slow; GEBCO's WMS)

The tiles are gitignored, so a fresh clone has none and the map simply uses the coarser grids until you build them.

data:wind is the exception to "no Python dependencies": ECMWF packs its open GRIB2 with CCSDS/AEC, which the standard library cannot decode. Install it with pip install -r scripts/requirements-wind.txt — binary wheels, no system packages. Everything else runs on a bare Python 3.

Deployment

Pushing to main deploys to GitHub Pages, gated on npm run verify — if the checks fail, nothing is published. The same workflow runs hourly on a schedule to refresh the map data; that run commits nothing, so the repository does not grow.

The full-resolution tiles are the exception to "refresh everything hourly": the current tiles are ~92 MB per depth and the Navy temperature and salinity tiles another ~43 MB each, and none of them change until the ocean model runs, once a day at 12Z. CI caches them keyed on that model run and rebuilds only when it advances.

DNS lives at the registrar: apex A records to 185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153, and a www CNAME to oceansensing.github.io. The apex is canonical and HTTPS is enforced. The same zone carries the lab's PrivateEmail MX records — do not let a registrar "Mail Settings" default overwrite them, or lab email stops.

Working on this repo with Claude Code? CLAUDE.md has the architecture notes and PLAN.md tracks what is still open.

License

Copyright (c) 2026 Donglai Gong and C4PO. All rights reserved — see LICENSE. The repository is public so the site can be served from GitHub Pages and so the work is open to inspection; that is not a grant of any license to reuse it.

Third-party material keeps its own terms: dependencies as declared in package.json, and the scientific data — served from ocean-data-repo — under the terms of the bodies that produced it (GEBCO, Natural Earth, NOAA, IOOS, Ifremer, the US Navy via HYCOM, Marine Regions and others), each credited on the map.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages