@fxi/zartigl 0.1.4 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,127 +1,307 @@
1
1
  # zartigl
2
2
 
3
- A MapLibre GL JS plugin for rendering animated particle flow from Zarr-based ocean current data directly in the browser.
3
+ A MapLibre GL JS plugin for exploring cloud-hosted Zarr geoscience data directly in the browser.
4
4
 
5
- [![Demo](https://img.shields.io/badge/demo-GitHub%20Pages-blue)](https://fxi.github.io/zartigl/)
5
+ [![Demo](https://img.shields.io/badge/demo-live-blue)](https://fxi.io/zartigl/)
6
+
7
+ Zartigl renders scalar rasters and vector particle fields from multidimensional Zarr stores without a dedicated tile server. It is aimed at expert analysis workflows where time, depth or pressure level, point inspection, and reproducible map state matter as much as the visual layer.
6
8
 
7
9
  ## Features
8
10
 
9
- - GPU-accelerated particle simulation (WebGL ping-pong framebuffers)
10
- - Reads Zarr v2 stores directly in the browser (blosc + zlib decompression)
11
- - Streams data from Copernicus Marine ARCO S3 — no local data or server needed
12
- - Viewport-aware chunk loading with LRU cache
13
- - Speed-based particle coloring via configurable color ramp
14
- - Trail rendering with adjustable fade opacity
11
+ - Browser-side Zarr v2 access with chunk-aware loading and LRU caching
12
+ - Scalar raster rendering with palettes, opacity, log scale, and value inspection
13
+ - Vector rendering with GPU particle advection and optional raster magnitude display
14
+ - Time and vertical-dimension controls for depth, elevation, level, or pressure-like axes
15
+ - Point queries for time series and vertical profiles when a point-series store is available
16
+ - Catalog-backed datasets with automatic Zarr/WMTS backend selection for scalar layers
17
+ - MapLibre custom layer integration, including Mercator and globe rendering paths
18
+ - Pre-rendered GeoVideo backends with true polar globe draping and portable masks
19
+ - No server component required for public CORS-enabled stores
15
20
 
16
- ## Quick start
21
+ ## Quick Start
17
22
 
18
23
  ```bash
19
- npm install zartigl maplibre-gl
24
+ npm install @fxi/zartigl maplibre-gl
20
25
  ```
21
26
 
22
27
  ```ts
23
28
  import maplibregl from "maplibre-gl";
24
- import { ParticleLayer } from "zartigl";
25
-
26
- const map = new maplibregl.Map({ container: "map", style: "..." });
27
-
28
- map.on("load", () => {
29
- map.addLayer(
30
- new ParticleLayer({
31
- id: "currents",
32
- source: "https://your-zarr-store/",
33
- variableU: "uo",
34
- variableV: "vo",
35
- }),
36
- );
29
+ import { Zartigl } from "@fxi/zartigl";
30
+ import { catalog } from "@fxi/zartigl/catalog";
31
+
32
+ const map = new maplibregl.Map({
33
+ container: "map",
34
+ style: "https://demotiles.maplibre.org/style.json",
35
+ center: [0, 20],
36
+ zoom: 2,
37
+ });
38
+
39
+ const z = new Zartigl({
40
+ id: "zarr-layer",
41
+ map,
42
+ catalog,
43
+ backend: "auto",
44
+ // Optional: expose only the month ending at the latest available timestamp.
45
+ timeRange: { trailing: "P1M" },
46
+ geoVideo: { autoplay: false, loop: true, playbackRate: 1 },
47
+ });
48
+
49
+ await z.setLayer("ocean-current-velocity");
50
+ z.setTimeAndDepth(new Date("2025-01-01T00:00:00Z"), 0);
51
+ z.updateSettings({
52
+ palette: "rdylbu",
53
+ opacity: 0.9,
37
54
  });
38
55
  ```
39
56
 
57
+ `setLayer()` resolves after the field store's consolidated metadata and coordinate axes have loaded. Metadata getters are therefore authoritative after the awaited call, including exact values for irregular time axes.
58
+
59
+ The root import does not bundle the catalog presets. Import catalog data from `@fxi/zartigl/catalog` only when you want the built-in catalog.
60
+
40
61
  ## Demo
41
62
 
63
+ The production demo is the default Vite app and is published at
64
+ [`fxi.io/zartigl`](https://fxi.io/zartigl/).
65
+
42
66
  ```bash
43
67
  npm install
44
68
  npm run dev
45
69
  ```
46
70
 
47
- The demo reads a catalog (`public/data/catalog.json`) that points to the [Copernicus Marine ARCO Zarr store](https://help.marine.copernicus.eu/en/articles/12332770-introduction-to-the-arco-format) on S3. No local data download is needed — chunks are fetched directly from Copernicus (public, CORS enabled).
71
+ Build the same app that the public demo publishes:
48
72
 
49
- To update the catalog metadata:
73
+ ```bash
74
+ npm run build:prod
75
+ ```
76
+
77
+ For the smaller public API demo:
50
78
 
51
79
  ```bash
52
- cd scripts
53
- uv run build_catalog.py
80
+ npm run dev:minimal
54
81
  ```
55
82
 
56
- This requires Python >= 3.12, [uv](https://docs.astral.sh/uv/), and a free [Copernicus Marine](https://data.marine.copernicus.eu/register) account.
83
+ The demos read the built-in catalog, which points to public Copernicus Marine ARCO stores. No local data download is needed; visible chunks are fetched directly from cloud object storage or, for some scalar layers, from WMTS when that is the safer backend.
84
+
85
+ ## Public API
86
+
87
+ The main entry point is the catalog-backed `Zartigl` facade:
88
+
89
+ ```ts
90
+ const time = z.getTimeMeta();
91
+ const depth = z.getDepthMeta();
92
+ const legend = z.getLegend();
57
93
 
58
- ## Dataset
94
+ z.on("loaded", (meta) => {
95
+ console.log(meta.min, meta.max, meta.unit);
96
+ });
97
+
98
+ z.on("status", (status) => {
99
+ // metadata → fetching → rendering → ready, or blocked/error
100
+ console.log(status.phase);
101
+ });
102
+
103
+ // GeoVideo uses native media playback and reports scientific time updates.
104
+ z.on("timeChange", (time) => console.log(new Date(time)));
105
+ z.on("playbackChange", (playing) => console.log({ playing }));
106
+ z.setPlaybackRate(2);
107
+ z.setLoop(false);
108
+
109
+ // Embedding applications can stop rendering and abort field requests while
110
+ // retaining the latest requested time/depth for one reload on resume.
111
+ z.suspend();
112
+ z.setTime(new Date("2025-02-01T00:00:00Z"));
113
+ z.resume();
114
+
115
+ const series = await z.queryTimeSeries({
116
+ longitude: 7.4,
117
+ latitude: 46.9,
118
+ depth: 0,
119
+ maxPoints: 256,
120
+ });
121
+ ```
59
122
 
60
- [GLOBAL_ANALYSISFORECAST_PHY_001_024](https://data.marine.copernicus.eu/product/GLOBAL_ANALYSISFORECAST_PHY_001_024) from Copernicus Marine Service — `uo`/`vo` surface current velocity at ~0.083deg resolution, streamed as ARCO Zarr directly from Copernicus S3.
123
+ New layers initially select the latest advertised timestamp that is not in the
124
+ future. Forecast timestamps remain available through `getTimeMeta().values`.
125
+ Missing remote chunks are interpreted as Zarr fill data. A wholly unavailable
126
+ frame is not cached, so selecting it again can recover after an upstream store
127
+ finishes publishing it; a renderable sparse frame is cached normally.
61
128
 
62
- ## Other use cases
129
+ `VectorLayer`, `ScalarLayer`, `ArcoLayer`, and `ZarrSource` remain exported for advanced use cases that need direct control over renderer internals. Ordinary MapLibre integrations should prefer `Zartigl`.
63
130
 
64
- Any 2D vector field stored as U/V components in a Zarr store works as a source. Examples:
131
+ ## Alternatives And Project Fit
65
132
 
66
- - **Atmospheric wind** — reanalysis products (ERA5, MERRA-2) or NWP model output; visualise jet streams, storm tracks, or surface winds at any pressure level
67
- - **Ocean surface waves** — Stokes drift or wave propagation direction fields
68
- - **Ice drift** — sea-ice motion vectors from passive microwave retrievals
69
- - **River discharge / flood routing** — depth-averaged U/V from hydrodynamic models (Delft3D, SCHISM)
70
- - **Air quality transport** — horizontal advection of pollutant plumes from dispersion models
71
- - **Climate projections** — CMIP6 ocean or atmosphere circulation at any time slice; compare historical vs. SSP scenarios
72
- - **Groundwater flow** — Darcy velocity fields from subsurface models
73
- - **Wildfire smoke** — wind-driven particle transport overlaid on active fire data
133
+ Zartigl overlaps with several useful projects. The goal is not to replace them, but to cover a specific MapX/ARCO workflow: public cloud Zarr stores, MapLibre integration, animated vector particles, scalar rasters, time/depth controls, point inspection, and reproducible widget configuration without a dedicated tile server.
74
134
 
75
- The only requirement is a Zarr v2 store with two named float variables representing the east and north components of the field.
135
+ The table below is a fit matrix based on documented project scope. "Not specified" means the feature is not a stated focus in the referenced project, not that it is impossible.
76
136
 
77
- ## How it works
137
+ | Project | Best fit | Zarr raster | Vector particles | Time/depth UI | Query API | Polar/globe rendering | Notes |
138
+ | --- | --- | --- | --- | --- | --- | --- | --- |
139
+ | zartigl | MapX-ready ARCO-like ocean layers | Yes, Zarr v2 | Yes | Built in | Time series and vertical profiles when a point-series store is available | MapLibre globe/ECEF path for lon/lat scalar and vector layers, including over-pole views | Catalog defaults, WMTS fallback for selected scalar layers, MapX snippet generation |
140
+ | [Copernicus Marine MyOcean Pro](https://data.marine.copernicus.eu/viewer/expert) | Copernicus end-user exploration | Service-managed | Product-dependent | Application UI | Product UI, not a reusable library API | Service-managed | Reference viewer, not a MapLibre/WebGL library to embed in MapX |
141
+ | [@carbonplan/zarr-layer](https://github.com/carbonplan/zarr-layer) | General Zarr raster layers for MapLibre/Mapbox | Yes, Zarr v2/v3 | No, raster-focused | Selector API | GeoJSON queries | Full polar coverage documented for MapLibre with suitable untiled datasets | Strong closest alternative for raster Zarr; supports custom stores, CRS reprojection, custom fragment shaders |
142
+ | [@carbonplan/maps](https://github.com/carbonplan/maps) | React maps for prepared multidimensional rasters | Yes, prepared Zarr pyramids | No | React selector workflow | Not its main focus | Web Mercator-focused | Higher-level React framework; its core MapLibre path is documented as Web Mercator-only |
143
+ | [zarr-gl](https://github.com/carderne/zarr-gl) | Lightweight Zarr layer for Mapbox/MapLibre | Yes | No | Limited | Not specified | Not specified | The README now recommends using `@carbonplan/zarr-layer` instead |
144
+ | [maplibre-gl-wind](https://github.com/geoql/maplibre-gl-wind) | Wind particle layer for deck.gl/MapLibre | No | Yes | No | No | Not specified | Takes wind textures or point data, not Zarr metadata/chunks |
78
145
 
79
- A plain-language walkthrough for GIS analysts unfamiliar with WebGL or Zarr.
146
+ The closest general alternative is `@carbonplan/zarr-layer`. It has a broader documented scope for raster Zarr rendering: Zarr v2/v3, custom stores, CRS reprojection, custom fragment shaders, GeoJSON queries, and full polar coverage in MapLibre for suitable untiled datasets. If the requirement were only "draw a multidimensional Zarr raster on a MapLibre map", using or contributing to `@carbonplan/zarr-layer` would be a strong option.
80
147
 
81
- ### 1. Zarr is a tiled raster cube
148
+ Zartigl remains useful because it combines pieces that are not currently covered together by those projects:
82
149
 
83
- Think of a Cloud Optimized GeoTIFF extended into four dimensions: longitude × latitude × depth × time. The data is split into chunks — here: 1 time step × 1 depth level × 512 rows × 2048 columns. Zartigl requests only the chunks that intersect the current viewport at the selected time and depth, exactly like a COG tile request.
150
+ - ARCO-oriented catalog entries and defaults for Copernicus Marine products.
151
+ - U/V vector products rendered as animated particles, with optional raster magnitude in the same layer.
152
+ - Time and vertical-axis metadata exposed as application controls.
153
+ - Depth/elevation labeling, ordering, and point inspection tuned for ocean products.
154
+ - MapLibre globe rendering for both scalar and vector fields, including polar scenes beyond the usual Web Mercator `85.0511°` latitude limit.
155
+ - GPU particle-state fallbacks for browser and hardware differences, including float, half-float, and `RGBA8` compatibility mode.
156
+ - A facade API that can generate MapX widget snippets and keep widget state reproducible.
84
157
 
85
- ### 2. Velocity becomes a two-channel raster
158
+ deck.gl, Three.js, or `@carbonplan/zarr-layer` could still be useful integration targets. They would not remove the ARCO catalog, time/depth metadata, vector particle simulation, point-query workflow, or MapX widget-state work that zartigl currently owns.
86
159
 
87
- Each chunk holds two float arrays — **U** (eastward m/s) and **V** (northward m/s). These are normalized and packed into the R and G channels of an image uploaded to the GPU:
160
+ ## Catalog Metadata
88
161
 
162
+ To validate the built-in catalog:
163
+
164
+ ```bash
165
+ npm run catalog:validate
89
166
  ```
90
- R = eastward velocity (0 → max west, 128 → still, 255 → max east)
91
- G = northward velocity
92
- A = 255 (ocean) | 0 (land / nodata) ← validity mask
167
+
168
+ To add or update catalog entries:
169
+
170
+ ```bash
171
+ uv run scripts/catalog_builder/skills/list_layers.py
172
+ uv run scripts/catalog_builder/skills/search_products.py wave
173
+ uv run scripts/catalog_builder/skills/query_dataset.py <dataset_id>
174
+ uv run scripts/catalog_builder/skills/validate_catalog.py
93
175
  ```
94
176
 
95
- The result is a global velocity raster in GPU memory. The alpha channel is a nodata mask.
177
+ This requires Python >= 3.12, [uv](https://docs.astral.sh/uv/), and a free [Copernicus Marine](https://data.marine.copernicus.eu/register) account.
96
178
 
97
- ### 3. Particles are virtual drifters
179
+ ## GeoVideo
98
180
 
99
- Thousands of virtual oceanographic drifters are seeded randomly across the viewport. Every frame (~60 fps), the GPU runs this logic for all of them simultaneously:
181
+ GeoVideo pre-renders an expensive scalar Zarr timeline into a short, streamable
182
+ H.264 MP4 while retaining spatial bounds, scientific dates, provenance, units,
183
+ palette, and color domain in a sidecar manifest. Version 2 transports quantized
184
+ scalar values in luminance and a static lossless mask as a separate PNG, so the
185
+ browser can apply palette and scalar styling in WebGL. It is a visualization
186
+ backend only: point queries always use the authoritative Zarr point-series store.
100
187
 
101
- > *Where am I? → sample the velocity raster at my position → move a tiny step in that direction → if I drift off-screen or hit land (A = 0), teleport to a random ocean pixel inside the viewport.*
188
+ Unlike MapLibre's native four-corner video source, `GeoVideoLayer` maps an
189
+ equirectangular scalar-luma texture through zartigl's subdivided lon/lat mesh.
190
+ Globe mode therefore reaches both poles. A separate lossless image supplies the
191
+ static validity mask without doubling the video width.
102
192
 
103
- Same logic as a drift model — just running on 65 000+ drifters at 60 frames per second.
193
+ ### Displaying a GeoVideo
104
194
 
105
- ### 4. Trails are faded time-lapse
195
+ The built-in catalog includes an Arctic sea-ice thickness animation. Select the
196
+ GeoVideo backend explicitly and enable autoplay when creating the `Zartigl`
197
+ instance:
106
198
 
107
- Before each new frame, the previous frame is multiplied by ~0.9999. Particles leave a fading streak — like a long-exposure photograph of drifters. Streak length encodes time; color encodes speed magnitude via a configurable color ramp.
199
+ ```ts
200
+ import maplibregl from "maplibre-gl";
201
+ import { Zartigl } from "@fxi/zartigl";
202
+ import { catalog } from "@fxi/zartigl/catalog";
203
+
204
+ const map = new maplibregl.Map({
205
+ container: "map",
206
+ style: "https://demotiles.maplibre.org/style.json",
207
+ center: [-132.454826, 85.051129],
208
+ zoom: 0.511,
209
+ });
108
210
 
109
- ### 5. Particle state lives in GPU memory
211
+ map.setProjection({ type: "globe" });
212
+
213
+ const z = new Zartigl({
214
+ map,
215
+ catalog,
216
+ backend: "geovideo",
217
+ geoVideo: {
218
+ autoplay: true,
219
+ loop: true,
220
+ playbackRate: 1,
221
+ },
222
+ });
110
223
 
111
- Drifter positions are never stored in a CPU array. They are encoded as pixel colors in a GPU texture (X → red, Y → blue, sub-pixel precision in the low byte). Two textures alternate each frame — read from A, write to B, swap — so the GPU never waits on itself. This is the **ping-pong** pattern.
224
+ await z.setLayer("sea-ice-thickness");
225
+ ```
226
+
227
+ `backend: "geovideo"` selects the published GeoVideo manifest from the
228
+ catalog's `sea-ice-thickness` entry. Globe projection demonstrates its polar
229
+ coverage. `loop` and `playbackRate` are optional playback settings. Browsers
230
+ may restrict autoplay, so embedding applications should provide a user-gesture
231
+ fallback when necessary.
232
+
233
+ Try the configured instance directly in the [Arctic sea-ice GeoVideo example](https://fxi.io/zartigl/#eyJkIjoxMSwidCI6MTc4MTAwNDMyNTY3OC40OTY4LCJ2IjowLCJwIjoiaWNlIiwicyI6Imdlb3ZpZGVvIiwicHIiOiJnbG9iZSIsImMiOlstMTMyLjQ1NDgyNiw4NS4wNTExMjldLCJ6IjowLjUxMSwiYiI6MCwicGkiOjAsInBkIjowLjA1LCJzcCI6MSwiZiI6MC43LCJybSI6InBhcnRpY2xlcyIsIm9wIjowLjksImxzIjp0cnVlLCJ2YiI6LTEsImNkIjpudWxsLCJnYSI6dHJ1ZSwiZ2wiOnRydWUsImdyIjoxMH0=).
234
+
235
+ ```bash
236
+ uv run scripts/geovideo/render.py scripts/geovideo/examples/sst-anomaly.json --dry-run
237
+ uv run scripts/geovideo/render.py scripts/geovideo/examples/sst-anomaly.json
238
+ uv run scripts/geovideo/render.py scripts/geovideo/examples/sst-anomaly.json --upload
239
+ ```
240
+
241
+ The Arctic sea-ice configuration is available at
242
+ [`scripts/geovideo/examples/sea-ice-thickness-arctic.json`](scripts/geovideo/examples/sea-ice-thickness-arctic.json).
243
+
244
+ Generation requires FFmpeg. Upload credentials remain in `.env`; no private
245
+ key is bundled into the demo or written to a manifest. See
246
+ [`scripts/geovideo/README.md`](scripts/geovideo/README.md) for the configuration
247
+ and smoke-test workflow.
248
+
249
+ ## Dataset Scope
250
+
251
+ The built-in catalog focuses on Copernicus Marine ARCO products, including ocean currents and scalar ocean/ice variables. Other public cloud-native Zarr v2 stores can be used when their store URLs and variables are described in a compatible catalog entry and runtime compatibility is verified. Time, vertical, spatial, and variable metadata are loaded live from consolidated Zarr metadata and coordinate chunks.
252
+
253
+ Good fits include:
254
+
255
+ - Ocean currents, Stokes drift, and wave fields
256
+ - Sea-ice drift and ice-related scalar products
257
+ - Atmospheric wind or pressure-level products
258
+ - Climate model outputs with time and vertical dimensions
259
+ - Hydrodynamic model rasters or vectors
260
+
261
+ Vector layers need either U/V component variables or a catalog derivation from direction and magnitude. Scalar layers need one numeric variable and enough coordinate metadata to locate chunks by longitude, latitude, time, and optional vertical dimension.
262
+
263
+ Catalog validation checks the JSON contract, not remote service behavior. For built-in catalog additions, use the ARCO-oriented catalog-builder scripts first. WMTS is only an optional scalar shortcut; for polar products, prefer Zarr unless WMTS coverage is verified to reach the poles.
264
+
265
+ ## How It Works
266
+
267
+ ### 1. Zarr Is A Multidimensional Raster Cube
112
268
 
113
- ### Pipeline at a glance
269
+ Think of a Cloud Optimized GeoTIFF extended into longitude, latitude, time, and depth or pressure. The data is split into chunks. Zartigl requests only the chunks that intersect the current viewport at the selected time and vertical coordinate, similar to a COG tile request.
270
+
271
+ ### 2. Chunks Become GPU Textures
272
+
273
+ Scalar chunks are normalized into a raster texture and colorized by the selected palette. By default the color domain follows the loaded frame; scalar layers can instead set `colorDomain: [min, max]` to keep colors comparable across viewports and dates. Vector chunks are converted into a two-channel field texture, with validity carried as a mask.
274
+
275
+ For vector U/V data:
276
+
277
+ ```txt
278
+ R = eastward component
279
+ G = northward component
280
+ A = valid data mask
281
+ ```
282
+
283
+ ### 3. Particles Are Virtual Drifters
284
+
285
+ For vector particle rendering, thousands of virtual drifters are seeded across the viewport. Every frame, the GPU samples the vector texture, moves each particle, and respawns particles that leave the valid domain.
286
+
287
+ Particle state lives in GPU textures using a ping-pong framebuffer pattern, so positions do not need to round-trip through CPU arrays each frame. Positions are stored at full float precision where the GPU supports it (falling back to 16-bit packing), which keeps trails crisp when zoomed in. Motion is zoom-compensated so a single `speed` setting yields roughly constant *visual* speed from low to high zoom.
288
+
289
+ Trail history currently remains a screen-space texture. During camera movement it is faded aggressively rather than cleared every frame, which avoids flicker while limiting displaced trail artifacts on both Mercator and globe projections. A world-aligned or reprojected trail buffer is a possible experimental direction, but globe reprojection and polar continuity require additional work.
290
+
291
+ ### 4. Queries Use The Same Source Logic
292
+
293
+ Point inspection, time-series sampling, and vertical-profile sampling reuse the same Zarr metadata, coordinate lookup, chunk decoding, and cache machinery as the renderer. The query API is intentionally separate from the visual interaction layer, so applications can build their own analysis UI.
294
+
295
+ ## Build And Release Checks
296
+
297
+ ```bash
298
+ npm test
299
+ npm run build:prod
300
+ npm run build:lib
301
+ npm run catalog:validate
302
+ ```
114
303
 
115
- | Step | What happens | GIS analogy |
116
- |---|---|---|
117
- | Fetch | Request Zarr chunks for viewport + time + depth | COG tile request |
118
- | Pack | Encode U/V floats into R/G/A texture | Raster → GPU image |
119
- | Seed | Place N drifters randomly in viewport | Random point sampling |
120
- | Advect | Each frame: sample velocity, move each drifter | Drift model at 60 fps |
121
- | Mask | Discard drifters over land (A = 0) | Nodata mask |
122
- | Fade | Dim previous frame slightly | Long-exposure photography |
123
- | Color | Map speed to color ramp | Graduated color by value |
124
- | Composite | Blend result over basemap | Raster layer blending |
304
+ `npm run release:check` runs the main validation path before publishing.
125
305
 
126
306
  ## License
127
307
 
@@ -0,0 +1,103 @@
1
+ export declare interface Catalog {
2
+ schemaVersion: 1;
3
+ layers: CatalogLayer[];
4
+ }
5
+
6
+ export declare const catalog: Catalog;
7
+
8
+ export declare interface CatalogGeoVideo {
9
+ id: string;
10
+ label?: string;
11
+ manifestUrl: string;
12
+ }
13
+
14
+ export declare type CatalogLayer = (CatalogLayerBase & {
15
+ kind: "scalar";
16
+ variables: Extract<CatalogVariables, {
17
+ kind: "scalar";
18
+ }>;
19
+ }) | (CatalogLayerBase & {
20
+ kind: "vector";
21
+ variables: Extract<CatalogVariables, {
22
+ kind: "vector";
23
+ }>;
24
+ });
25
+
26
+ declare interface CatalogLayerBase {
27
+ id: string;
28
+ label: string;
29
+ description?: string;
30
+ category?: string;
31
+ tags?: string[];
32
+ dataset: {
33
+ id: string;
34
+ provider?: "copernicus" | string;
35
+ productId?: string;
36
+ };
37
+ stores: {
38
+ field: {
39
+ url: string;
40
+ };
41
+ pointSeries?: {
42
+ url: string;
43
+ };
44
+ wmts?: CatalogWmts;
45
+ };
46
+ derived?: {
47
+ geoVideos?: CatalogGeoVideo[];
48
+ };
49
+ defaults?: {
50
+ backend?: "zarr" | "geovideo" | "wmts";
51
+ palette?: string;
52
+ renderMode?: "particles" | "raster" | "raster+particles";
53
+ particles?: {
54
+ density?: number;
55
+ speed?: number;
56
+ fade?: number;
57
+ };
58
+ raster?: {
59
+ opacity?: number;
60
+ logScale?: boolean;
61
+ vibrance?: number;
62
+ /** Fixed physical-value domain for scalar color normalization. */
63
+ colorDomain?: [number, number];
64
+ };
65
+ };
66
+ }
67
+
68
+ export declare type CatalogVariables = {
69
+ kind: "scalar";
70
+ value: string;
71
+ } | {
72
+ kind: "vector";
73
+ u?: string;
74
+ v?: string;
75
+ derivation?: CatalogVectorDerivation;
76
+ };
77
+
78
+ export declare interface CatalogVectorDerivation {
79
+ kind: "direction_magnitude";
80
+ direction_variable: string;
81
+ magnitude_variable: string;
82
+ direction_convention: "from" | "toward";
83
+ output_direction: "from" | "toward";
84
+ }
85
+
86
+ export declare interface CatalogWmts {
87
+ capabilities_url: string;
88
+ base_url: string;
89
+ layer: string;
90
+ tileMatrixSet: string;
91
+ format: string;
92
+ style?: string;
93
+ }
94
+
95
+ export declare function formatTime(ms: number): string;
96
+
97
+ export declare function formatVertical(v: number, label: string, units?: string): string;
98
+
99
+ export declare function getCatalogLayer(id: string, data?: Catalog): CatalogLayer | undefined;
100
+
101
+ export declare function requireCatalogLayer(id: string, data?: Catalog): CatalogLayer;
102
+
103
+ export { }
@@ -0,0 +1,28 @@
1
+ const t = /* @__PURE__ */ JSON.parse('[{"id":"ocean-current-velocity","label":"Ocean Current Velocity","description":"Eastward and northward sea water velocity from global 6-hourly physics analysis and forecast.","category":"Ocean","kind":"vector","dataset":{"id":"cmems_mod_glo_phy-cur_anfc_0.083deg_PT6H-i","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-009/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-cur_anfc_0.083deg_PT6H-i_202406/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-009/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-cur_anfc_0.083deg_PT6H-i_202406/geoChunked.zarr"}},"variables":{"kind":"vector","u":"uo","v":"vo"},"defaults":{"palette":"rdylbu","renderMode":"particles","particles":{"density":0.033,"speed":1,"fade":0.7},"raster":{"opacity":1,"logScale":true,"vibrance":0.39}}},{"id":"surface-wind","label":"Surface Wind","description":"Global hourly sea surface wind from scatterometer and model","category":"Atmosphere","kind":"vector","dataset":{"id":"cmems_obs-wind_glo_phy_nrt_l4_0.125deg_PT1H","provider":"copernicus","productId":"WIND_GLO_PHY_L4_NRT_012_004"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-050/arco/WIND_GLO_PHY_L4_NRT_012_004/cmems_obs-wind_glo_phy_nrt_l4_0.125deg_PT1H_202207/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-050/arco/WIND_GLO_PHY_L4_NRT_012_004/cmems_obs-wind_glo_phy_nrt_l4_0.125deg_PT1H_202207/geoChunked.zarr"}},"variables":{"kind":"vector","u":"eastward_wind","v":"northward_wind"},"defaults":{"palette":"rdylbu","renderMode":"particles","particles":{"density":0.004000000000000002,"speed":1,"fade":0.7},"raster":{"opacity":0.9,"logScale":true,"vibrance":1}}},{"id":"phytoplankton-chlorophyll","label":"Phytoplankton (Chlorophyll-a)","description":"Daily chlorophyll-a concentration from global biogeochemistry analysis and forecast.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-pft_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-pft_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-pft_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-pft_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-pft_anfc_0.25deg_P1D-m_202311/chl","tileMatrixSet":"EPSG:3857","format":"image/png","style":"cmap:algae,logScale"}},"variables":{"kind":"scalar","value":"chl"},"defaults":{"palette":"algae","raster":{"opacity":0.9,"logScale":true,"vibrance":0.2}}},{"id":"wave-stokes-drift","label":"Wave Stokes Drift","description":"Net horizontal wave-driven transport (Stokes drift) from global 3-hourly wave analysis — visualises wave energy propagation direction.","category":"Ocean","kind":"vector","dataset":{"id":"cmems_mod_glo_wav_anfc_0.083deg_PT3H-i","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_WAV_001_027"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/geoChunked.zarr"}},"variables":{"kind":"vector","u":"VSDX","v":"VSDY"},"defaults":{"palette":"rdylbu","renderMode":"particles","particles":{"density":0.024,"speed":1,"fade":0.7},"raster":{"opacity":1,"logScale":true,"vibrance":1}}},{"id":"dissolved-inorganic-carbon","label":"Dissolved Inorganic Carbon","description":"Daily dissolved inorganic carbon concentration from global biogeochemistry analysis and forecast.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311/dissic","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"dissic"},"defaults":{"palette":"balance","raster":{"opacity":0.9,"logScale":false,"vibrance":0.15}}},{"id":"ocean-ph","label":"Ocean pH","description":"Daily sea water pH reported on the total scale from global biogeochemistry analysis and forecast.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-car_anfc_0.25deg_P1D-m_202311/ph","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"ph"},"defaults":{"palette":"balance","raster":{"opacity":0.9,"logScale":false,"vibrance":0.1}}},{"id":"primary-production","label":"Primary Production","description":"Daily net primary production of biomass expressed as carbon per unit volume.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311/nppv","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"nppv"},"defaults":{"palette":"algae","raster":{"opacity":0.9,"logScale":true,"vibrance":0.25}}},{"id":"dissolved-oxygen","label":"Dissolved Oxygen","description":"Daily dissolved molecular oxygen concentration from global biogeochemistry analysis and forecast.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-bio_anfc_0.25deg_P1D-m_202311/o2","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"o2"},"defaults":{"palette":"balance","raster":{"opacity":0.9,"logScale":false,"vibrance":0.15}}},{"id":"nitrate","label":"Nitrate","description":"Daily nitrate concentration from global biogeochemistry analysis and forecast.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311/no3","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"no3"},"defaults":{"palette":"balance","raster":{"opacity":0.9,"logScale":true,"vibrance":0.2}}},{"id":"phosphate","label":"Phosphate","description":"Daily phosphate concentration from global biogeochemistry analysis and forecast.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-nut_anfc_0.25deg_P1D-m_202311/po4","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"po4"},"defaults":{"palette":"balance","raster":{"opacity":0.9,"logScale":true,"vibrance":0.2}}},{"id":"surface-co2","label":"Surface CO2","description":"Daily surface partial pressure of carbon dioxide in sea water.","category":"Biology","kind":"scalar","dataset":{"id":"cmems_mod_glo_bgc-co2_anfc_0.25deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_BGC_001_028"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-co2_anfc_0.25deg_P1D-m_202311/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-006/arco/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-co2_anfc_0.25deg_P1D-m_202311/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-co2_anfc_0.25deg_P1D-m_202311?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_BGC_001_028/cmems_mod_glo_bgc-co2_anfc_0.25deg_P1D-m_202311/spco2","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"spco2"},"defaults":{"palette":"balance","raster":{"opacity":0.9,"logScale":false,"vibrance":0.15}}},{"id":"sea-ice-thickness","label":"Sea Ice Thickness","description":"Daily sea ice thickness from global physics analysis and forecast.","category":"Ice","kind":"scalar","dataset":{"id":"cmems_mod_glo_phy_anfc_0.083deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg_P1D-m_202406/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg_P1D-m_202406/geoChunked.zarr"}},"derived":{"geoVideos":[{"id":"sea-ice-thickness-arctic-six-months-2026-scalar-luma","label":"Arctic six-month thickness animation (February–August 2026)","manifestUrl":"https://sos-ch-gva-2.exo.io/zartigl/geovideo/sea-ice-thickness/79d55879af89/manifest.json"}]},"variables":{"kind":"scalar","value":"sithick"},"defaults":{"palette":"ice","raster":{"opacity":0.9,"logScale":false,"vibrance":0.1}}},{"id":"sea-water-temperature","label":"Sea Water Temperature","description":"Daily sea water potential temperature from global physics analysis and forecast.","category":"Ocean","kind":"scalar","dataset":{"id":"cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-012/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m_202406/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-012/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m_202406/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m_202406?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m_202406/thetao","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"thetao"},"defaults":{"palette":"thermal","raster":{"opacity":0.9,"logScale":false,"vibrance":0.2}}},{"id":"sea-surface-temperature-anomaly","label":"Sea Surface Temperature Anomaly","description":"Daily source-provided sea surface temperature anomaly from global physics analysis and forecast, suitable for visualizing El Niño and La Niña patterns.","category":"Ocean","tags":["el-nino","la-nina","enso","anomaly"],"kind":"scalar","dataset":{"id":"cmems_mod_glo_phy_anfc_0.083deg-sst-anomaly_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-014/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg-sst-anomaly_P1D-m_202411/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-014/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg-sst-anomaly_P1D-m_202411/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg-sst-anomaly_P1D-m_202411?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg-sst-anomaly_P1D-m_202411/sea_surface_temperature_anomaly","tileMatrixSet":"EPSG:3857","format":"image/png"}},"derived":{"geoVideos":[{"id":"sst-anomaly-six-months-2026-scalar-luma","label":"Six-month scalar-luma animation (February–August 2026)","manifestUrl":"https://sos-ch-gva-2.exo.io/zartigl/geovideo/sea-surface-temperature-anomaly/5b560a8404a9/manifest.json"}]},"variables":{"kind":"scalar","value":"sea_surface_temperature_anomaly"},"defaults":{"backend":"zarr","palette":"balance","raster":{"opacity":0.9,"logScale":false,"vibrance":0.1,"colorDomain":[-3,3]}}},{"id":"sea-water-salinity","label":"Sea Water Salinity","description":"Daily sea water salinity from global physics analysis and forecast.","category":"Ocean","kind":"scalar","dataset":{"id":"cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-010/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m_202406/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-010/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m_202406/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m_202406?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m_202406/so","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"so"},"defaults":{"palette":"haline","raster":{"opacity":0.9,"logScale":false,"vibrance":0.15}}},{"id":"upward-sea-water-velocity","label":"Upward Sea Water Velocity","description":"Daily upward sea water velocity from global physics analysis and forecast.","category":"Ocean","kind":"scalar","dataset":{"id":"cmems_mod_glo_phy-wcur_anfc_0.083deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-wcur_anfc_0.083deg_P1D-m_202406/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-wcur_anfc_0.083deg_P1D-m_202406/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-wcur_anfc_0.083deg_P1D-m_202406?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy-wcur_anfc_0.083deg_P1D-m_202406/wo","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"wo"},"defaults":{"palette":"rdylbu","raster":{"opacity":0.9,"logScale":false,"vibrance":0.15}}},{"id":"mixed-layer-depth","label":"Mixed Layer Depth","description":"Daily ocean mixed layer thickness defined by sigma theta.","category":"Ocean","kind":"scalar","dataset":{"id":"cmems_mod_glo_phy_anfc_0.083deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg_P1D-m_202406/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg_P1D-m_202406/geoChunked.zarr"}},"variables":{"kind":"scalar","value":"mlotst"},"defaults":{"palette":"deep","raster":{"opacity":0.9,"logScale":false,"vibrance":0.15}}},{"id":"primary-swell-direction","label":"Primary Swell Direction","description":"Primary swell wave direction reported as the direction waves come from.","category":"Waves","kind":"scalar","dataset":{"id":"cmems_mod_glo_wav_anfc_0.083deg_PT3H-i","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_WAV_001_027"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/VMDR_SW1","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"VMDR_SW1"},"defaults":{"palette":"curl","raster":{"opacity":0.9,"logScale":false,"vibrance":0.2}}},{"id":"primary-swell-period","label":"Primary Swell Period","description":"Mean period of the primary swell wave from spectral moments.","category":"Waves","kind":"scalar","dataset":{"id":"cmems_mod_glo_wav_anfc_0.083deg_PT3H-i","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_WAV_001_027"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/VTM01_SW1","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"VTM01_SW1"},"defaults":{"palette":"rdylbu","raster":{"opacity":0.9,"logScale":false,"vibrance":0.2}}},{"id":"primary-swell-height","label":"Primary Swell Height","description":"Significant height of the primary swell wave.","category":"Waves","kind":"scalar","dataset":{"id":"cmems_mod_glo_wav_anfc_0.083deg_PT3H-i","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_WAV_001_027"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/geoChunked.zarr"},"wmts":{"capabilities_url":"https://wmts.marine.copernicus.eu/teroWmts/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411?service=WMTS&request=GetCapabilities","base_url":"https://wmts.marine.copernicus.eu/teroWmts","layer":"GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/VHM0_SW1","tileMatrixSet":"EPSG:3857","format":"image/png"}},"variables":{"kind":"scalar","value":"VHM0_SW1"},"defaults":{"palette":"deep","raster":{"opacity":0.9,"logScale":false,"vibrance":0.2}}},{"id":"primary-swell-propagation","label":"Primary Swell Propagation","description":"Derived primary swell propagation using significant height as magnitude and from-direction converted to travel direction.","category":"Waves","kind":"vector","dataset":{"id":"cmems_mod_glo_wav_anfc_0.083deg_PT3H-i","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_WAV_001_027"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-015/arco/GLOBAL_ANALYSISFORECAST_WAV_001_027/cmems_mod_glo_wav_anfc_0.083deg_PT3H-i_202411/geoChunked.zarr"}},"variables":{"kind":"vector","derivation":{"kind":"direction_magnitude","direction_variable":"VMDR_SW1","magnitude_variable":"VHM0_SW1","direction_convention":"from","output_direction":"toward"}},"defaults":{"palette":"rdylbu","renderMode":"particles","particles":{"density":0.02,"speed":1,"fade":0.7},"raster":{"opacity":1,"logScale":false,"vibrance":0.45}}},{"id":"sea-ice-drift","label":"Sea Ice Drift","description":"Daily eastward and northward velocity of sea ice, tracing polar ice floe motion.","category":"Ice","kind":"vector","dataset":{"id":"cmems_mod_glo_phy_anfc_0.083deg_P1D-m","provider":"copernicus","productId":"GLOBAL_ANALYSISFORECAST_PHY_001_024"},"stores":{"field":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-time-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg_P1D-m_202406/timeChunked.zarr"},"pointSeries":{"url":"https://s3.waw3-1.cloudferro.com/mdl-arco-geo-013/arco/GLOBAL_ANALYSISFORECAST_PHY_001_024/cmems_mod_glo_phy_anfc_0.083deg_P1D-m_202406/geoChunked.zarr"}},"variables":{"kind":"vector","u":"usi","v":"vsi"},"defaults":{"backend":"zarr","palette":"ice","renderMode":"particles","particles":{"density":0.04,"speed":1,"fade":0.7},"raster":{"opacity":1,"logScale":false,"vibrance":0}}}]'), c = {
2
+ schemaVersion: 1,
3
+ layers: t
4
+ }, o = c;
5
+ function s(e, a = o) {
6
+ return a.layers.find((r) => r.id === e);
7
+ }
8
+ function i(e, a = o) {
9
+ const r = s(e, a);
10
+ if (!r)
11
+ throw new Error(`Unknown zartigl catalog layer: ${e}`);
12
+ return r;
13
+ }
14
+ function l(e) {
15
+ const a = new Date(e), r = (_) => String(_).padStart(2, "0");
16
+ return `${a.getUTCFullYear()}-${r(a.getUTCMonth() + 1)}-${r(a.getUTCDate())} ${r(a.getUTCHours())}:${r(a.getUTCMinutes())} UTC`;
17
+ }
18
+ function m(e, a, r) {
19
+ const _ = r?.trim();
20
+ return a === "pressure" ? `${Math.round(e)} ${_ || "hPa"}` : _ && _ !== "m" ? `${Number(e.toPrecision(5))} ${_}` : e < 10 ? `${e.toFixed(2)} m` : e < 100 ? `${e.toFixed(1)} m` : `${Math.round(e)} m`;
21
+ }
22
+ export {
23
+ o as catalog,
24
+ l as formatTime,
25
+ m as formatVertical,
26
+ s as getCatalogLayer,
27
+ i as requireCatalogLayer
28
+ };