Skip to content

Commit 4094636

Browse files
committed
Website updates
1 parent 9e6af2e commit 4094636

7 files changed

Lines changed: 215 additions & 60 deletions

File tree

dist/en/main/examples/common.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/en/main/examples/common.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/en/main/ol/dist/ol.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/en/main/ol/dist/ol.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/en/main/ol/source/GeoZarr.d.ts

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,49 @@ export default class GeoZarr extends DataTileSource<import("../DataTile.js").def
8686
*/
8787
private loadTile_;
8888
}
89+
export type ShardInfo = {
90+
/**
91+
* The shard (outer chunk) shape [rows, cols].
92+
*/
93+
shardShape: Array<number>;
94+
/**
95+
* The inner chunk shape [rows, cols].
96+
*/
97+
innerChunkShape: Array<number>;
98+
};
99+
export type ResampleMethod = "nearest" | "linear";
100+
export type Options = {
101+
/**
102+
* The Zarr URL.
103+
*/
104+
url: string;
105+
/**
106+
* The group with arrays to render.
107+
*/
108+
group: string;
109+
/**
110+
* The band names to render.
111+
*/
112+
bands: Array<string>;
113+
/**
114+
* Source projection. If not provided, the GeoZarr metadata
115+
* will be read for projection information.
116+
*/
117+
projection?: import("../proj.js").ProjectionLike;
118+
/**
119+
* Duration of the opacity transition for rendering.
120+
* To disable the opacity transition, pass `transition: 0`.
121+
*/
122+
transition?: number | undefined;
123+
/**
124+
* Render tiles beyond the tile grid extent.
125+
*/
126+
wrapX?: boolean | undefined;
127+
/**
128+
* Resamplilng method if bands are not available for all multi-scale levels.
129+
*/
130+
resample?: ResampleMethod | undefined;
131+
};
89132
export type DatasetAttributes = {
90133
/**
91134
* The multiscales attribute.
@@ -139,39 +182,10 @@ export type TileGridInfo = {
139182
* The fill value.
140183
*/
141184
fillValue?: number | undefined;
142-
};
143-
export type ResampleMethod = "nearest" | "linear";
144-
export type Options = {
145-
/**
146-
* The Zarr URL.
147-
*/
148-
url: string;
149-
/**
150-
* The group with arrays to render.
151-
*/
152-
group: string;
153-
/**
154-
* The band names to render.
155-
*/
156-
bands: Array<string>;
157185
/**
158-
* Source projection. If not provided, the GeoZarr metadata
159-
* will be read for projection information.
186+
* The tile sizes for each level, if available.
160187
*/
161-
projection?: import("../proj.js").ProjectionLike;
162-
/**
163-
* Duration of the opacity transition for rendering.
164-
* To disable the opacity transition, pass `transition: 0`.
165-
*/
166-
transition?: number | undefined;
167-
/**
168-
* Render tiles beyond the tile grid extent.
169-
*/
170-
wrapX?: boolean | undefined;
171-
/**
172-
* Resamplilng method if bands are not available for all multi-scale levels.
173-
*/
174-
resample?: ResampleMethod | undefined;
188+
tileSizes?: Array<import("../size.js").Size> | undefined;
175189
};
176190
import DataTileSource from './DataTile.js';
177191
import WMTSTileGrid from '../tilegrid/WMTS.js';

dist/en/main/ol/source/GeoZarr.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/en/main/ol/source/GeoZarr.js

