bruce-cesium 5.4.5 → 5.4.7

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 (33) hide show
  1. package/dist/bruce-cesium.es5.js +2053 -1939
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2052 -1938
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/rendering/cesium-animated-property.js +152 -39
  7. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  8. package/dist/lib/rendering/entity-gatherer.js +327 -0
  9. package/dist/lib/rendering/entity-gatherer.js.map +1 -0
  10. package/dist/lib/rendering/entity-render-engine-model3d.js +3 -2
  11. package/dist/lib/rendering/entity-render-engine-model3d.js.map +1 -1
  12. package/dist/lib/rendering/entity-render-engine-point.js +6 -4
  13. package/dist/lib/rendering/entity-render-engine-point.js.map +1 -1
  14. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +38 -28
  15. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  16. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +6 -2
  17. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  18. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +37 -34
  19. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  20. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +6 -2
  21. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  22. package/dist/lib/rendering/tileset-render-engine.js +5 -899
  23. package/dist/lib/rendering/tileset-render-engine.js.map +1 -1
  24. package/dist/lib/rendering/tileset-styler.js +555 -0
  25. package/dist/lib/rendering/tileset-styler.js.map +1 -0
  26. package/dist/types/bruce-cesium.d.ts +1 -1
  27. package/dist/types/rendering/cesium-animated-property.d.ts +18 -0
  28. package/dist/types/rendering/entity-gatherer.d.ts +36 -0
  29. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +0 -2
  30. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +0 -2
  31. package/dist/types/rendering/tileset-render-engine.d.ts +3 -99
  32. package/dist/types/rendering/tileset-styler.d.ts +80 -0
  33. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { BruceEvent, Cartes, Entity as Entity$1, Carto, Geometry, MathUtils, LRUCache, Api, Calculator, ClientFile, EntityTag, EntityType, ObjectUtils, Style, ProjectViewTile, DelayQueue, EntityLod, Bounds, ZoomControl, EntityRelationType, ENVIRONMENT, EntityHistoricData, Tileset, EntityCoords, DataLab, EntitySource, MenuItem, EntityRelation, ProgramKey, ProjectView, ProjectViewBookmark, Camera, ProjectViewLegacyTile, EntityAttachment, EntityAttachmentType, EntityAttribute, AbstractApi, Session } from 'bruce-models';
2
2
  import * as Cesium from 'cesium';
3
- import { Cartographic, Cartesian2, Math as Math$1, Cartesian3, CallbackProperty, Color, HeightReference, Rectangle, JulianDate, Entity, DistanceDisplayCondition, HorizontalOrigin, VerticalOrigin, ConstantProperty, ClassificationType, ConstantPositionProperty, ArcType, CornerType, ShadowMode, PolygonHierarchy, PolylineGraphics, ColorMaterialProperty, ColorBlendMode, HeadingPitchRoll, Transforms, Model, SceneMode, Primitive, Cesium3DTileFeature, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Ion, KmlDataSource, SceneTransforms, OrthographicFrustum, EasingFunction, NearFarScalar, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, CesiumInspector, defined, ClockRange, Cesium3DTileset, Matrix4, Matrix3, IonResource, EllipsoidGeodesic, sampleTerrainMostDetailed, PolygonPipeline, BoundingSphere, GeometryInstance, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, ScreenSpaceEventHandler, ScreenSpaceEventType, Quaternion, CzmlDataSource, Intersect, Fullscreen } from 'cesium';
3
+ import { Cartographic, Cartesian2, Math as Math$1, Cartesian3, CallbackProperty, Color, HeightReference, Rectangle, JulianDate, Entity, DistanceDisplayCondition, ClassificationType, ArcType, CornerType, ShadowMode, ConstantProperty, ConstantPositionProperty, PolygonHierarchy, PolylineGraphics, ColorMaterialProperty, HorizontalOrigin, VerticalOrigin, ColorBlendMode, HeadingPitchRoll, Transforms, Model, Primitive, Cesium3DTileFeature, SceneMode, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Ion, Cesium3DTileColorBlendMode, KmlDataSource, OrthographicFrustum, EasingFunction, NearFarScalar, SceneTransforms, Cesium3DTileset, Matrix4, Matrix3, IonResource, EllipsoidTerrainProvider, CesiumInspector, defined, ClockRange, EllipsoidGeodesic, sampleTerrainMostDetailed, PolygonPipeline, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, BoundingSphere, GeometryInstance, Quaternion, ScreenSpaceEventHandler, ScreenSpaceEventType, CzmlDataSource, Intersect, Fullscreen } from 'cesium';
4
4
 
5
5
  const TIME_LAG = 300;
6
6
  const POSITION_CHECK_TIMER = 950;
@@ -1152,10 +1152,15 @@ var CesiumAnimatedProperty;
1152
1152
  this.lastCalcSeriesTime = null;
1153
1153
  this.lastCalcOrient = null;
1154
1154
  this.lastCalcOrientTime = null;
1155
+ // Indicates that the first time we get a GetValue position, we need to animate into it.
1156
+ this.animateFromPos3d = null;
1157
+ this.animateFromPos3dTimeStart = null;
1155
1158
  this.viewer = params.viewer;
1156
1159
  this.positions = params.posses;
1157
1160
  this.pitch = params.pitch;
1158
1161
  this.roll = params.roll;
1162
+ this.animateFromPos3d = params.animateFromPos3d;
1163
+ this.animateFromPos3dTimeStart = Date.now();
1159
1164
  this.processHeadings();
1160
1165
  // Order positions by date.
