@loaders.gl/tiles 5.0.0-alpha.4 → 5.0.0-alpha.6

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.
Files changed (48) hide show
  1. package/dist/dist.dev.js +476 -1643
  2. package/dist/dist.min.js +2 -2
  3. package/dist/index.cjs +223 -11
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/tileset-2d/adapter.d.ts +2 -3
  6. package/dist/tileset-2d/adapter.d.ts.map +1 -1
  7. package/dist/tileset-2d/tileset-2d.d.ts +2 -3
  8. package/dist/tileset-2d/tileset-2d.d.ts.map +1 -1
  9. package/dist/tileset-2d/tileset-2d.js.map +1 -1
  10. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts +3 -3
  11. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts.map +1 -1
  12. package/dist/tileset-3d/common/indexed-archive-tileset-source.js.map +1 -1
  13. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
  14. package/dist/tileset-3d/common/tile-3d.js +4 -2
  15. package/dist/tileset-3d/common/tile-3d.js.map +1 -1
  16. package/dist/tileset-3d/common/tileset-3d.d.ts +30 -2
  17. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  18. package/dist/tileset-3d/common/tileset-3d.js +4 -0
  19. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  20. package/dist/tileset-3d/common/tileset-source.d.ts +8 -8
  21. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
  22. package/dist/tileset-3d/common/tileset-source.js.map +1 -1
  23. package/dist/tileset-3d/common/tileset-traverser.d.ts +14 -0
  24. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
  25. package/dist/tileset-3d/common/tileset-traverser.js +17 -2
  26. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
  27. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +2 -2
  28. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
  29. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
  30. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +65 -0
  31. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
  32. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +209 -0
  33. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
  34. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +5 -3
  35. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
  36. package/dist/tileset-3d/format-i3s/i3s-source.js +14 -7
  37. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
  38. package/package.json +13 -13
  39. package/src/tileset-2d/adapter.ts +2 -3
  40. package/src/tileset-2d/tileset-2d.ts +2 -3
  41. package/src/tileset-3d/common/indexed-archive-tileset-source.ts +4 -9
  42. package/src/tileset-3d/common/tile-3d.ts +6 -2
  43. package/src/tileset-3d/common/tileset-3d.ts +34 -2
  44. package/src/tileset-3d/common/tileset-source.ts +8 -12
  45. package/src/tileset-3d/common/tileset-traverser.ts +28 -2
  46. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +4 -4
  47. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +236 -0
  48. package/src/tileset-3d/format-i3s/i3s-source.ts +25 -9
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {CoreAPI, LoaderOptions, LoaderWithParser} from '@loaders.gl/loader-utils';
5
+ import type {CoreAPI, Loader, LoaderOptions} from '@loaders.gl/loader-utils';
6
6
  import type {Vector3} from '@math.gl/core';
7
7
  import type {TilesetTraverser, TilesetTraverserProps} from './tileset-traverser';
8
8
  import type {Tile3D} from './tile-3d';
@@ -24,18 +24,14 @@ export type TilesetSourceResolver = {
24
24
  /**
25
25
  * Loads and parses the root metadata resource for a source.
26
26
  */
27
- loadRoot<DataT>(
28
- url: string,
29
- loader: LoaderWithParser<DataT>,
30
- loadOptions: LoaderOptions
31
- ): Promise<DataT>;
27
+ loadRoot<DataT>(url: string, loader: Loader<DataT>, loadOptions: LoaderOptions): Promise<DataT>;
32
28
 
33
29
  /**
34
30
  * Loads and parses an arbitrary source-relative resource.
35
31
  */
36
32
  loadResource<DataT>(
37
33
  url: string,
38
- loader: LoaderWithParser<DataT>,
34
+ loader: Loader<DataT>,
39
35
  loadOptions: LoaderOptions
40
36
  ): Promise<DataT>;
41
37
  };
