@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.
Files changed (58) hide show
  1. package/README.md +26 -0
  2. package/dist/dist.dev.js +6782 -6939
  3. package/dist/dist.min.js +3 -6
  4. package/dist/index.cjs +235 -75
  5. package/dist/index.cjs.map +3 -3
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -1
  11. package/dist/spatial/spatial-coordinate-transformer.js +18 -11
  12. package/dist/spatial/spatial-coordinate-transformer.js.map +1 -1
  13. package/dist/spatial/spatial-resource-registry.d.ts +4 -4
  14. package/dist/spatial/spatial-resource-registry.d.ts.map +1 -1
  15. package/dist/spatial/spatial-resource-registry.js +5 -5
  16. package/dist/spatial/spatial-resource-registry.js.map +1 -1
  17. package/dist/tileset-3d/common/tile-3d.d.ts +4 -0
  18. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
  19. package/dist/tileset-3d/common/tile-3d.js +35 -14
  20. package/dist/tileset-3d/common/tile-3d.js.map +1 -1
  21. package/dist/tileset-3d/common/tileset-3d.d.ts +1 -25
  22. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  23. package/dist/tileset-3d/common/tileset-3d.js +8 -41
  24. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  25. package/dist/tileset-3d/common/tileset-content-traversal.d.ts +37 -0
  26. package/dist/tileset-3d/common/tileset-content-traversal.d.ts.map +1 -0
  27. package/dist/tileset-3d/common/tileset-content-traversal.js +80 -0
  28. package/dist/tileset-3d/common/tileset-content-traversal.js.map +1 -0
  29. package/dist/tileset-3d/common/tileset-source.d.ts +8 -1
  30. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
  31. package/dist/tileset-3d/common/tileset-source.js.map +1 -1
  32. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +7 -1
  33. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
  34. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +35 -3
  35. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
  36. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +4 -2
  37. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
  38. package/dist/tileset-3d/format-i3s/i3s-source.js +60 -9
  39. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
  40. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
  41. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +1 -1
  42. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
  43. package/dist/tileset-3d/helpers/frame-state.d.ts +1 -0
  44. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
  45. package/dist/tileset-3d/helpers/frame-state.js +5 -4
  46. package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
  47. package/package.json +11 -13
  48. package/src/index.ts +5 -0
  49. package/src/spatial/spatial-coordinate-transformer.ts +24 -15
  50. package/src/spatial/spatial-resource-registry.ts +7 -7
  51. package/src/tileset-3d/common/tile-3d.ts +39 -17
  52. package/src/tileset-3d/common/tileset-3d.ts +8 -60
  53. package/src/tileset-3d/common/tileset-content-traversal.ts +114 -0
  54. package/src/tileset-3d/common/tileset-source.ts +16 -1
  55. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +43 -3
  56. package/src/tileset-3d/format-i3s/i3s-source.ts +84 -13
  57. package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +2 -1
  58. package/src/tileset-3d/helpers/frame-state.ts +9 -6
package/dist/index.cjs CHANGED
@@ -64,7 +64,8 @@ __export(index_exports, {
64
64
  registerGeoidModelFromPgm: () => registerGeoidModelFromPgm,
65
65
  registerSpatialCrs: () => registerSpatialCrs,
66
66
  registerSpatialDatumGrid: () => registerSpatialDatumGrid,
67
- replaceImplicitUrlTemplate: () => replaceImplicitUrlTemplate
67
+ replaceImplicitUrlTemplate: () => replaceImplicitUrlTemplate,
68
+ traverseTilesetContents: () => traverseTilesetContents
68
69
  });
69
70
  module.exports = __toCommonJS(index_exports);
70
71
 
@@ -113,6 +114,7 @@ var RequestScheduler = class {
113
114
  /** Tracks the number of active requests and prioritizes/cancels queued requests. */
114
115
  requestQueue = [];
115
116
  requestMap = /* @__PURE__ */ new Map();
117
+ /** Pending arrival debounce or next-tick refill; only new arrivals replace this timer. */
116
118
  updateTimer = null;
117
119
  constructor(props = {}) {
118
120
  this.props = { ...DEFAULT_PROPS, ...props };
@@ -180,20 +182,27 @@ var RequestScheduler = class {
180
182
  isDone = true;
181
183
  this.requestMap.delete(handle);
182
184
  this.activeRequestCount--;
183
- this._issueNewRequests();
185
+ this.scheduleRefill();
184
186
  }
185
187
  };
186
188
  this.activeRequestCount++;
187
189
  return resolve2 ? resolve2({ done }) : Promise.resolve({ done });
188
190
  }
