bruce-cesium 7.2.0 → 7.2.1

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 (25) hide show
  1. package/dist/bruce-cesium.es5.js +574 -280
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +572 -278
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/rendering/render-managers/common/historic-utils.js +471 -0
  7. package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -0
  8. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +14 -124
  9. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  10. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +16 -13
  11. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  12. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +31 -109
  13. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  14. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +23 -7
  15. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  16. package/dist/lib/rendering/tileset-styler.js +28 -24
  17. package/dist/lib/rendering/tileset-styler.js.map +1 -1
  18. package/dist/types/bruce-cesium.d.ts +1 -1
  19. package/dist/types/rendering/render-managers/common/historic-utils.d.ts +159 -0
  20. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +1 -0
  21. package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +5 -0
  22. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +1 -0
  23. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +2 -0
  24. package/dist/types/rendering/tileset-styler.d.ts +2 -0
  25. package/package.json +2 -2
@@ -10700,6 +10700,468 @@
10700
10700
  }
10701
10701
  }
10702
10702
 
10703
+ /**
10704
+ * How often a historic refresh is worth running, and which Entities it should ask about first.
10705
+ *
10706
+ * A handful of tracked vehicles can afford to re-read on almost every clock tick. Thousands of
10707
+ * parcels cannot, and refreshing them that often was never buying anything since the answer barely
10708
+ * changes between ticks.
10709
+ */
10710
+ var HistoricFetchOrder;
10711
+ (function (HistoricFetchOrder) {
10712
+ // The cadence for a set small enough that latency is what the user notices.
10713
+ HistoricFetchOrder.MIN_INTERVAL_MS = 250;
10714
+ // The cadence for a set large enough that the request itself is what the user notices.
10715
+ HistoricFetchOrder.MAX_INTERVAL_MS = 10000;
10716
+ // Below this many Entities the cadence stays at its fastest.
10717
+ HistoricFetchOrder.SMALL_SET = 30;
10718
+ // At and above this many it stays at its slowest.
10719
+ HistoricFetchOrder.LARGE_SET = 5000;
10720
+ /**
10721
+ * Returns how long to wait between historic refreshes for a set of this size.
10722
+ */
10723
+ function IntervalFor(entityCount, minMs = HistoricFetchOrder.MIN_INTERVAL_MS, maxMs = HistoricFetchOrder.MAX_INTERVAL_MS) {
10724
+ if (!(entityCount > HistoricFetchOrder.SMALL_SET)) {
10725
+ return minMs;
10726
+ }
10727
+ if (entityCount >= HistoricFetchOrder.LARGE_SET) {
10728
+ return maxMs;
10729
+ }
10730
+ // Log scaled, so going from 30 to 300 Entities costs more than going from 3000 to 5000.
10731
+ const progress = Math.log(entityCount / HistoricFetchOrder.SMALL_SET) / Math.log(HistoricFetchOrder.LARGE_SET / HistoricFetchOrder.SMALL_SET);
10732
+ return Math.round(minMs + ((maxMs - minMs) * progress));
10733
+ }
10734
+ HistoricFetchOrder.IntervalFor = IntervalFor;
10735
+ /**
10736
+ * Returns the Entities grouped into bands by distance from the camera, nearest band first.
10737
+ *
10738
+ * Bands rather than a true sort, so a small camera move does not reshuffle the whole queue and
10739
+ * restart it. What the user is looking at resolves first either way.
10740
+ */
10741
+ function ByCameraDistance(params) {
10742
+ var _a;
10743
+ const { viewer, entities } = params;
10744
+ const bands = params.bands || [500, 2000, 10000, 50000];
10745
+ if (!(entities === null || entities === void 0 ? void 0 : entities.length)) {
10746
+ return [];
10747
+ }
10748
+ const cameraPos = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.camera) === null || _a === void 0 ? void 0 : _a.position;
10749
+ if (!cameraPos) {
10750
+ return [entities];
10751
+ }
10752
+ // One band past the named bounds, for everything beyond the last of them.
10753
+ const banded = bands.map(() => []).concat([[]]);
10754
+ for (const entity of entities) {
10755
+ const pos = SafePos(entity, viewer);
10756
+ if (!pos) {
10757
+ banded[banded.length - 1].push(entity);
10758
+ continue;
10759
+ }
10760
+ const distance = Cesium.Cartesian3.distance(cameraPos, pos);
10761
+ let index = bands.findIndex(bound => distance <= bound);
10762
+ if (index < 0) {
10763
+ index = banded.length - 1;
10764
+ }
10765
+ banded[index].push(entity);
10766
+ }
10767
+ return banded.filter(band => band.length > 0);
10768
+ }
10769
+ HistoricFetchOrder.ByCameraDistance = ByCameraDistance;
10770
+ /*
10771
+ * Returns an Entity's world position, or null when it has none to place it by.
10772
+ */
10773
+ function SafePos(entity, viewer) {
10774
+ try {
10775
+ const pos = exports.EntityUtils.GetPos({
10776
+ entity: entity,
10777
+ viewer: viewer,
10778
+ allowRendered: true
10779
+ });
10780
+ if (!pos || isNaN(pos.x) || isNaN(pos.y) || isNaN(pos.z)) {
10781
+ return null;
10782
+ }
10783
+ return pos;
10784
+ }
10785
+ catch {
10786
+ return null;
10787
+ }
10788
+ }
10789
+ })(HistoricFetchOrder || (HistoricFetchOrder = {}));
10790
+ /**
10791
+ * Decides what slice of history is worth fetching for the clock as it is currently moving.
10792
+ */
10793
+ var HistoricFetchPlan;
10794
+ (function (HistoricFetchPlan) {
10795
+ // How many seconds of playback the detail window tries to stay ahead by.
10796
+ HistoricFetchPlan.DEFAULT_LEAD_SECONDS = 10;
10797
+ // The detail window never shrinks below this, so a paused clock still has something around it.
10798
+ HistoricFetchPlan.DEFAULT_MIN_DETAIL_MS = 60 * 1000;
10799
+ // Nor grows past it, which is what stops a fast scrub asking for the whole archive.
10800
+ HistoricFetchPlan.DEFAULT_MAX_DETAIL_MS = 6 * 60 * 60 * 1000;
10801
+ // Records the coarse pass may spend describing the whole range.
10802
+ HistoricFetchPlan.DEFAULT_SAMPLE = 200;
10803
+ /*
10804
+ * Returns how wide the detail window should be for the rate the clock is moving at.
10805
+ */
10806
+ function DetailSpanFor(trajectory, leadSeconds, minMs, maxMs) {
10807
+ const rate = Math.abs((trajectory === null || trajectory === void 0 ? void 0 : trajectory.ratePerSecondMs) || 0);
10808
+ const span = rate * leadSeconds;
10809
+ if (!(span > minMs)) {
10810
+ return minMs;
10811
+ }
10812
+ return Math.min(span, maxMs);
10813
+ }
10814
+ HistoricFetchPlan.DetailSpanFor = DetailSpanFor;
10815
+ /**
10816
+ * Returns the detail window and the coarse pass for the clock as it stands.
10817
+ */
10818
+ function Plan(params) {
10819
+ var _a;
10820
+ const { currentTime, windowStart, windowStop, trajectory, leadSeconds = HistoricFetchPlan.DEFAULT_LEAD_SECONDS, minDetailMs = HistoricFetchPlan.DEFAULT_MIN_DETAIL_MS, maxDetailMs = HistoricFetchPlan.DEFAULT_MAX_DETAIL_MS, sample = HistoricFetchPlan.DEFAULT_SAMPLE } = params;
10821
+ if (!(windowStop > windowStart)) {
10822
+ return { detail: null, coarse: null };
10823
+ }
10824
+ // A range that fits the detail budget has nothing worth sampling: reading it whole is both
10825
+ // cheaper than two requests and better than an approximation of it.
10826
+ if ((windowStop - windowStart) <= maxDetailMs) {
10827
+ return { detail: { start: windowStart, stop: windowStop }, coarse: null };
10828
+ }
10829
+ const span = DetailSpanFor(trajectory, leadSeconds, minDetailMs, maxDetailMs);
10830
+ const direction = (_a = trajectory === null || trajectory === void 0 ? void 0 : trajectory.direction) !== null && _a !== void 0 ? _a : 0;
10831
+ // Time running forward only ever reveals what is ahead, so the window leads rather than centres.
10832
+ // A little is still kept behind so a record just passed can still be interpolated from.
10833
+ const behind = direction === 0 ? span / 2 : span * 0.2;
10834
+ const ahead = direction === 0 ? span / 2 : span * 0.8;
10835
+ let start = direction < 0 ? currentTime - ahead : currentTime - behind;
10836
+ let stop = direction < 0 ? currentTime + behind : currentTime + ahead;
10837
+ start = Math.max(windowStart, start);
10838
+ stop = Math.min(windowStop, stop);
10839
+ if (!(stop > start)) {
10840
+ return { detail: null, coarse: { start: windowStart, stop: windowStop, sample } };
10841
+ }
10842
+ const detail = { start, stop };
10843
+ // Nothing to be coarse about once the detail window is the whole range.
10844
+ if (start <= windowStart && stop >= windowStop) {
10845
+ return { detail, coarse: null };
10846
+ }
10847
+ return { detail, coarse: { start: windowStart, stop: windowStop, sample } };
10848
+ }
10849
+ HistoricFetchPlan.Plan = Plan;
10850
+ /**
10851
+ * Tracks which way the clock is moving and how fast, from the times it is observed at.
10852
+ * Playback and a user dragging the timeline both look the same here, which is the point.
10853
+ */
10854
+ class TrajectoryTracker {
10855
+ // Smooths the rate so one slow frame does not collapse the window.
10856
+ constructor(smoothing = 0.4) {
10857
+ this.smoothing = smoothing;
10858
+ this.lastTime = null;
10859
+ this.lastObservedAt = null;
10860
+ this.direction = 0;
10861
+ this.ratePerSecondMs = 0;
10862
+ }
10863
+ /**
10864
+ * Records where the clock is now. observedAt is the wall clock, in milliseconds.
10865
+ */
10866
+ Observe(sceneTime, observedAt) {
10867
+ if (this.lastTime == null) {
10868
+ this.lastTime = sceneTime;
10869
+ this.lastObservedAt = observedAt;
10870
+ return;
10871
+ }
10872
+ const elapsed = observedAt - this.lastObservedAt;
10873
+ const moved = sceneTime - this.lastTime;
10874
+ this.lastTime = sceneTime;
10875
+ this.lastObservedAt = observedAt;
10876
+ if (!(elapsed > 0)) {
10877
+ return;
10878
+ }
10879
+ if (moved === 0) {
10880
+ this.direction = 0;
10881
+ this.ratePerSecondMs = 0;
10882
+ return;
10883
+ }
10884
+ this.direction = moved > 0 ? 1 : -1;
10885
+ const rate = Math.abs(moved) / (elapsed / 1000);
10886
+ this.ratePerSecondMs = this.ratePerSecondMs === 0
10887
+ ? rate
10888
+ : (this.ratePerSecondMs * (1 - this.smoothing)) + (rate * this.smoothing);
10889
+ }
10890
+ Get() {
10891
+ return { direction: this.direction, ratePerSecondMs: this.ratePerSecondMs };
10892
+ }
10893
+ Reset() {
10894
+ this.lastTime = null;
10895
+ this.lastObservedAt = null;
10896
+ this.direction = 0;
10897
+ this.ratePerSecondMs = 0;
10898
+ }
10899
+ }
10900
+ HistoricFetchPlan.TrajectoryTracker = TrajectoryTracker;
10901
+ })(HistoricFetchPlan || (HistoricFetchPlan = {}));
10902
+ /**
10903
+ * Tracks which time ranges of historic data have already been requested, and the records they
10904
+ * returned, so moving the clock only fetches the part that is genuinely new.
10905
+ *
10906
+ * Coverage is tracked by the ranges that were asked for, not by the timestamps that came back. A
10907
+ * window holding no records is still covered, otherwise an empty stretch is re-requested forever.
10908
+ */
10909
+ class HistoricRangeCache {
10910
+ // Ranges this far apart are treated as one, so a scrub does not leave a trail of slivers.
10911
+ constructor(mergeToleranceMs = 60000) {
10912
+ this.mergeToleranceMs = mergeToleranceMs;
10913
+ // Requested ranges, kept sorted and non-overlapping.
10914
+ this.held = [];
10915
+ // Ranges covered only by a sampled read, which describe their span without holding every record.
10916
+ this.heldCoarse = [];
10917
+ this.records = {};
10918
+ }
10919
+ /**
10920
+ * Returns the parts of the window that have not been requested yet.
10921
+ */
10922
+ MissingRanges(start, stop) {
10923
+ if (!(stop > start)) {
10924
+ return [];
10925
+ }
10926
+ const missing = [];
10927
+ let cursor = start;
10928
+ for (const range of this.held) {
10929
+ if (range.stop <= cursor) {
10930
+ continue;
10931
+ }
10932
+ if (range.start >= stop) {
10933
+ break;
10934
+ }
10935
+ if (range.start > cursor) {
10936
+ missing.push({ start: cursor, stop: Math.min(range.start, stop) });
10937
+ }
10938
+ cursor = Math.max(cursor, range.stop);
10939
+ if (cursor >= stop) {
10940
+ break;
10941
+ }
10942
+ }
10943
+ if (cursor < stop) {
10944
+ missing.push({ start: cursor, stop: stop });
10945
+ }
10946
+ return this.mergeAdjacent(missing);
10947
+ }
10948
+ /**
10949
+ * Marks a range as requested and merges the records it returned into what is already held.
10950
+ */
10951
+ Absorb(start, stop, recordsByIds, coarse = false) {
10952
+ if (stop > start) {
10953
+ if (coarse) {
10954
+ this.heldCoarse = this.mergeAdjacent([...this.heldCoarse, { start, stop }].sort((a, b) => a.start - b.start));
10955
+ }
10956
+ else {
10957
+ this.held = this.mergeAdjacent([...this.held, { start, stop }].sort((a, b) => a.start - b.start));
10958
+ }
10959
+ }
10960
+ if (!recordsByIds) {
10961
+ return;
10962
+ }
10963
+ for (const entityId of Object.keys(recordsByIds)) {
10964
+ const incoming = recordsByIds[entityId] || [];
10965
+ const current = this.records[entityId] || [];
10966
+ // Keyed by instant so a range fetched twice cannot double up its records.
10967
+ const byInstant = new Map();
10968
+ for (const record of current) {
10969
+ byInstant.set(new Date(record.dateTime).getTime(), record);
10970
+ }
10971
+ for (const record of incoming) {
10972
+ byInstant.set(new Date(record.dateTime).getTime(), record);
10973
+ }
10974
+ this.records[entityId] = Array.from(byInstant.values())
10975
+ .sort((a, b) => new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime());
10976
+ }
10977
+ }
10978
+ GetRecords() {
10979
+ return this.records;
10980
+ }
10981
+ /**
10982
+ * Returns whether the window is already covered in full.
10983
+ */
10984
+ Covers(start, stop) {
10985
+ return this.MissingRanges(start, stop).length === 0;
10986
+ }
10987
+ /**
10988
+ * Returns whether a sampled pass already describes the window.
10989
+ */
10990
+ CoversCoarse(start, stop) {
10991
+ if (!(stop > start)) {
10992
+ return true;
10993
+ }
10994
+ return this.heldCoarse.some(range => range.start <= start && range.stop >= stop);
10995
+ }
10996
+ Clear() {
10997
+ this.held = [];
10998
+ this.heldCoarse = [];
10999
+ this.records = {};
11000
+ }
11001
+ mergeAdjacent(ranges) {
11002
+ if (ranges.length <= 1) {
11003
+ return ranges;
11004
+ }
11005
+ const sorted = [...ranges].sort((a, b) => a.start - b.start);
11006
+ const merged = [];
11007
+ let current = { ...sorted[0] };
11008
+ for (let i = 1; i < sorted.length; i++) {
11009
+ const next = sorted[i];
11010
+ if (next.start - current.stop <= this.mergeToleranceMs) {
11011
+ current.stop = Math.max(current.stop, next.stop);
11012
+ }
11013
+ else {
11014
+ merged.push(current);
11015
+ current = { ...next };
11016
+ }
11017
+ }
11018
+ merged.push(current);
11019
+ return merged;
11020
+ }
11021
+ }
11022
+ /**
11023
+ * Pulls historic records for the visible clock range: what to ask for, in what order, and what has
11024
+ * already been asked for.
11025
+ */
11026
+ class HistoricGatherer {
11027
+ constructor(params) {
11028
+ this.params = params;
11029
+ this.trajectory = new HistoricFetchPlan.TrajectoryTracker();
11030
+ // Range key to the request in flight for it, so the same range is never asked for twice at once.
11031
+ this.inFlight = new Map();
11032
+ this.disposed = false;
11033
+ this.cache = new HistoricRangeCache();
11034
+ }
11035
+ get Records() {
11036
+ return this.cache.GetRecords();
11037
+ }
11038
+ Clear() {
11039
+ this.cache.Clear();
11040
+ this.trajectory.Reset();
11041
+ this.inFlight.clear();
11042
+ }
11043
+ Dispose() {
11044
+ this.disposed = true;
11045
+ this.Clear();
11046
+ }
11047
+ /**
11048
+ * Returns how long to wait between refreshes for a set of this size.
11049
+ */
11050
+ IntervalFor(entityCount) {
11051
+ return HistoricFetchOrder.IntervalFor(entityCount);
11052
+ }
11053
+ /**
11054
+ * Fetches whatever the current clock range needs that is not already held.
11055
+ * Returns whether anything was requested, alongside every record held for these Entities.
11056
+ */
11057
+ async Gather(entities) {
11058
+ var _a;
11059
+ if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
11060
+ return { fetched: false, recordsByIds: this.cache.GetRecords() };
11061
+ }
11062
+ const { viewer } = this.params;
11063
+ const windowStart = new Date(viewer.clock.startTime.toString()).getTime();
11064
+ const windowStop = new Date(viewer.clock.stopTime.toString()).getTime();
11065
+ const currentTime = new Date(viewer.clock.currentTime.toString()).getTime();
11066
+ this.trajectory.Observe(currentTime, Date.now());
11067
+ const liveEdge = exports.ViewUtils.GetTimeDetails({ viewer }).isLive ? currentTime : null;
11068
+ const liveTail = (_a = this.params.liveTailMs) !== null && _a !== void 0 ? _a : HistoricGatherer.LIVE_TAIL_MS;
11069
+ const plan = HistoricFetchPlan.Plan({
11070
+ currentTime,
11071
+ windowStart,
11072
+ windowStop,
11073
+ trajectory: this.trajectory.Get(),
11074
+ sample: this.params.sample
11075
+ });
11076
+ let fetched = false;
11077
+ if (plan.coarse && !this.cache.CoversCoarse(plan.coarse.start, plan.coarse.stop)) {
11078
+ const records = await this.request(plan.coarse.start, plan.coarse.stop, entities, plan.coarse.sample);
11079
+ if (this.disposed) {
11080
+ return { fetched, recordsByIds: this.cache.GetRecords() };
11081
+ }
11082
+ this.cache.Absorb(plan.coarse.start, this.holdTo(plan.coarse.stop, liveEdge, liveTail), records, true);
11083
+ fetched = true;
11084
+ }
11085
+ if (plan.detail) {
11086
+ const bands = entities.length > HistoricFetchOrder.SMALL_SET
11087
+ ? HistoricFetchOrder.ByCameraDistance({ viewer, entities })
11088
+ : [entities];
11089
+ for (const range of this.cache.MissingRanges(plan.detail.start, plan.detail.stop)) {
11090
+ if (this.disposed) {
11091
+ break;
11092
+ }
11093
+ for (const band of bands) {
11094
+ if (this.disposed) {
11095
+ break;
11096
+ }
11097
+ const records = await this.request(range.start, range.stop, band, 0);
11098
+ if (this.disposed) {
11099
+ break;
11100
+ }
11101
+ // Records land as each band arrives, while the range is only marked held once
11102
+ // every band has been asked for.
11103
+ this.cache.Absorb(0, 0, records);
11104
+ fetched = true;
11105
+ }
11106
+ if (this.disposed) {
11107
+ break;
11108
+ }
11109
+ const heldTo = this.holdTo(range.stop, liveEdge, liveTail);
11110
+ if (heldTo > range.start) {
11111
+ this.cache.Absorb(range.start, heldTo, {});
11112
+ }
11113
+ }
11114
+ }
11115
+ return { fetched, recordsByIds: this.cache.GetRecords() };
11116
+ }
11117
+ /*
11118
+ * Returns how much of a range that was just read may be marked as held.
11119
+ */
11120
+ holdTo(stop, liveEdge, liveTailMs) {
11121
+ if (liveEdge == null) {
11122
+ return stop;
11123
+ }
11124
+ return Math.min(stop, liveEdge - liveTailMs);
11125
+ }
11126
+ /*
11127
+ * Reads one range, joining a request already in flight for it rather than repeating it.
11128
+ */
11129
+ request(start, stop, entities, sample) {
11130
+ var _a;
11131
+ const padding = (_a = this.params.paddingMs) !== null && _a !== void 0 ? _a : 0;
11132
+ const dateTimeFrom = new Date(start - padding).toISOString();
11133
+ const dateTimeTo = new Date(stop + padding).toISOString();
11134
+ const entityIds = entities.map(x => { var _a; return (_a = x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }).filter(x => !!x);
11135
+ const key = `${dateTimeFrom}|${dateTimeTo}|${sample}|${entityIds.join(",")}`;
11136
+ const existing = this.inFlight.get(key);
11137
+ if (existing) {
11138
+ return existing;
11139
+ }
11140
+ const prom = BModels.EntityHistoricData.GetRecordsByEntity({
11141
+ attrKey: this.params.getAttrKey(),
11142
+ dateTimeFrom,
11143
+ dateTimeTo,
11144
+ entityIds,
11145
+ sample: sample > 0 ? sample : undefined,
11146
+ api: this.params.getApi()
11147
+ }).then((res) => {
11148
+ const records = res.recordsByIds || {};
11149
+ if (!this.disposed && this.params.onRecords) {
11150
+ this.params.onRecords(records);
11151
+ }
11152
+ return records;
11153
+ }).finally(() => {
11154
+ this.inFlight.delete(key);
11155
+ });
11156
+ this.inFlight.set(key, prom);
11157
+ return prom;
11158
+ }
11159
+ }
11160
+ // How far back from the live edge is left unheld, so a record written moments ago still falls in
11161
+ // something that gets asked for again. It has to outlast a write, a commit and a refresh, plus
11162
+ // whatever the writer's clock and this one disagree by.
11163
+ HistoricGatherer.LIVE_TAIL_MS = 15000;
11164
+
10703
11165
  const BATCH_SIZE = 500;
