@loaders.gl/tiles 5.0.0-alpha.7 → 5.0.0-alpha.9
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 +26 -0
- package/dist/dist.dev.js +6782 -6939
- package/dist/dist.min.js +3 -6
- package/dist/index.cjs +235 -75
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -1
- package/dist/spatial/spatial-coordinate-transformer.js +18 -11
- package/dist/spatial/spatial-coordinate-transformer.js.map +1 -1
- package/dist/spatial/spatial-resource-registry.d.ts +4 -4
- package/dist/spatial/spatial-resource-registry.d.ts.map +1 -1
- package/dist/spatial/spatial-resource-registry.js +5 -5
- package/dist/spatial/spatial-resource-registry.js.map +1 -1
- package/dist/tileset-3d/common/tile-3d.d.ts +4 -0
- package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tile-3d.js +35 -14
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +1 -25
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +8 -41
- package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-content-traversal.d.ts +37 -0
- package/dist/tileset-3d/common/tileset-content-traversal.d.ts.map +1 -0
- package/dist/tileset-3d/common/tileset-content-traversal.js +80 -0
- package/dist/tileset-3d/common/tileset-content-traversal.js.map +1 -0
- package/dist/tileset-3d/common/tileset-source.d.ts +8 -1
- package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-source.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +7 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +35 -3
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts +4 -2
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.js +60 -9
- package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.d.ts +1 -0
- package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.js +5 -4
- package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
- package/package.json +11 -13
- package/src/index.ts +5 -0
- package/src/spatial/spatial-coordinate-transformer.ts +24 -15
- package/src/spatial/spatial-resource-registry.ts +7 -7
- package/src/tileset-3d/common/tile-3d.ts +39 -17
- package/src/tileset-3d/common/tileset-3d.ts +8 -60
- package/src/tileset-3d/common/tileset-content-traversal.ts +114 -0
- package/src/tileset-3d/common/tileset-source.ts +16 -1
- package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +43 -3
- package/src/tileset-3d/format-i3s/i3s-source.ts +84 -13
- package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +2 -1
- package/src/tileset-3d/helpers/frame-state.ts +9 -6
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import {Geoid, parsePGM} from '@math.gl/geoid';
|
|
6
|
-
import {
|
|
7
|
-
import type {
|
|
6
|
+
import {Projection} from '@math.gl/projection';
|
|
7
|
+
import type {ReadonlyCRSDefinition} from '@math.gl/crs';
|
|
8
8
|
|
|
9
9
|
const geoidModels = new Map<string, Geoid>();
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Registers an application-supplied CRS alias for deterministic
|
|
12
|
+
* Registers an application-supplied CRS alias for deterministic coordinate transformations.
|
|
13
13
|
*
|
|
14
14
|
* loaders.gl never downloads registry definitions implicitly.
|
|
15
15
|
*
|
|
16
16
|
* @param name - Identifier used by dataset metadata or application options.
|
|
17
|
-
* @param definition - CRS definition understood by `@math.gl/
|
|
17
|
+
* @param definition - CRS definition understood by `@math.gl/projection`.
|
|
18
18
|
*/
|
|
19
|
-
export function registerSpatialCrs(name: string, definition:
|
|
19
|
+
export function registerSpatialCrs(name: string, definition: ReadonlyCRSDefinition): void {
|
|
20
20
|
validateResourceName(name, 'CRS');
|
|
21
|
-
|
|
21
|
+
Projection.defineProjectionAliases({[name]: definition});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -29,7 +29,7 @@ export function registerSpatialCrs(name: string, definition: Proj4CRSDefinition)
|
|
|
29
29
|
*/
|
|
30
30
|
export function registerSpatialDatumGrid(name: string, data: ArrayBuffer): void {
|
|
31
31
|
validateResourceName(name, 'datum grid');
|
|
32
|
-
|
|
32
|
+
Projection.registerDatumGrid(name, data);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -602,23 +602,7 @@ export class Tile3D {
|
|
|
602
602
|
}
|
|
603
603
|
|
|
604
604
|
try {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (loadResult.contents) {
|
|
608
|
-
this.contents = loadResult.contents;
|
|
609
|
-
this.content = this.contents[0] || null;
|
|
610
|
-
}
|
|
611
|
-
this.vectorContent =
|
|
612
|
-
this.contents.find(content => Boolean(content?.vectorContent))?.vectorContent || null;
|
|
613
|
-
|
|
614
|
-
if (this.tileset.options.contentLoader) {
|
|
615
|
-
await this.tileset.options.contentLoader(this);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
this.contentState = TILE_CONTENT_STATE.READY;
|
|
619
|
-
this._onContentLoaded();
|
|
620
|
-
this._updateContentEntriesAfterLoad(loadResult);
|
|
621
|
-
return loadResult;
|
|
605
|
+
return await this._loadContentFromSource();
|
|
622
606
|
} catch (error) {
|
|
623
607
|
// Tile is unloaded before the content finishes loading
|
|
624
608
|
this.contentState = TILE_CONTENT_STATE.FAILED;
|
|
@@ -628,6 +612,44 @@ export class Tile3D {
|
|
|
628
612
|
}
|
|
629
613
|
}
|
|
630
614
|
|
|
615
|
+
/** Loads content for complete dataset operations without viewport-based request priority. */
|
|
616
|
+
async loadContentForTraversal(): Promise<TileContentLoadResult> {
|
|
617
|
+
if (this.hasEmptyContent) {
|
|
618
|
+
return {loaded: false};
|
|
619
|
+
}
|
|
620
|
+
if (this.content) {
|
|
621
|
+
return {loaded: true, contents: this.contents};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
this.contentState = TILE_CONTENT_STATE.LOADING;
|
|
625
|
+
try {
|
|
626
|
+
return await this._loadContentFromSource();
|
|
627
|
+
} catch (error) {
|
|
628
|
+
this.contentState = TILE_CONTENT_STATE.FAILED;
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** Loads source content and updates the tile's renderer-neutral payload state. */
|
|
634
|
+
private async _loadContentFromSource(): Promise<TileContentLoadResult> {
|
|
635
|
+
const loadResult = await this.tileset.source.loadTileContent(this);
|
|
636
|
+
if (loadResult.contents) {
|
|
637
|
+
this.contents = loadResult.contents;
|
|
638
|
+
this.content = this.contents[0] || null;
|
|
639
|
+
}
|
|
640
|
+
this.vectorContent =
|
|
641
|
+
this.contents.find(content => Boolean(content?.vectorContent))?.vectorContent || null;
|
|
642
|
+
|
|
643
|
+
if (this.tileset.options.contentLoader) {
|
|
644
|
+
await this.tileset.options.contentLoader(this);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
this.contentState = TILE_CONTENT_STATE.READY;
|
|
648
|
+
this._onContentLoaded();
|
|
649
|
+
this._updateContentEntriesAfterLoad(loadResult);
|
|
650
|
+
return loadResult;
|
|
651
|
+
}
|
|
652
|
+
|
|
631
653
|
/**
|
|
632
654
|
* Requests and installs this tile's lazy child-header group through its source.
|
|
633
655
|
*
|
|
@@ -58,17 +58,6 @@ export type Tileset3DProps = {
|
|
|
58
58
|
* @default 536870912
|
|
59
59
|
*/
|
|
60
60
|
maximumCacheOverflowBytes?: number;
|
|
61
|
-
/**
|
|
62
|
-
* Soft cache target in mebibytes.
|
|
63
|
-
* @deprecated Use {@link Tileset3DProps.cacheBytes}; when both are supplied, `cacheBytes` wins.
|
|
64
|
-
*/
|
|
65
|
-
maximumMemoryUsage?: number;
|
|
66
|
-
/**
|
|
67
|
-
* Cache overflow headroom in mebibytes.
|
|
68
|
-
* @deprecated Use {@link Tileset3DProps.maximumCacheOverflowBytes}; when both are supplied, the
|
|
69
|
-
* byte-native option wins.
|
|
70
|
-
*/
|
|
71
|
-
memoryCacheOverflow?: number;
|
|
72
61
|
maximumTilesSelected?: number;
|
|
73
62
|
debounceTime?: number;
|
|
74
63
|
|
|
@@ -158,10 +147,6 @@ type Props = {
|
|
|
158
147
|
cacheBytes: number;
|
|
159
148
|
/** Current-frame headroom, in bytes, before memory-adjusted SSE rises. */
|
|
160
149
|
maximumCacheOverflowBytes: number;
|
|
161
|
-
/** @deprecated Byte-native code should use `cacheBytes`. */
|
|
162
|
-
maximumMemoryUsage: number;
|
|
163
|
-
/** @deprecated Byte-native code should use `maximumCacheOverflowBytes`. */
|
|
164
|
-
memoryCacheOverflow: number;
|
|
165
150
|
maximumTilesSelected: number;
|
|
166
151
|
debounceTime: number;
|
|
167
152
|
onTileLoad: (tile: Tile3D) => void;
|
|
@@ -241,31 +226,21 @@ function validateCacheByteLength(value: number, optionName: string): number {
|
|
|
241
226
|
}
|
|
242
227
|
|
|
243
228
|
/**
|
|
244
|
-
* Resolves the base cache target from byte-native or
|
|
229
|
+
* Resolves the base cache target from the byte-native option or its fallback.
|
|
245
230
|
*
|
|
246
|
-
*
|
|
247
|
-
* The fallback is already expressed in bytes and is used for both construction and runtime updates.
|
|
248
|
-
*
|
|
249
|
-
* @param options - Options that may contain byte-native or deprecated cache values.
|
|
231
|
+
* @param options - Options that may contain a byte-native cache value.
|
|
250
232
|
* @param fallbackCacheBytes - Existing or default cache target in bytes.
|
|
251
233
|
* @returns The resolved and validated cache target in bytes.
|
|
252
234
|
*/
|
|
253
235
|
function resolveCacheBytes(options: Tileset3DProps, fallbackCacheBytes: number): number {
|
|
254
|
-
const cacheBytes =
|
|
255
|
-
options.cacheBytes ??
|
|
256
|
-
(options.maximumMemoryUsage !== undefined
|
|
257
|
-
? options.maximumMemoryUsage * BYTES_PER_MEBIBYTE
|
|
258
|
-
: fallbackCacheBytes);
|
|
236
|
+
const cacheBytes = options.cacheBytes ?? fallbackCacheBytes;
|
|
259
237
|
return validateCacheByteLength(cacheBytes, 'cacheBytes');
|
|
260
238
|
}
|
|
261
239
|
|
|
262
240
|
/**
|
|
263
|
-
* Resolves cache overflow headroom from byte-native or
|
|
264
|
-
*
|
|
265
|
-
* The byte-native option takes precedence so callers can migrate the two cache controls
|
|
266
|
-
* independently without unit ambiguity.
|
|
241
|
+
* Resolves cache overflow headroom from the byte-native option or its fallback.
|
|
267
242
|
*
|
|
268
|
-
* @param options - Options that may contain byte-native
|
|
243
|
+
* @param options - Options that may contain a byte-native overflow value.
|
|
269
244
|
* @param fallbackOverflowBytes - Existing or default overflow headroom in bytes.
|
|
270
245
|
* @returns The resolved and validated overflow headroom in bytes.
|
|
271
246
|
*/
|
|
@@ -273,11 +248,7 @@ function resolveMaximumCacheOverflowBytes(
|
|
|
273
248
|
options: Tileset3DProps,
|
|
274
249
|
fallbackOverflowBytes: number
|
|
275
250
|
): number {
|
|
276
|
-
const maximumCacheOverflowBytes =
|
|
277
|
-
options.maximumCacheOverflowBytes ??
|
|
278
|
-
(options.memoryCacheOverflow !== undefined
|
|
279
|
-
? options.memoryCacheOverflow * BYTES_PER_MEBIBYTE
|
|
280
|
-
: fallbackOverflowBytes);
|
|
251
|
+
const maximumCacheOverflowBytes = options.maximumCacheOverflowBytes ?? fallbackOverflowBytes;
|
|
281
252
|
return validateCacheByteLength(maximumCacheOverflowBytes, 'maximumCacheOverflowBytes');
|
|
282
253
|
}
|
|
283
254
|
|
|
@@ -289,8 +260,6 @@ const DEFAULT_PROPS: Props = {
|
|
|
289
260
|
maxRequests: 64,
|
|
290
261
|
cacheBytes: DEFAULT_CACHE_BYTES,
|
|
291
262
|
maximumCacheOverflowBytes: DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES,
|
|
292
|
-
maximumMemoryUsage: DEFAULT_CACHE_BYTES / BYTES_PER_MEBIBYTE,
|
|
293
|
-
memoryCacheOverflow: DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES / BYTES_PER_MEBIBYTE,
|
|
294
263
|
maximumTilesSelected: 0,
|
|
295
264
|
debounceTime: 0,
|
|
296
265
|
onTileLoad: () => {},
|
|
@@ -456,8 +425,6 @@ export class Tileset3D {
|
|
|
456
425
|
...suppliedOptions,
|
|
457
426
|
cacheBytes,
|
|
458
427
|
maximumCacheOverflowBytes,
|
|
459
|
-
maximumMemoryUsage: cacheBytes / BYTES_PER_MEBIBYTE,
|
|
460
|
-
memoryCacheOverflow: maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE,
|
|
461
428
|
memoryAdjustedScreenSpaceError:
|
|
462
429
|
suppliedOptions.memoryAdjustedScreenSpaceError ?? usesTiles3DCacheDefaults
|
|
463
430
|
};
|
|
@@ -552,7 +519,6 @@ export class Tileset3D {
|
|
|
552
519
|
set cacheBytes(value: number) {
|
|
553
520
|
this._cacheBytes = validateCacheByteLength(value, 'cacheBytes');
|
|
554
521
|
this.options.cacheBytes = this._cacheBytes;
|
|
555
|
-
this.options.maximumMemoryUsage = this._cacheBytes / BYTES_PER_MEBIBYTE;
|
|
556
522
|
}
|
|
557
523
|
|
|
558
524
|
/**
|
|
@@ -569,19 +535,6 @@ export class Tileset3D {
|
|
|
569
535
|
set maximumCacheOverflowBytes(value: number) {
|
|
570
536
|
this._maximumCacheOverflowBytes = validateCacheByteLength(value, 'maximumCacheOverflowBytes');
|
|
571
537
|
this.options.maximumCacheOverflowBytes = this._maximumCacheOverflowBytes;
|
|
572
|
-
this.options.memoryCacheOverflow = this._maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Gets or sets the soft cache target in mebibytes.
|
|
577
|
-
* @deprecated Use {@link Tileset3D.cacheBytes}. Assignments remain synchronized for compatibility.
|
|
578
|
-
*/
|
|
579
|
-
get maximumMemoryUsage(): number {
|
|
580
|
-
return this.cacheBytes / BYTES_PER_MEBIBYTE;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
set maximumMemoryUsage(value: number) {
|
|
584
|
-
this.cacheBytes = validateCacheByteLength(value * BYTES_PER_MEBIBYTE, 'maximumMemoryUsage');
|
|
585
538
|
}
|
|
586
539
|
|
|
587
540
|
get queryParams(): string {
|
|
@@ -591,10 +544,7 @@ export class Tileset3D {
|
|
|
591
544
|
}
|
|
592
545
|
|
|
593
546
|
/**
|
|
594
|
-
* Updates traversal and runtime options
|
|
595
|
-
*
|
|
596
|
-
* For each budget, the byte-native option takes precedence when both unit forms are supplied.
|
|
597
|
-
* Unspecified budgets retain their current byte values, avoiding repeated MiB conversion.
|
|
547
|
+
* Updates traversal and runtime options while retaining unspecified byte budgets.
|
|
598
548
|
*
|
|
599
549
|
* @param props - Partial runtime options to apply.
|
|
600
550
|
*/
|
|
@@ -608,9 +558,7 @@ export class Tileset3D {
|
|
|
608
558
|
...this.options,
|
|
609
559
|
...props,
|
|
610
560
|
cacheBytes,
|
|
611
|
-
maximumCacheOverflowBytes
|
|
612
|
-
maximumMemoryUsage: cacheBytes / BYTES_PER_MEBIBYTE,
|
|
613
|
-
memoryCacheOverflow: maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE
|
|
561
|
+
maximumCacheOverflowBytes
|
|
614
562
|
};
|
|
615
563
|
this._cacheBytes = cacheBytes;
|
|
616
564
|
this._maximumCacheOverflowBytes = maximumCacheOverflowBytes;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {Tile3D} from './tile-3d';
|
|
6
|
+
import type {Tileset3D} from './tileset-3d';
|
|
7
|
+
import type {TileContentLoadResult} from './tileset-source';
|
|
8
|
+
import type {Tile3DContent} from './tile-3d-contracts';
|
|
9
|
+
import {TILESET_TYPE} from '../../constants';
|
|
10
|
+
|
|
11
|
+
/** Options for complete camera-independent tileset content traversal. */
|
|
12
|
+
export interface TraverseTilesetContentsOptions {
|
|
13
|
+
/** Cancel traversal and stop issuing further source requests. */
|
|
14
|
+
readonly signal?: AbortSignal;
|
|
15
|
+
/**
|
|
16
|
+
* Unload content loaded by this traversal when iteration advances or closes, including on error.
|
|
17
|
+
* Defaults to false. Preloaded content is retained. Use a runtime dedicated to this traversal;
|
|
18
|
+
* consume payloads before advancing and do not retain them or update the runtime concurrently.
|
|
19
|
+
*/
|
|
20
|
+
readonly unloadContent?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One tile placement and its ordered content entries from a completed source read. */
|
|
24
|
+
export interface TilesetContentTraversalItem {
|
|
25
|
+
/** Runtime tile placement. Shared content resources are yielded once per placement. */
|
|
26
|
+
readonly tile: Tile3D;
|
|
27
|
+
/** Ordered content entries, including multiple contents declared by a single tile. */
|
|
28
|
+
readonly contents: readonly Tile3DContent[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Walks every declared tile placement and its loaded content without a camera or visibility test.
|
|
33
|
+
*
|
|
34
|
+
* Source implementations remain responsible for lazy subtree and node-page resolution. Children
|
|
35
|
+
* are visited in declaration order, nested tilesets are installed by the source before descent,
|
|
36
|
+
* and repeated resource URLs are not deduplicated because each placement can have a distinct
|
|
37
|
+
* transform. Content stays attached unless `unloadContent` is enabled. In that mode, newly loaded
|
|
38
|
+
* payloads are valid until iteration advances or closes. Tile placements and children remain
|
|
39
|
+
* available; callers still own the tileset, source, and archive lifetime.
|
|
40
|
+
*
|
|
41
|
+
* @param tileset - Initialized or initializing source-backed tileset runtime.
|
|
42
|
+
* @param options - Cancellation and decoded-content lifetime options.
|
|
43
|
+
* @returns One item for each visited tile placement in deterministic depth-first order.
|
|
44
|
+
*/
|
|
45
|
+
export async function* traverseTilesetContents(
|
|
46
|
+
tileset: Tileset3D,
|
|
47
|
+
options: TraverseTilesetContentsOptions = {}
|
|
48
|
+
): AsyncGenerator<TilesetContentTraversalItem> {
|
|
49
|
+
await tileset.tilesetInitializationPromise;
|
|
50
|
+
throwIfAborted(options.signal);
|
|
51
|
+
if (!tileset.root) {
|
|
52
|
+
throw new Error('Tileset initialization completed without a root tile');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const stack: Tile3D[] = [tileset.root];
|
|
56
|
+
const visitedTiles = new Set<Tile3D>();
|
|
57
|
+
while (stack.length > 0) {
|
|
58
|
+
throwIfAborted(options.signal);
|
|
59
|
+
const tile = stack.pop() as Tile3D;
|
|
60
|
+
if (visitedTiles.has(tile)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
visitedTiles.add(tile);
|
|
64
|
+
|
|
65
|
+
if (tile.header.implicitSubtree && tile.childrenState !== 'ready') {
|
|
66
|
+
const loadChildren = tileset.source.loadTileChildrenForTraversal;
|
|
67
|
+
if (!loadChildren) {
|
|
68
|
+
throw new Error('Tileset source does not support camera-independent subtree loading');
|
|
69
|
+
}
|
|
70
|
+
const result = await loadChildren.call(tileset.source, tile, options.signal);
|
|
71
|
+
if (!result.loaded) {
|
|
72
|
+
throw new Error(`Unable to load implicit subtree for tile ${tile.id}`);
|
|
73
|
+
}
|
|
74
|
+
tile.childrenState = 'ready';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (tileset.type === TILESET_TYPE.I3S && tileset.source.loadTileChildrenForTraversal) {
|
|
78
|
+
const result = await tileset.source.loadTileChildrenForTraversal(tile, options.signal);
|
|
79
|
+
if (!result.loaded) {
|
|
80
|
+
throw new Error(`Unable to load I3S child nodes for tile ${tile.id}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throwIfAborted(options.signal);
|
|
85
|
+
const loadsContent = tile.contentUrls.length > 0 && !tile.content;
|
|
86
|
+
try {
|
|
87
|
+
if (loadsContent) {
|
|
88
|
+
const loadResult: TileContentLoadResult = await tile.loadContentForTraversal();
|
|
89
|
+
throwIfAborted(options.signal);
|
|
90
|
+
if (!loadResult.loaded) {
|
|
91
|
+
throw new Error(`Unable to load tile content for tile ${tile.id}`);
|
|
92
|
+
}
|
|
93
|
+
tileset.source.onTileLoaded?.(tileset, tile, loadResult);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
yield {tile, contents: tile.contentEntries};
|
|
97
|
+
} finally {
|
|
98
|
+
if (options.unloadContent && loadsContent) {
|
|
99
|
+
tile.unloadContent();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (let childIndex = tile.children.length - 1; childIndex >= 0; childIndex--) {
|
|
104
|
+
stack.push(tile.children[childIndex]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Throws the signal reason when traversal is cancelled. */
|
|
110
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
111
|
+
if (signal?.aborted) {
|
|
112
|
+
throw signal.reason ?? new DOMException('The operation was aborted', 'AbortError');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -221,6 +221,17 @@ export interface Tileset3DSource {
|
|
|
221
221
|
*/
|
|
222
222
|
loadTileChildren?(tile: Tile3D, frameState: FrameState): Promise<TileChildrenLoadResult>;
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Loads all source-managed child headers without requiring camera or visibility state.
|
|
226
|
+
*
|
|
227
|
+
* This hook is used by complete dataset operations such as conversion. Implementations retain
|
|
228
|
+
* ownership of implicit subtrees, node pages, archive resolution, and query inheritance.
|
|
229
|
+
*/
|
|
230
|
+
loadTileChildrenForTraversal?(
|
|
231
|
+
tile: Tile3D,
|
|
232
|
+
signal?: AbortSignal
|
|
233
|
+
): Promise<TileChildrenLoadResult>;
|
|
234
|
+
|
|
224
235
|
/**
|
|
225
236
|
* Resolves a tile-relative path to the final request URL.
|
|
226
237
|
*/
|
|
@@ -239,7 +250,11 @@ export interface Tileset3DSource {
|
|
|
239
250
|
/**
|
|
240
251
|
* Loads child tile metadata on demand for formats that do not expose the full tree up front.
|
|
241
252
|
*/
|
|
242
|
-
loadChildTileHeader?(
|
|
253
|
+
loadChildTileHeader?(
|
|
254
|
+
parentTile: Tile3D,
|
|
255
|
+
childId: string,
|
|
256
|
+
frameState: FrameState | null
|
|
257
|
+
): Promise<any>;
|
|
243
258
|
|
|
244
259
|
/**
|
|
245
260
|
* Updates source-managed bookkeeping after a tile finishes loading.
|
|
@@ -119,6 +119,8 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
119
119
|
private readonly tileUrlCache: Map<string, string> = new Map();
|
|
120
120
|
/** Original content descriptors retained across {@link Tile3D.unloadContent} calls. */
|
|
121
121
|
private readonly tileContentHeaders = new WeakMap<Tile3D, Record<string, any>[]>();
|
|
122
|
+
/** Installed nested roots by parent placement and ordered nested-content slot, across reloads. */
|
|
123
|
+
private readonly nestedTilesetRoots = new WeakMap<Tile3D, Tile3D[]>();
|
|
122
124
|
/** Source-local namespaces assigned to distinct embedded glTF package file collections. */
|
|
123
125
|
private readonly packageNamespaces = new WeakMap<PackageFile[], number>();
|
|
124
126
|
/** Next source-local package namespace. */
|
|
@@ -521,6 +523,17 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
521
523
|
};
|
|
522
524
|
}
|
|
523
525
|
|
|
526
|
+
/** Loads an implicit subtree for dataset traversal without camera state. */
|
|
527
|
+
async loadTileChildrenForTraversal(
|
|
528
|
+
tile: Tile3D,
|
|
529
|
+
signal?: AbortSignal
|
|
530
|
+
): Promise<TileChildrenLoadResult> {
|
|
531
|
+
throwIfTraversalAborted(signal);
|
|
532
|
+
const result = await this.loadTileChildren(tile, {} as FrameState);
|
|
533
|
+
throwIfTraversalAborted(signal);
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
536
|
+
|
|
524
537
|
/**
|
|
525
538
|
* Returns a snapshot of implicit-subtree request, cache, and materialization counters.
|
|
526
539
|
*
|
|
@@ -634,7 +647,9 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
634
647
|
}
|
|
635
648
|
|
|
636
649
|
/**
|
|
637
|
-
* Updates content-format flags and installs nested tileset subtrees.
|
|
650
|
+
* Updates content-format flags and installs nested tileset subtrees once per content slot.
|
|
651
|
+
* Reloading payloads retains attached roots and their descendants, including duplicate URLs
|
|
652
|
+
* declared in separate slots or parent placements. Detached roots are reinstalled in declaration order.
|
|
638
653
|
*/
|
|
639
654
|
onTileLoaded(tileset: Tileset3D, tile: Tile3D, loadResult: TileContentLoadResult): void {
|
|
640
655
|
const contents = tile.contents.length ? tile.contents : [tile.content];
|
|
@@ -651,8 +666,26 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
651
666
|
|
|
652
667
|
const nestedTilesets =
|
|
653
668
|
loadResult.nestedTilesets || (loadResult.nestedTileset ? [loadResult.nestedTileset] : []);
|
|
654
|
-
|
|
655
|
-
|
|
669
|
+
const installedRoots = this.nestedTilesetRoots.get(tile) || [];
|
|
670
|
+
for (const [nestedIndex, nestedTileset] of nestedTilesets.entries()) {
|
|
671
|
+
const installedRoot = installedRoots[nestedIndex];
|
|
672
|
+
if (installedRoot && tile.children.includes(installedRoot)) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const nextRoot = installedRoots
|
|
676
|
+
.slice(nestedIndex + 1)
|
|
677
|
+
.find(root => tile.children.includes(root));
|
|
678
|
+
const nestedRoot = tileset._initializeTileHeaders(
|
|
679
|
+
this.prepareNestedTileset(tileset, nestedTileset),
|
|
680
|
+
tile
|
|
681
|
+
);
|
|
682
|
+
// Header installation appends roots; restore declaration order before any later retained slot.
|
|
683
|
+
if (nextRoot) {
|
|
684
|
+
tile.children.splice(tile.children.indexOf(nestedRoot), 1);
|
|
685
|
+
tile.children.splice(tile.children.indexOf(nextRoot), 0, nestedRoot);
|
|
686
|
+
}
|
|
687
|
+
installedRoots[nestedIndex] = nestedRoot;
|
|
688
|
+
this.nestedTilesetRoots.set(tile, installedRoots);
|
|
656
689
|
}
|
|
657
690
|
}
|
|
658
691
|
|
|
@@ -939,6 +972,13 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
939
972
|
}
|
|
940
973
|
}
|
|
941
974
|
|
|
975
|
+
/** Throws a cancellation reason before or after source-managed traversal work. */
|
|
976
|
+
function throwIfTraversalAborted(signal?: AbortSignal): void {
|
|
977
|
+
if (signal?.aborted) {
|
|
978
|
+
throw signal.reason ?? new DOMException('The operation was aborted', 'AbortError');
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
942
982
|
function isTilesetRequest(input: TilesetSourceInput): input is TilesetSourceRequest {
|
|
943
983
|
return Boolean(
|
|
944
984
|
input && typeof input === 'object' && 'url' in input && 'loader' in input && !('type' in input)
|
|
@@ -8,10 +8,12 @@ import {Vector3} from '@math.gl/core';
|
|
|
8
8
|
import type {CoreAPI, Loader, LoaderOptions} from '@loaders.gl/loader-utils';
|
|
9
9
|
import type {Tile3D} from '../common/tile-3d';
|
|
10
10
|
import {Tile3D as Tile3DNode} from '../common/tile-3d';
|
|
11
|
+
import type {Tile3DContent} from '../common/tile-3d-contracts';
|
|
11
12
|
import {I3STilesetTraverser} from './i3s-tileset-traverser';
|
|
12
13
|
import type {Tileset3D} from '../common/tileset-3d';
|
|
13
14
|
import type {FrameState} from '../helpers/frame-state';
|
|
14
15
|
import type {
|
|
16
|
+
TileChildrenLoadResult,
|
|
15
17
|
TileContentLoadResult,
|
|
16
18
|
TilesetContentFormats,
|
|
17
19
|
TilesetJSON,
|
|
@@ -219,7 +221,17 @@ export class I3SSource implements Tileset3DSource {
|
|
|
219
221
|
};
|
|
220
222
|
|
|
221
223
|
tile.content = await this.loadResourceData(contentUrl, options, this.contentLoader);
|
|
222
|
-
|
|
224
|
+
const contentEntry: Tile3DContent = {
|
|
225
|
+
index: 0,
|
|
226
|
+
uri: tile.contentUrl,
|
|
227
|
+
type: tile.header.layerType,
|
|
228
|
+
payload: tile.content,
|
|
229
|
+
metadata: tile.header.metadata || null,
|
|
230
|
+
boundingVolume: tile.boundingVolume,
|
|
231
|
+
featureIds: tile.featureIdSets,
|
|
232
|
+
renderable: Boolean(tile.content)
|
|
233
|
+
};
|
|
234
|
+
return {loaded: true, contents: [tile.content], contentEntries: [contentEntry]};
|
|
223
235
|
}
|
|
224
236
|
|
|
225
237
|
/**
|
|
@@ -228,7 +240,7 @@ export class I3SSource implements Tileset3DSource {
|
|
|
228
240
|
async loadChildTileHeader(
|
|
229
241
|
_parentTile: Tile3D,
|
|
230
242
|
childId: string,
|
|
231
|
-
_frameState: FrameState
|
|
243
|
+
_frameState: FrameState | null
|
|
232
244
|
): Promise<any> {
|
|
233
245
|
const metadata = this.getMetadata();
|
|
234
246
|
if (metadata.tileset.nodePages || metadata.tileset.pointNodePages) {
|
|
@@ -253,6 +265,50 @@ export class I3SSource implements Tileset3DSource {
|
|
|
253
265
|
return await this.transformTileHeader(header);
|
|
254
266
|
}
|
|
255
267
|
|
|
268
|
+
/** Loads all declared I3S child headers without camera or visibility state. */
|
|
269
|
+
async loadTileChildrenForTraversal(
|
|
270
|
+
parentTile: Tile3D,
|
|
271
|
+
signal?: AbortSignal
|
|
272
|
+
): Promise<TileChildrenLoadResult> {
|
|
273
|
+
const childHeaders = parentTile.header.children || [];
|
|
274
|
+
let loadedChildren = 0;
|
|
275
|
+
const orderedChildren: Tile3D[] = [];
|
|
276
|
+
|
|
277
|
+
for (const childHeader of childHeaders) {
|
|
278
|
+
throwIfTraversalAborted(signal);
|
|
279
|
+
const childId = childHeader.id;
|
|
280
|
+
if (typeof childId !== 'string' && typeof childId !== 'number') {
|
|
281
|
+
throw new Error('I3S child header is missing its node ID');
|
|
282
|
+
}
|
|
283
|
+
const existingChild = parentTile.children.find(
|
|
284
|
+
tile => tile.id === String(childId) || tile.header?.id === childId
|
|
285
|
+
);
|
|
286
|
+
if (existingChild) {
|
|
287
|
+
orderedChildren.push(existingChild);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const childTileHeader = await this.loadChildTileHeader(parentTile, String(childId), null);
|
|
292
|
+
throwIfTraversalAborted(signal);
|
|
293
|
+
const childTile = new Tile3DNode(
|
|
294
|
+
parentTile.tileset,
|
|
295
|
+
childTileHeader,
|
|
296
|
+
parentTile,
|
|
297
|
+
String(childId)
|
|
298
|
+
);
|
|
299
|
+
orderedChildren.push(childTile);
|
|
300
|
+
loadedChildren++;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const orderedChildSet = new Set(orderedChildren);
|
|
304
|
+
parentTile.children = [
|
|
305
|
+
...orderedChildren,
|
|
306
|
+
...parentTile.children.filter(childTile => !orderedChildSet.has(childTile))
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
return {loaded: true, tileCount: loadedChildren, childSubtreeCount: 0};
|
|
310
|
+
}
|
|
311
|
+
|
|
256
312
|
/**
|
|
257
313
|
* Resolves an I3S request URL with source-managed query parameters.
|
|
258
314
|
*/
|
|
@@ -265,24 +321,28 @@ export class I3SSource implements Tileset3DSource {
|
|
|
265
321
|
return tilePath;
|
|
266
322
|
}
|
|
267
323
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
324
|
+
const hashIndex = tilePath.indexOf('#');
|
|
325
|
+
const hash = hashIndex >= 0 ? tilePath.slice(hashIndex) : '';
|
|
326
|
+
const tilePathWithoutHash = hashIndex >= 0 ? tilePath.slice(0, hashIndex) : tilePath;
|
|
327
|
+
const queryDelimiterIndex = tilePathWithoutHash.indexOf('?');
|
|
328
|
+
const path =
|
|
329
|
+
queryDelimiterIndex === -1
|
|
330
|
+
? tilePathWithoutHash
|
|
331
|
+
: tilePathWithoutHash.slice(0, queryDelimiterIndex);
|
|
332
|
+
const existingQuery =
|
|
333
|
+
queryDelimiterIndex === -1 ? '' : tilePathWithoutHash.slice(queryDelimiterIndex + 1);
|
|
334
|
+
const existingQueryParams = new URLSearchParams(existingQuery);
|
|
277
335
|
const queryParams = new URLSearchParams();
|
|
278
336
|
for (const [key, value] of Object.entries(this.queryParams)) {
|
|
279
|
-
if (!
|
|
337
|
+
if (!existingQueryParams.has(key)) {
|
|
280
338
|
queryParams.set(key, value);
|
|
281
339
|
}
|
|
282
340
|
}
|
|
283
341
|
|
|
284
342
|
const queryString = queryParams.toString();
|
|
285
|
-
return
|
|
343
|
+
return `${path}${existingQuery ? `?${existingQuery}` : ''}${
|
|
344
|
+
queryString ? `${existingQuery ? '&' : '?'}${queryString}` : ''
|
|
345
|
+
}${hash}`;
|
|
286
346
|
}
|
|
287
347
|
|
|
288
348
|
/**
|
|
@@ -403,6 +463,10 @@ export class I3SSource implements Tileset3DSource {
|
|
|
403
463
|
* @param loadOptions Loader options that may contain an ArcGIS token.
|
|
404
464
|
*/
|
|
405
465
|
private initializeQueryParams(loadOptions: LoaderOptions): void {
|
|
466
|
+
for (const [key, value] of Object.entries(loadOptions.searchParams || {})) {
|
|
467
|
+
this.queryParams[key] = String(value);
|
|
468
|
+
}
|
|
469
|
+
|
|
406
470
|
const i3sOptions = loadOptions.i3s;
|
|
407
471
|
if (i3sOptions && typeof i3sOptions === 'object' && 'token' in i3sOptions) {
|
|
408
472
|
const token = (i3sOptions as Record<string, unknown>).token;
|
|
@@ -449,6 +513,13 @@ export class I3SSource implements Tileset3DSource {
|
|
|
449
513
|
}
|
|
450
514
|
}
|
|
451
515
|
|
|
516
|
+
/** Throws a cancellation reason before or after source-managed traversal work. */
|
|
517
|
+
function throwIfTraversalAborted(signal?: AbortSignal): void {
|
|
518
|
+
if (signal?.aborted) {
|
|
519
|
+
throw signal.reason ?? new DOMException('The operation was aborted', 'AbortError');
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
452
523
|
/** Returns the format-provided binary content loader, falling back for custom legacy loaders. */
|
|
453
524
|
function getI3SContentLoader(loader: Loader): Loader {
|
|
454
525
|
return (loader as Loader & {contentLoader?: Loader}).contentLoader || loader;
|
|
@@ -38,7 +38,8 @@ export class I3STilesetTraverser extends TilesetTraverser {
|
|
|
38
38
|
for (const child of children) {
|
|
39
39
|
const extendedId = `${child.id}-${frameState.viewport.id}`;
|
|
40
40
|
// if child tile is not fetched
|
|
41
|
-
const childTile =
|
|
41
|
+
const childTile =
|
|
42
|
+
childTiles && childTiles.find(t => t.id === extendedId || t.header?.id === child.id);
|
|
42
43
|
if (!childTile) {
|
|
43
44
|
const request = () => this._loadTile(tile, child.id, frameState);
|
|
44
45
|
const cachedRequest = this._tileManager.find(extendedId);
|