@fxi/zartigl 0.3.0 → 0.4.0

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
@@ -14,9 +14,9 @@ Zartigl renders scalar rasters and vector particle fields from multidimensional
14
14
  - Vector rendering with GPU particle advection and optional raster magnitude display
15
15
  - Time and vertical-dimension controls for depth, elevation, level, or pressure-like axes
16
16
  - Point queries for time series and vertical profiles when a point-series store is available
17
- - Catalog-backed datasets with automatic Zarr/WMTS backend selection for scalar layers
17
+ - UUID-addressed catalog entries with independently selectable Zarr, WMTS, and GeoVideo sources
18
18
  - MapLibre custom layer integration, including Mercator and globe rendering paths
19
- - Pre-rendered GeoVideo backends with true polar globe draping and portable masks
19
+ - Pre-rendered GeoVideo sources with true polar globe draping and portable masks
20
20
  - No server component required for public CORS-enabled stores
21
21
 
22
22
  ## Quick Start
@@ -28,7 +28,7 @@ npm install @fxi/zartigl maplibre-gl
28
28
  ```ts
29
29
  import maplibregl from "maplibre-gl";
30
30
  import { Zartigl } from "@fxi/zartigl";
31
- import { catalog } from "@fxi/zartigl/catalog";
31
+ import { catalog, searchCatalog } from "@fxi/zartigl/catalog";
32
32
 
33
33
  const map = new maplibregl.Map({
34
34
  container: "map",
@@ -41,13 +41,14 @@ const z = new Zartigl({
41
41
  id: "zarr-layer",
42
42
  map,
43
43
  catalog,
44
- backend: "auto",
44
+ source: "auto",
45
45
  // Optional: expose only the month ending at the latest available timestamp.
46
46
  timeRange: { trailing: "P1M" },
47
47
  geoVideo: { autoplay: false, loop: true, playbackRate: 1 },
48
48
  });
49
49
 
50
- await z.setLayer("ocean-current-velocity");
50
+ const currents = searchCatalog("ocean-current-velocity")[0];
51
+ await z.setLayer(currents.id);
51
52
  z.setTimeAndDepth(new Date("2025-01-01T00:00:00Z"), 0);
52
53
  z.updateSettings({
53
54
  palette: "rdylbu",
@@ -55,6 +56,10 @@ z.updateSettings({
55
56
  });
56
57
  ```
57
58
 
59
+ Pass a source UUID (or source type) as the optional second argument when entry
60
+ and source should switch atomically. `setSource()` changes the source of the
61
+ active entry.
62
+
58
63
  `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.
59
64
 
60
65
  The root import does not bundle the catalog presets. Import catalog data from `@fxi/zartigl/catalog` only when you want the built-in catalog.
@@ -87,13 +92,7 @@ npm run dev:story
87
92
  npm run build:story
88
93
  ```
89
94
 
90
- For the smaller public API demo:
91
-
92
- ```bash
93
- npm run dev:minimal
94
- ```
95
-
96
- 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.
95
+ The demos read the built-in catalog, which points to public browser-accessible Zarr, WMTS, and GeoVideo sources. No local data download is needed; data is fetched directly from the selected source.
97
96
 
98
97
  ## Public API
99
98
 
@@ -139,17 +138,17 @@ Missing remote chunks are interpreted as Zarr fill data. A wholly unavailable
139
138
  frame is not cached, so selecting it again can recover after an upstream store
140
139
  finishes publishing it; a renderable sparse frame is cached normally.
141
140
 
142
- `VectorLayer`, `ScalarLayer`, `ArcoLayer`, and `ZarrSource` remain exported for advanced use cases that need direct control over renderer internals. Ordinary MapLibre integrations should prefer `Zartigl`.
141
+ `VectorLayer`, `ScalarLayer`, `GeoVideoLayer`, and `ZarrSource` remain exported for advanced use cases that need direct control over renderer internals. Ordinary MapLibre integrations should prefer `Zartigl`.
143
142
 
144
143
  ## Alternatives And Project Fit
145
144
 
146
- 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.
145
+ Zartigl overlaps with several useful projects. The goal is not to replace them, but to cover a specific cloud-native geoscience workflow: public Zarr stores and compatible WMTS/GeoVideo sources, MapLibre integration, animated vector particles, scalar rasters, time/depth controls, point inspection, and reproducible widget configuration without a dedicated tile server.
147
146
 
148
147
  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.
149
148
 
150
149
  | Project | Best fit | Zarr raster | Vector particles | Time/depth UI | Query API | Polar/globe rendering | Notes |
151
150
  | --- | --- | --- | --- | --- | --- | --- | --- |
