@loaders.gl/tiles 5.0.0-alpha.7 → 5.0.0-alpha.8

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 (56) hide show
  1. package/README.md +26 -0
  2. package/dist/dist.dev.js +6381 -6802
  3. package/dist/dist.min.js +3 -6
  4. package/dist/index.cjs +209 -65
  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 +13 -6
  12. package/dist/spatial/spatial-coordinate-transformer.js.map +1 -1
  13. package/dist/spatial/spatial-resource-registry.js +3 -3
  14. package/dist/spatial/spatial-resource-registry.js.map +1 -1
  15. package/dist/tileset-3d/common/tile-3d.d.ts +4 -0
  16. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
  17. package/dist/tileset-3d/common/tile-3d.js +35 -14
  18. package/dist/tileset-3d/common/tile-3d.js.map +1 -1
  19. package/dist/tileset-3d/common/tileset-3d.d.ts +1 -25
  20. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  21. package/dist/tileset-3d/common/tileset-3d.js +8 -41
  22. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  23. package/dist/tileset-3d/common/tileset-content-traversal.d.ts +37 -0
  24. package/dist/tileset-3d/common/tileset-content-traversal.d.ts.map +1 -0
  25. package/dist/tileset-3d/common/tileset-content-traversal.js +80 -0
  26. package/dist/tileset-3d/common/tileset-content-traversal.js.map +1 -0
  27. package/dist/tileset-3d/common/tileset-source.d.ts +8 -1
  28. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
  29. package/dist/tileset-3d/common/tileset-source.js.map +1 -1
  30. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +2 -0
  31. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
  32. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +13 -0
  33. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
  34. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +4 -2
  35. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
  36. package/dist/tileset-3d/format-i3s/i3s-source.js +60 -9
  37. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
  38. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
  39. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +1 -1
  40. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
  41. package/dist/tileset-3d/helpers/frame-state.d.ts +1 -0
  42. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
  43. package/dist/tileset-3d/helpers/frame-state.js +5 -4
  44. package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
  45. package/package.json +11 -13
  46. package/src/index.ts +5 -0
  47. package/src/spatial/spatial-coordinate-transformer.ts +18 -9
  48. package/src/spatial/spatial-resource-registry.ts +3 -3
  49. package/src/tileset-3d/common/tile-3d.ts +39 -17
  50. package/src/tileset-3d/common/tileset-3d.ts +8 -60
  51. package/src/tileset-3d/common/tileset-content-traversal.ts +114 -0
  52. package/src/tileset-3d/common/tileset-source.ts +16 -1
  53. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +18 -0
  54. package/src/tileset-3d/format-i3s/i3s-source.ts +84 -13
  55. package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +2 -1
  56. 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
  *
@@ -5723,11 +5794,11 @@ var import_proj4 = require("@math.gl/proj4");
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_proj4.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_proj4.Projection.registerDatumGrid(name, data);
5731
5802
  }
