ol-stac 1.6.0 → 1.7.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/http.d.ts +2 -2
- package/http.d.ts.map +1 -1
- package/http.js.map +1 -1
- package/layer/STAC.d.ts +32 -17
- package/layer/STAC.d.ts.map +1 -1
- package/layer/STAC.js +35 -49
- package/layer/STAC.js.map +1 -1
- package/layer/type.d.ts.map +1 -1
- package/package.json +2 -2
- package/proj.d.ts +2 -2
- package/proj.d.ts.map +1 -1
- package/proj.js.map +1 -1
- package/shim/ogcTileUtil.d.ts +1 -1
- package/shim/ogcTileUtil.d.ts.map +1 -1
- package/shim/ogcTileUtil.js.map +1 -1
- package/shim/proj4.d.ts.map +1 -1
- package/shim/proj4.js +1 -1
- package/shim/proj4.js.map +1 -1
- package/source/GeoZarr.d.ts +67 -90
- package/source/GeoZarr.d.ts.map +1 -1
- package/source/GeoZarr.js +305 -365
- package/source/GeoZarr.js.map +1 -1
- package/source/type.d.ts.map +1 -1
- package/util.d.ts +25 -15
- package/util.d.ts.map +1 -1
- package/util.js +52 -8
- package/util.js.map +1 -1
package/source/GeoZarr.js
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module ol/source/GeoZarr
|
|
3
3
|
*/
|
|
4
|
-
import { FetchStore, get, open, slice, withRangeCoalescing } from 'zarrita';
|
|
4
|
+
import { FetchStore, NotFoundError, get, open, slice, withRangeCoalescing, } from 'zarrita';
|
|
5
5
|
import { warn } from 'ol/console.js';
|
|
6
6
|
import { getCenter } from 'ol/extent.js';
|
|
7
7
|
import { get as getProjection, toUserCoordinate, toUserExtent } from 'ol/proj.js';
|
|
8
8
|
import { fromProjectionDefinition } from '../shim/proj4.js';
|
|
9
9
|
import { toSize } from 'ol/size.js';
|
|
10
10
|
import WMTSTileGrid from 'ol/tilegrid/WMTS.js';
|
|
11
|
+
import { DEFAULT_TILE_SIZE } from 'ol/tilegrid/common.js';
|
|
11
12
|
import { getUid } from 'ol/util.js';
|
|
12
13
|
import DataTileSource from 'ol/source/DataTile.js';
|
|
13
14
|
import { parseTileMatrixSet } from '../shim/ogcTileUtil.js';
|
|
14
|
-
const REQUIRED_ZARR_CONVENTIONS = [
|
|
15
|
-
'd35379db-88df-4056-af3a-620245f8e347',
|
|
16
|
-
'f17cb550-5864-4468-aeb7-f3180cfb622f',
|
|
17
|
-
'689b58e2-cf7b-45e0-9fff-9cfc0883d6b4', // spatial:
|
|
18
|
-
];
|
|
19
15
|
/**
|
|
20
16
|
* @typedef {'nearest'|'linear'} ResampleMethod
|
|
21
17
|
*/
|
|
@@ -55,23 +51,21 @@ const REQUIRED_ZARR_CONVENTIONS = [
|
|
|
55
51
|
* To disable the opacity transition, pass `transition: 0`.
|
|
56
52
|
* @property {boolean} [wrapX=false] Render tiles beyond the tile grid extent.
|
|
57
53
|
* @property {ResampleMethod} [resample='linear'] Resampling method if bands are not available for all multi-scale levels.
|
|
58
|
-
* @property {Object<string, number|string
|
|
59
|
-
* dimension of the band arrays, keyed by dimension name (e.g. `{time: 0}` for
|
|
60
|
-
* of a `[time, y, x]` cube)
|
|
61
|
-
*
|
|
62
|
-
*
|
|
54
|
+
* @property {Object<string, number|string|Array<number|string>>} [dimensions] How to slice
|
|
55
|
+
* each non-spatial dimension of the band arrays, keyed by dimension name (e.g. `{time: 0}` for
|
|
56
|
+
* the first time step of a `[time, y, x]` cube). Values are 0-based indices (number) or
|
|
57
|
+
* coordinate labels (string); unlisted dimensions default to index 0. Names come from each
|
|
58
|
+
* array's `dimension_names`, or are the axis position as a string when it has none; use the
|
|
59
|
+
* names from {@link getDimensions}. Labels are resolved against the dimension's coordinate
|
|
60
|
+
* array; if that array cannot be read, pass indices instead. With `variable`, at most one
|
|
61
|
+
* dimension may map to an array of values, whose entries are rendered as separate bands in
|
|
62
|
+
* the given order. Change the selection on the fly with
|
|
63
63
|
* {@link module:ol/source/GeoZarr~GeoZarr#updateDimensions}.
|
|
64
64
|
* @property {string} [variable] The name of an n-dimensional data array (variable) to
|
|
65
65
|
* render, for stores where all bands are packed into a single array (e.g. a
|
|
66
66
|
* `(time, band, y, x)` datacube). The array must exist within each multiscale level
|
|
67
|
-
* group (or at the group root for single-scale stores). Mutually exclusive with `bands
|
|
68
|
-
*
|
|
69
|
-
* `variable` mode: how to slice each non-spatial dimension, keyed by dimension name
|
|
70
|
-
* (from the array's `dimension_names`). Values are 0-based indices (number), coordinate
|
|
71
|
-
* labels (string), or an array of these. At most one dimension may map to an array; its
|
|
72
|
-
* entries are rendered as separate bands (in the given order). Unlisted non-spatial
|
|
73
|
-
* dimensions default to index 0. Labels are resolved against the dimension's coordinate
|
|
74
|
-
* array; if that array cannot be read, pass indices instead.
|
|
67
|
+
* group (or at the group root for single-scale stores). Mutually exclusive with `bands`,
|
|
68
|
+
* and required to select several bands from one dimension through `dimensions`.
|
|
75
69
|
* @property {import("ol/extent.js").Extent} [extent] Fallback extent of the data, in
|
|
76
70
|
* coordinates of the source projection. Only used when the store neither declares its
|
|
77
71
|
* extent (`spatial:bbox` or `bounds` attributes) nor has coordinate arrays to infer it.
|
|
@@ -85,19 +79,25 @@ const REQUIRED_ZARR_CONVENTIONS = [
|
|
|
85
79
|
* - [Geospatial projection convention](https://github.com/zarr-conventions/geo-proj)
|
|
86
80
|
* - [Spatial convention](https://github.com/zarr-conventions/spatial)
|
|
87
81
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* The legacy `tile_matrix_set` attribute is also supported,
|
|
92
|
-
*
|
|
82
|
+
* The store is read as a stack of resolution levels, enumerated from the
|
|
83
|
+
* `multiscales` attribute in either its `{layout: [{asset, ...}]}` or its
|
|
84
|
+
* `[{datasets: [{path, ...}]}]` form; a store with neither has the group itself as
|
|
85
|
+
* its only level. The legacy `tile_matrix_set` attribute is also supported, and
|
|
86
|
+
* describes the levels itself.
|
|
87
|
+
*
|
|
88
|
+
* Extent, resolution, projection and y-axis orientation are read from the store
|
|
89
|
+
* metadata (`spatial:bbox`, `spatial:shape`, `spatial:transform`, `proj:code`,
|
|
90
|
+
* ...) where declared, and otherwise inferred from the coordinate arrays. The
|
|
91
|
+
* conventions above are what make that metadata available, but none of them has
|
|
92
|
+
* to be declared for a store that provides the attributes.
|
|
93
93
|
*
|
|
94
94
|
* Two data layouts are supported:
|
|
95
95
|
* - One array per band (`bands` option), addressed by name at `<matrixId>/<bandName>`
|
|
96
|
-
* (multi-scale) or at the group root (single-scale).
|
|
97
|
-
* - A single n-dimensional data array shared by all bands (`variable` + `
|
|
98
|
-
* options), e.g. a `(time, band, y, x)` datacube.
|
|
99
|
-
*
|
|
100
|
-
*
|
|
96
|
+
* (multi-scale) or at the group root (single-scale).
|
|
97
|
+
* - A single n-dimensional data array shared by all bands (`variable` + `dimensions`
|
|
98
|
+
* options), e.g. a `(time, band, y, x)` datacube.
|
|
99
|
+
*
|
|
100
|
+
* Both layouts support Zarr v2 and v3.
|
|
101
101
|
*/
|
|
102
102
|
export default class GeoZarr extends DataTileSource {
|
|
103
103
|
/**
|
|
@@ -123,8 +123,9 @@ export default class GeoZarr extends DataTileSource {
|
|
|
123
123
|
*/
|
|
124
124
|
this.storeOptions_ = options.storeOptions;
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
126
|
+
* Selection per non-spatial dimension name, from the `dimensions` option.
|
|
127
|
+
* Coordinate labels are replaced by their index once resolved.
|
|
128
|
+
* @type {Object<string, number|string|Array<number|string>>}
|
|
128
129
|
* @private
|
|
129
130
|
*/
|
|
130
131
|
this.dimensions_ = options.dimensions || {};
|
|
@@ -133,11 +134,6 @@ export default class GeoZarr extends DataTileSource {
|
|
|
133
134
|
* @private
|
|
134
135
|
*/
|
|
135
136
|
this.variable_ = options.variable;
|
|
136
|
-
/**
|
|
137
|
-
* @type {Object<string, number|string|Array<number|string>>}
|
|
138
|
-
* @private
|
|
139
|
-
*/
|
|
140
|
-
this.selector_ = options.selector || {};
|
|
141
137
|
/**
|
|
142
138
|
* @type {import("ol/extent.js").Extent|undefined}
|
|
143
139
|
* @private
|
|
@@ -149,12 +145,13 @@ export default class GeoZarr extends DataTileSource {
|
|
|
149
145
|
*/
|
|
150
146
|
this.fallbackFlipY_ = options.flipY;
|
|
151
147
|
/**
|
|
152
|
-
* The zarrita open function, pinned to
|
|
153
|
-
*
|
|
148
|
+
* The zarrita open function, pinned to the store's Zarr version by
|
|
149
|
+
* `configure_`. Never the unpinned `open`, which probes v2 metadata first
|
|
150
|
+
* and so requests keys that a v3 store does not have.
|
|
154
151
|
* @type {Function}
|
|
155
152
|
* @private
|
|
156
153
|
*/
|
|
157
|
-
this.openFn_ = open;
|
|
154
|
+
this.openFn_ = open.v3;
|
|
158
155
|
/**
|
|
159
156
|
* Group path prefix per tile matrix id, for stores whose levels are not
|
|
160
157
|
* addressed by the matrix id itself (empty string for the group root).
|
|
@@ -171,7 +168,7 @@ export default class GeoZarr extends DataTileSource {
|
|
|
171
168
|
*/
|
|
172
169
|
this.levelRowInfo_ = null;
|
|
173
170
|
/**
|
|
174
|
-
* The axis selected as multiple bands through the `
|
|
171
|
+
* The axis selected as multiple bands through the `dimensions` option,
|
|
175
172
|
* or -1.
|
|
176
173
|
* @type {number}
|
|
177
174
|
* @private
|
|
@@ -285,19 +282,24 @@ export default class GeoZarr extends DataTileSource {
|
|
|
285
282
|
* Number of bands.
|
|
286
283
|
* @type {number}
|
|
287
284
|
*/
|
|
288
|
-
this.bandCount = this.bands_.length;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
bandCount = value.length;
|
|
298
|
-
}
|
|
285
|
+
this.bandCount = this.variable_ ? 1 : this.bands_.length;
|
|
286
|
+
// A dimension listing several values renders one band per value. Only
|
|
287
|
+
// `variable` can do that, since `bands` names one array per band.
|
|
288
|
+
let multiName;
|
|
289
|
+
for (const name in this.dimensions_) {
|
|
290
|
+
const value = this.dimensions_[name];
|
|
291
|
+
if (!Array.isArray(value)) {
|
|
292
|
+
continue;
|
|
299
293
|
}
|
|
300
|
-
this.
|
|
294
|
+
if (!this.variable_) {
|
|
295
|
+
throw new Error(`GeoZarr: dimension "${name}" selects several bands, which requires ` +
|
|
296
|
+
'the `variable` option.');
|
|
297
|
+
}
|
|
298
|
+
if (multiName) {
|
|
299
|
+
throw new Error(`GeoZarr: dimensions "${multiName}" and "${name}" both select ` +
|
|
300
|
+
'several bands; at most one may.');
|
|
301
|
+
}
|
|
302
|
+
multiName = name;
|
|
301
303
|
}
|
|
302
304
|
/**
|
|
303
305
|
* @type {import("ol/tilegrid/WMTS.js").default}
|
|
@@ -310,29 +312,34 @@ export default class GeoZarr extends DataTileSource {
|
|
|
310
312
|
this.setState('ready');
|
|
311
313
|
})
|
|
312
314
|
.catch((err) => {
|
|
313
|
-
|
|
315
|
+
// A 403 reads as a missing key, so the two causes are indistinguishable.
|
|
316
|
+
this.error_ =
|
|
317
|
+
err instanceof NotFoundError
|
|
318
|
+
? new Error(`GeoZarr: could not read ${this.url_}; it is missing, or ` +
|
|
319
|
+
`access to it is denied.`, { cause: err })
|
|
320
|
+
: err;
|
|
314
321
|
this.setState('error');
|
|
315
322
|
});
|
|
316
323
|
}
|
|
317
324
|
async configure_() {
|
|
318
|
-
var _a;
|
|
325
|
+
var _a, _b, _c;
|
|
319
326
|
const overrides = /** @type {RequestInit} */ (this.storeOptions_) || {};
|
|
320
|
-
const store = /** @type {FetchStore} */ (withRangeCoalescing(new FetchStore(this.url_, { overrides })));
|
|
321
|
-
// Fetch group zarr.json once for
|
|
322
|
-
//
|
|
323
|
-
// read would each make a separate HTTP request for the same file.
|
|
327
|
+
const store = /** @type {FetchStore} */ (withRangeCoalescing(new FetchStore(this.url_, { overrides, fetch: storeFetch })));
|
|
328
|
+
// Fetch group zarr.json once for opening the group, extracting consolidated
|
|
329
|
+
// metadata, and pinning the detected Zarr version.
|
|
324
330
|
const groupBytes = await probe(store, '/zarr.json');
|
|
331
|
+
this.openFn_ = groupBytes ? open.v3 : open.v2;
|
|
325
332
|
if (groupBytes) {
|
|
326
333
|
try {
|
|
327
334
|
this.consolidatedMetadata_ = JSON.parse(new TextDecoder().decode(groupBytes)).consolidated_metadata.metadata;
|
|
328
335
|
}
|
|
329
|
-
catch (
|
|
336
|
+
catch (_d) {
|
|
330
337
|
// no consolidated metadata
|
|
331
338
|
}
|
|
332
339
|
}
|
|
333
340
|
/** @type {Object<string, *>|null} */
|
|
334
341
|
let v2Metadata = null;
|
|
335
|
-
if (!
|
|
342
|
+
if (!groupBytes) {
|
|
336
343
|
// Zarr v2: consolidated metadata lives in .zmetadata
|
|
337
344
|
const v2Bytes = await probe(store, '/.zmetadata');
|
|
338
345
|
if (v2Bytes) {
|
|
@@ -341,7 +348,7 @@ export default class GeoZarr extends DataTileSource {
|
|
|
341
348
|
this.consolidatedMetadata_ = normalizeV2Metadata(
|
|
342
349
|
/** @type {Object<string, *>} */ (v2Metadata));
|
|
343
350
|
}
|
|
344
|
-
catch (
|
|
351
|
+
catch (_e) {
|
|
345
352
|
// no consolidated metadata
|
|
346
353
|
}
|
|
347
354
|
}
|
|
@@ -357,14 +364,14 @@ export default class GeoZarr extends DataTileSource {
|
|
|
357
364
|
const groupPromises = [];
|
|
358
365
|
if (this.groupPaths_) {
|
|
359
366
|
// Multi-group mode: open root, then each sub-group
|
|
360
|
-
const rootGroup = await
|
|
367
|
+
const rootGroup = await this.openFn_(cachedStore, { kind: 'group' });
|
|
361
368
|
for (const groupPath of this.groupPaths_) {
|
|
362
|
-
groupPromises.push(
|
|
369
|
+
groupPromises.push(this.openFn_(rootGroup.resolve(groupPath), { kind: 'group' }));
|
|
363
370
|
}
|
|
364
371
|
}
|
|
365
372
|
else {
|
|
366
373
|
// Single group mode
|
|
367
|
-
groupPromises.push(this.
|
|
374
|
+
groupPromises.push(this.openFn_(cachedStore, { kind: 'group' }));
|
|
368
375
|
}
|
|
369
376
|
this.groups_.push(...(await Promise.all(groupPromises)));
|
|
370
377
|
const attributes =
|
|
@@ -377,85 +384,38 @@ export default class GeoZarr extends DataTileSource {
|
|
|
377
384
|
if (Array.isArray(spatialDimensions) && spatialDimensions.length === 2) {
|
|
378
385
|
this.spatialDimensionNames_ = spatialDimensions;
|
|
379
386
|
}
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
:
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
387
|
+
const multiscales = attributes.multiscales;
|
|
388
|
+
// The two multiscale layouts that enumerate level groups: the GeoZarr
|
|
389
|
+
// `[{datasets: [...]}]` form and the convention's `{layout: [...]}` form.
|
|
390
|
+
const hasMultiscaleLevels = Array.isArray(multiscales) ||
|
|
391
|
+
Array.isArray(/** @type {Multiscales} */ (_a = (multiscales)) === null || _a === void 0 ? void 0 : _a.layout);
|
|
392
|
+
// The legacy tile_matrix_set describes the levels itself, so a store that
|
|
393
|
+
// has nothing else to enumerate them by is configured from it alone.
|
|
394
|
+
const legacyOnly = !!multiscales && 'tile_matrix_set' in multiscales && !hasMultiscaleLevels;
|
|
388
395
|
let hasTileSizes = false;
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.tileGrid = tileGrid;
|
|
398
|
-
this.projection = projection;
|
|
399
|
-
this.fillValue_ = fillValue;
|
|
400
|
-
hasTileSizes = !!tileSizes;
|
|
401
|
-
}
|
|
402
|
-
if (!hasTileSizes &&
|
|
403
|
-
attributes.multiscales &&
|
|
404
|
-
'tile_matrix_set' in attributes.multiscales) {
|
|
396
|
+
if (this.variable_ ||
|
|
397
|
+
(!legacyOnly &&
|
|
398
|
+
(hasMultiscaleLevels ||
|
|
399
|
+
'spatial:bbox' in attributes ||
|
|
400
|
+
'bounds' in attributes))) {
|
|
401
|
+
hasTileSizes = await this.configureLevels_(attributes, store);
|
|
402
|
+
}
|
|
403
|
+
if (!hasTileSizes && multiscales && 'tile_matrix_set' in multiscales) {
|
|
405
404
|
// If available, use tile_matrix_set (legacy attributes) to get a tile grid, because it
|
|
406
405
|
// should provide a better mapping of tiles to zarr chunks.
|
|
407
406
|
const { tileGrid, projection } = getTileGridInfoFromLegacyAttributes(
|
|
408
407
|
/** @type {LegacyDatasetAttributes} */ (attributes));
|
|
409
408
|
this.tileGrid = tileGrid;
|
|
409
|
+
// The tile matrix set brings its own matrix ids, which the levels
|
|
410
|
+
// resolved above are not keyed by.
|
|
411
|
+
this.bandsByLevel_ = null;
|
|
412
|
+
this.levelPaths_ = null;
|
|
413
|
+
this.levelRowInfo_ = null;
|
|
410
414
|
if (!this.projection) {
|
|
411
|
-
//
|
|
415
|
+
// Only the tile matrix set declares one for a legacy-only store
|
|
412
416
|
this.projection = projection;
|
|
413
417
|
}
|
|
414
418
|
}
|
|
415
|
-
if (!this.tileGrid && 'spatial:bbox' in attributes) {
|
|
416
|
-
// Standalone single-scale group: build tile grid directly from
|
|
417
|
-
// spatial:bbox and spatial:shape (`[height, width]`), or the x axis size
|
|
418
|
-
// from the array metadata.
|
|
419
|
-
let xSize = (_a = attributes['spatial:shape']) === null || _a === void 0 ? void 0 : _a[1];
|
|
420
|
-
if (xSize === undefined && consolidatedMetadata) {
|
|
421
|
-
for (const band of this.bands_) {
|
|
422
|
-
const bandMeta = consolidatedMetadata[band];
|
|
423
|
-
if (bandMeta === null || bandMeta === void 0 ? void 0 : bandMeta.shape) {
|
|
424
|
-
xSize = bandMeta.shape[this.axesOf_(bandMeta).col];
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (xSize !== undefined) {
|
|
430
|
-
const extent = attributes['spatial:bbox'];
|
|
431
|
-
const resolution = (extent[2] - extent[0]) / xSize;
|
|
432
|
-
if (!this.projection) {
|
|
433
|
-
this.projection = getProjectionFromAttributes(attributes);
|
|
434
|
-
}
|
|
435
|
-
if (consolidatedMetadata) {
|
|
436
|
-
this.bandsByLevel_ = { level0: [] };
|
|
437
|
-
for (const band of this.bands_) {
|
|
438
|
-
if (consolidatedMetadata[band]) {
|
|
439
|
-
this.bandsByLevel_['level0'].push(band);
|
|
440
|
-
if (this.fillValue_ === undefined) {
|
|
441
|
-
this.fillValue_ = Number(consolidatedMetadata[band]['fill_value']);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
this.tileGrid = new WMTSTileGrid({
|
|
447
|
-
extent: extent,
|
|
448
|
-
origins: [[extent[0], extent[3]]],
|
|
449
|
-
resolutions: [resolution],
|
|
450
|
-
matrixIds: ['level0'],
|
|
451
|
-
});
|
|
452
|
-
for (let i = 0; i < this.bands_.length; ++i) {
|
|
453
|
-
if (this.bandGroupIndex_[i] === 0) {
|
|
454
|
-
this.bandSingleScaleResolution_[i] = resolution;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
419
|
// For multi-group: determine which group owns each band and supplement
|
|
460
420
|
// bandsByLevel with bands from additional groups.
|
|
461
421
|
if (this.groupPaths_ && this.consolidatedMetadata_ && this.bandsByLevel_) {
|
|
@@ -469,9 +429,19 @@ export default class GeoZarr extends DataTileSource {
|
|
|
469
429
|
if (!this.tileGrid) {
|
|
470
430
|
throw new Error('Could not determine tile grid');
|
|
471
431
|
}
|
|
432
|
+
// Replace coordinate labels by their index once, so that the per-band
|
|
433
|
+
// resolution below and `updateDimensions` can stay synchronous.
|
|
434
|
+
// (In `variable` mode, configureLevels_ resolves them per band instead.)
|
|
435
|
+
if (!this.variable_) {
|
|
436
|
+
for (const [name, value] of Object.entries(this.dimensions_)) {
|
|
437
|
+
if (typeof value === 'string') {
|
|
438
|
+
this.dimensions_[name] = await this.resolveCoordinateLabel_(name, value, (_c = (_b = this.coordinateArray_(name)) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : name);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
472
442
|
// Resolve, per band, the spatial axes and the fixed indices for any
|
|
473
443
|
// non-spatial dimensions, and record the selectable dimensions.
|
|
474
|
-
// (In `variable` mode,
|
|
444
|
+
// (In `variable` mode, configureLevels_ resolves these instead.)
|
|
475
445
|
for (let i = 0, ii = this.variable_ ? 0 : this.bands_.length; i < ii; ++i) {
|
|
476
446
|
const arrayMeta = this.getBandArrayMeta_(this.bands_[i], this.bandGroupIndex_[i]);
|
|
477
447
|
const { row, col } = this.axesOf_(arrayMeta);
|
|
@@ -811,8 +781,9 @@ export default class GeoZarr extends DataTileSource {
|
|
|
811
781
|
* another `time` slice) without rebuilding the source. Values are merged into
|
|
812
782
|
* the current selection, so a partial update like `{time: 3}` leaves the other
|
|
813
783
|
* dimensions untouched. Takes effect immediately when the source is `ready`,
|
|
814
|
-
* otherwise once it becomes ready.
|
|
815
|
-
*
|
|
784
|
+
* otherwise once it becomes ready. Only integer indices are accepted here;
|
|
785
|
+
* coordinate labels are resolved once, when the source configures.
|
|
786
|
+
* @param {Object<string, number>} dimensions Index per dimension name
|
|
816
787
|
* to change; see the `dimensions` constructor option.
|
|
817
788
|
*/
|
|
818
789
|
updateDimensions(dimensions) {
|
|
@@ -830,7 +801,7 @@ export default class GeoZarr extends DataTileSource {
|
|
|
830
801
|
return this.resolveExtraSelection_(arrayMeta, merged);
|
|
831
802
|
}
|
|
832
803
|
// In `variable` mode, merge into the current selection so the axes
|
|
833
|
-
// fixed through the `
|
|
804
|
+
// fixed through the `dimensions` option are kept.
|
|
834
805
|
const dims = this.extraDimsOf_(arrayMeta);
|
|
835
806
|
const current = this.bandExtraSelection_[i];
|
|
836
807
|
const updated = current ? current.slice() : undefined;
|
|
@@ -842,7 +813,7 @@ export default class GeoZarr extends DataTileSource {
|
|
|
842
813
|
}
|
|
843
814
|
if (dim.axis === this.multiAxis_) {
|
|
844
815
|
throw new Error(`GeoZarr: dimension "${name}" selects the rendered bands; ` +
|
|
845
|
-
`set it
|
|
816
|
+
`set it when constructing the source instead.`);
|
|
846
817
|
}
|
|
847
818
|
if (typeof index !== 'number' ||
|
|
848
819
|
!Number.isInteger(index) ||
|
|
@@ -912,8 +883,8 @@ export default class GeoZarr extends DataTileSource {
|
|
|
912
883
|
* `null` at the two spatial axes (e.g. `[2, null, null]` for a `[time, y, x]`
|
|
913
884
|
* array with `{time: 2}`).
|
|
914
885
|
* @param {Object<string, *>|undefined} arrayMeta Zarr v3 array metadata.
|
|
915
|
-
* @param {Object<string, number|string
|
|
916
|
-
* to resolve against.
|
|
886
|
+
* @param {Object<string, number|string|Array<number|string>>} dimensions The
|
|
887
|
+
* dimension indices to resolve against.
|
|
917
888
|
* @return {Array<number|null>|undefined} The extra-axis selection template.
|
|
918
889
|
* @private
|
|
919
890
|
*/
|
|
@@ -951,48 +922,43 @@ export default class GeoZarr extends DataTileSource {
|
|
|
951
922
|
index = 0; // unspecified extra dimension defaults to the first slice
|
|
952
923
|
}
|
|
953
924
|
if (typeof index === 'string') {
|
|
954
|
-
//
|
|
955
|
-
throw new Error(`GeoZarr:
|
|
956
|
-
`
|
|
925
|
+
// Labels are resolved once, when the source configures.
|
|
926
|
+
throw new Error(`GeoZarr: coordinate labels cannot be passed to updateDimensions; ` +
|
|
927
|
+
`pass an integer index for dimension "${name}".`);
|
|
957
928
|
}
|
|
958
|
-
if (!Number.isInteger(index) ||
|
|
929
|
+
if (!Number.isInteger(index) ||
|
|
930
|
+
/** @type {number} */ (index) < 0 ||
|
|
931
|
+
/** @type {number} */ (index) >= dim.size) {
|
|
959
932
|
throw new Error(`GeoZarr: invalid index ${index} for dimension "${name}" ` +
|
|
960
933
|
`(size ${dim.size}).`);
|
|
961
934
|
}
|
|
962
|
-
selection[dim.axis] = index;
|
|
935
|
+
selection[dim.axis] = /** @type {number} */ (index);
|
|
963
936
|
}
|
|
964
937
|
return selection;
|
|
965
938
|
}
|
|
966
939
|
/**
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
970
|
-
* @return {Promise<import('zarrita').Group<any>>} The opened group.
|
|
971
|
-
* @private
|
|
972
|
-
*/
|
|
973
|
-
async openGroup_(source) {
|
|
974
|
-
try {
|
|
975
|
-
return await this.openFn_(source, { kind: 'group' });
|
|
976
|
-
}
|
|
977
|
-
catch (err) {
|
|
978
|
-
if (this.openFn_ !== open) {
|
|
979
|
-
throw err;
|
|
980
|
-
}
|
|
981
|
-
const group = await open.v2(source, { kind: 'group' });
|
|
982
|
-
this.openFn_ = open.v2;
|
|
983
|
-
return group;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
940
|
+
* Build the tile grid and the per-level band layout. Every store is read as a
|
|
941
|
+
* stack of levels holding n-dimensional arrays: in `variable` mode all bands
|
|
942
|
+
* are slices of one array, otherwise each band has an array of its own.
|
|
987
943
|
* @param {Object<string, *>} attributes The dataset attributes.
|
|
988
944
|
* @param {FetchStore} store The store, for metadata requests not covered
|
|
989
945
|
* by consolidated metadata.
|
|
946
|
+
* @return {Promise<boolean>} Whether the tile grid has explicit tile sizes.
|
|
990
947
|
* @private
|
|
991
948
|
*/
|
|
992
|
-
async
|
|
949
|
+
async configureLevels_(attributes, store) {
|
|
993
950
|
var _a;
|
|
994
|
-
const variable =
|
|
995
|
-
|
|
951
|
+
const variable = this.variable_;
|
|
952
|
+
// The arrays to look for at each level, and how to name them in messages.
|
|
953
|
+
const arrayNames = variable ? [variable] : this.bands_;
|
|
954
|
+
const what = variable
|
|
955
|
+
? `variable "${variable}"`
|
|
956
|
+
: `bands [${arrayNames.join(', ')}]`;
|
|
957
|
+
// For multi-group mode, use sub-metadata for the first group so that
|
|
958
|
+
// consolidated metadata keys match the expected relative paths.
|
|
959
|
+
const consolidatedMetadata = this.consolidatedMetadata_
|
|
960
|
+
? this.groupMetadata_(0)
|
|
961
|
+
: null;
|
|
996
962
|
// Collect the multiscale level paths; without multiscale metadata the
|
|
997
963
|
// group itself is the only level.
|
|
998
964
|
const multiscales = attributes['multiscales'];
|
|
@@ -1019,6 +985,11 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1019
985
|
}
|
|
1020
986
|
else if (multiscales && Array.isArray(multiscales.layout)) {
|
|
1021
987
|
for (const entry of multiscales.layout) {
|
|
988
|
+
// The declared `spatial:transform` is deliberately ignored here: its
|
|
989
|
+
// resolution is rounded in real stores (e.g. 720 where the extent and
|
|
990
|
+
// shape give 722.37), which leaves a sliver of a tile beyond the last
|
|
991
|
+
// full one. All layout levels share the group origin, so deriving the
|
|
992
|
+
// resolution from the extent and shape is both exact and sufficient.
|
|
1022
993
|
rawLevels.push({
|
|
1023
994
|
path: String(entry.asset),
|
|
1024
995
|
transform: undefined,
|
|
@@ -1033,40 +1004,47 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1033
1004
|
shape: attributes['spatial:shape'],
|
|
1034
1005
|
});
|
|
1035
1006
|
}
|
|
1036
|
-
|
|
1007
|
+
// Keep only the levels that hold at least one of the wanted arrays. Without
|
|
1008
|
+
// consolidated metadata their presence cannot be checked up front.
|
|
1009
|
+
/** @type {Array<{path: string, bands: Array<string>, meta: any, transform: Array<number>|undefined, shape: Array<number>|undefined}>} */
|
|
1037
1010
|
const levels = [];
|
|
1038
1011
|
for (const raw of rawLevels) {
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
warn(`Variable "${variable}" not found at level "${raw.path || '.'}"`);
|
|
1012
|
+
const bands = consolidatedMetadata
|
|
1013
|
+
? arrayNames.filter((name) => consolidatedMetadata[arrayPathOf(raw.path, name)])
|
|
1014
|
+
: arrayNames.slice();
|
|
1015
|
+
if (bands.length === 0) {
|
|
1016
|
+
warn(`No ${what} found at level "${raw.path || '.'}"`);
|
|
1045
1017
|
continue;
|
|
1046
1018
|
}
|
|
1047
|
-
levels.push(Object.assign(Object.assign({}, raw), {
|
|
1019
|
+
levels.push(Object.assign(Object.assign({}, raw), { bands, meta: consolidatedMetadata
|
|
1020
|
+
? consolidatedMetadata[arrayPathOf(raw.path, bands[0])]
|
|
1021
|
+
: undefined }));
|
|
1048
1022
|
}
|
|
1049
1023
|
if (levels.length === 0) {
|
|
1050
|
-
throw new Error(`
|
|
1024
|
+
throw new Error(`No level found holding ${what}`);
|
|
1051
1025
|
}
|
|
1052
|
-
// The dimension layout is the same for all levels
|
|
1026
|
+
// The dimension layout is the same for all levels. It is only needed when
|
|
1027
|
+
// the levels do not declare their shape, or to slice a `variable`.
|
|
1053
1028
|
let meta = (_a = levels.find((level) => level.meta)) === null || _a === void 0 ? void 0 : _a.meta;
|
|
1054
|
-
if (!meta) {
|
|
1055
|
-
meta = await getArrayMeta(store, levels[0].
|
|
1029
|
+
if (!meta && (variable || !levels.every((l) => Array.isArray(l.shape)))) {
|
|
1030
|
+
meta = await getArrayMeta(store, arrayPathOf(levels[0].path, levels[0].bands[0]), this.openFn_ === open.v2);
|
|
1056
1031
|
levels[0].meta = meta;
|
|
1057
1032
|
}
|
|
1058
|
-
|
|
1033
|
+
const hasMeta = !!meta && Array.isArray(meta.shape);
|
|
1034
|
+
if (variable && !hasMeta) {
|
|
1059
1035
|
throw new Error(`Could not read metadata for variable "${variable}"`);
|
|
1060
1036
|
}
|
|
1061
|
-
const ndim = meta.shape.length;
|
|
1062
|
-
let dimensionNames = meta['dimension_names'];
|
|
1037
|
+
const ndim = hasMeta ? meta.shape.length : 2;
|
|
1038
|
+
let dimensionNames = hasMeta ? meta['dimension_names'] : undefined;
|
|
1063
1039
|
if (!dimensionNames && ndim === 2) {
|
|
1064
1040
|
dimensionNames = ['y', 'x'];
|
|
1065
1041
|
}
|
|
1066
|
-
if (!dimensionNames) {
|
|
1042
|
+
if (variable && !dimensionNames) {
|
|
1067
1043
|
throw new Error(`Cannot determine dimension names for variable "${variable}"`);
|
|
1068
1044
|
}
|
|
1069
|
-
const { row, col } =
|
|
1045
|
+
const { row, col } = hasMeta
|
|
1046
|
+
? getSpatialAxes(attributes['spatial:dimensions'], meta)
|
|
1047
|
+
: { row: 0, col: 1 };
|
|
1070
1048
|
// Determine the extent and y axis orientation: declared metadata is
|
|
1071
1049
|
// authoritative, otherwise both are inferred from the coordinate arrays.
|
|
1072
1050
|
let extent = attributes['spatial:bbox'] || attributes['bounds'];
|
|
@@ -1080,6 +1058,12 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1080
1058
|
flipY = transform0[4] > 0;
|
|
1081
1059
|
orientationKnown = true;
|
|
1082
1060
|
}
|
|
1061
|
+
else if (extent && levels.every((l) => Array.isArray(l.shape))) {
|
|
1062
|
+
// Levels that declare their `spatial:shape` alongside a declared extent
|
|
1063
|
+
// follow the spatial: convention, which stores rasters north-up. Reading
|
|
1064
|
+
// coordinate arrays would only confirm that, at the cost of two requests.
|
|
1065
|
+
orientationKnown = true;
|
|
1066
|
+
}
|
|
1083
1067
|
else {
|
|
1084
1068
|
// All levels share the extent; the smallest level is cheapest to read
|
|
1085
1069
|
let coordLevel = levels[0];
|
|
@@ -1129,7 +1113,7 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1129
1113
|
}
|
|
1130
1114
|
const extentWidth = extent[2] - extent[0];
|
|
1131
1115
|
const extentHeight = extent[3] - extent[1];
|
|
1132
|
-
/** @type {Array<{path: string,
|
|
1116
|
+
/** @type {Array<{path: string, bands: Array<string>, meta: any, shape: Array<number>|undefined, resolution: number, rowResolution: number, origin: import("ol/coordinate.js").Coordinate}>} */
|
|
1133
1117
|
const configured = [];
|
|
1134
1118
|
for (const level of levels) {
|
|
1135
1119
|
let resolution;
|
|
@@ -1160,94 +1144,97 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1160
1144
|
}
|
|
1161
1145
|
configured.push({
|
|
1162
1146
|
path: level.path,
|
|
1163
|
-
|
|
1147
|
+
bands: level.bands,
|
|
1164
1148
|
meta: level.meta,
|
|
1149
|
+
shape: level.shape,
|
|
1165
1150
|
resolution,
|
|
1166
1151
|
rowResolution,
|
|
1167
1152
|
origin,
|
|
1168
1153
|
});
|
|
1169
1154
|
}
|
|
1170
1155
|
if (configured.length === 0) {
|
|
1171
|
-
throw new Error(`No usable level found for
|
|
1156
|
+
throw new Error(`No usable level found for ${what}`);
|
|
1172
1157
|
}
|
|
1173
1158
|
configured.sort((a, b) => b.resolution - a.resolution);
|
|
1174
1159
|
if (!this.projection) {
|
|
1175
1160
|
this.projection = this.inferProjection_(attributes, crsHint, extent);
|
|
1176
1161
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
if (Array.isArray(value)) {
|
|
1202
|
-
values = value;
|
|
1203
|
-
this.multiAxis_ = axis;
|
|
1204
|
-
}
|
|
1205
|
-
else {
|
|
1206
|
-
values = [value];
|
|
1207
|
-
}
|
|
1208
|
-
const indices = [];
|
|
1209
|
-
for (const v of values) {
|
|
1210
|
-
if (typeof v === 'number') {
|
|
1211
|
-
indices.push(v);
|
|
1162
|
+
if (variable) {
|
|
1163
|
+
// Resolve the selection to per-axis indices; labels are resolved against
|
|
1164
|
+
// the finest level's coordinate arrays
|
|
1165
|
+
const finestPath = configured[configured.length - 1].path;
|
|
1166
|
+
/** @type {Array<{axis: number, indices: Array<number>}>} */
|
|
1167
|
+
const slots = [];
|
|
1168
|
+
this.multiAxis_ = -1;
|
|
1169
|
+
this.extraDimensions_ = [];
|
|
1170
|
+
for (let axis = 0; axis < ndim; ++axis) {
|
|
1171
|
+
if (axis === row || axis === col) {
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
const dimName = dimensionNames[axis];
|
|
1175
|
+
this.extraDimensions_.push({ name: dimName, size: meta.shape[axis] });
|
|
1176
|
+
let value = this.dimensions_[dimName];
|
|
1177
|
+
if (value === undefined) {
|
|
1178
|
+
warn(`No value given for dimension "${dimName}", using index 0.`);
|
|
1179
|
+
value = 0;
|
|
1180
|
+
}
|
|
1181
|
+
/** @type {Array<number|string>} */
|
|
1182
|
+
let values;
|
|
1183
|
+
if (Array.isArray(value)) {
|
|
1184
|
+
values = value;
|
|
1185
|
+
this.multiAxis_ = axis;
|
|
1212
1186
|
}
|
|
1213
1187
|
else {
|
|
1214
|
-
|
|
1188
|
+
values = [value];
|
|
1215
1189
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1190
|
+
const indices = [];
|
|
1191
|
+
for (const v of values) {
|
|
1192
|
+
if (typeof v === 'number') {
|
|
1193
|
+
indices.push(v);
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
indices.push(await this.resolveCoordinateLabel_(dimName, v, arrayPathOf(finestPath, dimName)));
|
|
1197
|
+
}
|
|
1223
1198
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
.
|
|
1237
|
-
.
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1199
|
+
for (const index of indices) {
|
|
1200
|
+
if (!Number.isInteger(index) ||
|
|
1201
|
+
index < 0 ||
|
|
1202
|
+
index >= meta.shape[axis]) {
|
|
1203
|
+
throw new Error(`GeoZarr: invalid index ${index} for dimension "${dimName}" ` +
|
|
1204
|
+
`(size ${meta.shape[axis]}).`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
slots.push({ axis, indices });
|
|
1208
|
+
}
|
|
1209
|
+
// Materialize the datacube as the per-band state that loadTile_
|
|
1210
|
+
// consumes: each rendered band is the variable array with fixed indices
|
|
1211
|
+
// at the non-spatial axes.
|
|
1212
|
+
const multiSlot = slots.find((slot) => slot.axis === this.multiAxis_);
|
|
1213
|
+
const count = multiSlot ? multiSlot.indices.length : 1;
|
|
1214
|
+
this.bandCount = count;
|
|
1215
|
+
this.bands_ = new Array(count).fill(variable);
|
|
1216
|
+
this.bandGroupIndex_ = new Array(count).fill(0);
|
|
1217
|
+
this.bandSingleScaleResolution_ = new Array(count).fill(undefined);
|
|
1218
|
+
this.bandSpatialAxes_ = new Array(count).fill({ row, col });
|
|
1219
|
+
this.bandExtraSelection_ = new Array(count)
|
|
1220
|
+
.fill(undefined)
|
|
1221
|
+
.map((_, bandIndex) => {
|
|
1222
|
+
if (slots.length === 0) {
|
|
1223
|
+
return undefined;
|
|
1224
|
+
}
|
|
1225
|
+
const selection = new Array(ndim).fill(null);
|
|
1226
|
+
for (const slot of slots) {
|
|
1227
|
+
selection[slot.axis] =
|
|
1228
|
+
slot.axis === this.multiAxis_
|
|
1229
|
+
? slot.indices[bandIndex]
|
|
1230
|
+
: slot.indices[0];
|
|
1231
|
+
}
|
|
1232
|
+
return selection;
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
if (meta) {
|
|
1236
|
+
this.fillValue_ = parseFillValue(meta['fill_value']);
|
|
1237
|
+
}
|
|
1251
1238
|
// Align tiles to shards or chunks: every tile decodes all chunks it
|
|
1252
1239
|
// touches, so tiles much smaller than a chunk would decode the same
|
|
1253
1240
|
// chunk over and over.
|
|
@@ -1263,8 +1250,11 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1263
1250
|
getTileSizeForShard(shardInfo.shardShape[0], shardInfo.innerChunkShape[0]),
|
|
1264
1251
|
]);
|
|
1265
1252
|
}
|
|
1253
|
+
// Unsharded chunks only need alignment when they are larger than a
|
|
1254
|
+
// default tile, which would decode the same chunk for every tile it spans.
|
|
1266
1255
|
const chunkShape = (_b = (_a = level.meta['chunk_grid']) === null || _a === void 0 ? void 0 : _a.configuration) === null || _b === void 0 ? void 0 : _b.chunk_shape;
|
|
1267
|
-
if (Array.isArray(chunkShape)
|
|
1256
|
+
if (Array.isArray(chunkShape) &&
|
|
1257
|
+
Math.max(chunkShape[row], chunkShape[col]) > DEFAULT_TILE_SIZE) {
|
|
1268
1258
|
return /** @type {import("ol/size.js").Size} */ ([
|
|
1269
1259
|
getTileSizeForChunk(chunkShape[col], level.meta.shape[col]),
|
|
1270
1260
|
getTileSizeForChunk(chunkShape[row], level.meta.shape[row]),
|
|
@@ -1279,19 +1269,25 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1279
1269
|
const matrixIds = configured.map((level, i) => level.path || String(i));
|
|
1280
1270
|
for (let i = 0; i < configured.length; ++i) {
|
|
1281
1271
|
const level = configured[i];
|
|
1282
|
-
this.bandsByLevel_[matrixIds[i]] =
|
|
1272
|
+
this.bandsByLevel_[matrixIds[i]] = level.bands;
|
|
1283
1273
|
this.levelPaths_[matrixIds[i]] = level.path;
|
|
1274
|
+
let shapeY;
|
|
1275
|
+
if (level.meta && Array.isArray(level.meta.shape)) {
|
|
1276
|
+
shapeY = level.meta.shape[row];
|
|
1277
|
+
}
|
|
1278
|
+
else if (Array.isArray(level.shape)) {
|
|
1279
|
+
shapeY = level.shape[0];
|
|
1280
|
+
}
|
|
1284
1281
|
this.levelRowInfo_[matrixIds[i]] = {
|
|
1285
1282
|
rowResolution: level.rowResolution,
|
|
1286
|
-
shapeY
|
|
1287
|
-
? level.meta.shape[row]
|
|
1288
|
-
: undefined,
|
|
1283
|
+
shapeY,
|
|
1289
1284
|
flip: flipY,
|
|
1290
1285
|
};
|
|
1291
1286
|
}
|
|
1292
1287
|
this.tileGrid = new WMTSTileGrid(Object.assign({ extent: extent, origins: configured.map((level) => level.origin), resolutions: configured.map((level) => level.resolution), matrixIds: matrixIds }, (hasTileSizes
|
|
1293
1288
|
? { tileSizes: tileSizes.map((s) => s || [256, 256]) }
|
|
1294
1289
|
: {})));
|
|
1290
|
+
return hasTileSizes;
|
|
1295
1291
|
}
|
|
1296
1292
|
/**
|
|
1297
1293
|
* Determine the projection from the store metadata: the proj: convention,
|
|
@@ -1371,13 +1367,12 @@ export default class GeoZarr extends DataTileSource {
|
|
|
1371
1367
|
* coordinate array.
|
|
1372
1368
|
* @param {string} dimName The dimension name.
|
|
1373
1369
|
* @param {string} label The label to resolve.
|
|
1374
|
-
* @param {string}
|
|
1370
|
+
* @param {string} path The coordinate array path, relative to the group.
|
|
1375
1371
|
* @return {Promise<number>} The index of the label.
|
|
1376
1372
|
* @private
|
|
1377
1373
|
*/
|
|
1378
|
-
async resolveCoordinateLabel_(dimName, label,
|
|
1374
|
+
async resolveCoordinateLabel_(dimName, label, path) {
|
|
1379
1375
|
try {
|
|
1380
|
-
const path = levelPath ? `${levelPath}/${dimName}` : dimName;
|
|
1381
1376
|
const array = await this.openArray_(0, path);
|
|
1382
1377
|
const chunk = await get(array, [null]);
|
|
1383
1378
|
const values = Array.from(chunk.data, (v) => String(v));
|
|
@@ -1469,9 +1464,6 @@ function createCachedStore(store, groupBytes, consolidatedMetadata) {
|
|
|
1469
1464
|
* @typedef {Object} TileGridInfo
|
|
1470
1465
|
* @property {WMTSTileGrid} tileGrid The tile grid.
|
|
1471
1466
|
* @property {import("ol/proj/Projection.js").default} projection The projection.
|
|
1472
|
-
* @property {Object<string, Array<string>>} [bandsByLevel] Available bands by level.
|
|
1473
|
-
* @property {number} [fillValue] The fill value.
|
|
1474
|
-
* @property {Array<import("ol/size.js").Size>|undefined} [tileSizes] The tile sizes for each level, if available.
|
|
1475
1467
|
*/
|
|
1476
1468
|
/**
|
|
1477
1469
|
* Maximum tile size for rendering.
|
|
@@ -1488,6 +1480,15 @@ const MIN_TILE_SIZE = 64;
|
|
|
1488
1480
|
* @property {Array<number>} shardShape The shard (outer chunk) shape [rows, cols].
|
|
1489
1481
|
* @property {Array<number>} innerChunkShape The inner chunk shape [rows, cols].
|
|
1490
1482
|
*/
|
|
1483
|
+
/**
|
|
1484
|
+
* The path of a band array within its group.
|
|
1485
|
+
* @param {string} levelPath The level group path (empty for the group root).
|
|
1486
|
+
* @param {string} name The array name.
|
|
1487
|
+
* @return {string} The path, relative to the group.
|
|
1488
|
+
*/
|
|
1489
|
+
function arrayPathOf(levelPath, name) {
|
|
1490
|
+
return levelPath ? `${levelPath}/${name}` : name;
|
|
1491
|
+
}
|
|
1491
1492
|
/**
|
|
1492
1493
|
* Locate the row (y) and column (x) axis positions of an array by matching the
|
|
1493
1494
|
* group's `spatial:dimensions` names (`[y, x]`) against the array's
|
|
@@ -1583,78 +1584,6 @@ function getTileSizeForShard(shardSize, innerChunkSize) {
|
|
|
1583
1584
|
}
|
|
1584
1585
|
return Math.max(maxChunks * innerChunkSize, MIN_TILE_SIZE);
|
|
1585
1586
|
}
|
|
1586
|
-
/**
|
|
1587
|
-
* @param {DatasetAttributes} attributes The dataset attributes.
|
|
1588
|
-
* @param {Object<string, *>|null|undefined} consolidatedMetadata The consolidated metadata.
|
|
1589
|
-
* @param {Array<string>} wantedBands The wanted bands.
|
|
1590
|
-
* @return {TileGridInfo} The tile grid info.
|
|
1591
|
-
*/
|
|
1592
|
-
function getTileGridInfoFromAttributes(attributes, consolidatedMetadata, wantedBands) {
|
|
1593
|
-
const multiscales = attributes.multiscales;
|
|
1594
|
-
const extent = attributes['spatial:bbox'];
|
|
1595
|
-
const projection = getProjectionFromAttributes(attributes);
|
|
1596
|
-
const extentWidth = extent[2] - extent[0];
|
|
1597
|
-
const origin = [extent[0], extent[3]];
|
|
1598
|
-
/** @type {Array<{matrixId: string, resolution: number, origin: import("ol/coordinate.js").Coordinate, tileSize: import("ol/size.js").Size|undefined}>} */
|
|
1599
|
-
const groupInfo = [];
|
|
1600
|
-
/** @type {Object<string, Array<string>>|undefined} */
|
|
1601
|
-
const bandsByLevel = consolidatedMetadata ? {} : undefined;
|
|
1602
|
-
let fillValue;
|
|
1603
|
-
for (const groupMetadata of multiscales.layout) {
|
|
1604
|
-
const matrixId = /** @type {string} */ (groupMetadata['asset']);
|
|
1605
|
-
const spatialShape = /** @type {Array<number>} */ (groupMetadata['spatial:shape']);
|
|
1606
|
-
const resolution = extentWidth / spatialShape[1];
|
|
1607
|
-
/** @type {import("ol/size.js").Size|undefined} */
|
|
1608
|
-
let tileSize;
|
|
1609
|
-
if (consolidatedMetadata && bandsByLevel) {
|
|
1610
|
-
const availableBands = [];
|
|
1611
|
-
for (const band of wantedBands) {
|
|
1612
|
-
const bandArray = /** @type {Object<string, *>|undefined} */ (consolidatedMetadata[`${matrixId}/${band}`]);
|
|
1613
|
-
if (bandArray) {
|
|
1614
|
-
availableBands.push(band);
|
|
1615
|
-
if (fillValue === undefined) {
|
|
1616
|
-
fillValue = Number(bandArray['fill_value']);
|
|
1617
|
-
}
|
|
1618
|
-
//FIXME Remove this when GeoZarr datasets provide correct TileMatrixSet info or similar
|
|
1619
|
-
if (!tileSize) {
|
|
1620
|
-
const { row, col } = getSpatialAxes(attributes['spatial:dimensions'], bandArray);
|
|
1621
|
-
const shardInfo = getShardInfo(bandArray, row, col);
|
|
1622
|
-
if (shardInfo) {
|
|
1623
|
-
tileSize = [
|
|
1624
|
-
getTileSizeForShard(shardInfo.shardShape[1], shardInfo.innerChunkShape[1]),
|
|
1625
|
-
getTileSizeForShard(shardInfo.shardShape[0], shardInfo.innerChunkShape[0]),
|
|
1626
|
-
];
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
bandsByLevel[matrixId] = availableBands;
|
|
1632
|
-
}
|
|
1633
|
-
groupInfo.push({
|
|
1634
|
-
matrixId,
|
|
1635
|
-
resolution,
|
|
1636
|
-
origin,
|
|
1637
|
-
tileSize,
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
groupInfo.sort((a, b) => b.resolution - a.resolution);
|
|
1641
|
-
const tileSizes = groupInfo.map((g) => g.tileSize);
|
|
1642
|
-
const hasTileSizes = tileSizes.some((s) => s !== undefined);
|
|
1643
|
-
const tileGrid = new WMTSTileGrid(Object.assign({ extent: extent, origins: groupInfo.map((g) => g.origin), resolutions: groupInfo.map((g) => g.resolution), matrixIds: groupInfo.map((g) => g.matrixId) }, (hasTileSizes
|
|
1644
|
-
? {
|
|
1645
|
-
tileSizes: /** @type {Array<import("ol/size.js").Size>} */ (tileSizes.map((s) => s || [256, 256])),
|
|
1646
|
-
}
|
|
1647
|
-
: {})));
|
|
1648
|
-
return {
|
|
1649
|
-
tileGrid,
|
|
1650
|
-
projection,
|
|
1651
|
-
bandsByLevel,
|
|
1652
|
-
fillValue,
|
|
1653
|
-
tileSizes: hasTileSizes
|
|
1654
|
-
? /** @type {Array<import("ol/size.js").Size>} */ (tileSizes.map((s) => s || [256, 256]))
|
|
1655
|
-
: undefined,
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
1587
|
/**
|
|
1659
1588
|
* @param {LegacyDatasetAttributes} attributes The dataset attributes.
|
|
1660
1589
|
* @return {TileGridInfo} The tile grid info.
|
|
@@ -1932,46 +1861,57 @@ function normalizeV2Metadata(v2Metadata) {
|
|
|
1932
1861
|
return normalized;
|
|
1933
1862
|
}
|
|
1934
1863
|
/**
|
|
1935
|
-
* Probe the store for an optional metadata document. Absence
|
|
1936
|
-
*
|
|
1937
|
-
*
|
|
1864
|
+
* Probe the store for an optional metadata document. Absence yields
|
|
1865
|
+
* `undefined`; any other failure is thrown, so that a server error is not
|
|
1866
|
+
* mistaken for a missing document.
|
|
1938
1867
|
* @param {FetchStore} store The store.
|
|
1939
1868
|
* @param {string} key The key to probe.
|
|
1940
1869
|
* @return {Promise<Uint8Array|undefined>} The document, if present.
|
|
1941
1870
|
*/
|
|
1942
1871
|
function probe(store, key) {
|
|
1943
|
-
return store.get(/** @type {`/${string}`} */ (key))
|
|
1872
|
+
return store.get(/** @type {`/${string}`} */ (key));
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Fetch for the Zarr store, reporting a 403 as a missing key. S3 answers 403
|
|
1876
|
+
* for a key that is not there whenever the caller lacks `s3:ListBucket`, the
|
|
1877
|
+
* usual setup for publicly readable objects.
|
|
1878
|
+
* @param {Request} request The request.
|
|
1879
|
+
* @return {Promise<Response>} The response.
|
|
1880
|
+
*/
|
|
1881
|
+
function storeFetch(request) {
|
|
1882
|
+
return fetch(request).then((response) => response.status === 403 ? new Response(null, { status: 404 }) : response);
|
|
1944
1883
|
}
|
|
1945
1884
|
/**
|
|
1946
1885
|
* Read a single array's metadata directly from the store, for stores
|
|
1947
1886
|
* without consolidated metadata (Zarr v3 zarr.json or v2 .zarray/.zattrs).
|
|
1948
1887
|
* @param {FetchStore} store The store.
|
|
1949
1888
|
* @param {string} path The array path.
|
|
1889
|
+
* @param {boolean} v2 Whether the store is Zarr v2.
|
|
1950
1890
|
* @return {Promise<Object|undefined>} The array metadata (v3 shape).
|
|
1951
1891
|
*/
|
|
1952
|
-
async function getArrayMeta(store, path) {
|
|
1892
|
+
async function getArrayMeta(store, path, v2) {
|
|
1953
1893
|
const decoder = new TextDecoder();
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
return JSON.parse(decoder.decode(bytes));
|
|
1894
|
+
if (!v2) {
|
|
1895
|
+
const bytes = await probe(store, `/${path}/zarr.json`);
|
|
1896
|
+
return bytes ? JSON.parse(decoder.decode(bytes)) : undefined;
|
|
1957
1897
|
}
|
|
1958
|
-
bytes = await probe(store, `/${path}/.zarray`);
|
|
1959
|
-
if (bytes) {
|
|
1960
|
-
|
|
1961
|
-
const attrBytes = await probe(store, `/${path}/.zattrs`);
|
|
1962
|
-
const attributes = attrBytes ? JSON.parse(decoder.decode(attrBytes)) : {};
|
|
1963
|
-
return {
|
|
1964
|
-
shape: zarray['shape'],
|
|
1965
|
-
fill_value: zarray['fill_value'],
|
|
1966
|
-
dimension_names: attributes['_ARRAY_DIMENSIONS'],
|
|
1967
|
-
chunk_grid: {
|
|
1968
|
-
name: 'regular',
|
|
1969
|
-
configuration: { chunk_shape: zarray['chunks'] },
|
|
1970
|
-
},
|
|
1971
|
-
attributes,
|
|
1972
|
-
};
|
|
1898
|
+
const bytes = await probe(store, `/${path}/.zarray`);
|
|
1899
|
+
if (!bytes) {
|
|
1900
|
+
return undefined;
|
|
1973
1901
|
}
|
|
1974
|
-
|
|
1902
|
+
const zarray = JSON.parse(decoder.decode(bytes));
|
|
1903
|
+
const attrBytes = await probe(store, `/${path}/.zattrs`);
|
|
1904
|
+
const attributes = attrBytes ? JSON.parse(decoder.decode(attrBytes)) : {};
|
|
1905
|
+
return {
|
|
1906
|
+
shape: zarray['shape'],
|
|
1907
|
+
fill_value: zarray['fill_value'],
|
|
1908
|
+
dimension_names: attributes['_ARRAY_DIMENSIONS'],
|
|
1909
|
+
chunk_grid: {
|
|
1910
|
+
name: 'regular',
|
|
1911
|
+
configuration: { chunk_shape: zarray['chunks'] },
|
|
1912
|
+
},
|
|
1913
|
+
attributes,
|
|
1914
|
+
};
|
|
1975
1915
|
}
|
|
1976
1916
|
/**
|
|
1977
1917
|
* Flip the row order of a 2-dimensional chunk (for south-up data).
|