152
- | 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 |
151
+ | zartigl | MapX-ready cloud geoscience 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 | Provider-neutral catalog, selectable sources, MapX snippet generation |
153
152
  | [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 |
154
153
  | [@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 |
155
154
  | [@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 |
@@ -160,7 +159,7 @@ The closest general alternative is `@carbonplan/zarr-layer`. It has a broader do
160
159
 
161
160
  Zartigl remains useful because it combines pieces that are not currently covered together by those projects:
162
161
 
163
- - ARCO-oriented catalog entries and defaults for Copernicus Marine products.
162
+ - Provider-neutral catalog entries with native dataset/variable provenance and source-specific defaults.
164
163
  - U/V vector products rendered as animated particles, with optional raster magnitude in the same layer.
165
164
  - Time and vertical-axis metadata exposed as application controls.
166
165
  - Depth/elevation labeling, ordering, and point inspection tuned for ocean products.
@@ -168,7 +167,7 @@ Zartigl remains useful because it combines pieces that are not currently covered
168
167
  - GPU particle-state fallbacks for browser and hardware differences, including float, half-float, and `RGBA8` compatibility mode.
169
168
  - A facade API that can generate MapX widget snippets and keep widget state reproducible.
170
169
 
171
- 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.
170
+ deck.gl, Three.js, or `@carbonplan/zarr-layer` could still be useful integration targets. They would not remove the catalog identity/provenance model, time/depth metadata, vector particle simulation, point-query workflow, or MapX widget-state work that zartigl currently owns.
172
171
 
173
172
  ## Catalog Metadata
174
173
 
@@ -183,20 +182,25 @@ To add or update catalog entries:
183
182
  ```bash
184
183
  uv run scripts/catalog_builder/skills/list_layers.py
185
184
  uv run scripts/catalog_builder/skills/search_products.py wave
186
- uv run scripts/catalog_builder/skills/query_dataset.py <dataset_id>
185
+ uv run scripts/catalog_builder/skills/query_dataset.py <dataset_id> --variable <scalar_id>
187
186
  uv run scripts/catalog_builder/skills/validate_catalog.py
187
+ uv run scripts/catalog_builder/skills/validate_remote.py --entry <uuid-or-alias>
188
188
  ```
189
189
 
190
+ Copernicus scalar defaults are derived from the provider's WMTS JSON legend.
191
+ Use the maintained `analyze_variable.py` fallback only when upstream
192
+ visualization statistics are unavailable.
193
+
190
194
  This requires Python >= 3.12, [uv](https://docs.astral.sh/uv/), and a free [Copernicus Marine](https://data.marine.copernicus.eu/register) account.
191
195
 
192
196
  ## GeoVideo
193
197
 
194
198
  GeoVideo pre-renders an expensive scalar Zarr timeline into a short, streamable
195
199
  H.264 MP4 while retaining spatial bounds, scientific dates, provenance, units,
196
- palette, and color domain in a sidecar manifest. Version 2 transports quantized
200
+ palette, and color domain in a sidecar manifest. Version 3 transports quantized
197
201
  scalar values in luminance and a static lossless mask as a separate PNG, so the
198
202
  browser can apply palette and scalar styling in WebGL. It is a visualization
199
- backend only: point queries always use the authoritative Zarr point-series store.
203
+ render source only: point queries use a separately configured Zarr point-series source when the entry provides one.
200
204
 
201
205
  Unlike MapLibre's native four-corner video source, `GeoVideoLayer` maps an
202
206
  equirectangular scalar-luma texture through zartigl's subdivided lon/lat mesh.
@@ -206,13 +210,13 @@ static validity mask without doubling the video width.
206
210
  ### Displaying a GeoVideo
207
211
 
208
212
  The built-in catalog includes an Arctic sea-ice thickness animation. Select the
209
- GeoVideo backend explicitly and enable autoplay when creating the `Zartigl`
213
+ GeoVideo source explicitly and enable autoplay when creating the `Zartigl`
210
214
  instance:
211
215
 
212
216
  ```ts
213
217
  import maplibregl from "maplibre-gl";
214
218
  import { Zartigl } from "@fxi/zartigl";
215
- import { catalog } from "@fxi/zartigl/catalog";
219
+ import { catalog, searchCatalog } from "@fxi/zartigl/catalog";
216
220
 
217
221
  const map = new maplibregl.Map({
218
222
  container: "map",
@@ -226,7 +230,7 @@ map.setProjection({ type: "globe" });
226
230
  const z = new Zartigl({
227
231
  map,
228
232
  catalog,
229
- backend: "geovideo",
233
+ source: "auto",
230
234
  geoVideo: {
231
235
  autoplay: true,
232
236
  loop: true,
@@ -234,17 +238,17 @@ const z = new Zartigl({
234
238
  },
235
239
  });
236
240
 
237
- await z.setLayer("sea-ice-thickness");
241
+ const ice = searchCatalog("sea-ice-thickness")[0];
242
+ await z.setLayer(ice.id);
243
+ await z.setSource(ice.sources.find((source) => source.type === "geovideo")!.id);
238
244
  ```
239
245
 
240
- `backend: "geovideo"` selects the published GeoVideo manifest from the
241
- catalog's `sea-ice-thickness` entry. Globe projection demonstrates its polar
246
+ `setSource()` selects the entry's published GeoVideo manifest by its stable
247
+ source UUID. Globe projection demonstrates its polar
242
248
  coverage. `loop` and `playbackRate` are optional playback settings. Browsers
243
249
  may restrict autoplay, so embedding applications should provide a user-gesture
244
250
  fallback when necessary.
245
251
 
246
- Try the configured instance directly in the [Arctic sea-ice GeoVideo example](https://fxi.io/zartigl/#eyJkIjoxMSwidCI6MTc4MTAwNDMyNTY3OC40OTY4LCJ2IjowLCJwIjoiaWNlIiwicyI6Imdlb3ZpZGVvIiwicHIiOiJnbG9iZSIsImMiOlstMTMyLjQ1NDgyNiw4NS4wNTExMjldLCJ6IjowLjUxMSwiYiI6MCwicGkiOjAsInBkIjowLjA1LCJzcCI6MSwiZiI6MC43LCJybSI6InBhcnRpY2xlcyIsIm9wIjowLjksImxzIjp0cnVlLCJ2YiI6LTEsImNkIjpudWxsLCJnYSI6dHJ1ZSwiZ2wiOnRydWUsImdyIjoxMH0=).
247
-
248
252
  ```bash
249
253
  uv run scripts/geovideo/render.py scripts/geovideo/examples/sst-anomaly.json --dry-run
250
254
  uv run scripts/geovideo/render.py scripts/geovideo/examples/sst-anomaly.json
@@ -255,6 +259,11 @@ The Arctic sea-ice configuration covers June 2022 through August 2026 and is
255
259
  available at
256
260
  [`scripts/geovideo/examples/sea-ice-thickness-arctic.json`](scripts/geovideo/examples/sea-ice-thickness-arctic.json).
257
261
 
262
+ For exact scientific timelines, GeoVideo also supports discrete sample
263
+ sequences. The Baltic bottom-oxygen example shows every real monthly mean from
264
+ September 1993 through September 2025 without interpolating values; it is available
265
+ at [`scripts/geovideo/examples/baltic-bottom-oxygen-monthly.json`](scripts/geovideo/examples/baltic-bottom-oxygen-monthly.json).
266
+
258
267
  Generation requires FFmpeg. Upload credentials remain in `.env`; no private
259
268
  key is bundled into the demo or written to a manifest. See
260
269
  [`scripts/geovideo/README.md`](scripts/geovideo/README.md) for the configuration
@@ -262,7 +271,7 @@ and smoke-test workflow.
262
271
 
263
272
  ## Dataset Scope
264
273
 
265
- 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.
274
+ The built-in catalog currently focuses on Copernicus Marine products, but catalog v2 treats Zarr, WMTS, and GeoVideo as independent provider-neutral sources. Public cloud-native Zarr v2 stores can be used when their URLs and variables match the runtime contract; standalone WMTS sources load dimensions from GetCapabilities, and standalone GeoVideo sources load their manifest metadata. Live source metadata remains authoritative.
266
275
 
267
276
  Good fits include:
268
277
 
@@ -274,7 +283,7 @@ Good fits include:
274
283
 
275
284
  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.
276
285
 
277
- 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.
286
+ Catalog validation checks the JSON contract, not remote service behavior. For built-in catalog additions, use the ARCO-oriented discovery scripts first. WMTS sources must expose a compatible Web Mercator tile matrix; for polar products, prefer Zarr unless WMTS coverage is verified to reach the poles.
278
287
 
279
288
  ## How It Works
280
289
 
package/dist/catalog.d.ts CHANGED
@@ -1,53 +1,23 @@
1
1
  export declare interface Catalog {
2
- schemaVersion: 1;
3
- layers: CatalogLayer[];
2
+ schemaVersion: 2;
3
+ defaultLocale: string;
4
+ layers: CatalogEntry[];
4
5
  }
5
6
 
6
7
  export declare const catalog: Catalog;
7
8
 
8
- export declare interface CatalogGeoVideo {
9
+ export declare interface CatalogEntry {
9
10
  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;
11
+ aliases?: string[];
12
+ title: LocalizedText;
13
+ description?: LocalizedText;
14
+ category: string;
31
15
  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";
16
+ kind: "scalar" | "vector";
17
+ sources: CatalogSource[];
18
+ defaults: {
19
+ sourceId: string;
20
+ querySourceId?: string;
51
21
  palette?: string;
52
22
  renderMode?: "particles" | "raster" | "raster+particles";
53
23
  particles?: {
@@ -59,12 +29,53 @@ declare interface CatalogLayerBase {
59
29
  opacity?: number;
60
30
  logScale?: boolean;
61
31
  vibrance?: number;
62
- /** Fixed physical-value domain for scalar color normalization. */
63
32
  colorDomain?: [number, number];
64
33
  };
65
34
  };
66
35
  }
67
36
 
37
+ export declare interface CatalogEntrySelector extends CatalogSearchOptions {
38
+ identifiers?: Record<string, string>;
39
+ variableId?: string;
40
+ }
41
+
42
+ export declare interface CatalogGeoVideoSource extends CatalogSourceBase {
43
+ type: "geovideo";
44
+ manifestUrl: string;
45
+ }
46
+
47
+ export declare interface CatalogSearchOptions {
48
+ locale?: string;
49
+ category?: string;
50
+ sourceType?: CatalogSourceType;
51
+ provider?: string;
52
+ }
53
+
54
+ export declare type CatalogSource = CatalogZarrSource | CatalogWmtsSource | CatalogGeoVideoSource;
55
+
56
+ declare interface CatalogSourceBase {
57
+ id: string;
58
+ title: LocalizedText;
59
+ provenance?: CatalogSourceProvenance;
60
+ temporal?: CatalogTemporalSummary;
61
+ }
62
+
63
+ export declare type CatalogSourcePreference = "auto" | CatalogSourceType | string;
64
+
65
+ export declare interface CatalogSourceProvenance {
66
+ provider: string;
67
+ identifiers?: Record<string, string>;
68
+ }
69
+
70
+ export declare type CatalogSourceType = CatalogSource["type"];
71
+
72
+ export declare interface CatalogTemporalSummary {
73
+ cadence?: string;
74
+ mode: "historical" | "near-real-time" | "analysis-forecast" | "fixed";
75
+ start?: string;
76
+ end?: string;
77
+ }
78
+
68
79
  export declare type CatalogVariables = {
69
80
  kind: "scalar";
70
81
  value: string;
@@ -83,21 +94,42 @@ export declare interface CatalogVectorDerivation {
83
94
  output_direction: "from" | "toward";
84
95
  }
85
96
 
86
- export declare interface CatalogWmts {
87
- capabilities_url: string;
88
- base_url: string;
97
+ export declare interface CatalogWmtsSource extends CatalogSourceBase {
98
+ type: "wmts";
99
+ capabilitiesUrl: string;
100
+ baseUrl?: string;
89
101
  layer: string;
90
- tileMatrixSet: string;
91
- format: string;
102
+ tileMatrixSet?: string;
103
+ format?: string;
92
104
  style?: string;
105
+ tileUrlTemplate?: string;
93
106
  }
94
107
 
108
+ export declare interface CatalogZarrSource extends CatalogSourceBase {
109
+ type: "zarr";
110
+ endpoints: {
111
+ field: string;
112
+ pointSeries?: string;
113
+ };
114
+ variables: CatalogVariables;
115
+ }
116
+
117
+ export declare function findCatalogEntries(selector: CatalogEntrySelector, data?: Catalog): CatalogEntry[];
118
+
95
119
  export declare function formatTime(ms: number): string;
96
120
 
97
121
  export declare function formatVertical(v: number, label: string, units?: string): string;
98
122
 
99
- export declare function getCatalogLayer(id: string, data?: Catalog): CatalogLayer | undefined;
123
+ export declare function getCatalogEntry(id: string, data?: Catalog): CatalogEntry | undefined;
124
+
125
+ export declare type LocalizedText = Record<string, string>;
126
+
127
+ export declare function pickPreferredSource(entry: CatalogEntry): CatalogSource;
128
+
129
+ export declare function requireCatalogEntry(id: string, data?: Catalog): CatalogEntry;
130
+
131
+ export declare function resolveLocalizedText(value: LocalizedText | undefined, locale: string, defaultLocale?: string): string;
100
132
 
101
- export declare function requireCatalogLayer(id: string, data?: Catalog): CatalogLayer;
133
+ export declare function searchCatalog(query: string, options?: CatalogSearchOptions, data?: Catalog): CatalogEntry[];
102
134
 
103
135
  export { }