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.
- package/dist/bruce-cesium.es5.js +574 -280
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +572 -278
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/rendering/render-managers/common/historic-utils.js +471 -0
- package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -0
- package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +14 -124
- package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +16 -13
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +31 -109
- package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
- package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +23 -7
- package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
- package/dist/lib/rendering/tileset-styler.js +28 -24
- package/dist/lib/rendering/tileset-styler.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/rendering/render-managers/common/historic-utils.d.ts +159 -0
- package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +1 -0
- package/dist/types/rendering/render-managers/entities/entities-render-manager.d.ts +5 -0
- package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +1 -0
- package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +2 -0
- package/dist/types/rendering/tileset-styler.d.ts +2 -0
- package/package.json +2 -2
package/dist/bruce-cesium.es5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Cesium from 'cesium';
|
|
2
|
-
import { Cartographic,
|
|
3
|
-
import { Cartes, Entity as Entity$1, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style,
|
|
2
|
+
import { Cartographic, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Cartesian2, Color, ColorMaterialProperty, Entity, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, DistanceDisplayCondition, JulianDate, Quaternion, HeadingPitchRoll, HeightReference, ColorBlendMode, ShadowMode, ClassificationType, Model, ArcType, CornerType, ConstantPositionProperty, HorizontalOrigin, VerticalOrigin, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneTransforms, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, Cesium3DTileset, OrthographicFrustum, EasingFunction, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Intersect, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, CustomDataSource, GeometryInstance, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
|
|
3
|
+
import { Cartes, Entity as Entity$1, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry as Geometry$1, Api, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, Bounds, Color as Color$1, DelayQueue, DataLab, BruceApi, EntityRelation, AccountConcept, RecordChangeFeed, EntitySource, Camera, ProgramKey, ProjectViewLegacyTile, Account, AccountLimits, Session, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, EntityRelationType, EntityHistoricData } from 'bruce-models';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Ensures a number is returned from a given value.
|
|
@@ -10721,6 +10721,468 @@ class PointClustering {
|
|
|
10721
10721
|
}
|
|
10722
10722
|
}
|
|
10723
10723
|
|
|
10724
|
+
/**
|
|
10725
|
+
* How often a historic refresh is worth running, and which Entities it should ask about first.
|
|
10726
|
+
*
|
|
10727
|
+
* A handful of tracked vehicles can afford to re-read on almost every clock tick. Thousands of
|
|
10728
|
+
* parcels cannot, and refreshing them that often was never buying anything since the answer barely
|
|
10729
|
+
* changes between ticks.
|
|
10730
|
+
*/
|
|
10731
|
+
var HistoricFetchOrder;
|
|
10732
|
+
(function (HistoricFetchOrder) {
|
|
10733
|
+
// The cadence for a set small enough that latency is what the user notices.
|
|
10734
|
+
HistoricFetchOrder.MIN_INTERVAL_MS = 250;
|
|
10735
|
+
// The cadence for a set large enough that the request itself is what the user notices.
|
|
10736
|
+
HistoricFetchOrder.MAX_INTERVAL_MS = 10000;
|
|
10737
|
+
// Below this many Entities the cadence stays at its fastest.
|
|
10738
|
+
HistoricFetchOrder.SMALL_SET = 30;
|
|
10739
|
+
// At and above this many it stays at its slowest.
|
|
10740
|
+
HistoricFetchOrder.LARGE_SET = 5000;
|
|
10741
|
+
/**
|
|
10742
|
+
* Returns how long to wait between historic refreshes for a set of this size.
|
|
10743
|
+
*/
|
|
10744
|
+
function IntervalFor(entityCount, minMs = HistoricFetchOrder.MIN_INTERVAL_MS, maxMs = HistoricFetchOrder.MAX_INTERVAL_MS) {
|
|
10745
|
+
if (!(entityCount > HistoricFetchOrder.SMALL_SET)) {
|
|
10746
|
+
return minMs;
|
|
10747
|
+
}
|
|
10748
|
+
if (entityCount >= HistoricFetchOrder.LARGE_SET) {
|
|
10749
|
+
return maxMs;
|
|
10750
|
+
}
|
|
10751
|
+
// Log scaled, so going from 30 to 300 Entities costs more than going from 3000 to 5000.
|
|
10752
|
+
const progress = Math.log(entityCount / HistoricFetchOrder.SMALL_SET) / Math.log(HistoricFetchOrder.LARGE_SET / HistoricFetchOrder.SMALL_SET);
|
|
10753
|
+
return Math.round(minMs + ((maxMs - minMs) * progress));
|
|
10754
|
+
}
|
|
10755
|
+
HistoricFetchOrder.IntervalFor = IntervalFor;
|
|
10756
|
+
/**
|
|
10757
|
+
* Returns the Entities grouped into bands by distance from the camera, nearest band first.
|
|
10758
|
+
*
|
|
10759
|
+
* Bands rather than a true sort, so a small camera move does not reshuffle the whole queue and
|
|
10760
|
+
* restart it. What the user is looking at resolves first either way.
|
|
10761
|
+
*/
|
|
10762
|
+
function ByCameraDistance(params) {
|
|
10763
|
+
var _a;
|
|
10764
|
+
const { viewer, entities } = params;
|
|
10765
|
+
const bands = params.bands || [500, 2000, 10000, 50000];
|
|
10766
|
+
if (!(entities === null || entities === void 0 ? void 0 : entities.length)) {
|
|
10767
|
+
return [];
|
|
10768
|
+
}
|
|
10769
|
+
const cameraPos = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.camera) === null || _a === void 0 ? void 0 : _a.position;
|
|
10770
|
+
if (!cameraPos) {
|
|
10771
|
+
return [entities];
|
|
10772
|
+
}
|
|
10773
|
+
// One band past the named bounds, for everything beyond the last of them.
|
|
10774
|
+
const banded = bands.map(() => []).concat([[]]);
|
|
10775
|
+
for (const entity of entities) {
|
|
10776
|
+
const pos = SafePos(entity, viewer);
|
|
10777
|
+
if (!pos) {
|
|
10778
|
+
banded[banded.length - 1].push(entity);
|
|
10779
|
+
continue;
|
|
10780
|
+
}
|
|
10781
|
+
const distance = Cartesian3.distance(cameraPos, pos);
|
|
10782
|
+
let index = bands.findIndex(bound => distance <= bound);
|
|
10783
|
+
if (index < 0) {
|
|
10784
|
+
index = banded.length - 1;
|
|
10785
|
+
}
|
|
10786
|
+
banded[index].push(entity);
|
|
10787
|
+
}
|
|
10788
|
+
return banded.filter(band => band.length > 0);
|
|
10789
|
+
}
|
|
10790
|
+
HistoricFetchOrder.ByCameraDistance = ByCameraDistance;
|
|
10791
|
+
/*
|
|
10792
|
+
* Returns an Entity's world position, or null when it has none to place it by.
|
|
10793
|
+
*/
|
|
10794
|
+
function SafePos(entity, viewer) {
|
|
10795
|
+
try {
|
|
10796
|
+
const pos = EntityUtils.GetPos({
|
|
10797
|
+
entity: entity,
|
|
10798
|
+
viewer: viewer,
|
|
10799
|
+
allowRendered: true
|
|
10800
|
+
});
|
|
10801
|
+
if (!pos || isNaN(pos.x) || isNaN(pos.y) || isNaN(pos.z)) {
|
|
10802
|
+
return null;
|
|
10803
|
+
}
|
|
10804
|
+
return pos;
|
|
10805
|
+
}
|
|
10806
|
+
catch {
|
|
10807
|
+
return null;
|
|
10808
|
+
}
|
|
10809
|
+
}
|
|
10810
|
+
})(HistoricFetchOrder || (HistoricFetchOrder = {}));
|
|
10811
|
+
/**
|
|
10812
|
+
* Decides what slice of history is worth fetching for the clock as it is currently moving.
|
|
10813
|
+
*/
|
|
10814
|
+
var HistoricFetchPlan;
|
|
10815
|
+
(function (HistoricFetchPlan) {
|
|
10816
|
+
// How many seconds of playback the detail window tries to stay ahead by.
|
|
10817
|
+
HistoricFetchPlan.DEFAULT_LEAD_SECONDS = 10;
|
|
10818
|
+
// The detail window never shrinks below this, so a paused clock still has something around it.
|
|
10819
|
+
HistoricFetchPlan.DEFAULT_MIN_DETAIL_MS = 60 * 1000;
|
|
10820
|
+
// Nor grows past it, which is what stops a fast scrub asking for the whole archive.
|
|
10821
|
+
HistoricFetchPlan.DEFAULT_MAX_DETAIL_MS = 6 * 60 * 60 * 1000;
|
|
10822
|
+
// Records the coarse pass may spend describing the whole range.
|
|
10823
|
+
HistoricFetchPlan.DEFAULT_SAMPLE = 200;
|
|
10824
|
+
/*
|
|
10825
|
+
* Returns how wide the detail window should be for the rate the clock is moving at.
|
|
10826
|
+
*/
|
|
10827
|
+
function DetailSpanFor(trajectory, leadSeconds, minMs, maxMs) {
|
|
10828
|
+
const rate = Math.abs((trajectory === null || trajectory === void 0 ? void 0 : trajectory.ratePerSecondMs) || 0);
|
|
10829
|
+
const span = rate * leadSeconds;
|
|
10830
|
+
if (!(span > minMs)) {
|
|
10831
|
+
return minMs;
|
|
10832
|
+
}
|
|
10833
|
+
return Math.min(span, maxMs);
|
|
10834
|
+
}
|
|
10835
|
+
HistoricFetchPlan.DetailSpanFor = DetailSpanFor;
|
|
10836
|
+
/**
|
|
10837
|
+
* Returns the detail window and the coarse pass for the clock as it stands.
|
|
10838
|
+
*/
|
|
10839
|
+
function Plan(params) {
|
|
10840
|
+
var _a;
|
|
10841
|
+
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;
|
|
10842
|
+
if (!(windowStop > windowStart)) {
|
|
10843
|
+
return { detail: null, coarse: null };
|
|
10844
|
+
}
|
|
10845
|
+
// A range that fits the detail budget has nothing worth sampling: reading it whole is both
|
|
10846
|
+
// cheaper than two requests and better than an approximation of it.
|
|
10847
|
+
if ((windowStop - windowStart) <= maxDetailMs) {
|
|
10848
|
+
return { detail: { start: windowStart, stop: windowStop }, coarse: null };
|
|
10849
|
+
}
|
|
10850
|
+
const span = DetailSpanFor(trajectory, leadSeconds, minDetailMs, maxDetailMs);
|
|
10851
|
+
const direction = (_a = trajectory === null || trajectory === void 0 ? void 0 : trajectory.direction) !== null && _a !== void 0 ? _a : 0;
|
|
10852
|
+
// Time running forward only ever reveals what is ahead, so the window leads rather than centres.
|
|
10853
|
+
// A little is still kept behind so a record just passed can still be interpolated from.
|
|
10854
|
+
const behind = direction === 0 ? span / 2 : span * 0.2;
|
|
10855
|
+
const ahead = direction === 0 ? span / 2 : span * 0.8;
|
|
10856
|
+
let start = direction < 0 ? currentTime - ahead : currentTime - behind;
|
|
10857
|
+
let stop = direction < 0 ? currentTime + behind : currentTime + ahead;
|
|
10858
|
+
start = Math.max(windowStart, start);
|
|
10859
|
+
stop = Math.min(windowStop, stop);
|
|
10860
|
+
if (!(stop > start)) {
|
|
10861
|
+
return { detail: null, coarse: { start: windowStart, stop: windowStop, sample } };
|
|
10862
|
+
}
|
|
10863
|
+
const detail = { start, stop };
|
|
10864
|
+
// Nothing to be coarse about once the detail window is the whole range.
|
|
10865
|
+
if (start <= windowStart && stop >= windowStop) {
|
|
10866
|
+
return { detail, coarse: null };
|
|
10867
|
+
}
|
|
10868
|
+
return { detail, coarse: { start: windowStart, stop: windowStop, sample } };
|
|
10869
|
+
}
|
|
10870
|
+
HistoricFetchPlan.Plan = Plan;
|
|
10871
|
+
/**
|
|
10872
|
+
* Tracks which way the clock is moving and how fast, from the times it is observed at.
|
|
10873
|
+
* Playback and a user dragging the timeline both look the same here, which is the point.
|
|
10874
|
+
*/
|
|
10875
|
+
class TrajectoryTracker {
|
|
10876
|
+
// Smooths the rate so one slow frame does not collapse the window.
|
|
10877
|
+
constructor(smoothing = 0.4) {
|
|
10878
|
+
this.smoothing = smoothing;
|
|
10879
|
+
this.lastTime = null;
|
|
10880
|
+
this.lastObservedAt = null;
|
|
10881
|
+
this.direction = 0;
|
|
10882
|
+
this.ratePerSecondMs = 0;
|
|
10883
|
+
}
|
|
10884
|
+
/**
|
|
10885
|
+
* Records where the clock is now. observedAt is the wall clock, in milliseconds.
|
|
10886
|
+
*/
|
|
10887
|
+
Observe(sceneTime, observedAt) {
|
|
10888
|
+
if (this.lastTime == null) {
|
|
10889
|
+
this.lastTime = sceneTime;
|
|
10890
|
+
this.lastObservedAt = observedAt;
|
|
10891
|
+
return;
|
|
10892
|
+
}
|
|
10893
|
+
const elapsed = observedAt - this.lastObservedAt;
|
|
10894
|
+
const moved = sceneTime - this.lastTime;
|
|
10895
|
+
this.lastTime = sceneTime;
|
|
10896
|
+
this.lastObservedAt = observedAt;
|
|
10897
|
+
if (!(elapsed > 0)) {
|
|
10898
|
+
return;
|
|
10899
|
+
}
|
|
10900
|
+
if (moved === 0) {
|
|
10901
|
+
this.direction = 0;
|
|
10902
|
+
this.ratePerSecondMs = 0;
|
|
10903
|
+
return;
|
|
10904
|
+
}
|
|
10905
|
+
this.direction = moved > 0 ? 1 : -1;
|
|
10906
|
+
const rate = Math.abs(moved) / (elapsed / 1000);
|
|
10907
|
+
this.ratePerSecondMs = this.ratePerSecondMs === 0
|
|
10908
|
+
? rate
|
|
10909
|
+
: (this.ratePerSecondMs * (1 - this.smoothing)) + (rate * this.smoothing);
|
|
10910
|
+
}
|
|
10911
|
+
Get() {
|
|
10912
|
+
return { direction: this.direction, ratePerSecondMs: this.ratePerSecondMs };
|
|
10913
|
+
}
|
|
10914
|
+
Reset() {
|
|
10915
|
+
this.lastTime = null;
|
|
10916
|
+
this.lastObservedAt = null;
|
|
10917
|
+
this.direction = 0;
|
|
10918
|
+
this.ratePerSecondMs = 0;
|
|
10919
|
+
}
|
|
10920
|
+
}
|
|
10921
|
+
HistoricFetchPlan.TrajectoryTracker = TrajectoryTracker;
|
|
10922
|
+
})(HistoricFetchPlan || (HistoricFetchPlan = {}));
|
|
10923
|
+
/**
|
|
10924
|
+
* Tracks which time ranges of historic data have already been requested, and the records they
|
|
10925
|
+
* returned, so moving the clock only fetches the part that is genuinely new.
|
|
10926
|
+
*
|
|
10927
|
+
* Coverage is tracked by the ranges that were asked for, not by the timestamps that came back. A
|
|
10928
|
+
* window holding no records is still covered, otherwise an empty stretch is re-requested forever.
|
|
10929
|
+
*/
|
|
10930
|
+
class HistoricRangeCache {
|
|
10931
|
+
// Ranges this far apart are treated as one, so a scrub does not leave a trail of slivers.
|
|
10932
|
+
constructor(mergeToleranceMs = 60000) {
|
|
10933
|
+
this.mergeToleranceMs = mergeToleranceMs;
|
|
10934
|
+
// Requested ranges, kept sorted and non-overlapping.
|
|
10935
|
+
this.held = [];
|
|
10936
|
+
// Ranges covered only by a sampled read, which describe their span without holding every record.
|
|
10937
|
+
this.heldCoarse = [];
|
|
10938
|
+
this.records = {};
|
|
10939
|
+
}
|
|
10940
|
+
/**
|
|
10941
|
+
* Returns the parts of the window that have not been requested yet.
|
|
10942
|
+
*/
|
|
10943
|
+
MissingRanges(start, stop) {
|
|
10944
|
+
if (!(stop > start)) {
|
|
10945
|
+
return [];
|
|
10946
|
+
}
|
|
10947
|
+
const missing = [];
|
|
10948
|
+
let cursor = start;
|
|
10949
|
+
for (const range of this.held) {
|
|
10950
|
+
if (range.stop <= cursor) {
|
|
10951
|
+
continue;
|
|
10952
|
+
}
|
|
10953
|
+
if (range.start >= stop) {
|
|
10954
|
+
break;
|
|
10955
|
+
}
|
|
10956
|
+
if (range.start > cursor) {
|
|
10957
|
+
missing.push({ start: cursor, stop: Math.min(range.start, stop) });
|
|
10958
|
+
}
|
|
10959
|
+
cursor = Math.max(cursor, range.stop);
|
|
10960
|
+
if (cursor >= stop) {
|
|
10961
|
+
break;
|
|
10962
|
+
}
|
|
10963
|
+
}
|
|
10964
|
+
if (cursor < stop) {
|
|
10965
|
+
missing.push({ start: cursor, stop: stop });
|
|
10966
|
+
}
|
|
10967
|
+
return this.mergeAdjacent(missing);
|
|
10968
|
+
}
|
|
10969
|
+
/**
|
|
10970
|
+
* Marks a range as requested and merges the records it returned into what is already held.
|
|
10971
|
+
*/
|
|
10972
|
+
Absorb(start, stop, recordsByIds, coarse = false) {
|
|
10973
|
+
if (stop > start) {
|
|
10974
|
+
if (coarse) {
|
|
10975
|
+
this.heldCoarse = this.mergeAdjacent([...this.heldCoarse, { start, stop }].sort((a, b) => a.start - b.start));
|
|
10976
|
+
}
|
|
10977
|
+
else {
|
|
10978
|
+
this.held = this.mergeAdjacent([...this.held, { start, stop }].sort((a, b) => a.start - b.start));
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10981
|
+
if (!recordsByIds) {
|
|
10982
|
+
return;
|
|
10983
|
+
}
|
|
10984
|
+
for (const entityId of Object.keys(recordsByIds)) {
|
|
10985
|
+
const incoming = recordsByIds[entityId] || [];
|
|
10986
|
+
const current = this.records[entityId] || [];
|
|
10987
|
+
// Keyed by instant so a range fetched twice cannot double up its records.
|
|
10988
|
+
const byInstant = new Map();
|
|
10989
|
+
for (const record of current) {
|
|
10990
|
+
byInstant.set(new Date(record.dateTime).getTime(), record);
|
|
10991
|
+
}
|
|
10992
|
+
for (const record of incoming) {
|
|
10993
|
+
byInstant.set(new Date(record.dateTime).getTime(), record);
|
|
10994
|
+
}
|
|
10995
|
+
this.records[entityId] = Array.from(byInstant.values())
|
|
10996
|
+
.sort((a, b) => new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime());
|
|
10997
|
+
}
|
|
10998
|
+
}
|
|
10999
|
+
GetRecords() {
|
|
11000
|
+
return this.records;
|
|
11001
|
+
}
|
|
11002
|
+
/**
|
|
11003
|
+
* Returns whether the window is already covered in full.
|
|
11004
|
+
*/
|
|
11005
|
+
Covers(start, stop) {
|
|
11006
|
+
return this.MissingRanges(start, stop).length === 0;
|
|
11007
|
+
}
|
|
11008
|
+
/**
|
|
11009
|
+
* Returns whether a sampled pass already describes the window.
|
|
11010
|
+
*/
|
|
11011
|
+
CoversCoarse(start, stop) {
|
|
11012
|
+
if (!(stop > start)) {
|
|
11013
|
+
return true;
|
|
11014
|
+
}
|
|
11015
|
+
return this.heldCoarse.some(range => range.start <= start && range.stop >= stop);
|
|
11016
|
+
}
|
|
11017
|
+
Clear() {
|
|
11018
|
+
this.held = [];
|
|
11019
|
+
this.heldCoarse = [];
|
|
11020
|
+
this.records = {};
|
|
11021
|
+
}
|
|
11022
|
+
mergeAdjacent(ranges) {
|
|
11023
|
+
if (ranges.length <= 1) {
|
|
11024
|
+
return ranges;
|
|
11025
|
+
}
|
|
11026
|
+
const sorted = [...ranges].sort((a, b) => a.start - b.start);
|
|
11027
|
+
const merged = [];
|
|
11028
|
+
let current = { ...sorted[0] };
|
|
11029
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
11030
|
+
const next = sorted[i];
|
|
11031
|
+
if (next.start - current.stop <= this.mergeToleranceMs) {
|
|
11032
|
+
current.stop = Math.max(current.stop, next.stop);
|
|
11033
|
+
}
|
|
11034
|
+
else {
|
|
11035
|
+
merged.push(current);
|
|
11036
|
+
current = { ...next };
|
|
11037
|
+
}
|
|
11038
|
+
}
|
|
11039
|
+
merged.push(current);
|
|
11040
|
+
return merged;
|
|
11041
|
+
}
|
|
11042
|
+
}
|
|
11043
|
+
/**
|
|
11044
|
+
* Pulls historic records for the visible clock range: what to ask for, in what order, and what has
|
|
11045
|
+
* already been asked for.
|
|
11046
|
+
*/
|
|
11047
|
+
class HistoricGatherer {
|
|
11048
|
+
constructor(params) {
|
|
11049
|
+
this.params = params;
|
|
11050
|
+
this.trajectory = new HistoricFetchPlan.TrajectoryTracker();
|
|
11051
|
+
// Range key to the request in flight for it, so the same range is never asked for twice at once.
|
|
11052
|
+
this.inFlight = new Map();
|
|
11053
|
+
this.disposed = false;
|
|
11054
|
+
this.cache = new HistoricRangeCache();
|
|
11055
|
+
}
|
|
11056
|
+
get Records() {
|
|
11057
|
+
return this.cache.GetRecords();
|
|
11058
|
+
}
|
|
11059
|
+
Clear() {
|
|
11060
|
+
this.cache.Clear();
|
|
11061
|
+
this.trajectory.Reset();
|
|
11062
|
+
this.inFlight.clear();
|
|
11063
|
+
}
|
|
11064
|
+
Dispose() {
|
|
11065
|
+
this.disposed = true;
|
|
11066
|
+
this.Clear();
|
|
11067
|
+
}
|
|
11068
|
+
/**
|
|
11069
|
+
* Returns how long to wait between refreshes for a set of this size.
|
|
11070
|
+
*/
|
|
11071
|
+
IntervalFor(entityCount) {
|
|
11072
|
+
return HistoricFetchOrder.IntervalFor(entityCount);
|
|
11073
|
+
}
|
|
11074
|
+
/**
|
|
11075
|
+
* Fetches whatever the current clock range needs that is not already held.
|
|
11076
|
+
* Returns whether anything was requested, alongside every record held for these Entities.
|
|
11077
|
+
*/
|
|
11078
|
+
async Gather(entities) {
|
|
11079
|
+
var _a;
|
|
11080
|
+
if (this.disposed || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
|
|
11081
|
+
return { fetched: false, recordsByIds: this.cache.GetRecords() };
|
|
11082
|
+
}
|
|
11083
|
+
const { viewer } = this.params;
|
|
11084
|
+
const windowStart = new Date(viewer.clock.startTime.toString()).getTime();
|
|
11085
|
+
const windowStop = new Date(viewer.clock.stopTime.toString()).getTime();
|
|
11086
|
+
const currentTime = new Date(viewer.clock.currentTime.toString()).getTime();
|
|
11087
|
+
this.trajectory.Observe(currentTime, Date.now());
|
|
11088
|
+
const liveEdge = ViewUtils.GetTimeDetails({ viewer }).isLive ? currentTime : null;
|
|
11089
|
+
const liveTail = (_a = this.params.liveTailMs) !== null && _a !== void 0 ? _a : HistoricGatherer.LIVE_TAIL_MS;
|
|
11090
|
+
const plan = HistoricFetchPlan.Plan({
|
|
11091
|
+
currentTime,
|
|
11092
|
+
windowStart,
|
|
11093
|
+
windowStop,
|
|
11094
|
+
trajectory: this.trajectory.Get(),
|
|
11095
|
+
sample: this.params.sample
|
|
11096
|
+
});
|
|
11097
|
+
let fetched = false;
|
|
11098
|
+
if (plan.coarse && !this.cache.CoversCoarse(plan.coarse.start, plan.coarse.stop)) {
|
|
11099
|
+
const records = await this.request(plan.coarse.start, plan.coarse.stop, entities, plan.coarse.sample);
|
|
11100
|
+
if (this.disposed) {
|
|
11101
|
+
return { fetched, recordsByIds: this.cache.GetRecords() };
|
|
11102
|
+
}
|
|
11103
|
+
this.cache.Absorb(plan.coarse.start, this.holdTo(plan.coarse.stop, liveEdge, liveTail), records, true);
|
|
11104
|
+
fetched = true;
|
|
11105
|
+
}
|
|
11106
|
+
if (plan.detail) {
|
|
11107
|
+
const bands = entities.length > HistoricFetchOrder.SMALL_SET
|
|
11108
|
+
? HistoricFetchOrder.ByCameraDistance({ viewer, entities })
|
|
11109
|
+
: [entities];
|
|
11110
|
+
for (const range of this.cache.MissingRanges(plan.detail.start, plan.detail.stop)) {
|
|
11111
|
+
if (this.disposed) {
|
|
11112
|
+
break;
|
|
11113
|
+
}
|
|
11114
|
+
for (const band of bands) {
|
|
11115
|
+
if (this.disposed) {
|
|
11116
|
+
break;
|
|
11117
|
+
}
|
|
11118
|
+
const records = await this.request(range.start, range.stop, band, 0);
|
|
11119
|
+
if (this.disposed) {
|
|
11120
|
+
break;
|
|
11121
|
+
}
|
|
11122
|
+
// Records land as each band arrives, while the range is only marked held once
|
|
11123
|
+
// every band has been asked for.
|
|
11124
|
+
this.cache.Absorb(0, 0, records);
|
|
11125
|
+
fetched = true;
|
|
11126
|
+
}
|
|
11127
|
+
if (this.disposed) {
|
|
11128
|
+
break;
|
|
11129
|
+
}
|
|
11130
|
+
const heldTo = this.holdTo(range.stop, liveEdge, liveTail);
|
|
11131
|
+
if (heldTo > range.start) {
|
|
11132
|
+
this.cache.Absorb(range.start, heldTo, {});
|
|
11133
|
+
}
|
|
11134
|
+
}
|
|
11135
|
+
}
|
|
11136
|
+
return { fetched, recordsByIds: this.cache.GetRecords() };
|
|
11137
|
+
}
|
|
11138
|
+
/*
|
|
11139
|
+
* Returns how much of a range that was just read may be marked as held.
|
|
11140
|
+
*/
|
|
11141
|
+
holdTo(stop, liveEdge, liveTailMs) {
|
|
11142
|
+
if (liveEdge == null) {
|
|
11143
|
+
return stop;
|
|
11144
|
+
}
|
|
11145
|
+
return Math.min(stop, liveEdge - liveTailMs);
|
|
11146
|
+
}
|
|
11147
|
+
/*
|
|
11148
|
+
* Reads one range, joining a request already in flight for it rather than repeating it.
|
|
11149
|
+
*/
|
|
11150
|
+
request(start, stop, entities, sample) {
|
|
11151
|
+
var _a;
|
|
11152
|
+
const padding = (_a = this.params.paddingMs) !== null && _a !== void 0 ? _a : 0;
|
|
11153
|
+
const dateTimeFrom = new Date(start - padding).toISOString();
|
|
11154
|
+
const dateTimeTo = new Date(stop + padding).toISOString();
|
|
11155
|
+
const entityIds = entities.map(x => { var _a; return (_a = x.Bruce) === null || _a === void 0 ? void 0 : _a.ID; }).filter(x => !!x);
|
|
11156
|
+
const key = `${dateTimeFrom}|${dateTimeTo}|${sample}|${entityIds.join(",")}`;
|
|
11157
|
+
const existing = this.inFlight.get(key);
|
|
11158
|
+
if (existing) {
|
|
11159
|
+
return existing;
|
|
11160
|
+
}
|
|
11161
|
+
const prom = EntityHistoricData.GetRecordsByEntity({
|
|
11162
|
+
attrKey: this.params.getAttrKey(),
|
|
11163
|
+
dateTimeFrom,
|
|
11164
|
+
dateTimeTo,
|
|
11165
|
+
entityIds,
|
|
11166
|
+
sample: sample > 0 ? sample : undefined,
|
|
11167
|
+
api: this.params.getApi()
|
|
11168
|
+
}).then((res) => {
|
|
11169
|
+
const records = res.recordsByIds || {};
|
|
11170
|
+
if (!this.disposed && this.params.onRecords) {
|
|
11171
|
+
this.params.onRecords(records);
|
|
11172
|
+
}
|
|
11173
|
+
return records;
|
|
11174
|
+
}).finally(() => {
|
|
11175
|
+
this.inFlight.delete(key);
|
|
11176
|
+
});
|
|
11177
|
+
this.inFlight.set(key, prom);
|
|
11178
|
+
return prom;
|
|
11179
|
+
}
|
|
11180
|
+
}
|
|
11181
|
+
// How far back from the live edge is left unheld, so a record written moments ago still falls in
|
|
11182
|
+
// something that gets asked for again. It has to outlast a write, a commit and a refresh, plus
|
|
11183
|
+
// whatever the writer's clock and this one disagree by.
|
|
11184
|
+
HistoricGatherer.LIVE_TAIL_MS = 15000;
|
|
11185
|
+
|
|
10724
11186
|
const BATCH_SIZE = 500;
|
|
10725
11187
|
const CHECK_BATCH_SIZE = 250;
|
|
10726
11188
|
function getValue$2(viewer, obj) {
|
|
@@ -10810,6 +11272,13 @@ var EntitiesRenderManager;
|
|
|
10810
11272
|
this.item = item;
|
|
10811
11273
|
this.visualsManager = visualsManager;
|
|
10812
11274
|
this.onSeriesDiscovered = onSeriesDiscovered;
|
|
11275
|
+
this.historicGatherer = new HistoricGatherer({
|
|
11276
|
+
viewer: this.viewer,
|
|
11277
|
+
getApi: () => this.apiGetter.getApi(),
|
|
11278
|
+
getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
|
|
11279
|
+
// A second either side, to take in records sitting exactly on the boundary.
|
|
11280
|
+
paddingMs: 1000
|
|
11281
|
+
});
|
|
10813
11282
|
this.useGeojson = item.renderAsGeojson == true;
|
|
10814
11283
|
if (item.enableClustering) {
|
|
10815
11284
|
this.clustering = new PointClustering(this.visualsManager, this.item.id);
|
|
@@ -11773,6 +12242,13 @@ var EntitiesRenderManager;
|
|
|
11773
12242
|
this.renderAsIndividualsChain = next;
|
|
11774
12243
|
return next;
|
|
11775
12244
|
}
|
|
12245
|
+
/**
|
|
12246
|
+
* Returns the historic records to interpolate between, fetching only what is not already held.
|
|
12247
|
+
*/
|
|
12248
|
+
async gatherHistoric(entities) {
|
|
12249
|
+
const { recordsByIds } = await this.historicGatherer.Gather(entities);
|
|
12250
|
+
return recordsByIds;
|
|
12251
|
+
}
|
|
11776
12252
|
async doRenderAsIndividuals(entities, force = false) {
|
|
11777
12253
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
11778
12254
|
// When live we just want to show the latest pos.
|
|
@@ -11787,19 +12263,7 @@ var EntitiesRenderManager;
|
|
|
11787
12263
|
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);
|
|
11788
12264
|
// If we're interpolating then we request additional records to quickly swap between.
|
|
11789
12265
|
if (!isLive && isHistoric && entities.length && this.item.historicInterpolation) {
|
|
11790
|
-
|
|
11791
|
-
const startTmp = JulianDate.toDate(this.viewer.clock.startTime);
|
|
11792
|
-
const stopTmp = JulianDate.toDate(this.viewer.clock.stopTime);
|
|
11793
|
-
const startStr = new Date(startTmp.getTime() - 1000).toISOString();
|
|
11794
|
-
const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
|
|
11795
|
-
const historicData = await EntityHistoricData.GetList({
|
|
11796
|
-
attrKey: this.item.BruceEntity.historicAttrKey,
|
|
11797
|
-
dateTimeFrom: startStr,
|
|
11798
|
-
dateTimeTo: stopStr,
|
|
11799
|
-
entityIds: entities.map(x => x.Bruce.ID),
|
|
11800
|
-
api: this.apiGetter.getApi()
|
|
11801
|
-
});
|
|
11802
|
-
entitiesHistoric = historicData.recordsByIds;
|
|
12266
|
+
entitiesHistoric = await this.gatherHistoric(entities);
|
|
11803
12267
|
}
|
|
11804
12268
|
if (this.disposed) {
|
|
11805
12269
|
this.doDispose();
|
|
@@ -12924,6 +13388,13 @@ var EntitiesIdsRenderManager;
|
|
|
12924
13388
|
this.monitor = monitor;
|
|
12925
13389
|
this.item = item;
|
|
12926
13390
|
this.visualsManager = visualsManager;
|
|
13391
|
+
this.historicGatherer = new HistoricGatherer({
|
|
13392
|
+
viewer: this.viewer,
|
|
13393
|
+
getApi: () => this.apiGetter.getApi(),
|
|
13394
|
+
getAttrKey: () => { var _a; return (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historicAttrKey; },
|
|
13395
|
+
// Padding either side of a requested range, for clock desync between us and the server.
|
|
13396
|
+
paddingMs: 15000
|
|
13397
|
+
});
|
|
12927
13398
|
if (this.item.enableClustering) {
|
|
12928
13399
|
this.clustering = new PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
|
|
12929
13400
|
}
|
|
@@ -13273,138 +13744,22 @@ var EntitiesIdsRenderManager;
|
|
|
13273
13744
|
* @param entities
|
|
13274
13745
|
*/
|
|
13275
13746
|
async getHistoricInfo(entities) {
|
|
13276
|
-
|
|
13277
|
-
// Helps account for desync between client and server.
|
|
13278
|
-
const TIME_PADDING_MS = 15000;
|
|
13279
|
-
const minDateTimeStr = this.viewer.clock.startTime.toString();
|
|
13280
|
-
const maxDateTimeStr = this.viewer.clock.stopTime.toString();
|
|
13281
|
-
const minDateTime = new Date(minDateTimeStr).getTime();
|
|
13282
|
-
const maxDateTime = new Date(maxDateTimeStr).getTime();
|
|
13283
|
-
const tDetails = ViewUtils.GetTimeDetails({
|
|
13284
|
-
viewer: this.viewer,
|
|
13285
|
-
});
|
|
13286
|
-
const isLive = tDetails.isLive;
|
|
13287
|
-
let rangesToRequest = [{
|
|
13288
|
-
start: minDateTime,
|
|
13289
|
-
stop: maxDateTime
|
|
13290
|
-
}];
|
|
13291
|
-
// If we already have cached data, determine what ranges we're missing.
|
|
13292
|
-
if (Object.keys(this.entitiesHistoric).length >= entities.length) {
|
|
13293
|
-
let foundMinDateTime = null;
|
|
13294
|
-
let foundMaxDateTime = null;
|
|
13295
|
-
// Find the min/max based on cached data.
|
|
13296
|
-
// Since we set the values sorted, we only have to check the first and last records for each entity.
|
|
13297
|
-
for (const entityId of Object.keys(this.entitiesHistoric)) {
|
|
13298
|
-
const records = this.entitiesHistoric[entityId] || [];
|
|
13299
|
-
if (records.length) {
|
|
13300
|
-
const dateTime = new Date(records[0].dateTime).getTime();
|
|
13301
|
-
if (foundMinDateTime == null || dateTime < foundMinDateTime) {
|
|
13302
|
-
foundMinDateTime = dateTime;
|
|
13303
|
-
}
|
|
13304
|
-
const dateTime2 = new Date(records[records.length - 1].dateTime).getTime();
|
|
13305
|
-
if (foundMaxDateTime == null || dateTime2 > foundMaxDateTime) {
|
|
13306
|
-
foundMaxDateTime = dateTime2;
|
|
13307
|
-
}
|
|
13308
|
-
}
|
|
13309
|
-
}
|
|
13310
|
-
// Complete overlap - we already have all the data.
|
|
13311
|
-
if (foundMinDateTime != null && foundMaxDateTime != null && foundMinDateTime <= minDateTime && foundMaxDateTime >= maxDateTime) {
|
|
13312
|
-
return [false, this.entitiesHistoric];
|
|
13313
|
-
}
|
|
13314
|
-
// Calculate missing ranges.
|
|
13315
|
-
rangesToRequest = [];
|
|
13316
|
-
// Check if we need data before our cached range.
|
|
13317
|
-
if (foundMinDateTime != null && foundMinDateTime > minDateTime) {
|
|
13318
|
-
// When live we focus on getting the next range rather than old data.
|
|
13319
|
-
// Ends up being one less request to perform every second.
|
|
13320
|
-
if (!isLive || foundMinDateTime == null) {
|
|
13321
|
-
rangesToRequest.push({
|
|
13322
|
-
start: minDateTime,
|
|
13323
|
-
stop: new Date(foundMinDateTime).getTime()
|
|
13324
|
-
});
|
|
13325
|
-
}
|
|
13326
|
-
}
|
|
13327
|
-
// Check if we need data after our cached range.
|
|
13328
|
-
if (foundMaxDateTime != null && foundMaxDateTime < maxDateTime) {
|
|
13329
|
-
rangesToRequest.push({
|
|
13330
|
-
start: new Date(foundMaxDateTime).getTime(),
|
|
13331
|
-
stop: maxDateTime
|
|
13332
|
-
});
|
|
13333
|
-
}
|
|
13334
|
-
// If the ranges to request are within a tolerance, combine them.
|
|
13335
|
-
if (rangesToRequest.length > 1) {
|
|
13336
|
-
const TOLERANCE_MS = 60000; // 1 minute.
|
|
13337
|
-
let currentRange = rangesToRequest[0];
|
|
13338
|
-
for (let i = 1; i < rangesToRequest.length; i++) {
|
|
13339
|
-
const nextRange = rangesToRequest[i];
|
|
13340
|
-
if (nextRange.start - currentRange.stop <= TOLERANCE_MS) {
|
|
13341
|
-
currentRange.stop = nextRange.stop;
|
|
13342
|
-
}
|
|
13343
|
-
else {
|
|
13344
|
-
currentRange = nextRange;
|
|
13345
|
-
}
|
|
13346
|
-
}
|
|
13347
|
-
}
|
|
13348
|
-
}
|
|
13349
|
-
const entityIds = entities.map(x => x.Bruce.ID);
|
|
13350
|
-
const combined = { ...this.entitiesHistoric };
|
|
13351
|
-
// Make requests for each missing range
|
|
13352
|
-
for (const range of rangesToRequest) {
|
|
13353
|
-
if (this.disposed) {
|
|
13354
|
-
break;
|
|
13355
|
-
}
|
|
13356
|
-
const start = new Date(range.start - TIME_PADDING_MS);
|
|
13357
|
-
const stop = new Date(range.stop + TIME_PADDING_MS);
|
|
13358
|
-
const historicData = await EntityHistoricData.GetList({
|
|
13359
|
-
attrKey: this.item.BruceEntity.historicAttrKey,
|
|
13360
|
-
dateTimeFrom: start.toISOString(),
|
|
13361
|
-
dateTimeTo: stop.toISOString(),
|
|
13362
|
-
entityIds: entityIds,
|
|
13363
|
-
api: this.apiGetter.getApi()
|
|
13364
|
-
});
|
|
13365
|
-
if (this.disposed) {
|
|
13366
|
-
break;
|
|
13367
|
-
}
|
|
13368
|
-
// Merge the new data with existing data
|
|
13369
|
-
const records = historicData.recordsByIds;
|
|
13370
|
-
const recordsIds = Object.keys(records);
|
|
13371
|
-
for (let i = 0; i < recordsIds.length; i++) {
|
|
13372
|
-
const entityId = recordsIds[i];
|
|
13373
|
-
const latest = records[entityId] || [];
|
|
13374
|
-
const current = combined[entityId] || [];
|
|
13375
|
-
// Use a Map to de-duplicate by timestamp.
|
|
13376
|
-
const tmp = new Map();
|
|
13377
|
-
for (let j = 0; j < current.length; j++) {
|
|
13378
|
-
const record = current[j];
|
|
13379
|
-
const dateTime = new Date(record.dateTime).getTime();
|
|
13380
|
-
tmp.set(dateTime, record);
|
|
13381
|
-
}
|
|
13382
|
-
for (let j = 0; j < latest.length; j++) {
|
|
13383
|
-
const record = latest[j];
|
|
13384
|
-
const dateTime = new Date(record.dateTime).getTime();
|
|
13385
|
-
tmp.set(dateTime, record);
|
|
13386
|
-
}
|
|
13387
|
-
// Convert to array and sort by date.
|
|
13388
|
-
const sorted = Array.from(tmp.values()).sort((a, b) => {
|
|
13389
|
-
return new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime();
|
|
13390
|
-
});
|
|
13391
|
-
combined[entityId] = sorted;
|
|
13392
|
-
}
|
|
13393
|
-
}
|
|
13747
|
+
const { fetched, recordsByIds } = await this.historicGatherer.Gather(entities);
|
|
13394
13748
|
if (!this.disposed) {
|
|
13395
|
-
this.entitiesHistoric =
|
|
13749
|
+
this.entitiesHistoric = recordsByIds;
|
|
13396
13750
|
}
|
|
13397
|
-
return [
|
|
13751
|
+
return [fetched, recordsByIds];
|
|
13398
13752
|
}
|
|
13399
13753
|
viewerDateTimeSub() {
|
|
13400
13754
|
var _a, _b;
|
|
13401
13755
|
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) {
|
|
13402
13756
|
return;
|
|
13403
13757
|
}
|
|
13404
|
-
|
|
13758
|
+
// Scaled by how many Entities are on screen rather than fixed, since the cost of a refresh
|
|
13759
|
+
// is the set size and a large set barely changes between ticks anyway.
|
|
13405
13760
|
let queue = new DelayQueue(() => {
|
|
13406
13761
|
this.onGetterUpdate(Object.keys(this.renderedEntities));
|
|
13407
|
-
},
|
|
13762
|
+
}, this.historicGatherer.IntervalFor(Object.keys(this.renderedEntities).length), true);
|
|
13408
13763
|
let clockTickRemoval;
|
|
13409
13764
|
let prevTick = this.viewer.clock.currentTime.toString();
|
|
13410
13765
|
clockTickRemoval = this.viewer.clock.onTick.addEventListener(() => {
|
|
@@ -14096,6 +14451,9 @@ class TilesetStyler {
|
|
|
14096
14451
|
// Indicates that we are retrieving historic records.
|
|
14097
14452
|
// This means that the current scene's time is included in the request.
|
|
14098
14453
|
this.historic = false;
|
|
14454
|
+
// When false the Entity data is still gathered and the regos still updated, but no colour is
|
|
14455
|
+
// applied. A historic item that did not ask for styling needs the first without the second.
|
|
14456
|
+
this.applyStyles = true;
|
|
14099
14457
|
this.reRenderState = new EntityReRenderMaintainState();
|
|
14100
14458
|
// More expensive process.
|
|
14101
14459
|
// When an Entity is styled, the rego is reviewed and updated if needed.
|
|
@@ -14132,13 +14490,16 @@ class TilesetStyler {
|
|
|
14132
14490
|
}
|
|
14133
14491
|
Init(params) {
|
|
14134
14492
|
var _a, _b;
|
|
14135
|
-
let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic } = params;
|
|
14493
|
+
let { viewer, api, cTileset, fallbackStyleId, styleMapping, expandSources, menuItemId, register, scenario, historic, applyStyles } = params;
|
|
14136
14494
|
this.viewer = viewer;
|
|
14137
14495
|
this.api = api;
|
|
14138
14496
|
this.cTileset = cTileset;
|
|
14139
14497
|
this.register = register;
|
|
14140
14498
|
this.menuItemId = menuItemId;
|
|
14141
14499
|
this.historic = Boolean(historic);
|
|
14500
|
+
if (applyStyles != null) {
|
|
14501
|
+
this.applyStyles = Boolean(applyStyles);
|
|
14502
|
+
}
|
|
14142
14503
|
if (expandSources != null) {
|
|
14143
14504
|
this.expandSources = expandSources;
|
|
14144
14505
|
}
|
|
@@ -14632,33 +14993,31 @@ class TilesetStyler {
|
|
|
14632
14993
|
return;
|
|
14633
14994
|
}
|
|
14634
14995
|
const { settings: style, styleId: resolvedStyleId } = this.getTilesetFeatureStyleWithId(rego.entityTypeId);
|
|
14635
|
-
const fillColorTrace = (style && ((_a = style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor)) ? Calculator.TraceGetColor(style.modelStyle.fillColor, data, tags) : { value: null, effective: null };
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
if (bColor == null) {
|
|
14639
|
-
cColor = Color.WHITE;
|
|
14640
|
-
}
|
|
14641
|
-
else {
|
|
14642
|
-
cColor = colorToCColor$3(bColor);
|
|
14643
|
-
}
|
|
14996
|
+
const fillColorTrace = (this.applyStyles && style && ((_a = style.modelStyle) === null || _a === void 0 ? void 0 : _a.fillColor)) ? Calculator.TraceGetColor(style.modelStyle.fillColor, data, tags) : { value: null, effective: null };
|
|
14997
|
+
// Without styling the feature keeps whatever the tileset baked in. Falling through would paint
|
|
14998
|
+
// it the default white, which is a visible change for an item that only asked for historic.
|
|
14644
14999
|
const override = this.overrideFeatureColor.get(rego.entityId) == true;
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
15000
|
+
if (this.applyStyles) {
|
|
15001
|
+
const bColor = fillColorTrace.value;
|
|
15002
|
+
const cColor = bColor == null ? Color.WHITE : colorToCColor$3(bColor);
|
|
15003
|
+
CesiumEntityStyler.SetDefaultColor({
|
|
15004
|
+
color: cColor,
|
|
15005
|
+
entity: visual,
|
|
15006
|
+
viewer: this.viewer,
|
|
15007
|
+
override: override
|
|
15008
|
+
});
|
|
15009
|
+
this.overrideFeatureColor.set(rego.entityId, true);
|
|
15010
|
+
if (resolvedStyleId != null) {
|
|
15011
|
+
rego.styleId = resolvedStyleId;
|
|
15012
|
+
}
|
|
15013
|
+
rego.styleEffective = (_b = StyleEffective.Combine([
|
|
15014
|
+
{ key: "color", effective: fillColorTrace.effective }
|
|
15015
|
+
])) !== null && _b !== void 0 ? _b : rego.styleEffective;
|
|
15016
|
+
}
|
|
15017
|
+
if (((_c = data === null || data === void 0 ? void 0 : data.Bruce) === null || _c === void 0 ? void 0 : _c.InternalID) != null) {
|
|
14653
15018
|
rego.internalId = data.Bruce.InternalID;
|
|
14654
15019
|
this.styledByInternalId.set(data.Bruce.InternalID, rego.entityId);
|
|
14655
15020
|
}
|
|
14656
|
-
if (resolvedStyleId != null) {
|
|
14657
|
-
rego.styleId = resolvedStyleId;
|
|
14658
|
-
}
|
|
14659
|
-
rego.styleEffective = (_c = StyleEffective.Combine([
|
|
14660
|
-
{ key: "color", effective: fillColorTrace.effective }
|
|
14661
|
-
])) !== null && _c !== void 0 ? _c : rego.styleEffective;
|
|
14662
15021
|
this.styledEntityIds.set(rego.entityId, true);
|
|
14663
15022
|
this._styleProgressQueue.Call();
|
|
14664
15023
|
// Since we only need to update it for scenarios right now.
|
|
@@ -15629,6 +15988,7 @@ var TilesetCadRenderManager;
|
|
|
15629
15988
|
// Series of points to help interpolate movement when the timeline changes.
|
|
15630
15989
|
this.historicPossesInitialLoaded = false;
|
|
15631
15990
|
this.historicAnimation = null;
|
|
15991
|
+
this.historicGatherer = null;
|
|
15632
15992
|
this.historicPossesLoadingProm = null;
|
|
15633
15993
|
// Queue of loaded in features that we haven't processed yet.
|
|
15634
15994
|
this.featureQueue = [];
|
|
@@ -16666,120 +17026,41 @@ var TilesetCadRenderManager;
|
|
|
16666
17026
|
const api = this.getters.GetBruceApi({
|
|
16667
17027
|
accountId: accountId
|
|
16668
17028
|
});
|
|
16669
|
-
//
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
// Helps us avoid repeated requests that are the same.
|
|
16674
|
-
const pendingRequests = new Map();
|
|
16675
|
-
/**
|
|
16676
|
-
* Returns a list of historic positions for a given time range.
|
|
16677
|
-
* @param startStr
|
|
16678
|
-
* @param stopStr
|
|
16679
|
-
* @returns
|
|
17029
|
+
// The root Entity is the only one here, so nothing is banded and the plan sees a set of one.
|
|
17030
|
+
const rootEntity = { Bruce: { ID: this.rootId } };
|
|
17031
|
+
/*
|
|
17032
|
+
* Turns historic records into the animated positions this tileset moves along.
|
|
16680
17033
|
*/
|
|
16681
|
-
const
|
|
16682
|
-
|
|
16683
|
-
if (pendingRequests.has(requestKey)) {
|
|
16684
|
-
return pendingRequests.get(requestKey);
|
|
16685
|
-
}
|
|
16686
|
-
const requestPromise = new Promise(async (res) => {
|
|
16687
|
-
try {
|
|
16688
|
-
const historicData = await EntityHistoricData.GetList({
|
|
16689
|
-
attrKey: null,
|
|
16690
|
-
dateTimeFrom: startStr,
|
|
16691
|
-
dateTimeTo: stopStr,
|
|
16692
|
-
entityIds: [this.rootId],
|
|
16693
|
-
api: api
|
|
16694
|
-
});
|
|
16695
|
-
const posses = CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, HeightReference.NONE, HeightReference.CLAMP_TO_GROUND, historicData.recordsByIds[this.rootId]);
|
|
16696
|
-
res(posses);
|
|
16697
|
-
}
|
|
16698
|
-
catch (e) {
|
|
16699
|
-
console.error(e);
|
|
16700
|
-
res([]);
|
|
16701
|
-
}
|
|
16702
|
-
finally {
|
|
16703
|
-
pendingRequests.delete(requestKey);
|
|
16704
|
-
}
|
|
16705
|
-
});
|
|
16706
|
-
pendingRequests.set(requestKey, requestPromise);
|
|
16707
|
-
return requestPromise;
|
|
17034
|
+
const toPosses = (records) => {
|
|
17035
|
+
return CesiumAnimatedProperty.GetSeriesPossesForHistoricEntity(this.viewer, HeightReference.NONE, HeightReference.CLAMP_TO_GROUND, records);
|
|
16708
17036
|
};
|
|
17037
|
+
this.historicGatherer = new HistoricGatherer({
|
|
17038
|
+
viewer: this.viewer,
|
|
17039
|
+
getApi: () => api,
|
|
17040
|
+
// The assembly root snapshots the whole Entity, so no key narrows it.
|
|
17041
|
+
getAttrKey: () => null,
|
|
17042
|
+
// Time padding in milliseconds (15 seconds).
|
|
17043
|
+
// Helps account for desync between client and server.
|
|
17044
|
+
paddingMs: 15000,
|
|
17045
|
+
onRecords: (recordsByIds) => {
|
|
17046
|
+
var _a;
|
|
17047
|
+
const records = recordsByIds[this.rootId];
|
|
17048
|
+
if (!(records === null || records === void 0 ? void 0 : records.length) || !((_a = this.historicAnimation) === null || _a === void 0 ? void 0 : _a.addPositions)) {
|
|
17049
|
+
return;
|
|
17050
|
+
}
|
|
17051
|
+
this.historicAnimation.addPositions(toPosses(records));
|
|
17052
|
+
}
|
|
17053
|
+
});
|
|
16709
17054
|
/**
|
|
16710
|
-
*
|
|
16711
|
-
*
|
|
17055
|
+
* Fetches whatever the current timeline range needs that is not already held.
|
|
17056
|
+
* Positions reach the animation through the gatherer's callback as each batch lands.
|
|
16712
17057
|
* @returns
|
|
16713
17058
|
*/
|
|
16714
17059
|
const checkTimelineRange = async () => {
|
|
16715
|
-
|
|
16716
|
-
const minDateTime = new Date(this.viewer.clock.startTime.toString()).getTime();
|
|
16717
|
-
const maxDateTime = new Date(this.viewer.clock.stopTime.toString()).getTime();
|
|
16718
|
-
// What we have loaded.
|
|
16719
|
-
const range = this.historicAnimation.getDateRange();
|
|
16720
|
-
const foundMinDateTime = (range === null || range === void 0 ? void 0 : range.minDate) ? range.minDate.getTime() : null;
|
|
16721
|
-
const foundMaxDateTime = (range === null || range === void 0 ? void 0 : range.maxDate) ? range.maxDate.getTime() : null;
|
|
16722
|
-
// See if the current range is within the range we already have.
|
|
16723
|
-
if (this.historicPossesInitialLoaded &&
|
|
16724
|
-
foundMinDateTime &&
|
|
16725
|
-
foundMaxDateTime &&
|
|
16726
|
-
minDateTime >= foundMinDateTime &&
|
|
16727
|
-
maxDateTime <= foundMaxDateTime) {
|
|
16728
|
-
return;
|
|
16729
|
-
}
|
|
16730
|
-
const tDetails = ViewUtils.GetTimeDetails({
|
|
16731
|
-
viewer: this.viewer,
|
|
16732
|
-
});
|
|
16733
|
-
const isLive = tDetails.isLive;
|
|
16734
|
-
// See if the requested range is before or after the range we have.
|
|
16735
|
-
const fetchBefore = !foundMinDateTime || (!isLive && minDateTime < foundMinDateTime);
|
|
16736
|
-
const fetchAfter = !foundMaxDateTime || maxDateTime > foundMaxDateTime;
|
|
16737
|
-
if (!fetchBefore && !fetchAfter) {
|
|
16738
|
-
// Already have the data we need.
|
|
17060
|
+
if (this.disposed) {
|
|
16739
17061
|
return;
|
|
16740
17062
|
}
|
|
16741
|
-
|
|
16742
|
-
if (!foundMinDateTime || !foundMaxDateTime) {
|
|
16743
|
-
const startStr = new Date(minDateTime - TIME_PADDING_MS).toISOString();
|
|
16744
|
-
const stopStr = new Date(maxDateTime + TIME_PADDING_MS).toISOString();
|
|
16745
|
-
const newPositions = await getPossesForRange(startStr, stopStr);
|
|
16746
|
-
if (this.disposed) {
|
|
16747
|
-
return;
|
|
16748
|
-
}
|
|
16749
|
-
if (this.historicAnimation && this.historicAnimation.addPositions) {
|
|
16750
|
-
this.historicAnimation.addPositions(newPositions);
|
|
16751
|
-
}
|
|
16752
|
-
}
|
|
16753
|
-
else {
|
|
16754
|
-
// The data we want is before the range we've currently loaded.
|
|
16755
|
-
if (fetchBefore) {
|
|
16756
|
-
// Calculate the missing difference and request it.
|
|
16757
|
-
const startStr = new Date(minDateTime - TIME_PADDING_MS).toISOString();
|
|
16758
|
-
const stopStr = new Date(foundMinDateTime + TIME_PADDING_MS).toISOString();
|
|
16759
|
-
getPossesForRange(startStr, stopStr).then((newPositions) => {
|
|
16760
|
-
if (this.disposed) {
|
|
16761
|
-
return;
|
|
16762
|
-
}
|
|
16763
|
-
if (this.historicAnimation && this.historicAnimation.addPositions) {
|
|
16764
|
-
this.historicAnimation.addPositions(newPositions);
|
|
16765
|
-
}
|
|
16766
|
-
});
|
|
16767
|
-
}
|
|
16768
|
-
// The data we want is after the range we've currently loaded.
|
|
16769
|
-
if (fetchAfter) {
|
|
16770
|
-
// Calculate the missing difference and request it.
|
|
16771
|
-
const startStr = new Date(foundMaxDateTime - TIME_PADDING_MS).toISOString();
|
|
16772
|
-
const stopStr = new Date(maxDateTime + TIME_PADDING_MS).toISOString();
|
|
16773
|
-
getPossesForRange(startStr, stopStr).then((newPositions) => {
|
|
16774
|
-
if (this.disposed) {
|
|
16775
|
-
return;
|
|
16776
|
-
}
|
|
16777
|
-
if (this.historicAnimation && this.historicAnimation.addPositions) {
|
|
16778
|
-
this.historicAnimation.addPositions(newPositions);
|
|
16779
|
-
}
|
|
16780
|
-
});
|
|
16781
|
-
}
|
|
16782
|
-
}
|
|
17063
|
+
await this.historicGatherer.Gather([rootEntity]);
|
|
16783
17064
|
};
|
|
16784
17065
|
/**
|
|
16785
17066
|
* Requests the initial set of historic positions for the timeline range.
|
|
@@ -16793,11 +17074,8 @@ var TilesetCadRenderManager;
|
|
|
16793
17074
|
res(false);
|
|
16794
17075
|
return;
|
|
16795
17076
|
}
|
|
16796
|
-
const
|
|
16797
|
-
const
|
|
16798
|
-
const startStr = new Date(startTmp.getTime() - 1000).toISOString();
|
|
16799
|
-
const stopStr = new Date(stopTmp.getTime() + 1000).toISOString();
|
|
16800
|
-
const positions = await getPossesForRange(startStr, stopStr);
|
|
17077
|
+
const { recordsByIds } = await this.historicGatherer.Gather([rootEntity]);
|
|
17078
|
+
const positions = toPosses(recordsByIds[this.rootId] || []);
|
|
16801
17079
|
if (this.disposed) {
|
|
16802
17080
|
res(false);
|
|
16803
17081
|
return;
|
|
@@ -19584,12 +19862,11 @@ var TilesetEntitiesRenderManager;
|
|
|
19584
19862
|
}
|
|
19585
19863
|
this.viewer.scene.requestRender();
|
|
19586
19864
|
TilesetRenderEngine.OnTilesetReady(this.cTileset).then(() => {
|
|
19587
|
-
var _a;
|
|
19588
19865
|
try {
|
|
19589
19866
|
if (this.disposed || this.viewer.isDestroyed()) {
|
|
19590
19867
|
return;
|
|
19591
19868
|
}
|
|
19592
|
-
if (this.item
|
|
19869
|
+
if (Manager.NeedsStyler(this.item)) {
|
|
19593
19870
|
const api = this.getters.GetBruceApi();
|
|
19594
19871
|
this.styler.Init({
|
|
19595
19872
|
viewer: this.viewer,
|
|
@@ -19600,7 +19877,8 @@ var TilesetEntitiesRenderManager;
|
|
|
19600
19877
|
expandSources: false,
|
|
19601
19878
|
menuItemId: this.item.id,
|
|
19602
19879
|
register: this.visualsManager,
|
|
19603
|
-
historic: (
|
|
19880
|
+
historic: Manager.IsHistoric(this.item),
|
|
19881
|
+
applyStyles: Boolean(this.item.ApplyStyles)
|
|
19604
19882
|
});
|
|
19605
19883
|
}
|
|
19606
19884
|
this.onCTilesetLoad();
|
|
@@ -19807,6 +20085,19 @@ var TilesetEntitiesRenderManager;
|
|
|
19807
20085
|
get HandoffKey() {
|
|
19808
20086
|
return Manager.GetHandoffKey(this.item);
|
|
19809
20087
|
}
|
|
20088
|
+
/*
|
|
20089
|
+
* Returns whether the item asks for Entities as they were at the scene time.
|
|
20090
|
+
*/
|
|
20091
|
+
static IsHistoric(item) {
|
|
20092
|
+
var _a, _b;
|
|
20093
|
+
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));
|
|
20094
|
+
}
|
|
20095
|
+
/*
|
|
20096
|
+
* Returns whether the styler has to run, which it does for historic even without styling.
|
|
20097
|
+
*/
|
|
20098
|
+
static NeedsStyler(item) {
|
|
20099
|
+
return Boolean(item === null || item === void 0 ? void 0 : item.ApplyStyles) || Manager.IsHistoric(item);
|
|
20100
|
+
}
|
|
19810
20101
|
static GetHandoffKey(item) {
|
|
19811
20102
|
var _a, _b;
|
|
19812
20103
|
const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
|
|
@@ -19816,7 +20107,10 @@ var TilesetEntitiesRenderManager;
|
|
|
19816
20107
|
const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
|
|
19817
20108
|
// Styled <-> unstyled has no cheap revert path, force a reload instead.
|
|
19818
20109
|
const applyStyles = Boolean(item.ApplyStyles);
|
|
19819
|
-
|
|
20110
|
+
// Historic drives a different gather,
|
|
20111
|
+
// so a handoff must not reuse a styler set up for the other mode.
|
|
20112
|
+
const historic = Manager.IsHistoric(item);
|
|
20113
|
+
return `${accountId}:${tilesetId}:${applyStyles}:${historic}`;
|
|
19820
20114
|
}
|
|
19821
20115
|
// Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
|
|
19822
20116
|
PrepareHandoff() {
|
|
@@ -19856,7 +20150,6 @@ var TilesetEntitiesRenderManager;
|
|
|
19856
20150
|
// Takes ownership of a PrepareHandoff() payload instead of loading via Init().
|
|
19857
20151
|
// Then re-parents every already-registered rego to this menu item and re-runs styling.
|
|
19858
20152
|
AdoptHandoff(payload) {
|
|
19859
|
-
var _a;
|
|
19860
20153
|
this.cTileset = payload.cTileset;
|
|
19861
20154
|
this.tileset = payload.tileset;
|
|
19862
20155
|
this.typeId = payload.typeId;
|
|
@@ -19888,7 +20181,7 @@ var TilesetEntitiesRenderManager;
|
|
|
19888
20181
|
toPriority: this.renderPriority,
|
|
19889
20182
|
requestRender: false
|
|
19890
20183
|
});
|
|
19891
|
-
if (this.item
|
|
20184
|
+
if (Manager.NeedsStyler(this.item)) {
|
|
19892
20185
|
this.styler.Init({
|
|
19893
20186
|
viewer: this.viewer,
|
|
19894
20187
|
api: this.getters.GetBruceApi(),
|
|
@@ -19898,7 +20191,8 @@ var TilesetEntitiesRenderManager;
|
|
|
19898
20191
|
expandSources: false,
|
|
19899
20192
|
menuItemId: this.item.id,
|
|
19900
20193
|
register: this.visualsManager,
|
|
19901
|
-
historic: (
|
|
20194
|
+
historic: Manager.IsHistoric(this.item),
|
|
20195
|
+
applyStyles: Boolean(this.item.ApplyStyles)
|
|
19902
20196
|
});
|
|
19903
20197
|
this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
|
|
19904
20198
|
}
|
|
@@ -43658,7 +43952,7 @@ var StyleUtils;
|
|
|
43658
43952
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
43659
43953
|
})(StyleUtils || (StyleUtils = {}));
|
|
43660
43954
|
|
|
43661
|
-
const VERSION = "7.2.
|
|
43955
|
+
const VERSION = "7.2.1";
|
|
43662
43956
|
/**
|
|
43663
43957
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
43664
43958
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|