10704
11166
  const CHECK_BATCH_SIZE = 250;
10705
11167
  function getValue$2(viewer, obj) {
@@ -10784,6 +11246,13 @@
10784
11246
  this.item = item;
10785
11247
  this.visualsManager = visualsManager;
10786
11248
  this.onSeriesDiscovered = onSeriesDiscovered;
11249
+ this.historicGatherer = new HistoricGatherer({
11250
+ viewer: this.viewer,
11251
+ getApi: () => this.apiGetter.getApi(),
11252
+ getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
11253
+ // A second either side, to take in records sitting exactly on the boundary.
11254
+ paddingMs: 1000
11255
+ });
10787
11256
  this.useGeojson = item.renderAsGeojson == true;
10788
11257
  if (item.enableClustering) {
10789
11258
  this.clustering = new PointClustering(this.visualsManager, this.item.id);
@@ -11747,6 +12216,13 @@
11747
12216
  this.renderAsIndividualsChain = next;
11748
12217
  return next;
11749
12218
  }
12219
+ /**
12220
+ * Returns the historic records to interpolate between, fetching only what is not already held.
12221
+ */
12222
+ async gatherHistoric(entities) {
12223
+ const { recordsByIds } = await this.historicGatherer.Gather(entities);
12224
+ return recordsByIds;
12225
+ }
11750
12226
  async doRenderAsIndividuals(entities, force = false) {
11751
12227
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
11752
12228
  // When live we just want to show the latest pos.
@@ -11761,19 +12237,7 @@
11761
12237
  const isHistoric = ((_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic) || ((_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.historicAttrKey);
11762
12238
  // If we're interpolating then we request additional records to quickly swap between.
11763
12239
  if (!isLive && isHistoric && entities.length && this.item.historicInterpolation) {
11764
- // We'll add/remove 1 second to ensure we cover all records.
11765
- const startTmp = Cesium.JulianDate.toDate(this.viewer.clock.startTime);
11766
- const stopTmp = Cesium.JulianDate.toDate(this.viewer.clock.stopTime);
11767
- const startStr = new Date(startTmp.getTime() - 1000).toISOString();
11768
- const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
11769
- const historicData = await BModels.EntityHistoricData.GetList({
11770
- attrKey: this.item.BruceEntity.historicAttrKey,
11771
- dateTimeFrom: startStr,
11772
- dateTimeTo: stopStr,
11773
- entityIds: entities.map(x => x.Bruce.ID),
11774
- api: this.apiGetter.getApi()
11775
- });
11776
- entitiesHistoric = historicData.recordsByIds;
12240
+ entitiesHistoric = await this.gatherHistoric(entities);
11777
12241
  }
11778
12242
  if (this.disposed) {
11779
12243
  this.doDispose();
@@ -12887,6 +13351,13 @@
12887
13351
  this.monitor = monitor;
12888
13352
  this.item = item;
12889
13353
  this.visualsManager = visualsManager;
13354
+ this.historicGatherer = new HistoricGatherer({
13355
+ viewer: this.viewer,
13356
+ getApi: () => this.apiGetter.getApi(),
13357
+ getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
13358
+ // Padding either side of a requested range, for clock desync between us and the server.
13359
+ paddingMs: 15000
13360
+ });
12890
13361
  if (this.item.enableClustering) {
12891
13362
  this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
12892
13363
  }
@@ -13236,138 +13707,22 @@
13236
13707
  * @param entities
13237
13708
  */
13238
13709
  async getHistoricInfo(entities) {
13239
- // Time padding in milliseconds (15 seconds).
13240
- // Helps account for desync between client and server.
13241
- const TIME_PADDING_MS = 15000;
13242
- const minDateTimeStr = this.viewer.clock.startTime.toString();
13243
- const maxDateTimeStr = this.viewer.clock.stopTime.toString();
13244
- const minDateTime = new Date(minDateTimeStr).getTime();
13245
- const maxDateTime = new Date(maxDateTimeStr).getTime();
13246
- const tDetails = exports.ViewUtils.GetTimeDetails({
13247
- viewer: this.viewer,
13248
- });
13249
- const isLive = tDetails.isLive;
13250
- let rangesToRequest = [{
13251
- start: minDateTime,
13252
- stop: maxDateTime
13253
- }];
13254
- // If we already have cached data, determine what ranges we're missing.
13255
- if (Object.keys(this.entitiesHistoric).length >= entities.length) {
13256
- let foundMinDateTime = null;
13257
- let foundMaxDateTime = null;
13258
- // Find the min/max based on cached data.
13259
- // Since we set the values sorted, we only have to check the first and last records for each entity.
13260
- for (const entityId of Object.keys(this.entitiesHistoric)) {
13261
- const records = this.entitiesHistoric[entityId] || [];
13262
- if (records.length) {
13263
- const dateTime = new Date(records[0].dateTime).getTime();
13264
- if (foundMinDateTime == null || dateTime < foundMinDateTime) {
13265
- foundMinDateTime = dateTime;
13266
- }
13267
- const dateTime2 = new Date(records[records.length - 1].dateTime).getTime();
13268
- if (foundMaxDateTime == null || dateTime2 > foundMaxDateTime) {
13269
- foundMaxDateTime = dateTime2;
13270
- }
13271
- }
13272
- }
13273
- // Complete overlap - we already have all the data.
13274
- if (foundMinDateTime != null && foundMaxDateTime != null && foundMinDateTime <= minDateTime && foundMaxDateTime >= maxDateTime) {
13275
- return [false, this.entitiesHistoric];
13276
- }
13277
- // Calculate missing ranges.
13278
- rangesToRequest = [];
13279
- // Check if we need data before our cached range.
13280
- if (foundMinDateTime != null && foundMinDateTime > minDateTime) {
13281
- // When live we focus on getting the next range rather than old data.
13282
- // Ends up being one less request to perform every second.
13283
- if (!isLive || foundMinDateTime == null) {
13284
- rangesToRequest.push({
13285
- start: minDateTime,
13286
- stop: new Date(foundMinDateTime).getTime()
13287
- });
13288
- }
13289
- }
13290
- // Check if we need data after our cached range.
13291
- if (foundMaxDateTime != null && foundMaxDateTime < maxDateTime) {
13292
- rangesToRequest.push({
13293
- start: new Date(foundMaxDateTime).getTime(),
13294
- stop: maxDateTime
13295
- });
13296
- }
13297
- // If the ranges to request are within a tolerance, combine them.
13298
- if (rangesToRequest.length > 1) {
13299
- const TOLERANCE_MS = 60000; // 1 minute.
13300
- let currentRange = rangesToRequest[0];
13301
- for (let i = 1; i < rangesToRequest.length; i++) {
13302
- const nextRange = rangesToRequest[i];
13303
- if (nextRange.start - currentRange.stop <= TOLERANCE_MS) {
13304
- currentRange.stop = nextRange.stop;
13305
- }
13306
- else {
13307
- currentRange = nextRange;
13308
- }
13309
- }
13310
- }
13311
- }
13312
- const entityIds = entities.map(x => x.Bruce.ID);
13313
- const combined = { ...this.entitiesHistoric };
13314
- // Make requests for each missing range
13315
- for (const range of rangesToRequest) {
13316
- if (this.disposed) {
13317
- break;
13318
- }
13319
- const start = new Date(range.start - TIME_PADDING_MS);
13320
- const stop = new Date(range.stop + TIME_PADDING_MS);
13321
- const historicData = await BModels.EntityHistoricData.GetList({
13322
- attrKey: this.item.BruceEntity.historicAttrKey,
13323
- dateTimeFrom: start.toISOString(),
13324
- dateTimeTo: stop.toISOString(),
13325
- entityIds: entityIds,
13326
- api: this.apiGetter.getApi()
13327
- });
13328
- if (this.disposed) {
13329
- break;
13330
- }
13331
- // Merge the new data with existing data
13332
- const records = historicData.recordsByIds;
13333
- const recordsIds = Object.keys(records);
13334
- for (let i = 0; i < recordsIds.length; i++) {
13335
- const entityId = recordsIds[i];
13336
- const latest = records[entityId] || [];
13337
- const current = combined[entityId] || [];
13338
- // Use a Map to de-duplicate by timestamp.
13339
- const tmp = new Map();
13340
- for (let j = 0; j < current.length; j++) {
13341
- const record = current[j];
13342
- const dateTime = new Date(record.dateTime).getTime();
13343
- tmp.set(dateTime, record);
13344
- }
13345
- for (let j = 0; j < latest.length; j++) {
13346
- const record = latest[j];
13347
- const dateTime = new Date(record.dateTime).getTime();
13348
- tmp.set(dateTime, record);
13349
- }
13350
- // Convert to array and sort by date.
13351
- const sorted = Array.from(tmp.values()).sort((a, b) => {
13352
- return new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime();
13353
- });
13354
- combined[entityId] = sorted;
13355
- }
13356
- }
13710
+ const { fetched, recordsByIds } = await this.historicGatherer.Gather(entities);
13357
13711
  if (!this.disposed) {
13358
- this.entitiesHistoric = combined;
13712
+ this.entitiesHistoric = recordsByIds;
13359
13713
  }
13360
- return [rangesToRequest.length > 0, combined];
13714
+ return [fetched, recordsByIds];
13361
13715
  }
13362
13716
  viewerDateTimeSub() {
13363
13717
  var _a, _b;
13364
13718
  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) {
13365
13719
  return;
13366
13720
  }
13367
- const THROTTLE_INTERVAL = 2000;
13721
+ // Scaled by how many Entities are on screen rather than fixed, since the cost of a refresh
13722
+ // is the set size and a large set barely changes between ticks anyway.
13368
13723
  let queue = new BModels.DelayQueue(() => {
13369
13724
  this.onGetterUpdate(Object.keys(this.renderedEntities));
13370
- }, THROTTLE_INTERVAL, true);
13725
+ }, this.historicGatherer.IntervalFor(Object.keys(this.renderedEntities).length), true);
13371
13726
  let clockTickRemoval;
13372
13727
  let prevTick = this.viewer.clock.currentTime.toString();
13373
13728
  clockTickRemoval = this.viewer.clock.onTick.addEventListener(() => {
@@ -14055,6 +14410,9 @@
14055
14410
  // Indicates that we are retrieving historic records.
14056
14411
  // This means that the current scene's time is included in the request.
14057
14412
  this.historic = false;
14413
+ // When false the Entity data is still gathered and the regos still updated, but no colour is
14414
+ // applied. A historic item that did not ask for styling needs the first without the second.
14415
+ this.applyStyles = true;
14058
14416
  this.reRenderState = new EntityReRenderMaintainState();
14059
14417
  // More expensive process.
14060
14418
  // When an Entity is styled, the rego is reviewed and updated if needed.
@@ -14091,13 +14449,16 @@
14091
14449
  }
14092
14450
  Init(params) {
14093
14451
  var _a, _b;
14094
- let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic } = params;
14452
+ let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic, applyStyles } = params;
14095
14453
  this.viewer = viewer;
14096
14454
  this.api = api;
14097
14455
  this.cTileset = cTileset;
14098
14456
  this.register = register;
14099
14457
  this.menuItemId = menuItemId;
14100
14458
  this.historic = Boolean(historic);
14459
+ if (applyStyles != null) {
14460
+ this.applyStyles = Boolean(applyStyles);
14461
+ }
14101
14462
  if (expandSources != null) {
14102
14463
  this.expandSources = expandSources;
14103
14464
  }
@@ -14591,33 +14952,31 @@
14591
14952
  return;
14592
14953
  }
14593
14954
  const { settings: style, styleId: resolvedStyleId } = this.getTilesetFeatureStyleWithId(rego.entityTypeId);
14594
- const fillColorTrace = (style && ((_a = style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor)) ? BModels.Calculator.TraceGetColor(style.modelStyle.fillColor, data, tags) : { value: null, effective: null };
14595
- const bColor = fillColorTrace.value;
14596
- let cColor = null;
14597
- if (bColor == null) {
14598
- cColor = Cesium.Color.WHITE;
14599
- }
14600
- else {
14601
- cColor = colorToCColor$3(bColor);
14602
- }
14955
+ const fillColorTrace = (this.applyStyles && style && ((_a = style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor)) ? BModels.Calculator.TraceGetColor(style.modelStyle.fillColor, data, tags) : { value: null, effective: null };
14956
+ // Without styling the feature keeps whatever the tileset baked in. Falling through would paint
14957
+ // it the default white, which is a visible change for an item that only asked for historic.
14603
14958
  const override = this.overrideFeatureColor.get(rego.entityId) == true;
14604
- exports.CesiumEntityStyler.SetDefaultColor({
14605
- color: cColor,
14606
- entity: visual,
14607
- viewer: this.viewer,
14608
- override: override
14609
- });
14610
- this.overrideFeatureColor.set(rego.entityId, true);
14611
- if (((_b = data === null || data === void 0 ? void 0 : data.Bruce) === null || _b === void 0 ? void 0 : _b.InternalID) != null) {
14959
+ if (this.applyStyles) {
14960
+ const bColor = fillColorTrace.value;
14961
+ const cColor = bColor == null ? Cesium.Color.WHITE : colorToCColor$3(bColor);
14962
+ exports.CesiumEntityStyler.SetDefaultColor({
14963
+ color: cColor,
14964
+ entity: visual,
14965
+ viewer: this.viewer,
14966
+ override: override
14967
+ });
14968
+ this.overrideFeatureColor.set(rego.entityId, true);
14969
+ if (resolvedStyleId != null) {
14970
+ rego.styleId = resolvedStyleId;
14971
+ }
14972
+ rego.styleEffective = (_b = exports.StyleEffective.Combine([
14973
+ { key: "color", effective: fillColorTrace.effective }
14974
+ ])) !== null && _b !== void 0 ? _b : rego.styleEffective;
14975
+ }
14976
+ if (((_c = data === null || data === void 0 ? void 0 : data.Bruce) === null || _c === void 0 ? void 0 : _c.InternalID) != null) {
14612
14977
  rego.internalId = data.Bruce.InternalID;
14613
14978
  this.styledByInternalId.set(data.Bruce.InternalID, rego.entityId);
14614
14979
  }
14615
- if (resolvedStyleId != null) {
14616
- rego.styleId = resolvedStyleId;
14617
- }
14618
- rego.styleEffective = (_c = exports.StyleEffective.Combine([
14619
- { key: "color", effective: fillColorTrace.effective }
14620
- ])) !== null && _c !== void 0 ? _c : rego.styleEffective;
14621
14980
  this.styledEntityIds.set(rego.entityId, true);
14622
14981
  this._styleProgressQueue.Call();
14623
14982
  // Since we only need to update it for scenarios right now.
@@ -15582,6 +15941,7 @@
15582
15941
  // Series of points to help interpolate movement when the timeline changes.
15583
15942
  this.historicPossesInitialLoaded = false;
15584
15943
  this.historicAnimation = null;
15944
+ this.historicGatherer = null;
15585
15945
  this.historicPossesLoadingProm = null;
15586
15946
  // Queue of loaded in features that we haven't processed yet.
15587
15947
  this.featureQueue = [];
@@ -16619,120 +16979,41 @@
16619
16979
  const api = this.getters.GetBruceApi({
16620
16980
  accountId: accountId
16621
16981
  });
16622
- // Time padding in milliseconds (15 seconds).
16623
- // Helps account for desync between client and server.
16624
- const TIME_PADDING_MS = 15000;
16625
- // 'start-stop' time string that maps to a pending request.
16626
- // Helps us avoid repeated requests that are the same.
16627
- const pendingRequests = new Map();
16628
- /**
16629
- * Returns a list of historic positions for a given time range.
16630
- * @param startStr
16631
- * @param stopStr
16632
- * @returns
16982
+ // The root Entity is the only one here, so nothing is banded and the plan sees a set of one.
16983
+ const rootEntity = { Bruce: { ID: this.rootId } };
16984
+ /*
16985
+ * Turns historic records into the animated positions this tileset moves along.
16633
16986
  */
16634
- const getPossesForRange = async (startStr, stopStr) => {
16635
- const requestKey = `${startStr}-${stopStr}`;
16636
- if (pendingRequests.has(requestKey)) {
16637
- return pendingRequests.get(requestKey);
16638
- }
16639
- const requestPromise = new Promise(async (res) => {
16640
- try {
16641
- const historicData = await BModels.EntityHistoricData.GetList({
16642
- attrKey: null,
16643
- dateTimeFrom: startStr,
16644
- dateTimeTo: stopStr,
16645
- entityIds: [this.rootId],
16646
- api: api
16647
- });
16648
- const posses = exports.CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, Cesium.HeightReference.NONE, Cesium.HeightReference.CLAMP_TO_GROUND, historicData.recordsByIds[this.rootId]);
16649
- res(posses);
16650
- }
16651
- catch (e) {
16652
- console.error(e);
16653
- res([]);
16654
- }
16655
- finally {
16656
- pendingRequests.delete(requestKey);
16657
- }
16658
- });
16659
- pendingRequests.set(requestKey, requestPromise);
16660
- return requestPromise;
16987
+ const toPosses = (records) => {
16988
+ return exports.CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, Cesium.HeightReference.NONE, Cesium.HeightReference.CLAMP_TO_GROUND, records);
16661
16989
  };
16990
+ this.historicGatherer = new HistoricGatherer({
16991
+ viewer: this.viewer,
16992
+ getApi: () => api,
16993
+ // The assembly root snapshots the whole Entity, so no key narrows it.
16994
+ getAttrKey: () => null,
16995
+ // Time padding in milliseconds (15 seconds).
16996
+ // Helps account for desync between client and server.
16997
+ paddingMs: 15000,
16998
+ onRecords: (recordsByIds) => {
16999
+ var _a;
17000
+ const records = recordsByIds[this.rootId];
17001
+ if (!(records === null || records === void 0 ? void 0 : records.length) || !((_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.addPositions)) {
17002
+ return;
17003
+ }
17004
+ this.historicAnimation.addPositions(toPosses(records));
17005
+ }
17006
+ });
16662
17007
  /**
16663
- * Checks if the current timeline range is not fully loaded and requests the difference.
16664
- * This will call getPossesForRange() if it needs more data.
17008
+ * Fetches whatever the current timeline range needs that is not already held.
17009
+ * Positions reach the animation through the gatherer's callback as each batch lands.
16665
17010
  * @returns
16666
17011
  */
16667
17012
  const checkTimelineRange = async () => {
16668
- // Current timeline range.
16669
- const minDateTime = new Date(this.viewer.clock.startTime.toString()).getTime();
16670
- const maxDateTime = new Date(this.viewer.clock.stopTime.toString()).getTime();
16671
- // What we have loaded.
16672
- const range = this.historicAnimation.getDateRange();
16673
- const foundMinDateTime = (range === null || range === void 0 ? void 0 : range.minDate) ? range.minDate.getTime() : null;
16674
- const foundMaxDateTime = (range === null || range === void 0 ? void 0 : range.maxDate) ? range.maxDate.getTime() : null;
16675
- // See if the current range is within the range we already have.
16676
- if (this.historicPossesInitialLoaded &&
16677
- foundMinDateTime &&
16678
- foundMaxDateTime &&
16679
- minDateTime >= foundMinDateTime &&
16680
- maxDateTime <= foundMaxDateTime) {
16681
- return;
16682
- }
16683
- const tDetails = exports.ViewUtils.GetTimeDetails({
16684
- viewer: this.viewer,
16685
- });
16686
- const isLive = tDetails.isLive;
16687
- // See if the requested range is before or after the range we have.
16688
- const fetchBefore = !foundMinDateTime || (!isLive && minDateTime < foundMinDateTime);
16689
- const fetchAfter = !foundMaxDateTime || maxDateTime > foundMaxDateTime;
16690
- if (!fetchBefore && !fetchAfter) {
16691
- // Already have the data we need.
17013
+ if (this.disposed) {
16692
17014
  return;
16693
17015
  }
16694
- // No known range so we need to fetch the whole thing.
16695
- if (!foundMinDateTime || !foundMaxDateTime) {
16696
- const startStr = new Date(minDateTime - TIME_PADDING_MS).toISOString();
16697
- const stopStr = new Date(maxDateTime + TIME_PADDING_MS).toISOString();
16698
- const newPositions = await getPossesForRange(startStr, stopStr);
16699
- if (this.disposed) {
16700
- return;
16701
- }
16702
- if (this.historicAnimation && this.historicAnimation.addPositions) {
16703
- this.historicAnimation.addPositions(newPositions);
16704
- }
16705
- }
16706
- else {
16707
- // The data we want is before the range we've currently loaded.
16708
- if (fetchBefore) {
16709
- // Calculate the missing difference and request it.
16710
- const startStr = new Date(minDateTime - TIME_PADDING_MS).toISOString();
16711
- const stopStr = new Date(foundMinDateTime + TIME_PADDING_MS).toISOString();
16712
- getPossesForRange(startStr, stopStr).then((newPositions) => {
16713
- if (this.disposed) {
16714
- return;
16715
- }
16716
- if (this.historicAnimation && this.historicAnimation.addPositions) {
16717
- this.historicAnimation.addPositions(newPositions);
16718
- }
16719
- });
16720
- }
16721
- // The data we want is after the range we've currently loaded.
16722
- if (fetchAfter) {
16723
- // Calculate the missing difference and request it.
16724
- const startStr = new Date(foundMaxDateTime - TIME_PADDING_MS).toISOString();
16725
- const stopStr = new Date(maxDateTime + TIME_PADDING_MS).toISOString();
16726
- getPossesForRange(startStr, stopStr).then((newPositions) => {
16727
- if (this.disposed) {
16728
- return;
16729
- }
16730
- if (this.historicAnimation && this.historicAnimation.addPositions) {
16731
- this.historicAnimation.addPositions(newPositions);
16732
- }
16733
- });
16734
- }
16735
- }
17016
+ await this.historicGatherer.Gather([rootEntity]);
16736
17017
  };
16737
17018
  /**
16738
17019
  * Requests the initial set of historic positions for the timeline range.
@@ -16746,11 +17027,8 @@
16746
17027
  res(false);
16747
17028
  return;
16748
17029
  }
16749
- const startTmp = Cesium.JulianDate.toDate(this.viewer.clock.startTime);
16750
- const stopTmp = Cesium.JulianDate.toDate(this.viewer.clock.stopTime);
16751
- const startStr = new Date(startTmp.getTime() - 1000).toISOString();
16752
- const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
16753
- const positions = await getPossesForRange(startStr, stopStr);
17030
+ const { recordsByIds } = await this.historicGatherer.Gather([rootEntity]);
17031
+ const positions = toPosses(recordsByIds[this.rootId] || []);
16754
17032
  if (this.disposed) {
16755
17033
  res(false);
16756
17034
  return;
@@ -19531,12 +19809,11 @@
19531
19809
  }
19532
19810
  this.viewer.scene.requestRender();
19533
19811
  exports.TilesetRenderEngine.OnTilesetReady(this.cTileset).then(() => {
19534
- var _a;
19535
19812
  try {
19536
19813
  if (this.disposed || this.viewer.isDestroyed()) {
19537
19814
  return;
19538
19815
  }
19539
- if (this.item.ApplyStyles) {
19816
+ if (Manager.NeedsStyler(this.item)) {
19540
19817
  const api = this.getters.GetBruceApi();
19541
19818
  this.styler.Init({
19542
19819
  viewer: this.viewer,
@@ -19547,7 +19824,8 @@
19547
19824
  expandSources: false,
19548
19825
  menuItemId: this.item.id,
19549
19826
  register: this.visualsManager,
19550
- historic: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic
19827
+ historic: Manager.IsHistoric(this.item),
19828
+ applyStyles: Boolean(this.item.ApplyStyles)
19551
19829
  });
19552
19830
  }
19553
19831
  this.onCTilesetLoad();
@@ -19754,6 +20032,19 @@
19754
20032
  get HandoffKey() {
19755
20033
  return Manager.GetHandoffKey(this.item);
19756
20034
  }
20035
+ /*
20036
+ * Returns whether the item asks for Entities as they were at the scene time.
20037
+ */
20038
+ static IsHistoric(item) {
20039
+ var _a, _b;
20040
+ return Boolean(((_a = item === null || item === void 0 ? void 0 : item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic) || ((_b = item === null || item === void 0 ? void 0 : item.BruceEntity) === null || _b === void 0 ? void 0 : _b.historicAttrKey));
20041
+ }
20042
+ /*
20043
+ * Returns whether the styler has to run, which it does for historic even without styling.
20044
+ */
20045
+ static NeedsStyler(item) {
20046
+ return Boolean(item === null || item === void 0 ? void 0 : item.ApplyStyles) || Manager.IsHistoric(item);
20047
+ }
19757
20048
  static GetHandoffKey(item) {
19758
20049
  var _a, _b;
19759
20050
  const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
@@ -19763,7 +20054,10 @@
19763
20054
  const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
19764
20055
  // Styled <-> unstyled has no cheap revert path, force a reload instead.
19765
20056
  const applyStyles = Boolean(item.ApplyStyles);
19766
- return `${accountId}:${tilesetId}:${applyStyles}`;
20057
+ // Historic drives a different gather,
20058
+ // so a handoff must not reuse a styler set up for the other mode.
20059
+ const historic = Manager.IsHistoric(item);
20060
+ return `${accountId}:${tilesetId}:${applyStyles}:${historic}`;
19767
20061
  }
19768
20062
  // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
19769
20063
  PrepareHandoff() {
@@ -19803,7 +20097,6 @@
19803
20097
  // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
19804
20098
  // Then re-parents every already-registered rego to this menu item and re-runs styling.
19805
20099
  AdoptHandoff(payload) {
19806
- var _a;
19807
20100
  this.cTileset = payload.cTileset;
19808
20101
  this.tileset = payload.tileset;
19809
20102
  this.typeId = payload.typeId;
@@ -19835,7 +20128,7 @@
19835
20128
  toPriority: this.renderPriority,
19836
20129
  requestRender: false
19837
20130
  });
19838
- if (this.item.ApplyStyles) {
20131
+ if (Manager.NeedsStyler(this.item)) {
19839
20132
  this.styler.Init({
19840
20133
  viewer: this.viewer,
19841
20134
  api: this.getters.GetBruceApi(),
@@ -19845,7 +20138,8 @@
19845
20138
  expandSources: false,
19846
20139
  menuItemId: this.item.id,
19847
20140
  register: this.visualsManager,
19848
- historic: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic
20141
+ historic: Manager.IsHistoric(this.item),
20142
+ applyStyles: Boolean(this.item.ApplyStyles)
19849
20143
  });
19850
20144
  this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
19851
20145
  }
@@ -43525,7 +43819,7 @@ void main() {
43525
43819
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
43526
43820
  })(exports.StyleUtils || (exports.StyleUtils = {}));
43527
43821
 
43528
- const VERSION = "7.2.0";
43822
+ const VERSION = "7.2.1";
43529
43823
  /**
43530
43824
  * Updates the environment instance used by bruce-cesium to one specified.
43531
43825
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.