@loaders.gl/tiles 3.1.7 → 3.2.0-alpha.2

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 (73) hide show
  1. package/dist/dist.min.js +302 -318
  2. package/dist/es5/tileset/helpers/frame-state.js +79 -0
  3. package/dist/es5/tileset/helpers/frame-state.js.map +1 -1
  4. package/dist/es5/tileset/helpers/i3s-lod.js +17 -26
  5. package/dist/es5/tileset/helpers/i3s-lod.js.map +1 -1
  6. package/dist/es5/tileset/helpers/zoom.js +32 -0
  7. package/dist/es5/tileset/helpers/zoom.js.map +1 -1
  8. package/dist/es5/tileset/tile-3d.js +5 -0
  9. package/dist/es5/tileset/tile-3d.js.map +1 -1
  10. package/dist/es5/tileset/tileset-3d.js +244 -85
  11. package/dist/es5/tileset/tileset-3d.js.map +1 -1
  12. package/dist/es5/tileset/traversers/i3s-frame-counter.js +45 -0
  13. package/dist/es5/tileset/traversers/i3s-frame-counter.js.map +1 -0
  14. package/dist/es5/tileset/traversers/i3s-tile-manager.js +17 -0
  15. package/dist/es5/tileset/traversers/i3s-tile-manager.js.map +1 -1
  16. package/dist/es5/tileset/traversers/i3s-tileset-traverser.js +6 -1
  17. package/dist/es5/tileset/traversers/i3s-tileset-traverser.js.map +1 -1
  18. package/dist/es5/tileset/traversers/tileset-traverser.js +13 -1
  19. package/dist/es5/tileset/traversers/tileset-traverser.js.map +1 -1
  20. package/dist/esm/tileset/helpers/frame-state.js +52 -0
  21. package/dist/esm/tileset/helpers/frame-state.js.map +1 -1
  22. package/dist/esm/tileset/helpers/i3s-lod.js +17 -27
  23. package/dist/esm/tileset/helpers/i3s-lod.js.map +1 -1
  24. package/dist/esm/tileset/helpers/zoom.js +17 -0
  25. package/dist/esm/tileset/helpers/zoom.js.map +1 -1
  26. package/dist/esm/tileset/tile-3d.js +4 -0
  27. package/dist/esm/tileset/tile-3d.js.map +1 -1
  28. package/dist/esm/tileset/tileset-3d.js +95 -17
  29. package/dist/esm/tileset/tileset-3d.js.map +1 -1
  30. package/dist/esm/tileset/traversers/i3s-frame-counter.js +23 -0
  31. package/dist/esm/tileset/traversers/i3s-frame-counter.js.map +1 -0
  32. package/dist/esm/tileset/traversers/i3s-tile-manager.js +15 -0
  33. package/dist/esm/tileset/traversers/i3s-tile-manager.js.map +1 -1
  34. package/dist/esm/tileset/traversers/i3s-tileset-traverser.js +5 -1
  35. package/dist/esm/tileset/traversers/i3s-tileset-traverser.js.map +1 -1
  36. package/dist/esm/tileset/traversers/tileset-traverser.js +14 -1
  37. package/dist/esm/tileset/traversers/tileset-traverser.js.map +1 -1
  38. package/dist/tileset/helpers/frame-state.d.ts +14 -0
  39. package/dist/tileset/helpers/frame-state.d.ts.map +1 -1
  40. package/dist/tileset/helpers/frame-state.js +49 -1
  41. package/dist/tileset/helpers/i3s-lod.d.ts.map +1 -1
  42. package/dist/tileset/helpers/i3s-lod.js +17 -16
  43. package/dist/tileset/helpers/zoom.d.ts +35 -0
  44. package/dist/tileset/helpers/zoom.d.ts.map +1 -1
  45. package/dist/tileset/helpers/zoom.js +41 -1
  46. package/dist/tileset/tile-3d.d.ts +5 -0
  47. package/dist/tileset/tile-3d.d.ts.map +1 -1
  48. package/dist/tileset/tile-3d.js +7 -0
  49. package/dist/tileset/tileset-3d.d.ts +36 -4
  50. package/dist/tileset/tileset-3d.d.ts.map +1 -1
  51. package/dist/tileset/tileset-3d.js +99 -20
  52. package/dist/tileset/traversers/i3s-frame-counter.d.ts +24 -0
  53. package/dist/tileset/traversers/i3s-frame-counter.d.ts.map +1 -0
  54. package/dist/tileset/traversers/i3s-frame-counter.js +37 -0
  55. package/dist/tileset/traversers/i3s-tile-manager.d.ts +10 -2
  56. package/dist/tileset/traversers/i3s-tile-manager.d.ts.map +1 -1
  57. package/dist/tileset/traversers/i3s-tile-manager.js +24 -0
  58. package/dist/tileset/traversers/i3s-tileset-traverser.d.ts +9 -2
  59. package/dist/tileset/traversers/i3s-tileset-traverser.d.ts.map +1 -1
  60. package/dist/tileset/traversers/i3s-tileset-traverser.js +10 -1
  61. package/dist/tileset/traversers/tileset-traverser.d.ts +5 -1
  62. package/dist/tileset/traversers/tileset-traverser.d.ts.map +1 -1
  63. package/dist/tileset/traversers/tileset-traverser.js +10 -1
  64. package/package.json +5 -5
  65. package/src/tileset/helpers/frame-state.ts +56 -0
  66. package/src/tileset/helpers/i3s-lod.ts +18 -19
  67. package/src/tileset/helpers/zoom.ts +64 -0
  68. package/src/tileset/tile-3d.ts +8 -0
  69. package/src/tileset/tileset-3d.ts +123 -21
  70. package/src/tileset/traversers/i3s-frame-counter.ts +35 -0
  71. package/src/tileset/traversers/i3s-tile-manager.ts +26 -2
  72. package/src/tileset/traversers/i3s-tileset-traverser.ts +16 -3
  73. package/src/tileset/traversers/tileset-traverser.ts +13 -2
