bruce-cesium 7.3.8 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/bruce-cesium.es5.js +2911 -626
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2910 -623
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +8 -2
  6. package/dist/lib/bruce-cesium.js.map +1 -1
  7. package/dist/lib/rendering/entity-gatherer.js +249 -16
  8. package/dist/lib/rendering/entity-gatherer.js.map +1 -1
  9. package/dist/lib/rendering/render-managers/common/entity-label-group.js +482 -0
  10. package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -0
  11. package/dist/lib/rendering/render-managers/common/entity-label.js +1213 -155
  12. package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
  13. package/dist/lib/rendering/render-managers/common/historic-utils.js +64 -7
  14. package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -1
  15. package/dist/lib/rendering/render-managers/common/point-clustering-core.js +85 -0
  16. package/dist/lib/rendering/render-managers/common/point-clustering-core.js.map +1 -0
  17. package/dist/lib/rendering/render-managers/common/point-clustering.js +566 -419
  18. package/dist/lib/rendering/render-managers/common/point-clustering.js.map +1 -1
  19. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +1 -1
  20. package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js.map +1 -1
  21. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js +2 -2
  22. package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
  23. package/dist/lib/rendering/visuals-register.js +273 -24
  24. package/dist/lib/rendering/visuals-register.js.map +1 -1
  25. package/dist/types/bruce-cesium.d.ts +4 -1
  26. package/dist/types/rendering/entity-gatherer.d.ts +27 -4
  27. package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +104 -0
  28. package/dist/types/rendering/render-managers/common/entity-label.d.ts +138 -1
  29. package/dist/types/rendering/render-managers/common/historic-utils.d.ts +12 -4
  30. package/dist/types/rendering/render-managers/common/point-clustering-core.d.ts +17 -0
  31. package/dist/types/rendering/render-managers/common/point-clustering.d.ts +37 -72
  32. package/dist/types/rendering/visuals-register.d.ts +43 -2
  33. package/package.json +1 -1
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PointClustering = void 0;
3
+ exports.PointClustering = exports.PointClusterSize = exports.ClusterItems = void 0;
4
4
  const bruce_models_1 = require("bruce-models");
5
5
  const Cesium = require("cesium");
6
6
  const visuals_register_1 = require("../../visuals-register");
