@loaders.gl/tiles 3.2.0-alpha.1 → 3.2.0-alpha.4

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 (50) hide show
  1. package/dist/dist.min.js +121 -19
  2. package/dist/es5/tileset/helpers/zoom.js +32 -0
  3. package/dist/es5/tileset/helpers/zoom.js.map +1 -1
  4. package/dist/es5/tileset/tileset-3d.js +131 -43
  5. package/dist/es5/tileset/tileset-3d.js.map +1 -1
  6. package/dist/es5/tileset/traversers/i3s-frame-counter.js +45 -0
  7. package/dist/es5/tileset/traversers/i3s-frame-counter.js.map +1 -0
  8. package/dist/es5/tileset/traversers/i3s-tile-manager.js +17 -0
  9. package/dist/es5/tileset/traversers/i3s-tile-manager.js.map +1 -1
  10. package/dist/es5/tileset/traversers/i3s-tileset-traverser.js +6 -1
  11. package/dist/es5/tileset/traversers/i3s-tileset-traverser.js.map +1 -1
  12. package/dist/es5/tileset/traversers/tileset-traverser.js +13 -1
  13. package/dist/es5/tileset/traversers/tileset-traverser.js.map +1 -1
  14. package/dist/esm/tileset/helpers/zoom.js +17 -0
  15. package/dist/esm/tileset/helpers/zoom.js.map +1 -1
  16. package/dist/esm/tileset/tileset-3d.js +72 -16
  17. package/dist/esm/tileset/tileset-3d.js.map +1 -1
  18. package/dist/esm/tileset/traversers/i3s-frame-counter.js +23 -0
  19. package/dist/esm/tileset/traversers/i3s-frame-counter.js.map +1 -0
  20. package/dist/esm/tileset/traversers/i3s-tile-manager.js +15 -0
  21. package/dist/esm/tileset/traversers/i3s-tile-manager.js.map +1 -1
  22. package/dist/esm/tileset/traversers/i3s-tileset-traverser.js +5 -1
  23. package/dist/esm/tileset/traversers/i3s-tileset-traverser.js.map +1 -1
  24. package/dist/esm/tileset/traversers/tileset-traverser.js +14 -1
  25. package/dist/esm/tileset/traversers/tileset-traverser.js.map +1 -1
  26. package/dist/tileset/helpers/zoom.d.ts +35 -0
  27. package/dist/tileset/helpers/zoom.d.ts.map +1 -1
  28. package/dist/tileset/helpers/zoom.js +41 -1
  29. package/dist/tileset/tileset-3d.d.ts +17 -3
  30. package/dist/tileset/tileset-3d.d.ts.map +1 -1
  31. package/dist/tileset/tileset-3d.js +72 -21
  32. package/dist/tileset/traversers/i3s-frame-counter.d.ts +24 -0
  33. package/dist/tileset/traversers/i3s-frame-counter.d.ts.map +1 -0
  34. package/dist/tileset/traversers/i3s-frame-counter.js +37 -0
  35. package/dist/tileset/traversers/i3s-tile-manager.d.ts +10 -2
  36. package/dist/tileset/traversers/i3s-tile-manager.d.ts.map +1 -1
  37. package/dist/tileset/traversers/i3s-tile-manager.js +24 -0
  38. package/dist/tileset/traversers/i3s-tileset-traverser.d.ts +9 -2
  39. package/dist/tileset/traversers/i3s-tileset-traverser.d.ts.map +1 -1
  40. package/dist/tileset/traversers/i3s-tileset-traverser.js +10 -1
  41. package/dist/tileset/traversers/tileset-traverser.d.ts +5 -1
  42. package/dist/tileset/traversers/tileset-traverser.d.ts.map +1 -1
  43. package/dist/tileset/traversers/tileset-traverser.js +10 -1
  44. package/package.json +5 -5
  45. package/src/tileset/helpers/zoom.ts +64 -0
  46. package/src/tileset/tileset-3d.ts +84 -21
  47. package/src/tileset/traversers/i3s-frame-counter.ts +35 -0
  48. package/src/tileset/traversers/i3s-tile-manager.ts +26 -2
  49. package/src/tileset/traversers/i3s-tileset-traverser.ts +16 -3
  50. package/src/tileset/traversers/tileset-traverser.ts +13 -2
@@ -1,5 +1,6 @@
1
1
  import {Vector3} from '@math.gl/core';
2
2
  import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