@@ -3,6 +3,14 @@ import {Ellipsoid} from '@math.gl/geospatial';
3
3
  import Tile3D from '../tile-3d';
4
4
  import {FrameState} from './frame-state';
5
5
 
6
+ const cameraPositionCartesian = new Vector3();
7
+ const toEye = new Vector3();
8
+ const cameraPositionEnu = new Vector3();
9
+ const extraVertexEnu = new Vector3();
10
+ const projectedOriginVector = new Vector3();
11
+ const enuToCartesianMatrix = new Matrix4();
12
+ const cartesianToEnuMatrix = new Matrix4();
13
+
6
14
  /**
7
15
  * For the maxScreenThreshold error metric, maxError means that you should replace the node with it's children
8
16
  as soon as the nodes bounding sphere has a screen radius larger than maxError pixels.
@@ -36,45 +44,35 @@ export function getLodStatus(tile: Tile3D, frameState: FrameState): 'DIG' | 'OUT
36
44
  */
37
45
  // eslint-disable-next-line max-statements
38
46
  export function getProjectedRadius(tile: Tile3D, frameState: FrameState): number {
39
- const originalViewport = frameState.viewport;
40
- const ViewportClass = originalViewport.constructor;
41
- const {longitude, latitude, height, width, bearing, zoom} = originalViewport;
42
- // @ts-ignore
43
- const viewport = new ViewportClass({longitude, latitude, height, width, bearing, zoom, pitch: 0});
47
+ const {topDownViewport: viewport} = frameState;
44
48
  const mbsLat = tile.header.mbs[1];
45
49
  const mbsLon = tile.header.mbs[0];
46
50
  const mbsZ = tile.header.mbs[2];
47
51
  const mbsR = tile.header.mbs[3];
48
52
  const mbsCenterCartesian = [...tile.boundingVolume.center];
49
53
  const cameraPositionCartographic = viewport.unprojectPosition(viewport.cameraPosition);
50
- const cameraPositionCartesian = Ellipsoid.WGS84.cartographicToCartesian(
51
- cameraPositionCartographic,
52
- new Vector3()
53
- );
54
+ Ellipsoid.WGS84.cartographicToCartesian(cameraPositionCartographic, cameraPositionCartesian);
54
55
 
55
56
  // ---------------------------
56
57
  // Calculate mbs border vertex
57
58
  // ---------------------------
58
- const toEye = new Vector3(cameraPositionCartesian).subtract(mbsCenterCartesian).normalize();
59
+ toEye.copy(cameraPositionCartesian).subtract(mbsCenterCartesian).normalize();
59
60
  // Add extra vector to form plane
60
- const enuToCartesianMatrix = new Matrix4();
61
61
  Ellipsoid.WGS84.eastNorthUpToFixedFrame(mbsCenterCartesian, enuToCartesianMatrix);
62
- const cartesianToEnuMatrix = new Matrix4(enuToCartesianMatrix).invert();
63
- const cameraPositionEnu = new Vector3(cameraPositionCartesian).transform(cartesianToEnuMatrix);
62
+ cartesianToEnuMatrix.copy(enuToCartesianMatrix).invert();
63
+ cameraPositionEnu.copy(cameraPositionCartesian).transform(cartesianToEnuMatrix);
64
64
  // Mean Proportionals in Right Triangles - Altitude rule
65
65
  // https://mathbitsnotebook.com/Geometry/RightTriangles/RTmeanRight.html
66
66
  const projection = Math.sqrt(
67
67
  cameraPositionEnu[0] * cameraPositionEnu[0] + cameraPositionEnu[1] * cameraPositionEnu[1]
68
68
  );
69
69
  const extraZ = (projection * projection) / cameraPositionEnu[2];
70
- const extraVertexEnu = new Vector3([cameraPositionEnu[0], cameraPositionEnu[1], extraZ]);
70
+ extraVertexEnu.copy([cameraPositionEnu[0], cameraPositionEnu[1], extraZ]);
71
71
  const extraVertexCartesian = extraVertexEnu.transform(enuToCartesianMatrix);
72
- const extraVectorCartesian = new Vector3(extraVertexCartesian)
73
- .subtract(mbsCenterCartesian)
74
- .normalize();
72
+ const extraVectorCartesian = extraVertexCartesian.subtract(mbsCenterCartesian).normalize();
75
73
  // We need radius vector orthogonal to toEye vector
76
74
  const radiusVector = toEye.cross(extraVectorCartesian).normalize().scale(mbsR);
77
- const sphereMbsBorderVertexCartesian = new Vector3(mbsCenterCartesian).add(radiusVector);
75
+ const sphereMbsBorderVertexCartesian = radiusVector.add(mbsCenterCartesian);
78
76
  const sphereMbsBorderVertexCartographic = Ellipsoid.WGS84.cartesianToCartographic(
79
77
  sphereMbsBorderVertexCartesian
80
78
  );
@@ -83,7 +81,8 @@ export function getProjectedRadius(tile: Tile3D, frameState: FrameState): number
83
81
  // Project center vertex and border vertex and calculate projected radius of MBS
84
82
  const projectedOrigin = viewport.project([mbsLon, mbsLat, mbsZ]);
85
83
  const projectedMbsBorderVertex = viewport.project(sphereMbsBorderVertexCartographic);
86
- const projectedRadius = new Vector3(projectedOrigin)
84
+ const projectedRadius = projectedOriginVector
85
+ .copy(projectedOrigin)
87
86
  .subtract(projectedMbsBorderVertex)
88
87
  .magnitude();
89
88
  return projectedRadius;
@@ -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);
@@ -298,6 +298,14 @@ export default class TileHeader {
298
298
  }