@@ -47,7 +43,7 @@ export type TilesetSourceRequest = {
47
43
  /** Absolute root metadata URL. */
48
44
  url: string;
49
45
  /** Loader used to fetch and parse the root metadata. */
50
- loader: LoaderWithParser;
46
+ loader: Loader;
51
47
  /** Optional base path override for relative resources. */
52
48
  basePath?: string;
53
49
  /** Optional resource-loading strategy used for archive-backed datasets. */
@@ -119,8 +115,8 @@ export type TilesetSourceViewState = {
119
115
  export type TilesetSourceMetadata = {
120
116
  /** Format discriminator. */
121
117
  type: TILESET_TYPE;
122
- /** Loader used for metadata and content requests. */
123
- loader: LoaderWithParser;
118
+ /** Primary format loader used for metadata and default content requests. */
119
+ loader: Loader;
124
120
  /** Absolute root metadata URL. */
125
121
  url: string;
126
122
  /** Base path used for relative resource resolution. */
@@ -143,8 +139,8 @@ export type TilesetSourceMetadata = {
143
139
  export interface Tileset3DSource {
144
140
  /** Tileset format discriminator. */
145
141
  readonly type: TILESET_TYPE;
146
- /** Loader used for content and metadata requests. */
147
- readonly loader: LoaderWithParser;
142
+ /** Primary format loader used for metadata and default content requests. */
143
+ readonly loader: Loader;
148
144
  /** Absolute URL of the root tileset resource. */
149
145
  readonly url: string;
150
146
  /** Base directory used for relative resource resolution. */
@@ -10,6 +10,14 @@ import {FrameState} from '../helpers/frame-state';
10
10
  export type TilesetTraverserProps = {
11
11
  loadSiblings?: boolean;
12
12
  skipLevelOfDetail?: boolean;
13
+ /** SSE above which tiles remain part of the non-skipping base traversal. */
14
+ baseScreenSpaceError?: number;
15
+ /** Required SSE reduction from the nearest requested or loaded ancestor before loading a tile. */
16
+ skipScreenSpaceErrorFactor?: number;
17
+ /** Minimum hierarchy levels between requested tiles during skip-LOD traversal. */
18
+ skipLevels?: number;
19
+ /** Requests only final tiles that meet the active SSE target when skip-LOD is enabled. */
20
+ immediatelyLoadDesiredLevelOfDetail?: boolean;
13
21
  updateTransforms?: boolean;
14
22
  onTraversalEnd?: (frameState) => any;
15
23
  viewportTraversersMap?: Record<string, any>;
@@ -19,6 +27,10 @@ export type TilesetTraverserProps = {
19
27
  export const DEFAULT_PROPS: Required<TilesetTraverserProps> = {
20
28
  loadSiblings: false,
21
29
  skipLevelOfDetail: false,
30
+ baseScreenSpaceError: 1024,
31
+ skipScreenSpaceErrorFactor: 16,
32
+ skipLevels: 1,
33
+ immediatelyLoadDesiredLevelOfDetail: false,
22
34
  updateTransforms: true,
23
35
  onTraversalEnd: () => {},
24
36
  viewportTraversersMap: {},
@@ -28,6 +40,9 @@ export const DEFAULT_PROPS: Required<TilesetTraverserProps> = {
28
40
  export class TilesetTraverser {
29
41
  options: Required<TilesetTraverserProps>;
30
42
 
43
+ /** Whether loaded content has disabled skip-LOD traversal for this tileset. */
44
+ disableSkipLevelOfDetail: boolean = false;
45
+
31
46
  // fulfill in traverse call
32
47
  root: any = null;
33
48
 
@@ -58,6 +73,11 @@ export class TilesetTraverser {
58
73
  this.options = {...DEFAULT_PROPS, ...options};
59
74
  }
60
75
 
76
+ /** Returns whether skip-LOD traversal is enabled and supported by the loaded content. */
77
+ isSkipLevelOfDetailEnabled(): boolean {
78
+ return this.options.skipLevelOfDetail && !this.disableSkipLevelOfDetail;
79
+ }
80
+
61
81
  // tiles should be visible
62
82
  traverse(root, frameState, options) {
63
83
  this.root = root; // for root screen space error
@@ -142,7 +162,7 @@ export class TilesetTraverser {
142
162
  this.loadTile(tile, frameState);
143
163
  // Skip-LOD keeps a ready ancestor selected while descendants stream in. This prevents
144
164
  // replacement holes when traversal jumps over one or more hierarchy levels.
145
- if (stoppedRefining || this.options.skipLevelOfDetail) {
165
+ if (stoppedRefining || this.isSkipLevelOfDetailEnabled()) {
146
166
  this.selectTile(tile, frameState);
147
167
  }
148
168
  }
@@ -154,6 +174,11 @@ export class TilesetTraverser {
154
174
  tile._shouldRefine = shouldRefine && parentRefines;
155
175
  }
156
176
 
177
+ this.completeTraversal(frameState);
178
+ }
179
+
180
+ /** Notifies the owning tileset when traversal is complete or its debounce interval expires. */
181
+ protected completeTraversal(frameState: FrameState): void {
157
182
  const newTime = new Date().getTime();
158
183
  if (this.traversalFinished(frameState) || newTime - this.lastUpdate > this.updateDebounceTime) {
159
184
  this.lastUpdate = newTime;
@@ -170,7 +195,8 @@ export class TilesetTraverser {
170
195
 
171
196
  /* eslint-disable complexity, max-statements */
172
197
  updateAndPushChildren(tile: Tile3D, frameState: FrameState, stack, depth): boolean {
173
- const {loadSiblings, skipLevelOfDetail} = this.options;
198
+ const {loadSiblings} = this.options;
199
+ const skipLevelOfDetail = this.isSkipLevelOfDetailEnabled();
174
200
 
175
201
  const children = tile.children;
176
202
 
@@ -5,7 +5,7 @@
5
5
  import {path, RequestCache} from '@loaders.gl/loader-utils';
6
6
  import {Ellipsoid} from '@math.gl/geospatial';
7
7
  import {Vector3} from '@math.gl/core';
8
- import type {CoreAPI, LoaderOptions, LoaderWithParser} from '@loaders.gl/loader-utils';
8
+ import type {CoreAPI, Loader, LoaderOptions} from '@loaders.gl/loader-utils';
9
9
  import type {Tile3D} from '../common/tile-3d';
10
10
  import {Tileset3DTraverser} from './tileset-3d-traverser';
11
11
  import type {Tileset3D} from '../common/tileset-3d';
@@ -65,7 +65,7 @@ export class Tiles3DSource implements Tileset3DSource {
65
65
  /** 3D Tiles format discriminator. */
66
66
  readonly type = TILESET_TYPE.TILES3D;
67
67
  /** Loader used for tile content requests. */
68
- readonly loader: LoaderWithParser;
68
+ readonly loader: Loader;
69
69
  /** Root tileset URL. */
70
70
  readonly url: string;
71
71
  /** Base path used for relative tile resource resolution. */
@@ -487,7 +487,7 @@ export class Tiles3DSource implements Tileset3DSource {
487
487
  private async loadWithCoreApi(
488
488
  url: string,
489
489
  options: LoaderOptions,
490
- loader: LoaderWithParser = this.loader
490
+ loader: Loader = this.loader
491
491
  ): Promise<any> {
492
492
  if (!this.coreApi) {
493
493
  throw new Error('Tiles3DSource requires an injected coreApi to load tileset data');
@@ -513,7 +513,7 @@ export class Tiles3DSource implements Tileset3DSource {
513
513
  private async loadResourceData(
514
514
  url: string,
515
515
  options: LoaderOptions,
516
- loader: LoaderWithParser = this.loader
516
+ loader: Loader = this.loader
517
517
  ): Promise<any> {
518
518
  if (this.resolver) {
519
519
  return await this.resolver.loadResource(url, loader, options);
@@ -30,6 +30,242 @@ export class Tileset3DTraverser extends TilesetTraverser {
30
30
  return true;
31
31
  }
32
32
 
33
+ /**
34
+ * Runs normal replacement traversal or the dedicated skip-LOD traversal selected by options.
35
+ *
36
+ * @param root - Root of the runtime subtree to traverse.
37
+ * @param frameState - Current culling and LOD state.
38
+ */
39
+ executeTraversal(root: Tile3D, frameState: FrameState): void {
40
+ if (!this.isSkipLevelOfDetailEnabled()) {
41
+ super.executeTraversal(root, frameState);
42
+ return;
43
+ }
44
+ this.executeSkipTraversal(root, frameState);
45
+ }
46
+
47
+ /**
48
+ * Traverses visible replacement branches without requesting skipped intermediate content.
49
+ *
50
+ * Base-traversal tiles establish coarse coverage. Below that base, only final desired tiles and
51
+ * tiles that cross the configured depth/SSE thresholds are requested. Already available
52
+ * ancestors remain selectable while those requests stream in.
53
+ *
54
+ * @param root - Root of the runtime subtree to traverse.
55
+ * @param frameState - Current culling and LOD state.
56
+ */
57
+ private executeSkipTraversal(root: Tile3D, frameState: FrameState): void {
58
+ const stack: Tile3D[] = [root];
59
+ root._selectionDepth = root.parent ? root.parent._selectionDepth : 1;
60
+
61
+ while (stack.length > 0) {
62
+ const tile = stack.pop() as Tile3D;
63
+ const parentRefines = !tile.parent || tile.parent._shouldRefine;
64
+ const shouldRefine =
65
+ this.canTraverse(tile, frameState) &&
66
+ this.updateAndPushSkipChildren(tile, frameState, stack) &&
67
+ parentRefines;
68
+ const stoppedRefining = !shouldRefine && parentRefines;
69
+
70
+ if (!tile.hasRenderContent) {
71
+ this.emptyTiles[tile.id] = tile;
72
+ this.loadTile(tile, frameState);
73
+ if (stoppedRefining) {
74
+ this.selectDesiredTile(tile, frameState);
75
+ }
76
+ } else if (tile.refine === TILE_REFINEMENT.ADD) {
77
+ this.loadTile(tile, frameState);
78
+ this.selectDesiredTile(tile, frameState);
79
+ } else if (tile.refine === TILE_REFINEMENT.REPLACE) {
80
+ if (this.isInBaseTraversal(tile, frameState)) {
81
+ this.loadTile(tile, frameState);
82
+ if (stoppedRefining) {
83
+ this.selectDesiredTile(tile, frameState);
84
+ }
85
+ } else if (stoppedRefining) {
86
+ this.loadTile(tile, frameState);
87
+ this.selectDesiredTile(tile, frameState);
88
+ } else if (this.hasReachedSkippingThreshold(tile, frameState)) {
89
+ this.loadTile(tile, frameState);
90
+ }
91
+ }
92
+
93
+ this.touchTile(tile, frameState);
94
+ tile._shouldRefine = shouldRefine;
95
+ }
96
+
97
+ this.completeTraversal(frameState);
98
+ }
99
+
100
+ /**
101
+ * Updates and pushes visible children for skip traversal without requiring sibling readiness.
102
+ *
103
+ * @param tile - Parent whose children are considered.
104
+ * @param frameState - Current culling and LOD state.
105
+ * @param stack - Depth-first traversal stack.
106
+ * @returns `true` when at least one visible child continues refinement.
107
+ */
108
+ private updateAndPushSkipChildren(
109
+ tile: Tile3D,
110
+ frameState: FrameState,
111
+ stack: Tile3D[]
112
+ ): boolean {
113
+ this.updateChildTiles(tile, frameState);
114
+ tile.children.sort(this.compareDistanceToCamera.bind(this));
115
+
116
+ let hasVisibleChild = false;
117
+ for (const child of tile.children) {
118
+ child._selectionDepth = tile.hasRenderContent
119
+ ? tile._selectionDepth + 1
120
+ : tile._selectionDepth;
121
+ if (child.isVisibleAndInRequestVolume) {
122
+ stack.push(child);
123
+ hasVisibleChild = true;
124
+ } else if (this.options.loadSiblings) {
125
+ this.loadTile(child, frameState);
126
+ this.touchTile(child, frameState);
127
+ }
128
+ }
129
+ return hasVisibleChild;
130
+ }
131
+
132
+ /**
133
+ * Determines whether a tile belongs to the coarse non-skipping portion of the traversal.
134
+ *
135
+ * @param tile - Tile considered for loading.
136
+ * @param frameState - Current traversal frame.
137
+ * @returns `true` when the tile should always be requested for fallback coverage.
138
+ */
139
+ private isInBaseTraversal(tile: Tile3D, frameState: FrameState): boolean {
140
+ if (this.options.immediatelyLoadDesiredLevelOfDetail) {
141
+ return false;
142
+ }
143
+ if (!this.findNearestRequestedOrLoadedAncestor(tile, frameState)) {
144
+ return true;
145
+ }
146
+
147
+ const configuredBaseScreenSpaceError = Number.isFinite(this.options.baseScreenSpaceError)
148
+ ? Math.max(this.options.baseScreenSpaceError, 0)
149
+ : 1024;
150
+ const baseScreenSpaceError = Math.max(
151
+ configuredBaseScreenSpaceError,
152
+ tile.tileset.memoryAdjustedScreenSpaceError
153
+ );
154
+ if (tile._screenSpaceError === 0 && tile.parent) {
155
+ return tile.parent._screenSpaceError > baseScreenSpaceError;
156
+ }
157
+ return tile._screenSpaceError > baseScreenSpaceError;
158
+ }
159
+
160
+ /**
161
+ * Determines whether a skipped branch has crossed its configured request threshold.
162
+ *
163
+ * @param tile - Tile considered for loading.
164
+ * @param frameState - Current traversal frame.
165
+ * @returns `true` when the tile must be requested instead of skipped.
166
+ */
167
+ private hasReachedSkippingThreshold(tile: Tile3D, frameState: FrameState): boolean {
168
+ if (this.options.immediatelyLoadDesiredLevelOfDetail) {
169
+ return false;
170
+ }
171
+ const progressiveResolutionLeaf =
172
+ tile._priorityProgressiveResolution &&
173
+ tile._screenSpaceErrorProgressiveResolution <= tile.tileset.memoryAdjustedScreenSpaceError &&
174
+ Boolean(
175
+ tile.parent &&
176
+ tile.parent._screenSpaceErrorProgressiveResolution >
177
+ tile.tileset.memoryAdjustedScreenSpaceError
178
+ );
179
+ if (progressiveResolutionLeaf) {
180
+ return true;
181
+ }
182
+
183
+ const ancestor = this.findNearestRequestedOrLoadedAncestor(tile, frameState);
184
+ if (!ancestor) {
185
+ return false;
186
+ }
187
+ const skipScreenSpaceErrorFactor =
188
+ Number.isFinite(this.options.skipScreenSpaceErrorFactor) &&
189
+ this.options.skipScreenSpaceErrorFactor > 0
190
+ ? this.options.skipScreenSpaceErrorFactor
191
+ : 16;
192
+ const skipLevels = Number.isFinite(this.options.skipLevels)
193
+ ? Math.max(Math.floor(this.options.skipLevels), 0)
194
+ : 1;
195
+ return (
196
+ tile._screenSpaceError < ancestor._screenSpaceError / skipScreenSpaceErrorFactor &&
197
+ tile.depth > ancestor.depth + skipLevels
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Finds the nearest ancestor whose render content is loaded, loading, or requested this frame.
203
+ *
204
+ * @param tile - Descendant whose request threshold needs an anchor.
205
+ * @param frameState - Current traversal frame.
206
+ * @returns The nearest request anchor, or `null` when none exists.
207
+ */
208
+ private findNearestRequestedOrLoadedAncestor(
209
+ tile: Tile3D,
210
+ frameState: FrameState
211
+ ): Tile3D | null {
212
+ let ancestor = tile.parent;
213
+ while (ancestor) {
214
+ const contentIsUsableAnchor =
215
+ ancestor.hasRenderContent &&
216
+ !ancestor.contentFailed &&
217
+ (!ancestor.hasUnloadedContent || ancestor._requestedFrame === frameState.frameNumber);
218
+ if (contentIsUsableAnchor) {
219
+ return ancestor;
220
+ }
221
+ ancestor = ancestor.parent;
222
+ }
223
+ return null;
224
+ }
225
+
226
+ /**
227
+ * Selects the desired tile, its nearest ready ancestor, or nearby ready descendants.
228
+ *
229
+ * @param tile - Tile that ended the desired refinement branch.
230
+ * @param frameState - Current culling state.
231
+ */
232
+ private selectDesiredTile(tile: Tile3D, frameState: FrameState): void {
233
+ let fallbackTile: Tile3D | null = tile;
234
+ while (fallbackTile) {
235
+ if (this.shouldSelectTile(fallbackTile, frameState)) {
236
+ this.selectTile(fallbackTile, frameState);
237
+ return;
238
+ }
239
+ fallbackTile = fallbackTile.parent;
240
+ }
241
+ this.selectLoadedDescendants(tile, frameState);
242
+ }
243
+
244
+ /**
245
+ * Selects ready descendants near an unavailable desired tile to reduce temporary empty regions.
246
+ *
247
+ * @param root - Unavailable tile whose descendants are searched.
248
+ * @param frameState - Current culling state.
249
+ */
250
+ private selectLoadedDescendants(root: Tile3D, frameState: FrameState): void {
251
+ const stack: Array<{tile: Tile3D; depth: number}> = [{tile: root, depth: 0}];
252
+ while (stack.length > 0) {
253
+ const {tile, depth} = stack.pop() as {tile: Tile3D; depth: number};
254
+ for (const child of tile.children) {
255
+ this.updateTile(child, frameState);
256
+ if (!child.isVisibleAndInRequestVolume) {
257
+ continue;
258
+ }
259
+ if (child.contentAvailable) {
260
+ this.selectTile(child, frameState);
261
+ this.touchTile(child, frameState);
262
+ } else if (depth < 1) {
263
+ stack.push({tile: child, depth: depth + 1});
264
+ }
265
+ }
266
+ }
267
+ }
268
+
33
269
  compareDistanceToCamera(a, b) {
34
270
  // Sort by farthest child first since this is going on a stack
35
271
  return b._distanceToCamera === 0 && a._distanceToCamera === 0
@@ -5,7 +5,7 @@
5
5
  import {path} from '@loaders.gl/loader-utils';
6
6
  import {Ellipsoid} from '@math.gl/geospatial';
7
7
  import {Vector3} from '@math.gl/core';
8
- import type {CoreAPI, LoaderOptions, LoaderWithParser} from '@loaders.gl/loader-utils';
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
11
  import {I3STilesetTraverser} from './i3s-tileset-traverser';
@@ -41,8 +41,10 @@ const EMPTY_CONTENT_FORMATS: TilesetContentFormats = {
41
41
  export class I3SSource implements Tileset3DSource {
42
42
  /** I3S format discriminator. */
43
43
  readonly type = TILESET_TYPE.I3S;
44
- /** Loader used for tile metadata and content requests. */
45
- readonly loader: LoaderWithParser;
44
+ /** Primary I3S loader used for root and child metadata requests. */
45
+ readonly loader: Loader;
46
+ /** Worker-capable loader used for binary tile content when supplied by the format loader. */
47
+ readonly contentLoader: Loader;
46
48
  /** Root I3S layer URL. */
47
49
  readonly url: string;
48
50
  /** Base path used for relative tile resource resolution. */
@@ -80,6 +82,7 @@ export class I3SSource implements Tileset3DSource {
80
82
  this.rootTileset = isTilesetRequest(input) ? null : input;
81
83
  this.tileset = this.rootTileset;
82
84
  this.loader = request.loader;
85
+ this.contentLoader = getI3SContentLoader(request.loader);
83
86
  this.url = request.url;
84
87
  this.basePath = request.basePath || path.dirname(request.url);
85
88
  this.resolver = request.resolver;
@@ -215,7 +218,7 @@ export class I3SSource implements Tileset3DSource {
215
218
  }
216
219
  };
217
220
 
218
- tile.content = await this.loadResourceData(contentUrl, options);
221
+ tile.content = await this.loadResourceData(contentUrl, options, this.contentLoader);
219
222
  return {loaded: true};
220
223
  }
221
224
 
@@ -372,12 +375,16 @@ export class I3SSource implements Tileset3DSource {
372
375
  /**
373
376
  * Loads data through injected core APIs so this module stays independent from `@loaders.gl/core`.
374
377
  */
375
- private async loadWithCoreApi(url: string, options: LoaderOptions): Promise<any> {
378
+ private async loadWithCoreApi(
379
+ url: string,
380
+ options: LoaderOptions,
381
+ loader: Loader = this.loader
382
+ ): Promise<any> {
376
383
  if (!this.coreApi) {
377
384
  throw new Error('I3SSource requires an injected coreApi to load tileset data');
378
385
  }
379
386
 
380
- return await this.coreApi.load(url, this.loader, options);
387
+ return await this.coreApi.load(url, loader, options);
381
388
  }
382
389
 
383
390
  /**
@@ -408,12 +415,16 @@ export class I3SSource implements Tileset3DSource {
408
415
  /**
409
416
  * Loads tile metadata or content through an injected resolver when present, otherwise through the injected core API.
410
417
  */
411
- private async loadResourceData(url: string, options: LoaderOptions): Promise<any> {
418
+ private async loadResourceData(
419
+ url: string,
420
+ options: LoaderOptions,
421
+ loader: Loader = this.loader
422
+ ): Promise<any> {
412
423
  if (this.resolver) {
413
- return await this.resolver.loadResource(url, this.loader, options);
424
+ return await this.resolver.loadResource(url, loader, options);
414
425
  }
415
426
 
416
- return await this.loadWithCoreApi(url, options);
427
+ return await this.loadWithCoreApi(url, options, loader);
417
428
  }
418
429
 
419
430
  /** Transform one I3S header bound after target options and elevation providers are available. */
@@ -438,6 +449,11 @@ export class I3SSource implements Tileset3DSource {
438
449
  }
439
450
  }
440
451
 
452
+ /** Returns the format-provided binary content loader, falling back for custom legacy loaders. */
453
+ function getI3SContentLoader(loader: Loader): Loader {
454
+ return (loader as Loader & {contentLoader?: Loader}).contentLoader || loader;
455
+ }
456
+
441
457
  function isTilesetRequest(input: TilesetSourceInput): input is TilesetSourceRequest {
442
458
  return Boolean(
443
459
  input && typeof input === 'object' && 'url' in input && 'loader' in input && !('type' in input)