@loaders.gl/tiles 5.0.0-alpha.10 → 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 (41) hide show
  1. package/dist/dist.dev.js +8975 -8931
  2. package/dist/dist.min.js +3 -3
  3. package/dist/index.cjs +122 -57
  4. package/dist/index.cjs.map +2 -2
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/spatial/i3s-spatial-transformer.d.ts +16 -0
  9. package/dist/spatial/i3s-spatial-transformer.d.ts.map +1 -1
  10. package/dist/spatial/i3s-spatial-transformer.js +45 -0
  11. package/dist/spatial/i3s-spatial-transformer.js.map +1 -1
  12. package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -1
  13. package/dist/spatial/spatial-coordinate-transformer.js +8 -8
  14. package/dist/spatial/spatial-coordinate-transformer.js.map +1 -1
  15. package/dist/spatial/spatial-resource-registry.d.ts +3 -0
  16. package/dist/spatial/spatial-resource-registry.d.ts.map +1 -1
  17. package/dist/spatial/spatial-resource-registry.js +12 -3
  18. package/dist/spatial/spatial-resource-registry.js.map +1 -1
  19. package/dist/spatial/tiles-3d-spatial-transformer.d.ts.map +1 -1
  20. package/dist/spatial/tiles-3d-spatial-transformer.js +0 -12
  21. package/dist/spatial/tiles-3d-spatial-transformer.js.map +1 -1
  22. package/dist/tileset-3d/common/tileset-3d.d.ts +8 -1
  23. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  24. package/dist/tileset-3d/common/tileset-3d.js +42 -9
  25. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  26. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
  27. package/dist/tileset-3d/common/tileset-traverser.js +2 -3
  28. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
  29. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +1 -0
  30. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
  31. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +12 -4
  32. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
  33. package/package.json +11 -11
  34. package/src/index.ts +2 -1
  35. package/src/spatial/i3s-spatial-transformer.ts +64 -0
  36. package/src/spatial/spatial-coordinate-transformer.ts +9 -8
  37. package/src/spatial/spatial-resource-registry.ts +14 -3
  38. package/src/spatial/tiles-3d-spatial-transformer.ts +0 -12
  39. package/src/tileset-3d/common/tileset-3d.ts +54 -12
  40. package/src/tileset-3d/common/tileset-traverser.ts +2 -4
  41. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +16 -4
@@ -6,8 +6,8 @@ import type {Geoid} from '@math.gl/geoid';
6
6
  import {Vector3} from '@math.gl/core';
7
7
  import {Ellipsoid} from '@math.gl/geospatial';
8
8
  import type {ReadonlyCRSDefinition} from '@math.gl/crs';