189
- /** We check requests asynchronously, to prevent multiple updates */
191
+ /** Refills freed slots on the next tick while preserving any pending arrival debounce. */
192
+ scheduleRefill() {
193
+ if (this.updateTimer !== null) {
194
+ return;
195
+ }
196
+ this.updateTimer = setTimeout(() => this._issueNewRequestsAsync(), 0);
197
+ }
198
+ /** Restarts the quiet period when a new request enters the queue. */
190
199
  _issueNewRequests() {
191
200
  if (this.updateTimer !== null) {
192
201
  clearTimeout(this.updateTimer);
193
202
  }
194
203
  this.updateTimer = setTimeout(() => this._issueNewRequestsAsync(), this.props.debounceTime);
195
204
  }
196
- /** Refresh all requests */
205
+ /** Clears timer ownership before updating priorities and dispatching queued requests. */
197
206
  _issueNewRequestsAsync() {
198
207
  if (this.updateTimer !== null) {
199
208
  clearTimeout(this.updateTimer);
@@ -495,6 +504,7 @@ var RangeRequestScheduler = class {
495
504
  /** Optional event callback for range batching diagnostics. */
496
505
  onEvent;
497
506
  pendingRequests = [];
507
+ /** Pending batch window, independent of in-flight transport requests. */
498
508
  flushTimer = null;
499
509
  /** Creates a scheduler for one group of byte-range-addressable resources. */
500
510
  constructor(props = {}) {
@@ -571,7 +581,7 @@ var RangeRequestScheduler = class {
571
581
  }
572
582
  /** Immediately starts loading the currently queued requests. */
573
583
  flush() {
574
- if (this.flushTimer) {
584
+ if (this.flushTimer !== null) {
575
585
  clearTimeout(this.flushTimer);
576
586
  this.flushTimer = null;
577
587
  }
@@ -593,9 +603,9 @@ var RangeRequestScheduler = class {
593
603
  this.fetchMergedRequest(mergedRequest);
594
604
  }
595
605
  }
596
- /** Schedules the next pending-request flush. */
606
+ /** Starts a fixed batch window only when no flush is already scheduled. */
597
607
  scheduleFlush() {
598
- if (this.flushTimer) {
608
+ if (this.flushTimer !== null) {
599
609
  return;
600
610
  }
601
611
  this.flushTimer = setTimeout(() => this.flush(), this.props.batchDelayMs);
@@ -1921,13 +1931,14 @@ function getFrameState(viewport, frameNumber, options = {}) {
1921
1931
  position,
1922
1932
  pitch: 0
1923
1933
  });
1934
+ const verticalFieldOfView = Number.isFinite(viewport.fovy) && Number(viewport.fovy) > 0 && Number(viewport.fovy) < 180 ? Number(viewport.fovy) * Math.PI / 180 : Math.PI / 3;
1924
1935
  return {
1925
1936
  camera: {
1926
1937
  position: cameraPositionCartesian2,
1927
1938
  direction: cameraDirectionCartesian,
1928
1939
  up: cameraUpCartesian,
1929
1940
  cartographicPosition: cameraPositionCartographic,
1930
- verticalFieldOfView: Number.isFinite(viewport.fovy) && Number(viewport.fovy) > 0 ? Number(viewport.fovy) * Math.PI / 180 : Math.PI / 3,
1941
+ verticalFieldOfView,
1931
1942
  timeSinceMovement: options.timeSinceCameraMovement ?? Number.POSITIVE_INFINITY
1932
1943
  },
1933
1944
  viewport,
@@ -1937,8 +1948,7 @@ function getFrameState(viewport, frameNumber, options = {}) {
1937
1948
  clippingPlanes: viewport.clippingPlanes,
1938
1949
  frameNumber,
1939
1950
  // TODO: This can be the same between updates, what number is unique for between updates?
1940
- sseDenominator: 1.15,
1941
- // Assumes fovy = 60 degrees
1951
+ sseDenominator: 2 * Math.tan(verticalFieldOfView / 2),
1942
1952
  // Tileset3D fills this immediately before traversal because it depends on the root volume.
1943
1953
  dynamicScreenSpaceErrorDensity: 0
1944
1954
  };
@@ -2543,11 +2553,11 @@ function validateCacheByteLength(value, optionName) {
2543
2553
  return value;
2544
2554
  }
2545
2555
  function resolveCacheBytes(options, fallbackCacheBytes) {
2546
- const cacheBytes = options.cacheBytes ?? (options.maximumMemoryUsage !== void 0 ? options.maximumMemoryUsage * BYTES_PER_MEBIBYTE : fallbackCacheBytes);
2556
+ const cacheBytes = options.cacheBytes ?? fallbackCacheBytes;
2547
2557
  return validateCacheByteLength(cacheBytes, "cacheBytes");
2548
2558
  }
2549
2559
  function resolveMaximumCacheOverflowBytes(options, fallbackOverflowBytes) {
2550
- const maximumCacheOverflowBytes = options.maximumCacheOverflowBytes ?? (options.memoryCacheOverflow !== void 0 ? options.memoryCacheOverflow * BYTES_PER_MEBIBYTE : fallbackOverflowBytes);
2560
+ const maximumCacheOverflowBytes = options.maximumCacheOverflowBytes ?? fallbackOverflowBytes;
2551
2561
  return validateCacheByteLength(maximumCacheOverflowBytes, "maximumCacheOverflowBytes");
2552
2562
  }
2553
2563
  var DEFAULT_PROPS3 = {
@@ -2558,8 +2568,6 @@ var DEFAULT_PROPS3 = {
2558
2568
  maxRequests: 64,
2559
2569
  cacheBytes: DEFAULT_CACHE_BYTES,
2560
2570
  maximumCacheOverflowBytes: DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES,
2561
- maximumMemoryUsage: DEFAULT_CACHE_BYTES / BYTES_PER_MEBIBYTE,
2562
- memoryCacheOverflow: DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES / BYTES_PER_MEBIBYTE,
2563
2571
  maximumTilesSelected: 0,
2564
2572
  debounceTime: 0,
2565
2573
  onTileLoad: () => {
@@ -2703,8 +2711,6 @@ var Tileset3D = class {
2703
2711
  ...suppliedOptions,
2704
2712
  cacheBytes,
2705
2713
  maximumCacheOverflowBytes,
2706
- maximumMemoryUsage: cacheBytes / BYTES_PER_MEBIBYTE,
2707
- memoryCacheOverflow: maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE,
2708
2714
  memoryAdjustedScreenSpaceError: suppliedOptions.memoryAdjustedScreenSpaceError ?? usesTiles3DCacheDefaults
2709
2715
  };
2710
2716
  this.loadOptions = this.options.loadOptions || {};
@@ -2783,7 +2789,6 @@ var Tileset3D = class {
2783
2789
  set cacheBytes(value) {
2784
2790
  this._cacheBytes = validateCacheByteLength(value, "cacheBytes");
2785
2791
  this.options.cacheBytes = this._cacheBytes;
2786
- this.options.maximumMemoryUsage = this._cacheBytes / BYTES_PER_MEBIBYTE;
2787
2792
  }
2788
2793
  /**
2789
2794
  * Gets or sets the additional current-frame headroom, in bytes, before memory-adjusted SSE rises.
@@ -2798,17 +2803,6 @@ var Tileset3D = class {
2798
2803
  set maximumCacheOverflowBytes(value) {
2799
2804
  this._maximumCacheOverflowBytes = validateCacheByteLength(value, "maximumCacheOverflowBytes");
2800
2805
  this.options.maximumCacheOverflowBytes = this._maximumCacheOverflowBytes;
2801
- this.options.memoryCacheOverflow = this._maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE;
2802
- }
2803
- /**
2804
- * Gets or sets the soft cache target in mebibytes.
2805
- * @deprecated Use {@link Tileset3D.cacheBytes}. Assignments remain synchronized for compatibility.
2806
- */
2807
- get maximumMemoryUsage() {
2808
- return this.cacheBytes / BYTES_PER_MEBIBYTE;
2809
- }
2810
- set maximumMemoryUsage(value) {
2811
- this.cacheBytes = validateCacheByteLength(value * BYTES_PER_MEBIBYTE, "maximumMemoryUsage");
2812
2806
  }
2813
2807
  get queryParams() {
2814
2808
  const rootUrl = this.source.getTileUrl(this.url);
@@ -2816,10 +2810,7 @@ var Tileset3D = class {
2816
2810
  return queryIndex >= 0 ? rootUrl.slice(queryIndex + 1) : "";
2817
2811
  }
2818
2812
  /**
2819
- * Updates traversal and runtime options, synchronizing byte-native and deprecated cache values.
2820
- *
2821
- * For each budget, the byte-native option takes precedence when both unit forms are supplied.
2822
- * Unspecified budgets retain their current byte values, avoiding repeated MiB conversion.
2813
+ * Updates traversal and runtime options while retaining unspecified byte budgets.
2823
2814
  *
2824
2815
  * @param props - Partial runtime options to apply.
2825
2816
  */
@@ -2830,9 +2821,7 @@ var Tileset3D = class {
2830
2821
  ...this.options,
2831
2822
  ...props,
2832
2823
  cacheBytes,
2833
- maximumCacheOverflowBytes,
2834
- maximumMemoryUsage: cacheBytes / BYTES_PER_MEBIBYTE,
2835
- memoryCacheOverflow: maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE
2824
+ maximumCacheOverflowBytes
2836
2825
  };
2837
2826
  this._cacheBytes = cacheBytes;
2838
2827
  this._maximumCacheOverflowBytes = maximumCacheOverflowBytes;
@@ -3355,6 +3344,68 @@ function getTileset3DTraversalSnapshot(tileset) {
3355
3344
  };
3356
3345
  }
3357
3346
 
3347
+ // dist/tileset-3d/common/tileset-content-traversal.js
3348
+ async function* traverseTilesetContents(tileset, options = {}) {
3349
+ var _a, _b;
3350
+ await tileset.tilesetInitializationPromise;
3351
+ throwIfAborted(options.signal);
3352
+ if (!tileset.root) {
3353
+ throw new Error("Tileset initialization completed without a root tile");
3354
+ }
3355
+ const stack = [tileset.root];
3356
+ const visitedTiles = /* @__PURE__ */ new Set();
3357
+ while (stack.length > 0) {
3358
+ throwIfAborted(options.signal);
3359
+ const tile = stack.pop();
3360
+ if (visitedTiles.has(tile)) {
3361
+ continue;
3362
+ }
3363
+ visitedTiles.add(tile);
3364
+ if (tile.header.implicitSubtree && tile.childrenState !== "ready") {
3365
+ const loadChildren = tileset.source.loadTileChildrenForTraversal;
3366
+ if (!loadChildren) {
3367
+ throw new Error("Tileset source does not support camera-independent subtree loading");
3368
+ }
3369
+ const result = await loadChildren.call(tileset.source, tile, options.signal);
3370
+ if (!result.loaded) {
3371
+ throw new Error(`Unable to load implicit subtree for tile ${tile.id}`);
3372
+ }
3373
+ tile.childrenState = "ready";
3374
+ }
3375
+ if (tileset.type === TILESET_TYPE.I3S && tileset.source.loadTileChildrenForTraversal) {
3376
+ const result = await tileset.source.loadTileChildrenForTraversal(tile, options.signal);
3377
+ if (!result.loaded) {
3378
+ throw new Error(`Unable to load I3S child nodes for tile ${tile.id}`);
3379
+ }
3380
+ }
3381
+ throwIfAborted(options.signal);
3382
+ const loadsContent = tile.contentUrls.length > 0 && !tile.content;
3383
+ try {
3384
+ if (loadsContent) {
3385
+ const loadResult = await tile.loadContentForTraversal();
3386
+ throwIfAborted(options.signal);
3387
+ if (!loadResult.loaded) {
3388
+ throw new Error(`Unable to load tile content for tile ${tile.id}`);
3389
+ }
3390
+ (_b = (_a = tileset.source).onTileLoaded) == null ? void 0 : _b.call(_a, tileset, tile, loadResult);
3391
+ }
3392
+ yield { tile, contents: tile.contentEntries };
3393
+ } finally {
3394
+ if (options.unloadContent && loadsContent) {
3395
+ tile.unloadContent();
3396
+ }
3397
+ }
3398
+ for (let childIndex = tile.children.length - 1; childIndex >= 0; childIndex--) {
3399
+ stack.push(tile.children[childIndex]);
3400
+ }
3401
+ }
3402
+ }
3403
+ function throwIfAborted(signal) {
3404
+ if (signal == null ? void 0 : signal.aborted) {
3405
+ throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
3406
+ }
3407
+ }
3408
+
3358
3409
  // dist/tileset-3d/common/indexed-archive-tileset-source.js
3359
3410
  var IndexedArchiveTilesetSource = class {
3360
3411
  /** URL exposed to the tileset source that owns this archive source. */
@@ -4564,7 +4615,6 @@ var Tile3D = class {
4564
4615
  */
4565
4616
  // eslint-disable-next-line max-statements, complexity
4566
4617
  async loadContent() {
4567
- var _a;
4568
4618
  if (this.hasEmptyContent) {
4569
4619
  return { loaded: false };
4570
4620
  }
@@ -4582,19 +4632,7 @@ var Tile3D = class {
4582
4632
  return { loaded: false };
4583
4633
  }
4584
4634
  try {
4585
- const loadResult = await this.tileset.source.loadTileContent(this);
4586
- if (loadResult.contents) {
4587
- this.contents = loadResult.contents;
4588
- this.content = this.contents[0] || null;
4589
- }
4590
- this.vectorContent = ((_a = this.contents.find((content) => Boolean(content == null ? void 0 : content.vectorContent))) == null ? void 0 : _a.vectorContent) || null;
4591
- if (this.tileset.options.contentLoader) {
4592
- await this.tileset.options.contentLoader(this);
4593
- }
4594
- this.contentState = TILE_CONTENT_STATE.READY;
4595
- this._onContentLoaded();
4596
- this._updateContentEntriesAfterLoad(loadResult);
4597
- return loadResult;
4635
+ return await this._loadContentFromSource();
4598
4636
  } catch (error) {
4599
4637
  this.contentState = TILE_CONTENT_STATE.FAILED;
4600
4638
  throw error;
@@ -4602,6 +4640,39 @@ var Tile3D = class {
4602
4640
  requestToken.done();
4603
4641
  }
4604
4642
  }
4643
+ /** Loads content for complete dataset operations without viewport-based request priority. */
4644
+ async loadContentForTraversal() {
4645
+ if (this.hasEmptyContent) {
4646
+ return { loaded: false };
4647
+ }
4648
+ if (this.content) {
4649
+ return { loaded: true, contents: this.contents };
4650
+ }
4651
+ this.contentState = TILE_CONTENT_STATE.LOADING;
4652
+ try {
4653
+ return await this._loadContentFromSource();
4654
+ } catch (error) {
4655
+ this.contentState = TILE_CONTENT_STATE.FAILED;
4656
+ throw error;
4657
+ }
4658
+ }
4659
+ /** Loads source content and updates the tile's renderer-neutral payload state. */
4660
+ async _loadContentFromSource() {
4661
+ var _a;
4662
+ const loadResult = await this.tileset.source.loadTileContent(this);
4663
+ if (loadResult.contents) {
4664
+ this.contents = loadResult.contents;
4665
+ this.content = this.contents[0] || null;
4666
+ }
4667
+ this.vectorContent = ((_a = this.contents.find((content) => Boolean(content == null ? void 0 : content.vectorContent))) == null ? void 0 : _a.vectorContent) || null;
4668
+ if (this.tileset.options.contentLoader) {
4669
+ await this.tileset.options.contentLoader(this);
4670
+ }
4671
+ this.contentState = TILE_CONTENT_STATE.READY;
4672
+ this._onContentLoaded();
4673
+ this._updateContentEntriesAfterLoad(loadResult);
4674
+ return loadResult;
4675
+ }
4605
4676
  /**
4606
4677
  * Requests and installs this tile's lazy child-header group through its source.
4607
4678
  *
@@ -5715,19 +5786,19 @@ function getTilesetSemanticValue(tileset, semantic) {
5715
5786
  // dist/spatial/spatial-coordinate-transformer.js
5716
5787
  var import_core10 = require("@math.gl/core");
5717
5788
  var import_geospatial7 = require("@math.gl/geospatial");
5718
- var import_proj42 = require("@math.gl/proj4");
5789
+ var import_projection2 = require("@math.gl/projection");
5719
5790
 
5720
5791
  // dist/spatial/spatial-resource-registry.js
5721
5792
  var import_geoid = require("@math.gl/geoid");
5722
- var import_proj4 = require("@math.gl/proj4");
5793
+ var import_projection = require("@math.gl/projection");
5723
5794
  var geoidModels = /* @__PURE__ */ new Map();
5724
5795
  function registerSpatialCrs(name, definition) {
5725
5796
  validateResourceName(name, "CRS");
5726
- import_proj4.Proj4Projection.defineProjectionAliases({ [name]: definition });
5797
+ import_projection.Projection.defineProjectionAliases({ [name]: definition });
5727
5798
  }
5728
5799
  function registerSpatialDatumGrid(name, data) {
5729
5800
  validateResourceName(name, "datum grid");
5730
- import_proj4.Proj4Projection.registerDatumGrid(name, data);
5801
+ import_projection.Projection.registerDatumGrid(name, data);
5731
5802
  }
5732
5803
  function registerGeoidModel(name, geoid) {
5733
5804
  validateResourceName(name, "geoid model");
@@ -5782,24 +5853,24 @@ var SpatialCoordinateTransformer = class {
5782
5853
  this.sourceIsGeographic = isWgs84Geographic(spatialReference.sourceCrs);
5783
5854
  this.outputIsGeographic = isWgs84Geographic(outputCrs);
5784
5855
  if (spatialReference.sourceCrs && spatialReference.targetCrs && !this.sourceIsGeocentric && !this.outputIsGeocentric) {
5785
- this.horizontalProjection = new import_proj42.Proj4Projection({
5786
- from: getHorizontalProj4Definition(spatialReference.sourceCrs),
5787
- to: getHorizontalProj4Definition(spatialReference.targetCrs),
5856
+ this.horizontalProjection = new import_projection2.Projection({
5857
+ from: getHorizontalProjectionDefinition(spatialReference.sourceCrs),
5858
+ to: getHorizontalProjectionDefinition(spatialReference.targetCrs),
5788
5859
  enforceAxis: false
5789
5860
  });
5790
5861
  }
5791
5862
  if (requiresHeightTransformation(spatialReference) || this.sourceIsGeocentric || this.outputIsGeocentric) {
5792
5863
  if (!this.sourceIsGeographic && !this.sourceIsGeocentric) {
5793
- this.geographicProjection = new import_proj42.Proj4Projection({
5794
- from: getHorizontalProj4Definition(spatialReference.sourceCrs),
5864
+ this.geographicProjection = new import_projection2.Projection({
5865
+ from: getHorizontalProjectionDefinition(spatialReference.sourceCrs),
5795
5866
  to: WGS84_GEOGRAPHIC_CRS,
5796
5867
  enforceAxis: false
5797
5868
  });
5798
5869
  }
5799
5870
  if (!this.outputIsGeographic && !this.outputIsGeocentric) {
5800
- this.heightOutputProjection = new import_proj42.Proj4Projection({
5871
+ this.heightOutputProjection = new import_projection2.Projection({
5801
5872
  from: WGS84_GEOGRAPHIC_CRS,
5802
- to: getHorizontalProj4Definition(outputCrs),
5873
+ to: getHorizontalProjectionDefinition(outputCrs),
5803
5874
  enforceAxis: false
5804
5875
  });
5805
5876
  }
@@ -5944,11 +6015,18 @@ function inverseTransposeMultiply(jacobianColumns, vector) {
5944
6015
  ];
5945
6016
  return inverseTranspose.map((row) => (row[0] * vector[0] + row[1] * vector[1] + row[2] * vector[2]) / determinant);
5946
6017
  }
5947
- function getHorizontalProj4Definition(definition) {
6018
+ function getHorizontalProjectionDefinition(definition) {
5948
6019
  if (!definition) {
5949
6020
  throw new Error("Cannot construct a projection because the CRS is unknown");
5950
6021
  }
5951
- return (0, import_proj42.toProj4CRSDefinition)(definition, { mode: "horizontal" });
6022
+ if (typeof definition !== "string" && definition.type === "CompoundCRS") {
6023
+ const horizontalComponents = definition.components.filter((component) => component.type !== "VerticalCRS");
6024
+ if (horizontalComponents.length !== 1) {
6025
+ throw new Error("CompoundCRS requires exactly one horizontal component");
6026
+ }
6027
+ return getHorizontalProjectionDefinition(horizontalComponents[0]);
6028
+ }
6029
+ return definition;
5952
6030
  }
5953
6031
  function validateTransformRequest(spatialReference) {
5954
6032
  if (spatialReference.outputCoordinates === "local-enu") {
@@ -6700,6 +6778,8 @@ var Tiles3DSource = class {
6700
6778
  tileUrlCache = /* @__PURE__ */ new Map();
6701
6779
  /** Original content descriptors retained across {@link Tile3D.unloadContent} calls. */
6702
6780
  tileContentHeaders = /* @__PURE__ */ new WeakMap();
6781
+ /** Installed nested roots by parent placement and ordered nested-content slot, across reloads. */
6782
+ nestedTilesetRoots = /* @__PURE__ */ new WeakMap();
6703
6783
  /** Source-local namespaces assigned to distinct embedded glTF package file collections. */
6704
6784
  packageNamespaces = /* @__PURE__ */ new WeakMap();
6705
6785
  /** Next source-local package namespace. */
@@ -6985,6 +7065,13 @@ var Tiles3DSource = class {
6985
7065
  childSubtreeCount: materializedSubtree.childSubtreeCount
6986
7066
  };
6987
7067
  }
7068
+ /** Loads an implicit subtree for dataset traversal without camera state. */
7069
+ async loadTileChildrenForTraversal(tile, signal) {
7070
+ throwIfTraversalAborted(signal);
7071
+ const result = await this.loadTileChildren(tile, {});
7072
+ throwIfTraversalAborted(signal);
7073
+ return result;
7074
+ }
6988
7075
  /**
6989
7076
  * Returns a snapshot of implicit-subtree request, cache, and materialization counters.
6990
7077
  *
@@ -7086,7 +7173,9 @@ var Tiles3DSource = class {
7086
7173
  return this.extensionsUsed.includes(extensionName);
7087
7174
  }
7088
7175
  /**
7089
- * Updates content-format flags and installs nested tileset subtrees.
7176
+ * Updates content-format flags and installs nested tileset subtrees once per content slot.
7177
+ * Reloading payloads retains attached roots and their descendants, including duplicate URLs
7178
+ * declared in separate slots or parent placements. Detached roots are reinstalled in declaration order.
7090
7179
  */
7091
7180
  onTileLoaded(tileset, tile, loadResult) {
7092
7181
  const contents = tile.contents.length ? tile.contents : [tile.content];
@@ -7104,8 +7193,20 @@ var Tiles3DSource = class {
7104
7193
  this.contentFormats.ktx2 = true;
7105
7194
  }
7106
7195
  const nestedTilesets = loadResult.nestedTilesets || (loadResult.nestedTileset ? [loadResult.nestedTileset] : []);
7107
- for (const nestedTileset of nestedTilesets) {
7108
- tileset._initializeTileHeaders(this.prepareNestedTileset(tileset, nestedTileset), tile);
7196
+ const installedRoots = this.nestedTilesetRoots.get(tile) || [];
7197
+ for (const [nestedIndex, nestedTileset] of nestedTilesets.entries()) {
7198
+ const installedRoot = installedRoots[nestedIndex];
7199
+ if (installedRoot && tile.children.includes(installedRoot)) {
7200
+ continue;
7201
+ }
7202
+ const nextRoot = installedRoots.slice(nestedIndex + 1).find((root) => tile.children.includes(root));
7203
+ const nestedRoot = tileset._initializeTileHeaders(this.prepareNestedTileset(tileset, nestedTileset), tile);
7204
+ if (nextRoot) {
7205
+ tile.children.splice(tile.children.indexOf(nestedRoot), 1);
7206
+ tile.children.splice(tile.children.indexOf(nextRoot), 0, nestedRoot);
7207
+ }
7208
+ installedRoots[nestedIndex] = nestedRoot;
7209
+ this.nestedTilesetRoots.set(tile, installedRoots);
7109
7210
  }
7110
7211
  }
7111
7212
  /**
@@ -7312,6 +7413,11 @@ var Tiles3DSource = class {
7312
7413
  return materializedTileCount;
7313
7414
  }
7314
7415
  };
7416
+ function throwIfTraversalAborted(signal) {
7417
+ if (signal == null ? void 0 : signal.aborted) {
7418
+ throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
7419
+ }
7420
+ }
7315
7421
  function isTilesetRequest(input) {
7316
7422
  return Boolean(input && typeof input === "object" && "url" in input && "loader" in input && !("type" in input));
7317
7423
  }
@@ -7481,7 +7587,10 @@ var I3STilesetTraverser = class extends TilesetTraverser {
7481
7587
  const childTiles = tile.children;
7482
7588
  for (const child of children) {
7483
7589
  const extendedId = `${child.id}-${frameState.viewport.id}`;
7484
- const childTile = childTiles && childTiles.find((t) => t.id === extendedId);
7590
+ const childTile = childTiles && childTiles.find((t) => {
7591
+ var _a;
7592
+ return t.id === extendedId || ((_a = t.header) == null ? void 0 : _a.id) === child.id;
7593
+ });
7485
7594
  if (!childTile) {
7486
7595
  const request = () => this._loadTile(tile, child.id, frameState);
7487
7596
  const cachedRequest = this._tileManager.find(extendedId);
@@ -8305,7 +8414,17 @@ var I3SSource = class {
8305
8414
  }
8306
8415
  };
8307
8416
  tile.content = await this.loadResourceData(contentUrl, options, this.contentLoader);
8308
- return { loaded: true };
8417
+ const contentEntry = {
8418
+ index: 0,
8419
+ uri: tile.contentUrl,
8420
+ type: tile.header.layerType,
8421
+ payload: tile.content,
8422
+ metadata: tile.header.metadata || null,
8423
+ boundingVolume: tile.boundingVolume,
8424
+ featureIds: tile.featureIdSets,
8425
+ renderable: Boolean(tile.content)
8426
+ };
8427
+ return { loaded: true, contents: [tile.content], contentEntries: [contentEntry] };
8309
8428
  }
8310
8429
  /**
8311
8430
  * Loads child tile metadata on demand from node pages or `/nodes/{id}` resources.
@@ -8328,6 +8447,38 @@ var I3SSource = class {
8328
8447
  const header = await this.loadResourceData(nodeUrl, options);
8329
8448
  return await this.transformTileHeader(header);
8330
8449
  }
8450
+ /** Loads all declared I3S child headers without camera or visibility state. */
8451
+ async loadTileChildrenForTraversal(parentTile, signal) {
8452
+ const childHeaders = parentTile.header.children || [];
8453
+ let loadedChildren = 0;
8454
+ const orderedChildren = [];
8455
+ for (const childHeader of childHeaders) {
8456
+ throwIfTraversalAborted2(signal);
8457
+ const childId = childHeader.id;
8458
+ if (typeof childId !== "string" && typeof childId !== "number") {
8459
+ throw new Error("I3S child header is missing its node ID");
8460
+ }
8461
+ const existingChild = parentTile.children.find((tile) => {
8462
+ var _a;
8463
+ return tile.id === String(childId) || ((_a = tile.header) == null ? void 0 : _a.id) === childId;
8464
+ });
8465
+ if (existingChild) {
8466
+ orderedChildren.push(existingChild);
8467
+ continue;
8468
+ }
8469
+ const childTileHeader = await this.loadChildTileHeader(parentTile, String(childId), null);
8470
+ throwIfTraversalAborted2(signal);
8471
+ const childTile = new Tile3D(parentTile.tileset, childTileHeader, parentTile, String(childId));
8472
+ orderedChildren.push(childTile);
8473
+ loadedChildren++;
8474
+ }
8475
+ const orderedChildSet = new Set(orderedChildren);
8476
+ parentTile.children = [
8477
+ ...orderedChildren,
8478
+ ...parentTile.children.filter((childTile) => !orderedChildSet.has(childTile))
8479
+ ];
8480
+ return { loaded: true, tileCount: loadedChildren, childSubtreeCount: 0 };
8481
+ }
8331
8482
  /**
8332
8483
  * Resolves an I3S request URL with source-managed query parameters.
8333
8484
  */
@@ -8338,20 +8489,21 @@ var I3SSource = class {
8338
8489
  if (!Object.keys(this.queryParams).length) {
8339
8490
  return tilePath;
8340
8491
  }
8341
- const queryDelimiterIndex = tilePath.indexOf("?");
8342
- if (queryDelimiterIndex === -1) {
8343
- return `${tilePath}?${new URLSearchParams(this.queryParams).toString()}`;
8344
- }
8345
- const existingQuery = tilePath.slice(queryDelimiterIndex + 1);
8346
- const existingQueryKeys = new Set(existingQuery.split("&").map((parameter) => parameter.split("=", 1)[0]));
8492
+ const hashIndex = tilePath.indexOf("#");
8493
+ const hash = hashIndex >= 0 ? tilePath.slice(hashIndex) : "";
8494
+ const tilePathWithoutHash = hashIndex >= 0 ? tilePath.slice(0, hashIndex) : tilePath;
8495
+ const queryDelimiterIndex = tilePathWithoutHash.indexOf("?");
8496
+ const path = queryDelimiterIndex === -1 ? tilePathWithoutHash : tilePathWithoutHash.slice(0, queryDelimiterIndex);
8497
+ const existingQuery = queryDelimiterIndex === -1 ? "" : tilePathWithoutHash.slice(queryDelimiterIndex + 1);
8498
+ const existingQueryParams = new URLSearchParams(existingQuery);
8347
8499
  const queryParams = new URLSearchParams();
8348
8500
  for (const [key, value] of Object.entries(this.queryParams)) {
8349
- if (!existingQueryKeys.has(key)) {
8501
+ if (!existingQueryParams.has(key)) {
8350
8502
  queryParams.set(key, value);
8351
8503
  }
8352
8504
  }
8353
8505
  const queryString = queryParams.toString();
8354
- return queryString ? `${tilePath}${existingQuery ? "&" : ""}${queryString}` : tilePath;
8506
+ return `${path}${existingQuery ? `?${existingQuery}` : ""}${queryString ? `${existingQuery ? "&" : "?"}${queryString}` : ""}${hash}`;
8355
8507
  }
8356
8508
  /**
8357
8509
  * Derives the default view state from full extent or store extent metadata.
@@ -8455,6 +8607,9 @@ var I3SSource = class {
8455
8607
  * @param loadOptions Loader options that may contain an ArcGIS token.
8456
8608
  */
8457
8609
  initializeQueryParams(loadOptions) {
8610
+ for (const [key, value] of Object.entries(loadOptions.searchParams || {})) {
8611
+ this.queryParams[key] = String(value);
8612
+ }
8458
8613
  const i3sOptions = loadOptions.i3s;
8459
8614
  if (i3sOptions && typeof i3sOptions === "object" && "token" in i3sOptions) {
8460
8615
  const token = i3sOptions.token;
@@ -8494,6 +8649,11 @@ var I3SSource = class {
8494
8649
  };
8495
8650
  }
8496
8651
  };
8652
+ function throwIfTraversalAborted2(signal) {
8653
+ if (signal == null ? void 0 : signal.aborted) {
8654
+ throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
8655
+ }
8656
+ }
8497
8657
  function getI3SContentLoader(loader) {
8498
8658
  return loader.contentLoader || loader;
8499
8659
  }