1161
1166
  this.positions.sort((a, b) => {
@@ -1189,19 +1194,28 @@ var CesiumAnimatedProperty;
1189
1194
  if (!now) {
1190
1195
  now = this.viewer.clock.currentTime;
1191
1196
  }
1192
- const nowTime = JulianDate.toDate(now);
1193
- if (this.lastCalcTime == nowTime.getTime()) {
1197
+ const nowTimeMs = JulianDate.toDate(now).getTime();
1198
+ // Skip calculation if time hasn't changed.
1199
+ if (this.lastCalcTime === nowTimeMs) {
1200
+ if (this.animateFromPos3d) {
1201
+ // We emit an interpolated position if we're animating from the start position.
1202
+ const interpolatedPos = this.interpolateToStartPos3d(this.animateFromPos3dTimeStart, Date.now());
1203
+ if (Cartesian3.equals(interpolatedPos, this.animateFromPos3d)) {
1204
+ this.animateFromPos3d = null;
1205
+ }
1206
+ return interpolatedPos;
1207
+ }
1194
1208
  return this.lastCalcPos3d;
1195
1209
  }
1196
1210
  const calculate = () => {
1197
1211
  // See if we're before the first position.
1198
- if (nowTime.getTime() <= this.positions[0].dateTime.getTime()) {
1212
+ if (nowTimeMs <= this.positions[0].dateTime.getTime()) {
1199
1213
  this.lastCalcPosIndexLast = 0;
1200
1214
  this.lastCalcPosIndexNext = 0;
1201
1215
  return this.positions[0].pos3d;
1202
1216
  }
1203
1217
  // See if we're after the last position.
1204
- if (nowTime.getTime() >= this.positions[this.positions.length - 1].dateTime.getTime()) {
1218
+ if (nowTimeMs >= this.positions[this.positions.length - 1].dateTime.getTime()) {
1205
1219
  this.lastCalcPosIndexLast = this.positions.length - 1;
1206
1220
  this.lastCalcPosIndexNext = this.positions.length - 1;
1207
1221
  return this.positions[this.positions.length - 1].pos3d;
@@ -1210,7 +1224,7 @@ var CesiumAnimatedProperty;
1210
1224
  let lastIndex = 0;
1211
1225
  for (let i = 1; i < this.positions.length; i++) {
1212
1226
  let pos = this.positions[i];
1213
- if (nowTime.getTime() >= pos.dateTime.getTime()) {
1227
+ if (nowTimeMs >= pos.dateTime.getTime()) {
1214
1228
  lastIndex = i;
1215
1229
  }
1216
1230
  else {
@@ -1226,12 +1240,43 @@ var CesiumAnimatedProperty;
1226
1240
  return last.pos3d;
1227
1241
  }
1228
1242
  this.lastCalcPosIndexNext = lastIndex + 1;
1229
- let progress = (nowTime.getTime() - last.dateTime.getTime()) / (next.dateTime.getTime() - last.dateTime.getTime());
1243
+ let progress = (nowTimeMs - last.dateTime.getTime()) / (next.dateTime.getTime() - last.dateTime.getTime());
1230
1244
  return Cartesian3.lerp(last.pos3d, next.pos3d, progress, new Cartesian3());
1231
1245
  };
1232
- this.lastCalcTime = nowTime.getTime();
1246
+ this.lastCalcTime = nowTimeMs;
1233
1247
  this.lastCalcPos3d = calculate();
1234
- return this.lastCalcPos3d;
1248
+ let pos3d = this.lastCalcPos3d;
1249
+ // We emit an interpolated position if we're animating from the start position.
1250
+ if (this.animateFromPos3d && Cartes.ValidateCartes3(this.lastCalcPos3d)) {
1251
+ if (Cartesian3.equals(this.animateFromPos3d, this.lastCalcPos3d)) {
1252
+ this.animateFromPos3d = null;
1253
+ }
1254
+ else if (this.animateFromPos3d) {
1255
+ pos3d = this.interpolateToStartPos3d(this.animateFromPos3dTimeStart, Date.now());
1256
+ if (Cartesian3.equals(pos3d, this.animateFromPos3d)) {
1257
+ this.animateFromPos3d = null;
1258
+ }
1259
+ }
1260
+ }
1261
+ return pos3d;
1262
+ }
1263
+ /**
1264
+ * Interpolates between the start position and the last calculated position.
1265
+ * This lets us animate in the series positions.
1266
+ * @param nowMs
1267
+ */
1268
+ interpolateToStartPos3d(startMs, nowMs) {
1269
+ const startPos = this.animateFromPos3d;
1270
+ const targetPos = this.lastCalcPos3d;
1271
+ if (!startPos || !targetPos) {
1272
+ return startPos ? startPos : targetPos;
1273
+ }
1274
+ const duration = nowMs - startMs;
1275
+ let progress = duration / 300; // 300ms duration.
1276
+ if (progress > 1) {
1277
+ progress = 1;
1278
+ }
1279
+ return Cartesian3.lerp(startPos, targetPos, progress, new Cartesian3());
1235
1280
  }
1236
1281
  /**
1237
1282
  * Returns a series of positions to use for rendering the path.
@@ -1414,6 +1459,9 @@ var CesiumAnimatedProperty;
1414
1459
  // Cache for calculated values.
1415
1460
  this.lastCalcTime = null;
1416
1461
  this.lastCalcPos3d = null;
1462
+ this.lastCalcPosApplyOverTime = false;
1463
+ this.lastCalcPos3dApplyOverTimeTarget = null;
1464
+ this.lastCalcPos3dApplyOverTimeTimeStart = null;
1417
1465
  this.lastCalcHeading = null;
1418
1466
  this.removal = null;
1419
1467
  // Track the timeline range for which we have data.
@@ -1511,17 +1559,75 @@ var CesiumAnimatedProperty;
1511
1559
  const nowTimeMs = nowTime.getTime();
1512
1560
  // Skip calculation if time hasn't changed.
1513
1561
  if (this.lastCalcTime === nowTimeMs) {
1562
+ // If we're currently mid-animation, we need to update the position over time.
1563
+ if (this.lastCalcPosApplyOverTime) {
1564
+ const interpolatedPos = this.interpolatePos3dOverTime(this.lastCalcPos3dApplyOverTimeTimeStart, Date.now());
1565
+ if (Cartesian3.equals(interpolatedPos, this.lastCalcPos3dApplyOverTimeTarget)) {
1566
+ this.lastCalcPosApplyOverTime = false;
1567
+ this.lastCalcPos3d = interpolatedPos;
1568
+ }
1569
+ this.onUpdate(interpolatedPos, this.lastCalcHeading);
1570
+ }
1514
1571
  return;
1515
1572
  }
1516
1573
  // Calculate position.
1517
1574
  const position = this.calculatePosition(nowTimeMs);
1575
+ // Calculate what position to emit.
1576
+ let pos3d = null;
1518
1577
  // Update cache values.
1519
1578
  this.lastCalcTime = nowTimeMs;
1520
- this.lastCalcPos3d = position.pos3d;
1579
+ if (position.applyOverTime && Cartes.ValidateCartes3(this.lastCalcPos3d)) {
1580
+ if (Cartesian3.equals(this.lastCalcPos3d, position.pos3d)) {
1581
+ pos3d = position.pos3d;
1582
+ this.lastCalcPosApplyOverTime = false;
1583
+ }
1584
+ else if (Cartesian3.equals(this.lastCalcPos3dApplyOverTimeTarget, position.pos3d)) {
1585
+ pos3d = this.interpolatePos3dOverTime(this.lastCalcPos3dApplyOverTimeTimeStart, Date.now());
1586
+ if (Cartesian3.equals(pos3d, this.lastCalcPos3dApplyOverTimeTarget)) {
1587
+ this.lastCalcPos3d = pos3d;
1588
+ this.lastCalcPosApplyOverTime = false;
1589
+ }
1590
+ }
1591
+ else {
1592
+ if (!this.lastCalcPosApplyOverTime) {
1593
+ this.lastCalcPos3dApplyOverTimeTimeStart = Date.now();
1594
+ }
1595
+ this.lastCalcPosApplyOverTime = true;
1596
+ this.lastCalcPos3dApplyOverTimeTarget = position.pos3d;
1597
+ pos3d = this.interpolatePos3dOverTime(this.lastCalcPos3dApplyOverTimeTimeStart, Date.now());
1598
+ }
1599
+ }
1600
+ else {
1601
+ this.lastCalcPos3d = position.pos3d;
1602
+ pos3d = position.pos3d;
1603
+ }
1521
1604
  // Calculate heading.
1522
- this.lastCalcHeading = this.calculateHeading(nowTimeMs, position.indexLast, position.indexNext);
1605
+ const posses = this.positions;
1606
+ const lastPos = posses[position.indexLast];
1607
+ const nextPos = posses[position.indexNext];
1608
+ if (lastPos && nextPos) {
1609
+ this.lastCalcHeading = this.calculateHeading(nowTimeMs, lastPos, nextPos);
1610
+ }
1523
1611
  // Provide the calculated values to the caller.
1524
- this.onUpdate(this.lastCalcPos3d, this.lastCalcHeading);
1612
+ this.onUpdate(pos3d, this.lastCalcHeading);
1613
+ }
1614
+ /**
1615
+ * Interpolates the position over time between the last and target positions.
1616
+ * This is used when we reach a boundary position so to smoothly animate to it, we move from the current position to it.
1617
+ * @param nowMs
1618
+ */
1619
+ interpolatePos3dOverTime(prevMs, nowMs) {
1620
+ const lastPos = this.lastCalcPos3d;
1621
+ const targetPos = this.lastCalcPos3dApplyOverTimeTarget;
1622
+ if (!lastPos || !targetPos) {
1623
+ return lastPos ? lastPos : targetPos;
1624
+ }
1625
+ const duration = nowMs - prevMs;
1626
+ let progress = duration / 300; // 300ms duration.
1627
+ if (progress > 1) {
1628
+ progress = 1;
1629
+ }
1630
+ return Cartesian3.lerp(lastPos, targetPos, progress, new Cartesian3());
1525
1631
  }
1526
1632
  /**
1527
1633
  * Pre-process headings in the series.
@@ -1553,7 +1659,8 @@ var CesiumAnimatedProperty;
1553
1659
  return {
1554
1660
  pos3d: new Cartesian3(),
1555
1661
  indexLast: -1,
1556
- indexNext: -1
1662
+ indexNext: -1,
1663
+ applyOverTime: false
1557
1664
  };
1558
1665
  }
1559
1666
  // See if we're before the first position.
@@ -1561,7 +1668,8 @@ var CesiumAnimatedProperty;
1561
1668
  return {
1562
1669
  pos3d: posses[0].pos3d,
1563
1670
  indexLast: 0,
1564
- indexNext: 0
1671
+ indexNext: 0,
1672
+ applyOverTime: true
1565
1673
  };
1566
1674
  }
1567
1675
  // See if we're after the last position.
@@ -1570,7 +1678,8 @@ var CesiumAnimatedProperty;
1570
1678
  return {
1571
1679
  pos3d: posses[lastIndex].pos3d,
1572
1680
  indexLast: lastIndex,
1573
- indexNext: lastIndex
1681
+ indexNext: lastIndex,
1682
+ applyOverTime: true
1574
1683
  };
1575
1684
  }
1576
1685
  // Binary search to find the closest position.
@@ -1596,7 +1705,8 @@ var CesiumAnimatedProperty;
1596
1705
  return {
1597
1706
  pos3d: last.pos3d,
1598
1707
  indexLast: lastIndex,
1599
- indexNext: lastIndex
1708
+ indexNext: lastIndex,
1709
+ applyOverTime: true
1600
1710
  };
1601
1711
  }
1602
1712
  // Interpolate between the two closest positions.
@@ -1607,25 +1717,18 @@ var CesiumAnimatedProperty;
1607
1717
  return {
1608
1718
  pos3d: interpolatedPos,
1609
1719
  indexLast: lastIndex,
1610
- indexNext: nextIndex
1720
+ indexNext: nextIndex,
1721
+ applyOverTime: false
1611
1722
  };
1612
1723
  }
1613
1724
  /**
1614
1725
  * Calculate the heading at the given time.
1615
1726
  */
1616
- calculateHeading(currentTimeMs, lastIndex, nextIndex) {
1617
- const posses = this.positions;
1618
- // Ensure valid indices.
1619
- if (lastIndex < 0 || nextIndex < 0 ||
1620
- lastIndex >= posses.length || nextIndex >= posses.length) {
1621
- return null;
1622
- }
1623
- const lastPos = posses[lastIndex];
1624
- const nextPos = posses[nextIndex];
1727
+ calculateHeading(currentTimeMs, lastPos, nextPos) {
1625
1728
  // If the heading is present and not null, interpolate or use it directly.
1626
1729
  if (lastPos.heading !== null && nextPos.heading !== null) {
1627
1730
  // If they're the same position or same time, just return the heading.
1628
- if (lastIndex === nextIndex ||
1731
+ if (lastPos === nextPos ||
1629
1732
  lastPos.dateTime.getTime() === nextPos.dateTime.getTime()) {
1630
1733
  return lastPos.heading;
1631
1734
  }
@@ -1645,16 +1748,16 @@ var CesiumAnimatedProperty;
1645
1748
  }
1646
1749
  // If no valid heading is available, calculate based on movement direction.
1647
1750
  else {
1648
- const previousPos = lastPos.pos3d;
1649
- const currentPos = nextPos.pos3d;
1650
- if (!previousPos || isNaN(previousPos.x) ||
1651
- !currentPos || isNaN(currentPos.x)) {
1751
+ const prevPos3d = lastPos.pos3d;
1752
+ const nextPos3d = nextPos.pos3d;
1753
+ if (!prevPos3d || isNaN(prevPos3d.x) ||
1754
+ !nextPos3d || isNaN(nextPos3d.x)) {
1652
1755
  return null;
1653
1756
  }
1654
1757
  // Flatten to avoid orientation changes due to height differences.
1655
- const adjustedPointPrev = Cartographic.fromCartesian(previousPos);
1758
+ const adjustedPointPrev = Cartographic.fromCartesian(prevPos3d);
1656
1759
  const adjustedPos3dPrev = Cartesian3.fromRadians(adjustedPointPrev.longitude, adjustedPointPrev.latitude, 0);
1657
- const adjustedPointNext = Cartographic.fromCartesian(currentPos);
1760
+ const adjustedPointNext = Cartographic.fromCartesian(nextPos3d);
1658
1761
  const adjustedPos3dNext = Cartesian3.fromRadians(adjustedPointNext.longitude, adjustedPointNext.latitude, 0);
1659
1762
  // Check if the positions are too close.
1660
1763
  if (Cartesian3.distance(adjustedPos3dPrev, adjustedPos3dNext) < 0.05) {
@@ -1665,14 +1768,24 @@ var CesiumAnimatedProperty;
1665
1768
  if (direction.x === 0 && direction.y === 0 && direction.z === 0) {
1666
1769
  return null;
1667
1770
  }
1668
- // Calculate heading from the direction vector.
1771
+ // Normalize the direction vector.
1669
1772
  Cartesian3.normalize(direction, direction);
1670
- // Convert the direction to a heading angle.
1671
- const east = Cartesian3.UNIT_X;
1672
- const north = Cartesian3.UNIT_Y;
1673
- const heading = Math.atan2(Cartesian3.dot(direction, east), Cartesian3.dot(direction, north));
1674
- // Convert to degrees.
1675
- return (Math$1.toDegrees(heading) + 360) % 360;
1773
+ // Calculate heading in the ENU (East-North-Up).
1774
+ // First, get the local ENU frame at the midpoint.
1775
+ const midPoint = Cartesian3.midpoint(adjustedPos3dPrev, adjustedPos3dNext, new Cartesian3());
1776
+ // Convert direction from ECEF to ENU.
1777
+ // We need to create a transform from ECEF to ENU at our position.
1778
+ const transform = Transforms.eastNorthUpToFixedFrame(midPoint);
1779
+ const inverseTransform = Matrix4.inverseTransformation(transform, new Matrix4());
1780
+ // Transform our direction vector to local ENU.
1781
+ const localDirection = Matrix4.multiplyByPointAsVector(inverseTransform, direction, new Cartesian3());
1782
+ Cartesian3.normalize(localDirection, localDirection);
1783
+ // Now calculate the heading in the ENU frame (clockwise from north).
1784
+ // atan2(east, north) gives us the angle in the east-north plane measured clockwise from north.
1785
+ const heading = Math.atan2(localDirection.x, localDirection.y);
1786
+ // Convert to degrees and ensure it's in the range [0, 360].
1787
+ const headingDegrees = (Math$1.toDegrees(heading) + 360) % 360;
1788
+ return headingDegrees;
1676
1789
  }
1677
1790
  }
1678
1791
  }
@@ -4554,12 +4667,14 @@ var EntityRenderEnginePoint;
4554
4667
  // Unset width/height.
4555
4668
  cEntity.billboard.width = undefined;
4556
4669
  cEntity.billboard.height = undefined;
4670
+ const prevPos3d = GetCValue(params.viewer, cEntity.position);
4557
4671
  // If we have a series of time-based positions then we'll animate as time changes.
4558
4672
  const series = CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(params.viewer, heightRef, heightRef, params.entityHistoric);
4559
4673
  if (series.length > 1) {
4560
4674
  animatePosition = new CesiumAnimatedProperty.AnimatePositionSeries({
4561
4675
  posses: series,
4562
- viewer: params.viewer
4676
+ viewer: params.viewer,
4677
+ animateFromPos3d: prevPos3d
4563
4678
  });
4564
4679
  cEntity.position = new CallbackProperty(() => animatePosition.GetValue(), false);
4565
4680
  }
@@ -4571,7 +4686,6 @@ var EntityRenderEnginePoint;
4571
4686
  returnHeightRef: heightRef,
4572
4687
  allowRendered: false
4573
4688
  });
4574
- const prevPos3d = GetCValue(params.viewer, cEntity.position);
4575
4689
  if (!prevPos3d || !Cartesian3.equals(prevPos3d, pos3d)) {
4576
4690
  animatePosition = new CesiumAnimatedProperty.AnimatePosition({
4577
4691
  durationMs: 200,
@@ -4877,12 +4991,14 @@ var EntityRenderEnginePoint;
4877
4991
  cEntity.billboard.heightReference = new ConstantProperty(heightRef);
4878
4992
  cEntity.billboard.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance));
4879
4993
  cEntity.billboard.disableDepthTestDistance = new ConstantProperty(disableDepthTest ? Number.POSITIVE_INFINITY : undefined);
4994
+ const prevPos3d = GetCValue(params.viewer, cEntity.position);
4880
4995
  // If we have a series of time-based positions then we'll animate as time changes.
4881
4996
  const series = CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(params.viewer, heightRef, heightRef, params.entityHistoric);
4882
4997
  if (series.length > 1) {
4883
4998
  animatePosition = new CesiumAnimatedProperty.AnimatePositionSeries({
4884
4999
  posses: series,
4885
- viewer: params.viewer
5000
+ viewer: params.viewer,
5001
+ animateFromPos3d: prevPos3d
4886
5002
  });
4887
5003
  cEntity.position = new CallbackProperty(() => animatePosition.GetValue(), false);
4888
5004
  }
@@ -4894,7 +5010,6 @@ var EntityRenderEnginePoint;
4894
5010
  returnHeightRef: heightRef,
4895
5011
  allowRendered: false
4896
5012
  });
4897
- const prevPos3d = GetCValue(params.viewer, cEntity.position);
4898
5013
  if (!prevPos3d || !Cartesian3.equals(prevPos3d, pos3d)) {
4899
5014
  animatePosition = new CesiumAnimatedProperty.AnimatePosition({
4900
5015
  durationMs: 200,
@@ -6474,6 +6589,7 @@ var EntityRenderEngineModel3d;
6474
6589
  cEntity.model.colorBlendAmount = new ConstantProperty(blendAmount);
6475
6590
  cEntity.model.colorBlendMode = new ConstantProperty(blendMode);
6476
6591
  cEntity.model.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance));
6592
+ const prevPos3d = GetCValue(params.viewer, cEntity.position);
6477
6593
  // If we have a series of time-based positions then we'll animate as time changes.
6478
6594
  const series = CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(params.viewer, heightRef, heightRef, params.entityHistoric);
6479
6595
  if (series.length > 1) {
@@ -6481,12 +6597,12 @@ var EntityRenderEngineModel3d;
6481
6597
  posses: series,
6482
6598
  viewer: params.viewer,
6483
6599
  pitch: pitch,
6484
- roll: roll
6600
+ roll: roll,
6601
+ animateFromPos3d: prevPos3d
6485
6602
  });
6486
6603
  cEntity.position = new CallbackProperty(() => animatePosition.GetValue(), false);
6487
6604
  }
6488
6605
  else {
6489
- const prevPos3d = GetCValue(params.viewer, cEntity.position);
6490
6606
  const posChanged = !prevPos3d || !Cartesian3.equals(prevPos3d, pos3d);
6491
6607
  if (posChanged) {
6492
6608
  animatePosition = new CesiumAnimatedProperty.AnimatePosition({
@@ -14395,36 +14511,54 @@ var EntitiesIdsRenderManager;
14395
14511
  * @param entities
14396
14512
  */
14397
14513
  async getHistoricInfo(entities) {
14398
- const startTmp = JulianDate.toDate(this.viewer.clock.startTime);
14399
- const stopTmp = JulianDate.toDate(this.viewer.clock.stopTime);
14514
+ // Time padding in milliseconds (15 seconds).
14515
+ // Helps account for desync between client and server.
14516
+ const TIME_PADDING_MS = 15000;
14517
+ const minDateTimeStr = this.viewer.clock.startTime.toString();
14518
+ const maxDateTimeStr = this.viewer.clock.stopTime.toString();
14519
+ const minDateTime = new Date(minDateTimeStr).getTime();
14520
+ const maxDateTime = new Date(maxDateTimeStr).getTime();
14400
14521
  let rangesToRequest = [{
14401
- start: startTmp.toISOString(),
14402
- stop: stopTmp.toISOString()
14522
+ start: minDateTime,
14523
+ stop: maxDateTime
14403
14524
  }];
14404
14525
  // If we already have cached data, determine what ranges we're missing.
14405
- if (this.lastHistoricMin && this.lastHistoricMax && Object.keys(this.entitiesHistoric).length >= entities.length) {
14406
- const cachedStart = new Date(this.lastHistoricMin).getTime();
14407
- const cachedStop = new Date(this.lastHistoricMax).getTime();
14408
- const newStart = startTmp.getTime();
14409
- const newStop = stopTmp.getTime();
14526
+ if (Object.keys(this.entitiesHistoric).length >= entities.length) {
14527
+ let foundMinDateTime = null;
14528
+ let foundMaxDateTime = null;
14529
+ // Find the min/max based on cached data.
14530
+ // Since we set the values sorted, we only have to check the first and last records for each entity.
14531
+ for (const entityId of Object.keys(this.entitiesHistoric)) {
14532
+ const records = this.entitiesHistoric[entityId] || [];
14533
+ if (records.length) {
14534
+ const dateTime = new Date(records[0].dateTime).getTime();
14535
+ if (foundMinDateTime == null || dateTime < foundMinDateTime) {
14536
+ foundMinDateTime = dateTime;
14537
+ }
14538
+ const dateTime2 = new Date(records[records.length - 1].dateTime).getTime();
14539
+ if (foundMaxDateTime == null || dateTime2 > foundMaxDateTime) {
14540
+ foundMaxDateTime = dateTime2;
14541
+ }
14542
+ }
14543
+ }
14410
14544
  // Complete overlap - we already have all the data.
14411
- if (newStart >= cachedStart && newStop <= cachedStop) {
14545
+ if (foundMinDateTime != null && foundMaxDateTime != null && foundMinDateTime <= minDateTime && foundMaxDateTime >= maxDateTime) {
14412
14546
  return [false, this.entitiesHistoric];
14413
14547
  }
14414
14548
  // Calculate missing ranges.
14415
14549
  rangesToRequest = [];
14416
14550
  // Check if we need data before our cached range.
14417
- if (newStart < cachedStart) {
14551
+ if (foundMinDateTime != null && foundMinDateTime > minDateTime) {
14418
14552
  rangesToRequest.push({
14419
- start: startTmp.toISOString(),
14420
- stop: new Date(cachedStart).toISOString()
14553
+ start: minDateTime,
14554
+ stop: new Date(foundMinDateTime).getTime()
14421
14555
  });
14422
14556
  }
14423
14557
  // Check if we need data after our cached range.
14424
- if (newStop > cachedStop) {
14558
+ if (foundMaxDateTime != null && foundMaxDateTime < maxDateTime) {
14425
14559
  rangesToRequest.push({
14426
- start: new Date(cachedStop).toISOString(),
14427
- stop: stopTmp.toISOString()
14560
+ start: new Date(foundMaxDateTime).getTime(),
14561
+ stop: maxDateTime
14428
14562
  });
14429
14563
  }
14430
14564
  }
@@ -14435,17 +14569,12 @@ var EntitiesIdsRenderManager;
14435
14569
  if (this.disposed) {
14436
14570
  break;
14437
14571
  }
14438
- // Add padding to ensure we get all data.
14439
- const start = new Date(range.start);
14440
- const stop = new Date(range.stop);
14441
- start.setSeconds(start.getSeconds() - 1);
14442
- stop.setSeconds(stop.getSeconds() + 1);
14443
- const paddedStartStr = start.toISOString();
14444
- const paddedStopStr = stop.toISOString();
14572
+ const start = new Date(range.start - TIME_PADDING_MS);
14573
+ const stop = new Date(range.stop + TIME_PADDING_MS);
14445
14574
  const historicData = await EntityHistoricData.GetList({
14446
14575
  attrKey: this.item.BruceEntity.historicAttrKey,
14447
- dateTimeFrom: paddedStartStr,
14448
- dateTimeTo: paddedStopStr,
14576
+ dateTimeFrom: start.toISOString(),
14577
+ dateTimeTo: stop.toISOString(),
14449
14578
  entityIds: entityIds,
14450
14579
  api: this.apiGetter.getApi()
14451
14580
  });
@@ -14479,9 +14608,6 @@ var EntitiesIdsRenderManager;
14479
14608
  }
14480
14609
  }
14481
14610
  if (!this.disposed) {
14482
- // Update our cache boundaries.
14483
- this.lastHistoricMin = startTmp.toISOString();
14484
- this.lastHistoricMax = stopTmp.toISOString();
14485
14611
  this.entitiesHistoric = combined;
14486
14612
  }
14487
14613
  return [rangesToRequest.length > 0, combined];
@@ -14703,969 +14829,873 @@ var EntityRenderManager;
14703
14829
  EntityRenderManager.Manager = Manager;
14704
14830
  })(EntityRenderManager || (EntityRenderManager = {}));
14705
14831
 
14706
- /**
14707
- * This is a helper for making entity requests based on a grid area.
14708
- * It will return grid cells based on given view-area then will remember when stuff-
14709
- * was fully-fetched for those cells to avoid making duplicate requests.
14710
- */
14711
- var EntityGlobe;
14712
- (function (EntityGlobe) {
14713
- class Cell {
14714
- constructor() {
14715
- this.Fetched = false;
14716
- this.IsFetched = null;
14717
- this.FetchPageIndex = 0;
14718
- // Optional URL to use instead of default URL generation.
14719
- // This is typically a response from the API to use this specific URL for the next page.
14720
- this.FetchURL = null;
14721
- this.Boundaries = null;
14722
- this.Fetching = false;
14832
+ class EntityGatherer {
14833
+ get OnQueueProgress() {
14834
+ if (!this._onQueueProgress) {
14835
+ this._onQueueProgress = new BruceEvent();
14723
14836
  }
14724
- GetBounds() {
14725
- // Entity data works in -180 to 180 range.
14726
- function prepareRangeForBounds(range) {
14727
- if (range > 180) {
14728
- return range - 360;
14729
- }
14730
- return range;
14731
- }
14732
- // Add minor decimal as API crashes when giving it whole numbers.
14733
- const maxLon = prepareRangeForBounds(this.Boundaries.maxLongitude);
14734
- const minLon = prepareRangeForBounds(this.Boundaries.minLongitude);
14735
- const maxLat = prepareRangeForBounds(this.Boundaries.maxLatitude);
14736
- const minLat = prepareRangeForBounds(this.Boundaries.minLatitude);
14737
- return {
14738
- east: maxLon,
14739
- north: maxLat,
14740
- south: minLat,
14741
- west: minLon
14742
- };
14837
+ return this._onQueueProgress;
14838
+ }
14839
+ constructor(api, viewer, emitEntities) {
14840
+ this.disposed = false;
14841
+ this.expandSources = false;
14842
+ this.historic = null;
14843
+ this.hDisposals = [];
14844
+ // Last date-time taken from the clock.
14845
+ this.lastDateTime = null;
14846
+ // Last date-time range taken from the clock.
14847
+ this.lastDateTimeMin = null;
14848
+ this.lastDateTimeMax = null;
14849
+ // Entity ID -> last resolved date time.
14850
+ // Helps determine if a slow resolved request can still be applied while we wait for the next one.
14851
+ this.lastFoundDateTimes = new Map();
14852
+ // ID of Entity IDs that need to be requested.
14853
+ this.eIdQueue = [];
14854
+ // All IDs that have ever been queued.
14855
+ // When nothing is in queue, we'll re-request the existing IDs based on timeline changes.
14856
+ this.allEIds = new Set();
14857
+ // Indicates a tick is active.
14858
+ // When true, we won't bother calling onTick again.
14859
+ this.runningTick = false;
14860
+ // Entity ID -> Entity blob.
14861
+ // This is a tool that lets developers set their own JSON blobs instead of requesting data from API.
14862
+ this.entityDataOverrides = new Map();
14863
+ // 0 - 100 % progress callback for the queue.
14864
+ // Only called if at least 1 Entity is in the queue at start of the tick.
14865
+ this._onQueueProgress = null;
14866
+ this.api = api;
14867
+ this.viewer = viewer;
14868
+ this.emitEntities = emitEntities;
14869
+ }
14870
+ SetEntityDataOverride(eId, entity) {
14871
+ if (this.disposed) {
14872
+ return;
14873
+ }
14874
+ if (entity) {
14875
+ this.entityDataOverrides.set(eId, entity);
14876
+ }
14877
+ else {
14878
+ this.entityDataOverrides.delete(eId);
14743
14879
  }
14744
14880
  }
14745
- EntityGlobe.Cell = Cell;
14746
- class Grid {
14747
- constructor() {
14748
- this.cells = {};
14881
+ /**
14882
+ * Initializes or updates the manager with given new state settings.
14883
+ * @param historic
14884
+ * @param expandSources
14885
+ * @returns
14886
+ */
14887
+ Init(historic, expandSources) {
14888
+ if (historic == null || historic == undefined) {
14889
+ historic = this.historic;
14749
14890
  }
14750
- GetCellsForView(cameraPos, viewRect) {
14751
- const cells = [];
14752
- const minLat = viewRect.south;
14753
- const minLon = viewRect.west;
14754
- const maxLat = viewRect.north;
14755
- const maxLon = viewRect.east;
14756
- const MAX_CELLS = 150;
14757
- const cellDegreeSize = getCellSizeFromHeight(viewRect.alt);
14758
- // console.log("cell size", cellDegreeSize, "height", viewRect.alt);
14759
- let curMinLon = floorValueToCellSize(cellDegreeSize, minLon);
14760
- let curMinLat = floorValueToCellSize(cellDegreeSize, minLat);
14761
- // For larger views we add additional padding because our view-area culling is too strong.
14762
- if (cellDegreeSize >= 2) {
14763
- curMinLon -= cellDegreeSize;
14764
- curMinLat -= cellDegreeSize;
14765
- }
14766
- let centerX = cameraPos === null || cameraPos === void 0 ? void 0 : cameraPos.longitude;
14767
- let centerY = cameraPos === null || cameraPos === void 0 ? void 0 : cameraPos.latitude;
14768
- if (isNaN(centerX) || isNaN(centerY)) {
14769
- centerX = (minLon + maxLon) / 2;
14770
- centerY = (minLat + maxLat) / 2;
14771
- }
14772
- let width = Math.ceil((maxLon - curMinLon) / cellDegreeSize);
14773
- let height = Math.ceil((maxLat - curMinLat) / cellDegreeSize);
14774
- // For larger views we add additional padding because our view-area culling is too strong.
14775
- if (cellDegreeSize >= 2) {
14776
- width += 1;
14777
- height += 1;
14778
- }
14779
- const cellDistances = [];
14780
- for (let x = 0; x < width; x++) {
14781
- for (let y = 0; y < height; y++) {
14782
- const lon = x * cellDegreeSize + curMinLon;
14783
- const lat = y * cellDegreeSize + curMinLat;
14784
- const cellCenterX = lon + cellDegreeSize / 2;
14785
- const cellCenterY = lat + cellDegreeSize / 2;
14786
- const dist = Math.sqrt(Math.pow(cellCenterX - centerX, 2) + Math.pow(cellCenterY - centerY, 2));
14787
- cellDistances.push({ x, y, dist });
14788
- }
14789
- }
14790
- cellDistances.sort((a, b) => a.dist - b.dist);
14791
- for (const { x, y } of cellDistances) {
14792
- const lon = x * cellDegreeSize + curMinLon;
14793
- const lat = y * cellDegreeSize + curMinLat;
14794
- const [id, cell] = getOrCreateCell(this.cells, cellDegreeSize, lon, lon + cellDegreeSize, lat, lat + cellDegreeSize);
14795
- cells.push(cell);
14796
- if (cells.length >= MAX_CELLS) {
14797
- break;
14891
+ if (expandSources == null || expandSources == undefined) {
14892
+ expandSources = this.expandSources;
14893
+ }
14894
+ if (historic === this.historic && expandSources === this.expandSources) {
14895
+ return;
14896
+ }
14897
+ this.historic = historic;
14898
+ this.expandSources = expandSources;
14899
+ if (this.historic) {
14900
+ // 4 ticks per second.
14901
+ // The fastest data we are working with is 5 times per second.
14902
+ let tickDelay = new DelayQueue(() => {
14903
+ this.onTick(false);
14904
+ }, 250, true);
14905
+ this.hDisposals.push(() => {
14906
+ if (tickDelay) {
14907
+ tickDelay.Dispose();
14908
+ }
14909
+ tickDelay = null;
14910
+ });
14911
+ // React to clock changes and request new Entities.
14912
+ this.hDisposals.push(this.viewer.clock.onTick.addEventListener(() => {
14913
+ if (tickDelay) {
14914
+ tickDelay.Call();
14798
14915
  }
14799
- }
14800
- return cells;
14916
+ }));
14917
+ // Since the timeline range can change even without it ticking we have to check it occasionally.
14918
+ let rangeCheckInterval = setInterval(() => {
14919
+ this.checkRange(false);
14920
+ }, 800);
14921
+ this.hDisposals.push(() => {
14922
+ if (rangeCheckInterval) {
14923
+ clearInterval(rangeCheckInterval);
14924
+ }
14925
+ rangeCheckInterval = null;
14926
+ });
14801
14927
  }
14928
+ else {
14929
+ this.hDisposals.forEach((d) => d());
14930
+ this.hDisposals = [];
14931
+ }
14932
+ this.checkRange(true);
14802
14933
  }
14803
- EntityGlobe.Grid = Grid;
14804
- })(EntityGlobe || (EntityGlobe = {}));
14805
- function floorValueToCellSize(size, value) {
14806
- return Math.floor(value / size) * size;
14807
- }
14808
- function getCellSizeFromHeight(height) {
14809
- if (height < 1000) {
14810
- return 0.01;
14811
- }
14812
- if (height < 5000) {
14813
- return 0.04;
14934
+ Empty() {
14935
+ if (this.disposed) {
14936
+ return;
14937
+ }
14938
+ this.eIdQueue = [];
14939
+ this.allEIds.clear();
14940
+ this.lastFoundDateTimes.clear();
14941
+ this.lastDateTime = null;
14814
14942
  }
14815
- else if (height < 10000) {
14816
- return 0.15;
14943
+ ReQueueAll() {
14944
+ if (this.disposed) {
14945
+ return;
14946
+ }
14947
+ this.eIdQueue = Array.from(this.allEIds);
14817
14948
  }
14818
- else if (height < 30000) {
14819
- return 0.5;
14949
+ Queue(entityIds, topOfQueue = false) {
14950
+ if (this.disposed || !(entityIds === null || entityIds === void 0 ? void 0 : entityIds.length)) {
14951
+ return;
14952
+ }
14953
+ let changes = 0;
14954
+ for (let i = 0; i < entityIds.length; i++) {
14955
+ const eId = entityIds[i];
14956
+ const index = this.eIdQueue.indexOf(eId);
14957
+ if (topOfQueue) {
14958
+ // If we want it to be at the top of the queue, remove it from the queue first.
14959
+ if (index !== -1) {
14960
+ this.eIdQueue.splice(index, 1);
14961
+ }
14962
+ this.eIdQueue.unshift(eId);
14963
+ changes += 1;
14964
+ }
14965
+ else if (index === -1) {
14966
+ this.eIdQueue.push(eId);
14967
+ changes += 1;
14968
+ }
14969
+ // Flag that we've seen this ID.
14970
+ this.allEIds.add(entityIds[i]);
14971
+ }
14972
+ if (!changes) {
14973
+ return;
14974
+ }
14975
+ this.onTick(false);
14820
14976
  }
14821
- else if (height < 70000) {
14822
- return 0.5;
14977
+ Dispose() {
14978
+ if (this.disposed) {
14979
+ return;
14980
+ }
14981
+ this.disposed = true;
14982
+ this.hDisposals.forEach((d) => d());
14983
+ this.hDisposals = [];
14823
14984
  }
14824
- else if (height < 100000) {
14825
- return 1;
14985
+ checkRange(force) {
14986
+ const min = this.viewer.clock.startTime.toString();
14987
+ const max = this.viewer.clock.stopTime.toString();
14988
+ if (force || this.lastDateTimeMin !== min || this.lastDateTimeMax !== max) {
14989
+ this.lastDateTimeMin = min;
14990
+ this.lastDateTimeMax = max;
14991
+ // Changed, so we can pretend it's a clock tick and request new Entities.
14992
+ this.onTick(true);
14993
+ }
14826
14994
  }
14827
- else if (height < 150000) {
14828
- return 1;
14995
+ onTick(force) {
14996
+ if (this.runningTick) {
14997
+ return;
14998
+ }
14999
+ const rTime = this.viewer.clock.currentTime.toString();
15000
+ if (!force && !this.eIdQueue.length && this.historic && this.lastDateTime === rTime) {
15001
+ return;
15002
+ }
15003
+ this.lastDateTime = rTime;
15004
+ (async () => {
15005
+ this.runningTick = true;
15006
+ let rerun = false;
15007
+ try {
15008
+ const handleResponse = async (entities) => {
15009
+ var _a, _b;
15010
+ if (!(entities === null || entities === void 0 ? void 0 : entities.length)) {
15011
+ return;
15012
+ }
15013
+ // Lazy substitution of the entity data overrides.
15014
+ if (this.entityDataOverrides.size) {
15015
+ for (let i = 0; i < entities.length; i++) {
15016
+ const entity = entities[i];
15017
+ const eId = entity.Bruce.ID;
15018
+ if (this.entityDataOverrides.has(eId)) {
15019
+ entities[i] = this.entityDataOverrides.get(eId);
15020
+ }
15021
+ }
15022
+ }
15023
+ // Gather all Tag IDs from found Entities.
15024
+ let tagIds = new Set();
15025
+ for (let i = 0; i < entities.length; i++) {
15026
+ const entity = entities[i];
15027
+ if ((_b = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a["Layer.ID"]) === null || _b === void 0 ? void 0 : _b.length) {
15028
+ const eTagIds = entity.Bruce["Layer.ID"];
15029
+ for (let j = 0; j < eTagIds.length; j++) {
15030
+ const tagId = eTagIds[j];
15031
+ if (tagId) {
15032
+ tagIds.add(tagId);
15033
+ }
15034
+ }
15035
+ }
15036
+ }
15037
+ // Gather records.
15038
+ let tags = [];
15039
+ if (tagIds.size) {
15040
+ tags = (await EntityTag.GetListByIds({
15041
+ tagIds: Array.from(tagIds),
15042
+ api: this.api
15043
+ })).tags;
15044
+ }
15045
+ // 0 = rTime is still the current time.
15046
+ // 1 = rTime is in the future.
15047
+ // -1 = rTime is in the past.
15048
+ let changeDir = 0;
15049
+ if (this.lastDateTime !== this.viewer.clock.currentTime.toString()) {
15050
+ changeDir = this.lastDateTime < this.viewer.clock.currentTime.toString() ? 1 : -1;
15051
+ }
15052
+ let toEmit;
15053
+ if (this.historic) {
15054
+ // We can emit Entities if the last resolved time (per Entity) isn't newer than rTime.
15055
+ // We compare using the changeDir to determine if we should emit or not.
15056
+ toEmit = entities.filter((e) => {
15057
+ const lastFoundDateTime = this.lastFoundDateTimes.get(e.id);
15058
+ if (lastFoundDateTime) {
15059
+ if (changeDir === 1) {
15060
+ return lastFoundDateTime <= rTime;
15061
+ }
15062
+ else if (changeDir === -1) {
15063
+ return lastFoundDateTime >= rTime;
15064
+ }
15065
+ }
15066
+ return true;
15067
+ });
15068
+ // Update the last found date times for the filtered Entities.
15069
+ toEmit.forEach((e) => {
15070
+ this.lastFoundDateTimes.set(e.id, rTime);
15071
+ });
15072
+ }
15073
+ else {
15074
+ // If we're not historic, we can just emit everything.
15075
+ toEmit = entities;
15076
+ }
15077
+ this.emitEntities(toEmit, tags);
15078
+ };
15079
+ const QUEUE_BATCH_SIZE = 500;
15080
+ const requestedIds = [];
15081
+ // If we have a queue, we need to request those first.
15082
+ if (this.eIdQueue.length) {
15083
+ const total = this.eIdQueue.length;
15084
+ let done = 0;
15085
+ while (this.eIdQueue.length && !this.disposed) {
15086
+ if (this.historic && this.lastDateTime !== rTime) {
15087
+ break;
15088
+ }
15089
+ const batch = this.eIdQueue.splice(0, QUEUE_BATCH_SIZE);
15090
+ const { entities } = await Entity$1.GetListByIds({
15091
+ entityIds: batch,
15092
+ historicPoint: this.historic ? rTime : null,
15093
+ expandSources: this.expandSources,
15094
+ api: this.api
15095
+ });
15096
+ handleResponse(entities);
15097
+ requestedIds.push(...batch);
15098
+ done += batch.length;
15099
+ if (this._onQueueProgress) {
15100
+ let progress = (done / total) * 100;
15101
+ if (progress > 100) {
15102
+ progress = 100;
15103
+ }
15104
+ if (progress < 0) {
15105
+ progress = 0;
15106
+ }
15107
+ this._onQueueProgress.Trigger(progress);
15108
+ }
15109
+ }
15110
+ if (this._onQueueProgress) {
15111
+ this._onQueueProgress.Trigger(100);
15112
+ }
15113
+ }
15114
+ // Now run through all IDs that we've seen and request them.
15115
+ // We'll skip those that we just requested.
15116
+ let allEIds = Array.from(this.allEIds);
15117
+ if (requestedIds.length) {
15118
+ allEIds = allEIds.filter((eId) => requestedIds.indexOf(eId) === -1);
15119
+ }
15120
+ if (allEIds.length) {
15121
+ while (allEIds.length && !this.disposed && !this.eIdQueue.length) {
15122
+ if (this.historic && this.lastDateTime !== rTime) {
15123
+ break;
15124
+ }
15125
+ const batch = allEIds.splice(0, QUEUE_BATCH_SIZE);
15126
+ const { entities } = await Entity$1.GetListByIds({
15127
+ entityIds: batch,
15128
+ historicPoint: this.historic ? rTime : null,
15129
+ expandSources: this.expandSources,
15130
+ api: this.api
15131
+ });
15132
+ handleResponse(entities);
15133
+ requestedIds.push(...batch);
15134
+ }
15135
+ }
15136
+ // If we had leftovers because we stopped early, we need to re-run the tick.
15137
+ if (this.eIdQueue.length || allEIds.length) {
15138
+ rerun = true;
15139
+ }
15140
+ }
15141
+ catch (e) {
15142
+ console.error(e);
15143
+ }
15144
+ finally {
15145
+ this.runningTick = false;
15146
+ }
15147
+ if (rerun) {
15148
+ this.onTick(true);
15149
+ }
15150
+ })();
14829
15151
  }
14830
- else if (height < 200000) {
14831
- return 1.5;
14832
- }
14833
- else if (height < 300000) {
14834
- return 1.5;
14835
- }
14836
- else if (height < 500000) {
14837
- return 3;
14838
- }
14839
- else if (height < 1000000) {
14840
- return 3;
14841
- }
14842
- else if (height < 1200000) {
14843
- return 4;
14844
- }
14845
- else if (height < 2000000) {
14846
- return 6;
14847
- }
14848
- else if (height < 3000000) {
14849
- return 20;
14850
- }
14851
- return 35;
14852
- }
14853
- function isCellFetched(cell) {
14854
- if (cell.Fetched) {
14855
- return true;
14856
- }
14857
- return false;
14858
- }
14859
- function getOrCreateCell(cells, cellSize, lon, maxLon, lat, maxLat) {
14860
- const id = cellSize + "_" + lon + "_" + maxLon + "_" + lat + "_" + maxLat;
14861
- let cell = cells[id];
14862
- if (!cell) {
14863
- cell = cells[id] = new EntityGlobe.Cell();
14864
- cell.Boundaries = {
14865
- minLatitude: lat,
14866
- maxLatitude: maxLat,
14867
- minLongitude: lon,
14868
- maxLongitude: maxLon
14869
- };
14870
- cell.IsFetched = () => isCellFetched(cell);
14871
- }
14872
- return [id, cell];
14873
15152
  }
14874
15153
 
14875
- const MAX_AREA_IN_DEGREES$1 = 90;
14876
- const MAX_RETRY_ATTEMPTS = 1;
14877
- const RETRY_DELAY_INCREMENT = 500;
14878
- const REQUEST_PAGE_DELAY = 50;
14879
- class regMenuItemGetter {
14880
- constructor(typeId, tagIds, minHeight, maxHeight) {
14881
- this.TypeId = typeId;
14882
- this.TagIds = tagIds;
14883
- this.MinHeight = minHeight;
14884
- this.MaxHeight = maxHeight;
15154
+ // ND-1641. BOOKMARKS - (DEMO) View does not match bookmark.
15155
+ // We have some evil hard-coded style mappings that need to be fixed.
15156
+ // These exist within legacy project views.
15157
+ function correctStyle(style) {
15158
+ if (style && !style.Settings && !style.ID) {
15159
+ return {
15160
+ ID: -1,
15161
+ Name: "Unknown",
15162
+ Settings: {
15163
+ ...style
15164
+ },
15165
+ Type: Style.EType.Entity
15166
+ };
14885
15167
  }
15168
+ return style;
14886
15169
  }
14887
- async function delay(milliseconds) {
14888
- return new Promise((res) => {
14889
- setTimeout(() => {
14890
- res();
14891
- }, milliseconds);
14892
- });
15170
+ function colorToCColor$3(color) {
15171
+ return new Color(color.red ? color.red / 255 : 0, color.green ? color.green / 255 : 0, color.blue ? color.blue / 255 : 0, color.alpha);
14893
15172
  }
14894
- /**
14895
- * This is a batched entity getter.
14896
- * It will scan for entity records in a view-area and emit them in batches.
14897
- * It will restart scanning if the camera moves.
14898
- */
14899
- var EntityFilterGetter;
14900
- (function (EntityFilterGetter) {
14901
- let EStatus;
14902
- (function (EStatus) {
14903
- EStatus["Scanning"] = "SCANNING";
14904
- EStatus["Loading"] = "LOADING";
14905
- })(EStatus = EntityFilterGetter.EStatus || (EntityFilterGetter.EStatus = {}));
14906
- class Getter {
14907
- get OnUpdate() {
14908
- if (!this.onUpdate) {
14909
- this.onUpdate = new BruceEvent();
14910
- }
14911
- return this.onUpdate;
15173
+ class TilesetStyler {
15174
+ constructor() {
15175
+ this.disposed = false;
15176
+ // Indicates if the styler has been loaded/initialized and is ready to style.
15177
+ this.loaded = false;
15178
+ // Indicates that all mappings have been loaded.
15179
+ this.styleMappingLoaded = false;
15180
+ // Map of Entity Type ID -> boolean to indicate if the style mapping has been loaded.
15181
+ this.styleMappingsLoaded = new Map();
15182
+ // Dictionary of Entity Type ID -> Set of Entity IDs that are pending styling.
15183
+ // Once the style is loaded, we can queue the set for styling.
15184
+ this.stylePendingEntityIds = new Map();
15185
+ this.fallbackStyle = null;
15186
+ this.loadingCounter = 0;
15187
+ // Entity IDs -> boolean to indicate if we should override the feature colour.
15188
+ this.overrideFeatureColor = new Map();
15189
+ this.scenario = 0;
15190
+ // Indicates that we should expand the sources of the Entity.
15191
+ this.expandSources = false;
15192
+ // Indicates that we are retrieving historic records.
15193
+ // This means that the current scene's time is included in the request.
15194
+ this.historic = false;
15195
+ // More expensive process.
15196
+ // When an Entity is styled, the rego is reviewed and updated if needed.
15197
+ // This is currently used for scenarios, and off by default to keep other processes faster.
15198
+ this.shouldUpdateRegoStates = false;
15199
+ // Entity ID -> styled status.
15200
+ // Helps us emit progress events.
15201
+ this.styledEntityIds = new Map();
15202
+ // % progress for how many Entities have been styled so far.
15203
+ // This can change as Tiles load in and more queue.
15204
+ this._styleProgress = 0;
15205
+ this._styleProgressQueue = new DelayQueue(() => {
15206
+ this.updateStyleProgress();
15207
+ }, 250);
15208
+ // Event for when the style progress changes.
15209
+ this.OnStyleProgress = new BruceEvent();
15210
+ }
15211
+ get Disposed() {
15212
+ return this.disposed;
15213
+ }
15214
+ get Loaded() {
15215
+ return this.loaded;
15216
+ }
15217
+ get StyleProgress() {
15218
+ return this._styleProgress;
15219
+ }
15220
+ Init(params) {
15221
+ var _a;
15222
+ let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic } = params;
15223
+ this.viewer = viewer;
15224
+ this.api = api;
15225
+ this.cTileset = cTileset;
15226
+ this.register = register;
15227
+ this.menuItemId = menuItemId;
15228
+ this.historic = Boolean(historic);
15229
+ if (expandSources != null) {
15230
+ this.expandSources = expandSources;
14912
15231
  }
14913
- get OnStateUpdate() {
14914
- if (!this.onStateUpdate) {
14915
- this.onStateUpdate = new BruceEvent();
14916
- }
14917
- return this.onStateUpdate;
15232
+ if (fallbackStyleId != null) {
15233
+ this.fallbackStyleId = fallbackStyleId;
14918
15234
  }
14919
- get OnScanUpdate() {
14920
- if (!this.onScanUpdate) {
14921
- this.onScanUpdate = new BruceEvent();
15235
+ if (styleMapping) {
15236
+ this.styleMapping = styleMapping;
15237
+ if (this.styleMapping) {
15238
+ // Dereference.
15239
+ this.styleMapping = JSON.parse(JSON.stringify(this.styleMapping));
15240
+ }
15241
+ // ND-1641. BOOKMARKS - (DEMO) View does not match bookmark.
15242
+ // We have some evil hard-coded style mappings that need to be fixed.
15243
+ // These exist within legacy project views.
15244
+ // Update: This now also lets people have a style mapping without a real style record.
15245
+ if ((_a = this.styleMapping) === null || _a === void 0 ? void 0 : _a.length) {
15246
+ for (let i = 0; i < this.styleMapping.length; i++) {
15247
+ const mapItem = this.styleMapping[i];
15248
+ const mapStyle = mapItem.style ? mapItem.style : mapItem.Style;
15249
+ this.styleMapping[i].style = correctStyle(mapStyle);
15250
+ }
14922
15251
  }
14923
- return this.onScanUpdate;
14924
15252
  }
14925
- get isLooping() {
14926
- return this.looping > 0;
15253
+ if (scenario != null) {
15254
+ this.scenario = scenario;
14927
15255
  }
14928
- constructor(params) {
14929
- this.onUpdate = null;
14930
- this.LastStateUpdates = {};
14931
- this.onStateUpdate = null;
14932
- this.onScanUpdate = null;
14933
- this.viewPortChangeRemoval = null;
14934
- this.viewPortDelayQueue = null;
14935
- this.viewerDateTimeChangeRemoval = null;
14936
- this.cells = null;
14937
- this.registeredItems = {};
14938
- this.getterLoopId = 0;
14939
- this.getterLoopAbortControllers = {};
14940
- this.looping = 0;
14941
- this.tagIds = null;
14942
- this.minHeight = 0;
14943
- this.maxHeight = 100000;
14944
- this.viewRect = null;
14945
- this.viewCenter = null;
14946
- this.scenario = 0;
14947
- this.historicRefreshAbortController = null;
14948
- // Entity IDs found for the latest integrity.
14949
- // We use this for refreshing historic data without having to repeat geographic queries.
14950
- this.gatheredIntegrity = null;
14951
- this.gatheredEntityIds = [];
14952
- const { api, viewer, viewPort, typeIds, schemaId, batchSize, attrFilter, historicAttrKey, historicInterpolation, historic, viaCdn, scenario } = params;
14953
- this.api = api;
14954
- this.typeIds = typeIds;
14955
- this.schemaId = schemaId;
14956
- this.historic = Boolean(historic);
14957
- this.historicAttrKey = historicAttrKey;
14958
- this.historicInterpolation = Boolean(historicInterpolation);
14959
- this.viaCdn = Boolean(viaCdn);
14960
- this.batchSize = isNaN(batchSize) ? 300 : batchSize;
14961
- this.viewPort = viewPort;
14962
- this.attrFilter = attrFilter;
14963
- this.viewer = viewer;
14964
- this.scenario = scenario ? scenario : 0;
14965
- this.updateBounds();
15256
+ if (!!scenario) {
15257
+ this.shouldUpdateRegoStates = true;
14966
15258
  }
14967
- /**
14968
- * Returns id that represents the combined menu item parameters.
14969
- * If integrity changes while a request is running, the request will not emit a response.
14970
- * @returns
14971
- */
14972
- getIntegrityId() {
14973
- let integrity = this.tagIds == null ? "" : this.tagIds.join();
14974
- if (this.historicAttrKey) {
14975
- integrity += "isHistoric";
14976
- integrity += this.historicAttrKey;
15259
+ else if (this.historic) {
15260
+ this.shouldUpdateRegoStates = true;
15261
+ }
15262
+ this.entityGatherer = new EntityGatherer(this.api, this.viewer, (entities, tags) => {
15263
+ if (!(entities === null || entities === void 0 ? void 0 : entities.length)) {
15264
+ return;
14977
15265
  }
14978
- else if (this.historic) {
14979
- integrity += "isHistoric";
15266
+ for (let i = 0; i < entities.length; i++) {
15267
+ const entity = entities[i];
15268
+ const feature = this.getEntityRego(entity.Bruce.ID);
15269
+ if (feature) {
15270
+ const eTags = tags.filter(t => { var _a, _b, _c, _d; return ((_b = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a["Layer.ID"]) === null || _b === void 0 ? void 0 : _b.length) && ((_d = (_c = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _c === void 0 ? void 0 : _c["Layer.ID"]) === null || _d === void 0 ? void 0 : _d.includes(t.ID)); });
15271
+ this.styleTilesetFeatureFullData(feature, entity, eTags);
15272
+ }
14980
15273
  }
14981
- if (this.scenario) {
14982
- integrity += this.scenario;
15274
+ });
15275
+ this.loaded = true;
15276
+ this.loadStyles();
15277
+ }
15278
+ /**
15279
+ * Updates style mapping and fallback style.
15280
+ * This will trigger a re-style of all entities, and will stop existing style loads.
15281
+ * @param params
15282
+ */
15283
+ UpdateStyleMapping(params) {
15284
+ var _a;
15285
+ if (params.styleMapping) {
15286
+ // Dereference.
15287
+ params.styleMapping = JSON.parse(JSON.stringify(params.styleMapping));
15288
+ this.styleMapping = params.styleMapping;
15289
+ // ND-1641. BOOKMARKS - (DEMO) View does not match bookmark.
15290
+ // We have some evil hard-coded style mappings that need to be fixed.
15291
+ // These exist within legacy project views.
15292
+ // Update: This now also lets people have a style mapping without a real style record.
15293
+ if ((_a = this.styleMapping) === null || _a === void 0 ? void 0 : _a.length) {
15294
+ for (let i = 0; i < this.styleMapping.length; i++) {
15295
+ const mapItem = this.styleMapping[i];
15296
+ const mapStyle = mapItem.style ? mapItem.style : mapItem.Style;
15297
+ this.styleMapping[i].style = correctStyle(mapStyle);
15298
+ }
14983
15299
  }
14984
- if (this.typeIds) {
14985
- integrity += this.typeIds.join();
15300
+ }
15301
+ if (!isNaN(params.fallbackStyleId) && params.fallbackStyleId != null) {
15302
+ this.fallbackStyleId = params.fallbackStyleId;
15303
+ }
15304
+ if (params.fallbackStyle) {
15305
+ this.fallbackStyle = params.fallbackStyle;
15306
+ }
15307
+ else {
15308
+ this.fallbackStyle = null;
15309
+ }
15310
+ if (params.scenario != null) {
15311
+ this.scenario = params.scenario;
15312
+ if (!this.shouldUpdateRegoStates && !!this.scenario) {
15313
+ this.shouldUpdateRegoStates = true;
14986
15314
  }
14987
- return integrity;
14988
15315
  }
14989
- viewAreaSub() {
14990
- this.viewAreaDispose();
14991
- // We'll avoid restarting the scanner too often.
14992
- this.viewPortDelayQueue = new DelayQueue(() => {
14993
- this.updateBounds();
14994
- this.startGetterLoop();
14995
- }, 2000);
14996
- this.viewPortChangeRemoval = this.viewPort.Updated().Subscribe(() => {
14997
- var _a;
14998
- (_a = this.viewPortDelayQueue) === null || _a === void 0 ? void 0 : _a.Call();
15316
+ if (params.historic != null) {
15317
+ this.historic = params.historic;
15318
+ if (params.historic || this.historic != params.historic) {
15319
+ this.shouldUpdateRegoStates = true;
15320
+ }
15321
+ }
15322
+ // Reload styles.
15323
+ if (this.loaded) {
15324
+ this.styledEntityIds.clear();
15325
+ this.styleMappingsLoaded.clear();
15326
+ this.loadStyles();
15327
+ this._styleProgressQueue.Call(true);
15328
+ // Putting all rendered things back into the queue.
15329
+ const regos = this.register.GetRegos({
15330
+ menuItemId: this.menuItemId
14999
15331
  });
15332
+ this.QueueEntities(regos);
15000
15333
  }
15001
- viewAreaDispose() {
15002
- var _a, _b;
15003
- (_a = this.viewPortChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
15004
- this.viewPortChangeRemoval = null;
15005
- (_b = this.viewPortDelayQueue) === null || _b === void 0 ? void 0 : _b.Dispose();
15006
- this.viewPortDelayQueue = null;
15334
+ }
15335
+ /**
15336
+ * Updates a cache of Entity objects to use when styling.
15337
+ * When a cache is available, a request won't be performed to get that Entity's data.
15338
+ * @param entityIds
15339
+ * @param entities If an object is not available for a supplied Entity ID, then the cache for it is removed.
15340
+ */
15341
+ SetEntityCache(entityIds, entities) {
15342
+ // Turn the Entities array into an accessible dictionary.
15343
+ const newDataMap = new Map();
15344
+ if (entities) {
15345
+ for (let i = 0; i < entities.length; i++) {
15346
+ const entity = entities[i];
15347
+ if (entity.Bruce) {
15348
+ newDataMap.set(entity.Bruce.ID, entity);
15349
+ }
15350
+ }
15007
15351
  }
15008
- /**
15009
- * Monitors the Cesium viewer and updates the historic data filter values.
15010
- * If there is no historic attr set, this will do nothing.
15011
- */
15012
- viewerDateTimeSub() {
15013
- if ((!this.historicAttrKey && !this.historic) || this.viewerDateTimeChangeRemoval) {
15014
- return;
15352
+ // Update the cache for each supplied Entity ID.
15353
+ for (let i = 0; i < entityIds.length; i++) {
15354
+ const entityId = entityIds[i];
15355
+ this.entityGatherer.SetEntityDataOverride(entityId, newDataMap.get(entityId));
15356
+ }
15357
+ }
15358
+ QueueEntities(regos, highPriority = false) {
15359
+ if (!this.loaded) {
15360
+ // Mark as pending?
15361
+ return;
15362
+ }
15363
+ for (let i = 0; i < regos.length; i++) {
15364
+ const rego = regos[i];
15365
+ this.overrideFeatureColor.set(rego.entityId, true);
15366
+ // We set a default colour right away to avoid race conditions at later times.
15367
+ CesiumEntityStyler.BakeDefaultColor({
15368
+ entity: rego.visual,
15369
+ viewer: this.viewer,
15370
+ override: false
15371
+ });
15372
+ }
15373
+ this.queueTilesetFeatureStyle(regos, highPriority);
15374
+ }
15375
+ /**
15376
+ * Calculates the current progress % and updates the progress if there is a change.
15377
+ */
15378
+ updateStyleProgress() {
15379
+ if (this.disposed) {
15380
+ return;
15381
+ }
15382
+ const total = this.overrideFeatureColor.size;
15383
+ const styled = this.styledEntityIds.size;
15384
+ let progress = 100;
15385
+ if (styled < total) {
15386
+ progress = Math.round((styled / total) * 100);
15387
+ if (progress < 0) {
15388
+ progress = 0;
15015
15389
  }
15016
- // This is multiplied by the speed of animation to figure
15017
- // out how many animation "ticks" before we allow an update.
15018
- let INTERVAL_WHILE_ANIMATING = 2.5 * 1000;
15019
- let INTERVAL_WHILE_NOT_ANIMATING = 1000;
15020
- if (this.historicInterpolation) {
15021
- INTERVAL_WHILE_ANIMATING = 6 * 1000;
15022
- INTERVAL_WHILE_NOT_ANIMATING = 3.5 * 1000;
15390
+ else if (progress > 100) {
15391
+ progress = 100;
15023
15392
  }
15024
- let lastUpdateTime = null;
15025
- let delayQueue = new DelayQueue(() => {
15026
- try {
15027
- // If the timeline is animating then we'll wait longer to update.
15028
- if (this.viewer.clock.shouldAnimate && lastUpdateTime) {
15029
- if (Math.abs(new Date().getTime() - lastUpdateTime) < INTERVAL_WHILE_ANIMATING) {
15030
- return;
15031
- }
15032
- }
15033
- const current = this.historicAttrDateTime;
15034
- this.updateHistoricDateTime();
15035
- if (current != this.historicAttrDateTime) {
15036
- this.emitHistoricData();
15037
- }
15038
- }
15039
- catch (e) {
15040
- console.error(e);
15041
- }
15042
- }, INTERVAL_WHILE_NOT_ANIMATING);
15043
- let postUpdateRemoval = this.viewer.scene.postUpdate.addEventListener(() => {
15044
- if (delayQueue) {
15045
- delayQueue.Call();
15046
- }
15047
- });
15048
- this.viewerDateTimeChangeRemoval = () => {
15049
- delayQueue === null || delayQueue === void 0 ? void 0 : delayQueue.Dispose();
15050
- postUpdateRemoval === null || postUpdateRemoval === void 0 ? void 0 : postUpdateRemoval();
15051
- delayQueue = null;
15052
- postUpdateRemoval = null;
15053
- };
15054
15393
  }
15055
- updateHistoricDateTime() {
15056
- if (!this.historicAttrKey && !this.historic) {
15057
- this.historicAttrDateTime = null;
15058
- return;
15394
+ if (this._styleProgress != progress) {
15395
+ this._styleProgress = progress;
15396
+ this.OnStyleProgress.Trigger(progress);
15397
+ }
15398
+ }
15399
+ getEntityRego(entityId) {
15400
+ return this.register.GetRego({
15401
+ entityId,
15402
+ menuItemId: this.menuItemId
15403
+ });
15404
+ }
15405
+ Dispose() {
15406
+ if (this.disposed) {
15407
+ return;
15408
+ }
15409
+ this.disposed = true;
15410
+ this._styleProgressQueue.Dispose();
15411
+ this.disposeGatherer();
15412
+ }
15413
+ async loadStyles() {
15414
+ var _a, _b;
15415
+ const counter = ++this.loadingCounter;
15416
+ this.styleMappingLoaded = false;
15417
+ this.styleMappingsLoaded.clear();
15418
+ // Apply state changes.
15419
+ this.entityGatherer.Empty();
15420
+ this.entityGatherer.Init(this.historic, this.expandSources);
15421
+ let fallbackStyleId = this.fallbackStyleId;
15422
+ if (fallbackStyleId && fallbackStyleId > 0) {
15423
+ try {
15424
+ let { style: data } = await Style.Get({
15425
+ api: this.api,
15426
+ styleId: fallbackStyleId
15427
+ });
15428
+ this.fallbackStyle = data;
15059
15429
  }
15060
- const isChanged = (before, after) => {
15061
- if (before && !after) {
15062
- return true;
15063
- }
15064
- if (!before && after) {
15065
- return true;
15066
- }
15067
- // Change must be at least 0.1 seconds.
15068
- return Math.abs(before.getTime() - after.getTime()) > 100;
15069
- };
15070
- const oldDateTime = this.historicAttrDateTime ? new Date(this.historicAttrDateTime) : null;
15071
- const newDateTime = JulianDate.toDate(this.viewer.clock.currentTime);
15072
- if (isChanged(oldDateTime, newDateTime)) {
15073
- this.historicAttrDateTime = newDateTime.toISOString();
15430
+ catch (e) {
15431
+ console.error(e);
15074
15432
  }
15075
15433
  }
15076
- viewerDateTimeDispose() {
15077
- var _a;
15078
- (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
15079
- this.viewerDateTimeChangeRemoval = null;
15434
+ if (this.loadingCounter != counter) {
15435
+ return;
15080
15436
  }
15081
- GetMenuItems() {
15082
- return Object.keys(this.registeredItems);
15437
+ // Load styles in the style mapping
15438
+ let styleMapping = this.styleMapping;
15439
+ if (!styleMapping) {
15440
+ styleMapping = [];
15083
15441
  }
15084
- IncludeMenuItem(menuItemId, typeId, layerIds, minHeight, maxHeight) {
15085
- this.registeredItems[menuItemId] = new regMenuItemGetter(typeId, layerIds, minHeight, maxHeight);
15086
- this.updateState();
15442
+ this.styleMapping = styleMapping;
15443
+ // Append all found entity-types.
15444
+ // That way we can mark them as loaded instead of just the ones in the style mapping.
15445
+ try {
15446
+ const modelTree = (_b = (_a = this.cTileset) === null || _a === void 0 ? void 0 : _a.extensions) === null || _b === void 0 ? void 0 : _b.modelTree;
15447
+ if (modelTree) {
15448
+ const entityTypeIds = this.getEntityTypeIdsFromModelTree(modelTree);
15449
+ for (let i = 0; i < entityTypeIds.length; i++) {
15450
+ const entityTypeId = entityTypeIds[i];
15451
+ if (styleMapping.findIndex(x => x.EntityTypeID == entityTypeId) <= -1) {
15452
+ styleMapping.push({
15453
+ EntityTypeID: entityTypeId,
15454
+ StyleID: this.fallbackStyle ? fallbackStyleId : 0,
15455
+ style: this.fallbackStyle
15456
+ });
15457
+ }
15458
+ }
15459
+ }
15087
15460
  }
15088
- ExcludeMenuItem(menuItemId) {
15089
- this.registeredItems[menuItemId] = null;
15090
- delete this.registeredItems[menuItemId];
15091
- this.updateState(true);
15461
+ catch (e) {
15462
+ console.error(e);
15092
15463
  }
15093
- updateBounds() {
15094
- const viewRect = this.viewPort.GetBounds();
15095
- const poi = this.viewPort.GetTarget();
15096
- if (viewRect && poi) {
15097
- if (Math.abs(viewRect.west - viewRect.east) > MAX_AREA_IN_DEGREES$1) {
15098
- return;
15099
- }
15100
- if (Math.abs(viewRect.south - viewRect.north) > MAX_AREA_IN_DEGREES$1) {
15101
- return;
15464
+ // Before we start the loop we'll do a single request for the needed Entity Types.
15465
+ // This will be aimed at rows that don't specify a Style and if the default fallback-
15466
+ //is set to 0 (using the default of the Entity Type).
15467
+ const typeMap = new Map();
15468
+ const typeIds = styleMapping.map(x => x.StyleID == -1 || Boolean(x.style) ? null : x.EntityTypeID).filter(x => !!x);
15469
+ if (typeIds.length) {
15470
+ // We'll split up into batches of 50.
15471
+ // Since we add the type IDs as query params I am paranoid of hitting the limit.
15472
+ const splits = Math.ceil(typeIds.length / 50);
15473
+ const batchSize = 50;
15474
+ for (let i = 0; i < splits; i++) {
15475
+ const batch = typeIds.slice(i * batchSize, (i + 1) * batchSize);
15476
+ const { entityTypes } = await EntityType.GetList({
15477
+ api: this.api,
15478
+ entityTypeIds: batch
15479
+ });
15480
+ for (let i = 0; i < entityTypes.length; i++) {
15481
+ const entityType = entityTypes[i];
15482
+ typeMap.set(entityType.ID, entityType);
15102
15483
  }
15103
- this.viewRect = viewRect;
15104
- this.viewCenter = poi;
15105
15484
  }
15106
15485
  }
15107
- updateState(onlyIfLooping = false) {
15108
- const tagIds = [];
15109
- const typeIds = [];
15110
- const menuItemIds = this.GetMenuItems();
15111
- let minHeight = null;
15112
- let maxHeight = null;
15113
- for (let i = 0; i < menuItemIds.length; i++) {
15114
- const menuItem = this.registeredItems[menuItemIds[i]];
15115
- if (menuItem) {
15116
- if (maxHeight == null || maxHeight < menuItem.MaxHeight) {
15117
- maxHeight = menuItem.MaxHeight;
15486
+ for (let i = 0; i < styleMapping.length; i++) {
15487
+ if (this.disposed) {
15488
+ break;
15489
+ }
15490
+ let styleMap = styleMapping[i];
15491
+ if (!styleMap.style && styleMap.StyleID != -1) {
15492
+ let styleId = styleMap.StyleID;
15493
+ // Get default style of the entity type, if
15494
+ //no default is already specified.
15495
+ if (!styleId && !this.fallbackStyle) {
15496
+ try {
15497
+ let entityType = typeMap.get(styleMap.EntityTypeID);
15498
+ // If the map excluded the type for whatever reason we'll load it now.
15499
+ if (!entityType) {
15500
+ entityType = (await EntityType.Get({
15501
+ api: this.api,
15502
+ entityTypeId: styleMap.EntityTypeID
15503
+ })).entityType;
15504
+ }
15505
+ styleId = entityType === null || entityType === void 0 ? void 0 : entityType["DisplaySetting.ID"];
15118
15506
  }
15119
- if (minHeight == null || minHeight > menuItem.MinHeight) {
15120
- minHeight = menuItem.MinHeight;
15507
+ catch (e) {
15508
+ console.error(e);
15121
15509
  }
15122
- const itemLayerIds = menuItem.TagIds;
15123
- if (itemLayerIds) {
15124
- for (let j = 0; j < itemLayerIds.length; j++) {
15125
- const itemLayerId = itemLayerIds[j];
15126
- if (!tagIds.includes(itemLayerId)) {
15127
- tagIds.push(itemLayerId);
15128
- }
15510
+ }
15511
+ if (styleId) {
15512
+ try {
15513
+ let { style: data } = await Style.Get({
15514
+ api: this.api,
15515
+ styleId
15516
+ });
15517
+ if (data) {
15518
+ styleMap.style = data;
15519
+ styleMap.StyleID = styleId;
15129
15520
  }
15130
15521
  }
15131
- const itemTypeId = menuItem.TypeId;
15132
- if (itemTypeId) {
15133
- if (!typeIds.includes(itemTypeId)) {
15134
- typeIds.push(itemTypeId);
15135
- }
15522
+ catch (e) {
15523
+ console.error(e);
15136
15524
  }
15137
15525
  }
15526
+ if (this.loadingCounter != counter) {
15527
+ return;
15528
+ }
15138
15529
  }
15139
- if (menuItemIds.length > 0 && (!onlyIfLooping || this.isLooping)) {
15140
- // Reset cells so none are marked as fetched.
15141
- this.cells = new EntityGlobe.Grid();
15142
- this.tagIds = tagIds;
15143
- this.typeIds = typeIds;
15144
- this.minHeight = minHeight;
15145
- this.maxHeight = maxHeight;
15146
- this.updateBounds();
15147
- this.updateHistoricDateTime();
15148
- this.startGetterLoop();
15149
- this.viewAreaSub();
15150
- this.viewerDateTimeSub();
15530
+ this.styleMappingsLoaded.set(styleMap.EntityTypeID, true);
15531
+ if (this.loaded) {
15532
+ // If we have a bunch of Entities that were waiting for the style to load,
15533
+ // then we can pass them to the gatherer now.
15534
+ const relatedIds = this.stylePendingEntityIds.get(styleMap.EntityTypeID);
15535
+ if (relatedIds) {
15536
+ this.entityGatherer.Queue(Array.from(relatedIds));
15537
+ this.stylePendingEntityIds.delete(styleMap.EntityTypeID);
15538
+ }
15151
15539
  }
15152
- else {
15153
- this.getterLoopId += 1;
15154
- this.viewAreaDispose();
15155
- this.viewerDateTimeDispose();
15540
+ }
15541
+ if (this.loadingCounter != counter) {
15542
+ return;
15543
+ }
15544
+ this.styleMappingLoaded = true;
15545
+ if (!this.disposed && this.loaded) {
15546
+ if (this.loadingCounter != counter) {
15547
+ return;
15548
+ }
15549
+ // Queue all Entities that are pending styling.
15550
+ const pendingKeys = this.stylePendingEntityIds.keys();
15551
+ for (const key of pendingKeys) {
15552
+ const relatedIds = this.stylePendingEntityIds[key];
15553
+ if (relatedIds) {
15554
+ this.entityGatherer.Queue(Array.from(relatedIds));
15555
+ }
15156
15556
  }
15557
+ this.stylePendingEntityIds.clear();
15157
15558
  }
15158
- postStatus(status) {
15159
- var _a;
15160
- this.LastStateUpdates[status.msg] = status;
15161
- (_a = this.onStateUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(status);
15559
+ }
15560
+ disposeGatherer() {
15561
+ if (this.entityGatherer) {
15562
+ this.entityGatherer.Dispose();
15563
+ this.entityGatherer = null;
15162
15564
  }
15163
- startGetterLoop() {
15164
- // Increase id so that existing loops stop.
15165
- this.getterLoopId += 1;
15166
- const loopId = this.getterLoopId;
15167
- const loopIntegrity = this.getIntegrityId();
15168
- // Abort any existing loops that don't match the current loop.
15169
- // We tried using integrity, however we want to interrupt when user moves camera quickly.
15170
- // So it's better to use the loop ID.
15171
- const abortId = String(loopId);
15172
- {
15173
- const newAbortControllers = {};
15174
- for (const key in this.getterLoopAbortControllers) {
15175
- this.getterLoopAbortControllers[key].abort();
15565
+ }
15566
+ getEntityTypeIdsFromModelTree(modelTree) {
15567
+ const entityTypeIds = [];
15568
+ this.digEntityTypeIdsFromModelTreeBranch(modelTree, entityTypeIds);
15569
+ return entityTypeIds;
15570
+ }
15571
+ digEntityTypeIdsFromModelTreeBranch(branch, arr) {
15572
+ if (branch) {
15573
+ // Does not yet include this entity type id.
15574
+ if (branch.typeId && !arr.includes(branch.typeId)) {
15575
+ arr.push(branch.typeId);
15576
+ }
15577
+ if (branch.children) {
15578
+ for (let i = 0; i < branch.children.length; i++) {
15579
+ let child = branch.children[i];
15580
+ this.digEntityTypeIdsFromModelTreeBranch(child, arr);
15176
15581
  }
15177
- this.getterLoopAbortControllers = newAbortControllers;
15178
15582
  }
15179
- const abortController = this.getterLoopAbortControllers[abortId] = new AbortController();
15180
- this.looping += 1;
15181
- (async () => {
15182
- var _a, _b, _c, _d, _e, _f, _g, _h;
15183
- // Larger initial delay for the first loops because terrain is likely loading in.
15184
- // We also delay because if we enable 50 Menu Items at the same time, common requests we be made if we wait a bit.
15185
- // Eg: same entity type will be grouped into the same filter getter instance.
15186
- await delay(loopId <= 3 ? 800 : 300);
15187
- const MIN_HEIGHT = this.minHeight;
15188
- const MAX_HEIGHT = this.maxHeight;
15189
- const PAGE_SIZE = this.batchSize;
15190
- let retryAttempts = MAX_RETRY_ATTEMPTS;
15191
- let retryDelay = 0;
15192
- let prevFirstId = "";
15193
- let prevLastId = "";
15194
- let prevTicks = 0;
15195
- while ((!this.viewCenter || !this.viewRect) && this.getterLoopId == loopId) {
15196
- await delay(RETRY_DELAY_INCREMENT);
15583
+ }
15584
+ }
15585
+ queueTilesetFeatureStyle(entities, highPriority) {
15586
+ const needsDataIds = [];
15587
+ for (const rego of entities) {
15588
+ if (!this.overrideFeatureColor.has(rego.entityId)) {
15589
+ this.overrideFeatureColor.set(rego.entityId, false);
15590
+ }
15591
+ const typeId = rego.entityTypeId || "";
15592
+ if (this.styleMappingLoaded || this.styleMappingsLoaded.get(typeId)) {
15593
+ if (this.getTilesetFeatureNeedsFullData(typeId)) {
15594
+ needsDataIds.push(rego.entityId);
15197
15595
  }
15198
- if (this.getterLoopId != loopId) {
15199
- return;
15596
+ else {
15597
+ this.styleTilesetFeature(rego);
15200
15598
  }
15201
- const alt = this.viewRect.alt;
15202
- if (alt > MAX_HEIGHT || (alt < MIN_HEIGHT && MIN_HEIGHT > 0)) {
15203
- return;
15599
+ }
15600
+ else {
15601
+ if (!this.stylePendingEntityIds.has(typeId)) {
15602
+ this.stylePendingEntityIds.set(typeId, new Set());
15204
15603
  }
15205
- const cells = this.cells.GetCellsForView(this.viewCenter, this.viewRect);
15206
- (_a = this.onScanUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(cells);
15207
- let curCellIndex = cells.length > 0 ? 0 : null;
15208
- let postedScanning = false;
15209
- let postedLoading = false;
15210
- let total = 0;
15211
- while (retryAttempts > 0 && curCellIndex != null) {
15212
- if (retryDelay > 0) {
15213
- await delay(retryDelay);
15214
- }
15215
- if (this.getterLoopId != loopId) {
15216
- break;
15217
- }
15218
- if (!postedScanning) {
15219
- this.postStatus({ msg: EStatus.Scanning, revoking: false });
15220
- postedScanning = true;
15221
- }
15222
- const curCell = cells[curCellIndex];
15223
- if (curCell.IsFetched()) {
15224
- curCell.Fetching = false;
15225
- curCellIndex += 1;
15226
- if (cells[curCellIndex]) {
15227
- cells[curCellIndex].Fetching = true;
15228
- }
15229
- else {
15230
- curCellIndex = null;
15231
- }
15232
- (_b = this.onScanUpdate) === null || _b === void 0 ? void 0 : _b.Trigger(cells);
15233
- continue;
15234
- }
15235
- try {
15236
- let response = {
15237
- entities: [],
15238
- nextPage: false,
15239
- nextPageUrl: null
15240
- };
15241
- await SharedGetters.Queue.Run("Loading Entities from Menu Item that loads Entity Type: " + this.typeIds, async () => {
15242
- var _a;
15243
- if (abortController.signal.aborted || !((_a = this.GetMenuItems()) === null || _a === void 0 ? void 0 : _a.length)) {
15244
- return;
15245
- }
15246
- // API gave us a URL to use.
15247
- if (curCell.FetchURL) {
15248
- const tmpResponse = await this.api.get(curCell.FetchURL, {
15249
- abortSignal: abortController.signal,
15250
- noCache: true
15251
- });
15252
- // Same mapping as bruce-models doing Entity.GetList.
15253
- response = {
15254
- entities: tmpResponse.Items ? tmpResponse.Items : [],
15255
- nextPage: tmpResponse.NextPage,
15256
- nextPageUrl: tmpResponse.NextPageURL
15257
- };
15258
- }
15259
- else {
15260
- response = await Entity$1.GetList({
15261
- api: this.api,
15262
- scenario: this.scenario,
15263
- historicKey: this.historicAttrKey,
15264
- historicPoint: (this.historicAttrKey || this.historic) ? this.historicAttrDateTime : null,
15265
- schemaId: this.schemaId,
15266
- filter: {
15267
- pageSize: PAGE_SIZE,
15268
- pageIndex: curCell.FetchPageIndex,
15269
- entityTypeId: this.typeIds,
15270
- layerIds: this.tagIds,
15271
- // Any tag specified will be allowed.
15272
- layerIdsOperator: "in",
15273
- bounds: curCell.GetBounds(),
15274
- sortOrder: Api.ESortOrder.Asc,
15275
- entityTypeConditions: this.attrFilter
15276
- },
15277
- viaCdn: this.viaCdn,
15278
- migrated: true,
15279
- // If we're taking 2+ minutes to make a query, it's a dud.
15280
- // This is a timeout imposed on our DB and not external sources.
15281
- // Honestly could lower down to 30 seconds, but we'll keep it high for now.
15282
- maxSearchTimeSec: 60 * 2,
15283
- req: {
15284
- // If we are passing in an abort, we MUST pass in noCache.
15285
- // Otherwise we will cache an aborted request.
15286
- noCache: true,
15287
- abortSignal: abortController.signal
15288
- }
15289
- });
15290
- }
15291
- });
15292
- const entities = response.entities;
15293
- const integrity = this.getIntegrityId();
15294
- if (loopIntegrity == integrity && entities) {
15295
- (_c = this.onUpdate) === null || _c === void 0 ? void 0 : _c.Trigger(entities);
15296
- }
15297
- if (this.gatheredIntegrity != integrity) {
15298
- this.gatheredIntegrity = integrity;
15299
- this.gatheredEntityIds = [];
15300
- }
15301
- // Add to the integrity list for any new IDs found.
15302
- // This lets us keep track of all IDs we've found within the same integrity for historic data.
15303
- if (this.historicAttrKey || this.historic) {
15304
- for (let i = 0; i < entities.length; i++) {
15305
- const entity = entities[i];
15306
- if (!this.gatheredEntityIds.includes(entity.Bruce.ID)) {
15307
- this.gatheredEntityIds.push(entity.Bruce.ID);
15308
- }
15309
- }
15310
- }
15311
- if (this.getterLoopId != loopId) {
15312
- break;
15313
- }
15314
- if (entities.length) {
15315
- total += entities.length;
15316
- }
15317
- if (!postedLoading) {
15318
- this.postStatus({ msg: EStatus.Loading, revoking: false });
15319
- postedLoading = true;
15320
- }
15321
- // Only mark as fetched when ALL pages are done.
15322
- // Known issue where external sources may return less than page size.
15323
- // Right now we're making it as fetched as we're siding with the majority use-case.
15324
- if (
15325
- // API explicity says no more pages.
15326
- response.nextPage === false ||
15327
- // API didn't explicity say anything so we guess based on size of response.
15328
- (response.nextPage == null &&
15329
- (entities.length <= 0 || entities.length < PAGE_SIZE))) {
15330
- curCell.Fetched = true;
15331
- curCell.Fetching = false;
15332
- (_d = this.onScanUpdate) === null || _d === void 0 ? void 0 : _d.Trigger(cells);
15333
- continue;
15334
- }
15335
- // Checking to make sure it's not just the same batch over and over again.
15336
- if (entities.length > 0) {
15337
- const first = (_f = (_e = entities[0]) === null || _e === void 0 ? void 0 : _e.Bruce) === null || _f === void 0 ? void 0 : _f.ID;
15338
- const last = (_h = (_g = entities[entities.length - 1]) === null || _g === void 0 ? void 0 : _g.Bruce) === null || _h === void 0 ? void 0 : _h.ID;
15339
- if (prevFirstId == first && prevLastId == last) {
15340
- prevTicks += 1;
15341
- if (prevTicks > 3) {
15342
- break;
15343
- }
15344
- }
15345
- else {
15346
- prevFirstId = first;
15347
- prevLastId = last;
15348
- prevTicks = 0;
15349
- }
15350
- }
15351
- // Using URL if specified.
15352
- // If not then we'll just manually paginate.
15353
- curCell.FetchURL = response.nextPageUrl;
15354
- curCell.FetchPageIndex++;
15355
- // Request passed so let's assume it was server hiccup and refresh counts.
15356
- retryAttempts = MAX_RETRY_ATTEMPTS;
15357
- retryDelay = 0;
15358
- }
15359
- catch (e) {
15360
- // Ignore abort errors.
15361
- if (e && typeof e === "object" && e.name == "AbortError") {
15362
- // console.debug("Aborted entity-filter-getter request.");
15363
- break;
15364
- }
15365
- console.error(e);
15366
- if (this.getterLoopId != loopId) {
15367
- break;
15368
- }
15369
- // Request failed so let's add a delay and try again soon.
15370
- retryDelay += RETRY_DELAY_INCREMENT;
15371
- retryAttempts -= 1;
15372
- }
15373
- await delay(REQUEST_PAGE_DELAY);
15374
- }
15375
- if (postedLoading) {
15376
- this.postStatus({ msg: EStatus.Loading, revoking: true });
15377
- }
15378
- if (postedScanning) {
15379
- this.postStatus({ msg: EStatus.Scanning, revoking: true });
15380
- }
15381
- })().then(() => {
15382
- this.looping -= 1;
15383
- }).catch(() => {
15384
- this.looping -= 1;
15385
- });
15386
- }
15387
- /**
15388
- * Gets the historic state of found Entities for the current date times and emits them.
15389
- * Since geometry searches are tied to the base Entity, we don't have to re-scan the viewport.
15390
- */
15391
- emitHistoricData() {
15392
- if (!this.GetMenuItems().length) {
15393
- return;
15394
- }
15395
- let integrity = this.getIntegrityId();
15396
- // Gathered ID does't match current one.
15397
- if (this.gatheredIntegrity != integrity) {
15398
- return;
15604
+ this.stylePendingEntityIds.get(typeId).add(rego.entityId);
15399
15605
  }
15400
- const historicAttrDateTime = this.historicAttrDateTime;
15401
- const SCAN_BATCH_SIZE = 1000;
15402
- if (this.historicRefreshAbortController) {
15403
- this.historicRefreshAbortController.abort();
15404
- this.historicRefreshAbortController = null;
15405
- }
15406
- (async () => {
15407
- var _a;
15408
- try {
15409
- // Loop through all IDs we've found and get their historic records.
15410
- for (let i = 0; i < this.gatheredEntityIds.length; i += SCAN_BATCH_SIZE) {
15411
- let batch = this.gatheredEntityIds.slice(i, i + SCAN_BATCH_SIZE);
15412
- if (!batch.length) {
15413
- break;
15414
- }
15415
- // Controller we can use to abort the request when a new loop starts.
15416
- const controller = this.historicRefreshAbortController = new AbortController();
15417
- let entities = [];
15418
- await SharedGetters.Queue.Run("Refreshing historic data in Menu Item that loads Entity Type: " + this.typeIds, async () => {
15419
- var _a;
15420
- if (controller.signal.aborted || !((_a = this.GetMenuItems()) === null || _a === void 0 ? void 0 : _a.length)) {
15421
- return;
15422
- }
15423
- entities = (await Entity$1.GetList({
15424
- api: this.api,
15425
- scenario: this.scenario,
15426
- historicKey: this.historicAttrKey,
15427
- historicPoint: historicAttrDateTime,
15428
- schemaId: this.schemaId,
15429
- filter: {
15430
- pageSize: batch.length,
15431
- pageIndex: 0,
15432
- entityTypeId: this.typeIds,
15433
- layerIds: this.tagIds,
15434
- layerIdsOperator: "in",
15435
- sortOrder: Api.ESortOrder.Asc,
15436
- entityTypeConditions: {
15437
- "ID": {
15438
- "in": batch
15439
- }
15440
- },
15441
- },
15442
- viaCdn: this.viaCdn,
15443
- migrated: true,
15444
- // If we're taking 5+ minutes to make a query, it's a dud.
15445
- // This is a timeout imposed on our DB and not external sources.
15446
- // Honestly could lower down to 30 seconds, but we'll keep it high for now.
15447
- maxSearchTimeSec: 60 * 5,
15448
- req: {
15449
- // If we are passing in an abort, we MUST pass in noCache.
15450
- // Otherwise we will cache an aborted request.
15451
- noCache: true,
15452
- abortSignal: controller.signal
15453
- }
15454
- })).entities;
15455
- });
15456
- // Date changed.
15457
- if (this.historicAttrDateTime != historicAttrDateTime) {
15458
- break;
15459
- }
15460
- // Integrity changed.
15461
- if (this.gatheredIntegrity != integrity) {
15462
- break;
15463
- }
15464
- // No Menu Items.
15465
- if (!this.GetMenuItems().length) {
15466
- break;
15467
- }
15468
- (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(entities);
15469
- }
15470
- }
15471
- catch (e) {
15472
- // Ignore abort errors.
15473
- if (e && typeof e === "object" && e.name == "AbortError") {
15474
- // console.debug("Aborted entity-filter-getter historic refresh request.");
15475
- return;
15476
- }
15477
- console.error(e);
15478
- }
15479
- })();
15606
+ }
15607
+ if (needsDataIds.length) {
15608
+ this.entityGatherer.Queue(needsDataIds, highPriority);
15480
15609
  }
15481
15610
  }
15482
- EntityFilterGetter.Getter = Getter;
15483
- })(EntityFilterGetter || (EntityFilterGetter = {}));
15484
-
15485
- function createFilterGetterCacheKey(params) {
15486
- let cacheKey = "";
15487
- cacheKey += params.api.GetBaseUrl();
15488
- // Not including Type ID in the cache key now.
15489
- // This allows us to re-use the same getter between Entity Types.
15490
- // cacheKey += params.typeId;
15491
- cacheKey += params.batchSize;
15492
- cacheKey += String(params.cdn);
15493
- cacheKey += params.schemaId ? params.schemaId : "";
15494
- cacheKey += JSON.stringify(params.tagIds ? params.tagIds : []);
15495
- cacheKey += params.historicAttrKey ? params.historicAttrKey : "";
15496
- cacheKey += params.historic ? "true" : "false";
15497
- cacheKey += params.scenario ? params.scenario : 0;
15498
- if (params.historicAttrKey) {
15499
- cacheKey += params.historicInterpolation ? "true" : "false";
15611
+ styleTilesetFeature(entity) {
15612
+ this.styleTilesetFeatureFullData(entity, null, []);
15500
15613
  }
15501
- // This could potentially crash, but if it crashes here then it would crash during API request anyways.
15502
- cacheKey += JSON.stringify(params.attrFilter ? params.attrFilter : {});
15503
- return cacheKey;
15504
- }
15505
- var SharedGetters;
15506
- (function (SharedGetters) {
15507
- class Cache {
15508
- constructor() {
15509
- this.data = {};
15614
+ styleTilesetFeatureFullData(entity, data, tags) {
15615
+ var _a, _b, _c, _d, _e, _f, _g, _h;
15616
+ const visual = entity.visual;
15617
+ if (!visual || !(visual instanceof Cesium3DTileFeature)) {
15618
+ return;
15510
15619
  }
15511
- GetOrCreateFilterGetter(params) {
15512
- params.cdn = Boolean(params.cdn);
15513
- const cacheKey = createFilterGetterCacheKey(params);
15514
- let getter = this.data[cacheKey];
15515
- if (!getter) {
15516
- getter = new EntityFilterGetter.Getter({
15517
- api: params.api,
15518
- viewer: params.viewer,
15519
- viewPort: params.monitor,
15520
- typeIds: !params.typeId ? null : typeof params.typeId == "string" ? [params.typeId] : params.typeId,
15521
- schemaId: params.schemaId,
15522
- batchSize: params.batchSize,
15523
- attrFilter: params.attrFilter,
15524
- historic: params.historic,
15525
- historicAttrKey: params.historicAttrKey,
15526
- historicInterpolation: params.historicInterpolation,
15527
- viaCdn: params.cdn,
15528
- scenario: params.scenario,
15620
+ const style = this.getTilesetFeatureStyle(entity.entityTypeId);
15621
+ const bColor = style && ((_a = style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor) ? Calculator.GetColor(style.modelStyle.fillColor, data, tags) : null;
15622
+ let cColor = null;
15623
+ if (bColor == null) {
15624
+ cColor = Color.WHITE;
15625
+ }
15626
+ else {
15627
+ cColor = colorToCColor$3(bColor);
15628
+ }
15629
+ const override = this.overrideFeatureColor.get(entity.entityId) == true;
15630
+ CesiumEntityStyler.SetDefaultColor({
15631
+ color: cColor,
15632
+ entity: visual,
15633
+ viewer: this.viewer,
15634
+ override: override
15635
+ });
15636
+ this.overrideFeatureColor.set(entity.entityId, true);
15637
+ this.styledEntityIds.set(entity.entityId, true);
15638
+ this._styleProgressQueue.Call();
15639
+ // Since we only need to update it for scenarios right now.
15640
+ // We'll avoid doing it if not needed, eg: first render and no scenario (same state as default).
15641
+ if (this.shouldUpdateRegoStates && (override || ((_b = data === null || data === void 0 ? void 0 : data.Bruce) === null || _b === void 0 ? void 0 : _b.Scenario) || this.historic)) {
15642
+ // Update the Entity's rego state.
15643
+ let changed = false;
15644
+ // Changed scenario.
15645
+ if (entity.scenario != ((_c = data === null || data === void 0 ? void 0 : data.Bruce) === null || _c === void 0 ? void 0 : _c.Scenario)) {
15646
+ entity.scenario = (_d = data === null || data === void 0 ? void 0 : data.Bruce) === null || _d === void 0 ? void 0 : _d.Scenario;
15647
+ changed = true;
15648
+ }
15649
+ // Changed historic.
15650
+ if ((data && isHistoricMetadataChanged(entity, data)) || (!data && ((_e = entity.historicLayers) === null || _e === void 0 ? void 0 : _e.length) && !((_g = (_f = data.Bruce) === null || _f === void 0 ? void 0 : _f.HistoricLayers) === null || _g === void 0 ? void 0 : _g.length))) {
15651
+ entity.historicLayers = (_h = data === null || data === void 0 ? void 0 : data.Bruce) === null || _h === void 0 ? void 0 : _h.HistoricLayers;
15652
+ changed = true;
15653
+ }
15654
+ // Something changed, trigger a rego update.
15655
+ // This lets UI know when the rego has changed.
15656
+ if (changed) {
15657
+ this.register.OnUpdate.Trigger({
15658
+ type: VisualsRegister.EVisualUpdateType.Update,
15659
+ entityId: entity.entityId,
15660
+ rego: entity
15529
15661
  });
15530
- this.data[cacheKey] = getter;
15531
- /**
15532
- * Debug option.
15533
- * This will display the bounds of the cells that are being fetched.
15534
- */
15535
- if (params.viewer && params.debugShowBounds) {
15536
- // Cell id -> entity.
15537
- const cellCache = {};
15538
- const cellPrefix = ObjectUtils.UId(10) + "_";
15539
- getter.OnScanUpdate.Subscribe((cells) => {
15540
- if (window.ON_SCAN_UPDATE_PAUSED == true) {
15541
- return;
15542
- }
15543
- const curCellIds = [];
15544
- let fetchingCellId = null;
15545
- const fetchedCells = {};
15546
- cells.forEach((cell) => {
15547
- var _a;
15548
- const bounds = cell.GetBounds();
15549
- const id = cellPrefix + bounds.east + "_" + bounds.north + "_" + bounds.south + "_" + bounds.west;
15550
- curCellIds.push(id);
15551
- fetchedCells[id] = cell.IsFetched();
15552
- fetchingCellId = cell.Fetching ? id : fetchingCellId;
15553
- let material = null;
15554
- if (fetchedCells[id]) {
15555
- material = Color.LIGHTGREEN.clone().withAlpha(0.3);
15556
- }
15557
- else if (fetchingCellId == id) {
15558
- material = Color.LIGHTBLUE.clone().withAlpha(0.3);
15559
- }
15560
- else {
15561
- material = Color.GOLD.clone().withAlpha(0.3);
15562
- }
15563
- if (cellCache[id]) {
15564
- const rect = (_a = cellCache[id]) === null || _a === void 0 ? void 0 : _a.rectangle;
15565
- if (rect) {
15566
- rect.material = material;
15567
- }
15568
- return;
15569
- }
15570
- const rect = new Rectangle(Math$1.toRadians(bounds.west), Math$1.toRadians(bounds.south), Math$1.toRadians(bounds.east), Math$1.toRadians(bounds.north));
15571
- const entity = params.viewer.entities.add(new Entity({
15572
- id: id,
15573
- rectangle: {
15574
- coordinates: rect,
15575
- fill: true,
15576
- //material: Cesium.Color.fromRandom().withAlpha(0.3),
15577
- material: material,
15578
- zIndex: 0
15579
- },
15580
- // point: {
15581
- // pixelSize: 15,
15582
- // outlineColor: Cesium.Color.WHITE,
15583
- // outlineWidth: 2,
15584
- // color: Cesium.Color.fromRandom().withAlpha(0.5),
15585
- // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
15586
- // },
15587
- position: Cartesian3.fromRadians((rect.east + rect.west) / 2, (rect.north + rect.south) / 2)
15588
- }));
15589
- cellCache[id] = entity;
15590
- });
15591
- Object.keys(cellCache).forEach((id) => {
15592
- if (curCellIds.indexOf(id) == -1) {
15593
- const entity = cellCache[id];
15594
- if (entity && params.viewer.entities.contains(entity)) {
15595
- params.viewer.entities.remove(entity);
15596
- }
15597
- delete cellCache[id];
15598
- }
15599
- });
15600
- });
15601
- }
15602
15662
  }
15603
- return getter;
15604
15663
  }
15605
15664
  }
15606
- SharedGetters.Cache = Cache;
15607
- /**
15608
- * To avoid performing multiple expensive queries at the same time,
15609
- * we'll have the getters work through a queue of requests.
15610
- */
15611
- let Queue;
15612
- (function (Queue) {
15613
- const queue = [];
15614
- let isProcessing = false;
15615
- /**
15616
- * Called to run a provided function.
15617
- * When the function is complete, the next function in the queue will be called.
15618
- * This function will resolve when the provided function is complete.
15619
- * If the provided function crashes, the error bubbles up to the caller.
15620
- * @param name
15621
- * @param call
15622
- */
15623
- function Run(name, call) {
15624
- // console.debug(`Queueing: ${name}`);
15625
- return new Promise((resolve, reject) => {
15626
- queue.push(async () => {
15627
- try {
15628
- // console.debug(`Running: ${name}`);
15629
- const result = await call();
15630
- resolve(result);
15631
- }
15632
- catch (error) {
15633
- reject(error);
15634
- }
15635
- finally {
15636
- // Process the next function in the queue.
15637
- queue.shift();
15638
- // If there are more functions in the queue, process the next one.
15639
- if (queue.length > 0) {
15640
- processNext();
15641
- }
15642
- // No more items to process.
15643
- else {
15644
- isProcessing = false;
15645
- }
15646
- }
15647
- });
15648
- if (!isProcessing) {
15649
- // If not currently processing, start processing the queue.
15650
- isProcessing = true;
15651
- processNext();
15652
- }
15653
- });
15665
+ getTilesetFeatureStyle(entityTypeId) {
15666
+ var _a, _b, _c, _d;
15667
+ // Locate what style is applicable to the feature.
15668
+ let style = null;
15669
+ if (entityTypeId) {
15670
+ style = ((_b = (_a = this.styleMapping.find(x => x.EntityTypeID == entityTypeId)) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.Settings);
15654
15671
  }
15655
- Queue.Run = Run;
15656
- async function processNext() {
15657
- const nextCall = queue[0];
15658
- if (nextCall) {
15659
- // Call the next function in the queue.
15660
- await nextCall();
15661
- }
15672
+ if (!style) {
15673
+ style = ((_c = this.fallbackStyle) === null || _c === void 0 ? void 0 : _c.Settings);
15674
+ }
15675
+ if (!style || !((_d = style === null || style === void 0 ? void 0 : style.modelStyle) === null || _d === void 0 ? void 0 : _d.customize)) {
15676
+ return null;
15677
+ }
15678
+ return style;
15679
+ }
15680
+ getTilesetFeatureNeedsFullData(entityTypeId) {
15681
+ var _a;
15682
+ if (this.historic) {
15683
+ // Unfortunately when we are working with historic we have to request the entity.
15684
+ // This is because we need to know if a record exists at the point in time.
15685
+ return true;
15686
+ }
15687
+ const style = this.getTilesetFeatureStyle(entityTypeId);
15688
+ if (!style) {
15689
+ return false;
15662
15690
  }
15663
- })(Queue = SharedGetters.Queue || (SharedGetters.Queue = {}));
15664
- })(SharedGetters || (SharedGetters = {}));
15665
-
15666
- function colorToCColor$3(color) {
15667
- return new Color(color.red ? color.red / 255 : 0, color.green ? color.green / 255 : 0, color.blue ? color.blue / 255 : 0, color.alpha);
15691
+ const fill = (_a = style === null || style === void 0 ? void 0 : style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor;
15692
+ if (!fill || fill.length <= 0) {
15693
+ return false;
15694
+ }
15695
+ return fill[0].type != 0;
15696
+ }
15668
15697
  }
15698
+
15669
15699
  /**
15670
15700
  * Returns if a given tileset is alive and in the scene.
15671
15701
  * @param viewer
@@ -15684,22 +15714,6 @@ function isAlive$2(viewer, cTileset) {
15684
15714
  }
15685
15715
  return viewer.scene.primitives.contains(cTileset);
15686
15716
  }
15687
- // ND-1641. BOOKMARKS - (DEMO) View does not match bookmark.
15688
- // We have some evil hard-coded style mappings that need to be fixed.
15689
- // These exist within legacy project views.
15690
- function correctStyle(style) {
15691
- if (style && !style.Settings && !style.ID) {
15692
- return {
15693
- ID: -1,
15694
- Name: "Unknown",
15695
- Settings: {
15696
- ...style
15697
- },
15698
- Type: Style.EType.Entity
15699
- };
15700
- }
15701
- return style;
15702
- }
15703
15717
  const VIEWER_WATCH_KEY = "bruce-viewer-watch";
15704
15718
  const WATCH_KEY = "bruce-tileset-watch";
15705
15719
  /**
@@ -16060,693 +16074,896 @@ var TilesetRenderEngine;
16060
16074
  else if (etc === null || etc === void 0 ? void 0 : etc.ExternalTilesetURL) {
16061
16075
  loadUrl = etc.ExternalTilesetURL;
16062
16076
  }
16063
- if (!loadUrl && tileset) {
16064
- const api = apiGetter.getApi(accountId);
16065
- await api.Loading;
16066
- loadUrl = Tileset.GetFileUrl({
16067
- file: "tileset.json",
16068
- tilesetId: (tileset === null || tileset === void 0 ? void 0 : tileset.ID) ? tileset.ID : params.tileset.id,
16069
- viaCdn: false,
16070
- legacy: true,
16071
- cacheToken: tileset.GenerateVersion,
16072
- api: api
16073
- });
16077
+ if (!loadUrl && tileset) {
16078
+ const api = apiGetter.getApi(accountId);
16079
+ await api.Loading;
16080
+ loadUrl = Tileset.GetFileUrl({
16081
+ file: "tileset.json",
16082
+ tilesetId: (tileset === null || tileset === void 0 ? void 0 : tileset.ID) ? tileset.ID : params.tileset.id,
16083
+ viaCdn: false,
16084
+ legacy: true,
16085
+ cacheToken: tileset.GenerateVersion,
16086
+ api: api
16087
+ });
16088
+ }
16089
+ if (viaCdn && (tileset === null || tileset === void 0 ? void 0 : tileset.ID) && !ionId && !(etc === null || etc === void 0 ? void 0 : etc.ExternalTilesetURL)) {
16090
+ const api = apiGetter.getApi(accountId);
16091
+ await api.Loading;
16092
+ if (api.GetCdnBaseUrl()) {
16093
+ /*
16094
+ const rootFile = tileset.RootFileName ? tileset.RootFileName : "tileset.json";
16095
+ loadUrl = api.LegacyTilesetCdnUrl
16096
+ .replace("<TILESETID>", tileset.ID)
16097
+ .replace("<FILEPATH>", rootFile)
16098
+ .replace("<ACCOUNT>", accountId);
16099
+ */
16100
+ loadUrl = Tileset.GetFileUrl({
16101
+ file: "tileset.json",
16102
+ tilesetId: (tileset === null || tileset === void 0 ? void 0 : tileset.ID) ? tileset.ID : params.tileset.id,
16103
+ viaCdn: true,
16104
+ legacy: true,
16105
+ cacheToken: params.tileset.generateVersion,
16106
+ api: api
16107
+ });
16108
+ }
16109
+ }
16110
+ const cTileset = await createTileset(loadUrl, {
16111
+ backFaceCulling: backFaceCulling === true
16112
+ }, noMemoryLimit);
16113
+ viewer.scene.primitives.add(cTileset);
16114
+ OnTilesetReady(cTileset).then(() => {
16115
+ var _a;
16116
+ try {
16117
+ if (!isAlive$2(viewer, cTileset)) {
16118
+ return;
16119
+ }
16120
+ if (noMemoryLimit != true) {
16121
+ (_a = GetMemoryWatcher(params.viewer)) === null || _a === void 0 ? void 0 : _a.Watch(cTileset);
16122
+ }
16123
+ if (tileset) {
16124
+ let settings = tileset.Settings;
16125
+ settings = {
16126
+ ...settings
16127
+ };
16128
+ let origin = settings.origin;
16129
+ origin = {
16130
+ ...origin
16131
+ };
16132
+ let etc = settings.etc;
16133
+ etc = {
16134
+ ...etc
16135
+ };
16136
+ let positionOffset = settings.positionOffset;
16137
+ positionOffset = {
16138
+ ...positionOffset
16139
+ };
16140
+ const rotation = settings.rotation;
16141
+ ApplySettings({
16142
+ cTileset,
16143
+ settings: {
16144
+ attenuation: visual.attenuation != false,
16145
+ colorMask: visual.colorMask,
16146
+ maximumAttenuation: visual.attenuationMax,
16147
+ maxScreenSpaceError: visual.maximumScreenSpaceError
16148
+ }
16149
+ });
16150
+ ApplyPosition({
16151
+ cTileset: cTileset,
16152
+ position: {
16153
+ transform: rotation ? {
16154
+ heading: rotation.z,
16155
+ pitch: rotation.x,
16156
+ roll: rotation.y,
16157
+ scale: EnsureNumber(origin === null || origin === void 0 ? void 0 : origin.scale, 1),
16158
+ z: EnsureNumber(positionOffset === null || positionOffset === void 0 ? void 0 : positionOffset.altitude),
16159
+ x: EnsureNumber(positionOffset === null || positionOffset === void 0 ? void 0 : positionOffset.latitude),
16160
+ y: EnsureNumber(positionOffset === null || positionOffset === void 0 ? void 0 : positionOffset.longitude)
16161
+ } : null,
16162
+ location: (origin === null || origin === void 0 ? void 0 : origin.latitude) ? {
16163
+ latitude: origin.latitude,
16164
+ longitude: origin.longitude,
16165
+ altitude: origin.altitude
16166
+ } : null,
16167
+ isLegacy: true,
16168
+ legacyProps: {
16169
+ rotate: etc.dontRotate != true,
16170
+ transform: etc.dontTransform != true,
16171
+ usingBoundingBox: etc.usingBoundingBox != false
16172
+ }
16173
+ }
16174
+ });
16175
+ params.viewer.scene.requestRender();
16176
+ }
16177
+ }
16178
+ catch (e) {
16179
+ console.error(e);
16180
+ }
16181
+ });
16182
+ return cTileset;
16183
+ }
16184
+ TilesetRenderEngine.RenderLegacy = RenderLegacy;
16185
+ // Util for styling Tileset features.
16186
+ // Also used to flag historic metadata based on timeline changes.
16187
+ class Styler extends TilesetStyler {
16188
+ }
16189
+ TilesetRenderEngine.Styler = Styler;
16190
+ /**
16191
+ * The maximum memory in MB that can be used by all tilesets.
16192
+ * This is distributed evenly between all loaded tilesets.
16193
+ */
16194
+ TilesetRenderEngine.MAX_TILESET_MEMORY = 1024;
16195
+ /**
16196
+ * Watches tilesets in the viewer.
16197
+ * This will regulate their max memory param.
16198
+ * As more get watched their memory will be reduced.
16199
+ */
16200
+ class MemoryWatcher {
16201
+ constructor(viewer) {
16202
+ this.watched = [];
16203
+ this.viewer = viewer;
16204
+ }
16205
+ distributeMemory() {
16206
+ this.clean();
16207
+ // Total 1gb as default.
16208
+ // We'll need to allow user to change this somehow.
16209
+ const maxMemory = TilesetRenderEngine.MAX_TILESET_MEMORY;
16210
+ // Minimum memory in MB per tileset.
16211
+ const minMemory = 80;
16212
+ const totalPerTileset = Math.max(this.watched.length ? maxMemory / this.watched.length : maxMemory, minMemory);
16213
+ this.watched.forEach(x => {
16214
+ // Newer Cesium killed this property.
16215
+ // TODO: Check if it's needed then.
16216
+ x["maximumMemoryUsage"] = totalPerTileset;
16217
+ });
16218
+ }
16219
+ destroy() {
16220
+ this.watched = [];
16221
+ }
16222
+ /**
16223
+ * Remove all dead tilesets.
16224
+ */
16225
+ clean() {
16226
+ // Remove all dead tilesets.
16227
+ this.watched = this.watched.filter(x => isAlive$2(this.viewer, x));
16228
+ // Check if viewer is destroyed.
16229
+ if (!this.viewer || this.viewer.isDestroyed()) {
16230
+ this.destroy();
16231
+ }
16232
+ }
16233
+ Watch(tileset) {
16234
+ if (!tileset) {
16235
+ return;
16236
+ }
16237
+ if (!tileset[WATCH_KEY]) {
16238
+ tileset[WATCH_KEY] = ObjectUtils.UId();
16239
+ }
16240
+ const index = this.watched.findIndex(x => x[WATCH_KEY] === tileset[WATCH_KEY]);
16241
+ if (index >= 0) {
16242
+ return;
16243
+ }
16244
+ this.watched.push(tileset);
16245
+ this.distributeMemory();
16246
+ }
16247
+ Unwatch(tileset) {
16248
+ if (!tileset) {
16249
+ return;
16250
+ }
16251
+ if (!tileset[WATCH_KEY]) {
16252
+ tileset[WATCH_KEY] = ObjectUtils.UId();
16253
+ }
16254
+ const index = this.watched.findIndex(x => (x === null || x === void 0 ? void 0 : x[WATCH_KEY]) === tileset[WATCH_KEY]);
16255
+ if (index > -1) {
16256
+ this.watched.splice(index, 1);
16257
+ }
16258
+ this.distributeMemory();
16259
+ }
16260
+ }
16261
+ TilesetRenderEngine.MemoryWatcher = MemoryWatcher;
16262
+ function GetMemoryWatcher(viewer) {
16263
+ // If viewer is dead return nothing.
16264
+ if (!viewer || viewer.isDestroyed()) {
16265
+ return null;
16266
+ }
16267
+ if (!viewer[VIEWER_WATCH_KEY]) {
16268
+ viewer[VIEWER_WATCH_KEY] = new MemoryWatcher(viewer);
16269
+ }
16270
+ return viewer[VIEWER_WATCH_KEY];
16271
+ }
16272
+ TilesetRenderEngine.GetMemoryWatcher = GetMemoryWatcher;
16273
+ })(TilesetRenderEngine || (TilesetRenderEngine = {}));
16274
+
16275
+ /**
16276
+ * This is a helper for making entity requests based on a grid area.
16277
+ * It will return grid cells based on given view-area then will remember when stuff-
16278
+ * was fully-fetched for those cells to avoid making duplicate requests.
16279
+ */
16280
+ var EntityGlobe;
16281
+ (function (EntityGlobe) {
16282
+ class Cell {
16283
+ constructor() {
16284
+ this.Fetched = false;
16285
+ this.IsFetched = null;
16286
+ this.FetchPageIndex = 0;
16287
+ // Optional URL to use instead of default URL generation.
16288
+ // This is typically a response from the API to use this specific URL for the next page.
16289
+ this.FetchURL = null;
16290
+ this.Boundaries = null;
16291
+ this.Fetching = false;
16292
+ }
16293
+ GetBounds() {
16294
+ // Entity data works in -180 to 180 range.
16295
+ function prepareRangeForBounds(range) {
16296
+ if (range > 180) {
16297
+ return range - 360;
16298
+ }
16299
+ return range;
16300
+ }
16301
+ // Add minor decimal as API crashes when giving it whole numbers.
16302
+ const maxLon = prepareRangeForBounds(this.Boundaries.maxLongitude);
16303
+ const minLon = prepareRangeForBounds(this.Boundaries.minLongitude);
16304
+ const maxLat = prepareRangeForBounds(this.Boundaries.maxLatitude);
16305
+ const minLat = prepareRangeForBounds(this.Boundaries.minLatitude);
16306
+ return {
16307
+ east: maxLon,
16308
+ north: maxLat,
16309
+ south: minLat,
16310
+ west: minLon
16311
+ };
16312
+ }
16313
+ }
16314
+ EntityGlobe.Cell = Cell;
16315
+ class Grid {
16316
+ constructor() {
16317
+ this.cells = {};
16074
16318
  }
16075
- if (viaCdn && (tileset === null || tileset === void 0 ? void 0 : tileset.ID) && !ionId && !(etc === null || etc === void 0 ? void 0 : etc.ExternalTilesetURL)) {
16076
- const api = apiGetter.getApi(accountId);
16077
- await api.Loading;
16078
- if (api.GetCdnBaseUrl()) {
16079
- /*
16080
- const rootFile = tileset.RootFileName ? tileset.RootFileName : "tileset.json";
16081
- loadUrl = api.LegacyTilesetCdnUrl
16082
- .replace("<TILESETID>", tileset.ID)
16083
- .replace("<FILEPATH>", rootFile)
16084
- .replace("<ACCOUNT>", accountId);
16085
- */
16086
- loadUrl = Tileset.GetFileUrl({
16087
- file: "tileset.json",
16088
- tilesetId: (tileset === null || tileset === void 0 ? void 0 : tileset.ID) ? tileset.ID : params.tileset.id,
16089
- viaCdn: true,
16090
- legacy: true,
16091
- cacheToken: params.tileset.generateVersion,
16092
- api: api
16093
- });
16319
+ GetCellsForView(cameraPos, viewRect) {
16320
+ const cells = [];
16321
+ const minLat = viewRect.south;
16322
+ const minLon = viewRect.west;
16323
+ const maxLat = viewRect.north;
16324
+ const maxLon = viewRect.east;
16325
+ const MAX_CELLS = 150;
16326
+ const cellDegreeSize = getCellSizeFromHeight(viewRect.alt);
16327
+ // console.log("cell size", cellDegreeSize, "height", viewRect.alt);
16328
+ let curMinLon = floorValueToCellSize(cellDegreeSize, minLon);
16329
+ let curMinLat = floorValueToCellSize(cellDegreeSize, minLat);
16330
+ // For larger views we add additional padding because our view-area culling is too strong.
16331
+ if (cellDegreeSize >= 2) {
16332
+ curMinLon -= cellDegreeSize;
16333
+ curMinLat -= cellDegreeSize;
16094
16334
  }
16095
- }
16096
- const cTileset = await createTileset(loadUrl, {
16097
- backFaceCulling: backFaceCulling === true
16098
- }, noMemoryLimit);
16099
- viewer.scene.primitives.add(cTileset);
16100
- OnTilesetReady(cTileset).then(() => {
16101
- var _a;
16102
- try {
16103
- if (!isAlive$2(viewer, cTileset)) {
16104
- return;
16105
- }
16106
- if (noMemoryLimit != true) {
16107
- (_a = GetMemoryWatcher(params.viewer)) === null || _a === void 0 ? void 0 : _a.Watch(cTileset);
16335
+ let centerX = cameraPos === null || cameraPos === void 0 ? void 0 : cameraPos.longitude;
16336
+ let centerY = cameraPos === null || cameraPos === void 0 ? void 0 : cameraPos.latitude;
16337
+ if (isNaN(centerX) || isNaN(centerY)) {
16338
+ centerX = (minLon + maxLon) / 2;
16339
+ centerY = (minLat + maxLat) / 2;
16340
+ }
16341
+ let width = Math.ceil((maxLon - curMinLon) / cellDegreeSize);
16342
+ let height = Math.ceil((maxLat - curMinLat) / cellDegreeSize);
16343
+ // For larger views we add additional padding because our view-area culling is too strong.
16344
+ if (cellDegreeSize >= 2) {
16345
+ width += 1;
16346
+ height += 1;
16347
+ }
16348
+ const cellDistances = [];
16349
+ for (let x = 0; x < width; x++) {
16350
+ for (let y = 0; y < height; y++) {
16351
+ const lon = x * cellDegreeSize + curMinLon;
16352
+ const lat = y * cellDegreeSize + curMinLat;
16353
+ const cellCenterX = lon + cellDegreeSize / 2;
16354
+ const cellCenterY = lat + cellDegreeSize / 2;
16355
+ const dist = Math.sqrt(Math.pow(cellCenterX - centerX, 2) + Math.pow(cellCenterY - centerY, 2));
16356
+ cellDistances.push({ x, y, dist });
16108
16357
  }
16109
- if (tileset) {
16110
- let settings = tileset.Settings;
16111
- settings = {
16112
- ...settings
16113
- };
16114
- let origin = settings.origin;
16115
- origin = {
16116
- ...origin
16117
- };
16118
- let etc = settings.etc;
16119
- etc = {
16120
- ...etc
16121
- };
16122
- let positionOffset = settings.positionOffset;
16123
- positionOffset = {
16124
- ...positionOffset
16125
- };
16126
- const rotation = settings.rotation;
16127
- ApplySettings({
16128
- cTileset,
16129
- settings: {
16130
- attenuation: visual.attenuation != false,
16131
- colorMask: visual.colorMask,
16132
- maximumAttenuation: visual.attenuationMax,
16133
- maxScreenSpaceError: visual.maximumScreenSpaceError
16134
- }
16135
- });
16136
- ApplyPosition({
16137
- cTileset: cTileset,
16138
- position: {
16139
- transform: rotation ? {
16140
- heading: rotation.z,
16141
- pitch: rotation.x,
16142
- roll: rotation.y,
16143
- scale: EnsureNumber(origin === null || origin === void 0 ? void 0 : origin.scale, 1),
16144
- z: EnsureNumber(positionOffset === null || positionOffset === void 0 ? void 0 : positionOffset.altitude),
16145
- x: EnsureNumber(positionOffset === null || positionOffset === void 0 ? void 0 : positionOffset.latitude),
16146
- y: EnsureNumber(positionOffset === null || positionOffset === void 0 ? void 0 : positionOffset.longitude)
16147
- } : null,
16148
- location: (origin === null || origin === void 0 ? void 0 : origin.latitude) ? {
16149
- latitude: origin.latitude,
16150
- longitude: origin.longitude,
16151
- altitude: origin.altitude
16152
- } : null,
16153
- isLegacy: true,
16154
- legacyProps: {
16155
- rotate: etc.dontRotate != true,
16156
- transform: etc.dontTransform != true,
16157
- usingBoundingBox: etc.usingBoundingBox != false
16158
- }
16159
- }
16160
- });
16161
- params.viewer.scene.requestRender();
16358
+ }
16359
+ cellDistances.sort((a, b) => a.dist - b.dist);
16360
+ for (const { x, y } of cellDistances) {
16361
+ const lon = x * cellDegreeSize + curMinLon;
16362
+ const lat = y * cellDegreeSize + curMinLat;
16363
+ const [id, cell] = getOrCreateCell(this.cells, cellDegreeSize, lon, lon + cellDegreeSize, lat, lat + cellDegreeSize);
16364
+ cells.push(cell);
16365
+ if (cells.length >= MAX_CELLS) {
16366
+ break;
16162
16367
  }
16163
16368
  }
16164
- catch (e) {
16165
- console.error(e);
16369
+ return cells;
16370
+ }
16371
+ }
16372
+ EntityGlobe.Grid = Grid;
16373
+ })(EntityGlobe || (EntityGlobe = {}));
16374
+ function floorValueToCellSize(size, value) {
16375
+ return Math.floor(value / size) * size;
16376
+ }
16377
+ function getCellSizeFromHeight(height) {
16378
+ if (height < 1000) {
16379
+ return 0.01;
16380
+ }
16381
+ if (height < 5000) {
16382
+ return 0.04;
16383
+ }
16384
+ else if (height < 10000) {
16385
+ return 0.15;
16386
+ }
16387
+ else if (height < 30000) {
16388
+ return 0.5;
16389
+ }
16390
+ else if (height < 70000) {
16391
+ return 0.5;
16392
+ }
16393
+ else if (height < 100000) {
16394
+ return 1;
16395
+ }
16396
+ else if (height < 150000) {
16397
+ return 1;
16398
+ }
16399
+ else if (height < 200000) {
16400
+ return 1.5;
16401
+ }
16402
+ else if (height < 300000) {
16403
+ return 1.5;
16404
+ }
16405
+ else if (height < 500000) {
16406
+ return 3;
16407
+ }
16408
+ else if (height < 1000000) {
16409
+ return 3;
16410
+ }
16411
+ else if (height < 1200000) {
16412
+ return 4;
16413
+ }
16414
+ else if (height < 2000000) {
16415
+ return 6;
16416
+ }
16417
+ else if (height < 3000000) {
16418
+ return 20;
16419
+ }
16420
+ return 35;
16421
+ }
16422
+ function isCellFetched(cell) {
16423
+ if (cell.Fetched) {
16424
+ return true;
16425
+ }
16426
+ return false;
16427
+ }
16428
+ function getOrCreateCell(cells, cellSize, lon, maxLon, lat, maxLat) {
16429
+ const id = cellSize + "_" + lon + "_" + maxLon + "_" + lat + "_" + maxLat;
16430
+ let cell = cells[id];
16431
+ if (!cell) {
16432
+ cell = cells[id] = new EntityGlobe.Cell();
16433
+ cell.Boundaries = {
16434
+ minLatitude: lat,
16435
+ maxLatitude: maxLat,
16436
+ minLongitude: lon,
16437
+ maxLongitude: maxLon
16438
+ };
16439
+ cell.IsFetched = () => isCellFetched(cell);
16440
+ }
16441
+ return [id, cell];
16442
+ }
16443
+
16444
+ const MAX_AREA_IN_DEGREES$1 = 90;
16445
+ const MAX_RETRY_ATTEMPTS = 1;
16446
+ const RETRY_DELAY_INCREMENT = 500;
16447
+ const REQUEST_PAGE_DELAY = 50;
16448
+ class regMenuItemGetter {
16449
+ constructor(typeId, tagIds, minHeight, maxHeight) {
16450
+ this.TypeId = typeId;
16451
+ this.TagIds = tagIds;
16452
+ this.MinHeight = minHeight;
16453
+ this.MaxHeight = maxHeight;
16454
+ }
16455
+ }
16456
+ async function delay(milliseconds) {
16457
+ return new Promise((res) => {
16458
+ setTimeout(() => {
16459
+ res();
16460
+ }, milliseconds);
16461
+ });
16462
+ }
16463
+ /**
16464
+ * This is a batched entity getter.
16465
+ * It will scan for entity records in a view-area and emit them in batches.
16466
+ * It will restart scanning if the camera moves.
16467
+ */
16468
+ var EntityFilterGetter;
16469
+ (function (EntityFilterGetter) {
16470
+ let EStatus;
16471
+ (function (EStatus) {
16472
+ EStatus["Scanning"] = "SCANNING";
16473
+ EStatus["Loading"] = "LOADING";
16474
+ })(EStatus = EntityFilterGetter.EStatus || (EntityFilterGetter.EStatus = {}));
16475
+ class Getter {
16476
+ get OnUpdate() {
16477
+ if (!this.onUpdate) {
16478
+ this.onUpdate = new BruceEvent();
16166
16479
  }
16167
- });
16168
- return cTileset;
16169
- }
16170
- TilesetRenderEngine.RenderLegacy = RenderLegacy;
16171
- class Styler {
16172
- constructor() {
16173
- this.disposed = false;
16174
- // Indicates if the styler has been loaded/initialized and is ready to style.
16175
- this.loaded = false;
16176
- this.styleMappingLoaded = false;
16177
- this.styleMappingsLoaded = {};
16178
- this.fallbackStyle = null;
16179
- this.expandSources = false;
16180
- this.loadingCounter = 0;
16181
- // Dictionary of Entity ID -> boolean to indicate which Entities have been styled.
16182
- // This is used to determine if the Style colour needs to be overriden or not.
16183
- this.styledEntityIds = {};
16184
- // Dictionary of Entity ID -> boolean to indicate which Entities have had full Entity data loaded.
16185
- // This is used to determine what Entities need to be re-requested when the scene time changes.
16186
- this.styledWithAttrEntityIds = {};
16187
- this.scenario = 0;
16188
- // Indicates that we are retrieving historic records.
16189
- // This means that the current scene's time is included in the request.
16190
- this.historic = false;
16191
- this.viewerDateTimeChangeRemoval = null;
16192
- this.historicRefreshAbortController = null;
16193
- // More expensive process.
16194
- // When an Entity is styled, the rego is reviewed and updated if needed.
16195
- // This is currently used for scenarios, and off by default to keep other processes faster.
16196
- this.shouldUpdateRegoStates = false;
16197
- this.runningQueues = 0;
16198
- this.recordLoadQueue = [];
16199
- this.recordCheckQueue = [];
16200
- // % progress for how many Entities have been styled so far.
16201
- // This can change as Tiles load in and more queue.
16202
- this._styleProgress = 0;
16203
- this._styleProgressQueue = new DelayQueue(() => {
16204
- this.updateStyleProgress();
16205
- }, 250);
16206
- // Event for when the style progress changes.
16207
- this.OnStyleProgress = new BruceEvent();
16208
- // Cache of Entity objects to use when styling instead of requesting them.
16209
- this.entityDataCache = {};
16480
+ return this.onUpdate;
16210
16481
  }
16211
- get Disposed() {
16212
- return this.disposed;
16482
+ get OnStateUpdate() {
16483
+ if (!this.onStateUpdate) {
16484
+ this.onStateUpdate = new BruceEvent();
16485
+ }
16486
+ return this.onStateUpdate;
16213
16487
  }
16214
- get Loaded() {
16215
- return this.loaded;
16488
+ get OnScanUpdate() {
16489
+ if (!this.onScanUpdate) {
16490
+ this.onScanUpdate = new BruceEvent();
16491
+ }
16492
+ return this.onScanUpdate;
16216
16493
  }
16217
- get StyleProgress() {
16218
- return this._styleProgress;
16494
+ get isLooping() {
16495
+ return this.looping > 0;
16219
16496
  }
16220
- Init(params) {
16221
- var _a;
16222
- let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic } = params;
16223
- this.viewer = viewer;
16497
+ constructor(params) {
16498
+ this.onUpdate = null;
16499
+ this.LastStateUpdates = {};
16500
+ this.onStateUpdate = null;
16501
+ this.onScanUpdate = null;
16502
+ this.viewPortChangeRemoval = null;
16503
+ this.viewPortDelayQueue = null;
16504
+ this.viewerDateTimeChangeRemoval = null;
16505
+ this.cells = null;
16506
+ this.registeredItems = {};
16507
+ this.getterLoopId = 0;
16508
+ this.getterLoopAbortControllers = {};
16509
+ this.looping = 0;
16510
+ this.tagIds = null;
16511
+ this.minHeight = 0;
16512
+ this.maxHeight = 100000;
16513
+ this.viewRect = null;
16514
+ this.viewCenter = null;
16515
+ this.scenario = 0;
16516
+ this.historicRefreshAbortController = null;
16517
+ // Entity IDs found for the latest integrity.
16518
+ // We use this for refreshing historic data without having to repeat geographic queries.
16519
+ this.gatheredIntegrity = null;
16520
+ this.gatheredEntityIds = [];
16521
+ const { api, viewer, viewPort, typeIds, schemaId, batchSize, attrFilter, historicAttrKey, historicInterpolation, historic, viaCdn, scenario } = params;
16224
16522
  this.api = api;
16225
- this.cTileset = cTileset;
16226
- this.register = register;
16227
- this.menuItemId = menuItemId;
16523
+ this.typeIds = typeIds;
16524
+ this.schemaId = schemaId;
16228
16525
  this.historic = Boolean(historic);
16229
- if (expandSources != null) {
16230
- this.expandSources = expandSources;
16231
- }
16232
- if (fallbackStyleId != null) {
16233
- this.fallbackStyleId = fallbackStyleId;
16234
- }
16235
- if (styleMapping) {
16236
- this.styleMapping = styleMapping;
16237
- if (this.styleMapping) {
16238
- // Dereference.
16239
- this.styleMapping = JSON.parse(JSON.stringify(this.styleMapping));
16240
- }
16241
- // ND-1641. BOOKMARKS - (DEMO) View does not match bookmark.
16242
- // We have some evil hard-coded style mappings that need to be fixed.
16243
- // These exist within legacy project views.
16244
- // Update: This now also lets people have a style mapping without a real style record.
16245
- if ((_a = this.styleMapping) === null || _a === void 0 ? void 0 : _a.length) {
16246
- for (let i = 0; i < this.styleMapping.length; i++) {
16247
- const mapItem = this.styleMapping[i];
16248
- const mapStyle = mapItem.style ? mapItem.style : mapItem.Style;
16249
- this.styleMapping[i].style = correctStyle(mapStyle);
16250
- }
16251
- }
16252
- }
16253
- if (scenario != null) {
16254
- this.scenario = scenario;
16255
- }
16256
- if (!!scenario) {
16257
- this.shouldUpdateRegoStates = true;
16258
- }
16259
- else if (this.historic) {
16260
- this.shouldUpdateRegoStates = true;
16261
- }
16262
- this.loadStyles();
16263
- this.viewerDateTimeSub();
16264
- this.loaded = true;
16526
+ this.historicAttrKey = historicAttrKey;
16527
+ this.historicInterpolation = Boolean(historicInterpolation);
16528
+ this.viaCdn = Boolean(viaCdn);
16529
+ this.batchSize = isNaN(batchSize) ? 300 : batchSize;
16530
+ this.viewPort = viewPort;
16531
+ this.attrFilter = attrFilter;
16532
+ this.viewer = viewer;
16533
+ this.scenario = scenario ? scenario : 0;
16534
+ this.updateBounds();
16265
16535
  }
16266
16536
  /**
16267
- * Updates style mapping and fallback style.
16268
- * This will trigger a re-style of all entities, and will stop existing style loads.
16269
- * @param params
16537
+ * Returns id that represents the combined menu item parameters.
16538
+ * If integrity changes while a request is running, the request will not emit a response.
16539
+ * @returns
16270
16540
  */
16271
- UpdateStyleMapping(params) {
16272
- var _a;
16273
- if (params.styleMapping) {
16274
- // Dereference.
16275
- params.styleMapping = JSON.parse(JSON.stringify(params.styleMapping));
16276
- this.styleMapping = params.styleMapping;
16277
- // ND-1641. BOOKMARKS - (DEMO) View does not match bookmark.
16278
- // We have some evil hard-coded style mappings that need to be fixed.
16279
- // These exist within legacy project views.
16280
- // Update: This now also lets people have a style mapping without a real style record.
16281
- if ((_a = this.styleMapping) === null || _a === void 0 ? void 0 : _a.length) {
16282
- for (let i = 0; i < this.styleMapping.length; i++) {
16283
- const mapItem = this.styleMapping[i];
16284
- const mapStyle = mapItem.style ? mapItem.style : mapItem.Style;
16285
- this.styleMapping[i].style = correctStyle(mapStyle);
16286
- }
16287
- }
16288
- }
16289
- if (!isNaN(params.fallbackStyleId) && params.fallbackStyleId != null) {
16290
- this.fallbackStyleId = params.fallbackStyleId;
16291
- }
16292
- if (params.fallbackStyle) {
16293
- this.fallbackStyle = params.fallbackStyle;
16294
- }
16295
- else {
16296
- this.fallbackStyle = null;
16297
- }
16298
- if (params.expandSources != null) {
16299
- this.expandSources = params.expandSources;
16541
+ getIntegrityId() {
16542
+ let integrity = this.tagIds == null ? "" : this.tagIds.join();
16543
+ if (this.historicAttrKey) {
16544
+ integrity += "isHistoric";
16545
+ integrity += this.historicAttrKey;
16300
16546
  }
16301
- if (params.scenario != null) {
16302
- this.scenario = params.scenario;
16303
- if (!this.shouldUpdateRegoStates && !!this.scenario) {
16304
- this.shouldUpdateRegoStates = true;
16305
- }
16547
+ else if (this.historic) {
16548
+ integrity += "isHistoric";
16306
16549
  }
16307
- if (params.historic != null) {
16308
- this.historic = params.historic;
16309
- if (params.historic || this.historic != params.historic) {
16310
- this.shouldUpdateRegoStates = true;
16311
- }
16312
- if (params.historic) {
16313
- this.viewerDateTimeSub();
16314
- }
16315
- else {
16316
- this.viewerDateTimeDispose();
16317
- }
16550
+ if (this.scenario) {
16551
+ integrity += this.scenario;
16318
16552
  }
16319
- if (this.loaded) {
16320
- // Empty queues.
16321
- // Requeue all.
16322
- this.recordLoadQueue = [];
16323
- this.recordCheckQueue = [];
16324
- const regos = this.register.GetRegos({
16325
- menuItemId: this.menuItemId
16326
- });
16327
- // Reset progress.
16328
- this._styleProgressQueue.Call(true);
16329
- this.styleMappingLoaded = false;
16330
- this.styleMappingsLoaded = {};
16331
- this.QueueEntities(regos);
16332
- this.loadStyles();
16553
+ if (this.typeIds) {
16554
+ integrity += this.typeIds.join();
16333
16555
  }
16556
+ return integrity;
16557
+ }
16558
+ viewAreaSub() {
16559
+ this.viewAreaDispose();
16560
+ // We'll avoid restarting the scanner too often.
16561
+ this.viewPortDelayQueue = new DelayQueue(() => {
16562
+ this.updateBounds();
16563
+ this.startGetterLoop();
16564
+ }, 2000);
16565
+ this.viewPortChangeRemoval = this.viewPort.Updated().Subscribe(() => {
16566
+ var _a;
16567
+ (_a = this.viewPortDelayQueue) === null || _a === void 0 ? void 0 : _a.Call();
16568
+ });
16569
+ }
16570
+ viewAreaDispose() {
16571
+ var _a, _b;
16572
+ (_a = this.viewPortChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
16573
+ this.viewPortChangeRemoval = null;
16574
+ (_b = this.viewPortDelayQueue) === null || _b === void 0 ? void 0 : _b.Dispose();
16575
+ this.viewPortDelayQueue = null;
16334
16576
  }
16335
16577
  /**
16336
- * Updates a cache of Entity objects to use when styling.
16337
- * When a cache is available, a request won't be performed to get that Entity's data.
16338
- * @param entityIds
16339
- * @param entities If an object is not available for a supplied Entity ID, then the cache for it is removed.
16578
+ * Monitors the Cesium viewer and updates the historic data filter values.
16579
+ * If there is no historic attr set, this will do nothing.
16340
16580
  */
16341
- SetEntityCache(entityIds, entities) {
16342
- // Turn the Entities array into an accessible dictionary.
16343
- const newDataMap = {};
16344
- if (entities) {
16345
- for (let i = 0; i < entities.length; i++) {
16346
- const entity = entities[i];
16347
- if (entity.Bruce) {
16348
- newDataMap[entity.Bruce.ID] = entity;
16581
+ viewerDateTimeSub() {
16582
+ if ((!this.historicAttrKey && !this.historic) || this.viewerDateTimeChangeRemoval) {
16583
+ return;
16584
+ }
16585
+ // This is multiplied by the speed of animation to figure
16586
+ // out how many animation "ticks" before we allow an update.
16587
+ let INTERVAL_WHILE_ANIMATING = 2.5 * 1000;
16588
+ let INTERVAL_WHILE_NOT_ANIMATING = 1000;
16589
+ if (this.historicInterpolation) {
16590
+ INTERVAL_WHILE_ANIMATING = 6 * 1000;
16591
+ INTERVAL_WHILE_NOT_ANIMATING = 3.5 * 1000;
16592
+ }
16593
+ let lastUpdateTime = null;
16594
+ let delayQueue = new DelayQueue(() => {
16595
+ try {
16596
+ // If the timeline is animating then we'll wait longer to update.
16597
+ if (this.viewer.clock.shouldAnimate && lastUpdateTime) {
16598
+ if (Math.abs(new Date().getTime() - lastUpdateTime) < INTERVAL_WHILE_ANIMATING) {
16599
+ return;
16600
+ }
16601
+ }
16602
+ const current = this.historicAttrDateTime;
16603
+ this.updateHistoricDateTime();
16604
+ if (current != this.historicAttrDateTime) {
16605
+ this.emitHistoricData();
16349
16606
  }
16350
16607
  }
16351
- }
16352
- // Update the cache for each supplied Entity ID.
16353
- for (let i = 0; i < entityIds.length; i++) {
16354
- const entityId = entityIds[i];
16355
- if (newDataMap[entityId]) {
16356
- this.entityDataCache[entityId] = newDataMap[entityId];
16608
+ catch (e) {
16609
+ console.error(e);
16357
16610
  }
16358
- else {
16359
- delete this.entityDataCache[entityId];
16611
+ }, INTERVAL_WHILE_NOT_ANIMATING);
16612
+ let postUpdateRemoval = this.viewer.scene.postUpdate.addEventListener(() => {
16613
+ if (delayQueue) {
16614
+ delayQueue.Call();
16360
16615
  }
16361
- }
16616
+ });
16617
+ this.viewerDateTimeChangeRemoval = () => {
16618
+ delayQueue === null || delayQueue === void 0 ? void 0 : delayQueue.Dispose();
16619
+ postUpdateRemoval === null || postUpdateRemoval === void 0 ? void 0 : postUpdateRemoval();
16620
+ delayQueue = null;
16621
+ postUpdateRemoval = null;
16622
+ };
16362
16623
  }
16363
- QueueEntities(entities, highPriority = false) {
16364
- if (!this.loaded) {
16624
+ updateHistoricDateTime() {
16625
+ if (!this.historicAttrKey && !this.historic) {
16626
+ this.historicAttrDateTime = null;
16365
16627
  return;
16366
16628
  }
16367
- // We set a default colour right away to avoid race conditions at later times.
16368
- for (let i = 0; i < entities.length; i++) {
16369
- const entity = entities[i];
16370
- this.styledEntityIds[entity.entityId] = true;
16371
- CesiumEntityStyler.BakeDefaultColor({
16372
- entity: entity.visual,
16373
- viewer: this.viewer,
16374
- override: false
16375
- });
16376
- }
16377
- for (let i = 0; i < entities.length; i++) {
16378
- const entity = entities[i];
16379
- this.queueTilesetFeatureStyle(entity, highPriority);
16380
- }
16381
- if (this.recordLoadQueue.length > 0) {
16382
- this.processQueue();
16629
+ const isChanged = (before, after) => {
16630
+ if (before && !after) {
16631
+ return true;
16632
+ }
16633
+ if (!before && after) {
16634
+ return true;
16635
+ }
16636
+ // Change must be at least 0.1 seconds.
16637
+ return Math.abs(before.getTime() - after.getTime()) > 100;
16638
+ };
16639
+ const oldDateTime = this.historicAttrDateTime ? new Date(this.historicAttrDateTime) : null;
16640
+ const newDateTime = JulianDate.toDate(this.viewer.clock.currentTime);
16641
+ if (isChanged(oldDateTime, newDateTime)) {
16642
+ this.historicAttrDateTime = newDateTime.toISOString();
16383
16643
  }
16384
16644
  }
16385
- async processQueue() {
16386
- var _a, _b;
16387
- const MAX_BATCHES = 2;
16388
- const BATCH_DELAY = 200;
16389
- if (this.runningQueues >= MAX_BATCHES) {
16390
- return;
16391
- }
16392
- else if (this.disposed) {
16393
- return;
16394
- }
16395
- this.runningQueues += 1;
16396
- let batch = [];
16397
- let rerun = false;
16398
- try {
16399
- batch = this.getEntityIdsForQueue();
16400
- if (batch.length > 0) {
16401
- let entities = [];
16402
- {
16403
- // Constructing new array of IDs for any ones that don't have cached data.
16404
- const batchToRequest = [];
16405
- // Check cache for available Entities.
16406
- for (let i = 0; i < batch.length; i++) {
16407
- const entityId = batch[i];
16408
- const entity = this.entityDataCache[entityId];
16409
- if (entity) {
16410
- entities.push(entity);
16411
- }
16412
- else {
16413
- batchToRequest.push(entityId);
16414
- }
16415
- }
16416
- if (batchToRequest.length) {
16417
- const dateTime = this.historic ? this.historicAttrDateTime : null;
16418
- const { entities: response } = await Entity$1.GetListByIds({
16419
- api: this.api,
16420
- entityIds: batchToRequest,
16421
- migrated: true,
16422
- expandSources: this.expandSources,
16423
- scenario: this.scenario,
16424
- historicPoint: dateTime
16425
- });
16426
- // See if the historic date is the same.
16427
- // If not, then we have to re-add the Entities to the queue and re-run.
16428
- const curDateTime = this.historic ? this.historicAttrDateTime : null;
16429
- if (curDateTime == dateTime) {
16430
- // Add all found Entities to the list.
16431
- entities.push(...response);
16432
- }
16433
- else {
16434
- rerun = true;
16435
- }
16436
- }
16645
+ viewerDateTimeDispose() {
16646
+ var _a;
16647
+ (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
16648
+ this.viewerDateTimeChangeRemoval = null;
16649
+ }
16650
+ GetMenuItems() {
16651
+ return Object.keys(this.registeredItems);
16652
+ }
16653
+ IncludeMenuItem(menuItemId, typeId, layerIds, minHeight, maxHeight) {
16654
+ this.registeredItems[menuItemId] = new regMenuItemGetter(typeId, layerIds, minHeight, maxHeight);
16655
+ this.updateState();
16656
+ }
16657
+ ExcludeMenuItem(menuItemId) {
16658
+ this.registeredItems[menuItemId] = null;
16659
+ delete this.registeredItems[menuItemId];
16660
+ this.updateState(true);
16661
+ }
16662
+ updateBounds() {
16663
+ const viewRect = this.viewPort.GetBounds();
16664
+ const poi = this.viewPort.GetTarget();
16665
+ if (viewRect && poi) {
16666
+ if (Math.abs(viewRect.west - viewRect.east) > MAX_AREA_IN_DEGREES$1) {
16667
+ return;
16668
+ }
16669
+ if (Math.abs(viewRect.south - viewRect.north) > MAX_AREA_IN_DEGREES$1) {
16670
+ return;
16671
+ }
16672
+ this.viewRect = viewRect;
16673
+ this.viewCenter = poi;
16674
+ }
16675
+ }
16676
+ updateState(onlyIfLooping = false) {
16677
+ const tagIds = [];
16678
+ const typeIds = [];
16679
+ const menuItemIds = this.GetMenuItems();
16680
+ let minHeight = null;
16681
+ let maxHeight = null;
16682
+ for (let i = 0; i < menuItemIds.length; i++) {
16683
+ const menuItem = this.registeredItems[menuItemIds[i]];
16684
+ if (menuItem) {
16685
+ if (maxHeight == null || maxHeight < menuItem.MaxHeight) {
16686
+ maxHeight = menuItem.MaxHeight;
16437
16687
  }
16438
- if (rerun) {
16439
- this.recordLoadQueue = this.recordLoadQueue.concat(batch);
16688
+ if (minHeight == null || minHeight > menuItem.MinHeight) {
16689
+ minHeight = menuItem.MinHeight;
16440
16690
  }
16441
- else {
16442
- // Quick access dictionary of Entity ID -> Entities.
16443
- const entityMap = {};
16444
- // Gather all Tag IDs from found Entities.
16445
- let tagIds = [];
16446
- for (let i = 0; i < entities.length; i++) {
16447
- const entity = entities[i];
16448
- entityMap[entity.Bruce.ID] = entity;
16449
- if ((_b = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a["Layer.ID"]) === null || _b === void 0 ? void 0 : _b.length) {
16450
- const eTagIds = entity.Bruce["Layer.ID"];
16451
- for (let j = 0; j < eTagIds.length; j++) {
16452
- tagIds.push(eTagIds[j]);
16453
- }
16691
+ const itemLayerIds = menuItem.TagIds;
16692
+ if (itemLayerIds) {
16693
+ for (let j = 0; j < itemLayerIds.length; j++) {
16694
+ const itemLayerId = itemLayerIds[j];
16695
+ if (!tagIds.includes(itemLayerId)) {
16696
+ tagIds.push(itemLayerId);
16454
16697
  }
16455
16698
  }
16456
- // Turn into unique list.
16457
- if (tagIds.length) {
16458
- tagIds = tagIds.filter((v, i, a) => a.indexOf(v) === i);
16459
- }
16460
- // Gather records.
16461
- let tags = [];
16462
- if (tagIds.length) {
16463
- tags = (await EntityTag.GetListByIds({
16464
- tagIds: tagIds,
16465
- api: this.api
16466
- })).tags;
16467
- }
16468
- for (let i = 0; i < batch.length; i++) {
16469
- const entityId = batch[i];
16470
- const record = entityMap[entityId];
16471
- const feature = this.getEntityRego(entityId);
16472
- if (feature) {
16473
- const eTags = tags.filter(t => { var _a, _b, _c, _d; return ((_b = (_a = record === null || record === void 0 ? void 0 : record.Bruce) === null || _a === void 0 ? void 0 : _a["Layer.ID"]) === null || _b === void 0 ? void 0 : _b.length) && ((_d = (_c = record === null || record === void 0 ? void 0 : record.Bruce) === null || _c === void 0 ? void 0 : _c["Layer.ID"]) === null || _d === void 0 ? void 0 : _d.includes(t.ID)); });
16474
- this.styleTilesetFeatureFullData(feature, record, eTags);
16475
- }
16699
+ }
16700
+ const itemTypeId = menuItem.TypeId;
16701
+ if (itemTypeId) {
16702
+ if (!typeIds.includes(itemTypeId)) {
16703
+ typeIds.push(itemTypeId);
16476
16704
  }
16477
- rerun = rerun || batch.length > 0;
16478
- this.viewer.scene.requestRender();
16479
16705
  }
16480
16706
  }
16481
16707
  }
16482
- finally {
16483
- setTimeout(() => {
16484
- this.runningQueues -= 1;
16485
- if (rerun || this.recordLoadQueue.length > 0) {
16486
- this.processQueue();
16487
- }
16488
- }, BATCH_DELAY);
16489
- }
16490
- this.viewer.scene.requestRender();
16491
- }
16492
- getEntityIdsForQueue() {
16493
- const BATCH_SIZE = this.expandSources ? 100 : 500;
16494
- return this.recordLoadQueue.splice(0, BATCH_SIZE);
16495
- }
16496
- /**
16497
- * Calculates the current progress % and updates the progress if there is a change.
16498
- */
16499
- updateStyleProgress() {
16500
- if (this.disposed) {
16501
- return;
16502
- }
16503
- let progress = 100; // Done when idling.
16504
- if (this.recordCheckQueue.length || this.recordLoadQueue.length) {
16505
- const total = Object.keys(this.styledEntityIds).length;
16506
- // Done is the total minus the amount left.
16507
- // We have to ensure the same ID isn't counted within or across these arrays as well.
16508
- const uniqueSet = new Set(this.recordCheckQueue.concat(this.recordLoadQueue));
16509
- const done = total - uniqueSet.size;
16510
- progress = done / total * 100;
16511
- // Round to 2dp, 0, or 100.
16512
- if (progress < 0) {
16513
- progress = 0;
16514
- }
16515
- else if (progress < 100) {
16516
- progress = Math.round(progress * 100) / 100;
16517
- }
16518
- else if (progress > 100) {
16519
- progress = 100;
16520
- }
16708
+ if (menuItemIds.length > 0 && (!onlyIfLooping || this.isLooping)) {
16709
+ // Reset cells so none are marked as fetched.
16710
+ this.cells = new EntityGlobe.Grid();
16711
+ this.tagIds = tagIds;
16712
+ this.typeIds = typeIds;
16713
+ this.minHeight = minHeight;
16714
+ this.maxHeight = maxHeight;
16715
+ this.updateBounds();
16716
+ this.updateHistoricDateTime();
16717
+ this.startGetterLoop();
16718
+ this.viewAreaSub();
16719
+ this.viewerDateTimeSub();
16521
16720
  }
16522
- if (this._styleProgress != progress) {
16523
- this._styleProgress = progress;
16524
- this.OnStyleProgress.Trigger(progress);
16721
+ else {
16722
+ this.getterLoopId += 1;
16723
+ this.viewAreaDispose();
16724
+ this.viewerDateTimeDispose();
16525
16725
  }
16526
16726
  }
16527
- getEntityRego(entityId) {
16528
- return this.register.GetRego({
16529
- entityId,
16530
- menuItemId: this.menuItemId
16531
- });
16532
- }
16533
- Dispose() {
16534
- if (this.disposed) {
16535
- return;
16536
- }
16537
- this.disposed = true;
16538
- clearInterval(this.queueLoadInterval);
16539
- clearInterval(this.queueCheckInterval);
16540
- this._styleProgressQueue.Dispose();
16541
- this.viewerDateTimeDispose();
16727
+ postStatus(status) {
16728
+ var _a;
16729
+ this.LastStateUpdates[status.msg] = status;
16730
+ (_a = this.onStateUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(status);
16542
16731
  }
16543
- async loadStyles() {
16544
- var _a, _b;
16545
- const counter = ++this.loadingCounter;
16546
- this.styleMappingLoaded = false;
16547
- this.styleMappingsLoaded = {};
16548
- let fallbackStyleId = this.fallbackStyleId;
16549
- if (fallbackStyleId && fallbackStyleId > 0) {
16550
- try {
16551
- let { style: data } = await Style.Get({
16552
- api: this.api,
16553
- styleId: fallbackStyleId
16554
- });
16555
- this.fallbackStyle = data;
16556
- }
16557
- catch (e) {
16558
- console.error(e);
16732
+ startGetterLoop() {
16733
+ // Increase id so that existing loops stop.
16734
+ this.getterLoopId += 1;
16735
+ const loopId = this.getterLoopId;
16736
+ const loopIntegrity = this.getIntegrityId();
16737
+ // Abort any existing loops that don't match the current loop.
16738
+ // We tried using integrity, however we want to interrupt when user moves camera quickly.
16739
+ // So it's better to use the loop ID.
16740
+ const abortId = String(loopId);
16741
+ {
16742
+ const newAbortControllers = {};
16743
+ for (const key in this.getterLoopAbortControllers) {
16744
+ this.getterLoopAbortControllers[key].abort();
16559
16745
  }
16746
+ this.getterLoopAbortControllers = newAbortControllers;
16560
16747
  }
16561
- if (this.loadingCounter != counter) {
16562
- return;
16563
- }
16564
- // Load styles in the style mapping
16565
- let styleMapping = this.styleMapping;
16566
- if (!styleMapping) {
16567
- styleMapping = [];
16568
- }
16569
- this.styleMapping = styleMapping;
16570
- // Append all found entity-types.
16571
- // That way we can mark them as loaded instead of just the ones in the style mapping.
16572
- try {
16573
- const modelTree = (_b = (_a = this.cTileset) === null || _a === void 0 ? void 0 : _a.extensions) === null || _b === void 0 ? void 0 : _b.modelTree;
16574
- if (modelTree) {
16575
- const entityTypeIds = this.getEntityTypeIdsFromModelTree(modelTree);
16576
- for (let i = 0; i < entityTypeIds.length; i++) {
16577
- const entityTypeId = entityTypeIds[i];
16578
- if (styleMapping.findIndex(x => x.EntityTypeID == entityTypeId) <= -1) {
16579
- styleMapping.push({
16580
- EntityTypeID: entityTypeId,
16581
- StyleID: this.fallbackStyle ? fallbackStyleId : 0,
16582
- style: this.fallbackStyle
16583
- });
16584
- }
16585
- }
16748
+ const abortController = this.getterLoopAbortControllers[abortId] = new AbortController();
16749
+ this.looping += 1;
16750
+ (async () => {
16751
+ var _a, _b, _c, _d, _e, _f, _g, _h;
16752
+ // Larger initial delay for the first loops because terrain is likely loading in.
16753
+ // We also delay because if we enable 50 Menu Items at the same time, common requests we be made if we wait a bit.
16754
+ // Eg: same entity type will be grouped into the same filter getter instance.
16755
+ await delay(loopId <= 3 ? 800 : 300);
16756
+ const MIN_HEIGHT = this.minHeight;
16757
+ const MAX_HEIGHT = this.maxHeight;
16758
+ const PAGE_SIZE = this.batchSize;
16759
+ let retryAttempts = MAX_RETRY_ATTEMPTS;
16760
+ let retryDelay = 0;
16761
+ let prevFirstId = "";
16762
+ let prevLastId = "";
16763
+ let prevTicks = 0;
16764
+ while ((!this.viewCenter || !this.viewRect) && this.getterLoopId == loopId) {
16765
+ await delay(RETRY_DELAY_INCREMENT);
16586
16766
  }
16587
- }
16588
- catch (e) {
16589
- console.error(e);
16590
- }
16591
- // Before we start the loop we'll do a single request for the needed Entity Types.
16592
- // This will be aimed at rows that don't specify a Style and if the default fallback-
16593
- //is set to 0 (using the default of the Entity Type).
16594
- const typeMap = new Map();
16595
- const typeIds = styleMapping.map(x => x.StyleID == -1 || Boolean(x.style) ? null : x.EntityTypeID).filter(x => !!x);
16596
- if (typeIds.length) {
16597
- // We'll split up into batches of 50.
16598
- // Since we add the type IDs as query params I am paranoid of hitting the limit.
16599
- const splits = Math.ceil(typeIds.length / 50);
16600
- const batchSize = 50;
16601
- for (let i = 0; i < splits; i++) {
16602
- const batch = typeIds.slice(i * batchSize, (i + 1) * batchSize);
16603
- const { entityTypes } = await EntityType.GetList({
16604
- api: this.api,
16605
- entityTypeIds: batch
16606
- });
16607
- for (let i = 0; i < entityTypes.length; i++) {
16608
- const entityType = entityTypes[i];
16609
- typeMap.set(entityType.ID, entityType);
16610
- }
16767
+ if (this.getterLoopId != loopId) {
16768
+ return;
16611
16769
  }
16612
- }
16613
- for (let i = 0; i < styleMapping.length; i++) {
16614
- if (this.disposed) {
16615
- break;
16770
+ const alt = this.viewRect.alt;
16771
+ if (alt > MAX_HEIGHT || (alt < MIN_HEIGHT && MIN_HEIGHT > 0)) {
16772
+ return;
16616
16773
  }
16617
- let styleMap = styleMapping[i];
16618
- if (!styleMap.style && styleMap.StyleID != -1) {
16619
- let styleId = styleMap.StyleID;
16620
- // Get default style of the entity type, if
16621
- //no default is already specified.
16622
- if (!styleId && !this.fallbackStyle) {
16623
- try {
16624
- let entityType = typeMap.get(styleMap.EntityTypeID);
16625
- // If the map excluded the type for whatever reason we'll load it now.
16626
- if (!entityType) {
16627
- entityType = (await EntityType.Get({
16774
+ const cells = this.cells.GetCellsForView(this.viewCenter, this.viewRect);
16775
+ (_a = this.onScanUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(cells);
16776
+ let curCellIndex = cells.length > 0 ? 0 : null;
16777
+ let postedScanning = false;
16778
+ let postedLoading = false;
16779
+ let total = 0;
16780
+ while (retryAttempts > 0 && curCellIndex != null) {
16781
+ if (retryDelay > 0) {
16782
+ await delay(retryDelay);
16783
+ }
16784
+ if (this.getterLoopId != loopId) {
16785
+ break;
16786
+ }
16787
+ if (!postedScanning) {
16788
+ this.postStatus({ msg: EStatus.Scanning, revoking: false });
16789
+ postedScanning = true;
16790
+ }
16791
+ const curCell = cells[curCellIndex];
16792
+ if (curCell.IsFetched()) {
16793
+ curCell.Fetching = false;
16794
+ curCellIndex += 1;
16795
+ if (cells[curCellIndex]) {
16796
+ cells[curCellIndex].Fetching = true;
16797
+ }
16798
+ else {
16799
+ curCellIndex = null;
16800
+ }
16801
+ (_b = this.onScanUpdate) === null || _b === void 0 ? void 0 : _b.Trigger(cells);
16802
+ continue;
16803
+ }
16804
+ try {
16805
+ let response = {
16806
+ entities: [],
16807
+ nextPage: false,
16808
+ nextPageUrl: null
16809
+ };
16810
+ await SharedGetters.Queue.Run("Loading Entities from Menu Item that loads Entity Type: " + this.typeIds, async () => {
16811
+ var _a;
16812
+ if (abortController.signal.aborted || !((_a = this.GetMenuItems()) === null || _a === void 0 ? void 0 : _a.length)) {
16813
+ return;
16814
+ }
16815
+ // API gave us a URL to use.
16816
+ if (curCell.FetchURL) {
16817
+ const tmpResponse = await this.api.get(curCell.FetchURL, {
16818
+ abortSignal: abortController.signal,
16819
+ noCache: true
16820
+ });
16821
+ // Same mapping as bruce-models doing Entity.GetList.
16822
+ response = {
16823
+ entities: tmpResponse.Items ? tmpResponse.Items : [],
16824
+ nextPage: tmpResponse.NextPage,
16825
+ nextPageUrl: tmpResponse.NextPageURL
16826
+ };
16827
+ }
16828
+ else {
16829
+ response = await Entity$1.GetList({
16628
16830
  api: this.api,
16629
- entityTypeId: styleMap.EntityTypeID
16630
- })).entityType;
16831
+ scenario: this.scenario,
16832
+ historicKey: this.historicAttrKey,
16833
+ historicPoint: (this.historicAttrKey || this.historic) ? this.historicAttrDateTime : null,
16834
+ schemaId: this.schemaId,
16835
+ filter: {
16836
+ pageSize: PAGE_SIZE,
16837
+ pageIndex: curCell.FetchPageIndex,
16838
+ entityTypeId: this.typeIds,
16839
+ layerIds: this.tagIds,
16840
+ // Any tag specified will be allowed.
16841
+ layerIdsOperator: "in",
16842
+ bounds: curCell.GetBounds(),
16843
+ sortOrder: Api.ESortOrder.Asc,
16844
+ entityTypeConditions: this.attrFilter
16845
+ },
16846
+ viaCdn: this.viaCdn,
16847
+ migrated: true,
16848
+ // If we're taking 2+ minutes to make a query, it's a dud.
16849
+ // This is a timeout imposed on our DB and not external sources.
16850
+ // Honestly could lower down to 30 seconds, but we'll keep it high for now.
16851
+ maxSearchTimeSec: 60 * 2,
16852
+ req: {
16853
+ // If we are passing in an abort, we MUST pass in noCache.
16854
+ // Otherwise we will cache an aborted request.
16855
+ noCache: true,
16856
+ abortSignal: abortController.signal
16857
+ }
16858
+ });
16859
+ }
16860
+ });
16861
+ const entities = response.entities;
16862
+ const integrity = this.getIntegrityId();
16863
+ if (loopIntegrity == integrity && entities) {
16864
+ (_c = this.onUpdate) === null || _c === void 0 ? void 0 : _c.Trigger(entities);
16865
+ }
16866
+ if (this.gatheredIntegrity != integrity) {
16867
+ this.gatheredIntegrity = integrity;
16868
+ this.gatheredEntityIds = [];
16869
+ }
16870
+ // Add to the integrity list for any new IDs found.
16871
+ // This lets us keep track of all IDs we've found within the same integrity for historic data.
16872
+ if (this.historicAttrKey || this.historic) {
16873
+ for (let i = 0; i < entities.length; i++) {
16874
+ const entity = entities[i];
16875
+ if (!this.gatheredEntityIds.includes(entity.Bruce.ID)) {
16876
+ this.gatheredEntityIds.push(entity.Bruce.ID);
16877
+ }
16631
16878
  }
16632
- styleId = entityType === null || entityType === void 0 ? void 0 : entityType["DisplaySetting.ID"];
16633
16879
  }
16634
- catch (e) {
16635
- console.error(e);
16880
+ if (this.getterLoopId != loopId) {
16881
+ break;
16636
16882
  }
16637
- }
16638
- if (styleId) {
16639
- try {
16640
- let { style: data } = await Style.Get({
16641
- api: this.api,
16642
- styleId
16643
- });
16644
- if (data) {
16645
- styleMap.style = data;
16646
- styleMap.StyleID = styleId;
16647
- }
16883
+ if (entities.length) {
16884
+ total += entities.length;
16648
16885
  }
16649
- catch (e) {
16650
- console.error(e);
16886
+ if (!postedLoading) {
16887
+ this.postStatus({ msg: EStatus.Loading, revoking: false });
16888
+ postedLoading = true;
16651
16889
  }
16652
- }
16653
- if (this.loadingCounter != counter) {
16654
- return;
16655
- }
16656
- }
16657
- this.styleMappingsLoaded[styleMap.EntityTypeID] = true;
16658
- if (this.loaded) {
16659
- this.processTilesetFeatureCheckQueue();
16660
- this.processQueue();
16661
- }
16662
- }
16663
- if (this.loadingCounter != counter) {
16664
- return;
16665
- }
16666
- this.styleMappingLoaded = true;
16667
- if (!this.disposed && this.loaded) {
16668
- await this.processTilesetFeatureCheckQueue();
16669
- if (this.loadingCounter != counter) {
16670
- return;
16671
- }
16672
- this.processQueue();
16673
- }
16674
- }
16675
- /**
16676
- * Monitors the Cesium viewer and updates the historic data filter values.
16677
- * If there is no historic attr set, this will do nothing.
16678
- */
16679
- viewerDateTimeSub() {
16680
- if (!this.historic || this.viewerDateTimeChangeRemoval) {
16681
- return;
16682
- }
16683
- // This is multiplied by the speed of animation to figure
16684
- // out how many animation "ticks" before we allow an update.
16685
- const INTERVAL_WHILE_ANIMATING = 2.5 * 1000;
16686
- const INTERVAL_WHILE_NOT_ANIMATING = 1000;
16687
- let lastUpdateTime = null;
16688
- let delayQueue = new DelayQueue(() => {
16689
- try {
16690
- // If the timeline is animating then we'll wait longer to update.
16691
- if (this.viewer.clock.shouldAnimate && lastUpdateTime) {
16692
- if (Math.abs(new Date().getTime() - lastUpdateTime) < INTERVAL_WHILE_ANIMATING) {
16693
- return;
16890
+ // Only mark as fetched when ALL pages are done.
16891
+ // Known issue where external sources may return less than page size.
16892
+ // Right now we're making it as fetched as we're siding with the majority use-case.
16893
+ if (
16894
+ // API explicity says no more pages.
16895
+ response.nextPage === false ||
16896
+ // API didn't explicity say anything so we guess based on size of response.
16897
+ (response.nextPage == null &&
16898
+ (entities.length <= 0 || entities.length < PAGE_SIZE))) {
16899
+ curCell.Fetched = true;
16900
+ curCell.Fetching = false;
16901
+ (_d = this.onScanUpdate) === null || _d === void 0 ? void 0 : _d.Trigger(cells);
16902
+ continue;
16903
+ }
16904
+ // Checking to make sure it's not just the same batch over and over again.
16905
+ if (entities.length > 0) {
16906
+ const first = (_f = (_e = entities[0]) === null || _e === void 0 ? void 0 : _e.Bruce) === null || _f === void 0 ? void 0 : _f.ID;
16907
+ const last = (_h = (_g = entities[entities.length - 1]) === null || _g === void 0 ? void 0 : _g.Bruce) === null || _h === void 0 ? void 0 : _h.ID;
16908
+ if (prevFirstId == first && prevLastId == last) {
16909
+ prevTicks += 1;
16910
+ if (prevTicks > 3) {
16911
+ break;
16912
+ }
16913
+ }
16914
+ else {
16915
+ prevFirstId = first;
16916
+ prevLastId = last;
16917
+ prevTicks = 0;
16918
+ }
16694
16919
  }
16920
+ // Using URL if specified.
16921
+ // If not then we'll just manually paginate.
16922
+ curCell.FetchURL = response.nextPageUrl;
16923
+ curCell.FetchPageIndex++;
16924
+ // Request passed so let's assume it was server hiccup and refresh counts.
16925
+ retryAttempts = MAX_RETRY_ATTEMPTS;
16926
+ retryDelay = 0;
16695
16927
  }
16696
- const current = this.historicAttrDateTime;
16697
- this.updateHistoricDateTime();
16698
- if (current != this.historicAttrDateTime) {
16699
- this.emitHistoricData();
16928
+ catch (e) {
16929
+ // Ignore abort errors.
16930
+ if (e && typeof e === "object" && e.name == "AbortError") {
16931
+ // console.debug("Aborted entity-filter-getter request.");
16932
+ break;
16933
+ }
16934
+ console.error(e);
16935
+ if (this.getterLoopId != loopId) {
16936
+ break;
16937
+ }
16938
+ // Request failed so let's add a delay and try again soon.
16939
+ retryDelay += RETRY_DELAY_INCREMENT;
16940
+ retryAttempts -= 1;
16700
16941
  }
16942
+ await delay(REQUEST_PAGE_DELAY);
16701
16943
  }
16702
- catch (e) {
16703
- console.error(e);
16944
+ if (postedLoading) {
16945
+ this.postStatus({ msg: EStatus.Loading, revoking: true });
16704
16946
  }
16705
- }, INTERVAL_WHILE_NOT_ANIMATING);
16706
- let postUpdateRemoval = this.viewer.scene.postUpdate.addEventListener(() => {
16707
- if (delayQueue) {
16708
- delayQueue.Call();
16947
+ if (postedScanning) {
16948
+ this.postStatus({ msg: EStatus.Scanning, revoking: true });
16709
16949
  }
16950
+ })().then(() => {
16951
+ this.looping -= 1;
16952
+ }).catch(() => {
16953
+ this.looping -= 1;
16710
16954
  });
16711
- this.viewerDateTimeChangeRemoval = () => {
16712
- delayQueue === null || delayQueue === void 0 ? void 0 : delayQueue.Dispose();
16713
- postUpdateRemoval === null || postUpdateRemoval === void 0 ? void 0 : postUpdateRemoval();
16714
- delayQueue = null;
16715
- postUpdateRemoval = null;
16716
- };
16717
- }
16718
- updateHistoricDateTime() {
16719
- if (!this.historic) {
16720
- this.historicAttrDateTime = null;
16721
- return;
16722
- }
16723
- const isChanged = (before, after) => {
16724
- if (before && !after) {
16725
- return true;
16726
- }
16727
- if (!before && after) {
16728
- return true;
16729
- }
16730
- // Change must be at least 0.1 seconds.
16731
- return Math.abs(before.getTime() - after.getTime()) > 100;
16732
- };
16733
- const oldDateTime = this.historicAttrDateTime ? new Date(this.historicAttrDateTime) : null;
16734
- const newDateTime = JulianDate.toDate(this.viewer.clock.currentTime);
16735
- if (isChanged(oldDateTime, newDateTime)) {
16736
- this.historicAttrDateTime = newDateTime.toISOString();
16737
- }
16738
- }
16739
- viewerDateTimeDispose() {
16740
- var _a;
16741
- (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
16742
- this.viewerDateTimeChangeRemoval = null;
16743
16955
  }
16744
16956
  /**
16745
16957
  * Gets the historic state of found Entities for the current date times and emits them.
16746
16958
  * Since geometry searches are tied to the base Entity, we don't have to re-scan the viewport.
16747
16959
  */
16748
16960
  emitHistoricData() {
16749
- if (this.disposed) {
16961
+ if (!this.GetMenuItems().length) {
16962
+ return;
16963
+ }
16964
+ let integrity = this.getIntegrityId();
16965
+ // Gathered ID does't match current one.
16966
+ if (this.gatheredIntegrity != integrity) {
16750
16967
  return;
16751
16968
  }
16752
16969
  const historicAttrDateTime = this.historicAttrDateTime;
@@ -16756,29 +16973,34 @@ var TilesetRenderEngine;
16756
16973
  this.historicRefreshAbortController = null;
16757
16974
  }
16758
16975
  (async () => {
16759
- var _a, _b;
16976
+ var _a;
16760
16977
  try {
16761
- const gatheredEntityIds = Object.keys(this.styledWithAttrEntityIds);
16762
16978
  // Loop through all IDs we've found and get their historic records.
16763
- for (let i = 0; i < gatheredEntityIds.length; i += SCAN_BATCH_SIZE) {
16764
- let batch = gatheredEntityIds.slice(i, i + SCAN_BATCH_SIZE);
16979
+ for (let i = 0; i < this.gatheredEntityIds.length; i += SCAN_BATCH_SIZE) {
16980
+ let batch = this.gatheredEntityIds.slice(i, i + SCAN_BATCH_SIZE);
16765
16981
  if (!batch.length) {
16766
16982
  break;
16767
16983
  }
16768
16984
  // Controller we can use to abort the request when a new loop starts.
16769
16985
  const controller = this.historicRefreshAbortController = new AbortController();
16770
16986
  let entities = [];
16771
- await SharedGetters.Queue.Run("Refreshing historic data in Menu Item that loads a Tileset.", async () => {
16772
- if (controller.signal.aborted || this.disposed) {
16987
+ await SharedGetters.Queue.Run("Refreshing historic data in Menu Item that loads Entity Type: " + this.typeIds, async () => {
16988
+ var _a;
16989
+ if (controller.signal.aborted || !((_a = this.GetMenuItems()) === null || _a === void 0 ? void 0 : _a.length)) {
16773
16990
  return;
16774
16991
  }
16775
16992
  entities = (await Entity$1.GetList({
16776
16993
  api: this.api,
16777
16994
  scenario: this.scenario,
16995
+ historicKey: this.historicAttrKey,
16778
16996
  historicPoint: historicAttrDateTime,
16997
+ schemaId: this.schemaId,
16779
16998
  filter: {
16780
16999
  pageSize: batch.length,
16781
17000
  pageIndex: 0,
17001
+ entityTypeId: this.typeIds,
17002
+ layerIds: this.tagIds,
17003
+ layerIdsOperator: "in",
16782
17004
  sortOrder: Api.ESortOrder.Asc,
16783
17005
  entityTypeConditions: {
16784
17006
  "ID": {
@@ -16786,6 +17008,7 @@ var TilesetRenderEngine;
16786
17008
  }
16787
17009
  },
16788
17010
  },
17011
+ viaCdn: this.viaCdn,
16789
17012
  migrated: true,
16790
17013
  // If we're taking 5+ minutes to make a query, it's a dud.
16791
17014
  // This is a timeout imposed on our DB and not external sources.
@@ -16803,331 +17026,211 @@ var TilesetRenderEngine;
16803
17026
  if (this.historicAttrDateTime != historicAttrDateTime) {
16804
17027
  break;
16805
17028
  }
16806
- // Gather all Tag IDs from found Entities.
16807
- let tagIds = [];
16808
- for (let i = 0; i < entities.length; i++) {
16809
- const entity = entities[i];
16810
- if ((_b = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a["Layer.ID"]) === null || _b === void 0 ? void 0 : _b.length) {
16811
- const eTagIds = entity.Bruce["Layer.ID"];
16812
- for (let j = 0; j < eTagIds.length; j++) {
16813
- tagIds.push(eTagIds[j]);
16814
- }
16815
- }
16816
- }
16817
- // Turn into unique list.
16818
- if (tagIds.length) {
16819
- tagIds = tagIds.filter((v, i, a) => a.indexOf(v) === i);
16820
- }
16821
- // Gather records.
16822
- let tags = [];
16823
- if (tagIds.length) {
16824
- tags = (await EntityTag.GetListByIds({
16825
- tagIds: tagIds,
16826
- api: this.api
16827
- })).tags;
16828
- }
16829
- // Date changed.
16830
- if (this.historicAttrDateTime != historicAttrDateTime) {
17029
+ // Integrity changed.
17030
+ if (this.gatheredIntegrity != integrity) {
16831
17031
  break;
16832
17032
  }
16833
- // Style the Entities.
16834
- for (let i = 0; i < entities.length; i++) {
16835
- const entity = entities[i];
16836
- const feature = this.getEntityRego(entity.Bruce.ID);
16837
- if (feature) {
16838
- const eTags = tags.filter(t => { var _a, _b, _c, _d; return ((_b = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a["Layer.ID"]) === null || _b === void 0 ? void 0 : _b.length) && ((_d = (_c = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _c === void 0 ? void 0 : _c["Layer.ID"]) === null || _d === void 0 ? void 0 : _d.includes(t.ID)); });
16839
- this.styleTilesetFeatureFullData(feature, entity, eTags);
16840
- }
17033
+ // No Menu Items.
17034
+ if (!this.GetMenuItems().length) {
17035
+ break;
16841
17036
  }
17037
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(entities);
16842
17038
  }
16843
17039
  }
16844
17040
  catch (e) {
16845
17041
  // Ignore abort errors.
16846
17042
  if (e && typeof e === "object" && e.name == "AbortError") {
17043
+ // console.debug("Aborted entity-filter-getter historic refresh request.");
16847
17044
  return;
16848
17045
  }
16849
17046
  console.error(e);
16850
17047
  }
16851
17048
  })();
16852
17049
  }
16853
- getEntityTypeIdsFromModelTree(modelTree) {
16854
- const entityTypeIds = [];
16855
- this.digEntityTypeIdsFromModelTreeBranch(modelTree, entityTypeIds);
16856
- return entityTypeIds;
16857
- }
16858
- digEntityTypeIdsFromModelTreeBranch(branch, arr) {
16859
- if (branch) {
16860
- // Does not yet include this entity type id.
16861
- if (branch.typeId && !arr.includes(branch.typeId)) {
16862
- arr.push(branch.typeId);
16863
- }
16864
- if (branch.children) {
16865
- for (let i = 0; i < branch.children.length; i++) {
16866
- let child = branch.children[i];
16867
- this.digEntityTypeIdsFromModelTreeBranch(child, arr);
16868
- }
16869
- }
16870
- }
17050
+ }
17051
+ EntityFilterGetter.Getter = Getter;
17052
+ })(EntityFilterGetter || (EntityFilterGetter = {}));
17053
+
17054
+ function createFilterGetterCacheKey(params) {
17055
+ let cacheKey = "";
17056
+ cacheKey += params.api.GetBaseUrl();
17057
+ // Not including Type ID in the cache key now.
17058
+ // This allows us to re-use the same getter between Entity Types.
17059
+ // cacheKey += params.typeId;
17060
+ cacheKey += params.batchSize;
17061
+ cacheKey += String(params.cdn);
17062
+ cacheKey += params.schemaId ? params.schemaId : "";
17063
+ cacheKey += JSON.stringify(params.tagIds ? params.tagIds : []);
17064
+ cacheKey += params.historicAttrKey ? params.historicAttrKey : "";
17065
+ cacheKey += params.historic ? "true" : "false";
17066
+ cacheKey += params.scenario ? params.scenario : 0;
17067
+ if (params.historicAttrKey) {
17068
+ cacheKey += params.historicInterpolation ? "true" : "false";
17069
+ }
17070
+ // This could potentially crash, but if it crashes here then it would crash during API request anyways.
17071
+ cacheKey += JSON.stringify(params.attrFilter ? params.attrFilter : {});
17072
+ return cacheKey;
17073
+ }
17074
+ var SharedGetters;
17075
+ (function (SharedGetters) {
17076
+ class Cache {
17077
+ constructor() {
17078
+ this.data = {};
16871
17079
  }
16872
- async processTilesetFeatureCheckQueue() {
16873
- const BATCH_CHECK_SIZE = 1000;
16874
- return new Promise((res) => {
16875
- clearInterval(this.queueCheckInterval);
16876
- this.queueCheckInterval = setInterval(() => {
16877
- if (this.disposed) {
16878
- clearInterval(this.queueCheckInterval);
16879
- res();
16880
- return;
16881
- }
16882
- // Construct batch for loaded styles.
16883
- let batch = [];
16884
- if (this.styleMappingLoaded) {
16885
- batch = this.recordCheckQueue.splice(0, BATCH_CHECK_SIZE);
16886
- }
16887
- else {
16888
- for (let i = 0; i < this.recordCheckQueue.length; i++) {
16889
- const entityId = this.recordCheckQueue[i];
16890
- const entity = this.getEntityRego(entityId);
16891
- if (entity) {
16892
- const entityTypeId = entity.entityTypeId;
16893
- if (entityTypeId) {
16894
- if (this.styleMappingsLoaded[entityTypeId] == true) {
16895
- batch.push(entityId);
16896
- }
16897
- }
17080
+ GetOrCreateFilterGetter(params) {
17081
+ params.cdn = Boolean(params.cdn);
17082
+ const cacheKey = createFilterGetterCacheKey(params);
17083
+ let getter = this.data[cacheKey];
17084
+ if (!getter) {
17085
+ getter = new EntityFilterGetter.Getter({
17086
+ api: params.api,
17087
+ viewer: params.viewer,
17088
+ viewPort: params.monitor,
17089
+ typeIds: !params.typeId ? null : typeof params.typeId == "string" ? [params.typeId] : params.typeId,
17090
+ schemaId: params.schemaId,
17091
+ batchSize: params.batchSize,
17092
+ attrFilter: params.attrFilter,
17093
+ historic: params.historic,
17094
+ historicAttrKey: params.historicAttrKey,
17095
+ historicInterpolation: params.historicInterpolation,
17096
+ viaCdn: params.cdn,
17097
+ scenario: params.scenario,
17098
+ });
17099
+ this.data[cacheKey] = getter;
17100
+ /**
17101
+ * Debug option.
17102
+ * This will display the bounds of the cells that are being fetched.
17103
+ */
17104
+ if (params.viewer && params.debugShowBounds) {
17105
+ // Cell id -> entity.
17106
+ const cellCache = {};
17107
+ const cellPrefix = ObjectUtils.UId(10) + "_";
17108
+ getter.OnScanUpdate.Subscribe((cells) => {
17109
+ if (window.ON_SCAN_UPDATE_PAUSED == true) {
17110
+ return;
17111
+ }
17112
+ const curCellIds = [];
17113
+ let fetchingCellId = null;
17114
+ const fetchedCells = {};
17115
+ cells.forEach((cell) => {
17116
+ var _a;
17117
+ const bounds = cell.GetBounds();
17118
+ const id = cellPrefix + bounds.east + "_" + bounds.north + "_" + bounds.south + "_" + bounds.west;
17119
+ curCellIds.push(id);
17120
+ fetchedCells[id] = cell.IsFetched();
17121
+ fetchingCellId = cell.Fetching ? id : fetchingCellId;
17122
+ let material = null;
17123
+ if (fetchedCells[id]) {
17124
+ material = Color.LIGHTGREEN.clone().withAlpha(0.3);
16898
17125
  }
16899
- if (batch.length >= BATCH_CHECK_SIZE) {
16900
- break;
17126
+ else if (fetchingCellId == id) {
17127
+ material = Color.LIGHTBLUE.clone().withAlpha(0.3);
16901
17128
  }
16902
- }
16903
- for (let i = 0; i < batch.length; i++) {
16904
- const entityId = batch[i];
16905
- const index = this.recordCheckQueue.findIndex(x => x == entityId);
16906
- if (index > -1) {
16907
- this.recordCheckQueue.splice(index, 1);
17129
+ else {
17130
+ material = Color.GOLD.clone().withAlpha(0.3);
16908
17131
  }
16909
- }
16910
- }
16911
- if (batch.length) {
16912
- for (let i = 0; i < batch.length; i++) {
16913
- const entityId = batch[i];
16914
- const entity = this.getEntityRego(entityId);
16915
- if (entity) {
16916
- this.queueTilesetFeatureStyle(entity, false);
17132
+ if (cellCache[id]) {
17133
+ const rect = (_a = cellCache[id]) === null || _a === void 0 ? void 0 : _a.rectangle;
17134
+ if (rect) {
17135
+ rect.material = material;
17136
+ }
17137
+ return;
16917
17138
  }
16918
- }
16919
- this.viewer.scene.requestRender();
16920
- }
16921
- else {
16922
- clearInterval(this.queueCheckInterval);
16923
- res();
16924
- }
16925
- }, 50);
16926
- });
16927
- }
16928
- queueTilesetFeatureStyle(entity, highPriority) {
16929
- // Add to the style dict if not already there.
16930
- // This helps us know the styling progress for things that have loaded in so far.
16931
- if (!this.styledEntityIds[entity.entityId]) {
16932
- this.styledEntityIds[entity.entityId] = null;
16933
- }
16934
- if (this.styleMappingLoaded || this.styleMappingsLoaded[entity.entityTypeId] == true) {
16935
- const needsData = this.getTilesetFeatureNeedsFullData(entity.entityId, entity.entityTypeId);
16936
- if (needsData) {
16937
- if (!this.recordLoadQueue.find(x => x == entity.entityId)) {
16938
- this.recordLoadQueue.push(entity.entityId);
16939
- }
16940
- }
16941
- else {
16942
- this.styleTilesetFeature(entity);
16943
- }
16944
- }
16945
- else {
16946
- if (highPriority) {
16947
- this.recordCheckQueue.unshift(entity.entityId);
16948
- }
16949
- else {
16950
- this.recordCheckQueue.push(entity.entityId);
16951
- }
16952
- }
16953
- }
16954
- styleTilesetFeature(entity) {
16955
- this.styleTilesetFeatureFullData(entity, null, []);
16956
- }
16957
- styleTilesetFeatureFullData(entity, data, tags) {
16958
- var _a, _b, _c, _d, _e, _f, _g, _h;
16959
- const visual = entity.visual;
16960
- if (!visual || !(visual instanceof Cesium3DTileFeature)) {
16961
- return;
16962
- }
16963
- const style = this.getTilesetFeatureStyle(entity.entityId, entity.entityTypeId);
16964
- const bColor = style && ((_a = style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor) ? Calculator.GetColor(style.modelStyle.fillColor, data, tags) : null;
16965
- let cColor = null;
16966
- if (bColor == null) {
16967
- cColor = Color.WHITE;
16968
- }
16969
- else {
16970
- cColor = colorToCColor$3(bColor);
16971
- }
16972
- const override = this.styledEntityIds[entity.entityId] == true;
16973
- CesiumEntityStyler.SetDefaultColor({
16974
- color: cColor,
16975
- entity: visual,
16976
- viewer: this.viewer,
16977
- override: override
16978
- });
16979
- this.styledEntityIds[entity.entityId] = true;
16980
- if (data) {
16981
- this.styledWithAttrEntityIds[entity.entityId] = true;
16982
- }
16983
- else {
16984
- delete this.styledWithAttrEntityIds[entity.entityId];
16985
- }
16986
- this._styleProgressQueue.Call();
16987
- // Since we only need to update it for scenarios right now.
16988
- // We'll avoid doing it if not needed, eg: first render and no scenario (same state as default).
16989
- if (this.shouldUpdateRegoStates && (override || ((_b = data === null || data === void 0 ? void 0 : data.Bruce) === null || _b === void 0 ? void 0 : _b.Scenario) || this.historic)) {
16990
- // Update the Entity's rego state.
16991
- let changed = false;
16992
- // Changed scenario.
16993
- if (entity.scenario != ((_c = data === null || data === void 0 ? void 0 : data.Bruce) === null || _c === void 0 ? void 0 : _c.Scenario)) {
16994
- entity.scenario = (_d = data === null || data === void 0 ? void 0 : data.Bruce) === null || _d === void 0 ? void 0 : _d.Scenario;
16995
- changed = true;
16996
- }
16997
- // Changed historic.
16998
- if ((data && isHistoricMetadataChanged(entity, data)) || (!data && ((_e = entity.historicLayers) === null || _e === void 0 ? void 0 : _e.length) && !((_g = (_f = data.Bruce) === null || _f === void 0 ? void 0 : _f.HistoricLayers) === null || _g === void 0 ? void 0 : _g.length))) {
16999
- entity.historicLayers = (_h = data === null || data === void 0 ? void 0 : data.Bruce) === null || _h === void 0 ? void 0 : _h.HistoricLayers;
17000
- changed = true;
17001
- }
17002
- // Something changed, trigger a rego update.
17003
- // This lets UI know when the rego has changed.
17004
- if (changed) {
17005
- this.register.OnUpdate.Trigger({
17006
- type: VisualsRegister.EVisualUpdateType.Update,
17007
- entityId: entity.entityId,
17008
- rego: entity
17139
+ const rect = new Rectangle(Math$1.toRadians(bounds.west), Math$1.toRadians(bounds.south), Math$1.toRadians(bounds.east), Math$1.toRadians(bounds.north));
17140
+ const entity = params.viewer.entities.add(new Entity({
17141
+ id: id,
17142
+ rectangle: {
17143
+ coordinates: rect,
17144
+ fill: true,
17145
+ //material: Cesium.Color.fromRandom().withAlpha(0.3),
17146
+ material: material,
17147
+ zIndex: 0
17148
+ },
17149
+ // point: {
17150
+ // pixelSize: 15,
17151
+ // outlineColor: Cesium.Color.WHITE,
17152
+ // outlineWidth: 2,
17153
+ // color: Cesium.Color.fromRandom().withAlpha(0.5),
17154
+ // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
17155
+ // },
17156
+ position: Cartesian3.fromRadians((rect.east + rect.west) / 2, (rect.north + rect.south) / 2)
17157
+ }));
17158
+ cellCache[id] = entity;
17159
+ });
17160
+ Object.keys(cellCache).forEach((id) => {
17161
+ if (curCellIds.indexOf(id) == -1) {
17162
+ const entity = cellCache[id];
17163
+ if (entity && params.viewer.entities.contains(entity)) {
17164
+ params.viewer.entities.remove(entity);
17165
+ }
17166
+ delete cellCache[id];
17167
+ }
17168
+ });
17009
17169
  });
17010
17170
  }
17011
17171
  }
17012
- }
17013
- getTilesetFeatureStyle(entityId, entityTypeId) {
17014
- var _a, _b, _c, _d;
17015
- // Locate what style is applicable to the feature.
17016
- let style = null;
17017
- if (entityTypeId) {
17018
- style = ((_b = (_a = this.styleMapping.find(x => x.EntityTypeID == entityTypeId)) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.Settings);
17019
- }
17020
- if (!style) {
17021
- style = ((_c = this.fallbackStyle) === null || _c === void 0 ? void 0 : _c.Settings);
17022
- }
17023
- if (!style || !((_d = style === null || style === void 0 ? void 0 : style.modelStyle) === null || _d === void 0 ? void 0 : _d.customize)) {
17024
- return null;
17025
- }
17026
- return style;
17027
- }
17028
- getTilesetFeatureNeedsFullData(entityId, entityTypeId) {
17029
- var _a;
17030
- if (this.historic) {
17031
- // Unfortunately when we are working with historic we have to request the entity.
17032
- // This is because we need to know if a record exists at the point in time.
17033
- return true;
17034
- }
17035
- const style = this.getTilesetFeatureStyle(entityId, entityTypeId);
17036
- if (!style) {
17037
- return false;
17038
- }
17039
- const fill = (_a = style === null || style === void 0 ? void 0 : style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor;
17040
- if (!fill || fill.length <= 0) {
17041
- return false;
17042
- }
17043
- return fill[0].type != 0;
17172
+ return getter;
17044
17173
  }
17045
17174
  }
17046
- TilesetRenderEngine.Styler = Styler;
17047
- /**
17048
- * The maximum memory in MB that can be used by all tilesets.
17049
- * This is distributed evenly between all loaded tilesets.
17050
- */
17051
- TilesetRenderEngine.MAX_TILESET_MEMORY = 1024;
17175
+ SharedGetters.Cache = Cache;
17052
17176
  /**
17053
- * Watches tilesets in the viewer.
17054
- * This will regulate their max memory param.
17055
- * As more get watched their memory will be reduced.
17177
+ * To avoid performing multiple expensive queries at the same time,
17178
+ * we'll have the getters work through a queue of requests.
17056
17179
  */
17057
- class MemoryWatcher {
17058
- constructor(viewer) {
17059
- this.watched = [];
17060
- this.viewer = viewer;
17061
- }
17062
- distributeMemory() {
17063
- this.clean();
17064
- // Total 1gb as default.
17065
- // We'll need to allow user to change this somehow.
17066
- const maxMemory = TilesetRenderEngine.MAX_TILESET_MEMORY;
17067
- // Minimum memory in MB per tileset.
17068
- const minMemory = 80;
17069
- const totalPerTileset = Math.max(this.watched.length ? maxMemory / this.watched.length : maxMemory, minMemory);
17070
- this.watched.forEach(x => {
17071
- // Newer Cesium killed this property.
17072
- // TODO: Check if it's needed then.
17073
- x["maximumMemoryUsage"] = totalPerTileset;
17074
- });
17075
- }
17076
- destroy() {
17077
- this.watched = [];
17078
- }
17180
+ let Queue;
17181
+ (function (Queue) {
17182
+ const queue = [];
17183
+ let isProcessing = false;
17079
17184
  /**
17080
- * Remove all dead tilesets.
17185
+ * Called to run a provided function.
17186
+ * When the function is complete, the next function in the queue will be called.
17187
+ * This function will resolve when the provided function is complete.
17188
+ * If the provided function crashes, the error bubbles up to the caller.
17189
+ * @param name
17190
+ * @param call
17081
17191
  */
17082
- clean() {
17083
- // Remove all dead tilesets.
17084
- this.watched = this.watched.filter(x => isAlive$2(this.viewer, x));
17085
- // Check if viewer is destroyed.
17086
- if (!this.viewer || this.viewer.isDestroyed()) {
17087
- this.destroy();
17088
- }
17089
- }
17090
- Watch(tileset) {
17091
- if (!tileset) {
17092
- return;
17093
- }
17094
- if (!tileset[WATCH_KEY]) {
17095
- tileset[WATCH_KEY] = ObjectUtils.UId();
17096
- }
17097
- const index = this.watched.findIndex(x => x[WATCH_KEY] === tileset[WATCH_KEY]);
17098
- if (index >= 0) {
17099
- return;
17100
- }
17101
- this.watched.push(tileset);
17102
- this.distributeMemory();
17192
+ function Run(name, call) {
17193
+ // console.debug(`Queueing: ${name}`);
17194
+ return new Promise((resolve, reject) => {
17195
+ queue.push(async () => {
17196
+ try {
17197
+ // console.debug(`Running: ${name}`);
17198
+ const result = await call();
17199
+ resolve(result);
17200
+ }
17201
+ catch (error) {
17202
+ reject(error);
17203
+ }
17204
+ finally {
17205
+ // Process the next function in the queue.
17206
+ queue.shift();
17207
+ // If there are more functions in the queue, process the next one.
17208
+ if (queue.length > 0) {
17209
+ processNext();
17210
+ }
17211
+ // No more items to process.
17212
+ else {
17213
+ isProcessing = false;
17214
+ }
17215
+ }
17216
+ });
17217
+ if (!isProcessing) {
17218
+ // If not currently processing, start processing the queue.
17219
+ isProcessing = true;
17220
+ processNext();
17221
+ }
17222
+ });
17103
17223
  }
17104
- Unwatch(tileset) {
17105
- if (!tileset) {
17106
- return;
17107
- }
17108
- if (!tileset[WATCH_KEY]) {
17109
- tileset[WATCH_KEY] = ObjectUtils.UId();
17110
- }
17111
- const index = this.watched.findIndex(x => (x === null || x === void 0 ? void 0 : x[WATCH_KEY]) === tileset[WATCH_KEY]);
17112
- if (index > -1) {
17113
- this.watched.splice(index, 1);
17224
+ Queue.Run = Run;
17225
+ async function processNext() {
17226
+ const nextCall = queue[0];
17227
+ if (nextCall) {
17228
+ // Call the next function in the queue.
17229
+ await nextCall();
17114
17230
  }
17115
- this.distributeMemory();
17116
- }
17117
- }
17118
- TilesetRenderEngine.MemoryWatcher = MemoryWatcher;
17119
- function GetMemoryWatcher(viewer) {
17120
- // If viewer is dead return nothing.
17121
- if (!viewer || viewer.isDestroyed()) {
17122
- return null;
17123
- }
17124
- if (!viewer[VIEWER_WATCH_KEY]) {
17125
- viewer[VIEWER_WATCH_KEY] = new MemoryWatcher(viewer);
17126
17231
  }
17127
- return viewer[VIEWER_WATCH_KEY];
17128
- }
17129
- TilesetRenderEngine.GetMemoryWatcher = GetMemoryWatcher;
17130
- })(TilesetRenderEngine || (TilesetRenderEngine = {}));
17232
+ })(Queue = SharedGetters.Queue || (SharedGetters.Queue = {}));
17233
+ })(SharedGetters || (SharedGetters = {}));
17131
17234
 
17132
17235
  /**
17133
17236
  * Manager for rendering CAD tilesets.
@@ -17353,6 +17456,7 @@ var TilesetCadRenderManager;
17353
17456
  if (!content) {
17354
17457
  return;
17355
17458
  }
17459
+ const regosToQueue = new Map();
17356
17460
  for (let i = 0; i < content.featuresLength; i++) {
17357
17461
  const feature = content.getFeature(i);
17358
17462
  let rego = this.mapTilesetFeature(feature, load);
@@ -17361,9 +17465,10 @@ var TilesetCadRenderManager;
17361
17465
  feature.show = true;
17362
17466
  continue;
17363
17467
  }
17364
- if (this.styler) {
17365
- this.styler.QueueEntities([rego]);
17366
- }
17468
+ regosToQueue.set(rego.entityId, rego);
17469
+ }
17470
+ if (this.styler && regosToQueue.size) {
17471
+ this.styler.QueueEntities(Array.from(regosToQueue.values()));
17367
17472
  }
17368
17473
  this.viewer.scene.requestRender();
17369
17474
  }
@@ -17636,6 +17741,9 @@ var TilesetCadRenderManager;
17636
17741
  const api = this.getters.GetBruceApi({
17637
17742
  accountId: accountId
17638
17743
  });
17744
+ // Time padding in milliseconds (15 seconds).
17745
+ // Helps account for desync between client and server.
17746
+ const TIME_PADDING_MS = 15000;
17639
17747
  // 'start-stop' time string that maps to a pending request.
17640
17748
  // Helps us avoid repeated requests that are the same.
17641
17749
  const pendingRequests = new Map();
@@ -17679,29 +17787,32 @@ var TilesetCadRenderManager;
17679
17787
  * @returns
17680
17788
  */
17681
17789
  const checkTimelineRange = async () => {
17682
- const minDateTime = this.viewer.clock.startTime.toString();
17683
- const maxDateTime = this.viewer.clock.stopTime.toString();
17790
+ // Current timeline range.
17791
+ const minDateTime = new Date(this.viewer.clock.startTime.toString()).getTime();
17792
+ const maxDateTime = new Date(this.viewer.clock.stopTime.toString()).getTime();
17793
+ // What we have loaded.
17794
+ const range = this.historicAnimation.getDateRange();
17795
+ const foundMinDateTime = (range === null || range === void 0 ? void 0 : range.minDate) ? range.minDate.getTime() : null;
17796
+ const foundMaxDateTime = (range === null || range === void 0 ? void 0 : range.maxDate) ? range.maxDate.getTime() : null;
17684
17797
  // See if the current range is within the range we already have.
17685
17798
  if (this.historicPosses.length > 0 &&
17686
- this.historicPossesMinDateTime &&
17687
- this.historicPossesMaxDateTime &&
17688
- minDateTime >= this.historicPossesMinDateTime &&
17689
- maxDateTime <= this.historicPossesMaxDateTime) {
17799
+ foundMinDateTime &&
17800
+ foundMaxDateTime &&
17801
+ minDateTime >= foundMinDateTime &&
17802
+ maxDateTime <= foundMaxDateTime) {
17690
17803
  return;
17691
17804
  }
17692
17805
  // See if the requested range is before or after the range we have.
17693
- const fetchBefore = !this.historicPossesMinDateTime || minDateTime < this.historicPossesMinDateTime;
17694
- const fetchAfter = !this.historicPossesMaxDateTime || maxDateTime > this.historicPossesMaxDateTime;
17806
+ const fetchBefore = !foundMinDateTime || minDateTime < foundMinDateTime;
17807
+ const fetchAfter = !foundMaxDateTime || maxDateTime > foundMaxDateTime;
17695
17808
  if (!fetchBefore && !fetchAfter) {
17696
17809
  // Already have the data we need.
17697
17810
  return;
17698
17811
  }
17699
17812
  // No known range so we need to fetch the whole thing.
17700
- if (!this.historicPossesMinDateTime || !this.historicPossesMaxDateTime) {
17701
- const startTmp = JulianDate.toDate(this.viewer.clock.startTime);
17702
- const stopTmp = JulianDate.toDate(this.viewer.clock.stopTime);
17703
- const startStr = new Date(startTmp.getTime() - 1000).toISOString();
17704
- const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
17813
+ if (!foundMinDateTime || !foundMaxDateTime) {
17814
+ const startStr = new Date(minDateTime - TIME_PADDING_MS).toISOString();
17815
+ const stopStr = new Date(maxDateTime + TIME_PADDING_MS).toISOString();
17705
17816
  const newPositions = await getPossesForRange(startStr, stopStr);
17706
17817
  if (this.disposed) {
17707
17818
  return;
@@ -17709,22 +17820,17 @@ var TilesetCadRenderManager;
17709
17820
  if (this.historicAnimation && this.historicAnimation.addPositions) {
17710
17821
  this.historicAnimation.addPositions(newPositions);
17711
17822
  }
17712
- this.historicPossesMinDateTime = minDateTime;
17713
- this.historicPossesMaxDateTime = maxDateTime;
17714
17823
  }
17715
17824
  else {
17716
17825
  // The data we want is before the range we've currently loaded.
17717
17826
  if (fetchBefore) {
17718
17827
  // Calculate the missing difference and request it.
17719
- const startTmp = JulianDate.toDate(this.viewer.clock.startTime);
17720
- const stopTmp = new Date(this.historicPossesMinDateTime);
17721
- const startStr = new Date(startTmp.getTime() - 1000).toISOString();
17722
- const stopStr = stopTmp.toISOString();
17723
- getPossesForRange(startStr, stopStr).then(newPositions => {
17828
+ const startStr = new Date(minDateTime - TIME_PADDING_MS).toISOString();
17829
+ const stopStr = new Date(foundMinDateTime + TIME_PADDING_MS).toISOString();
17830
+ getPossesForRange(startStr, stopStr).then((newPositions) => {
17724
17831
  if (this.disposed) {
17725
17832
  return;
17726
17833
  }
17727
- this.historicPossesMinDateTime = minDateTime;
17728
17834
  if (this.historicAnimation && this.historicAnimation.addPositions) {
17729
17835
  this.historicAnimation.addPositions(newPositions);
17730
17836
  }
@@ -17733,15 +17839,12 @@ var TilesetCadRenderManager;
17733
17839
  // The data we want is after the range we've currently loaded.
17734
17840
  if (fetchAfter) {
17735
17841
  // Calculate the missing difference and request it.
17736
- const startTmp = new Date(this.historicPossesMaxDateTime);
17737
- const stopTmp = JulianDate.toDate(this.viewer.clock.stopTime);
17738
- const startStr = startTmp.toISOString();
17739
- const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
17740
- getPossesForRange(startStr, stopStr).then(newPositions => {
17842
+ const startStr = new Date(foundMaxDateTime - TIME_PADDING_MS).toISOString();
17843
+ const stopStr = new Date(maxDateTime + TIME_PADDING_MS).toISOString();
17844
+ getPossesForRange(startStr, stopStr).then((newPositions) => {
17741
17845
  if (this.disposed) {
17742
17846
  return;
17743
17847
  }
17744
- this.historicPossesMaxDateTime = maxDateTime;
17745
17848
  if (this.historicAnimation && this.historicAnimation.addPositions) {
17746
17849
  this.historicAnimation.addPositions(newPositions);
17747
17850
  }
@@ -17755,8 +17858,6 @@ var TilesetCadRenderManager;
17755
17858
  * @returns
17756
17859
  */
17757
17860
  const getInitialPosses = async () => {
17758
- const minDateTime = this.viewer.clock.startTime.toString();
17759
- const maxDateTime = this.viewer.clock.stopTime.toString();
17760
17861
  this.historicPossesLoadingProm = new Promise(async (res) => {
17761
17862
  try {
17762
17863
  if (this.disposed) {
@@ -17773,8 +17874,6 @@ var TilesetCadRenderManager;
17773
17874
  return;
17774
17875
  }
17775
17876
  this.historicPosses = positions;
17776
- this.historicPossesMinDateTime = minDateTime;
17777
- this.historicPossesMaxDateTime = maxDateTime;
17778
17877
  res(positions);
17779
17878
  }
17780
17879
  catch (e) {
@@ -17800,6 +17899,13 @@ var TilesetCadRenderManager;
17800
17899
  if (this.disposed) {
17801
17900
  return;
17802
17901
  }
17902
+ // Reverse heading .
17903
+ // Not sure if calc or model is the issue.
17904
+ // This is all for a single demo until we figure out how we configure assemblies for this anyways.
17905
+ heading = heading + 180;
17906
+ if (heading > 360) {
17907
+ heading = heading - 360;
17908
+ }
17803
17909
  // Jank code that hacks the position to be different.
17804
17910
  // This can mess up our panels so we'll need to disable editing until a better system is in place.
17805
17911
  const location = Cartographic.fromCartesian(pos3d);
@@ -18592,13 +18698,17 @@ var TilesetEntitiesRenderManager;
18592
18698
  if (!content) {
18593
18699
  return;
18594
18700
  }
18701
+ const regosToQueue = new Map();
18595
18702
  for (let i = 0; i < content.featuresLength; i++) {
18596
18703
  const feature = content.getFeature(i);
18597
18704
  const rego = this.mapTilesetFeature(feature);
18598
- if ((rego === null || rego === void 0 ? void 0 : rego.entityId) && this.styler) {
18599
- this.styler.QueueEntities([rego]);
18705
+ if (rego === null || rego === void 0 ? void 0 : rego.entityId) {
18706
+ regosToQueue.set(rego.entityId, rego);
18600
18707
  }
18601
18708
  }
18709
+ if (this.styler && regosToQueue.size) {
18710
+ this.styler.QueueEntities(Array.from(regosToQueue.values()));
18711
+ }
18602
18712
  this.viewer.scene.requestRender();
18603
18713
  }
18604
18714
  mapTilesetFeature(feature) {
@@ -19151,13 +19261,17 @@ var TilesetArbRenderManager;
19151
19261
  if (!content) {
19152
19262
  return;
19153
19263
  }
19264
+ const regosToQueue = new Map();
19154
19265
  for (let i = 0; i < content.featuresLength; i++) {
19155
19266
  const feature = content.getFeature(i);
19156
19267
  let rego = this.mapTilesetFeature(feature);
19157
- if ((rego === null || rego === void 0 ? void 0 : rego.entityId) && this.styler) {
19158
- this.styler.QueueEntities([rego]);
19268
+ if (rego === null || rego === void 0 ? void 0 : rego.entityId) {
19269
+ regosToQueue.set(rego.entityId, rego);
19159
19270
  }
19160
19271
  }
19272
+ if (this.styler && regosToQueue.size) {
19273
+ this.styler.QueueEntities(Array.from(regosToQueue.values()));
19274
+ }
19161
19275
  this.viewer.scene.requestRender();
19162
19276
  }
19163
19277
  mapTilesetFeature(feature) {
@@ -30644,7 +30758,7 @@ class WidgetViewBar extends Widget.AWidget {
30644
30758
  }
30645
30759
  }
30646
30760
 
30647
- const VERSION = "5.4.5";
30761
+ const VERSION = "5.4.7";
30648
30762
 
30649
30763
  export { VERSION, CesiumViewMonitor, ViewerUtils, ViewerEventTracker, MenuItemManager, isHistoricMetadataChanged, EntityRenderEngine, EntityRenderEnginePoint, EntityRenderEnginePolyline, EntityRenderEnginePolygon, EntityRenderEngineModel3d, MenuItemCreator, VisualsRegister, RenderManager, EntitiesIdsRenderManager, DataLabRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, TilesetCadRenderManager, TilesetArbRenderManager, TilesetEntitiesRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TilesetGooglePhotosRenderManager, DataSourceStaticKmlManager, GoogleSearchRenderManager, RelationsRenderManager, SharedGetters, CesiumParabola, EntityLabel, ViewRenderEngine, TileRenderEngine, TilesetRenderEngine, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_FORWARD_KEY, CESIUM_TIMELINE_LIVE_PADDING_BACKWARD_KEY, CESIUM_TIMELINE_INTERVAL_KEY, ViewUtils, DrawingUtils, MeasureUtils, EntityUtils, CesiumEntityStyler, CesiumAnimatedProperty, CesiumAnimatedInOut, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar, WidgetControlViewBar, WidgetControlViewBarSearch, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, WidgetLeftPanel, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks };
30650
30764
  //# sourceMappingURL=bruce-cesium.es5.js.map