299
299
  }
300
300
 
301
+ /**
302
+ * Make tile unselected than means it won't be shown
303
+ * but it can be still loaded in memory
304
+ */
305
+ unselect(): void {
306
+ this._selectedFrame = 0;
307
+ }
308
+
301
309
  /*
302
310
  * If skipLevelOfDetail is off try to load child tiles as soon as possible so that their parent can refine sooner.
303
311
  * Tiles are prioritized by screen space error.
@@ -48,8 +48,8 @@ import {
48
48
  } from '@loaders.gl/loader-utils';
49
49
  import TilesetCache from './tileset-cache';
50
50
  import {calculateTransformProps} from './helpers/transform-utils';
51
- import {FrameState, getFrameState} from './helpers/frame-state';
52
- import {getZoomFromBoundingVolume} from './helpers/zoom';
51
+ import {FrameState, getFrameState, limitSelectedTiles} from './helpers/frame-state';
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';
@@ -64,6 +64,8 @@ export type Tileset3DProps = {
64
64
  loadTiles?: boolean;
65
65
  basePath?: string;
66
66
  maximumMemoryUsage?: number;
67
+ maximumTilesSelected?: number;
68
+ debounceTime?: number;
67
69
 
68
70
  // Metadata
69
71
  description?: string;
@@ -93,6 +95,8 @@ type Props = {
93
95
  modelMatrix: Matrix4;
94
96
  throttleRequests: boolean;
95
97
  maximumMemoryUsage: number;
98
+ maximumTilesSelected: number;
99
+ debounceTime: number;
96
100
  onTileLoad: (tile: Tile3D) => any;
97
101
  onTileUnload: (tile: Tile3D) => any;
98
102
  onTileError: (tile: Tile3D, message: string, url: string) => any;
@@ -124,6 +128,10 @@ const DEFAULT_PROPS: Props = {
124
128
  maxRequests: 64,
125
129
 
126
130
  maximumMemoryUsage: 32,
131
+ /** Maximum number limit of tiles selected for show. 0 means no limit */
132
+ maximumTilesSelected: 0,
133
+ /** Delay time before the tileset traversal. It prevents traversal requests spam.*/
134
+ debounceTime: 0,
127
135
 
