bruce-cesium 5.4.0 → 5.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/bruce-cesium.es5.js +585 -449
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +587 -447
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/rendering/cesium-animated-property.js +97 -45
  7. package/dist/lib/rendering/cesium-animated-property.js.map +1 -1
  8. package/dist/lib/rendering/getters/batched-data-getter.js +7 -1
  9. package/dist/lib/rendering/getters/batched-data-getter.js.map +1 -1
  10. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +237 -357
  11. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  12. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +172 -37
  13. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  14. package/dist/lib/rendering/view-render-engine.js +4 -2
  15. package/dist/lib/rendering/view-render-engine.js.map +1 -1
  16. package/dist/lib/utils/view-utils.js +62 -2
  17. package/dist/lib/utils/view-utils.js.map +1 -1
  18. package/dist/types/bruce-cesium.d.ts +1 -1
  19. package/dist/types/rendering/cesium-animated-property.d.ts +24 -21
  20. package/dist/types/rendering/getters/batched-data-getter.d.ts +3 -1
  21. package/dist/types/rendering/render-managers/entities/entities-ids-render-manager.d.ts +22 -7
  22. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +2 -2
  23. package/dist/types/utils/view-utils.d.ts +9 -0
  24. package/package.json +2 -2
@@ -7,21 +7,7 @@ const entity_render_engine_1 = require("../../entity-render-engine");
7
7
  const visuals_register_1 = require("../../visuals-register");
8
8
  const point_clustering_1 = require("../common/point-clustering");
9
9
  const batched_data_getter_1 = require("../../getters/batched-data-getter");
10
- const js_utils_1 = require("../../../internal/js-utils");
11
10
  const BATCH_SIZE = 500;
