bruce-cesium 5.4.0 → 5.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/bruce-cesium.es5.js +585 -449
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +587 -447
  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 +97 -45
  7. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  8. package/dist/lib/rendering/getters/batched-data-getter.js +7 -1
  9. package/dist/lib/rendering/getters/batched-data-getter.js.map +1 -1
  10. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +237 -357
  11. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  12. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +172 -37
  13. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  14. package/dist/lib/rendering/view-render-engine.js +4 -2
  15. package/dist/lib/rendering/view-render-engine.js.map +1 -1
  16. package/dist/lib/utils/view-utils.js +62 -2
  17. package/dist/lib/utils/view-utils.js.map +1 -1
  18. package/dist/types/bruce-cesium.d.ts +1 -1
  19. package/dist/types/rendering/cesium-animated-property.d.ts +24 -21
  20. package/dist/types/rendering/getters/batched-data-getter.d.ts +3 -1
  21. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +22 -7
  22. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +2 -2
  23. package/dist/types/utils/view-utils.d.ts +9 -0
  24. package/package.json +2 -2
@@ -1366,6 +1366,9 @@
1366
1366
  }
1367
1367
  CesiumAnimatedProperty.AnimatePositionSeries = AnimatePositionSeries;
1368
1368
  function GetSeriesPossesForHistoricEntity(viewer, heightRef, historic) {
1369
+ if (!historic || !historic.length) {
1370
+ return [];
1371
+ }
1369
1372
  const series = [];
1370
1373
  for (let i = 0; i < historic.length; i++) {
1371
1374
  const item = historic[i];
@@ -1404,25 +1407,6 @@
1404
1407
  return series;
1405
1408
  }
1406
1409
  CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity = GetSeriesPossesForHistoricEntity;
1407
- /**
1408
- * Animates a tileset position and heading from a series of positions over time.
1409
- * Unlike other animation functions that directly modify the tileset,
1410
- * this provides a callback with calculated values for the caller to apply.
1411
- *
1412
- * Example:
1413
- * ```
1414
- * const dispose = CesiumAnimatedProperty.AnimateTPositionSeries({
1415
- * viewer: viewer,
1416
- * posses: positionSeries,
1417
- * onUpdate: (position, heading) => {}
1418
- * });
1419
- *
1420
- * // To dispose:
1421
- * dispose();
1422
- * ```
1423
- * @param params Animation parameters
1424
- * @returns Function to stop the animation
1425
- */
1426
1410
  class AnimateTPositionSeries {
1427
1411
  constructor(params) {
1428
1412
  // Cache for calculated values.
@@ -1430,23 +1414,74 @@
1430
1414
  this.lastCalcPos3d = null;
1431
1415
  this.lastCalcHeading = null;
1432
1416
  this.removal = null;
1417
+ // Track the timeline range for which we have data.
1418
+ this.minDate = null;
1419
+ this.maxDate = null;
1420
+ // Map of positions by timestamp for quick lookup and deduplication.
1421
+ this.positionsMap = new Map();
1433
1422
  this.viewer = params.viewer;
1434
1423
  this.onUpdate = params.onUpdate;
1435
1424
  this.onDone = params.onDone;
1436
- // No positions to animate.
1437
- if (!params.posses || params.posses.length === 0) {
1438
- return;
1425
+ this.onRangeChangeCallback = params.onRangeChange;
1426
+ // Initialize the positions map
1427
+ this.positionsMap = new Map();
1428
+ // Add initial positions
1429
+ if (params.posses && params.posses.length > 0) {
1430
+ this.addPositions(params.posses);
1439
1431
  }
1440
- // Order positions by date.
1441
- const orderedPosses = [...params.posses].sort((a, b) => {
1442
- return a.dateTime.getTime() - b.dateTime.getTime();
1443
- });
1444
- // Process headings - if all are null or 0, assume all are null.
1445
- this.positions = this.processHeadings([...orderedPosses]);
1446
1432
  // Set up the animation loop.
1447
1433
  this.removal = this.viewer.scene.postUpdate.addEventListener(() => this.update());
1448
1434
  this.update();
1449
1435
  }
1436
+ /**
1437
+ * Add new positions to the animation.
1438
+ * Will deduplicate positions with the same timestamp and sort them.
1439
+ * @param newPositions Positions to add
1440
+ */
1441
+ addPositions(newPositions) {
1442
+ if (!newPositions || newPositions.length === 0) {
1443
+ return;
1444
+ }
1445
+ // Add new positions to our map, overwriting any existing positions with the same timestamp
1446
+ for (const pos of newPositions) {
1447
+ const key = pos.dateTime.toISOString();
1448
+ this.positionsMap.set(key, pos);
1449
+ }
1450
+ // Rebuild our sorted array from the map
1451
+ this.positions = Array.from(this.positionsMap.values()).sort((a, b) => {
1452
+ return a.dateTime.getTime() - b.dateTime.getTime();
1453
+ });
1454
+ // Process headings
1455
+ this.positions = this.processHeadings([...this.positions]);
1456
+ // Update our min/max dates
1457
+ if (this.positions.length > 0) {
1458
+ const first = this.positions[0];
1459
+ const last = this.positions[this.positions.length - 1];
1460
+ if (!this.minDate || first.dateTime < this.minDate) {
1461
+ this.minDate = first.dateTime;
1462
+ }
1463
+ if (!this.maxDate || last.dateTime > this.maxDate) {
1464
+ this.maxDate = last.dateTime;
1465
+ }
1466
+ }
1467
+ }
1468
+ /**
1469
+ * Get the current date range covered by our positions.
1470
+ */
1471
+ getDateRange() {
1472
+ return {
1473
+ minDate: this.minDate,
1474
+ maxDate: this.maxDate
1475
+ };
1476
+ }
1477
+ /**
1478
+ * Trigger the onRangeChange callback to request more data.
1479
+ */
1480
+ onRangeChange() {
1481
+ if (this.onRangeChangeCallback) {
1482
+ this.onRangeChangeCallback();
1483
+ }
1484
+ }
1450
1485
  /**
1451
1486
  * Stop the animation and call the onDone callback if provided.
1452
1487
  */
@@ -1460,9 +1495,12 @@
1460
1495
  }
1461
1496
  }
1462
1497
  /**
1463
- * Update function called on each frame.
1498
+ * Update function called on each render frame.
1464
1499
  */
1465
1500
  update() {
1501
+ if (!this.positions || this.positions.length === 0) {
1502
+ return;
1503
+ }
1466
1504
  let now = this.viewer.scene.lastRenderTime;
1467
1505
  if (!now) {
1468
1506
  now = this.viewer.clock.currentTime;
@@ -1504,11 +1542,19 @@
1504
1542
  return posses;
1505
1543
  }
1506
1544
  /**
1507
- * Calculate the position at the given time
1545
+ * Calculate the position at the given time.
1508
1546
  */
1509
1547
  calculatePosition(currentTimeMs) {
1510
1548
  const posses = this.positions;
1511
- // See if we're before the first position
1549
+ // No positions to interpolate
1550
+ if (!posses || posses.length === 0) {
1551
+ return {
1552
+ pos3d: new Cesium.Cartesian3(),
1553
+ indexLast: -1,
1554
+ indexNext: -1
1555
+ };
1556
+ }
1557
+ // See if we're before the first position.
1512
1558
  if (currentTimeMs <= posses[0].dateTime.getTime()) {
1513
1559
  return {
1514
1560
  pos3d: posses[0].pos3d,
@@ -1516,7 +1562,7 @@
1516
1562
  indexNext: 0
1517
1563
  };
1518
1564
  }
1519
- // See if we're after the last position
1565
+ // See if we're after the last position.
1520
1566
  if (currentTimeMs >= posses[posses.length - 1].dateTime.getTime()) {
1521
1567
  const lastIndex = posses.length - 1;
1522
1568
  return {
@@ -1525,35 +1571,41 @@
1525
1571
  indexNext: lastIndex
1526
1572
  };
1527
1573
  }
1528
- // Find the current position
1574
+ // Binary search to find the closest position.
1575
+ let start = 0;
1576
+ let end = posses.length - 1;
1529
1577
  let lastIndex = 0;
1530
- for (let i = 1; i < posses.length; i++) {
1531
- let pos = posses[i];
1532
- if (currentTimeMs >= pos.dateTime.getTime()) {
1533
- lastIndex = i;
1578
+ while (start <= end) {
1579
+ const mid = Math.floor((start + end) / 2);
1580
+ const midTime = posses[mid].dateTime.getTime();
1581
+ if (midTime <= currentTimeMs) {
1582
+ lastIndex = mid;
1583
+ start = mid + 1;
1534
1584
  }
1535
1585
  else {
1536
- break;
1586
+ end = mid - 1;
1537
1587
  }
1538
1588
  }
1539
1589
  const last = posses[lastIndex];
1540
- const next = posses[lastIndex + 1];
1541
- // If no next position, use the last one
1542
- if (!next) {
1590
+ const nextIndex = Math.min(lastIndex + 1, posses.length - 1);
1591
+ const next = posses[nextIndex];
1592
+ // If we found an exact match or there's no next position.
1593
+ if (currentTimeMs === last.dateTime.getTime() || lastIndex === nextIndex) {
1543
1594
  return {
1544
1595
  pos3d: last.pos3d,
1545
1596
  indexLast: lastIndex,
1546
1597
  indexNext: lastIndex
1547
1598
  };
1548
1599
  }
1549
- // Interpolate the position
1550
- const progress = (currentTimeMs - last.dateTime.getTime()) /
1551
- (next.dateTime.getTime() - last.dateTime.getTime());
1600
+ // Interpolate between the two closest positions.
1601
+ const lastTime = last.dateTime.getTime();
1602
+ const nextTime = next.dateTime.getTime();
1603
+ const progress = (currentTimeMs - lastTime) / (nextTime - lastTime);
1552
1604
  const interpolatedPos = Cesium.Cartesian3.lerp(last.pos3d, next.pos3d, progress, new Cesium.Cartesian3());
1553
1605
  return {
1554
1606
  pos3d: interpolatedPos,
1555
1607
  indexLast: lastIndex,
1556
- indexNext: lastIndex + 1
1608
+ indexNext: nextIndex
1557
1609
  };
1558
1610
  }
1559
1611
  /**
@@ -5225,6 +5277,12 @@
5225
5277
 
5226
5278
  const CESIUM_INSPECTOR_KEY = "_nextspace_inspector";
5227
5279
  const CESIUM_TIMELINE_KEY = "_nextspace_timeline";
5280
+ const CESIUM_TIMELINE_LIVE_KEY = "_nextspace_timeline_live";
5281
+ const CESIUM_TIMELINE_LIVE_PADDING_FORWARD_KEY = "_nextspace_timeline_live_padding_forward";
5282
+ const CESIUM_TIMELINE_LIVE_PADDING_BACKWARD_KEY = "_nextspace_timeline_live_padding_backward";
5283
+ const CESIUM_TIMELINE_INTERVAL_KEY = "_nextspace_timeline_interval";
5284
+ const DEFAULT_LIVE_PADDING_FORWARD_SECONDS = 5 * 60;
5285
+ const DEFAULT_LIVE_PADDING_BACKWARD_SECONDS = 25 * 60;
5228
5286
  (function (ViewUtils) {
5229
5287
  function GatherLegacyMapTiles(params) {
5230
5288
  const viewer = params.viewer;
@@ -5446,7 +5504,10 @@
5446
5504
  currentTimeIso: clock.currentTime.toString(),
5447
5505
  stopTime: clock.stopTime,
5448
5506
  stopTimeIso: clock.stopTime.toString(),
5449
- areCesiumValues: clock[CESIUM_TIMELINE_KEY] != true
5507
+ areCesiumValues: clock[CESIUM_TIMELINE_KEY] != true,
5508
+ isLive: clock[CESIUM_TIMELINE_LIVE_KEY] == true,
5509
+ livePaddingForwardSeconds: clock[CESIUM_TIMELINE_LIVE_PADDING_FORWARD_KEY] || DEFAULT_LIVE_PADDING_FORWARD_SECONDS,
5510
+ livePaddingBackwardSeconds: clock[CESIUM_TIMELINE_LIVE_PADDING_BACKWARD_KEY] || DEFAULT_LIVE_PADDING_BACKWARD_SECONDS
5450
5511
  };
5451
5512
  }
5452
5513
  ViewUtils.GetTimeDetails = GetTimeDetails;
@@ -5504,8 +5565,59 @@
5504
5565
  }
5505
5566
  viewer.scene.requestRender();
5506
5567
  clock[CESIUM_TIMELINE_KEY] = true;
5568
+ clock[CESIUM_TIMELINE_LIVE_KEY] = params.isLive == true;
5569
+ clock[CESIUM_TIMELINE_LIVE_PADDING_FORWARD_KEY] = params.livePaddingSeconds || DEFAULT_LIVE_PADDING_FORWARD_SECONDS;
5570
+ clock[CESIUM_TIMELINE_LIVE_PADDING_BACKWARD_KEY] = params.livePaddingSeconds || DEFAULT_LIVE_PADDING_BACKWARD_SECONDS;
5571
+ if (params.isLive) {
5572
+ startLive(viewer);
5573
+ }
5574
+ else {
5575
+ stopLive(viewer);
5576
+ }
5507
5577
  }
5508
5578
  ViewUtils.SetTimeDetails = SetTimeDetails;
5579
+ function startLive(viewer) {
5580
+ if (viewer[CESIUM_TIMELINE_INTERVAL_KEY]) {
5581
+ // Already live.
5582
+ return;
5583
+ }
5584
+ const doUpdate = () => {
5585
+ if (!viewer || viewer.isDestroyed() || !viewer.scene) {
5586
+ stopLive(viewer);
5587
+ return;
5588
+ }
5589
+ // Get the current settings.
5590
+ const tDetails = GetTimeDetails({ viewer });
5591
+ const clock = viewer.clock;
5592
+ if (clock) {
5593
+ clock.shouldAnimate = true;
5594
+ clock.multiplier = 1;
5595
+ clock.currentTime = Cesium.JulianDate.now();
5596
+ clock.startTime = Cesium.JulianDate.addSeconds(clock.currentTime, -tDetails.livePaddingBackwardSeconds, new Cesium.JulianDate());
5597
+ clock.stopTime = Cesium.JulianDate.addSeconds(clock.currentTime, tDetails.livePaddingForwardSeconds, new Cesium.JulianDate());
5598
+ clock.clockRange = Cesium.ClockRange.UNBOUNDED;
5599
+ }
5600
+ };
5601
+ // We'll start an interval that occasionally updates the Cesium clock to stay in the desired settings.
5602
+ // Since it moves live (1 second per second), we don't have to worry about the ranges being out of date quickly.
5603
+ viewer[CESIUM_TIMELINE_INTERVAL_KEY] = setInterval(() => {
5604
+ doUpdate();
5605
+ }, 800);
5606
+ viewer[CESIUM_TIMELINE_LIVE_KEY] = true;
5607
+ // Initial update.
5608
+ doUpdate();
5609
+ }
5610
+ function stopLive(viewer) {
5611
+ if (!viewer) {
5612
+ return;
5613
+ }
5614
+ let interval = viewer[CESIUM_TIMELINE_INTERVAL_KEY];
5615
+ if (interval) {
5616
+ clearInterval(interval);
5617
+ viewer[CESIUM_TIMELINE_INTERVAL_KEY] = null;
5618
+ }
5619
+ viewer[CESIUM_TIMELINE_LIVE_KEY] = false;
5620
+ }
5509
5621
  })(exports.ViewUtils || (exports.ViewUtils = {}));
5510
5622
 
5511
5623
  const MODEL_MIN_RADIUS = 10;
@@ -13131,14 +13243,16 @@
13131
13243
  }
13132
13244
  return this.onUpdate;
13133
13245
  }
13134
- constructor(data, viewPort, batchSize) {
13246
+ constructor(data, viewPort, batchSize, singleEmit = false) {
13135
13247
  this.viewPortRemoval = null;
13136
13248
  this.viewRect = null;
13137
13249
  this.viewCenter = null;
13138
13250
  this.onUpdate = null;
13251
+ this.emitted = false;
13139
13252
  this.data = data;
13140
13253
  this.viewPort = viewPort;
13141
13254
  this.batchSize = batchSize;
13255
+ this.singleEmit = singleEmit;
13142
13256
  }
13143
13257
  Start() {
13144
13258
  this.viewPortRemoval = this.viewPort.Updated().Subscribe(() => {
@@ -13169,6 +13283,10 @@
13169
13283
  }
13170
13284
  }
13171
13285
  startGetterLoop() {
13286
+ if (this.singleEmit && this.emitted) {
13287
+ return;
13288
+ }
13289
+ this.emitted = true;
13172
13290
  clearInterval(this.getterInterval);
13173
13291
  if (!this.viewRect || !this.viewCenter) {
13174
13292
  return;
@@ -13895,19 +14013,6 @@
13895
14013
  })(exports.EntitiesLoadedRenderManager || (exports.EntitiesLoadedRenderManager = {}));
13896
14014
 
13897
14015
  const BATCH_SIZE$2 = 500;
13898
- function getValue$4(viewer, obj) {
13899
- if (obj === null || obj === void 0 ? void 0 : obj.getValue) {
13900
- let date = viewer.scene.lastRenderTime;
13901
- if (!date) {
13902
- date = viewer.clock.currentTime;
13903
- }
13904
- return obj.getValue(date);
13905
- }
13906
- return obj;
13907
- }
13908
- function colorToCColor$3(color) {
13909
- return new Cesium.Color(color.red ? color.red / 255 : 0, color.green ? color.green / 255 : 0, color.blue ? color.blue / 255 : 0, color.alpha);
13910
- }
13911
14016
  (function (EntitiesIdsRenderManager) {
13912
14017
  class Manager {
13913
14018
  get Disposed() {
@@ -13919,23 +14024,27 @@
13919
14024
  this.getterSub = null;
13920
14025
  this.disposed = false;
13921
14026
  this.renderedEntities = {};
13922
- this.sources = [];
13923
- // Highly experimental flag to try improve rendering large sets of polygons and polylines.
13924
- // Many things are not supported when this is enabled.
13925
- this.useGeojson = false;
14027
+ // Callback to dispose the monitoring of the viewer's timeline for historic data.
14028
+ this.viewerDateTimeChangeRemoval = null;
14029
+ // Entity ID -> historic information on the Entity.
14030
+ // This helps us manage what needs changing whenever we receive the same Entity update, or when the timeline range changes.
14031
+ this.entitiesHistoric = {};
14032
+ // Queue for renderAsIndividuals to avoid concurrent calls.
14033
+ this.renderQueueActive = false;
14034
+ this.renderQueue = [];
14035
+ this.renderQueueForceFlags = [];
13926
14036
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
13927
14037
  this.viewer = viewer;
13928
14038
  this.apiGetter = apiGetter;
13929
14039
  this.monitor = monitor;
13930
14040
  this.item = item;
13931
14041
  this.visualsManager = visualsManager;
13932
- this.useGeojson = item.renderAsGeojson == true;
13933
14042
  if (this.item.enableClustering) {
13934
14043
  this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
13935
14044
  }
13936
14045
  }
13937
14046
  Init() {
13938
- var _a;
14047
+ var _a, _b, _c;
13939
14048
  if (this.disposed) {
13940
14049
  throw (new Error("This item is disposed."));
13941
14050
  }
@@ -13955,15 +14064,19 @@
13955
14064
  if (this.renderPriority == null) {
13956
14065
  this.renderPriority = 2;
13957
14066
  }
13958
- if (this.renderAsGeojson && this.item.CameraZoomSettings.length > 1) {
13959
- console.warn("Geojson rendering does not support multiple zoom controls. Only the first one will be used.");
13960
- this.item.CameraZoomSettings = [this.item.CameraZoomSettings[0]];
13961
- }
13962
- this.getter = new BatchedDataGetter.Getter(this.item.BruceEntity.EntityIds, this.monitor, BATCH_SIZE$2);
14067
+ this.getter = new BatchedDataGetter.Getter(this.item.BruceEntity.EntityIds, this.monitor, BATCH_SIZE$2,
14068
+ // Don't emit the same Entity multiple times.
14069
+ true);
13963
14070
  this.getterSub = this.getter.OnUpdate.Subscribe((ids) => {
13964
14071
  this.onGetterUpdate(ids);
13965
14072
  });
13966
14073
  this.getter.Start();
14074
+ if (((_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.historic) || ((_c = this.item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historicAttrKey)) {
14075
+ this.viewerDateTimeSub();
14076
+ }
14077
+ else {
14078
+ this.viewerDateTimeDispose();
14079
+ }
13967
14080
  }
13968
14081
  Dispose() {
13969
14082
  if (this.disposed) {
@@ -13981,11 +14094,11 @@
13981
14094
  });
13982
14095
  (_b = this.clustering) === null || _b === void 0 ? void 0 : _b.Dispose();
13983
14096
  this.clustering = null;
13984
- for (let i = 0; i < this.sources.length; i++) {
13985
- const source = this.sources[i];
13986
- this.viewer.dataSources.remove(source);
13987
- }
13988
- this.sources = [];
14097
+ this.viewerDateTimeDispose();
14098
+ // Clear render queue when disposed
14099
+ this.renderQueue = [];
14100
+ this.renderQueueForceFlags = [];
14101
+ this.renderQueueActive = false;
13989
14102
  }
13990
14103
  async ReRender(params) {
13991
14104
  let { entityIds, force, entities } = params;
@@ -14020,10 +14133,6 @@
14020
14133
  }
14021
14134
  if (CameraZoomSettings === null || CameraZoomSettings === void 0 ? void 0 : CameraZoomSettings.length) {
14022
14135
  this.item.CameraZoomSettings = CameraZoomSettings;
14023
- if (this.renderAsGeojson && this.item.CameraZoomSettings.length > 1) {
14024
- console.warn("Geojson rendering does not support multiple zoom controls. Only the first one will be used.");
14025
- this.item.CameraZoomSettings = [this.item.CameraZoomSettings[0]];
14026
- }
14027
14136
  }
14028
14137
  if (queueRerender != false) {
14029
14138
  const entityIds = Object.keys(this.renderedEntities);
@@ -14035,15 +14144,25 @@
14035
14144
  }
14036
14145
  }
14037
14146
  async onGetterUpdate(entityIds, force = false) {
14038
- if (this.disposed || this.viewer.isDestroyed()) {
14147
+ if (this.disposed || this.viewer.isDestroyed() || !(entityIds === null || entityIds === void 0 ? void 0 : entityIds.length)) {
14039
14148
  return;
14040
14149
  }
14041
14150
  try {
14042
14151
  const api = this.apiGetter.getApi();
14152
+ let newDateTime = null;
14153
+ if (this.item.BruceEntity.historic || this.item.BruceEntity.historicAttrKey) {
14154
+ newDateTime = Cesium.JulianDate.toDate(this.viewer.clock.currentTime).toISOString();
14155
+ }
14043
14156
  const { entities } = await BModels.Entity.GetListByIds({
14044
14157
  api,
14045
14158
  entityIds,
14046
- migrated: true
14159
+ historicPoint: newDateTime,
14160
+ historicKey: this.item.BruceEntity.historicAttrKey,
14161
+ migrated: true,
14162
+ // If we're taking 5+ minutes to make a query, it's a dud.
14163
+ // This is a timeout imposed on our DB and not external sources.
14164
+ // Honestly could lower down to 30 seconds, but we'll keep it high for now.
14165
+ maxSearchTimeSec: 60 * 5,
14047
14166
  });
14048
14167
  await this.renderEntities(entities, force);
14049
14168
  }
@@ -14052,347 +14171,82 @@
14052
14171
  }
14053
14172
  }
14054
14173
  async renderEntities(entities, force = false) {
14055
- if (this.disposed || this.viewer.isDestroyed()) {
14174
+ if (this.disposed || this.viewer.isDestroyed() || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
14056
14175
  return;
14057
14176
  }
14058
14177
  try {
14059
- if (this.useGeojson) {
14060
- const zoomItem = this.item.CameraZoomSettings[0];
14061
- if (zoomItem.DisplayType == BModels.ZoomControl.EDisplayType.Point) {
14062
- // We'll just render these as individuals since we don't support point geojson.
14063
- await this.renderAsIndividuals(entities, force);
14064
- }
14065
- else {
14066
- await this.renderAsGeojson(entities, force);
14067
- }
14068
- }
14069
- else {
14070
- await this.renderAsIndividuals(entities, force);
14071
- }
14178
+ await this.queueRenderAsIndividuals(entities, force);
14072
14179
  }
14073
14180
  catch (e) {
14074
14181
  console.error(e);
14075
14182
  }
14076
14183
  }
14077
14184
  /**
14078
- * Our optimized and more stable path.
14079
- * We construct a geojson that we draw in one go.
14080
- * @param entities
14081
- * @param force TODO: This should re-render entities that are already rendered.
14185
+ * Queues a render operation to be processed in sequence.
14186
+ * If an operation is already in progress, this will be added to the queue.
14082
14187
  */
14083
- async renderAsGeojson(entities, force) {
14084
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
14085
- entities = entities.filter((entity) => {
14086
- var _a;
14087
- return !this.renderedEntities[(_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID];
14088
- });
14089
- // Mark these as rendered.
14090
- entities.forEach((entity) => {
14091
- var _a;
14092
- this.renderedEntities[(_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID] = true;
14093
- });
14094
- // This process only supports a single zoom control item.
14095
- const zoomItem = this.item.CameraZoomSettings[0];
14096
- // If we're after 3d models then we'll check if there are LODs and render those as individuals.
14097
- if (zoomItem.DisplayType == BModels.ZoomControl.EDisplayType.Model3D && entities.length) {
14098
- const { lods } = await BModels.EntityLod.GetLods({
14099
- api: this.apiGetter.getApi(),
14100
- filter: {
14101
- externalSources: false,
14102
- Items: entities.map(x => {
14103
- var _a, _b;
14104
- return {
14105
- entityId: (_a = x.Bruce) === null || _a === void 0 ? void 0 : _a.ID,
14106
- categoryId: (_b = zoomItem.LODCategoryID) !== null && _b !== void 0 ? _b : "GLB",
14107
- group: "DEFAULT",
14108
- level: Number(zoomItem.LODLevel)
14109
- };
14110
- }),
14111
- strict: false
14112
- }
14113
- });
14114
- if (this.disposed) {
14115
- this.doDispose();
14116
- return;
14117
- }
14118
- const withLods = lods.filter(x => x.entityId && !!x.clientFileId).map(x => x.entityId);
14119
- const individuals = entities.filter((entity) => {
14120
- var _a;
14121
- return withLods.includes((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID);
14122
- });
14123
- if (individuals.length) {
14124
- this.renderAsIndividuals(individuals, force);
14125
- }
14126
- // Now we proceed with what is left.
14127
- entities = entities.filter((entity) => {
14128
- var _a;
14129
- return !withLods.includes((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID);
14130
- });
14131
- }
14132
- if (!entities.length) {
14188
+ async queueRenderAsIndividuals(entities, force = false) {
14189
+ if (this.disposed) {
14133
14190
  return;
14134
14191
  }
14135
- let style = null;
14136
- if ((zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) && (zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) > -1) {
14137
- try {
14138
- style = (_a = (await BModels.Style.Get({
14139
- api: this.apiGetter.getApi(),
14140
- styleId: zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID
14141
- })).style) === null || _a === void 0 ? void 0 : _a.Settings;
14142
- }
14143
- // Probably deleted.
14144
- catch (e) {
14145
- console.error(e);
14146
- }
14192
+ // If no active render is happening, start processing immediately.
14193
+ if (!this.renderQueueActive) {
14194
+ this.renderQueueActive = true;
14195
+ await this.processRenderQueue(entities, force);
14196
+ return;
14147
14197
  }
14148
- let entityTypeId = (_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b["EntityType.ID"];
14149
- if (!entityTypeId) {
14150
- entityTypeId = (_d = (_c = entities.find(x => { var _a; return !!((_a = x.Bruce) === null || _a === void 0 ? void 0 : _a["EntityType.ID"]); })) === null || _c === void 0 ? void 0 : _c.Bruce) === null || _d === void 0 ? void 0 : _d["EntityType.ID"];
14198
+ // Otherwise, add to the queue.
14199
+ this.renderQueue.push(entities);
14200
+ this.renderQueueForceFlags.push(force);
14201
+ }
14202
+ /**
14203
+ * Processes the render queue, handling one rendering operation at a time.
14204
+ */
14205
+ async processRenderQueue(initialEntities, initialForce = false) {
14206
+ if (this.disposed) {
14207
+ this.renderQueueActive = false;
14208
+ return;
14151
14209
  }
14152
- // Getting type regardless cause it's needed for name calculations.
14153
- let entityType;
14154
- if (entityTypeId) {
14155
- // Try using the Entity Type default if one is specified in the menu item.
14156
- try {
14157
- entityType = (_e = (await BModels.EntityType.Get({
14158
- entityTypeId: entityTypeId,
14159
- api: this.apiGetter.getApi()
14160
- }))) === null || _e === void 0 ? void 0 : _e.entityType;
14161
- if (!style && entityType) {
14162
- if (entityType["DisplaySetting.ID"] && entityType["DisplaySetting.ID"] > 0) {
14163
- style = (_f = (await BModels.Style.Get({
14164
- api: this.apiGetter.getApi(),
14165
- styleId: entityType["DisplaySetting.ID"]
14166
- })).style) === null || _f === void 0 ? void 0 : _f.Settings;
14210
+ try {
14211
+ // Process the initial batch.
14212
+ await this.renderAsIndividuals(initialEntities, initialForce);
14213
+ if (this.disposed) {
14214
+ return;
14215
+ }
14216
+ // Process any queued items.
14217
+ while (this.renderQueue.length > 0 && !this.disposed) {
14218
+ // If multiple entities are waiting, combine them to reduce render operations.
14219
+ const allEntities = [];
14220
+ const forces = [];
14221
+ // Collect all pending entities.
14222
+ while (this.renderQueue.length > 0) {
14223
+ const entities = this.renderQueue.shift();
14224
+ const force = this.renderQueueForceFlags.shift();
14225
+ if (entities && entities.length > 0) {
14226
+ allEntities.push(...entities);
14227
+ forces.push(force);
14167
14228
  }
14168
14229
  }
14230
+ if (allEntities.length > 0) {
14231
+ // If any are forced, we need to force the render.
14232
+ const combinedForce = forces.some(f => f === true);
14233
+ const uniqueEntities = this.deduplicateEntities(allEntities);
14234
+ await this.renderAsIndividuals(uniqueEntities, combinedForce);
14235
+ }
14169
14236
  }
14170
- catch (e) {
14171
- console.error(e);
14172
- }
14173
- }
14174
- const pStyle = (_g = style === null || style === void 0 ? void 0 : style.polygonStyle) !== null && _g !== void 0 ? _g : {};
14175
- const lStyle = (_h = style === null || style === void 0 ? void 0 : style.polylineStyle) !== null && _h !== void 0 ? _h : {};
14176
- const polygonsClamped = ((_j = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _j === void 0 ? void 0 : _j.id) == null ? true : ((_k = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _k === void 0 ? void 0 : _k.id) == 0;
14177
- const bFillColor = BModels.Calculator.GetColor(pStyle.fillColor, {}, []);
14178
- const cFillColor = bFillColor ? colorToCColor$3(bFillColor) : Cesium.Color.fromCssColorString("rgba(139, 195, 74, 0.8)");
14179
- const bLineColor = BModels.Calculator.GetColor(pStyle.lineColor, {}, []);
14180
- const cLineColor = bLineColor ? colorToCColor$3(bLineColor) : Cesium.Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
14181
- let lineWidthPx = pStyle.lineWidth ? BModels.Calculator.GetNumber(pStyle.lineWidth, {}, []) : null;
14182
- if (lineWidthPx == null) {
14183
- lineWidthPx = 1;
14184
14237
  }
14185
- lineWidthPx = EnsureNumber(lineWidthPx);
14186
- if (lineWidthPx < 0.01) {
14187
- lineWidthPx = 0;
14238
+ finally {
14239
+ this.renderQueueActive = false;
14188
14240
  }
14189
- lineWidthPx = Math.round(lineWidthPx);
14190
- const collection = {
14191
- features: [],
14192
- type: "FeatureCollection"
14193
- };
14194
- for (let i = 0; i < entities.length; i++) {
14195
- const entity = entities[i];
14196
- const feature = BModels.Geometry.ToGeoJsonFeature({
14197
- geometry: entity.Bruce.VectorGeometry,
14198
- noAltitude: polygonsClamped && lineWidthPx <= 0,
14199
- altitude: lineWidthPx > 0 && polygonsClamped ? 1 : null,
14200
- properties: {
14201
- ...entity,
14202
- Bruce: {
14203
- ...entity.Bruce,
14204
- // Exclude as we just converted it to geojson.
14205
- VectorGeometry: null
14206
- }
14207
- }
14208
- });
14209
- if (feature) {
14210
- collection.features.push(feature);
14241
+ }
14242
+ deduplicateEntities(entities) {
14243
+ const entityMap = new Map();
14244
+ for (const entity of entities) {
14245
+ if (entity && entity.Bruce && entity.Bruce.ID) {
14246
+ entityMap.set(entity.Bruce.ID, entity);
14211
14247
  }
14212
14248
  }
14213
- // Anything that failed to turn into geojson or points we'll render as individuals.
14214
- const individuals = entities.filter((entity) => {
14215
- const feature = collection.features.find(x => { var _a, _b; return ((_b = (_a = x.properties) === null || _a === void 0 ? void 0 : _a.Bruce) === null || _b === void 0 ? void 0 : _b.ID) == entity.Bruce.ID; });
14216
- if (!feature) {
14217
- return true;
14218
- }
14219
- if (feature.geometry.type == "Point") {
14220
- return true;
14221
- }
14222
- return false;
14223
- });
14224
- if (individuals.length) {
14225
- this.renderAsIndividuals(individuals, force);
14226
- }
14227
- // Filter out points (the ones we just rendered as individuals).
14228
- collection.features = collection.features.filter(x => x.geometry.type != "Point");
14229
- // If there is nothing to render now, return.
14230
- if (!collection.features.length) {
14231
- return;
14232
- }
14233
- const source = await Cesium.GeoJsonDataSource.load(collection, {
14234
- stroke: cLineColor,
14235
- fill: cFillColor,
14236
- strokeWidth: lineWidthPx,
14237
- clampToGround: lineWidthPx <= 0 && polygonsClamped
14238
- });
14239
- this.viewer.dataSources.add(source);
14240
- this.sources.push(source);
14241
- if (this.disposed) {
14242
- this.doDispose();
14243
- return;
14244
- }
14245
- const groups = [];
14246
- /**
14247
- * Applies individual styling to a rendered entity.
14248
- * This will exclude tag styling for now since it's harder to load that on the fly.
14249
- * @param thing
14250
- * @param entityId
14251
- * @param data
14252
- * @returns
14253
- */
14254
- const applyStyle = (thing, entityId, data) => {
14255
- if (thing.polygon) {
14256
- const bFillColor = BModels.Calculator.GetColor(pStyle.fillColor, data, []);
14257
- const cFillColor = bFillColor ? colorToCColor$3(bFillColor) : Cesium.Color.fromCssColorString("rgba(139, 195, 74, 0.8)");
14258
- const bLineColor = BModels.Calculator.GetColor(pStyle.lineColor, data, []);
14259
- const cLineColor = bLineColor ? colorToCColor$3(bLineColor) : Cesium.Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
14260
- let width = pStyle.lineWidth ? BModels.Calculator.GetNumber(pStyle.lineWidth, data, []) : null;
14261
- if (width == null) {
14262
- width = 1;
14263
- }
14264
- width = EnsureNumber(width);
14265
- if (width < 0.01) {
14266
- width = 0;
14267
- }
14268
- let curFillColor = getValue$4(this.viewer, thing.polygon.material);
14269
- if (curFillColor && curFillColor instanceof Cesium.ColorMaterialProperty) {
14270
- curFillColor = curFillColor.color;
14271
- }
14272
- let curLineColor = getValue$4(this.viewer, thing.polygon.outlineColor);
14273
- if (curLineColor && curLineColor instanceof Cesium.ColorMaterialProperty) {
14274
- curLineColor = curLineColor.color;
14275
- }
14276
- const curWidth = getValue$4(this.viewer, thing.polygon.outlineWidth);
14277
- if ((curFillColor instanceof Cesium.Color && curFillColor.equals(cFillColor)) &&
14278
- (curLineColor instanceof Cesium.Color && curLineColor.equals(cLineColor)) &&
14279
- curWidth == width) {
14280
- return;
14281
- }
14282
- thing.polygon.material = new Cesium.ColorMaterialProperty(cFillColor);
14283
- thing.polygon.outlineColor = new Cesium.ConstantProperty(cLineColor);
14284
- thing.polygon.outlineWidth = new Cesium.ConstantProperty(width);
14285
- }
14286
- else if (thing.polyline) {
14287
- const bColor = lStyle.lineColor ? BModels.Calculator.GetColor(lStyle.lineColor, data, []) : null;
14288
- const cColor = bColor ? colorToCColor$3(bColor) : Cesium.Color.fromCssColorString("rgba(255, 193, 7, 0.8)");
14289
- let width = lStyle.lineWidth ? BModels.Calculator.GetNumber(lStyle.lineWidth, data, []) : null;
14290
- if (width == null) {
14291
- width = 2;
14292
- }
14293
- width = EnsureNumber(width);
14294
- if (width < 0.01) {
14295
- width = 0;
14296
- }
14297
- let curColor = getValue$4(this.viewer, thing.polyline.material);
14298
- if (curColor && curColor instanceof Cesium.ColorMaterialProperty) {
14299
- curColor = curColor.color;
14300
- }
14301
- const curWidth = getValue$4(this.viewer, thing.polyline.width);
14302
- if ((curColor instanceof Cesium.Color && curColor.equals(cColor)) &&
14303
- curWidth == width) {
14304
- return;
14305
- }
14306
- thing.polyline.material = new Cesium.ColorMaterialProperty(cColor);
14307
- thing.polyline.width = new Cesium.ConstantProperty(width);
14308
- }
14309
- };
14310
- let toForceUpdate = [];
14311
- /**
14312
- * Registers a given cesium entity produced from rendering geojson.
14313
- * Since one nextspace entity can have multiple cesium entities, there is special logic to group them together.
14314
- * @param thing
14315
- * @returns
14316
- */
14317
- const register = (thing) => {
14318
- var _a, _b, _c, _d;
14319
- // See if the cesium entity already exists in a group.
14320
- let group = groups.find((x) => { var _a; return ((_a = x.visual) === null || _a === void 0 ? void 0 : _a.id) == thing.id || x.siblings.find(x => (x === null || x === void 0 ? void 0 : x.id) == thing.id); });
14321
- if (group) {
14322
- return;
14323
- }
14324
- const metadata = getValue$4(this.viewer, thing === null || thing === void 0 ? void 0 : thing.properties);
14325
- const entityId = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
14326
- if (!entityId) {
14327
- return;
14328
- }
14329
- // Find group for the nextspace entity ID.
14330
- group = groups.find((x) => x.entityId == entityId);
14331
- // No group yet. We can designate this as the primary entity and create a new group for it.
14332
- if (!group) {
14333
- group = {
14334
- entityId: entityId,
14335
- visual: thing,
14336
- tagIds: (_b = metadata === null || metadata === void 0 ? void 0 : metadata.Bruce) === null || _b === void 0 ? void 0 : _b["Layer.ID"],
14337
- entityTypeId: (_c = metadata === null || metadata === void 0 ? void 0 : metadata.Bruce) === null || _c === void 0 ? void 0 : _c["EntityType.ID"],
14338
- siblings: [],
14339
- data: entities.find(x => { var _a; return ((_a = x.Bruce) === null || _a === void 0 ? void 0 : _a.ID) == entityId; }),
14340
- rego: null
14341
- };
14342
- groups.push(group);
14343
- applyStyle(thing, entityId, group.data);
14344
- const rego = {
14345
- entityId: entityId,
14346
- menuItemId: this.item.id,
14347
- menuItemType: this.item.Type,
14348
- visual: thing,
14349
- priority: this.renderPriority,
14350
- entityTypeId: group.entityTypeId,
14351
- accountId: this.apiGetter.accountId,
14352
- tagIds: group.tagIds ? [].concat(group.tagIds) : [],
14353
- name: entityType ? (_d = BModels.Entity.CalculateName({
14354
- entity: group.data,
14355
- type: entityType,
14356
- defaultToId: false
14357
- })) !== null && _d !== void 0 ? _d : "Unnamed Entity" : "Unnamed Entity",
14358
- cdn: this.item.cdnEnabled,
14359
- collection: source.entities
14360
- };
14361
- group.rego = rego;
14362
- this.visualsManager.AddRego({
14363
- rego,
14364
- requestRender: false
14365
- });
14366
- }
14367
- // Found a group. We flag this as a sibling entity of the primary.
14368
- else {
14369
- applyStyle(thing, entityId, group.data);
14370
- group.siblings.push(thing);
14371
- group.visual._siblingGraphics = group.siblings;
14372
- thing._parentEntity = group.visual;
14373
- if (group.rego) {
14374
- this.visualsManager.RefreshMark({
14375
- rego: group.rego
14376
- });
14377
- if (!toForceUpdate.includes(entityId)) {
14378
- toForceUpdate.push(entityId);
14379
- }
14380
- }
14381
- }
14382
- };
14383
- const sEntities = source.entities.values;
14384
- for (let i = 0; i < sEntities.length; i++) {
14385
- const cEntity = sEntities[i];
14386
- register(cEntity);
14387
- }
14388
- if (toForceUpdate.length) {
14389
- this.visualsManager.ForceUpdate({
14390
- entityIds: toForceUpdate,
14391
- refreshColors: true,
14392
- requestRender: false
14393
- });
14394
- }
14395
- this.viewer.scene.requestRender();
14249
+ return Array.from(entityMap.values());
14396
14250
  }
14397
14251
  /**
14398
14252
  * Our default path.
@@ -14403,16 +14257,28 @@
14403
14257
  */
14404
14258
  async renderAsIndividuals(entities, force = false) {
14405
14259
  var _a;
14260
+ if (this.disposed || this.viewer.isDestroyed() || !entities.length) {
14261
+ return;
14262
+ }
14263
+ let entitiesHistoric = {};
14264
+ if ((this.item.BruceEntity.historic || this.item.BruceEntity.historicAttrKey) && entities.length) {
14265
+ entitiesHistoric = await this.getHistoricInfo(entities);
14266
+ }
14406
14267
  const { updated, entities: cEntities } = await exports.EntityRenderEngine.Render({
14407
14268
  viewer: this.viewer,
14408
14269
  apiGetter: this.apiGetter,
14409
14270
  entities: entities,
14410
14271
  menuItemId: this.item.id,
14411
14272
  visualRegister: this.visualsManager,
14273
+ entitiesHistoric: entitiesHistoric,
14274
+ entityHistoricDrawTrack: this.item.historicDrawTrack,
14412
14275
  zoomControl: this.item.CameraZoomSettings,
14413
14276
  force
14414
14277
  });
14415
14278
  for (let i = 0; i < entities.length; i++) {
14279
+ if (this.disposed) {
14280
+ break;
14281
+ }
14416
14282
  const entity = entities[i];
14417
14283
  const id = entity.Bruce.ID;
14418
14284
  const cEntity = cEntities.get(id);
@@ -14461,11 +14327,144 @@
14461
14327
  (_a = this.clustering) === null || _a === void 0 ? void 0 : _a.RemoveEntity(id, false);
14462
14328
  }
14463
14329
  }
14464
- this.viewer.scene.requestRender();
14465
- if (this.clustering && entities.length) {
14466
- this.clustering.Update();
14330
+ if (!this.disposed) {
14331
+ this.viewer.scene.requestRender();
14332
+ if (this.clustering && entities.length) {
14333
+ this.clustering.Update();
14334
+ }
14467
14335
  }
14468
14336
  }
14337
+ /**
14338
+ * Returns historic information for the given set of Entities at the current timeline range.
14339
+ * @param entities
14340
+ */
14341
+ async getHistoricInfo(entities) {
14342
+ const startTmp = Cesium.JulianDate.toDate(this.viewer.clock.startTime);
14343
+ const stopTmp = Cesium.JulianDate.toDate(this.viewer.clock.stopTime);
14344
+ let rangesToRequest = [{
14345
+ start: startTmp.toISOString(),
14346
+ stop: stopTmp.toISOString()
14347
+ }];
14348
+ // If we already have cached data, determine what ranges we're missing.
14349
+ if (this.lastHistoricMin && this.lastHistoricMax && Object.keys(this.entitiesHistoric).length >= entities.length) {
14350
+ const cachedStart = new Date(this.lastHistoricMin).getTime();
14351
+ const cachedStop = new Date(this.lastHistoricMax).getTime();
14352
+ const newStart = startTmp.getTime();
14353
+ const newStop = stopTmp.getTime();
14354
+ // Complete overlap - we already have all the data.
14355
+ if (newStart >= cachedStart && newStop <= cachedStop) {
14356
+ return this.entitiesHistoric;
14357
+ }
14358
+ // Calculate missing ranges.
14359
+ rangesToRequest = [];
14360
+ // Check if we need data before our cached range.
14361
+ if (newStart < cachedStart) {
14362
+ rangesToRequest.push({
14363
+ start: startTmp.toISOString(),
14364
+ stop: new Date(cachedStart).toISOString()
14365
+ });
14366
+ }
14367
+ // Check if we need data after our cached range.
14368
+ if (newStop > cachedStop) {
14369
+ rangesToRequest.push({
14370
+ start: new Date(cachedStop).toISOString(),
14371
+ stop: stopTmp.toISOString()
14372
+ });
14373
+ }
14374
+ }
14375
+ const entityIds = entities.map(x => x.Bruce.ID);
14376
+ const combined = { ...this.entitiesHistoric };
14377
+ // Make requests for each missing range
14378
+ for (const range of rangesToRequest) {
14379
+ if (this.disposed) {
14380
+ break;
14381
+ }
14382
+ // Add padding to ensure we get all data.
14383
+ const start = new Date(range.start);
14384
+ const stop = new Date(range.stop);
14385
+ start.setSeconds(start.getSeconds() - 1);
14386
+ stop.setSeconds(stop.getSeconds() + 1);
14387
+ const paddedStartStr = start.toISOString();
14388
+ const paddedStopStr = stop.toISOString();
14389
+ const historicData = await BModels.EntityHistoricData.GetList({
14390
+ attrKey: this.item.BruceEntity.historicAttrKey,
14391
+ dateTimeFrom: paddedStartStr,
14392
+ dateTimeTo: paddedStopStr,
14393
+ entityIds: entityIds,
14394
+ api: this.apiGetter.getApi()
14395
+ });
14396
+ if (this.disposed) {
14397
+ break;
14398
+ }
14399
+ // Merge the new data with existing data
14400
+ const records = historicData.recordsByIds;
14401
+ const recordsIds = Object.keys(records);
14402
+ for (let i = 0; i < recordsIds.length; i++) {
14403
+ const entityId = recordsIds[i];
14404
+ const latest = records[entityId] || [];
14405
+ const current = combined[entityId] || [];
14406
+ // Use a Map to de-duplicate by timestamp.
14407
+ const tmp = new Map();
14408
+ for (let j = 0; j < current.length; j++) {
14409
+ const record = current[j];
14410
+ const dateTime = new Date(record.dateTime).getTime();
14411
+ tmp.set(dateTime, record);
14412
+ }
14413
+ for (let j = 0; j < latest.length; j++) {
14414
+ const record = latest[j];
14415
+ const dateTime = new Date(record.dateTime).getTime();
14416
+ tmp.set(dateTime, record);
14417
+ }
14418
+ // Convert to array and sort by date.
14419
+ const sorted = Array.from(tmp.values()).sort((a, b) => {
14420
+ return new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime();
14421
+ });
14422
+ combined[entityId] = sorted;
14423
+ }
14424
+ }
14425
+ if (!this.disposed) {
14426
+ // Update our cache boundaries.
14427
+ this.lastHistoricMin = startTmp.toISOString();
14428
+ this.lastHistoricMax = stopTmp.toISOString();
14429
+ this.entitiesHistoric = combined;
14430
+ }
14431
+ return combined;
14432
+ }
14433
+ viewerDateTimeSub() {
14434
+ var _a, _b;
14435
+ if ((!((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic) && !((_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.historicAttrKey)) || this.viewerDateTimeChangeRemoval) {
14436
+ return;
14437
+ }
14438
+ const THROTTLE_INTERVAL = 2000;
14439
+ let queue = new BModels.DelayQueue(() => {
14440
+ this.onGetterUpdate(Object.keys(this.renderedEntities));
14441
+ }, THROTTLE_INTERVAL, true);
14442
+ let clockTickRemoval;
14443
+ let prevTick = this.viewer.clock.currentTime.toString();
14444
+ clockTickRemoval = this.viewer.clock.onTick.addEventListener(() => {
14445
+ if (this.disposed || this.viewer.isDestroyed()) {
14446
+ return;
14447
+ }
14448
+ const currentTick = this.viewer.clock.currentTime.toString();
14449
+ if (currentTick === prevTick) {
14450
+ return;
14451
+ }
14452
+ queue.Call();
14453
+ });
14454
+ this.viewerDateTimeChangeRemoval = () => {
14455
+ if (clockTickRemoval) {
14456
+ clockTickRemoval();
14457
+ clockTickRemoval = null;
14458
+ }
14459
+ queue.Dispose();
14460
+ queue = null;
14461
+ };
14462
+ }
14463
+ viewerDateTimeDispose() {
14464
+ var _a;
14465
+ (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
14466
+ this.viewerDateTimeChangeRemoval = null;
14467
+ }
14469
14468
  }
14470
14469
  EntitiesIdsRenderManager.Manager = Manager;
14471
14470
  })(exports.EntitiesIdsRenderManager || (exports.EntitiesIdsRenderManager = {}));
@@ -15603,7 +15602,7 @@
15603
15602
  })(Queue = SharedGetters.Queue || (SharedGetters.Queue = {}));
15604
15603
  })(exports.SharedGetters || (exports.SharedGetters = {}));
15605
15604
 
15606
- function colorToCColor$4(color) {
15605
+ function colorToCColor$3(color) {
15607
15606
  return new Cesium.Color(color.red ? color.red / 255 : 0, color.green ? color.green / 255 : 0, color.blue ? color.blue / 255 : 0, color.alpha);
15608
15607
  }
15609
15608
  /**
@@ -16906,7 +16905,7 @@
16906
16905
  cColor = Cesium.Color.WHITE;
16907
16906
  }
16908
16907
  else {
16909
- cColor = colorToCColor$4(bColor);
16908
+ cColor = colorToCColor$3(bColor);
16910
16909
  }
16911
16910
  const override = this.styledEntityIds[entity.entityId] == true;
16912
16911
  exports.CesiumEntityStyler.SetDefaultColor({
@@ -17099,7 +17098,7 @@
17099
17098
  this.viewerDateTimeChangeRemoval = null;
17100
17099
  // Series of points to help interpolate movement when the timeline changes.
17101
17100
  this.historicPosses = [];
17102
- this.historicPossesLoading = false;
17101
+ this.historicAnimation = null;
17103
17102
  this.historicPossesLoadingProm = null;
17104
17103
  const { viewer, register: visualsManager, getters, item } = params;
17105
17104
  this.viewer = viewer;
@@ -17556,7 +17555,7 @@
17556
17555
  /**
17557
17556
  * Monitors the Cesium Viewer and updates the root Entity based on the scene time changing.
17558
17557
  * If the root Entity is historic, this can allow for movement of the assembly as a whole.
17559
- * @parma tileset
17558
+ * @param tileset
17560
17559
  */
17561
17560
  viewerDateTimeSub(tileset) {
17562
17561
  var _a, _b;
@@ -17570,60 +17569,172 @@
17570
17569
  const api = this.getters.GetBruceApi({
17571
17570
  accountId: accountId
17572
17571
  });
17573
- // Returns a series of positions to use for position interpolation based on time.
17574
- // If the timeline range is different to the previous query, we'll re-request the data.
17575
- const getSeriesPosses = async () => {
17572
+ // 'start-stop' time string that maps to a pending request.
17573
+ // Helps us avoid repeated requests that are the same.
17574
+ const pendingRequests = new Map();
17575
+ /**
17576
+ * Returns a list of historic positions for a given time range.
17577
+ * @param startStr
17578
+ * @param stopStr
17579
+ * @returns
17580
+ */
17581
+ const getPossesForRange = async (startStr, stopStr) => {
17582
+ const requestKey = `${startStr}-${stopStr}`;
17583
+ if (pendingRequests.has(requestKey)) {
17584
+ return pendingRequests.get(requestKey);
17585
+ }
17586
+ const requestPromise = new Promise(async (res) => {
17587
+ try {
17588
+ const historicData = await BModels.EntityHistoricData.GetList({
17589
+ attrKey: null,
17590
+ dateTimeFrom: startStr,
17591
+ dateTimeTo: stopStr,
17592
+ entityIds: [this.rootId],
17593
+ api: api
17594
+ });
17595
+ const posses = exports.CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, Cesium.HeightReference.NONE, historicData.recordsByIds[this.rootId]);
17596
+ res(posses);
17597
+ }
17598
+ catch (e) {
17599
+ console.error(e);
17600
+ res([]);
17601
+ }
17602
+ finally {
17603
+ pendingRequests.delete(requestKey);
17604
+ }
17605
+ });
17606
+ pendingRequests.set(requestKey, requestPromise);
17607
+ return requestPromise;
17608
+ };
17609
+ /**
17610
+ * Checks if the current timeline range is not fully loaded and requests the difference.
17611
+ * This will call getPossesForRange() if it needs more data.
17612
+ * @returns
17613
+ */
17614
+ const checkTimelineRange = async () => {
17576
17615
  const minDateTime = this.viewer.clock.startTime.toString();
17577
17616
  const maxDateTime = this.viewer.clock.stopTime.toString();
17578
- if (this.historicPossesMinDateTime == minDateTime &&
17579
- this.historicPossesMaxDateTime == maxDateTime) {
17580
- if (this.historicPossesLoading) {
17581
- if (!await this.historicPossesLoadingProm) {
17582
- return false;
17583
- }
17584
- }
17585
- return this.historicPosses;
17617
+ // See if the current range is within the range we already have.
17618
+ if (this.historicPosses.length > 0 &&
17619
+ this.historicPossesMinDateTime &&
17620
+ this.historicPossesMaxDateTime &&
17621
+ minDateTime >= this.historicPossesMinDateTime &&
17622
+ maxDateTime <= this.historicPossesMaxDateTime) {
17623
+ return;
17586
17624
  }
17587
- this.historicPossesMinDateTime = minDateTime;
17588
- this.historicPossesMaxDateTime = maxDateTime;
17589
- this.historicPossesLoading = true;
17590
- this.historicPossesLoadingProm = new Promise(async (res) => {
17591
- // We'll add/remove 1 second to ensure we cover all records.
17625
+ // See if the requested range is before or after the range we have.
17626
+ const fetchBefore = !this.historicPossesMinDateTime || minDateTime < this.historicPossesMinDateTime;
17627
+ const fetchAfter = !this.historicPossesMaxDateTime || maxDateTime > this.historicPossesMaxDateTime;
17628
+ if (!fetchBefore && !fetchAfter) {
17629
+ // Already have the data we need.
17630
+ return;
17631
+ }
17632
+ // No known range so we need to fetch the whole thing.
17633
+ if (!this.historicPossesMinDateTime || !this.historicPossesMaxDateTime) {
17592
17634
  const startTmp = Cesium.JulianDate.toDate(this.viewer.clock.startTime);
17593
17635
  const stopTmp = Cesium.JulianDate.toDate(this.viewer.clock.stopTime);
17594
17636
  const startStr = new Date(startTmp.getTime() - 1000).toISOString();
17595
17637
  const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
17596
- const historicData = await BModels.EntityHistoricData.GetList({
17597
- attrKey: null,
17598
- dateTimeFrom: startStr,
17599
- dateTimeTo: stopStr,
17600
- entityIds: [this.rootId],
17601
- api: api
17602
- });
17603
- const posses = exports.CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, Cesium.HeightReference.NONE, historicData.recordsByIds[this.rootId]);
17604
- if (this.historicPossesMinDateTime == minDateTime &&
17605
- this.historicPossesMaxDateTime == maxDateTime) {
17606
- this.historicPosses = posses;
17638
+ const newPositions = await getPossesForRange(startStr, stopStr);
17639
+ if (this.disposed) {
17640
+ return;
17641
+ }
17642
+ if (this.historicAnimation && this.historicAnimation.addPositions) {
17643
+ this.historicAnimation.addPositions(newPositions);
17644
+ }
17645
+ this.historicPossesMinDateTime = minDateTime;
17646
+ this.historicPossesMaxDateTime = maxDateTime;
17647
+ }
17648
+ else {
17649
+ // The data we want is before the range we've currently loaded.
17650
+ if (fetchBefore) {
17651
+ // Calculate the missing difference and request it.
17652
+ const startTmp = Cesium.JulianDate.toDate(this.viewer.clock.startTime);
17653
+ const stopTmp = new Date(this.historicPossesMinDateTime);
17654
+ const startStr = new Date(startTmp.getTime() - 1000).toISOString();
17655
+ const stopStr = stopTmp.toISOString();
17656
+ getPossesForRange(startStr, stopStr).then(newPositions => {
17657
+ if (this.disposed) {
17658
+ return;
17659
+ }
17660
+ this.historicPossesMinDateTime = minDateTime;
17661
+ if (this.historicAnimation && this.historicAnimation.addPositions) {
17662
+ this.historicAnimation.addPositions(newPositions);
17663
+ }
17664
+ });
17665
+ }
17666
+ // The data we want is after the range we've currently loaded.
17667
+ if (fetchAfter) {
17668
+ // Calculate the missing difference and request it.
17669
+ const startTmp = new Date(this.historicPossesMaxDateTime);
17670
+ const stopTmp = Cesium.JulianDate.toDate(this.viewer.clock.stopTime);
17671
+ const startStr = startTmp.toISOString();
17672
+ const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
17673
+ getPossesForRange(startStr, stopStr).then(newPositions => {
17674
+ if (this.disposed) {
17675
+ return;
17676
+ }
17677
+ this.historicPossesMaxDateTime = maxDateTime;
17678
+ if (this.historicAnimation && this.historicAnimation.addPositions) {
17679
+ this.historicAnimation.addPositions(newPositions);
17680
+ }
17681
+ });
17682
+ }
17683
+ }
17684
+ };
17685
+ /**
17686
+ * Requests the initial set of historic positions for the timeline range.
17687
+ * This is called when the tileset is loaded and the timeline range is set.
17688
+ * @returns
17689
+ */
17690
+ const getInitialPosses = async () => {
17691
+ const minDateTime = this.viewer.clock.startTime.toString();
17692
+ const maxDateTime = this.viewer.clock.stopTime.toString();
17693
+ this.historicPossesLoadingProm = new Promise(async (res) => {
17694
+ try {
17695
+ if (this.disposed) {
17696
+ res(false);
17697
+ return;
17698
+ }
17699
+ const startTmp = Cesium.JulianDate.toDate(this.viewer.clock.startTime);
17700
+ const stopTmp = Cesium.JulianDate.toDate(this.viewer.clock.stopTime);
17701
+ const startStr = new Date(startTmp.getTime() - 1000).toISOString();
17702
+ const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
17703
+ const positions = await getPossesForRange(startStr, stopStr);
17704
+ if (this.disposed) {
17705
+ res(false);
17706
+ return;
17707
+ }
17708
+ this.historicPosses = positions;
17607
17709
  this.historicPossesMinDateTime = minDateTime;
17608
17710
  this.historicPossesMaxDateTime = maxDateTime;
17609
- this.historicPossesLoading = false;
17610
- res(posses);
17711
+ res(positions);
17712
+ }
17713
+ catch (e) {
17714
+ console.error(e);
17715
+ res(false);
17611
17716
  }
17612
- res(false);
17613
17717
  });
17614
17718
  return await this.historicPossesLoadingProm;
17615
17719
  };
17616
- getSeriesPosses().then((posses) => {
17720
+ // Last known timeline range. Helps us detect changes.
17721
+ let lastStartTime = this.viewer.clock.startTime.toString();
17722
+ let lastStopTime = this.viewer.clock.stopTime.toString();
17723
+ let clockTickRemoval;
17724
+ getInitialPosses().then((posses) => {
17617
17725
  if (this.disposed || posses === false) {
17618
17726
  return;
17619
17727
  }
17620
- let animation = new exports.CesiumAnimatedProperty.AnimateTPositionSeries({
17728
+ // Generate animation utility for the initial set of positions.
17729
+ this.historicAnimation = new exports.CesiumAnimatedProperty.AnimateTPositionSeries({
17621
17730
  viewer: this.viewer,
17622
17731
  posses: posses,
17623
17732
  onUpdate: (pos3d, heading) => {
17624
17733
  if (this.disposed) {
17625
17734
  return;
17626
17735
  }
17736
+ // Jank code that hacks the position to be different.
17737
+ // This can mess up our panels so we'll need to disable editing until a better system is in place.
17627
17738
  const location = Cesium.Cartographic.fromCartesian(pos3d);
17628
17739
  const lat = Cesium.Math.toDegrees(location.latitude);
17629
17740
  const lon = Cesium.Math.toDegrees(location.longitude);
@@ -17650,12 +17761,35 @@
17650
17761
  }
17651
17762
  };
17652
17763
  this.applyCoords(tileset, coords);
17764
+ },
17765
+ onRangeChange: () => {
17766
+ checkTimelineRange();
17767
+ },
17768
+ onDone: () => {
17769
+ if (this.viewerDateTimeChangeRemoval) {
17770
+ this.viewerDateTimeChangeRemoval();
17771
+ this.viewerDateTimeChangeRemoval = null;
17772
+ }
17773
+ }
17774
+ });
17775
+ // When the clock changes we'll see if the range is different and queue a request for the difference.
17776
+ clockTickRemoval = this.viewer.clock.onTick.addEventListener(() => {
17777
+ const startTime = this.viewer.clock.startTime.toString();
17778
+ const stopTime = this.viewer.clock.stopTime.toString();
17779
+ if (startTime !== lastStartTime || stopTime !== lastStopTime) {
17780
+ lastStartTime = startTime;
17781
+ lastStopTime = stopTime;
17782
+ this.historicAnimation.onRangeChange();
17653
17783
  }
17654
17784
  });
17655
17785
  this.viewerDateTimeChangeRemoval = () => {
17656
- if (animation) {
17657
- animation.stop();
17658
- animation = null;
17786
+ if (this.historicAnimation) {
17787
+ this.historicAnimation.stop();
17788
+ this.historicAnimation = null;
17789
+ }
17790
+ if (clockTickRemoval) {
17791
+ clockTickRemoval();
17792
+ clockTickRemoval = null;
17659
17793
  }
17660
17794
  };
17661
17795
  });
@@ -24588,7 +24722,8 @@
24588
24722
  end: null,
24589
24723
  playing: false,
24590
24724
  speed: 1,
24591
- start: null
24725
+ start: null,
24726
+ live: false
24592
24727
  };
24593
24728
  }
24594
24729
  exports.ViewUtils.SetTimeDetails({
@@ -24597,7 +24732,8 @@
24597
24732
  currentTimeIso: dateTime,
24598
24733
  startTimeIso: timeline.start,
24599
24734
  stopTimeIso: timeline.end,
24600
- isAnimating: timeline.playing
24735
+ isAnimating: timeline.playing,
24736
+ isLive: timeline.live
24601
24737
  });
24602
24738
  // Newer version that has states per Entity ID + Menu Item ID.
24603
24739
  let states = bSettings === null || bSettings === void 0 ? void 0 : bSettings.states;
@@ -30404,13 +30540,17 @@
30404
30540
  }
30405
30541
  }
30406
30542
 
30407
- const VERSION = "5.4.0";
30543
+ const VERSION = "5.4.2";
30408
30544
 
30409
30545
  exports.VERSION = VERSION;
30410
30546
  exports.isHistoricMetadataChanged = isHistoricMetadataChanged;
30411
30547
  exports.CesiumParabola = CesiumParabola;
30412
30548
  exports.CESIUM_INSPECTOR_KEY = CESIUM_INSPECTOR_KEY;
30413
30549
  exports.CESIUM_TIMELINE_KEY = CESIUM_TIMELINE_KEY;
30550
+ exports.CESIUM_TIMELINE_LIVE_KEY = CESIUM_TIMELINE_LIVE_KEY;
30551
+ exports.CESIUM_TIMELINE_LIVE_PADDING_FORWARD_KEY = CESIUM_TIMELINE_LIVE_PADDING_FORWARD_KEY;
30552
+ exports.CESIUM_TIMELINE_LIVE_PADDING_BACKWARD_KEY = CESIUM_TIMELINE_LIVE_PADDING_BACKWARD_KEY;
30553
+ exports.CESIUM_TIMELINE_INTERVAL_KEY = CESIUM_TIMELINE_INTERVAL_KEY;
30414
30554
  exports.Draw3dPolygon = Draw3dPolygon;
30415
30555
  exports.Draw3dPolyline = Draw3dPolyline;
30416
30556
  exports.VIEWER_BOOKMARKS_WIDGET_KEY = VIEWER_BOOKMARKS_WIDGET_KEY;