128
136
  /**
129
137
  * Callback. Indicates this a tile's content was loaded
@@ -149,7 +157,6 @@ const DEFAULT_PROPS: Props = {
149
157
  // View distance scale modifier
150
158
  viewDistanceScale: 1.0,
151
159
 
152
- // TODO CESIUM
153
160
  // The maximum screen space error used to drive level of detail refinement.
154
161
  maximumScreenSpaceError: 8,
155
162
 
@@ -207,6 +214,8 @@ export default class Tileset3D {
207
214
  traverseCounter: number;
208
215
  geometricError: number;
209
216
  selectedTiles: Tile3D[];
217
+ private updatePromise: Promise<number> | null = null;
218
+ tilesetInitializationPromise: Promise<void>;
210
219
 
211
220
  cartographicCenter: Vector3 | null;
212
221
  cartesianCenter: Vector3 | null;
@@ -324,7 +333,7 @@ export default class Tileset3D {
324
333
  this.credits = {};
325
334
  this.description = this.options.description || '';
326
335
 
327
- this._initializeTileSet(json);
336
+ this.tilesetInitializationPromise = this._initializeTileSet(json);
328
337
  }
329
338
 
330
339
  /** Release resources */
@@ -382,20 +391,55 @@ export default class Tileset3D {
382
391
  /**
383
392
  * Update visible tiles relying on a list of viewports
384
393
  * @param viewports - list of viewports
394
+ * @deprecated
395
+ */
396
+ update(viewports: any[] | null = null) {
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
+ });
406
+ }
407
+
408
+ /**
409
+ * Update visible tiles relying on a list of viewports.
410
+ * Do it with debounce delay to prevent update spam
411
+ * @param viewports viewports
412
+ * @returns Promise of new frameNumber
413
+ */
414
+ async selectTiles(viewports: any[] | null = null): Promise<number> {
415
+ await this.tilesetInitializationPromise;
416
+ if (viewports) {
417
+ this.lastUpdatedVieports = viewports;
418
+ }
419
+ if (!this.updatePromise) {
420
+ this.updatePromise = new Promise<number>((resolve) => {
421
+ setTimeout(() => {
422
+ this.doUpdate(this.lastUpdatedVieports);
423
+ resolve(this._frameNumber);
424
+ this.updatePromise = null;
425
+ }, this.options.debounceTime);
426
+ });
427
+ }
428
+ return this.updatePromise;
429
+ }
430
+
431
+ /**
432
+ * Update visible tiles relying on a list of viewports
433
+ * @param viewports viewports
385
434
  */
386
435
  // eslint-disable-next-line max-statements, complexity