7
+ const point_clustering_core_1 = require("./point-clustering-core");
8
+ const drawing_utils_1 = require("../../../utils/drawing-utils");
9
+ const cesium_entity_styler_1 = require("../../../utils/cesium-entity-styler");
10
+ var point_clustering_core_2 = require("./point-clustering-core");
11
+ Object.defineProperty(exports, "ClusterItems", { enumerable: true, get: function () { return point_clustering_core_2.ClusterItems; } });
7
12
  function GetValue(viewer, obj) {
8
13
  if (obj === null || obj === void 0 ? void 0 : obj.getValue) {
9
14
  let date = viewer.scene.lastRenderTime;
@@ -16,6 +21,8 @@ function GetValue(viewer, obj) {
16
21
  }
17
22
  class Point {
18
23
  constructor(lon, lat, id) {
24
+ this.radius = 0;
25
+ this.minDistance = 0;
19
26
  this.lon = lon;
20
27
  this.lat = lat;
21
28
  this.id = id;
@@ -33,8 +40,8 @@ class Circle {
33
40
  let xDist = Math.abs(range.x - this.x);
34
41
  let yDist = Math.abs(range.y - this.y);
35
42
  let r = this.radius;
36
- let w = range.width;
37
- let h = range.height;
43
+ let w = range.width / 2;
44
+ let h = range.height / 2;
38
45
  if (xDist > (w + r) || yDist > (h + r)) {
39
46
  return false;
40
47
  }
@@ -48,8 +55,7 @@ class Circle {
48
55
  Contains(point) {
49
56
  const dx = this.x - point.lon;
50
57
  const dy = this.y - point.lat;
51
- const distance = Math.sqrt(dx * dx + dy * dy);
52
- return distance <= this.radius;
58
+ return dx * dx + dy * dy <= this.radius * this.radius;
53
59
  }
54
60
  }
55
61
  class Rectangle {
@@ -86,13 +92,13 @@ class Quad {
86
92
  let y = this.boundary.y;
87
93
  let w = this.boundary.w / 2;
88
94
  let h = this.boundary.h / 2;
89
- let ne = new Rectangle(x + w / 2, y - h / 2, w, h);
95
+ let ne = new Rectangle(x + w, y - h, w, h);
90
96
  this.northeast = new Quad(ne, this.capacity);
91
- let nw = new Rectangle(x - w / 2, y - h / 2, w, h);
97
+ let nw = new Rectangle(x - w, y - h, w, h);
92
98
  this.northwest = new Quad(nw, this.capacity);
93
- let se = new Rectangle(x + w / 2, y + h / 2, w, h);
99
+ let se = new Rectangle(x + w, y + h, w, h);
94
100
  this.southeast = new Quad(se, this.capacity);
95
- let sw = new Rectangle(x - w / 2, y + h / 2, w, h);
101
+ let sw = new Rectangle(x - w, y + h, w, h);
96
102
  this.southwest = new Quad(sw, this.capacity);
97
103
  this.divided = true;
98
104
  }
@@ -100,7 +106,8 @@ class Quad {
100
106
  if (!this.boundary.Contains(point)) {
101
107
  return false;
102
108
  }
103
- if (this.points.length < this.capacity) {
109
+ // Coincident points must terminate in a bucket rather than subdividing indefinitely.
110
+ if (this.points.length < this.capacity || this.boundary.w < 1e-10) {
104
111
  this.points.push(point);
105
112
  return true;
106
113
  }
@@ -330,496 +337,636 @@ function getClusterImage(params) {
330
337
  }
331
338
  return null;
332
339
  }
333
- const FORCE_UPDATE_BATCH_SIZE = 1000;
334
- const FORCE_UPDATE_BATCH_DELAY = 100;
335
- class PointClustering {
336
- constructor(register, menuItemId, settings) {
337
- this.minDistance = 5000;
338
- this.minTotalCount = 0;
339
- this.disposed = false;
340
- this.registeredEntityIds = new Set();
341
- // Queue to force update entities.
342
- this.updateEntityQueue = [];
343
- this.register = register;
344
- this.viewer = register.Viewer;
345
- this.menuItemId = menuItemId;
346
- let boundary = new Rectangle(0, 0, 360, 180);
347
- this.quadTree = new Quad(boundary, 30);
348
- this.prevClusteredEntities = new Set();
349
- this.currClusteredEntities = new Set();
350
- this.clusterEntities = new Map();
351
- this.updateQueue = new bruce_models_1.DelayQueue(() => {
352
- this.doUpdate();
353
- }, 1000);
354
- this.listenCamera();
355
- this.listenSelection();
356
- if (settings) {
357
- if (!isNaN(+settings.minDistance)) {
358
- this.minDistance = +settings.minDistance;
359
- }
360
- if (!isNaN(+settings.minTotalCount)) {
361
- this.minTotalCount = +settings.minTotalCount;
340
+ const clusterPickers = new WeakMap();
341
+ /*
342
+ * One capture listener set per viewer prevents host canvas picking from undoing cluster selection.
343
+ */
344
+ function getClusterPicker(viewer) {
345
+ let picker = clusterPickers.get(viewer);
346
+ if (picker) {
347
+ return picker;
348
+ }
349
+ const canvas = viewer.canvas;
350
+ picker = {
351
+ owners: new Set(),
352
+ targets: new Map(),
353
+ remove: () => { }
354
+ };
355
+ let gesture;
356
+ let released;
357
+ const handler = (event) => {
358
+ if (viewer.isDestroyed())
359
+ return;
360
+ const stop = () => { event.preventDefault(); event.stopImmediatePropagation(); };
361
+ if (event.type === "pointermove") {
362
+ if (gesture && Math.hypot(event.clientX - gesture.x, event.clientY - gesture.y) > 6) {
363
+ gesture.moved = true;
362
364
  }
365
+ return;
363
366
  }
364
- }
365
- Update() {
366
- this.updateQueue.Call();
367
- }
368
- queueForceUpdate(entityIds) {
369
- for (let i = 0; i < entityIds.length; i++) {
370
- if (this.updateEntityQueue.includes(entityIds[i])) {
371
- continue;
367
+ if (event.type === "pointercancel") {
368
+ if (gesture) {
369
+ released = { ...gesture, moved: true };
370
+ gesture = null;
371
+ if (released.target) {
372
+ stop();
373
+ }
372
374
  }
373
- this.updateEntityQueue.push(entityIds[i]);
374
- }
375
- this.runForceUpdateQueue();
376
- }
377
- runForceUpdateQueue() {
378
- if (!this.updateEntityQueue.length) {
379
375
  return;
380
376
  }
381
- if (this.queueInterval) {
377
+ if (event.button !== 0) {
382
378
  return;
383
379
  }
384
- this.queueInterval = setInterval(() => {
385
- if (this.disposed) {
386
- clearInterval(this.queueInterval);
387
- this.queueInterval = null;
380
+ if (event.type === "pointerup" || event.type === "mouseup") {
381
+ // Only consume releases whose press we owned, even if the cursor left the billboard.
382
+ if (!gesture) {
388
383
  return;
389
384
  }
390
- const ids = this.updateEntityQueue.splice(0, FORCE_UPDATE_BATCH_SIZE);
391
- this.register.ForceUpdate({
392
- entityIds: ids
393
- });
394
- if (!this.updateEntityQueue.length) {
395
- clearInterval(this.queueInterval);
396
- this.queueInterval = null;
385
+ gesture.moved || (gesture.moved = Math.hypot(event.clientX - gesture.x, event.clientY - gesture.y) > 6);
386
+ released = gesture;
387
+ gesture = null;
388
+ if (released.target) {
389
+ stop();
397
390
  }
398
- }, FORCE_UPDATE_BATCH_DELAY);
399
- }
400
- /**
401
- * Starts listening to camera changes.
402
- * This will trigger the clustering update whenever camera is moved.
403
- */
404
- listenCamera() {
405
- const viewer = this.viewer;
406
- let lastPos3d;
407
- const hasMoved = () => {
408
- var _a;
409
- const cameraPos = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.camera) === null || _a === void 0 ? void 0 : _a.positionCartographic;
410
- if (!cameraPos) {
411
- return false;
412
- }
413
- const pos3d = Cesium.Cartographic.toCartesian(cameraPos);
414
- if (!lastPos3d) {
415
- lastPos3d = pos3d;
416
- return false;
417
- }
418
- const distance = Cesium.Cartesian3.distance(pos3d, lastPos3d);
419
- lastPos3d = pos3d;
420
- // 50 meter movement at least.
421
- return distance > 50;
422
- };
423
- this.listenCameraRemoval = viewer.scene.camera.changed.addEventListener(() => {
424
- if (hasMoved()) {
425
- this.updateQueue.Call();
391
+ return;
392
+ }
393
+ const rect = canvas.getBoundingClientRect();
394
+ const picked = viewer.scene.pick(new Cesium.Cartesian2(event.clientX - rect.left, event.clientY - rect.top));
395
+ const select = picker.targets.get(picked === null || picked === void 0 ? void 0 : picked.id);
396
+ if (event.type === "pointerdown" || event.type === "mousedown") {
397
+ released = null;
398
+ gesture = { target: select ? picked.id : null, x: event.clientX, y: event.clientY, moved: false };
399
+ if (gesture.target) {
400
+ stop();
426
401
  }
427
- });
428
- }
429
- /**
430
- * Stops listening to camera changes.
431
- */
432
- unlistenCamera() {
433
- if (this.listenCameraRemoval) {
434
- this.listenCameraRemoval();
435
- this.listenCameraRemoval = null;
402
+ return;
436
403
  }
404
+ if (released) {
405
+ const owned = !!released.target;
406
+ const invalid = released.moved || released.target !== (picked === null || picked === void 0 ? void 0 : picked.id);
407
+ released = null;
408
+ if (!owned) {
409
+ return;
410
+ }
411
+ if (invalid) {
412
+ stop();
413
+ return;
414
+ }
415
+ }
416
+ if (!select) {
417
+ return;
418
+ }
419
+ stop();
420
+ if (event.type === "click") {
421
+ select();
422
+ }
423
+ };
424
+ const types = ["pointerdown", "pointermove", "pointerup", "pointercancel", "mousedown", "mouseup", "click", "dblclick"];
425
+ if (canvas === null || canvas === void 0 ? void 0 : canvas.addEventListener) {
426
+ for (const type of types) {
427
+ canvas.addEventListener(type, handler, true);
428
+ }
429
+ picker.remove = () => {
430
+ for (const type of types) {
431
+ canvas.removeEventListener(type, handler, true);
432
+ }
433
+ };
437
434
  }
438
- /**
439
- * Starts listening to entity selection changes.
440
- * Selected entities are not clustered.
441
- */
442
- listenSelection() {
443
- this.listenUpdateRemoval = this.register.OnUpdate.Subscribe((data) => {
444
- if (data.type == visuals_register_1.VisualsRegister.EVisualUpdateType.DeselectAll) {
445
- this.updateQueue.Call();
435
+ clusterPickers.set(viewer, picker);
436
+ return picker;
437
+ }
438
+ /*
439
+ * Count tiers are bounded so large clusters do not cover the scene with enormous billboards.
440
+ */
441
+ function PointClusterSize(count) {
442
+ return count >= 10000 ? 140 : count >= 1000 ? 120 : count >= 100 ? 90 : count >= 10 ? 80 : 70;
443
+ }
444
+ exports.PointClusterSize = PointClusterSize;
445
+ class PointClustering {
446
+ constructor(register, menuItemId, settings = {}) {
447
+ this.register = register;
448
+ this.menuItemId = menuItemId;
449
+ this.settings = settings;
450
+ this.minDistance = 5000;
451
+ this.minTotalCount = 0;
452
+ this.currClusteredEntities = new Set();
453
+ this.clusterEntities = new Map();
454
+ this.clusterViews = new Map();
455
+ this.registeredEntityIds = new Set();
456
+ this.records = new Map();
457
+ this.disposed = false;
458
+ this.removals = [];
459
+ this.selectionQueued = false;
460
+ this.viewer = register.Viewer;
461
+ this.settings = settings = settings || {};
462
+ if (Number.isFinite(+settings.minDistance)) {
463
+ this.minDistance = Math.max(0, +settings.minDistance);
464
+ }
465
+ if (Number.isFinite(+settings.minTotalCount)) {
466
+ this.minTotalCount = Math.max(0, +settings.minTotalCount);
467
+ }
468
+ this.quadTree = new Quad(new Rectangle(0, 0, 360, 180), 30);
469
+ this.updateQueue = new bruce_models_1.DelayQueue(() => this.doUpdate(), 1000);
470
+ this.picker = getClusterPicker(this.viewer);
471
+ this.picker.owners.add(this);
472
+ this.removals.push(this.viewer.scene.camera.changed.addEventListener(() => this.Update()));
473
+ this.removals.push(this.viewer.scene.terrainProviderChanged.addEventListener(() => this.Update()));
474
+ this.removals.push(register.OnUpdate.Subscribe(data => {
475
+ if (data.type === visuals_register_1.VisualsRegister.EVisualUpdateType.DeselectAll ||
476
+ (data.entityId && this.registeredEntityIds.has(data.entityId) && data.selected != null)) {
477
+ if (!this.selectionQueued) {
478
+ this.selectionQueued = true;
479
+ Promise.resolve().then(() => {
480
+ this.selectionQueued = false;
481
+ if (!this.disposed)
482
+ this.refreshSelection();
483
+ });
484
+ }
446
485
  }
447
- else if (data.entityId &&
448
- data.selected != null &&
449
- data.type == visuals_register_1.VisualsRegister.EVisualUpdateType.Update &&
450
- this.registeredEntityIds.has(data.entityId)) {
451
- this.updateQueue.Call();
486
+ if ("isolated" in data || (data.entityId && this.registeredEntityIds.has(data.entityId) && "hidden" in data)) {
487
+ this.Update();
452
488
  }
453
- });
489
+ }));
454
490
  }
455
- unlistenSelection() {
456
- if (this.listenUpdateRemoval) {
457
- this.listenUpdateRemoval();
458
- this.listenUpdateRemoval = null;
491
+ Update() {
492
+ if (!this.disposed) {
493
+ this.updateQueue.Call();
459
494
  }
460
495
  }
461
- /**
462
- * Removes all clusters and updates entities to no longer be suppressed.
463
- * @returns
464
- */
496
+ GetClusters() {
497
+ return Array.from(this.clusterViews.values(), view => ({ ...view, entityIds: [...view.entityIds] }));
498
+ }
465
499
  Dispose() {
466
500
  if (this.disposed) {
467
501
  return;
468
502
  }
469
- for (let [, entity] of Array.from(this.clusterEntities)) {
470
- this.viewer.entities.remove(entity);
471
- }
472
- this.clusterEntities.clear();
473
- this.unlistenCamera();
474
- this.unlistenSelection();
475
503
  this.disposed = true;
476
- this.updateEntityQueue = [];
477
- // Restore entities.
478
- const toUpdateIds = [];
479
- for (let id of Array.from(this.registeredEntityIds)) {
480
- const rego = this.register.GetRego({
481
- entityId: id,
482
- menuItemId: this.menuItemId
483
- });
484
- if (rego && rego.overrideShow) {
504
+ this.updateQueue.Dispose();
505
+ for (const remove of this.removals) {
506
+ remove();
507
+ }
508
+ for (const id of this.clusterEntities.keys()) {
509
+ this.removeClusterEntity(id);
510
+ }
511
+ this.picker.owners.delete(this);
512
+ if (!this.picker.owners.size) {
513
+ this.picker.remove();
514
+ clusterPickers.delete(this.viewer);
515
+ }
516
+ const restore = [];
517
+ for (const id of this.registeredEntityIds) {
518
+ const rego = this.register.GetRego({ entityId: id, menuItemId: this.menuItemId });
519
+ if ((rego === null || rego === void 0 ? void 0 : rego.overrideShow) === false) {
485
520
  rego.overrideShow = null;
486
- toUpdateIds.push(id);
521
+ restore.push(id);
487
522
  }
488
523
  }
489
- if (toUpdateIds.length) {
490
- this.register.ForceUpdate({
491
- entityIds: toUpdateIds,
492
- });
524
+ this.records.clear();
525
+ this.registeredEntityIds.clear();
526
+ this.currClusteredEntities.clear();
527
+ if (restore.length) {
528
+ this.register.ForceUpdate({ entityIds: restore });
493
529
  }
494
530
  }
495
- /**
496
- * Calculates center of given points.
497
- * @param points
498
- * @returns
499
- */
500
- calculateCentroid(points) {
501
- let lonSum = 0;
502
- let latSum = 0;
503
- for (let point of points) {
504
- lonSum += point.lon;
505
- latSum += point.lat;
531
+ refreshSelection() {
532
+ for (const view of this.clusterViews.values()) {
533
+ const selected = view.entityIds.length > 0 && view.entityIds.every(entityId => this.register.GetIsSelected({ entityId, menuItemId: this.menuItemId }));
534
+ if (selected === view.selected)
535
+ continue;
536
+ view.selected = selected;
537
+ if (selected)
538
+ cesium_entity_styler_1.CesiumEntityStyler.Select({ viewer: this.viewer, entity: view.entity, requestRender: false });
539
+ else
540
+ cesium_entity_styler_1.CesiumEntityStyler.Deselect({ viewer: this.viewer, entity: view.entity, requestRender: false });
506
541
  }
507
- const lonAvg = lonSum / points.length;
508
- const latAvg = latSum / points.length;
509
- return new Point(lonAvg, latAvg, "");
542
+ this.viewer.scene.requestRender();
510
543
  }
511
- /**
512
- * Gathers current clusters and renders them.
513
- * @returns
514
- */
515
- doUpdate() {
516
- if (this.disposed) {
544
+ selectCluster(clusterId) {
545
+ const view = this.clusterViews.get(clusterId);
546
+ if (this.disposed || !view)
517
547
  return;
548
+ this.register.ClearSelected({ requestRender: false });
549
+ this.register.SetSelected({ entityIds: view.entityIds, selected: true });
550
+ this.refreshSelection();
551
+ }
552
+ resolveGeometry(visual, seen = new Set()) {
553
+ var _a, _b, _c;
554
+ if (!visual || seen.has(visual)) {
555
+ return null;
556
+ }
557
+ seen.add(visual);
558
+ const value = (property) => GetValue(this.viewer, property);
559
+ const valid = (p) => p && [p.x, p.y, p.z].every(Number.isFinite) && Cesium.Cartesian3.magnitudeSquared(p) > 0;
560
+ const kind = visual.model || visual.box || visual.cylinder || visual.ellipsoid ? "model" :
561
+ visual.polygon || visual.rectangle || visual.ellipse ? "polygon" :
562
+ visual.polyline || visual.corridor || visual.wall || visual.polylineVolume ? "polyline" :
563
+ visual.point || visual.billboard || visual.label ? "point" : "other";
564
+ const configured = (_a = this.settings.minDistanceByType) === null || _a === void 0 ? void 0 : _a[kind];
565
+ let minDistance = Number.isFinite(configured) ? Math.max(0, configured) : this.minDistance * (kind === "point" || kind === "other" ? 1 : 2);
566
+ const graphic = (visual.model || visual.polygon || visual.rectangle || visual.ellipse || visual.polyline ||
567
+ visual.corridor || visual.wall || visual.polylineVolume || visual.billboard || visual.point || visual.label || visual.box || visual.cylinder || visual.ellipsoid);
568
+ let heightRef = (_b = value(graphic === null || graphic === void 0 ? void 0 : graphic.heightReference)) !== null && _b !== void 0 ? _b : Cesium.HeightReference.NONE;
569
+ if (visual.polyline) {
570
+ heightRef = value(visual.polyline.clampToGround) ? Cesium.HeightReference.CLAMP_TO_GROUND : Cesium.HeightReference.NONE;
571
+ }
572
+ let positions;
573
+ let height;
574
+ if (visual.polygon) {
575
+ const hierarchy = value(visual.polygon.hierarchy);
576
+ positions = hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.positions;
577
+ if (!value(visual.polygon.perPositionHeight)) {
578
+ height = value(visual.polygon.height);
579
+ if (height == null && value(visual.polygon.extrudedHeight) == null) {
580
+ heightRef = Cesium.HeightReference.CLAMP_TO_GROUND;
581
+ }
582
+ height !== null && height !== void 0 ? height : (height = 0);
583
+ }
584
+ }
585
+ else if (visual.rectangle) {
586
+ const rectangle = value(visual.rectangle.coordinates);
587
+ if (rectangle) {
588
+ positions = [Cesium.Rectangle.northwest(rectangle), Cesium.Rectangle.northeast(rectangle),
589
+ Cesium.Rectangle.southwest(rectangle), Cesium.Rectangle.southeast(rectangle)].map(c => Cesium.Cartographic.toCartesian(c));
590
+ }
591
+ height = value(visual.rectangle.height);
592
+ if (height == null && value(visual.rectangle.extrudedHeight) == null) {
593
+ heightRef = Cesium.HeightReference.CLAMP_TO_GROUND;
594
+ }
595
+ height !== null && height !== void 0 ? height : (height = 0);
596
+ }
597
+ else if (visual.polyline || visual.corridor || visual.wall || visual.polylineVolume) {
598
+ positions = value(graphic.positions);
599
+ if (visual.wall && positions) {
600
+ const minimum = value(visual.wall.minimumHeights);
601
+ const maximum = value(visual.wall.maximumHeights);
602
+ positions = positions.flatMap((position, index) => {
603
+ var _a, _b;
604
+ if (!valid(position)) {
605
+ return [];
606
+ }
607
+ const carto = Cesium.Cartographic.fromCartesian(position);
608
+ if (!carto) {
609
+ return [];
610
+ }
611
+ return ([(_a = minimum === null || minimum === void 0 ? void 0 : minimum[index]) !== null && _a !== void 0 ? _a : 0, (_b = maximum === null || maximum === void 0 ? void 0 : maximum[index]) !== null && _b !== void 0 ? _b : carto.height]
612
+ .filter(Number.isFinite).map(height => Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, height)));
613
+ });
614
+ }
518
615
  }
519
- // 1: Update precision.
520
- // This defines how far apart these clusters can be.
521
- const cameraHeight = this.viewer.camera.positionCartographic.height;
522
- this.getClusterSpacing(cameraHeight);
523
- // 2: Get clusters.
524
- const { clusters, noLongerClustered } = this.getClusters();
525
- const entitiesToUpdate = [];
526
- // 3: Remove all cesium cluster entities that are no longer clustered.
527
- for (let id of Array.from(noLongerClustered)) {
528
- this.removeClusterEntity(id);
616
+ if (visual.corridor || visual.ellipse) {
617
+ height = value(graphic.height);
618
+ if (height == null && value(graphic.extrudedHeight) == null)
619
+ heightRef = Cesium.HeightReference.CLAMP_TO_GROUND;
620
+ height !== null && height !== void 0 ? height : (height = 0);
529
621
  }
530
- // 4: Stop hiding entities that are no longer clustered.
531
- const previousEntityIds = new Set(this.registeredEntityIds);
532
- for (let id of Array.from(previousEntityIds)) {
533
- if (!this.currClusteredEntities.has(id)) {
534
- const rego = this.register.GetRego({
535
- entityId: id,
536
- menuItemId: this.menuItemId
622
+ if (!(positions === null || positions === void 0 ? void 0 : positions.length)) {
623
+ const position = value(visual.position);
624
+ if (valid(position)) {
625
+ positions = [position];
626
+ }
627
+ }
628
+ const children = visual._siblingGraphics || [];
629
+ // Container entities often copy their first child's position; don't count that anchor twice.
630
+ if (!graphic && children.length) {
631
+ positions = [];
632
+ minDistance = 0;
633
+ }
634
+ const resolved = [];
635
+ for (const position of positions || []) {
636
+ if (!valid(position))
637
+ continue;
638
+ let source = position;
639
+ if (height != null) {
640
+ const carto = Cesium.Cartographic.fromCartesian(position);
641
+ if (!carto)
642
+ continue;
643
+ source = Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, height);
644
+ }
645
+ const actual = drawing_utils_1.DrawingUtils.EnsurePosHeight({
646
+ viewer: this.viewer,
647
+ pos3d: source,
648
+ desiredHeightRef: Cesium.HeightReference.NONE,
649
+ heightRef
650
+ });
651
+ if (valid(actual)) {
652
+ resolved.push(actual);
653
+ }
654
+ }
655
+ const extrudedHeight = value(graphic === null || graphic === void 0 ? void 0 : graphic.extrudedHeight);
656
+ if (Number.isFinite(extrudedHeight)) {
657
+ const reference = (_c = value(graphic.extrudedHeightReference)) !== null && _c !== void 0 ? _c : Cesium.HeightReference.NONE;
658
+ for (const position of positions || []) {
659
+ if (!valid(position)) {
660
+ continue;
661
+ }
662
+ const carto = Cesium.Cartographic.fromCartesian(position);
663
+ if (!carto) {
664
+ continue;
665
+ }
666
+ const actual = drawing_utils_1.DrawingUtils.EnsurePosHeight({
667
+ viewer: this.viewer,
668
+ pos3d: Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, extrudedHeight),
669
+ desiredHeightRef: Cesium.HeightReference.NONE, heightRef: reference
537
670
  });
538
- if (rego && rego.overrideShow != null) {
539
- rego.overrideShow = null;
540
- entitiesToUpdate.push(id);
671
+ if (valid(actual)) {
672
+ resolved.push(actual);
541
673
  }
542
674
  }
543
675
  }
544
- const getScale = (count) => {
545
- // const MIN_SCALE = 15;
546
- // const MAX_SCALE = 80;
547
- // const SCALE_PER_POINT = 1.01;
548
- // let scale = MIN_SCALE + (count * SCALE_PER_POINT);
549
- // scale = Math.min(scale, MAX_SCALE);
550
- // return scale;
551
- if (count >= 10000) {
552
- return 140;
676
+ const spheres = [];
677
+ const anchors = [];
678
+ if (resolved.length) {
679
+ const sphere = Cesium.BoundingSphere.fromPoints(resolved);
680
+ // A chord midpoint can lie underground. Preserve the average rendered vertex altitude.
681
+ const center = Cesium.Cartographic.fromCartesian(sphere.center);
682
+ if (center) {
683
+ const altitude = resolved.reduce((sum, p) => sum + Cesium.Cartographic.fromCartesian(p).height, 0) / resolved.length;
684
+ const position = Cesium.Cartesian3.fromRadians(center.longitude, center.latitude, altitude);
685
+ sphere.radius += Cesium.Cartesian3.distance(position, sphere.center);
686
+ sphere.center = position;
553
687
  }
554
- if (count >= 1000) {
555
- return 120;
688
+ if (visual.ellipse) {
689
+ sphere.radius = Math.max(sphere.radius, value(visual.ellipse.semiMajorAxis) || 0, value(visual.ellipse.semiMinorAxis) || 0);
556
690
  }
557
- if (count >= 100) {
558
- return 90;
691
+ if (visual.box) {
692
+ sphere.radius = Math.max(sphere.radius, Cesium.Cartesian3.magnitude(value(visual.box.dimensions) || Cesium.Cartesian3.ZERO) / 2);
559
693
  }
560
- if (count >= 10) {
561
- return 80;
694
+ if (visual.ellipsoid) {
695
+ sphere.radius = Math.max(sphere.radius, Cesium.Cartesian3.maximumComponent(value(visual.ellipsoid.radii) || Cesium.Cartesian3.ZERO));
562
696
  }
563
- return 70;
697
+ if (visual.cylinder) {
698
+ sphere.radius = Math.max(sphere.radius, Math.hypot((value(visual.cylinder.length) || 0) / 2, Math.max(value(visual.cylinder.topRadius) || 0, value(visual.cylinder.bottomRadius) || 0)));
699
+ }
700
+ if (visual.corridor) {
701
+ sphere.radius += (value(visual.corridor.width) || 0) / 2;
702
+ }
703
+ if (visual.polylineVolume) {
704
+ const shape = value(visual.polylineVolume.shape) || [];
705
+ sphere.radius += shape.reduce((radius, p) => Math.max(radius, Math.hypot(p.x, p.y)), 0);
706
+ }
707
+ spheres.push(sphere);
708
+ anchors.push(sphere.center);
709
+ }
710
+ for (const child of children) {
711
+ const result = this.resolveGeometry(child, seen);
712
+ if (!result) {
713
+ continue;
714
+ }
715
+ spheres.push(new Cesium.BoundingSphere(result.position, result.radius));
716
+ anchors.push(result.position);
717
+ minDistance = Math.max(minDistance, result.minDistance);
718
+ }
719
+ if (!spheres.length) {
720
+ return null;
721
+ }
722
+ const sphere = spheres.length === 1 ? spheres[0] : Cesium.BoundingSphere.fromBoundingSpheres(spheres);
723
+ if (!valid(sphere.center) || !Number.isFinite(sphere.radius)) {
724
+ return null;
725
+ }
726
+ if (spheres.length > 1) {
727
+ const carto = Cesium.Cartographic.fromCartesian(sphere.center);
728
+ if (!carto) {
729
+ return null;
730
+ }
731
+ const height = anchors.reduce((sum, p) => sum + Cesium.Cartographic.fromCartesian(p).height, 0) / anchors.length;
732
+ const position = Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, height);
733
+ sphere.radius += Cesium.Cartesian3.distance(position, sphere.center);
734
+ sphere.center = position;
735
+ }
736
+ return { position: sphere.center, radius: sphere.radius, minDistance };
737
+ }
738
+ appearance(id) {
739
+ var _a, _b, _c, _d, _e, _f;
740
+ const { visual, metadata } = this.records.get(id);
741
+ const data = metadata.entity || { Bruce: { ID: id } };
742
+ let priority;
743
+ let status;
744
+ try {
745
+ priority = Array.isArray(this.settings.iconPriority) ? bruce_models_1.Calculator.GetNumber(this.settings.iconPriority, data) : this.settings.iconPriority;
746
+ status = Array.isArray(this.settings.iconStatus) ? bruce_models_1.Calculator.GetString(this.settings.iconStatus, data) : this.settings.iconStatus;
747
+ }
748
+ catch {
749
+ priority = undefined;
750
+ status = undefined;
751
+ }
752
+ const rawImage = GetValue(this.viewer, (_a = visual.billboard) === null || _a === void 0 ? void 0 : _a.image);
753
+ const imageKey = (_b = visual.billboard) === null || _b === void 0 ? void 0 : _b._billboardImgKey;
754
+ const iconUrl = (_c = metadata.iconUrl) !== null && _c !== void 0 ? _c : (typeof rawImage === "string" ? rawImage : undefined);
755
+ // Original URLs retain status tokens after the point renderer converts an image into a data URI.
756
+ const inferenceUrl = (_e = (_d = metadata.iconUrl) !== null && _d !== void 0 ? _d : imageKey) !== null && _e !== void 0 ? _e : iconUrl;
757
+ const colorGraphic = visual.point || visual.polygon || visual.polyline || visual.corridor || visual.ellipse || visual.model;
758
+ const color = colorGraphic ? cesium_entity_styler_1.CesiumEntityStyler.GetDefaultColor(this.viewer, colorGraphic) : null;
759
+ let colorStatus;
760
+ if (color && color.alpha > 0 && color.red > 0.5 && color.blue < 0.4) {
761
+ colorStatus = color.green > 0.4 ? "warning" : "red";
762
+ }
763
+ const bgColor = color === null || color === void 0 ? void 0 : color.toCssColorString();
764
+ return {
765
+ priority: (0, point_clustering_core_1.GetClusterIconPriority)({
766
+ iconPriority: Number.isFinite(priority) ? priority : metadata.iconPriority,
767
+ iconStatus: (_f = status !== null && status !== void 0 ? status : metadata.iconStatus) !== null && _f !== void 0 ? _f : colorStatus, icon: metadata.icon, iconUrl: inferenceUrl
768
+ }),
769
+ iconUrl, bgColor,
770
+ txtColor: color && color.red * 0.299 + color.green * 0.587 + color.blue * 0.114 > 0.5 ? "black" : "white"
564
771
  };
565
- /**
566
- * Generate circle with label in center.
567
- * @param count
568
- * @returns
569
- */
570
- const getCanvas = (count) => {
772
+ }
773
+ doUpdate() {
774
+ var _a;
775
+ if (this.disposed) {
776
+ return;
777
+ }
778
+ const previous = new Set(this.currClusteredEntities);
779
+ const { clusters } = this.getClusters();
780
+ const live = new Set(clusters.map(cluster => cluster.center.id));
781
+ for (const id of this.clusterEntities.keys()) {
782
+ if (!live.has(id)) {
783
+ this.removeClusterEntity(id);
784
+ }
785
+ }
786
+ const changed = new Set();
787
+ for (const id of new Set([...previous, ...this.registeredEntityIds])) {
788
+ if (this.currClusteredEntities.has(id)) {
789
+ continue;
790
+ }
791
+ const rego = this.register.GetRego({ entityId: id, menuItemId: this.menuItemId });
792
+ if ((rego === null || rego === void 0 ? void 0 : rego.overrideShow) === false) {
793
+ rego.overrideShow = null;
794
+ changed.add(id);
795
+ }
796
+ }
797
+ for (const cluster of clusters) {
798
+ const clusterId = cluster.center.id;
799
+ let representative = cluster.center;
800
+ let appearance = this.appearance(representative.id);
801
+ if (this.settings.representative !== "nearest") {
802
+ for (const point of cluster.points) {
803
+ const candidate = this.appearance(point.id);
804
+ const distance = Cesium.Cartesian3.distanceSquared(point.position, this.viewer.camera.positionWC || this.viewer.camera.position);
805
+ const representativeDistance = Cesium.Cartesian3.distanceSquared(representative.position, this.viewer.camera.positionWC || this.viewer.camera.position);
806
+ if (candidate.priority > appearance.priority || (candidate.priority === appearance.priority &&
807
+ (distance < representativeDistance || (distance === representativeDistance &&
808
+ point.id.localeCompare(representative.id) < 0)))) {
809
+ representative = point;
810
+ appearance = candidate;
811
+ }
812
+ }
813
+ }
814
+ const position = new Cesium.Cartesian3();
815
+ for (const point of cluster.points) {
816
+ Cesium.Cartesian3.add(position, point.position, position);
817
+ }
818
+ Cesium.Cartesian3.divideByScalar(position, cluster.points.length, position);
819
+ const count = cluster.points.length;
571
820
  const params = {
572
- size: getScale(count),
573
- txtColor: this.pointColorTxt,
574
- bgColor: this.pointColorBg,
821
+ size: PointClusterSize(count),
575
822
  text: String(count),
576
- iconUrl: this.iconUrl
823
+ iconUrl: appearance.iconUrl,
824
+ bgColor: appearance.bgColor,
825
+ txtColor: appearance.txtColor
577
826
  };
578
- return getClusterImage(params);
579
- };
580
- // 5: iterate over clusters and add/update them as Cesium entities.
581
- for (let cluster of clusters) {
582
- const clusterId = cluster.center.id;
583
- let clusterEntity = this.clusterEntities.get(clusterId);
584
- const centroid = this.calculateCentroid(cluster.points);
585
- const count = cluster.points.length;
586
- if (clusterEntity) {
587
- clusterEntity.position = new Cesium.ConstantPositionProperty(Cesium.Cartesian3.fromDegrees(centroid.lon, centroid.lat, 150));
588
- clusterEntity.billboard.image = new Cesium.CallbackProperty(() => {
589
- return getCanvas(count);
590
- }, false),
591
- clusterEntity.billboard.width = new Cesium.ConstantProperty(getScale(cluster.points.length));
592
- clusterEntity.billboard.height = new Cesium.ConstantProperty(getScale(cluster.points.length));
593
- }
594
- else {
595
- clusterEntity = this.viewer.entities.add({
596
- position: Cesium.Cartesian3.fromDegrees(centroid.lon, centroid.lat, 150),
827
+ const image = new Cesium.CallbackProperty(() => getClusterImage(params), false);
828
+ let entity = this.clusterEntities.get(clusterId);
829
+ if (!entity) {
830
+ entity = this.viewer.entities.add({
831
+ position,
597
832
  billboard: {
833
+ image,
834
+ color: Cesium.Color.WHITE,
835
+ width: params.size,
836
+ height: params.size,
598
837
  heightReference: Cesium.HeightReference.NONE,
599
- image: new Cesium.CallbackProperty(() => {
600
- return getCanvas(count);
601
- }, false),
602
- width: getScale(count),
603
- height: getScale(count),
604
838
  verticalOrigin: Cesium.VerticalOrigin.CENTER,
605
839
  horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
606
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
607
- scaleByDistance: new Cesium.NearFarScalar(1.5e2, 0.5, 3.0e7, 0.1),
840
+ disableDepthTestDistance: Infinity,
841
+ scaleByDistance: new Cesium.NearFarScalar(1.5e2, 0.5, 3.0e7, 0.1)
608
842
  }
609
843
  });
610
- this.clusterEntities.set(clusterId, clusterEntity);
844
+ this.clusterEntities.set(clusterId, entity);
845
+ if (this.settings.selectOnClick !== false)
846
+ this.picker.targets.set(entity, () => this.selectCluster(clusterId));
611
847
  }
612
- for (let i = 0; i < cluster.points.length; i++) {
613
- const entityId = cluster.points[i].id;
848
+ else {
849
+ entity.position = new Cesium.ConstantPositionProperty(position);
850
+ entity.billboard.image = image;
851
+ entity.billboard.width = new Cesium.ConstantProperty(params.size);
852
+ entity.billboard.height = new Cesium.ConstantProperty(params.size);
853
+ }
854
+ const old = this.clusterViews.get(clusterId);
855
+ this.clusterViews.set(clusterId, {
856
+ entity,
857
+ entityIds: cluster.points.map(point => point.id),
858
+ representativeId: representative.id,
859
+ selected: (_a = old === null || old === void 0 ? void 0 : old.selected) !== null && _a !== void 0 ? _a : false
860
+ });
861
+ _loadClusterImage(params).then(() => {
862
+ if (!this.disposed && !this.viewer.isDestroyed()) {
863
+ this.viewer.scene.requestRender();
864
+ }
865
+ });
866
+ for (const point of cluster.points) {
614
867
  const rego = this.register.GetRego({
615
- entityId: entityId,
868
+ entityId: point.id,
616
869
  menuItemId: this.menuItemId
617
870
  });
618
- if (rego && rego.overrideShow != false) {
871
+ if (rego && rego.overrideShow !== false) {
619
872
  rego.overrideShow = false;
620
- entitiesToUpdate.push(entityId);
873
+ changed.add(point.id);
621
874
  }
622
875
  }
623
876
  }
624
- this.queueForceUpdate(entitiesToUpdate);
625
- // 6: Iterate over existing cluster entities and remove those that are no longer clustered.
626
- for (let [clusterId, clusterEntity] of Array.from(this.clusterEntities)) {
627
- if (!clusters.find(x => x.center.id == clusterId)) {
628
- this.viewer.entities.remove(clusterEntity);
629
- this.clusterEntities.delete(clusterId);
630
- }
877
+ if (changed.size) {
878
+ this.register.ForceUpdate({ entityIds: Array.from(changed) });
631
879
  }
632
- this.viewer.scene.requestRender();
880
+ this.refreshSelection();
633
881
  }
634
- getClusterSpacing(distanceFromCluster) {
635
- // Distance thresholds in meters from the cluster.
636
- const distanceThresholds = [3000, 4000, 5300, 6000, 7000];
637
- // Distance increments in degrees.
638
- const distanceIncrements = [0.001, 0.002, 0.01, 0.03, 0.09];
639
- let index = 0;
640
- if (distanceFromCluster) {
641
- for (let i = 0; i < distanceThresholds.length; i++) {
642
- if (distanceFromCluster > distanceThresholds[i]) {
643
- index = i;
644
- }
645
- }
646
- }
647
- return distanceIncrements[index];
882
+ getClusterSpacing(distance) {
883
+ return distance > 7000 ? 0.09 : distance > 6000 ? 0.03 : distance > 5300 ? 0.01 : distance > 4000 ? 0.002 : 0.001;
648
884
  }
649
885
  getClusters() {
886
+ const previous = new Set(this.currClusteredEntities);
650
887
  this.currClusteredEntities.clear();
651
- let clusters = [];
652
- let processedPoints = new Set();
653
- const cameraPosition = this.viewer.camera.position;
654
- // We won't cluster selected entities.
655
- const selectedIds = this.register.GetSelected();
656
- // Only cluster if total registered points exceeds minTotalCount.
657
- let validClusters = [];
658
- if (this.registeredEntityIds.size >= this.minTotalCount) {
659
- const MIN_CAMERA_DISTANCE = this.minDistance;
660
- const processQuad = (quad) => {
661
- // Distance to quad.
662
- // TODO: Needs improvement.
663
- const distanceToQuad = quad.GetDistanceToQuad(cameraPosition);
664
- // Skip quads that are too close.
665
- if (distanceToQuad >= MIN_CAMERA_DISTANCE) {
666
- for (let point of quad.points) {
667
- // Skip points already processed in previous clusters.
668
- if (selectedIds.includes(point.id) || processedPoints.has(point.id)) {
669
- continue;
670
- }
671
- // Skip points closer than MIN_CAMERA_DISTANCE meters to the camera.
672
- const cartesian3 = Cesium.Cartesian3.fromDegrees(point.lon, point.lat);
673
- const distanceFromCluster = Cesium.Cartesian3.distance(cartesian3, cameraPosition);
674
- if (distanceFromCluster <= MIN_CAMERA_DISTANCE) {
675
- continue;
676
- }
677
- let found = [];
678
- let nearbyPoints = quad.Query(new Circle(point.lon, point.lat, this.getClusterSpacing(distanceFromCluster)), found);
679
- nearbyPoints = nearbyPoints.filter(x => !processedPoints.has(x.id) && !selectedIds.includes(x.id));
680
- if (nearbyPoints.length > 1) {
681
- let cluster = { center: point, points: [] };
682
- for (let nearby of nearbyPoints) {
683
- if (!cluster.points.includes(nearby)) {
684
- cluster.points.push(nearby);
685
- processedPoints.add(nearby.id);
686
- this.currClusteredEntities.add(nearby.id);
687
- }
688
- }
689
- clusters.push(cluster);
690
- }
691
- }
692
- }
693
- if (quad.divided) {
694
- processQuad(quad.northwest);
695
- processQuad(quad.northeast);
696
- processQuad(quad.southwest);
697
- processQuad(quad.southeast);
698
- }
699
- };
700
- processQuad(this.quadTree);
701
- // Filter out clusters with only one point.
702
- validClusters = clusters.filter((cluster) => cluster.points.length > 1);
703
- }
704
- // Get the entity IDs that are no longer clustered
705
- const noLongerClustered = new Set(Array.from(this.prevClusteredEntities).filter((id) => !this.currClusteredEntities.has(id)));
706
- // Update the previous clustered entities ref.
707
- this.prevClusteredEntities = new Set(this.currClusteredEntities);
708
- return { clusters: validClusters, noLongerClustered };
709
- }
710
- /**
711
- * Removes Cesium cluster entity.
712
- * @param clusterId
713
- */
714
- removeClusterEntity(clusterId) {
715
- const clusterEntity = this.clusterEntities.get(clusterId);
716
- if (clusterEntity) {
717
- this.viewer.entities.remove(clusterEntity);
718
- this.clusterEntities.delete(clusterId);
719
- }
720
- }
721
- convertCartesianToCartographic(cartesian3) {
722
- const carto = Cesium.Cartographic.fromCartesian(cartesian3);
723
- const lon = Cesium.Math.toDegrees(carto.longitude);
724
- const lat = Cesium.Math.toDegrees(carto.latitude);
725
- const id = lon + ',' + lat;
726
- return new Point(lon, lat, id);
727
- }
728
- /**
729
- * Adds new entity to the clustering logic.
730
- * @param id Bruce entity ID.
731
- * @param cartesian3 entity's position.
732
- */
733
- addPoint(id, cartesian3) {
734
- let point = this.convertCartesianToCartographic(cartesian3);
735
- point.id = id;
736
- return this.quadTree.Insert(point);
737
- }
738
- /**
739
- * Adds entity to clustering logic.
740
- * Will return false if entity could not be clustered and therefor should not be hidden.
741
- * @param id
742
- * @param entity
743
- * @param queueUpdate
744
- * @returns
745
- */
746
- AddEntity(id, entity, queueUpdate = true) {
747
- if (this.disposed) {
748
- return false;
749
- }
750
- if (!entity) {
751
- return false;
752
- }
753
- if (entity.polygon || entity.polyline) {
754
- return false;
755
- }
756
- const pos3d = GetValue(this.viewer, entity.position);
757
- if (!(pos3d === null || pos3d === void 0 ? void 0 : pos3d.x)) {
758
- return false;
759
- }
760
- if (!this.pointColorBg && entity.point) {
761
- const pointColorBg = GetValue(this.viewer, entity.point.color);
762
- if (pointColorBg) {
763
- let cColor = null;
764
- if (pointColorBg instanceof Object) {
765
- cColor = new Cesium.Color(pointColorBg.red, pointColorBg.green, pointColorBg.blue, pointColorBg.alpha);
766
- }
767
- else if (typeof pointColorBg === "string") {
768
- cColor = Cesium.Color.fromCssColorString(pointColorBg);
769
- }
770
- // Determine if text color should instead be black based on background.
771
- // cColor contains r,g,b,a values where r,g,b are in the range [0,1].
772
- if (cColor) {
773
- const brightness = (cColor.red * 299 + cColor.green * 587 + cColor.blue * 114) / 1000;
774
- if (brightness > 0.5) {
775
- this.pointColorTxt = "black";
776
- }
777
- else {
778
- this.pointColorTxt = "white";
779
- }
780
- this.pointColorBg = cColor.toCssColorString();
781
- }
888
+ this.quadTree = new Quad(new Rectangle(0, 0, 360, 180), 30);
889
+ const points = [];
890
+ const distances = new Map();
891
+ const camera = this.viewer.camera.positionWC || this.viewer.camera.position;
892
+ const isolated = this.register.GetIsIsolatedAny();
893
+ for (const [id, record] of this.records) {
894
+ const state = this.register.GetState({ entityId: id, menuItemId: this.menuItemId });
895
+ if (state.hidden || (isolated && !state.isolated)) {
896
+ continue;
782
897
  }
898
+ const geometry = this.resolveGeometry(record.visual);
899
+ if (!geometry) {
900
+ continue;
901
+ }
902
+ const { position, radius, minDistance } = geometry;
903
+ const carto = Cesium.Cartographic.fromCartesian(position);
904
+ if (!carto) {
905
+ continue;
906
+ }
907
+ const point = new Point(Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude), id);
908
+ point.position = position;
909
+ point.radius = radius;
910
+ point.minDistance = minDistance;
911
+ if (!this.quadTree.Insert(point)) {
912
+ continue;
913
+ }
914
+ points.push(point);
915
+ distances.set(id, Cesium.Cartesian3.distance(position, camera));
783
916
  }
784
- if (!this.iconUrl && entity.billboard) {
785
- const iconUrl = GetValue(this.viewer, entity.billboard.image);
786
- if (typeof iconUrl == "string") {
787
- this.iconUrl = iconUrl;
917
+ const clusters = points.length < this.minTotalCount ? [] :
918
+ (0, point_clustering_core_1.ClusterItems)({
919
+ items: points.sort((a, b) => distances.get(a.id) - distances.get(b.id) || a.id.localeCompare(b.id)),
920
+ id: point => point.id,
921
+ eligible: point => Math.max(0, distances.get(point.id) - point.radius) > point.minDistance,
922
+ neighbours: point => this.quadTree.Query(new Circle(point.lon, point.lat, this.getClusterSpacing(distances.get(point.id))), [])
923
+ }).map(cluster => ({ center: cluster.representative, points: cluster.members }));
924
+ for (const cluster of clusters) {
925
+ for (const point of cluster.points) {
926
+ this.currClusteredEntities.add(point.id);
788
927
  }
789
928
  }
790
- if (this.registeredEntityIds.has(id)) {
791
- this.RemoveEntity(id, false);
929
+ return {
930
+ clusters,
931
+ noLongerClustered: new Set(Array.from(previous).filter(id => !this.currClusteredEntities.has(id)))
932
+ };
933
+ }
934
+ removeClusterEntity(id) {
935
+ const entity = this.clusterEntities.get(id);
936
+ if (!entity)
937
+ return;
938
+ this.picker.targets.delete(entity);
939
+ this.viewer.entities.remove(entity);
940
+ this.clusterEntities.delete(id);
941
+ this.clusterViews.delete(id);
942
+ }
943
+ /*
944
+ * Metadata is additive; existing three-argument calls keep their original signature.
945
+ */
946
+ AddEntity(id, visual, queueUpdate = true, metadata = {}) {
947
+ if (this.disposed || !visual) {
948
+ return false;
792
949
  }
793
- const added = this.addPoint(id, pos3d);
794
- if (!added) {
950
+ if (!this.resolveGeometry(visual)) {
951
+ this.RemoveEntity(id, queueUpdate);
795
952
  return false;
796
953
  }
954
+ this.records.set(id, { visual, metadata });
797
955
  this.registeredEntityIds.add(id);
798
956
  if (queueUpdate) {
799
- this.updateQueue.Call();
957
+ this.Update();
800
958
  }
801
959
  return true;
802
960
  }
803
- /**
804
- * Removes entity from clustering logic.
805
- * Warning: This will not reveal the entity, suppressShow will remain true.
806
- * This is made with the assumption that the entity is being removed from viewer.
807
- * @param id
808
- * @param queueUpdate
809
- * @returns
810
- */
811
961
  RemoveEntity(id, queueUpdate = false) {
812
962
  if (this.disposed) {
813
963
  return;
814
964
  }
815
- let pointToRemove = this.quadTree.Find(id);
816
- if (pointToRemove) {
817
- this.quadTree.Remove(pointToRemove);
818
- if (queueUpdate) {
819
- this.updateQueue.Call();
820
- }
821
- }
965
+ this.records.delete(id);
822
966
  this.registeredEntityIds.delete(id);
967
+ if (queueUpdate) {
968
+ this.Update();
969
+ }
823
970
  }
824
971
  }
825
972
  exports.PointClustering = PointClustering;