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.
- package/dist/bruce-cesium.es5.js +2911 -626
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +2910 -623
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +8 -2
- package/dist/lib/bruce-cesium.js.map +1 -1
- package/dist/lib/rendering/entity-gatherer.js +249 -16
- package/dist/lib/rendering/entity-gatherer.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label-group.js +482 -0
- package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -0
- package/dist/lib/rendering/render-managers/common/entity-label.js +1213 -155
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/historic-utils.js +64 -7
- package/dist/lib/rendering/render-managers/common/historic-utils.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/point-clustering-core.js +85 -0
- package/dist/lib/rendering/render-managers/common/point-clustering-core.js.map +1 -0
- package/dist/lib/rendering/render-managers/common/point-clustering.js +566 -419
- package/dist/lib/rendering/render-managers/common/point-clustering.js.map +1 -1
- package/dist/lib/rendering/render-managers/entities/entities-ids-render-manager.js +1 -1
- 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 +2 -2
- package/dist/lib/rendering/render-managers/entities/entities-render-manager.js.map +1 -1
- package/dist/lib/rendering/visuals-register.js +273 -24
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +4 -1
- package/dist/types/rendering/entity-gatherer.d.ts +27 -4
- package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +104 -0
- package/dist/types/rendering/render-managers/common/entity-label.d.ts +138 -1
- package/dist/types/rendering/render-managers/common/historic-utils.d.ts +12 -4
- package/dist/types/rendering/render-managers/common/point-clustering-core.d.ts +17 -0
- package/dist/types/rendering/render-managers/common/point-clustering.d.ts +37 -72
- package/dist/types/rendering/visuals-register.d.ts +43 -2
- 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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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 (
|
|
377
|
+
if (event.button !== 0) {
|
|
382
378
|
return;
|
|
383
379
|
}
|
|
384
|
-
|
|
385
|
-
if
|
|
386
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
448
|
-
|
|
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
|
-
|
|
456
|
-
if (this.
|
|
457
|
-
this.
|
|
458
|
-
this.listenUpdateRemoval = null;
|
|
491
|
+
Update() {
|
|
492
|
+
if (!this.disposed) {
|
|
493
|
+
this.updateQueue.Call();
|
|
459
494
|
}
|
|
460
495
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
|
|
521
|
+
restore.push(id);
|
|
487
522
|
}
|
|
488
523
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
508
|
-
const latAvg = latSum / points.length;
|
|
509
|
-
return new Point(lonAvg, latAvg, "");
|
|
542
|
+
this.viewer.scene.requestRender();
|
|
510
543
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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 (
|
|
539
|
-
|
|
540
|
-
entitiesToUpdate.push(id);
|
|
671
|
+
if (valid(actual)) {
|
|
672
|
+
resolved.push(actual);
|
|
541
673
|
}
|
|
542
674
|
}
|
|
543
675
|
}
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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 (
|
|
555
|
-
|
|
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 (
|
|
558
|
-
|
|
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 (
|
|
561
|
-
|
|
694
|
+
if (visual.ellipsoid) {
|
|
695
|
+
sphere.radius = Math.max(sphere.radius, Cesium.Cartesian3.maximumComponent(value(visual.ellipsoid.radii) || Cesium.Cartesian3.ZERO));
|
|
562
696
|
}
|
|
563
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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:
|
|
573
|
-
txtColor: this.pointColorTxt,
|
|
574
|
-
bgColor: this.pointColorBg,
|
|
821
|
+
size: PointClusterSize(count),
|
|
575
822
|
text: String(count),
|
|
576
|
-
iconUrl:
|
|
823
|
+
iconUrl: appearance.iconUrl,
|
|
824
|
+
bgColor: appearance.bgColor,
|
|
825
|
+
txtColor: appearance.txtColor
|
|
577
826
|
};
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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:
|
|
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,
|
|
844
|
+
this.clusterEntities.set(clusterId, entity);
|
|
845
|
+
if (this.settings.selectOnClick !== false)
|
|
846
|
+
this.picker.targets.set(entity, () => this.selectCluster(clusterId));
|
|
611
847
|
}
|
|
612
|
-
|
|
613
|
-
|
|
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:
|
|
868
|
+
entityId: point.id,
|
|
616
869
|
menuItemId: this.menuItemId
|
|
617
870
|
});
|
|
618
|
-
if (rego && rego.overrideShow
|
|
871
|
+
if (rego && rego.overrideShow !== false) {
|
|
619
872
|
rego.overrideShow = false;
|
|
620
|
-
|
|
873
|
+
changed.add(point.id);
|
|
621
874
|
}
|
|
622
875
|
}
|
|
623
876
|
}
|
|
624
|
-
|
|
625
|
-
|
|
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.
|
|
880
|
+
this.refreshSelection();
|
|
633
881
|
}
|
|
634
|
-
getClusterSpacing(
|
|
635
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
791
|
-
|
|
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
|
-
|
|
794
|
-
|
|
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.
|
|
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
|
-
|
|
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;
|