3
+ import {Ellipsoid} from '@math.gl/geospatial';
3
4
  import {BoundingRectangle} from '../../types';
4
5
 
5
6
  const WGS84_RADIUS_X = 6378137.0;
@@ -42,6 +43,69 @@ export function getZoomFromBoundingVolume(
42
43
  return 1;
43
44
  }
44
45
 
46
+ /**
47
+ * Calculate initial zoom for the tileset from 3D `fullExtent` defined in
48
+ * the tileset metadata
49
+ * @param fullExtent - 3D extent of the tileset
50
+ * @param fullExtent.xmin - minimal longitude in decimal degrees
51
+ * @param fullExtent.xmax - maximal longitude in decimal degrees
52
+ * @param fullExtent.ymin - minimal latitude in decimal degrees
53
+ * @param fullExtent.ymax - maximal latitude in decimal degrees
54
+ * @param fullExtent.zmin - minimal elevation in meters
55
+ * @param fullExtent.zmax - maximal elevation in meters
56
+ * @param cartorgraphicCenter - tileset center in cartographic coordinate system
57
+ * @param cartesianCenter - tileset center in cartesian coordinate system
58
+ * @returns - initial zoom for the tileset
59
+ */
60
+ export function getZoomFromFullExtent(
61
+ fullExtent: {
62
+ xmin: number;
63
+ xmax: number;
64
+ ymin: number;
65
+ ymax: number;
66
+ zmin: number;
67
+ zmax: number;
68
+ },
69
+ cartorgraphicCenter: Vector3,
70
+ cartesianCenter: Vector3
71
+ ) {
72
+ const extentVertex = Ellipsoid.WGS84.cartographicToCartesian(
73
+ [fullExtent.xmax, fullExtent.ymax, fullExtent.zmax],
74
+ new Vector3()
75
+ );
76
+ const extentSize = Math.sqrt(
77
+ Math.pow(extentVertex[0] - cartesianCenter[0], 2) +
78
+ Math.pow(extentVertex[1] - cartesianCenter[1], 2) +
79
+ Math.pow(extentVertex[2] - cartesianCenter[2], 2)
80
+ );
81
+ return Math.log2(WGS84_RADIUS_Z / (extentSize + cartorgraphicCenter[2]));
82
+ }
83
+
84
+ /**
85
+ * Calculate initial zoom for the tileset from 2D `extent` defined in
86
+ * the tileset metadata
87
+ * @param extent - 2D extent of the tileset. It is array of 4 elements [xmin, ymin, xmax, ymax]
88
+ * @param extent[0] - minimal longitude in decimal degrees
89
+ * @param extent[1] - minimal latitude in decimal degrees
90
+ * @param extent[2] - maximal longitude in decimal degrees
91
+ * @param extent[3] - maximal latitude in decimal degrees
92
+ * @param cartorgraphicCenter - tileset center in cartographic coordinate system
93
+ * @param cartesianCenter - tileset center in cartesian coordinate system
94
+ * @returns - initial zoom for the tileset
95
+ */
96
+ export function getZoomFromExtent(
97
+ extent: [number, number, number, number],
98
+ cartorgraphicCenter: Vector3,
99
+ cartesianCenter: Vector3
100
+ ) {
101
+ const [xmin, ymin, xmax, ymax] = extent;
102
+ return getZoomFromFullExtent(
103
+ {xmin, xmax, ymin, ymax, zmin: 0, zmax: 0},
104
+ cartorgraphicCenter,
105
+ cartesianCenter
106
+ );
107
+ }
108
+
45
109
  function getObbSize(halfAxes) {
46
110
  halfAxes.getColumn(0, scratchVector);
47
111
  const axeY = halfAxes.getColumn(1);
@@ -49,7 +49,7 @@ import {
49
49
  import TilesetCache from './tileset-cache';
50
50
  import {calculateTransformProps} from './helpers/transform-utils';
51
51
  import {FrameState, getFrameState, limitSelectedTiles} from './helpers/frame-state';
52
- import {getZoomFromBoundingVolume} from './helpers/zoom';
52
+ import {getZoomFromBoundingVolume, getZoomFromExtent, getZoomFromFullExtent} from './helpers/zoom';
53
53
  import Tile3D from './tile-3d';
54
54
  import Tileset3DTraverser from './traversers/tileset-3d-traverser';
55
55
  import TilesetTraverser from './traversers/tileset-traverser';
@@ -157,7 +157,6 @@ const DEFAULT_PROPS: Props = {
157
157
  // View distance scale modifier
158
158
  viewDistanceScale: 1.0,
159
159
 
160
- // TODO CESIUM
161
160
  // The maximum screen space error used to drive level of detail refinement.
162
161
  maximumScreenSpaceError: 8,
163
162
 
@@ -182,7 +181,7 @@ const TILES_LOADED = 'Tiles Loaded';
182
181
  const TILES_LOADING = 'Tiles Loading';
183
182
  const TILES_UNLOADED = 'Tiles Unloaded';
184
183
  const TILES_LOAD_FAILED = 'Failed Tile Loads';
185
- const POINTS_COUNT = 'Points';
184
+ const POINTS_COUNT = 'Points/Vertices';
186
185
  const TILES_GPU_MEMORY = 'Tile Memory Use';
187
186
 
188
187
  export default class Tileset3D {
@@ -216,6 +215,7 @@ export default class Tileset3D {
216
215
  geometricError: number;
217
216
  selectedTiles: Tile3D[];
218
217
  private updatePromise: Promise<number> | null = null;
218
+ tilesetInitializationPromise: Promise<void>;
219
219
 
220
220
  cartographicCenter: Vector3 | null;
221
221
  cartesianCenter: Vector3 | null;
@@ -333,7 +333,7 @@ export default class Tileset3D {
333
333
  this.credits = {};
334
334
  this.description = this.options.description || '';
335
335
 
336
- this._initializeTileSet(json);
336
+ this.tilesetInitializationPromise = this._initializeTileSet(json);
337
337
  }
338
338
 
339
339
  /** Release resources */
@@ -344,7 +344,7 @@ export default class Tileset3D {
344
344
  /** Is the tileset loaded (update needs to have been called at least once) */
345
345
  isLoaded(): boolean {
346
346
  // Check that `_frameNumber !== 0` which means that update was called at least once
347
- return this._pendingCount === 0 && this._frameNumber !== 0;
347
+ return this._pendingCount === 0 && this._frameNumber !== 0 && this._requestedTiles.length === 0;
348
348
  }
349
349
 
350
350
  get tiles(): object[] {
@@ -394,12 +394,15 @@ export default class Tileset3D {
394
394
  * @deprecated
395
395
  */
396
396
  update(viewports: any[] | null = null) {
397
- if (!viewports && this.lastUpdatedVieports) {
398
- viewports = this.lastUpdatedVieports;
399
- } else {
400
- this.lastUpdatedVieports = viewports;
401
- }
402
- this.doUpdate(viewports);
397
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
398
+ this.tilesetInitializationPromise.then(() => {
399
+ if (!viewports && this.lastUpdatedVieports) {
400
+ viewports = this.lastUpdatedVieports;
401
+ } else {
402
+ this.lastUpdatedVieports = viewports;
403
+ }
404
+ this.doUpdate(viewports);
405
+ });
403
406
  }
404
407
 
405
408
  /**
@@ -409,6 +412,7 @@ export default class Tileset3D {
409
412
  * @returns Promise of new frameNumber
410
413
  */
411
414
  async selectTiles(viewports: any[] | null = null): Promise<number> {
415
+ await this.tilesetInitializationPromise;
412
416
  if (viewports) {
413
417
  this.lastUpdatedVieports = viewports;
414
418
  }
@@ -580,6 +584,9 @@ export default class Tileset3D {
580
584
  tilesRenderable++;
581
585
  if (tile.content.pointCount) {
582
586
  pointsRenderable += tile.content.pointCount;
587
+ } else {
588
+ // Calculate vertices for non point cloud tiles.
589
+ pointsRenderable += tile.content.vertexCount;
583
590
  }
584
591
  }
585
592
  }
@@ -589,29 +596,78 @@ export default class Tileset3D {
589
596
  this.stats.get(POINTS_COUNT).count = pointsRenderable;
590
597
  }
591
598
 
592
- _initializeTileSet(tilesetJson) {
599
+ async _initializeTileSet(tilesetJson) {
600
+ if (this.type === TILESET_TYPE.I3S) {
601
+ this.calculateViewPropsI3S();
602
+ tilesetJson.root = await tilesetJson.root;
603
+ }
593
604
  this.root = this._initializeTileHeaders(tilesetJson, null);
594
605
 
595
- // TODO CESIUM Specific
596
606
  if (this.type === TILESET_TYPE.TILES3D) {
597
- this._initializeCesiumTileset(tilesetJson);
607
+ this._initializeTiles3DTileset(tilesetJson);
608
+ this.calculateViewPropsTiles3D();
598
609
  }
599
610
 
600
611
  if (this.type === TILESET_TYPE.I3S) {
601
612
  this._initializeI3STileset();
602
613
  }
603
- // Calculate cartographicCenter & zoom props to help apps center view on tileset
604
- this._calculateViewProps();
605
614
  }
606
615
 
607
- // Called during initialize Tileset to initialize the tileset's cartographic center (longitude, latitude) and zoom.
608
- _calculateViewProps() {
616
+ /**
617
+ * Called during initialize Tileset to initialize the tileset's cartographic center (longitude, latitude) and zoom.
618
+ * These metrics help apps center view on tileset
619
+ * For I3S there is extent (<1.8 version) or fullExtent (>=1.8 version) to calculate view props
620
+ * @returns
621
+ */
622
+ private calculateViewPropsI3S() {
623
+ // for I3S 1.8 try to calculate with fullExtent
624
+ const fullExtent = this.tileset.fullExtent;
625
+ if (fullExtent) {
626
+ const {xmin, xmax, ymin, ymax, zmin, zmax} = fullExtent;
627
+ this.cartographicCenter = new Vector3(
628
+ xmin + (xmax - xmin) / 2,
629
+ ymin + (ymax - ymin) / 2,
630
+ zmin + (zmax - zmin) / 2
631
+ );
632
+ this.cartesianCenter = Ellipsoid.WGS84.cartographicToCartesian(
633
+ this.cartographicCenter,
634
+ new Vector3()
635
+ );
636
+ this.zoom = getZoomFromFullExtent(fullExtent, this.cartographicCenter, this.cartesianCenter);
637
+ return;
638
+ }
639
+ // for I3S 1.6-1.7 try to calculate with extent
640
+ const extent = this.tileset.store?.extent;
641
+ if (extent) {
642
+ const [xmin, ymin, xmax, ymax] = extent;
643
+ this.cartographicCenter = new Vector3(xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2, 0);
644
+ this.cartesianCenter = Ellipsoid.WGS84.cartographicToCartesian(
645
+ this.cartographicCenter,
646
+ new Vector3()
647
+ );
648
+ this.zoom = getZoomFromExtent(extent, this.cartographicCenter, this.cartesianCenter);
649
+ return;
650
+ }
651
+ // eslint-disable-next-line no-console
652
+ console.warn('Extent is not defined in the tileset header');
653
+ this.cartographicCenter = new Vector3();
654
+ this.zoom = 1;
655
+ return;
656
+ }
657
+
658
+ /**
659
+ * Called during initialize Tileset to initialize the tileset's cartographic center (longitude, latitude) and zoom.
660
+ * These metrics help apps center view on tileset.
661
+ * For 3DTiles the root tile data is used to calculate view props.
662
+ * @returns
663
+ */
664
+ private calculateViewPropsTiles3D() {
609
665
  const root = this.root as Tile3D;
610
666
  assert(root);
611
667
  const {center} = root.boundingVolume;
612
668
  // TODO - handle all cases
613
669
  if (!center) {
614
- // eslint-disable-next-line
670
+ // eslint-disable-next-line no-console
615
671
  console.warn('center was not pre-calculated for the root tile');
616
672
  this.cartographicCenter = new Vector3();
617
673
  this.zoom = 1;
@@ -649,7 +705,7 @@ export default class Tileset3D {
649
705
  rootTile.depth = parentTileHeader.depth + 1;
650
706
  }
651
707
 
652
- // Cesium 3d tiles knows the hierarchy beforehand
708
+ // 3DTiles knows the hierarchy beforehand
653
709
  if (this.type === TILESET_TYPE.TILES3D) {
654
710
  const stack: Tile3D[] = [];
655
711
  stack.push(rootTile);
@@ -722,6 +778,13 @@ export default class Tileset3D {
722
778
  return;
723
779
  }
724
780
 
781
+ if (this.type === TILESET_TYPE.I3S) {
782
+ // We can't calculate tiles total in I3S in advance so we calculate it dynamically.
783
+ const nodesInNodePages = this.tileset?.nodePagesTile?.nodesInNodePages || 0;
784
+ this.stats.get(TILES_TOTAL).reset();
785
+ this.stats.get(TILES_TOTAL).addCount(nodesInNodePages);
786
+ }
787
+
725
788
  // add coordinateOrigin and modelMatrix to tile
726
789
  if (tile && tile.content) {
727
790
  calculateTransformProps(tile, tile.content);
@@ -808,7 +871,7 @@ export default class Tileset3D {
808
871
  tile.destroy();
809
872
  }
810
873
 
811
- _initializeCesiumTileset(tilesetJson) {
874
+ _initializeTiles3DTileset(tilesetJson) {
812
875
  this.asset = tilesetJson.asset;
813
876
  if (!this.asset) {
814
877
  throw new Error('Tileset must have an asset property.');
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Counter to register pending tile headers for the particular frameNumber
3
+ * Until all tiles are loaded we won't call `onTraversalEnd` callback
4
+ */
5
+ export default class I3SPendingTilesRegister {
6
+ private frameNumberMap: Map<number, number> = new Map();
7
+
8
+ /**
9
+ * Register a new pending tile header for the particular frameNumber
10
+ * @param frameNumber
11
+ */
12
+ register(frameNumber: number) {
13
+ const oldCount = this.frameNumberMap.get(frameNumber) || 0;
14
+ this.frameNumberMap.set(frameNumber, (oldCount || 0) + 1);
15
+ }
16
+
17
+ /**
18
+ * Deregister a pending tile header for the particular frameNumber
19
+ * @param frameNumber
20
+ */
21
+ deregister(frameNumber: number) {
22
+ const oldCount = this.frameNumberMap.get(frameNumber) || 1;
23
+ this.frameNumberMap.set(frameNumber, (oldCount || 0) - 1);
24
+ }
25
+
26
+ /**
27
+ * Check is there are no pending tile headers registered for the particular frameNumber
28
+ * @param frameNumber
29
+ * @returns
30
+ */
31
+ isZero(frameNumber: number) {
32
+ const count = this.frameNumberMap.get(frameNumber) || 0;
33
+ return count === 0;
34
+ }
35
+ }
@@ -1,3 +1,6 @@
1
+ import {FrameState} from '../helpers/frame-state';
2
+ import I3SPendingTilesRegister from './i3s-frame-counter';
3
+
1
4
  const STATUS = {
2
5
  REQUESTED: 'REQUESTED',
3
6
  COMPLETED: 'COMPLETED',
@@ -7,28 +10,40 @@ const STATUS = {
7
10
  // A helper class to manage tile metadata fetching
8
11
  export default class I3STileManager {
9
12
  private _statusMap: object;
13
+ private pendingTilesRegister = new I3SPendingTilesRegister();
10
14
 
11
15
  constructor() {
12
16
  this._statusMap = {};
13
17
  }
14
18
 
15
- add(request, key, callback, frameState) {
19
+ add(request, key, callback, frameState: FrameState) {
16
20
  if (!this._statusMap[key]) {
21
+ const {frameNumber} = frameState;
17
22
  this._statusMap[key] = {request, callback, key, frameState, status: STATUS.REQUESTED};
23
+ // Register pending request for the frameNumber
24
+ this.pendingTilesRegister.register(frameNumber);
18
25
  request()
19
26
  .then((data) => {
20
27
  this._statusMap[key].status = STATUS.COMPLETED;
28
+ // Deregister pending request for the frameNumber
29
+ this.pendingTilesRegister.deregister(frameNumber);
21
30
  this._statusMap[key].callback(data, frameState);
22
31
  })
23
32
  .catch((error) => {
24
33
  this._statusMap[key].status = STATUS.ERROR;
34
+ // Deregister pending request for the frameNumber
35
+ this.pendingTilesRegister.deregister(frameNumber);
25
36
  callback(error);
26
37
  });
27
38
  }
28
39
  }
29
40
 
30
- update(key, frameState) {
41
+ update(key, frameState: FrameState) {
31
42
  if (this._statusMap[key]) {
43
+ // Deregister pending request for the old frameNumber
44
+ this.pendingTilesRegister.deregister(this._statusMap[key].frameState.frameNumber);
45
+ // Register pending request for the new frameNumber
46
+ this.pendingTilesRegister.register(frameState.frameNumber);
32
47
  this._statusMap[key].frameState = frameState;
33
48
  }
34
49
  }
@@ -36,4 +51,13 @@ export default class I3STileManager {
36
51
  find(key) {
37
52
  return this._statusMap[key];
38
53
  }
54
+
55
+ /**
56
+ * Check it there are pending tile headers for the particular frameNumber
57
+ * @param frameNumber
58
+ * @returns
59
+ */
60
+ hasPendingTiles(frameNumber: number): boolean {
61
+ return !this.pendingTilesRegister.isZero(frameNumber);
62
+ }
39
63
  }
@@ -4,21 +4,31 @@ import TilesetTraverser from './tileset-traverser';
4
4
  import {getLodStatus} from '../helpers/i3s-lod';
5
5
  import TileHeader from '../tile-3d';
6
6
  import I3STileManager from './i3s-tile-manager';
7
+ import {FrameState} from '../helpers/frame-state';
7
8
 
8
9
  export default class I3STilesetTraverser extends TilesetTraverser {
9
10
  private _tileManager: I3STileManager;
10
11
 
12
+ /**
13
+ * Check if there are no penging tile header requests,
14
+ * that means the traversal is finished and we can call
15
+ * following-up callbacks.
16
+ */
17
+ protected get traversalFinished(): boolean {
18
+ return !this._tileManager.hasPendingTiles(this._frameNumber || 0);
19
+ }
20
+
11
21
  constructor(options) {
12
22
  super(options);
13
23
  this._tileManager = new I3STileManager();
14
24
  }
15
25
 
16
- shouldRefine(tile, frameState) {
26
+ shouldRefine(tile, frameState: FrameState) {
17
27
  tile._lodJudge = getLodStatus(tile, frameState);
18
28
  return tile._lodJudge === 'DIG';
19
29
  }
20
30
 
21
- updateChildTiles(tile, frameState): boolean {
31
+ updateChildTiles(tile, frameState: FrameState): boolean {
22
32
  const children = tile.header.children || [];
23
33
  // children which are already fetched and constructed as Tile3D instances
24
34
  const childTiles = tile.children;
@@ -86,7 +96,10 @@ export default class I3STilesetTraverser extends TilesetTraverser {
86
96
  this.updateTile(childTile, frameState);
87
97
 
88
98
  // after tile fetched, resume traversal if still in current update/traversal frame
89
- if (this._frameNumber === frameState.frameNumber) {
99
+ if (
100
+ this._frameNumber === frameState.frameNumber &&
101
+ (this.traversalFinished || new Date().getTime() - this.lastUpdate > this.updateDebounceTime)
102
+ ) {
90
103
  this.executeTraversal(childTile, frameState);
91
104
  }
92
105
  }
@@ -1,5 +1,6 @@
1
1
  import ManagedArray from '../../utils/managed-array';
2
2
  import {TILE_REFINEMENT} from '../../constants';
3
+ import {FrameState} from '../helpers/frame-state';
3
4
 
4
5
  export type TilesetTraverserProps = {
5
6
  loadSiblings?: boolean;
@@ -38,10 +39,16 @@ export default class TilesetTraverser {
38
39
  selectedTiles: object;
39
40
  emptyTiles: object;
40
41
 
42
+ protected lastUpdate: number = new Date().getTime();
43
+ protected readonly updateDebounceTime = 1000;
41
44
  protected _traversalStack: ManagedArray;
42
45
  protected _emptyTraversalStack: ManagedArray;
43
46
  protected _frameNumber: number | null;
44
47
 
48
+ protected get traversalFinished(): boolean {
49
+ return true;
50
+ }
51
+
45
52
  // TODO nested props
46
53
  constructor(options: TilesetTraverserProps) {
47
54
  this.options = {...DEFAULT_PROPS, ...options};
@@ -96,7 +103,7 @@ export default class TilesetTraverser {
96
103
  // all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
97
104
  // and rendering children and parent tiles simultaneously.
98
105
  /* eslint-disable-next-line complexity, max-statements */
99
- executeTraversal(root, frameState) {
106
+ executeTraversal(root, frameState: FrameState) {
100
107
  // stack to store traversed tiles, only visible tiles should be added to stack
101
108
  // visible: visible in the current view frustum
102
109
  const stack = this._traversalStack;
@@ -156,7 +163,11 @@ export default class TilesetTraverser {
156
163
  tile._shouldRefine = shouldRefine && parentRefines;
157
164
  }
158
165
 
159
- this.options.onTraversalEnd(frameState);
166
+ const newTime = new Date().getTime();
167
+ if (this.traversalFinished || newTime - this.lastUpdate > this.updateDebounceTime) {
168
+ this.lastUpdate = newTime;
169
+ this.options.onTraversalEnd(frameState);
170
+ }
160
171
  }
161
172
 
162
173
  updateChildTiles(tile, frameState) {