@loaders.gl/tiles 5.0.0-alpha.1 → 5.0.0-alpha.11

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 (205) hide show
  1. package/LICENSE +14 -3
  2. package/README.md +26 -0
  3. package/dist/dist.dev.js +20341 -5081
  4. package/dist/dist.min.js +3 -1
  5. package/dist/index.cjs +7904 -2071
  6. package/dist/index.cjs.map +3 -4
  7. package/dist/index.d.ts +25 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +11 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/point-cloud/point-cloud-tile.d.ts.map +1 -1
  12. package/dist/point-cloud/point-cloud-tile.js +3 -0
  13. package/dist/point-cloud/point-cloud-tile.js.map +1 -1
  14. package/dist/point-cloud/point-cloud-tileset.d.ts +4 -0
  15. package/dist/point-cloud/point-cloud-tileset.d.ts.map +1 -1
  16. package/dist/point-cloud/point-cloud-tileset.js +54 -12
  17. package/dist/point-cloud/point-cloud-tileset.js.map +1 -1
  18. package/dist/point-cloud/types.d.ts +17 -0
  19. package/dist/point-cloud/types.d.ts.map +1 -1
  20. package/dist/point-cloud/types.js +3 -0
  21. package/dist/point-cloud/types.js.map +1 -1
  22. package/dist/raster-set.d.ts +48 -31
  23. package/dist/raster-set.d.ts.map +1 -1
  24. package/dist/raster-set.js +104 -24
  25. package/dist/raster-set.js.map +1 -1
  26. package/dist/spatial/format-spatial-reference.d.ts +68 -0
  27. package/dist/spatial/format-spatial-reference.d.ts.map +1 -0
  28. package/dist/spatial/format-spatial-reference.js +291 -0
  29. package/dist/spatial/format-spatial-reference.js.map +1 -0
  30. package/dist/spatial/get-spatial-coordinate-frame.d.ts +20 -0
  31. package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -0
  32. package/dist/spatial/get-spatial-coordinate-frame.js +139 -0
  33. package/dist/spatial/get-spatial-coordinate-frame.js.map +1 -0
  34. package/dist/spatial/i3s-elevation.d.ts +20 -0
  35. package/dist/spatial/i3s-elevation.d.ts.map +1 -0
  36. package/dist/spatial/i3s-elevation.js +90 -0
  37. package/dist/spatial/i3s-elevation.js.map +1 -0
  38. package/dist/spatial/i3s-spatial-transformer.d.ts +205 -0
  39. package/dist/spatial/i3s-spatial-transformer.d.ts.map +1 -0
  40. package/dist/spatial/i3s-spatial-transformer.js +731 -0
  41. package/dist/spatial/i3s-spatial-transformer.js.map +1 -0
  42. package/dist/spatial/normalize-crs-identifier.d.ts +15 -0
  43. package/dist/spatial/normalize-crs-identifier.d.ts.map +1 -0
  44. package/dist/spatial/normalize-crs-identifier.js +22 -0
  45. package/dist/spatial/normalize-crs-identifier.js.map +1 -0
  46. package/dist/spatial/spatial-coordinate-transformer.d.ts +52 -0
  47. package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -0
  48. package/dist/spatial/spatial-coordinate-transformer.js +295 -0
  49. package/dist/spatial/spatial-coordinate-transformer.js.map +1 -0
  50. package/dist/spatial/spatial-resource-registry.d.ts +47 -0
  51. package/dist/spatial/spatial-resource-registry.d.ts.map +1 -0
  52. package/dist/spatial/spatial-resource-registry.js +76 -0
  53. package/dist/spatial/spatial-resource-registry.js.map +1 -0
  54. package/dist/spatial/spatial-types.d.ts +190 -0
  55. package/dist/spatial/spatial-types.d.ts.map +1 -0
  56. package/dist/spatial/spatial-types.js +192 -0
  57. package/dist/spatial/spatial-types.js.map +1 -0
  58. package/dist/spatial/tiles-3d-spatial-transformer.d.ts +32 -0
  59. package/dist/spatial/tiles-3d-spatial-transformer.d.ts.map +1 -0
  60. package/dist/spatial/tiles-3d-spatial-transformer.js +163 -0
  61. package/dist/spatial/tiles-3d-spatial-transformer.js.map +1 -0
  62. package/dist/tileset-2d/adapter.d.ts +2 -3
  63. package/dist/tileset-2d/adapter.d.ts.map +1 -1
  64. package/dist/tileset-2d/tileset-2d.d.ts +2 -3
  65. package/dist/tileset-2d/tileset-2d.d.ts.map +1 -1
  66. package/dist/tileset-2d/tileset-2d.js.map +1 -1
  67. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts +3 -3
  68. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts.map +1 -1
  69. package/dist/tileset-3d/common/indexed-archive-tileset-source.js +9 -4
  70. package/dist/tileset-3d/common/indexed-archive-tileset-source.js.map +1 -1
  71. package/dist/tileset-3d/common/tile-3d-contracts.d.ts +63 -0
  72. package/dist/tileset-3d/common/tile-3d-contracts.d.ts.map +1 -0
  73. package/dist/tileset-3d/common/tile-3d-contracts.js +51 -0
  74. package/dist/tileset-3d/common/tile-3d-contracts.js.map +1 -0
  75. package/dist/tileset-3d/common/tile-3d.d.ts +176 -5
  76. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
  77. package/dist/tileset-3d/common/tile-3d.js +523 -89
  78. package/dist/tileset-3d/common/tile-3d.js.map +1 -1
  79. package/dist/tileset-3d/common/tileset-3d.d.ts +199 -9
  80. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  81. package/dist/tileset-3d/common/tileset-3d.js +345 -32
  82. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  83. package/dist/tileset-3d/common/tileset-cache.d.ts +11 -1
  84. package/dist/tileset-3d/common/tileset-cache.d.ts.map +1 -1
  85. package/dist/tileset-3d/common/tileset-cache.js +11 -3
  86. package/dist/tileset-3d/common/tileset-cache.js.map +1 -1
  87. package/dist/tileset-3d/common/tileset-content-traversal.d.ts +37 -0
  88. package/dist/tileset-3d/common/tileset-content-traversal.d.ts.map +1 -0
  89. package/dist/tileset-3d/common/tileset-content-traversal.js +80 -0
  90. package/dist/tileset-3d/common/tileset-content-traversal.js.map +1 -0
  91. package/dist/tileset-3d/common/tileset-observability.d.ts +60 -0
  92. package/dist/tileset-3d/common/tileset-observability.d.ts.map +1 -0
  93. package/dist/tileset-3d/common/tileset-observability.js +39 -0
  94. package/dist/tileset-3d/common/tileset-observability.js.map +1 -0
  95. package/dist/tileset-3d/common/tileset-source.d.ts +51 -9
  96. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
  97. package/dist/tileset-3d/common/tileset-source.js.map +1 -1
  98. package/dist/tileset-3d/common/tileset-traverser.d.ts +33 -1
  99. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
  100. package/dist/tileset-3d/common/tileset-traverser.js +61 -14
  101. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
  102. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +182 -0
  103. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -0
  104. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +681 -0
  105. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -0
  106. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +127 -6
  107. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
  108. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +485 -27
  109. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
  110. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +104 -0
  111. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
  112. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +286 -0
  113. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
  114. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.d.ts.map +1 -1
  115. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js +3 -0
  116. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js.map +1 -1
  117. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +23 -6
  118. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
  119. package/dist/tileset-3d/format-i3s/i3s-source.js +176 -25
  120. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
  121. package/dist/tileset-3d/format-i3s/i3s-tile-manager.d.ts.map +1 -1
  122. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js +3 -0
  123. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js.map +1 -1
  124. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
  125. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +4 -1
  126. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
  127. package/dist/tileset-3d/helpers/3d-tiles-options.d.ts.map +1 -1
  128. package/dist/tileset-3d/helpers/3d-tiles-options.js +3 -0
  129. package/dist/tileset-3d/helpers/3d-tiles-options.js.map +1 -1
  130. package/dist/tileset-3d/helpers/bounding-volume.d.ts +7 -3
  131. package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -1
  132. package/dist/tileset-3d/helpers/bounding-volume.js +20 -91
  133. package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
  134. package/dist/tileset-3d/helpers/frame-state.d.ts +67 -2
  135. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
  136. package/dist/tileset-3d/helpers/frame-state.js +92 -10
  137. package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
  138. package/dist/tileset-3d/helpers/i3s-lod.d.ts.map +1 -1
  139. package/dist/tileset-3d/helpers/i3s-lod.js +20 -8
  140. package/dist/tileset-3d/helpers/i3s-lod.js.map +1 -1
  141. package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts +17 -0
  142. package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts.map +1 -0
  143. package/dist/tileset-3d/helpers/s2-bounding-volume.js +20 -0
  144. package/dist/tileset-3d/helpers/s2-bounding-volume.js.map +1 -0
  145. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts +61 -9
  146. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts.map +1 -1
  147. package/dist/tileset-3d/helpers/tiles-3d-lod.js +198 -93
  148. package/dist/tileset-3d/helpers/tiles-3d-lod.js.map +1 -1
  149. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts +134 -0
  150. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts.map +1 -0
  151. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js +179 -0
  152. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js.map +1 -0
  153. package/dist/types.d.ts +7 -0
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +3 -0
  156. package/dist/types.js.map +1 -1
  157. package/dist/utils/doubly-linked-list.d.ts.map +1 -1
  158. package/dist/utils/doubly-linked-list.js +1 -0
  159. package/dist/utils/doubly-linked-list.js.map +1 -1
  160. package/dist/utils/managed-array.d.ts.map +1 -1
  161. package/dist/utils/managed-array.js +1 -0
  162. package/dist/utils/managed-array.js.map +1 -1
  163. package/package.json +12 -10
  164. package/src/index.ts +79 -1
  165. package/src/point-cloud/point-cloud-tile.ts +4 -0
  166. package/src/point-cloud/point-cloud-tileset.ts +70 -12
  167. package/src/point-cloud/types.ts +21 -0
  168. package/src/raster-set.ts +197 -67
  169. package/src/spatial/format-spatial-reference.ts +385 -0
  170. package/src/spatial/get-spatial-coordinate-frame.ts +169 -0
  171. package/src/spatial/i3s-elevation.ts +96 -0
  172. package/src/spatial/i3s-spatial-transformer.ts +1021 -0
  173. package/src/spatial/normalize-crs-identifier.ts +26 -0
  174. package/src/spatial/spatial-coordinate-transformer.ts +377 -0
  175. package/src/spatial/spatial-resource-registry.ts +90 -0
  176. package/src/spatial/spatial-types.ts +422 -0
  177. package/src/spatial/tiles-3d-spatial-transformer.ts +194 -0
  178. package/src/tileset-2d/adapter.ts +2 -3
  179. package/src/tileset-2d/tileset-2d.ts +2 -3
  180. package/src/tileset-3d/common/indexed-archive-tileset-source.ts +13 -13
  181. package/src/tileset-3d/common/tile-3d-contracts.ts +115 -0
  182. package/src/tileset-3d/common/tile-3d.ts +629 -98
  183. package/src/tileset-3d/common/tileset-3d.ts +512 -40
  184. package/src/tileset-3d/common/tileset-cache.ts +15 -7
  185. package/src/tileset-3d/common/tileset-content-traversal.ts +114 -0
  186. package/src/tileset-3d/common/tileset-observability.ts +100 -0
  187. package/src/tileset-3d/common/tileset-source.ts +63 -13
  188. package/src/tileset-3d/common/tileset-traverser.ts +73 -15
  189. package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +1059 -0
  190. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +674 -28
  191. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +328 -0
  192. package/src/tileset-3d/format-i3s/i3s-pending-tiles-register.ts +4 -0
  193. package/src/tileset-3d/format-i3s/i3s-source.ts +220 -29
  194. package/src/tileset-3d/format-i3s/i3s-tile-manager.ts +4 -0
  195. package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +6 -1
  196. package/src/tileset-3d/helpers/3d-tiles-options.ts +4 -0
  197. package/src/tileset-3d/helpers/bounding-volume.ts +31 -114
  198. package/src/tileset-3d/helpers/frame-state.ts +152 -13
  199. package/src/tileset-3d/helpers/i3s-lod.ts +22 -8
  200. package/src/tileset-3d/helpers/s2-bounding-volume.ts +35 -0
  201. package/src/tileset-3d/helpers/tiles-3d-lod.ts +291 -112
  202. package/src/tileset-3d/helpers/tiles-3d-request-priority.ts +301 -0
  203. package/src/types.ts +11 -0
  204. package/src/utils/doubly-linked-list.ts +2 -0
  205. package/src/utils/managed-array.ts +2 -0