9
- import {Projection} from '@math.gl/projection';
10
- import {getGeoidModel} from './spatial-resource-registry';
9
+ import type {Projection} from '@math.gl/projection';
10
+ import {getGeoidModel, getSpatialProjectionEngine} from './spatial-resource-registry';
11
11
  import {
12
12
  normalizeCrsIdentifier,
13
13
  WGS84_GEOCENTRIC_CRS,
@@ -64,6 +64,7 @@ export class SpatialCoordinateTransformer {
64
64
  */
65
65
  constructor(spatialReference: TilesetSpatialReference, options: TilesetSpatialOptions = {}) {
66
66
  this.spatialReference = spatialReference;
67
+ const projectionEngine = getSpatialProjectionEngine();
67
68
  validateTransformRequest(spatialReference);
68
69
  const outputCrs = spatialReference.targetCrs || spatialReference.sourceCrs;
69
70
  this.sourceIsGeocentric = isWgs84Geocentric(spatialReference.sourceCrs);
@@ -77,7 +78,7 @@ export class SpatialCoordinateTransformer {
77
78
  !this.sourceIsGeocentric &&
78
79
  !this.outputIsGeocentric
79
80
  ) {
80
- this.horizontalProjection = new Projection({
81
+ this.horizontalProjection = projectionEngine.createProjection({
81
82
  from: getHorizontalProjectionDefinition(spatialReference.sourceCrs),
82
83
  to: getHorizontalProjectionDefinition(spatialReference.targetCrs),
83
84
  enforceAxis: false
@@ -90,14 +91,14 @@ export class SpatialCoordinateTransformer {
90
91
  this.outputIsGeocentric
91
92
  ) {
92
93
  if (!this.sourceIsGeographic && !this.sourceIsGeocentric) {
93
- this.geographicProjection = new Projection({
94
+ this.geographicProjection = projectionEngine.createProjection({
94
95
  from: getHorizontalProjectionDefinition(spatialReference.sourceCrs),
95
96
  to: WGS84_GEOGRAPHIC_CRS,
96
97
  enforceAxis: false
97
98
  });
98
99
  }
99
100
  if (!this.outputIsGeographic && !this.outputIsGeocentric) {
100
- this.heightOutputProjection = new Projection({
101
+ this.heightOutputProjection = projectionEngine.createProjection({
101
102
  from: WGS84_GEOGRAPHIC_CRS,
102
103
  to: getHorizontalProjectionDefinition(outputCrs),
103
104
  enforceAxis: false
@@ -153,7 +154,7 @@ export class SpatialCoordinateTransformer {
153
154
  }
154
155
 
155
156
  if (this.horizontalProjection) {
156
- const projected = this.horizontalProjection.project(result.slice(0, 3));
157
+ const projected = this.horizontalProjection.projectSync(result.slice(0, 3));
157
158
  result.splice(0, projected.length, ...projected);
158
159
  }
159
160
  return result;
@@ -220,7 +221,7 @@ export class SpatialCoordinateTransformer {
220
221
  if (this.sourceIsGeographic) {
221
222
  return coordinate.slice(0, 3);
222
223
  }
223
- return this.geographicProjection!.project(coordinate.slice(0, 3));
224
+ return this.geographicProjection!.projectSync(coordinate.slice(0, 3));
224
225
  }
225
226
 
226
227
  /** Convert one conventional WGS84 geographic coordinate to the selected output CRS. */
@@ -231,7 +232,7 @@ export class SpatialCoordinateTransformer {
231
232
  if (this.outputIsGeographic) {
232
233
  return coordinate.slice(0, 3);
233
234
  }
234
- return this.heightOutputProjection!.project(coordinate.slice(0, 3));
235
+ return this.heightOutputProjection!.projectSync(coordinate.slice(0, 3));
235
236
  }
236
237
  }
237
238
 
@@ -3,10 +3,19 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {Geoid, parsePGM} from '@math.gl/geoid';
6
- import {Projection} from '@math.gl/projection';
6
+ import {FullProjectionEngine, parseNTv2Grid} from '@math.gl/projection';
7
+ import type {DatumGrid, ProjectionEngine} from '@math.gl/projection';
7
8
  import type {ReadonlyCRSDefinition} from '@math.gl/crs';
8
9
 
9
10
  const geoidModels = new Map<string, Geoid>();
11
+ const projectionAliases: Record<string, ReadonlyCRSDefinition> = {};
12
+ const datumGrids: Record<string, DatumGrid> = {};
13
+ let spatialProjectionEngine: ProjectionEngine = new FullProjectionEngine();
14
+
15
+ /** Returns the projection engine containing application-registered spatial resources. */
16
+ export function getSpatialProjectionEngine(): ProjectionEngine {
17
+ return spatialProjectionEngine;
18
+ }
10
19
 
11
20
  /**
12
21
  * Registers an application-supplied CRS alias for deterministic coordinate transformations.
@@ -18,7 +27,8 @@ const geoidModels = new Map<string, Geoid>();
18
27
  */
19
28
  export function registerSpatialCrs(name: string, definition: ReadonlyCRSDefinition): void {
20
29
  validateResourceName(name, 'CRS');
21
- Projection.defineProjectionAliases({[name]: definition});
30
+ projectionAliases[name] = definition;
31
+ spatialProjectionEngine = new FullProjectionEngine({aliases: projectionAliases, datumGrids});
22
32
  }
23
33
 
24
34
  /**
@@ -29,7 +39,8 @@ export function registerSpatialCrs(name: string, definition: ReadonlyCRSDefiniti
29
39
  */
30
40
  export function registerSpatialDatumGrid(name: string, data: ArrayBuffer): void {
31
41
  validateResourceName(name, 'datum grid');
32
- Projection.registerDatumGrid(name, data);
42
+ datumGrids[name] = parseNTv2Grid(data);
43
+ spatialProjectionEngine = new FullProjectionEngine({aliases: projectionAliases, datumGrids});
33
44
  }
34
45
 
35
46
  /**
@@ -3,9 +3,7 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {SpatialCoordinateTransformer} from './spatial-coordinate-transformer';
6
- import {getSpatialCoordinateFrame} from './get-spatial-coordinate-frame';
7
6
  import {Matrix4} from '@math.gl/core';
8
- import {Ellipsoid} from '@math.gl/geospatial';
9
7
  import type {TilesetSpatialOptions, TilesetSpatialReference} from './spatial-types';
10
8
 
11
9
  /** Structural 3D Tiles volume shape used by the source adapter. */
@@ -80,16 +78,6 @@ export class Tiles3DSpatialTransformer {
80
78
 
81
79
  /** Transforms one WGS84 region sample into the selected output frame. */
82
80
  private transformRegionSample(sample: number[]): number[] {
83
- const [longitudeDegrees, latitudeDegrees, height] = sample;
84
- const targetCrs = this.spatialReference.targetCrs || this.spatialReference.sourceCrs;
85
- if (targetCrs && getSpatialCoordinateFrame(targetCrs) === 'geocentric') {
86
- const cartesian = Ellipsoid.WGS84.cartographicToCartesian([
87
- longitudeDegrees,
88
- latitudeDegrees,
89
- height
90
- ]);
91
- return Array.from(cartesian);
92
- }
93
81
  return this.geographicCoordinateTransformer.transformPosition(sample);
94
82
  }
95
83
  }
@@ -73,7 +73,7 @@ export type Tileset3DProps = {
73
73
  /**
74
74
  * Enables replacement traversal that may skip hierarchy levels while retaining ready ancestors
75
75
  * as coverage. This trades temporary overdraw for faster refinement on deep trees.
76
- * @default false
76
+ * Defaults to `true` for 3D Tiles and `false` for I3S. Set to `false` to request every level.
77
77
  */
78
78
  skipLevelOfDetail?: boolean;
79
79
  /**
@@ -393,7 +393,8 @@ export class Tileset3D {
393
393
  _cache = new TilesetCache();
394
394
  _requestScheduler: RequestScheduler;
395
395
 
396
- private _heldTiles: Set<string> = new Set();
396
+ /** Drawn fallback instances retained independently for each viewport tree. */
397
+ private _heldTiles: Set<Tile3D> = new Set();
397
398
  private updatePromise: Promise<number> | null = null;
398
399
  tilesetInitializationPromise: Promise<void>;
399
400
 
@@ -425,6 +426,7 @@ export class Tileset3D {
425
426
  ...suppliedOptions,
426
427
  cacheBytes,
427
428
  maximumCacheOverflowBytes,
429
+ skipLevelOfDetail: suppliedOptions.skipLevelOfDetail ?? source.type === TILESET_TYPE.TILES3D,
428
430
  memoryAdjustedScreenSpaceError:
429
431
  suppliedOptions.memoryAdjustedScreenSpaceError ?? usesTiles3DCacheDefaults
430
432
  };
@@ -782,28 +784,50 @@ export class Tileset3D {
782
784
 
783
785
  this.selectedTiles = this.options.onTraversalComplete(this.selectedTiles);
784
786
 
785
- const selectedIds = new Set(this.selectedTiles.map(tile => tile.id));
787
+ const selectedTiles = new Set(this.selectedTiles);
786
788
  const hasUndrawnTiles = this.selectedTiles.some(tile => !tile.tileDrawn);
787
789
 
788
790
  let heldBackCount = 0;
789
791
  if (hasUndrawnTiles) {
790
- for (const tileId of selectedIds) {
791
- this._heldTiles.add(tileId);
792
+ const activeRoots = new Set(
793
+ Object.keys(this.frameStateData).map(viewportId => this.roots[viewportId])
794
+ );
795
+ const descendantReadiness = new Map<Tile3D, boolean>();
796
+ for (const tile of this.selectedTiles) {
797
+ for (let ancestor = tile.parent; ancestor; ancestor = ancestor.parent) {
798
+ descendantReadiness.set(
799
+ ancestor,
800
+ (descendantReadiness.get(ancestor) ?? true) && tile.tileDrawn
801
+ );
802
+ }
792
803
  }
793
- for (const tileId of this._heldTiles) {
794
- if (selectedIds.has(tileId)) continue;
795
-
796
- const tile = this._tiles[tileId];
797
- if (tile && tile.contentAvailable) {
804
+ // Drawing can finish between traversals. Promote only previously selected
805
+ // geometry that actually drew; pending tiles cannot provide visible fallback.
806
+ for (const tile of previousSelectedTiles) {
807
+ if (tile.tileDrawn) this._heldTiles.add(tile);
808
+ }
809
+ for (const tile of this._heldTiles) {
810
+ let root = tile;
811
+ while (root.parent) root = root.parent;
812
+ // Removed viewport trees cannot contribute fallback to the remaining views.
813
+ if (!activeRoots.has(root)) {
814
+ this._heldTiles.delete(tile);
815
+ continue;
816
+ }
817
+ if (selectedTiles.has(tile)) continue;
818
+ if (
819
+ tile.contentAvailable &&
820
+ !this._hasDrawnReplacement(tile, selectedTiles, descendantReadiness)
821
+ ) {
798
822
  tile._selectedFrame = this._frameNumber;
799
823
  this.selectedTiles.push(tile);
800
824
  heldBackCount++;
801
825
  } else {
802
- this._heldTiles.delete(tileId);
826
+ this._heldTiles.delete(tile);
803
827
  }
804
828
  }
805
829
  } else {
806
- this._heldTiles = selectedIds;
830
+ this._heldTiles = selectedTiles;
807
831
  }
808
832
 
809
833
  if (heldBackCount > 0) {
@@ -825,6 +849,24 @@ export class Tileset3D {
825
849
  }
826
850
  }
827
851
 
852
+ /**
853
+ * Releases fallback geometry once its own selected replacement has drawn.
854
+ * Unrelated pending branches must not delay this region's handoff. When no related
855
+ * replacement is selected, retain the fallback until the complete selection has drawn.
856
+ */
857
+ private _hasDrawnReplacement(
858
+ tile: Tile3D,
859
+ selectedTiles: Set<Tile3D>,
860
+ descendantReadiness: Map<Tile3D, boolean>
861
+ ): boolean {
862
+ for (let ancestor = tile.parent; ancestor; ancestor = ancestor.parent) {
863
+ if (selectedTiles.has(ancestor)) {
864
+ return ancestor.tileDrawn;
865
+ }
866
+ }
867
+ return descendantReadiness.get(tile) === true;
868
+ }
869
+
828
870
  _tilesChanged(oldSelectedTiles: Tile3D[], selectedTiles: Tile3D[]): boolean {
829
871
  if (oldSelectedTiles.length !== selectedTiles.length) {
830
872
  return true;
@@ -236,11 +236,9 @@ export class TilesetTraverser {
236
236
  } else {
237
237
  childRefines = child.contentAvailable;
238
238
  }
239
+ // Readiness controls replacement, not discovery. Visit every sibling so
240
+ // one unavailable child cannot serialize requests or hide ready siblings.
239
241
  refines = refines && childRefines;
240
-
241
- if (!refines) {
242
- return false;
243
- }
244
242
  }
245
243
  }
246
244
 
@@ -70,7 +70,9 @@ export class Tileset3DTraverser extends TilesetTraverser {
70
70
  if (!tile.hasRenderContent) {
71
71
  this.emptyTiles[tile.id] = tile;
72
72
  this.loadTile(tile, frameState);
73
- if (stoppedRefining) {
73
+ // A known empty terminal branch needs no fallback geometry. Lazy child groups still
74
+ // report hasChildren while unresolved, so they retain coverage until materialized.
75
+ if (stoppedRefining && (!tile.hasEmptyContent || tile.hasChildren)) {
74
76
  this.selectDesiredTile(tile, frameState);
75
77
  }
76
78
  } else if (tile.refine === TILE_REFINEMENT.ADD) {
@@ -121,7 +123,7 @@ export class Tileset3DTraverser extends TilesetTraverser {
121
123
  if (child.isVisibleAndInRequestVolume) {
122
124
  stack.push(child);
123
125
  hasVisibleChild = true;
124
- } else if (this.options.loadSiblings) {
126
+ } else if (this.options.loadSiblings && child._inRequestVolume) {
125
127
  this.loadTile(child, frameState);
126
128
  this.touchTile(child, frameState);
127
129
  }
@@ -234,6 +236,11 @@ export class Tileset3DTraverser extends TilesetTraverser {
234
236
  while (fallbackTile) {
235
237
  if (this.shouldSelectTile(fallbackTile, frameState)) {
236
238
  this.selectTile(fallbackTile, frameState);
239
+ if (fallbackTile !== tile) {
240
+ // On zoom-out the desired coarse level may still be unavailable. Preserve
241
+ // nearby detail that already drew while the ancestor fills uncovered pixels.
242
+ this.selectLoadedDescendants(tile, frameState, true);
243
+ }
237
244
  return;
238
245
  }
239
246
  fallbackTile = fallbackTile.parent;
@@ -246,8 +253,13 @@ export class Tileset3DTraverser extends TilesetTraverser {
246
253
  *
247
254
  * @param root - Unavailable tile whose descendants are searched.
248
255
  * @param frameState - Current culling state.
256
+ * @param drawnOnly - Reuse drawn detail without starting obsolete GPU work.
249
257
  */
250
- private selectLoadedDescendants(root: Tile3D, frameState: FrameState): void {
258
+ private selectLoadedDescendants(
259
+ root: Tile3D,
260
+ frameState: FrameState,
261
+ drawnOnly: boolean = false
262
+ ): void {
251
263
  const stack: Array<{tile: Tile3D; depth: number}> = [{tile: root, depth: 0}];
252
264
  while (stack.length > 0) {
253
265
  const {tile, depth} = stack.pop() as {tile: Tile3D; depth: number};
@@ -256,7 +268,7 @@ export class Tileset3DTraverser extends TilesetTraverser {
256
268
  if (!child.isVisibleAndInRequestVolume) {
257
269
  continue;
258
270
  }
259
- if (child.contentAvailable) {
271
+ if (this.shouldSelectTile(child, frameState) && (!drawnOnly || child.tileDrawn)) {
260
272
  this.selectTile(child, frameState);
261
273
  this.touchTile(child, frameState);
262
274
  } else if (depth < 1) {