Lines changed: 165 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,34 @@ export default class GeoZarr extends DataTileSource {
134134
async configure_() {
135135
const store = new FetchStore(this.url_);
136136

137-
this.root_ = await open(store, {kind: 'group'});
138-
139-
try {
140-
this.consolidatedMetadata_ = JSON.parse(
141-
new TextDecoder().decode(
142-
await store.get(this.root_.resolve('zarr.json').path),
143-
),
144-
).consolidated_metadata.metadata;
145-
} catch {
146-
// empty catch block
137+
// Fetch root zarr.json once for both opening the root and extracting
138+
// consolidated metadata. Without this, open() and the manual metadata
139+
// read would each make a separate HTTP request for the same file.
140+
const rootBytes = await store.get('/zarr.json');
141+
if (rootBytes) {
142+
try {
143+
this.consolidatedMetadata_ = JSON.parse(
144+
new TextDecoder().decode(rootBytes),
145+
).consolidated_metadata.metadata;
146+
} catch {
147+
// no consolidated metadata
148+
}
147149
}
148150

151+
// Wrap the store so that child metadata (groups, arrays) is served from
152+
// the consolidated metadata instead of making per-child HTTP requests.
153+
const cachedStore = this.consolidatedMetadata_
154+
? createCachedStore(store, rootBytes, this.consolidatedMetadata_)
155+
: store;
156+
157+
this.root_ = await open(cachedStore, {kind: 'group'});
158+
149159
const group = await open(this.root_.resolve(this.group_), {kind: 'group'});
150160

151161
const attributes =
152162
/** @type {LegacyDatasetAttributes | DatasetAttributes} */ (group.attrs);
153163

164+
let hasTileSizes = false;
154165
if (
155166
'zarr_conventions' in attributes &&
156167
Array.isArray(attributes.zarr_conventions) &&
@@ -159,7 +170,7 @@ export default class GeoZarr extends DataTileSource {
159170
) &&
160171
'layout' in attributes.multiscales
161172
) {
162-
const {tileGrid, projection, bandsByLevel, fillValue} =
173+
const {tileGrid, projection, bandsByLevel, fillValue, tileSizes} =
163174
getTileGridInfoFromAttributes(
164175
/** @type {DatasetAttributes} */ (attributes),
165176
this.consolidatedMetadata_,
@@ -170,14 +181,11 @@ export default class GeoZarr extends DataTileSource {
170181
this.tileGrid = tileGrid;
171182
this.projection = projection;
172183
this.fillValue_ = fillValue;
173-
if (this.fillValue_ !== null && this.fillValue_ !== undefined) {
174-
this.bandCount = this.bands_.length + 1;
175-
this.nodataBandIndex = this.bandCount;
176-
}
184+
hasTileSizes = !!tileSizes;
177185
}
178-
if ('tile_matrix_set' in attributes.multiscales) {
186+
if (!hasTileSizes && 'tile_matrix_set' in attributes.multiscales) {
179187
// If available, use tile_matrix_set (legacy attributes) to get a tile grid, because it
180-
// provides a better mapping of tiles to zarr chunks.
188+
// should provide a better mapping of tiles to zarr chunks.
181189
const {tileGrid, projection} = getTileGridInfoFromLegacyAttributes(
182190
/** @type {LegacyDatasetAttributes} */ (attributes),
183191
);
@@ -187,6 +195,10 @@ export default class GeoZarr extends DataTileSource {
187195
this.projection = projection;
188196
}
189197
}
198+
if (this.fillValue_ !== null && this.fillValue_ !== undefined) {
199+
this.bandCount = this.bands_.length + 1;
200+
this.nodataBandIndex = this.bandCount;
201+
}
190202
if (!this.tileGrid) {
191203
throw new Error('Could not determine tile grid');
192204
}
@@ -310,6 +322,32 @@ export default class GeoZarr extends DataTileSource {
310322
}
311323
}
312324

325+
/**
326+
* Create a store wrapper that serves Zarr v3 metadata from consolidated
327+
* metadata, avoiding per-child HTTP requests.
328+
* @param {import('zarrita').FetchStore} store The underlying store.
329+
* @param {Uint8Array} rootBytes The already-fetched root zarr.json bytes.
330+
* @param {Object} consolidatedMetadata The parsed consolidated_metadata.metadata entries.
331+
* @return {Object} A store-compatible object.
332+
*/
333+
function createCachedStore(store, rootBytes, consolidatedMetadata) {
334+
const cache = new Map();
335+
cache.set('/zarr.json', rootBytes);
336+
const encoder = new TextEncoder();
337+
for (const [key, value] of Object.entries(consolidatedMetadata)) {
338+
cache.set(`/${key}/zarr.json`, encoder.encode(JSON.stringify(value)));
339+
}
340+
return {
341+
async get(key, opts) {
342+
if (cache.has(key)) {
343+
return cache.get(key);
344+
}
345+
return store.get(key, opts);
346+
},
347+
getRange: store.getRange?.bind(store),
348+
};
349+
}
350+
313351
/**
314352
* @typedef {Object} DatasetAttributes
315353
* @property {Multiscales} multiscales The multiscales attribute.
@@ -338,8 +376,87 @@ export default class GeoZarr extends DataTileSource {
338376
* @property {import("../proj/Projection.js").default} projection The projection.
339377
* @property {Object<string, Array<string>>} [bandsByLevel] Available bands by level.
340378
* @property {number} [fillValue] The fill value.
379+
* @property {Array<import("../size.js").Size>|undefined} [tileSizes] The tile sizes for each level, if available.
341380
*/
342381

382+
/**
383+
* Maximum tile size for rendering.
384+
* @type {number}
385+
*/
386+
const MAX_TILE_SIZE = 512;
387+
388+
/**
389+
* Minimum tile size when sharding is used.
390+
* @type {number}
391+
*/
392+
const MIN_TILE_SIZE = 64;
393+
394+
/**
395+
* @typedef {Object} ShardInfo
396+
* @property {Array<number>} shardShape The shard (outer chunk) shape [rows, cols].
397+
* @property {Array<number>} innerChunkShape The inner chunk shape [rows, cols].
398+
*/
399+
400+
/**
401+
* FIXME Remove this when GeoZarr datasets provide correct TileMatrixSet info or similar.
402+
*
403+
* Get the shard and inner chunk shapes from the Zarr v3 array metadata.
404+
* Only returns info when a `sharding_indexed` codec is present, meaning
405+
* `chunk_grid.configuration.chunk_shape` represents the shard (outer chunk) size.
406+
* @param {Object} arrayMeta The Zarr v3 array metadata from consolidated metadata.
407+
* @return {ShardInfo|undefined} The shard info, or undefined.
408+
*/
409+
function getShardInfo(arrayMeta) {
410+
const chunkGrid = arrayMeta['chunk_grid'];
411+
if (!chunkGrid || chunkGrid['name'] !== 'regular') {
412+
return undefined;
413+
}
414+
const codecs = arrayMeta['codecs'];
415+
if (!Array.isArray(codecs)) {
416+
return undefined;
417+
}
418+
const shardingCodec = codecs.find((c) => c['name'] === 'sharding_indexed');
419+
if (!shardingCodec) {
420+
return undefined;
421+
}
422+
return {
423+
shardShape: chunkGrid['configuration']['chunk_shape'],
424+
innerChunkShape: shardingCodec['configuration']['chunk_shape'],
425+
};
426+
}
427+
428+
/**
429+
* FIXME Remove this when GeoZarr datasets provide correct TileMatrixSet info or similar.
430+
*
431+
* Compute a tile size that is a multiple of the inner chunk size, evenly divides
432+
* the shard size, is at most MAX_TILE_SIZE, and is at least MIN_TILE_SIZE.
433+
* Aligning with inner chunk boundaries avoids fetching the same inner chunk
434+
* data for adjacent tiles.
435+
* @param {number} shardSize The shard size in pixels along one dimension.
436+
* @param {number} innerChunkSize The inner chunk size in pixels along one dimension.
437+
* @return {number} The tile size.
438+
*/
439+
function getTileSizeForShard(shardSize, innerChunkSize) {
440+
// Find the largest multiple of innerChunkSize that divides shardSize
441+
// and is within [MIN_TILE_SIZE, MAX_TILE_SIZE].
442+
const maxChunks = Math.floor(MAX_TILE_SIZE / innerChunkSize);
443+
for (let n = maxChunks; n >= 1; --n) {
444+
const candidate = n * innerChunkSize;
445+
if (candidate >= MIN_TILE_SIZE && shardSize % candidate === 0) {
446+
return candidate;
447+
}
448+
}
449+
// No ideal size found. Use shard size itself when it fits, otherwise
450+
// use the largest chunk-aligned size that fits within MAX_TILE_SIZE.
451+
if (shardSize <= MAX_TILE_SIZE && shardSize >= MIN_TILE_SIZE) {
452+
return shardSize;
453+
}
454+
if (shardSize < MIN_TILE_SIZE) {
455+
return MIN_TILE_SIZE;
456+
}
457+
return Math.max(maxChunks * innerChunkSize, MIN_TILE_SIZE);
458+
}
459+
343460
/**
344461
* @param {DatasetAttributes} attributes The dataset attributes.
345462
* @param {any} consolidatedMetadata The consolidated metadata.
@@ -356,7 +473,7 @@ function getTileGridInfoFromAttributes(
356473
const multiscales = attributes.multiscales;
357474
const extent = attributes['spatial:bbox'];
358475
const projection = getProjection(attributes['proj:code']);
359-
/** @type {Array<{matrixId: string, resolution: number, origin: import("ol/coordinate").Coordinate}>} */
476+
/** @type {Array<{matrixId: string, resolution: number, origin: import("ol/coordinate").Coordinate, tileSize: import("../size.js").Size|undefined}>} */
360477
const groupInfo = [];
361478
const bandsByLevel = consolidatedMetadata ? {} : null;
362479
let fillValue;
@@ -366,11 +483,8 @@ function getTileGridInfoFromAttributes(
366483
const resolution = transform[0];
367484
const origin = [transform[2], transform[5]];
368485
const matrixId = groupMetadata.asset;
369-
groupInfo.push({
370-
matrixId,
371-
resolution,
372-
origin,
373-
});
486+
/** @type {import("../size.js").Size|undefined} */
487+
let tileSize;
374488
if (consolidatedMetadata) {
375489
const availableBands = [];
376490
for (const band of wantedBands) {
@@ -381,20 +495,47 @@ function getTileGridInfoFromAttributes(
381495
if (fillValue === undefined) {
382496
fillValue = Number(bandArray['fill_value']);
383497
}
498+
//FIXME Remove this when GeoZarr datasets provide correct TileMatrixSet info or similar
499+
if (!tileSize) {
500+
const shardInfo = getShardInfo(bandArray);
501+
if (shardInfo) {
502+
tileSize = [
503+
getTileSizeForShard(
504+
shardInfo.shardShape[1],
505+
shardInfo.innerChunkShape[1],
506+
),
507+
getTileSizeForShard(
508+
shardInfo.shardShape[0],
509+
shardInfo.innerChunkShape[0],
510+
),
511+
];
512+
}
513+
}
384514
}
385515
}
386516
bandsByLevel[matrixId] = availableBands;
387517
}
518+
groupInfo.push({
519+
matrixId,
520+
resolution,
521+
origin,
522+
tileSize,
523+
});
388524
}
389525
groupInfo.sort((a, b) => b.resolution - a.resolution);
526+
527+
const tileSizes = groupInfo.map((g) => g.tileSize);
528+
const hasTileSizes = tileSizes.some((s) => s !== undefined);
529+
390530
const tileGrid = new WMTSTileGrid({
391531
extent: extent,
392532
origins: groupInfo.map((g) => g.origin),
393533
resolutions: groupInfo.map((g) => g.resolution),
394534
matrixIds: groupInfo.map((g) => g.matrixId),
535+
...(hasTileSizes ? {tileSizes: tileSizes.map((s) => s || [256, 256])} : {}),
395536
});
396537

397-
return {tileGrid, projection, bandsByLevel, fillValue};
538+
return {tileGrid, projection, bandsByLevel, fillValue, tileSizes};
398539
}
399540

400541
/**

0 commit comments

Comments
 (0)