12
- function getValue(viewer, obj) {
13
- if (obj === null || obj === void 0 ? void 0 : obj.getValue) {
14
- let date = viewer.scene.lastRenderTime;
15
- if (!date) {
16
- date = viewer.clock.currentTime;
17
- }
18
- return obj.getValue(date);
19
- }
20
- return obj;
21
- }
22
- function colorToCColor(color) {
23
- return new Cesium.Color(color.red ? color.red / 255 : 0, color.green ? color.green / 255 : 0, color.blue ? color.blue / 255 : 0, color.alpha);
24
- }
25
11
  /**
26
12
  * Render manager for rendering an array of entity ids.
27
13
  * This will render them in batches to avoid overloading the viewer.
@@ -38,23 +24,27 @@ var EntitiesIdsRenderManager;
38
24
  this.getterSub = null;
39
25
  this.disposed = false;
40
26
  this.renderedEntities = {};
41
- this.sources = [];
42
- // Highly experimental flag to try improve rendering large sets of polygons and polylines.
43
- // Many things are not supported when this is enabled.
44
- this.useGeojson = false;
27
+ // Callback to dispose the monitoring of the viewer's timeline for historic data.
28
+ this.viewerDateTimeChangeRemoval = null;
29
+ // Entity ID -> historic information on the Entity.
30
+ // This helps us manage what needs changing whenever we receive the same Entity update, or when the timeline range changes.
31
+ this.entitiesHistoric = {};
32
+ // Queue for renderAsIndividuals to avoid concurrent calls.
33
+ this.renderQueueActive = false;
34
+ this.renderQueue = [];
35
+ this.renderQueueForceFlags = [];
45
36
  const { viewer, apiGetter, monitor, item, register: visualsManager } = params;
46
37
  this.viewer = viewer;
47
38
  this.apiGetter = apiGetter;
48
39
  this.monitor = monitor;
49
40
  this.item = item;
50
41
  this.visualsManager = visualsManager;
51
- this.useGeojson = item.renderAsGeojson == true;
52
42
  if (this.item.enableClustering) {
53
43
  this.clustering = new point_clustering_1.PointClustering(visualsManager, this.item.id, (_a = this.item) === null || _a === void 0 ? void 0 : _a.clustering);
54
44
  }
55
45
  }
56
46
  Init() {
57
- var _a;
47
+ var _a, _b, _c;
58
48
  if (this.disposed) {
59
49
  throw (new Error("This item is disposed."));
60
50
  }
@@ -74,15 +64,19 @@ var EntitiesIdsRenderManager;
74
64
  if (this.renderPriority == null) {
75
65
  this.renderPriority = 2;
76
66
  }
77
- if (this.renderAsGeojson && this.item.CameraZoomSettings.length > 1) {
78
- console.warn("Geojson rendering does not support multiple zoom controls. Only the first one will be used.");
79
- this.item.CameraZoomSettings = [this.item.CameraZoomSettings[0]];
80
- }
81
- this.getter = new batched_data_getter_1.BatchedDataGetter.Getter(this.item.BruceEntity.EntityIds, this.monitor, BATCH_SIZE);
67
+ this.getter = new batched_data_getter_1.BatchedDataGetter.Getter(this.item.BruceEntity.EntityIds, this.monitor, BATCH_SIZE,
68
+ // Don't emit the same Entity multiple times.
69
+ true);
82
70
  this.getterSub = this.getter.OnUpdate.Subscribe((ids) => {
83
71
  this.onGetterUpdate(ids);
84
72
  });
85
73
  this.getter.Start();
74
+ if (((_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.historic) || ((_c = this.item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historicAttrKey)) {
75
+ this.viewerDateTimeSub();
76
+ }
77
+ else {
78
+ this.viewerDateTimeDispose();
79
+ }
86
80
  }
87
81
  Dispose() {
88
82
  if (this.disposed) {
@@ -100,11 +94,11 @@ var EntitiesIdsRenderManager;
100
94
  });
101
95
  (_b = this.clustering) === null || _b === void 0 ? void 0 : _b.Dispose();
102
96
  this.clustering = null;
103
- for (let i = 0; i < this.sources.length; i++) {
104
- const source = this.sources[i];
105
- this.viewer.dataSources.remove(source);
106
- }
107
- this.sources = [];
97
+ this.viewerDateTimeDispose();
98
+ // Clear render queue when disposed
99
+ this.renderQueue = [];
100
+ this.renderQueueForceFlags = [];
101
+ this.renderQueueActive = false;
108
102
  }
109
103
  async ReRender(params) {
110
104
  let { entityIds, force, entities } = params;
@@ -139,10 +133,6 @@ var EntitiesIdsRenderManager;
139
133
  }
140
134
  if (CameraZoomSettings === null || CameraZoomSettings === void 0 ? void 0 : CameraZoomSettings.length) {
141
135
  this.item.CameraZoomSettings = CameraZoomSettings;
142
- if (this.renderAsGeojson && this.item.CameraZoomSettings.length > 1) {
143
- console.warn("Geojson rendering does not support multiple zoom controls. Only the first one will be used.");
144
- this.item.CameraZoomSettings = [this.item.CameraZoomSettings[0]];
145
- }
146
136
  }
147
137
  if (queueRerender != false) {
148
138
  const entityIds = Object.keys(this.renderedEntities);
@@ -154,15 +144,25 @@ var EntitiesIdsRenderManager;
154
144
  }
155
145
  }
156
146
  async onGetterUpdate(entityIds, force = false) {
157
- if (this.disposed || this.viewer.isDestroyed()) {
147
+ if (this.disposed || this.viewer.isDestroyed() || !(entityIds === null || entityIds === void 0 ? void 0 : entityIds.length)) {
158
148
  return;
159
149
  }
160
150
  try {
161
151
  const api = this.apiGetter.getApi();
152
+ let newDateTime = null;
153
+ if (this.item.BruceEntity.historic || this.item.BruceEntity.historicAttrKey) {
154
+ newDateTime = Cesium.JulianDate.toDate(this.viewer.clock.currentTime).toISOString();
155
+ }
162
156
  const { entities } = await bruce_models_1.Entity.GetListByIds({
163
157
  api,
164
158
  entityIds,
165
- migrated: true
159
+ historicPoint: newDateTime,
160
+ historicKey: this.item.BruceEntity.historicAttrKey,
161
+ migrated: true,
162
+ // If we're taking 5+ minutes to make a query, it's a dud.
163
+ // This is a timeout imposed on our DB and not external sources.
164
+ // Honestly could lower down to 30 seconds, but we'll keep it high for now.
165
+ maxSearchTimeSec: 60 * 5,
166
166
  });
167
167
  await this.renderEntities(entities, force);
168
168
  }
@@ -171,347 +171,82 @@ var EntitiesIdsRenderManager;
171
171
  }
172
172
  }
173
173
  async renderEntities(entities, force = false) {
174
- if (this.disposed || this.viewer.isDestroyed()) {
174
+ if (this.disposed || this.viewer.isDestroyed() || !(entities === null || entities === void 0 ? void 0 : entities.length)) {
175
175
  return;
176
176
  }
177
177
  try {
178
- if (this.useGeojson) {
179
- const zoomItem = this.item.CameraZoomSettings[0];
180
- if (zoomItem.DisplayType == bruce_models_1.ZoomControl.EDisplayType.Point) {
181
- // We'll just render these as individuals since we don't support point geojson.
182
- await this.renderAsIndividuals(entities, force);
183
- }
184
- else {
185
- await this.renderAsGeojson(entities, force);
186
- }
187
- }
188
- else {
189
- await this.renderAsIndividuals(entities, force);
190
- }
178
+ await this.queueRenderAsIndividuals(entities, force);
191
179
  }
192
180
  catch (e) {
193
181
  console.error(e);
194
182
  }
195
183
  }
196
184
  /**
197
- * Our optimized and more stable path.
198
- * We construct a geojson that we draw in one go.
199
- * @param entities
200
- * @param force TODO: This should re-render entities that are already rendered.
185
+ * Queues a render operation to be processed in sequence.
186
+ * If an operation is already in progress, this will be added to the queue.
201
187
  */