387
- update(viewports: any[]): void {
436
+ private doUpdate(viewports: any[] | null = null): void {
388
437
  if ('loadTiles' in this.options && !this.options.loadTiles) {
389
438
  return;
390
439
  }
391
440
  if (this.traverseCounter > 0) {
392
441
  return;
393
442
  }
394
- if (!viewports && this.lastUpdatedVieports) {
395
- viewports = this.lastUpdatedVieports;
396
- } else {
397
- this.lastUpdatedVieports = viewports;
398
- }
399
443
  if (!(viewports instanceof Array)) {
400
444
  viewports = [viewports];
401
445
  }
@@ -457,7 +501,16 @@ export default class Tileset3D {
457
501
  }
458
502
  const currentFrameStateData = this.frameStateData[id];
459
503
  const selectedTiles = Object.values(this._traverser.selectedTiles);
460
- currentFrameStateData.selectedTiles = selectedTiles;
504
+ const [filteredSelectedTiles, unselectedTiles] = limitSelectedTiles(
505
+ selectedTiles,
506
+ frameState,
507
+ this.options.maximumTilesSelected
508
+ );
509
+ currentFrameStateData.selectedTiles = filteredSelectedTiles;
510
+ for (const tile of unselectedTiles) {
511
+ tile.unselect();
512
+ }
513
+
461
514
  currentFrameStateData._requestedTiles = Object.values(this._traverser.requestedTiles);
462
515
  currentFrameStateData._emptyTiles = Object.values(this._traverser.emptyTiles);
463
516
 
@@ -540,29 +593,78 @@ export default class Tileset3D {
540
593
  this.stats.get(POINTS_COUNT).count = pointsRenderable;
541
594
  }
542
595
 
543
- _initializeTileSet(tilesetJson) {
596
+ async _initializeTileSet(tilesetJson) {
597
+ if (this.type === TILESET_TYPE.I3S) {
598
+ this.calculateViewPropsI3S();
599
+ tilesetJson.root = await tilesetJson.root;
600
+ }
544
601
  this.root = this._initializeTileHeaders(tilesetJson, null);
545
602
 
546
- // TODO CESIUM Specific
547
603
  if (this.type === TILESET_TYPE.TILES3D) {
548
- this._initializeCesiumTileset(tilesetJson);
604
+ this._initializeTiles3DTileset(tilesetJson);
605
+ this.calculateViewPropsTiles3D();
549
606
  }
550
607
 
551
608
  if (this.type === TILESET_TYPE.I3S) {
552
609
  this._initializeI3STileset();
553
610
  }
554
- // Calculate cartographicCenter & zoom props to help apps center view on tileset
555
- this._calculateViewProps();
556
611
  }
557
612
 
558
- // Called during initialize Tileset to initialize the tileset's cartographic center (longitude, latitude) and zoom.
559
- _calculateViewProps() {
613
+ /**
614
+ * Called during initialize Tileset to initialize the tileset's cartographic center (longitude, latitude) and zoom.
615
+ * These metrics help apps center view on tileset
616
+ * For I3S there is extent (<1.8 version) or fullExtent (>=1.8 version) to calculate view props
617
+ * @returns
618
+ */
619
+ private calculateViewPropsI3S() {
620
+ // for I3S 1.8 try to calculate with fullExtent
621
+ const fullExtent = this.tileset.fullExtent;
622
+ if (fullExtent) {
623
+ const {xmin, xmax, ymin, ymax, zmin, zmax} = fullExtent;
624
+ this.cartographicCenter = new Vector3(
625
+ xmin + (xmax - xmin) / 2,
626
+ ymin + (ymax - ymin) / 2,
627
+ zmin + (zmax - zmin) / 2
628
+ );
629
+ this.cartesianCenter = Ellipsoid.WGS84.cartographicToCartesian(
630
+ this.cartographicCenter,
631
+ new Vector3()
632
+ );
633
+ this.zoom = getZoomFromFullExtent(fullExtent, this.cartographicCenter, this.cartesianCenter);
634
+ return;
635
+ }
636
+ // for I3S 1.6-1.7 try to calculate with extent
637
+ const extent = this.tileset.store?.extent;
638
+ if (extent) {
639
+ const [xmin, ymin, xmax, ymax] = extent;
640
+ this.cartographicCenter = new Vector3(xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2, 0);
641
+ this.cartesianCenter = Ellipsoid.WGS84.cartographicToCartesian(
642
+ this.cartographicCenter,
643
+ new Vector3()
644
+ );
645
+ this.zoom = getZoomFromExtent(extent, this.cartographicCenter, this.cartesianCenter);
646
+ return;
647
+ }
648
+ // eslint-disable-next-line no-console
649
+ console.warn('Extent is not defined in the tileset header');
650
+ this.cartographicCenter = new Vector3();
651
+ this.zoom = 1;
652
+ return;
653
+ }
654
+
655
+ /**
656
+ * Called during initialize Tileset to initialize the tileset's cartographic center (longitude, latitude) and zoom.
657
+ * These metrics help apps center view on tileset.
658
+ * For 3DTiles the root tile data is used to calculate view props.
659
+ * @returns
660
+ */
661
+ private calculateViewPropsTiles3D() {
560
662
  const root = this.root as Tile3D;
561
663
  assert(root);
562
664
  const {center} = root.boundingVolume;
563
665
  // TODO - handle all cases
564
666
  if (!center) {
565
- // eslint-disable-next-line
667
+ // eslint-disable-next-line no-console
566
668
  console.warn('center was not pre-calculated for the root tile');
567
669
  this.cartographicCenter = new Vector3();
568
670
  this.zoom = 1;
@@ -600,7 +702,7 @@ export default class Tileset3D {
600
702
  rootTile.depth = parentTileHeader.depth + 1;
601
703
  }
602
704
 
603
- // Cesium 3d tiles knows the hierarchy beforehand
705
+ // 3DTiles knows the hierarchy beforehand
604
706
  if (this.type === TILESET_TYPE.TILES3D) {
605
707
  const stack: Tile3D[] = [];
606
708
  stack.push(rootTile);
@@ -759,7 +861,7 @@ export default class Tileset3D {
759
861
  tile.destroy();
760
862
  }
761
863
 
762
- _initializeCesiumTileset(tilesetJson) {
864
+ _initializeTiles3DTileset(tilesetJson) {
763
865
  this.asset = tilesetJson.asset;
764
866
  if (!this.asset) {
765
867
  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) {