5732
5803
  function registerGeoidModel(name, geoid) {
5733
5804
  validateResourceName(name, "geoid model");
@@ -5782,7 +5853,7 @@ 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({
5856
+ this.horizontalProjection = new import_proj42.Projection({
5786
5857
  from: getHorizontalProj4Definition(spatialReference.sourceCrs),
5787
5858
  to: getHorizontalProj4Definition(spatialReference.targetCrs),
5788
5859
  enforceAxis: false
@@ -5790,14 +5861,14 @@ var SpatialCoordinateTransformer = class {
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({
5864
+ this.geographicProjection = new import_proj42.Projection({
5794
5865
  from: getHorizontalProj4Definition(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_proj42.Projection({
5801
5872
  from: WGS84_GEOGRAPHIC_CRS,
5802
5873
  to: getHorizontalProj4Definition(outputCrs),
5803
5874
  enforceAxis: false
@@ -5948,7 +6019,14 @@ function getHorizontalProj4Definition(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 getHorizontalProj4Definition(horizontalComponents[0]);
6028
+ }
6029
+ return definition;
5952
6030
  }
5953
6031
  function validateTransformRequest(spatialReference) {
5954
6032
  if (spatialReference.outputCoordinates === "local-enu") {
@@ -6985,6 +7063,13 @@ var Tiles3DSource = class {
6985
7063
  childSubtreeCount: materializedSubtree.childSubtreeCount
6986
7064
  };
6987
7065
  }
7066
+ /** Loads an implicit subtree for dataset traversal without camera state. */
7067
+ async loadTileChildrenForTraversal(tile, signal) {
7068
+ throwIfTraversalAborted(signal);
7069
+ const result = await this.loadTileChildren(tile, {});
7070
+ throwIfTraversalAborted(signal);
7071
+ return result;
7072
+ }
6988
7073
  /**
6989
7074
  * Returns a snapshot of implicit-subtree request, cache, and materialization counters.
6990
7075
  *
@@ -7312,6 +7397,11 @@ var Tiles3DSource = class {
7312
7397
  return materializedTileCount;
7313
7398
  }
7314
7399
  };
7400
+ function throwIfTraversalAborted(signal) {
7401
+ if (signal == null ? void 0 : signal.aborted) {
7402
+ throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
7403
+ }
7404
+ }
7315
7405
  function isTilesetRequest(input) {
7316
7406
  return Boolean(input && typeof input === "object" && "url" in input && "loader" in input && !("type" in input));
7317
7407
  }
@@ -7481,7 +7571,10 @@ var I3STilesetTraverser = class extends TilesetTraverser {
7481
7571
  const childTiles = tile.children;
7482
7572
  for (const child of children) {
7483
7573
  const extendedId = `${child.id}-${frameState.viewport.id}`;
7484
- const childTile = childTiles && childTiles.find((t) => t.id === extendedId);
7574
+ const childTile = childTiles && childTiles.find((t) => {
7575
+ var _a;
7576
+ return t.id === extendedId || ((_a = t.header) == null ? void 0 : _a.id) === child.id;
7577
+ });
7485
7578
  if (!childTile) {
7486
7579
  const request = () => this._loadTile(tile, child.id, frameState);
7487
7580
  const cachedRequest = this._tileManager.find(extendedId);
@@ -8305,7 +8398,17 @@ var I3SSource = class {
8305
8398
  }
8306
8399
  };
8307
8400
  tile.content = await this.loadResourceData(contentUrl, options, this.contentLoader);
8308
- return { loaded: true };
8401
+ const contentEntry = {
8402
+ index: 0,
8403
+ uri: tile.contentUrl,
8404
+ type: tile.header.layerType,
8405
+ payload: tile.content,
8406
+ metadata: tile.header.metadata || null,
8407
+ boundingVolume: tile.boundingVolume,
8408
+ featureIds: tile.featureIdSets,
8409
+ renderable: Boolean(tile.content)
8410
+ };
8411
+ return { loaded: true, contents: [tile.content], contentEntries: [contentEntry] };
8309
8412
  }
8310
8413
  /**
8311
8414
  * Loads child tile metadata on demand from node pages or `/nodes/{id}` resources.
@@ -8328,6 +8431,38 @@ var I3SSource = class {
8328
8431
  const header = await this.loadResourceData(nodeUrl, options);
8329
8432
  return await this.transformTileHeader(header);
8330
8433
  }
8434
+ /** Loads all declared I3S child headers without camera or visibility state. */
8435
+ async loadTileChildrenForTraversal(parentTile, signal) {
8436
+ const childHeaders = parentTile.header.children || [];
8437
+ let loadedChildren = 0;
8438
+ const orderedChildren = [];
8439
+ for (const childHeader of childHeaders) {
8440
+ throwIfTraversalAborted2(signal);
8441
+ const childId = childHeader.id;
8442
+ if (typeof childId !== "string" && typeof childId !== "number") {
8443
+ throw new Error("I3S child header is missing its node ID");
8444
+ }
8445
+ const existingChild = parentTile.children.find((tile) => {
8446
+ var _a;
8447
+ return tile.id === String(childId) || ((_a = tile.header) == null ? void 0 : _a.id) === childId;
8448
+ });
8449
+ if (existingChild) {
8450
+ orderedChildren.push(existingChild);
8451
+ continue;
8452
+ }
8453
+ const childTileHeader = await this.loadChildTileHeader(parentTile, String(childId), null);
8454
+ throwIfTraversalAborted2(signal);
8455
+ const childTile = new Tile3D(parentTile.tileset, childTileHeader, parentTile, String(childId));
8456
+ orderedChildren.push(childTile);
8457
+ loadedChildren++;
8458
+ }
8459
+ const orderedChildSet = new Set(orderedChildren);
8460
+ parentTile.children = [
8461
+ ...orderedChildren,
8462
+ ...parentTile.children.filter((childTile) => !orderedChildSet.has(childTile))
8463
+ ];
8464
+ return { loaded: true, tileCount: loadedChildren, childSubtreeCount: 0 };
8465
+ }
8331
8466
  /**
8332
8467
  * Resolves an I3S request URL with source-managed query parameters.
8333
8468
  */
@@ -8338,20 +8473,21 @@ var I3SSource = class {
8338
8473
  if (!Object.keys(this.queryParams).length) {
8339
8474
  return tilePath;
8340
8475
  }
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]));
8476
+ const hashIndex = tilePath.indexOf("#");
8477
+ const hash = hashIndex >= 0 ? tilePath.slice(hashIndex) : "";
8478
+ const tilePathWithoutHash = hashIndex >= 0 ? tilePath.slice(0, hashIndex) : tilePath;
8479
+ const queryDelimiterIndex = tilePathWithoutHash.indexOf("?");
8480
+ const path = queryDelimiterIndex === -1 ? tilePathWithoutHash : tilePathWithoutHash.slice(0, queryDelimiterIndex);
8481
+ const existingQuery = queryDelimiterIndex === -1 ? "" : tilePathWithoutHash.slice(queryDelimiterIndex + 1);
8482
+ const existingQueryParams = new URLSearchParams(existingQuery);
8347
8483
  const queryParams = new URLSearchParams();
8348
8484
  for (const [key, value] of Object.entries(this.queryParams)) {
8349
- if (!existingQueryKeys.has(key)) {
8485
+ if (!existingQueryParams.has(key)) {
8350
8486
  queryParams.set(key, value);
8351
8487
  }
8352
8488
  }
8353
8489
  const queryString = queryParams.toString();
8354
- return queryString ? `${tilePath}${existingQuery ? "&" : ""}${queryString}` : tilePath;
8490
+ return `${path}${existingQuery ? `?${existingQuery}` : ""}${queryString ? `${existingQuery ? "&" : "?"}${queryString}` : ""}${hash}`;
8355
8491
  }
8356
8492
  /**
8357
8493
  * Derives the default view state from full extent or store extent metadata.
@@ -8455,6 +8591,9 @@ var I3SSource = class {
8455
8591
  * @param loadOptions Loader options that may contain an ArcGIS token.
8456
8592
  */
8457
8593
  initializeQueryParams(loadOptions) {
8594
+ for (const [key, value] of Object.entries(loadOptions.searchParams || {})) {
8595
+ this.queryParams[key] = String(value);
8596
+ }
8458
8597
  const i3sOptions = loadOptions.i3s;
8459
8598
  if (i3sOptions && typeof i3sOptions === "object" && "token" in i3sOptions) {
8460
8599
  const token = i3sOptions.token;
@@ -8494,6 +8633,11 @@ var I3SSource = class {
8494
8633
  };
8495
8634
  }
8496
8635
  };
8636
+ function throwIfTraversalAborted2(signal) {
8637
+ if (signal == null ? void 0 : signal.aborted) {
8638
+ throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
8639
+ }
8640
+ }
8497
8641
  function getI3SContentLoader(loader) {
8498
8642
  return loader.contentLoader || loader;
8499
8643
  }