202
- async renderAsGeojson(entities, force) {
203
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
204
- entities = entities.filter((entity) => {
205
- var _a;
206
- return !this.renderedEntities[(_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID];
207
- });
208
- // Mark these as rendered.
209
- entities.forEach((entity) => {
210
- var _a;
211
- this.renderedEntities[(_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID] = true;
212
- });
213
- // This process only supports a single zoom control item.
214
- const zoomItem = this.item.CameraZoomSettings[0];
215
- // If we're after 3d models then we'll check if there are LODs and render those as individuals.
216
- if (zoomItem.DisplayType == bruce_models_1.ZoomControl.EDisplayType.Model3D && entities.length) {
217
- const { lods } = await bruce_models_1.EntityLod.GetLods({
218
- api: this.apiGetter.getApi(),
219
- filter: {
220
- externalSources: false,
221
- Items: entities.map(x => {
222
- var _a, _b;
223
- return {
224
- entityId: (_a = x.Bruce) === null || _a === void 0 ? void 0 : _a.ID,
225
- categoryId: (_b = zoomItem.LODCategoryID) !== null && _b !== void 0 ? _b : "GLB",
226
- group: "DEFAULT",
227
- level: Number(zoomItem.LODLevel)
228
- };
229
- }),
230
- strict: false
231
- }
232
- });
233
- if (this.disposed) {
234
- this.doDispose();
235
- return;
236
- }
237
- const withLods = lods.filter(x => x.entityId && !!x.clientFileId).map(x => x.entityId);
238
- const individuals = entities.filter((entity) => {
239
- var _a;
240
- return withLods.includes((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID);
241
- });
242
- if (individuals.length) {
243
- this.renderAsIndividuals(individuals, force);
244
- }
245
- // Now we proceed with what is left.
246
- entities = entities.filter((entity) => {
247
- var _a;
248
- return !withLods.includes((_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID);
249
- });
250
- }
251
- if (!entities.length) {
188
+ async queueRenderAsIndividuals(entities, force = false) {
189
+ if (this.disposed) {
252
190
  return;
253
191
  }
254
- let style = null;
255
- if ((zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) && (zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) > -1) {
256
- try {
257
- style = (_a = (await bruce_models_1.Style.Get({
258
- api: this.apiGetter.getApi(),
259
- styleId: zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID
260
- })).style) === null || _a === void 0 ? void 0 : _a.Settings;
261
- }
262
- // Probably deleted.
263
- catch (e) {
264
- console.error(e);
265
- }
266
- }
267
- let entityTypeId = (_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b["EntityType.ID"];
268
- if (!entityTypeId) {
269
- entityTypeId = (_d = (_c = entities.find(x => { var _a; return !!((_a = x.Bruce) === null || _a === void 0 ? void 0 : _a["EntityType.ID"]); })) === null || _c === void 0 ? void 0 : _c.Bruce) === null || _d === void 0 ? void 0 : _d["EntityType.ID"];
270
- }
271
- // Getting type regardless cause it's needed for name calculations.
272
- let entityType;
273
- if (entityTypeId) {
274
- // Try using the Entity Type default if one is specified in the menu item.
275
- try {
276
- entityType = (_e = (await bruce_models_1.EntityType.Get({
277
- entityTypeId: entityTypeId,
278
- api: this.apiGetter.getApi()
279
- }))) === null || _e === void 0 ? void 0 : _e.entityType;
280
- if (!style && entityType) {
281
- if (entityType["DisplaySetting.ID"] && entityType["DisplaySetting.ID"] > 0) {
282
- style = (_f = (await bruce_models_1.Style.Get({
283
- api: this.apiGetter.getApi(),
284
- styleId: entityType["DisplaySetting.ID"]
285
- })).style) === null || _f === void 0 ? void 0 : _f.Settings;
286
- }
287
- }
288
- }
289
- catch (e) {
290
- console.error(e);
291
- }
292
- }
293
- const pStyle = (_g = style === null || style === void 0 ? void 0 : style.polygonStyle) !== null && _g !== void 0 ? _g : {};
294
- const lStyle = (_h = style === null || style === void 0 ? void 0 : style.polylineStyle) !== null && _h !== void 0 ? _h : {};
295
- const polygonsClamped = ((_j = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _j === void 0 ? void 0 : _j.id) == null ? true : ((_k = pStyle === null || pStyle === void 0 ? void 0 : pStyle.altitudeOption) === null || _k === void 0 ? void 0 : _k.id) == 0;
296
- const bFillColor = bruce_models_1.Calculator.GetColor(pStyle.fillColor, {}, []);
297
- const cFillColor = bFillColor ? colorToCColor(bFillColor) : Cesium.Color.fromCssColorString("rgba(139, 195, 74, 0.8)");
298
- const bLineColor = bruce_models_1.Calculator.GetColor(pStyle.lineColor, {}, []);
299
- const cLineColor = bLineColor ? colorToCColor(bLineColor) : Cesium.Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
300
- let lineWidthPx = pStyle.lineWidth ? bruce_models_1.Calculator.GetNumber(pStyle.lineWidth, {}, []) : null;
301
- if (lineWidthPx == null) {
302
- lineWidthPx = 1;
303
- }
304
- lineWidthPx = (0, js_utils_1.EnsureNumber)(lineWidthPx);
305
- if (lineWidthPx < 0.01) {
306
- lineWidthPx = 0;
307
- }
308
- lineWidthPx = Math.round(lineWidthPx);
309
- const collection = {
310
- features: [],
311
- type: "FeatureCollection"
312
- };
313
- for (let i = 0; i < entities.length; i++) {
314
- const entity = entities[i];
315
- const feature = bruce_models_1.Geometry.ToGeoJsonFeature({
316
- geometry: entity.Bruce.VectorGeometry,
317
- noAltitude: polygonsClamped && lineWidthPx <= 0,
318
- altitude: lineWidthPx > 0 && polygonsClamped ? 1 : null,
319
- properties: {
320
- ...entity,
321
- Bruce: {
322
- ...entity.Bruce,
323
- // Exclude as we just converted it to geojson.
324
- VectorGeometry: null
325
- }
326
- }
327
- });
328
- if (feature) {
329
- collection.features.push(feature);
330
- }
331
- }
332
- // Anything that failed to turn into geojson or points we'll render as individuals.
333
- const individuals = entities.filter((entity) => {
334
- const feature = collection.features.find(x => { var _a, _b; return ((_b = (_a = x.properties) === null || _a === void 0 ? void 0 : _a.Bruce) === null || _b === void 0 ? void 0 : _b.ID) == entity.Bruce.ID; });
335
- if (!feature) {
336
- return true;
337
- }
338
- if (feature.geometry.type == "Point") {
339
- return true;
340
- }
341
- return false;
342
- });
343
- if (individuals.length) {
344
- this.renderAsIndividuals(individuals, force);
345
- }
346
- // Filter out points (the ones we just rendered as individuals).
347
- collection.features = collection.features.filter(x => x.geometry.type != "Point");
348
- // If there is nothing to render now, return.
349
- if (!collection.features.length) {
192
+ // If no active render is happening, start processing immediately.
193
+ if (!this.renderQueueActive) {
194
+ this.renderQueueActive = true;
195
+ await this.processRenderQueue(entities, force);
350
196
  return;
351
197
  }
352
- const source = await Cesium.GeoJsonDataSource.load(collection, {
353
- stroke: cLineColor,
354
- fill: cFillColor,
355
- strokeWidth: lineWidthPx,
356
- clampToGround: lineWidthPx <= 0 && polygonsClamped
357
- });
358
- this.viewer.dataSources.add(source);
359
- this.sources.push(source);
198
+ // Otherwise, add to the queue.
199
+ this.renderQueue.push(entities);
200
+ this.renderQueueForceFlags.push(force);
201
+ }
202
+ /**
203
+ * Processes the render queue, handling one rendering operation at a time.
204
+ */
205
+ async processRenderQueue(initialEntities, initialForce = false) {
360
206
  if (this.disposed) {
361
- this.doDispose();
207
+ this.renderQueueActive = false;
362
208
  return;
363
209
  }
364
- const groups = [];
365
- /**
366
- * Applies individual styling to a rendered entity.
367
- * This will exclude tag styling for now since it's harder to load that on the fly.
368
- * @param thing
369
- * @param entityId
370
- * @param data
371
- * @returns
372
- */
373
- const applyStyle = (thing, entityId, data) => {
374
- if (thing.polygon) {
375
- const bFillColor = bruce_models_1.Calculator.GetColor(pStyle.fillColor, data, []);
376
- const cFillColor = bFillColor ? colorToCColor(bFillColor) : Cesium.Color.fromCssColorString("rgba(139, 195, 74, 0.8)");
377
- const bLineColor = bruce_models_1.Calculator.GetColor(pStyle.lineColor, data, []);
378
- const cLineColor = bLineColor ? colorToCColor(bLineColor) : Cesium.Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
379
- let width = pStyle.lineWidth ? bruce_models_1.Calculator.GetNumber(pStyle.lineWidth, data, []) : null;
380
- if (width == null) {
381
- width = 1;
382
- }
383
- width = (0, js_utils_1.EnsureNumber)(width);
384
- if (width < 0.01) {
385
- width = 0;
386
- }
387
- let curFillColor = getValue(this.viewer, thing.polygon.material);
388
- if (curFillColor && curFillColor instanceof Cesium.ColorMaterialProperty) {
389
- curFillColor = curFillColor.color;
390
- }
391
- let curLineColor = getValue(this.viewer, thing.polygon.outlineColor);
392
- if (curLineColor && curLineColor instanceof Cesium.ColorMaterialProperty) {
393
- curLineColor = curLineColor.color;
394
- }
395
- const curWidth = getValue(this.viewer, thing.polygon.outlineWidth);
396
- if ((curFillColor instanceof Cesium.Color && curFillColor.equals(cFillColor)) &&
397
- (curLineColor instanceof Cesium.Color && curLineColor.equals(cLineColor)) &&
398
- curWidth == width) {
399
- return;
400
- }
401
- thing.polygon.material = new Cesium.ColorMaterialProperty(cFillColor);
402
- thing.polygon.outlineColor = new Cesium.ConstantProperty(cLineColor);
403
- thing.polygon.outlineWidth = new Cesium.ConstantProperty(width);
404
- }
405
- else if (thing.polyline) {
406
- const bColor = lStyle.lineColor ? bruce_models_1.Calculator.GetColor(lStyle.lineColor, data, []) : null;
407
- const cColor = bColor ? colorToCColor(bColor) : Cesium.Color.fromCssColorString("rgba(255, 193, 7, 0.8)");
408
- let width = lStyle.lineWidth ? bruce_models_1.Calculator.GetNumber(lStyle.lineWidth, data, []) : null;
409
- if (width == null) {
410
- width = 2;
411
- }
412
- width = (0, js_utils_1.EnsureNumber)(width);
413
- if (width < 0.01) {
414
- width = 0;
415
- }
416
- let curColor = getValue(this.viewer, thing.polyline.material);
417
- if (curColor && curColor instanceof Cesium.ColorMaterialProperty) {
418
- curColor = curColor.color;
419
- }
420
- const curWidth = getValue(this.viewer, thing.polyline.width);
421
- if ((curColor instanceof Cesium.Color && curColor.equals(cColor)) &&
422
- curWidth == width) {
423
- return;
424
- }
425
- thing.polyline.material = new Cesium.ColorMaterialProperty(cColor);
426
- thing.polyline.width = new Cesium.ConstantProperty(width);
427
- }
428
- };
429
- let toForceUpdate = [];
430
- /**
431
- * Registers a given cesium entity produced from rendering geojson.
432
- * Since one nextspace entity can have multiple cesium entities, there is special logic to group them together.
433
- * @param thing
434
- * @returns
435
- */
436
- const register = (thing) => {
437
- var _a, _b, _c, _d;
438
- // See if the cesium entity already exists in a group.
439
- let group = groups.find((x) => { var _a; return ((_a = x.visual) === null || _a === void 0 ? void 0 : _a.id) == thing.id || x.siblings.find(x => (x === null || x === void 0 ? void 0 : x.id) == thing.id); });
440
- if (group) {
441
- return;
442
- }
443
- const metadata = getValue(this.viewer, thing === null || thing === void 0 ? void 0 : thing.properties);
444
- const entityId = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.Bruce) === null || _a === void 0 ? void 0 : _a.ID;
445
- if (!entityId) {
210
+ try {
211
+ // Process the initial batch.
212
+ await this.renderAsIndividuals(initialEntities, initialForce);
213
+ if (this.disposed) {
446
214
  return;
447
215
  }
448
- // Find group for the nextspace entity ID.
449
- group = groups.find((x) => x.entityId == entityId);
450
- // No group yet. We can designate this as the primary entity and create a new group for it.
451
- if (!group) {
452
- group = {
453
- entityId: entityId,
454
- visual: thing,
455
- tagIds: (_b = metadata === null || metadata === void 0 ? void 0 : metadata.Bruce) === null || _b === void 0 ? void 0 : _b["Layer.ID"],
456
- entityTypeId: (_c = metadata === null || metadata === void 0 ? void 0 : metadata.Bruce) === null || _c === void 0 ? void 0 : _c["EntityType.ID"],
457
- siblings: [],
458
- data: entities.find(x => { var _a; return ((_a = x.Bruce) === null || _a === void 0 ? void 0 : _a.ID) == entityId; }),
459
- rego: null
460
- };
461
- groups.push(group);
462
- applyStyle(thing, entityId, group.data);
463
- const rego = {
464
- entityId: entityId,
465
- menuItemId: this.item.id,
466
- menuItemType: this.item.Type,
467
- visual: thing,
468
- priority: this.renderPriority,
469
- entityTypeId: group.entityTypeId,
470
- accountId: this.apiGetter.accountId,
471
- tagIds: group.tagIds ? [].concat(group.tagIds) : [],
472
- name: entityType ? (_d = bruce_models_1.Entity.CalculateName({
473
- entity: group.data,
474
- type: entityType,
475
- defaultToId: false
476
- })) !== null && _d !== void 0 ? _d : "Unnamed Entity" : "Unnamed Entity",
477
- cdn: this.item.cdnEnabled,
478
- collection: source.entities
479
- };
480
- group.rego = rego;
481
- this.visualsManager.AddRego({
482
- rego,
483
- requestRender: false
484
- });
485
- }
486
- // Found a group. We flag this as a sibling entity of the primary.
487
- else {
488
- applyStyle(thing, entityId, group.data);
489
- group.siblings.push(thing);
490
- group.visual._siblingGraphics = group.siblings;
491
- thing._parentEntity = group.visual;
492
- if (group.rego) {
493
- this.visualsManager.RefreshMark({
494
- rego: group.rego
495
- });
496
- if (!toForceUpdate.includes(entityId)) {
497
- toForceUpdate.push(entityId);
216
+ // Process any queued items.
217
+ while (this.renderQueue.length > 0 && !this.disposed) {
218
+ // If multiple entities are waiting, combine them to reduce render operations.
219
+ const allEntities = [];
220
+ const forces = [];
221
+ // Collect all pending entities.
222
+ while (this.renderQueue.length > 0) {
223
+ const entities = this.renderQueue.shift();
224
+ const force = this.renderQueueForceFlags.shift();
225
+ if (entities && entities.length > 0) {
226
+ allEntities.push(...entities);
227
+ forces.push(force);
498
228
  }
499
229
  }
230
+ if (allEntities.length > 0) {
231
+ // If any are forced, we need to force the render.
232
+ const combinedForce = forces.some(f => f === true);
233
+ const uniqueEntities = this.deduplicateEntities(allEntities);
234
+ await this.renderAsIndividuals(uniqueEntities, combinedForce);
235
+ }
236
+ }
237
+ }
238
+ finally {
239
+ this.renderQueueActive = false;
240
+ }
241
+ }
242
+ deduplicateEntities(entities) {
243
+ const entityMap = new Map();
244
+ for (const entity of entities) {
245
+ if (entity && entity.Bruce && entity.Bruce.ID) {
246
+ entityMap.set(entity.Bruce.ID, entity);
500
247
  }
501
- };
502
- const sEntities = source.entities.values;
503
- for (let i = 0; i < sEntities.length; i++) {
504
- const cEntity = sEntities[i];
505
- register(cEntity);
506
- }
507
- if (toForceUpdate.length) {
508
- this.visualsManager.ForceUpdate({
509
- entityIds: toForceUpdate,
510
- refreshColors: true,
511
- requestRender: false
512
- });
513
248
  }
514
- this.viewer.scene.requestRender();
249
+ return Array.from(entityMap.values());
515
250
  }
516
251
  /**
517
252
  * Our default path.
@@ -522,16 +257,28 @@ var EntitiesIdsRenderManager;
522
257
  */
523
258
  async renderAsIndividuals(entities, force = false) {
524
259
  var _a;
260
+ if (this.disposed || this.viewer.isDestroyed() || !entities.length) {
261
+ return;
262
+ }
263
+ let entitiesHistoric = {};
264
+ if ((this.item.BruceEntity.historic || this.item.BruceEntity.historicAttrKey) && entities.length) {
265
+ entitiesHistoric = await this.getHistoricInfo(entities);
266
+ }
525
267
  const { updated, entities: cEntities } = await entity_render_engine_1.EntityRenderEngine.Render({
526
268
  viewer: this.viewer,
527
269
  apiGetter: this.apiGetter,
528
270
  entities: entities,
529
271
  menuItemId: this.item.id,
530
272
  visualRegister: this.visualsManager,
273
+ entitiesHistoric: entitiesHistoric,
274
+ entityHistoricDrawTrack: this.item.historicDrawTrack,
531
275
  zoomControl: this.item.CameraZoomSettings,
532
276
  force
533
277
  });
534
278
  for (let i = 0; i < entities.length; i++) {
279
+ if (this.disposed) {
280
+ break;
281
+ }
535
282
  const entity = entities[i];
536
283
  const id = entity.Bruce.ID;
537
284
  const cEntity = cEntities.get(id);
@@ -580,10 +327,143 @@ var EntitiesIdsRenderManager;
580
327
  (_a = this.clustering) === null || _a === void 0 ? void 0 : _a.RemoveEntity(id, false);
581
328
  }
582
329
  }
583
- this.viewer.scene.requestRender();
584
- if (this.clustering && entities.length) {
585
- this.clustering.Update();
330
+ if (!this.disposed) {
331
+ this.viewer.scene.requestRender();
332
+ if (this.clustering && entities.length) {
333
+ this.clustering.Update();
334
+ }
335
+ }
336
+ }
337
+ /**
338
+ * Returns historic information for the given set of Entities at the current timeline range.
339
+ * @param entities
340
+ */
341
+ async getHistoricInfo(entities) {
342
+ const startTmp = Cesium.JulianDate.toDate(this.viewer.clock.startTime);
343
+ const stopTmp = Cesium.JulianDate.toDate(this.viewer.clock.stopTime);
344
+ let rangesToRequest = [{
345
+ start: startTmp.toISOString(),
346
+ stop: stopTmp.toISOString()
347
+ }];
348
+ // If we already have cached data, determine what ranges we're missing.
349
+ if (this.lastHistoricMin && this.lastHistoricMax && Object.keys(this.entitiesHistoric).length >= entities.length) {
350
+ const cachedStart = new Date(this.lastHistoricMin).getTime();
351
+ const cachedStop = new Date(this.lastHistoricMax).getTime();
352
+ const newStart = startTmp.getTime();
353
+ const newStop = stopTmp.getTime();
354
+ // Complete overlap - we already have all the data.
355
+ if (newStart >= cachedStart && newStop <= cachedStop) {
356
+ return this.entitiesHistoric;
357
+ }
358
+ // Calculate missing ranges.
359
+ rangesToRequest = [];
360
+ // Check if we need data before our cached range.
361
+ if (newStart < cachedStart) {
362
+ rangesToRequest.push({
363
+ start: startTmp.toISOString(),
364
+ stop: new Date(cachedStart).toISOString()
365
+ });
366
+ }
367
+ // Check if we need data after our cached range.
368
+ if (newStop > cachedStop) {
369
+ rangesToRequest.push({
370
+ start: new Date(cachedStop).toISOString(),
371
+ stop: stopTmp.toISOString()
372
+ });
373
+ }
374
+ }
375
+ const entityIds = entities.map(x => x.Bruce.ID);
376
+ const combined = { ...this.entitiesHistoric };
377
+ // Make requests for each missing range
378
+ for (const range of rangesToRequest) {
379
+ if (this.disposed) {
380
+ break;
381
+ }
382
+ // Add padding to ensure we get all data.
383
+ const start = new Date(range.start);
384
+ const stop = new Date(range.stop);
385
+ start.setSeconds(start.getSeconds() - 1);
386
+ stop.setSeconds(stop.getSeconds() + 1);
387
+ const paddedStartStr = start.toISOString();
388
+ const paddedStopStr = stop.toISOString();
389
+ const historicData = await bruce_models_1.EntityHistoricData.GetList({
390
+ attrKey: this.item.BruceEntity.historicAttrKey,
391
+ dateTimeFrom: paddedStartStr,
392
+ dateTimeTo: paddedStopStr,
393
+ entityIds: entityIds,
394
+ api: this.apiGetter.getApi()
395
+ });
396
+ if (this.disposed) {
397
+ break;
398
+ }
399
+ // Merge the new data with existing data
400
+ const records = historicData.recordsByIds;
401
+ const recordsIds = Object.keys(records);
402
+ for (let i = 0; i < recordsIds.length; i++) {
403
+ const entityId = recordsIds[i];
404
+ const latest = records[entityId] || [];
405
+ const current = combined[entityId] || [];
406
+ // Use a Map to de-duplicate by timestamp.
407
+ const tmp = new Map();
408
+ for (let j = 0; j < current.length; j++) {
409
+ const record = current[j];
410
+ const dateTime = new Date(record.dateTime).getTime();
411
+ tmp.set(dateTime, record);
412
+ }
413
+ for (let j = 0; j < latest.length; j++) {
414
+ const record = latest[j];
415
+ const dateTime = new Date(record.dateTime).getTime();
416
+ tmp.set(dateTime, record);
417
+ }
418
+ // Convert to array and sort by date.
419
+ const sorted = Array.from(tmp.values()).sort((a, b) => {
420
+ return new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime();
421
+ });
422
+ combined[entityId] = sorted;
423
+ }
424
+ }
425
+ if (!this.disposed) {
426
+ // Update our cache boundaries.
427
+ this.lastHistoricMin = startTmp.toISOString();
428
+ this.lastHistoricMax = stopTmp.toISOString();
429
+ this.entitiesHistoric = combined;
430
+ }
431
+ return combined;
432
+ }
433
+ viewerDateTimeSub() {
434
+ var _a, _b;
435
+ 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) {
436
+ return;
586
437
  }
438
+ const THROTTLE_INTERVAL = 2000;
439
+ let queue = new bruce_models_1.DelayQueue(() => {
440
+ this.onGetterUpdate(Object.keys(this.renderedEntities));
441
+ }, THROTTLE_INTERVAL, true);
442
+ let clockTickRemoval;
443
+ let prevTick = this.viewer.clock.currentTime.toString();
444
+ clockTickRemoval = this.viewer.clock.onTick.addEventListener(() => {
445
+ if (this.disposed || this.viewer.isDestroyed()) {
446
+ return;
447
+ }
448
+ const currentTick = this.viewer.clock.currentTime.toString();
449
+ if (currentTick === prevTick) {
450
+ return;
451
+ }
452
+ queue.Call();
453
+ });
454
+ this.viewerDateTimeChangeRemoval = () => {
455
+ if (clockTickRemoval) {
456
+ clockTickRemoval();
457
+ clockTickRemoval = null;
458
+ }
459
+ queue.Dispose();
460
+ queue = null;
461
+ };
462
+ }
463
+ viewerDateTimeDispose() {
464
+ var _a;
465
+ (_a = this.viewerDateTimeChangeRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
466
+ this.viewerDateTimeChangeRemoval = null;
587
467
  }
588
468
  }
589
469
  EntitiesIdsRenderManager.Manager = Manager;