@@ -71,24 +71,32 @@ export class TilesetCache {
71
71
  }
72
72
  }
73
73
 
74
- unloadTiles(tileset, unloadCallback): void {
74
+ /**
75
+ * Unloads least-recently-used tiles until the byte-native cache target is met.
76
+ *
77
+ * Tiles touched after the frame sentinel remain protected, so {@link Tileset3D.cacheBytes} is a
78
+ * soft target rather than a hard cap on the visible working set. Overflow headroom affects
79
+ * memory-adjusted SSE, not eviction of unused tiles.
80
+ *
81
+ * @param tileset - Tileset that owns the cache and reports estimated content memory.
82
+ * @param unloadCallback - Callback that releases content and decrements the memory estimate.
83
+ */
84
+ unloadTiles(
85
+ tileset: Tileset3D,
86
+ unloadCallback?: (tileset: Tileset3D, tile: Tile3D) => void
87
+ ): void {
75
88
  const trimTiles = this._trimTiles;
76
89
  this._trimTiles = false;
77
90
 
78
91
  const list = this._list;
79
92
 
80
- const maximumMemoryUsageInBytes = tileset.maximumMemoryUsage * 1024 * 1024;
81
-
82
93
  // Traverse the list only to the sentinel since tiles/nodes to the
83
94
  // right of the sentinel were used this frame.
84
95
  // The sub-list to the left of the sentinel is ordered from LRU to MRU.
85
96
  const sentinel = this._sentinel;
86
97
  let node = list.head;
87
98
 
88
- while (
89
- node !== sentinel &&
90
- (tileset.gpuMemoryUsageInBytes > maximumMemoryUsageInBytes || trimTiles)
91
- ) {
99
+ while (node !== sentinel && (tileset.gpuMemoryUsageInBytes > tileset.cacheBytes || trimTiles)) {
92
100
  // @ts-expect-error
93
101
  const tile = node.item;
94
102
  // @ts-expect-error
@@ -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
+ }
@@ -0,0 +1,100 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) vis.gl contributors
3
+
4
+ import type {Tileset3D} from './tileset-3d';
5
+ import type {Tile3D} from './tile-3d';
6
+
7
+ /**
8
+ * A deterministic, renderer-neutral snapshot of one {@link Tileset3D} traversal.
9
+ *
10
+ * Tile IDs are sorted so snapshots can be compared across runs even when child requests complete
11
+ * in a different order. Counts are reported for the completed traversal frame and cache values are
12
+ * expressed in bytes. This is an inspection contract; it does not alter traversal or request policy.
13
+ */
14
+ export type Tileset3DTraversalSnapshot = {
15
+ /** Monotonically increasing traversal frame number. */
16
+ frameNumber: number;
17
+ /** IDs selected for rendering, sorted for stable comparisons. */
18
+ selectedTileIds: string[];
19
+ /** IDs whose content was requested, sorted for stable comparisons. */
20
+ requestedTileIds: string[];
21
+ /** IDs visited as hierarchy-only or empty tiles, sorted for stable comparisons. */
22
+ emptyTileIds: string[];
23
+ /** Number of selected tiles in the completed frame. */
24
+ visibleTileCount: number;
25
+ /** Number of selected tiles with renderable content. */
26
+ renderableTileCount: number;
27
+ /** Number of tile or subtree loads currently in flight. */
28
+ loadingTileCount: number;
29
+ /** Cumulative number of tiles loaded into the runtime cache. */
30
+ loadedTileCount: number;
31
+ /** Cumulative number of failed tile loads. */
32
+ failedTileCount: number;
33
+ /** Number of tiles currently retained in the runtime cache. */
34
+ cachedTileCount: number;
35
+ /** Estimated cached content bytes. */
36
+ cacheBytes: number;
37
+ /** Active memory-adjusted maximum SSE in logical/CSS pixels. */
38
+ maximumScreenSpaceError: number;
39
+ /** Source-specific implicit subtree counters, when the source exposes them. */
40
+ implicitTiling?: {
41
+ /** Number of subtree resources requested from the source. */
42
+ requestedSubtrees: number;
43
+ /** Number of subtree resources successfully materialized. */
44
+ loadedSubtrees: number;
45
+ /** Number of requests served by the parsed-subtree cache. */
46
+ cacheHits: number;
47
+ /** Number of parsed subtrees currently retained for reuse. */
48
+ cachedSubtrees: number;
49
+ /** Number of subtree requests currently in flight. */
50
+ pendingSubtrees: number;
51
+ /** Number of runtime tile headers created from materialized subtrees. */
52
+ materializedTiles: number;
53
+ };
54
+ };
55
+
56
+ /**
57
+ * Creates a stable observability snapshot from public {@link Tileset3D} state.
58
+ *
59
+ * The helper intentionally reads only public runtime fields and source diagnostics. It is safe to
60
+ * call from an instrumentation loop and does not retain tile or content references.
61
+ *
62
+ * @param tileset - Runtime whose most recent traversal should be inspected.
63
+ * @returns A serializable snapshot suitable for logs, regression fixtures, and benchmark output.
64
+ */
65
+ export function getTileset3DTraversalSnapshot(tileset: Tileset3D): Tileset3DTraversalSnapshot {
66
+ const selectedTiles = tileset.selectedTiles.slice();
67
+ const requestedTiles = tileset.requestedTiles.slice();
68
+ const emptyTiles = tileset.emptyTiles.slice();
69
+
70
+ const getTileIds = (tiles: readonly Tile3D[]): string[] =>
71
+ tiles.map(tile => String(tile.id)).sort();
72
+
73
+ const getStatCount = (name: string): number => {
74
+ const count = tileset.stats.get(name).count;
75
+ return typeof count === 'number' && Number.isFinite(count) ? count : 0;
76
+ };
77
+
78
+ const implicitTilingSource = tileset.source as Tileset3D['source'] & {
79
+ getImplicitTilingStats?: () => Tileset3DTraversalSnapshot['implicitTiling'];
80
+ };
81
+ const implicitTiling = implicitTilingSource.getImplicitTilingStats?.();
82
+
83
+ return {
84
+ frameNumber: tileset.frameNumber,
85
+ selectedTileIds: getTileIds(selectedTiles),
86
+ requestedTileIds: getTileIds(requestedTiles),
87
+ emptyTileIds: getTileIds(emptyTiles),
88
+ visibleTileCount: selectedTiles.length,
89
+ renderableTileCount: selectedTiles.filter(
90
+ tile => tile.contentAvailable && Boolean(tile.content)
91
+ ).length,
92
+ loadingTileCount: getStatCount('Tiles Loading'),
93
+ loadedTileCount: getStatCount('Tiles Loaded'),
94
+ failedTileCount: getStatCount('Failed Tile Loads'),
95
+ cachedTileCount: getStatCount('Tiles In Memory'),
96
+ cacheBytes: tileset.gpuMemoryUsageInBytes,
97
+ maximumScreenSpaceError: tileset.memoryAdjustedScreenSpaceError,
98
+ ...(implicitTiling ? {implicitTiling} : {})
99
+ };
100
+ }
@@ -2,13 +2,15 @@
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';
9
9
  import type {Tileset3D} from './tileset-3d';
10
10
  import type {FrameState} from '../helpers/frame-state';
11
11
  import type {TILESET_TYPE} from '../../constants';
12
+ import type {TilesetSpatialReference} from '../../spatial/spatial-types';
13
+ import type {Tile3DContent} from './tile-3d-contracts';
12
14
 
13
15
  /**
14
16
  * Parsed top-level tileset payload consumed by {@link Tileset3D} or a {@link Tileset3DSource}.
@@ -23,18 +25,14 @@ export type TilesetSourceResolver = {
23
25
  /**
24
26
  * Loads and parses the root metadata resource for a source.
25
27
  */
26
- loadRoot<DataT>(
27
- url: string,
28
- loader: LoaderWithParser<DataT>,
29
- loadOptions: LoaderOptions
30
- ): Promise<DataT>;
28
+ loadRoot<DataT>(url: string, loader: Loader<DataT>, loadOptions: LoaderOptions): Promise<DataT>;
31
29
 
32
30
  /**
33
31
  * Loads and parses an arbitrary source-relative resource.
34
32
  */
35
33
  loadResource<DataT>(
36
34
  url: string,
37
- loader: LoaderWithParser<DataT>,
35
+ loader: Loader<DataT>,
38
36
  loadOptions: LoaderOptions
39
37
  ): Promise<DataT>;
40
38
  };
@@ -46,7 +44,7 @@ export type TilesetSourceRequest = {
46
44
  /** Absolute root metadata URL. */
47
45
  url: string;
48
46
  /** Loader used to fetch and parse the root metadata. */
49
- loader: LoaderWithParser;
47
+ loader: Loader;
50
48
  /** Optional base path override for relative resources. */
51
49
  basePath?: string;
52
50
  /** Optional resource-loading strategy used for archive-backed datasets. */
@@ -69,8 +67,24 @@ export type TilesetSourceInput = TilesetJSON | TilesetSourceRequest;
69
67
  export type TileContentLoadResult = {
70
68
  /** Whether the tile content request produced usable content. */
71
69
  loaded: boolean;
70
+ /** All payloads loaded for a tile, in source order. */
71
+ contents?: any[];
72
+ /** Optional normalized renderer-neutral descriptors for each content entry. */
73
+ contentEntries?: Tile3DContent[];
72
74
  /** Nested tileset metadata to install below the loaded tile, if any. */
73
75
  nestedTileset?: TilesetJSON;
76
+ /** Nested tilesets found among multiple content payloads. */
77
+ nestedTilesets?: TilesetJSON[];
78
+ };
79
+
80
+ /** Result of a source-managed lazy child-header request. */
81
+ export type TileChildrenLoadResult = {
82
+ /** Whether a subtree resource was loaded and installed. */
83
+ loaded: boolean;
84
+ /** Number of available tiles described by the subtree, including its existing root placeholder. */
85
+ tileCount: number;
86
+ /** Number of lazy child-subtree references installed at the new boundary. */
87
+ childSubtreeCount: number;
74
88
  };
75
89
 
76
90
  /**
@@ -104,8 +118,8 @@ export type TilesetSourceViewState = {
104
118
  export type TilesetSourceMetadata = {
105
119
  /** Format discriminator. */
106
120
  type: TILESET_TYPE;
107
- /** Loader used for metadata and content requests. */
108
- loader: LoaderWithParser;
121
+ /** Primary format loader used for metadata and default content requests. */
122
+ loader: Loader;
109
123
  /** Absolute root metadata URL. */
110
124
  url: string;
111
125
  /** Base path used for relative resource resolution. */
@@ -118,6 +132,8 @@ export type TilesetSourceMetadata = {
118
132
  lodMetricValue: number;
119
133
  /** Root refinement mode used as fallback by runtime tiles. */
120
134
  refine: string;
135
+ /** Format-discovered coordinate reference system metadata. */
136
+ spatialReference?: TilesetSpatialReference;
121
137
  };
122
138
 
123
139
  /**
@@ -126,8 +142,8 @@ export type TilesetSourceMetadata = {
126
142
  export interface Tileset3DSource {
127
143
  /** Tileset format discriminator. */
128
144
  readonly type: TILESET_TYPE;
129
- /** Loader used for content and metadata requests. */
130
- readonly loader: LoaderWithParser;
145
+ /** Primary format loader used for metadata and default content requests. */
146
+ readonly loader: Loader;
131
147
  /** Absolute URL of the root tileset resource. */
132
148
  readonly url: string;
133
149
  /** Base directory used for relative resource resolution. */
@@ -157,6 +173,17 @@ export interface Tileset3DSource {
157
173
  */
158
174
  initialize(): Promise<void>;
159
175
 
176
+ /**
177
+ * Prepares option-dependent asynchronous source state before runtime headers are constructed.
178
+ *
179
+ * I3S uses this hook to sample elevation surfaces for its root bound after `Tileset3D` has
180
+ * applied spatial options. Sources without asynchronous header preparation omit the hook.
181
+ */
182
+ prepareTileset?(tileset: Tileset3D): Promise<void>;
183
+
184
+ /** Releases source-local caches and prevents late asynchronous work from mutating runtime tiles. */
185
+ destroy?(): void;
186
+
160
187
  /**
161
188
  * Returns normalized source metadata after initialization completes.
162
189
  */
@@ -186,6 +213,25 @@ export interface Tileset3DSource {
186
213
  */
187
214
  loadTileContent(tile: Tile3D): Promise<TileContentLoadResult>;
188
215
 
216
+ /**
217
+ * Loads and installs a complete lazy child-header group for a runtime tile.
218
+ *
219
+ * Sources that implement this hook retain ownership of resource parsing, caching, query
220
+ * inheritance, and archive resolution. Traversal calls it only after visibility and LOD checks.
221
+ */
222
+ loadTileChildren?(tile: Tile3D, frameState: FrameState): Promise<TileChildrenLoadResult>;
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
+
189
235
  /**
190
236
  * Resolves a tile-relative path to the final request URL.
191
237
  */
@@ -204,7 +250,11 @@ export interface Tileset3DSource {
204
250
  /**
205
251
  * Loads child tile metadata on demand for formats that do not expose the full tree up front.
206
252
  */
207
- loadChildTileHeader?(parentTile: Tile3D, childId: string, frameState: FrameState): Promise<any>;
253
+ loadChildTileHeader?(
254
+ parentTile: Tile3D,
255
+ childId: string,
256
+ frameState: FrameState | null
257
+ ): Promise<any>;
208
258
 
209
259
  /**
210
260
  * Updates source-managed bookkeeping after a tile finishes loading.
@@ -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
 
@@ -35,6 +50,8 @@ export class TilesetTraverser {
35
50
  selectedTiles: Record<string, Tile3D> = {};
36
51
  // tiles should be loaded from server
37
52
  requestedTiles: Record<string, Tile3D> = {};
53
+ /** Tiles intentionally held until the camera has remained still for the configured delay. */
54
+ deferredTiles: Record<string, Tile3D> = {};
38
55
  // tiles does not have render content
39
56
  emptyTiles: Record<string, Tile3D> = {};
40
57
 
@@ -56,6 +73,11 @@ export class TilesetTraverser {
56
73
  this.options = {...DEFAULT_PROPS, ...options};
57
74
  }
58
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
+
59
81
  // tiles should be visible
60
82
  traverse(root, frameState, options) {
61
83
  this.root = root; // for root screen space error
@@ -73,6 +95,7 @@ export class TilesetTraverser {
73
95
 
74
96
  reset() {
75
97
  this.requestedTiles = {};
98
+ this.deferredTiles = {};
76
99
  this.selectedTiles = {};
77
100
  this.emptyTiles = {};
78
101
  this._traversalStack.reset();
@@ -137,7 +160,9 @@ export class TilesetTraverser {
137
160
  // Always load tiles in the base traversal
138
161
  // Select tiles that can't refine further
139
162
  this.loadTile(tile, frameState);
140
- if (stoppedRefining) {
163
+ // Skip-LOD keeps a ready ancestor selected while descendants stream in. This prevents
164
+ // replacement holes when traversal jumps over one or more hierarchy levels.
165
+ if (stoppedRefining || this.isSkipLevelOfDetailEnabled()) {
141
166
  this.selectTile(tile, frameState);
142
167
  }
143
168
  }
@@ -149,6 +174,11 @@ export class TilesetTraverser {
149
174
  tile._shouldRefine = shouldRefine && parentRefines;
150
175
  }
151
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 {
152
182
  const newTime = new Date().getTime();
153
183
  if (this.traversalFinished(frameState) || newTime - this.lastUpdate > this.updateDebounceTime) {
154
184
  this.lastUpdate = newTime;
@@ -165,7 +195,8 @@ export class TilesetTraverser {
165
195
 
166
196
  /* eslint-disable complexity, max-statements */
167
197
  updateAndPushChildren(tile: Tile3D, frameState: FrameState, stack, depth): boolean {
168
- const {loadSiblings, skipLevelOfDetail} = this.options;
198
+ const {loadSiblings} = this.options;
199
+ const skipLevelOfDetail = this.isSkipLevelOfDetailEnabled();
169
200
 
170
201
  const children = tile.children;
171
202
 
@@ -188,9 +219,10 @@ export class TilesetTraverser {
188
219
  }
189
220
  stack.push(child);
190
221
  hasVisibleChild = true;
191
- } else if (checkRefines || loadSiblings) {
192
- // Keep non-visible children loaded since they are still needed before the parent can refine.
193
- // Or loadSiblings is true so always load tiles regardless of visibility.
222
+ } else if ((checkRefines || loadSiblings) && child._inRequestVolume) {
223
+ // Keep non-visible children loaded when they are inside their viewer request volume;
224
+ // they may still be needed before a replacement parent can refine. The request-volume
225
+ // check intentionally applies to sibling loading as well, preventing off-volume work.
194
226
  this.loadTile(child, frameState);
195
227
  this.touchTile(child, frameState);
196
228
  }
@@ -204,11 +236,9 @@ export class TilesetTraverser {
204
236
  } else {
205
237
  childRefines = child.contentAvailable;
206
238
  }
239
+ // Readiness controls replacement, not discovery. Visit every sibling so
240
+ // one unavailable child cannot serialize requests or hide ready siblings.
207
241
  refines = refines && childRefines;
208
-
209
- if (!refines) {
210
- return false;
211
- }
212
242
  }
213
243
  }
214
244
 
@@ -225,7 +255,7 @@ export class TilesetTraverser {
225
255
 
226
256
  // tile to render in the browser
227
257
  selectTile(tile: Tile3D, frameState: FrameState): void {
228
- if (this.shouldSelectTile(tile)) {
258
+ if (this.shouldSelectTile(tile, frameState)) {
229
259
  // The tile can be selected right away and does not require traverseAndSelect
230
260
  tile._selectedFrame = frameState.frameNumber;
231
261
  this.selectedTiles[tile.id] = tile;
@@ -235,12 +265,30 @@ export class TilesetTraverser {
235
265
  // tile to load from server
236
266
  loadTile(tile: Tile3D, frameState: FrameState): void {
237
267
  if (this.shouldLoadTile(tile)) {
268
+ if (this.shouldDeferTileRequest(tile)) {
269
+ this.deferredTiles[tile.id] = tile;
270
+ return;
271
+ }
238
272
  tile._requestedFrame = frameState.frameNumber;
239
273
  tile._priority = tile._getPriority();
240
274
  this.requestedTiles[tile.id] = tile;
241
275
  }
242
276
  }
243
277
 
278
+ /**
279
+ * Returns whether an eligible peripheral request should wait for camera motion to settle.
280
+ *
281
+ * Eligibility and the current time window are calculated together when tile visibility updates.
282
+ * Keeping the active state on the tile also lets a queued scheduler callback cancel work that
283
+ * became deferred after its original traversal.
284
+ *
285
+ * @param tile - Tile considered for loading.
286
+ * @returns `true` when the request should be retried after the remaining delay.
287
+ */
288
+ shouldDeferTileRequest(tile: Tile3D): boolean {
289
+ return tile.priorityDeferred;
290
+ }
291
+
244
292
  // cache tile
245
293
  touchTile(tile: Tile3D, frameState: FrameState): void {
246
294
  tile.tileset._cache.touch(tile);
@@ -271,10 +319,13 @@ export class TilesetTraverser {
271
319
  return tile.hasUnloadedContent || tile.contentExpired;
272
320
  }
273
321
 
274
- shouldSelectTile(tile: Tile3D): boolean {
275
- // if select tile is in current frame
276
- // and content available
277
- return tile.contentAvailable && !this.options.skipLevelOfDetail;
322
+ /**
323
+ * Returns whether a tile's content is available and visible for rendering.
324
+ * @param tile Tile considered for rendering.
325
+ * @param frameState Current culling state.
326
+ */
327
+ shouldSelectTile(tile: Tile3D, frameState: FrameState): boolean {
328
+ return tile.contentAvailable && tile.contentVisibility(frameState) !== 'outside';
278
329
  }
279
330
 
280
331
  /** Decide if tile LoD (level of detail) is not sufficient under current viewport */
@@ -323,6 +374,7 @@ export class TilesetTraverser {
323
374
  // Ignores visibility.
324
375
  executeEmptyTraversal(root: Tile3D, frameState: FrameState): boolean {
325
376
  let allDescendantsLoaded = true;
377
+ let hasPendingLazyChildren = false;
326
378
  const stack = this._emptyTraversalStack;
327
379
  stack.push(root);
328
380
 
@@ -331,6 +383,12 @@ export class TilesetTraverser {
331
383
 
332
384
  const traverse = !tile.hasRenderContent && this.canTraverse(tile, frameState);
333
385
  const emptyLeaf = !tile.hasRenderContent && tile.children.length === 0;
386
+ if (tile.header.implicitSubtree) {
387
+ // An unresolved implicit boundary is not a proven empty leaf. Treating it as loaded would
388
+ // allow REPLACE traversal to drop its ancestor before availability and content are known.
389
+ hasPendingLazyChildren = true;
390
+ allDescendantsLoaded = false;
391
+ }
334
392
 
335
393
  // Traversal stops but the tile does not have content yet
336
394
  // There will be holes if the parent tries to refine to its children, so don't refine
@@ -353,6 +411,6 @@ export class TilesetTraverser {
353
411
  }
354
412
  }
355
413
  }
356
- return root.hasEmptyContent || allDescendantsLoaded;
414
+ return (root.hasEmptyContent && !hasPendingLazyChildren) || allDescendantsLoaded;
357
415
  }
358
416
  }