cesium-alpha-earth 1.0.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.
@@ -0,0 +1,1000 @@
1
+ import {
2
+ BoundingRectangle,
3
+ Cartesian2,
4
+ Cartesian3,
5
+ defaultValue,
6
+ defined,
7
+ EllipsoidalOccluder,
8
+ Event,
9
+ Matrix4,
10
+ Billboard,
11
+ BillboardCollection,
12
+ Label,
13
+ LabelCollection,
14
+ PointPrimitive,
15
+ PointPrimitiveCollection,
16
+ SceneMode,
17
+ } from "cesium";
18
+ import KDBush from "kdbush";
19
+
20
+ /**
21
+ * Defines how screen space objects (billboards, points, labels) are clustered.
22
+ *
23
+ * @param {Object} [options] An object with the following properties:
24
+ * @param {Boolean} [options.enabled=false] Whether or not to enable clustering.
25
+ * @param {Number} [options.pixelRange=80] The pixel range to extend the screen space bounding box.
26
+ * @param {Number} [options.minimumClusterSize=2] The minimum number of screen space objects that can be clustered.
27
+ * @param {Boolean} [options.clusterBillboards=true] Whether or not to cluster the billboards of an entity.
28
+ * @param {Boolean} [options.clusterLabels=true] Whether or not to cluster the labels of an entity.
29
+ * @param {Boolean} [options.clusterPoints=true] Whether or not to cluster the points of an entity.
30
+ * @param {Boolean} [options.show=true] Determines if the entities in the cluster will be shown.
31
+ *
32
+ * @alias PrimitiveCluster
33
+ * @constructor
34
+ *
35
+ * @demo {@link https://sandcastle.cesium.com/index.html?src=Clustering.html|Cesium Sandcastle Clustering Demo}
36
+ */
37
+ function PrimitiveCluster(options) {
38
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
39
+
40
+ this._enabled = defaultValue(options.enabled, false);
41
+ this._pixelRange = defaultValue(options.pixelRange, 80);
42
+ this._minimumClusterSize = defaultValue(options.minimumClusterSize, 2);
43
+ this._clusterBillboards = defaultValue(options.clusterBillboards, true);
44
+ this._clusterLabels = defaultValue(options.clusterLabels, true);
45
+ this._clusterPoints = defaultValue(options.clusterPoints, true);
46
+
47
+ this._labelCollection = undefined;
48
+ this._billboardCollection = undefined;
49
+ this._pointCollection = undefined;
50
+
51
+ this._clusterBillboardCollection = undefined;
52
+ this._clusterLabelCollection = undefined;
53
+ this._clusterPointCollection = undefined;
54
+
55
+ this._collectionIndicesByEntity = {};
56
+
57
+ this._unusedLabelIndices = [];
58
+ this._unusedBillboardIndices = [];
59
+ this._unusedPointIndices = [];
60
+
61
+ this._previousClusters = [];
62
+ this._previousHeight = undefined;
63
+
64
+ this._enabledDirty = false;
65
+ this._clusterDirty = false;
66
+
67
+ this._cluster = undefined;
68
+ this._removeEventListener = undefined;
69
+
70
+ this._listenerFun=undefined
71
+
72
+ this._clusterEvent = new Event();
73
+
74
+ /**
75
+ * Determines if entities in this collection will be shown.
76
+ *
77
+ * @type {Boolean}
78
+ * @default true
79
+ */
80
+ this.show = defaultValue(options.show, true);
81
+ }
82
+
83
+ function getX(point) {
84
+ return point.coord.x;
85
+ }
86
+
87
+ function getY(point) {
88
+ return point.coord.y;
89
+ }
90
+
91
+ function expandBoundingBox(bbox, pixelRange) {
92
+ bbox.x -= pixelRange;
93
+ bbox.y -= pixelRange;
94
+ bbox.width += pixelRange * 2.0;
95
+ bbox.height += pixelRange * 2.0;
96
+ }
97
+
98
+ const labelBoundingBoxScratch = new BoundingRectangle();
99
+
100
+ function getBoundingBox(item, coord, pixelRange, entityCluster, result) {
101
+ if (defined(item._labelCollection) && entityCluster._clusterLabels) {
102
+ result = Label.getScreenSpaceBoundingBox(item, coord, result);
103
+ } else if (
104
+ defined(item._billboardCollection) &&
105
+ entityCluster._clusterBillboards
106
+ ) {
107
+ result = Billboard.getScreenSpaceBoundingBox(item, coord, result);
108
+ } else if (
109
+ defined(item._pointPrimitiveCollection) &&
110
+ entityCluster._clusterPoints
111
+ ) {
112
+ result = PointPrimitive.getScreenSpaceBoundingBox(item, coord, result);
113
+ }
114
+
115
+ expandBoundingBox(result, pixelRange);
116
+
117
+ if (
118
+ entityCluster._clusterLabels &&
119
+ !defined(item._labelCollection) &&
120
+ defined(item.id) &&
121
+ hasLabelIndex(entityCluster, item.id.id) &&
122
+ defined(item.id._label)
123
+ ) {
124
+ const labelIndex =
125
+ entityCluster._collectionIndicesByEntity[item.id.id].labelIndex;
126
+ const label = entityCluster._labelCollection.get(labelIndex);
127
+ const labelBBox = Label.getScreenSpaceBoundingBox(
128
+ label,
129
+ coord,
130
+ labelBoundingBoxScratch
131
+ );
132
+ expandBoundingBox(labelBBox, pixelRange);
133
+ result = BoundingRectangle.union(result, labelBBox, result);
134
+ }
135
+
136
+ return result;
137
+ }
138
+
139
+ function addNonClusteredItem(item, entityCluster) {
140
+ item.clusterShow = true;
141
+
142
+ if (
143
+ !defined(item._labelCollection) &&
144
+ defined(item.id) &&
145
+ hasLabelIndex(entityCluster, item.id.id) &&
146
+ defined(item.id._label)
147
+ ) {
148
+ const labelIndex =
149
+ entityCluster._collectionIndicesByEntity[item.id.id].labelIndex;
150
+ const label = entityCluster._labelCollection.get(labelIndex);
151
+ label.clusterShow = true;
152
+ }
153
+ }
154
+
155
+ function addCluster(position, numPoints, ids, entityCluster) {
156
+ const cluster = {
157
+ billboard: entityCluster._clusterBillboardCollection.add(),
158
+ label: entityCluster._clusterLabelCollection.add(),
159
+ point: entityCluster._clusterPointCollection.add(),
160
+ };
161
+
162
+ cluster.billboard.show = false;
163
+ cluster.point.show = false;
164
+ cluster.label.show = true;
165
+ cluster.label.text = numPoints.toLocaleString();
166
+ cluster.label.id = ids;
167
+ cluster.billboard.position = cluster.label.position = cluster.point.position = position;
168
+
169
+ entityCluster._clusterEvent.raiseEvent(ids, cluster);
170
+ }
171
+
172
+ function hasLabelIndex(entityCluster, entityId) {
173
+ return (
174
+ defined(entityCluster) &&
175
+ defined(entityCluster._collectionIndicesByEntity[entityId]) &&
176
+ defined(entityCluster._collectionIndicesByEntity[entityId].labelIndex)
177
+ );
178
+ }
179
+
180
+ function getScreenSpacePositions(
181
+ collection,
182
+ points,
183
+ scene,
184
+ occluder,
185
+ entityCluster
186
+ ) {
187
+ if (!defined(collection)) {
188
+ return;
189
+ }
190
+
191
+ const length = collection.length;
192
+ for (let i = 0; i < length; ++i) {
193
+ const item = collection.get(i);
194
+ item.clusterShow = false;
195
+
196
+ if (
197
+ !item.show ||
198
+ (entityCluster._scene.mode === SceneMode.SCENE3D &&
199
+ !occluder.isPointVisible(item.position))
200
+ ) {
201
+ continue;
202
+ }
203
+
204
+ // const canClusterLabels =
205
+ // entityCluster._clusterLabels && defined(item._labelCollection);
206
+ // const canClusterBillboards =
207
+ // entityCluster._clusterBillboards && defined(item.id._billboard);
208
+ // const canClusterPoints =
209
+ // entityCluster._clusterPoints && defined(item.id._point);
210
+ // if (canClusterLabels && (canClusterPoints || canClusterBillboards)) {
211
+ // continue;
212
+ // }
213
+
214
+ const coord = item.computeScreenSpacePosition(scene);
215
+ if (!defined(coord)) {
216
+ continue;
217
+ }
218
+
219
+ points.push({
220
+ index: i,
221
+ collection: collection,
222
+ clustered: false,
223
+ coord: coord,
224
+ });
225
+ }
226
+ }
227
+
228
+ const pointBoundinRectangleScratch = new BoundingRectangle();
229
+ const totalBoundingRectangleScratch = new BoundingRectangle();
230
+ const neighborBoundingRectangleScratch = new BoundingRectangle();
231
+
232
+ function createDeclutterCallback(entityCluster) {
233
+ return function (amount) {
234
+ if ((defined(amount) && amount < 0.05) || !entityCluster.enabled) {
235
+ return;
236
+ }
237
+
238
+ const scene = entityCluster._scene;
239
+
240
+ const labelCollection = entityCluster._labelCollection;
241
+ const billboardCollection = entityCluster._billboardCollection;
242
+ const pointCollection = entityCluster._pointCollection;
243
+
244
+ if (
245
+ (!defined(labelCollection) &&
246
+ !defined(billboardCollection) &&
247
+ !defined(pointCollection)) ||
248
+ (!entityCluster._clusterBillboards &&
249
+ !entityCluster._clusterLabels &&
250
+ !entityCluster._clusterPoints)
251
+ ) {
252
+ return;
253
+ }
254
+
255
+ let clusteredLabelCollection = entityCluster._clusterLabelCollection;
256
+ let clusteredBillboardCollection =
257
+ entityCluster._clusterBillboardCollection;
258
+ let clusteredPointCollection = entityCluster._clusterPointCollection;
259
+
260
+ if (defined(clusteredLabelCollection)) {
261
+ clusteredLabelCollection.removeAll();
262
+ } else {
263
+ clusteredLabelCollection = entityCluster._clusterLabelCollection = new LabelCollection(
264
+ {
265
+ scene: scene,
266
+ }
267
+ );
268
+ }
269
+
270
+ if (defined(clusteredBillboardCollection)) {
271
+ clusteredBillboardCollection.removeAll();
272
+ } else {
273
+ clusteredBillboardCollection = entityCluster._clusterBillboardCollection = new BillboardCollection(
274
+ {
275
+ scene: scene,
276
+ }
277
+ );
278
+ }
279
+
280
+ if (defined(clusteredPointCollection)) {
281
+ clusteredPointCollection.removeAll();
282
+ } else {
283
+ clusteredPointCollection = entityCluster._clusterPointCollection = new PointPrimitiveCollection();
284
+ }
285
+
286
+ const pixelRange = entityCluster._pixelRange;
287
+ const minimumClusterSize = entityCluster._minimumClusterSize;
288
+
289
+ const clusters = entityCluster._previousClusters;
290
+ const newClusters = [];
291
+
292
+ const previousHeight = entityCluster._previousHeight;
293
+ const currentHeight = scene.camera.positionCartographic.height;
294
+
295
+ const ellipsoid = scene.mapProjection.ellipsoid;
296
+ const cameraPosition = scene.camera.positionWC;
297
+ const occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition);
298
+
299
+ const points = [];
300
+ if (entityCluster._clusterLabels) {
301
+ getScreenSpacePositions(
302
+ labelCollection,
303
+ points,
304
+ scene,
305
+ occluder,
306
+ entityCluster
307
+ );
308
+ }
309
+ if (entityCluster._clusterBillboards) {
310
+ getScreenSpacePositions(
311
+ billboardCollection,
312
+ points,
313
+ scene,
314
+ occluder,
315
+ entityCluster
316
+ );
317
+ }
318
+ if (entityCluster._clusterPoints) {
319
+ getScreenSpacePositions(
320
+ pointCollection,
321
+ points,
322
+ scene,
323
+ occluder,
324
+ entityCluster
325
+ );
326
+ }
327
+
328
+ let i;
329
+ let j;
330
+ let length;
331
+ let bbox;
332
+ let neighbors;
333
+ let neighborLength;
334
+ let neighborIndex;
335
+ let neighborPoint;
336
+ let ids;
337
+ let numPoints;
338
+
339
+ let collection;
340
+ let collectionIndex;
341
+
342
+ const index = new KDBush(points, getX, getY, 64, Int32Array);
343
+
344
+ if (currentHeight < previousHeight) {
345
+ length = clusters.length;
346
+ for (i = 0; i < length; ++i) {
347
+ const cluster = clusters[i];
348
+
349
+ if (!occluder.isPointVisible(cluster.position)) {
350
+ continue;
351
+ }
352
+
353
+ const coord = Billboard._computeScreenSpacePosition(
354
+ Matrix4.IDENTITY,
355
+ cluster.position,
356
+ Cartesian3.ZERO,
357
+ Cartesian2.ZERO,
358
+ scene
359
+ );
360
+ if (!defined(coord)) {
361
+ continue;
362
+ }
363
+
364
+ const factor = 1.0 - currentHeight / previousHeight;
365
+ let width = (cluster.width = cluster.width * factor);
366
+ let height = (cluster.height = cluster.height * factor);
367
+
368
+ width = Math.max(width, cluster.minimumWidth);
369
+ height = Math.max(height, cluster.minimumHeight);
370
+
371
+ const minX = coord.x - width * 0.5;
372
+ const minY = coord.y - height * 0.5;
373
+ const maxX = coord.x + width;
374
+ const maxY = coord.y + height;
375
+
376
+ neighbors = index.range(minX, minY, maxX, maxY);
377
+ neighborLength = neighbors.length;
378
+ numPoints = 0;
379
+ ids = [];
380
+
381
+ for (j = 0; j < neighborLength; ++j) {
382
+ neighborIndex = neighbors[j];
383
+ neighborPoint = points[neighborIndex];
384
+ if (!neighborPoint.clustered) {
385
+ ++numPoints;
386
+
387
+ collection = neighborPoint.collection;
388
+ collectionIndex = neighborPoint.index;
389
+ ids.push(collection.get(collectionIndex).id);
390
+ }
391
+ }
392
+
393
+ if (numPoints >= minimumClusterSize) {
394
+ addCluster(cluster.position, numPoints, ids, entityCluster);
395
+ newClusters.push(cluster);
396
+
397
+ for (j = 0; j < neighborLength; ++j) {
398
+ points[neighbors[j]].clustered = true;
399
+ }
400
+ }
401
+ }
402
+ }
403
+
404
+ length = points.length;
405
+ for (i = 0; i < length; ++i) {
406
+ const point = points[i];
407
+ if (point.clustered) {
408
+ continue;
409
+ }
410
+
411
+ point.clustered = true;
412
+
413
+ collection = point.collection;
414
+ collectionIndex = point.index;
415
+
416
+ const item = collection.get(collectionIndex);
417
+ bbox = getBoundingBox(
418
+ item,
419
+ point.coord,
420
+ pixelRange,
421
+ entityCluster,
422
+ pointBoundinRectangleScratch
423
+ );
424
+ const totalBBox = BoundingRectangle.clone(
425
+ bbox,
426
+ totalBoundingRectangleScratch
427
+ );
428
+
429
+ neighbors = index.range(
430
+ bbox.x,
431
+ bbox.y,
432
+ bbox.x + bbox.width,
433
+ bbox.y + bbox.height
434
+ );
435
+ neighborLength = neighbors.length;
436
+
437
+ const clusterPosition = Cartesian3.clone(item.position);
438
+ numPoints = 1;
439
+ ids = [item.id];
440
+
441
+ for (j = 0; j < neighborLength; ++j) {
442
+ neighborIndex = neighbors[j];
443
+ neighborPoint = points[neighborIndex];
444
+ if (!neighborPoint.clustered) {
445
+ const neighborItem = neighborPoint.collection.get(
446
+ neighborPoint.index
447
+ );
448
+ const neighborBBox = getBoundingBox(
449
+ neighborItem,
450
+ neighborPoint.coord,
451
+ pixelRange,
452
+ entityCluster,
453
+ neighborBoundingRectangleScratch
454
+ );
455
+
456
+ Cartesian3.add(
457
+ neighborItem.position,
458
+ clusterPosition,
459
+ clusterPosition
460
+ );
461
+
462
+ BoundingRectangle.union(totalBBox, neighborBBox, totalBBox);
463
+ ++numPoints;
464
+
465
+ ids.push(neighborItem.id);
466
+ }
467
+ }
468
+
469
+ if (numPoints >= minimumClusterSize) {
470
+ const position = Cartesian3.multiplyByScalar(
471
+ clusterPosition,
472
+ 1.0 / numPoints,
473
+ clusterPosition
474
+ );
475
+ addCluster(position, numPoints, ids, entityCluster);
476
+ newClusters.push({
477
+ position: position,
478
+ width: totalBBox.width,
479
+ height: totalBBox.height,
480
+ minimumWidth: bbox.width,
481
+ minimumHeight: bbox.height,
482
+ });
483
+
484
+ for (j = 0; j < neighborLength; ++j) {
485
+ points[neighbors[j]].clustered = true;
486
+ }
487
+ } else {
488
+ addNonClusteredItem(item, entityCluster);
489
+ }
490
+ }
491
+
492
+ if (clusteredLabelCollection.length === 0) {
493
+ clusteredLabelCollection.destroy();
494
+ entityCluster._clusterLabelCollection = undefined;
495
+ }
496
+
497
+ if (clusteredBillboardCollection.length === 0) {
498
+ clusteredBillboardCollection.destroy();
499
+ entityCluster._clusterBillboardCollection = undefined;
500
+ }
501
+
502
+ if (clusteredPointCollection.length === 0) {
503
+ clusteredPointCollection.destroy();
504
+ entityCluster._clusterPointCollection = undefined;
505
+ }
506
+
507
+ entityCluster._previousClusters = newClusters;
508
+ entityCluster._previousHeight = currentHeight;
509
+ };
510
+ }
511
+
512
+ PrimitiveCluster.prototype._initialize = function (scene) {
513
+ this._scene = scene;
514
+
515
+ const cluster = createDeclutterCallback(this);
516
+ this._cluster = cluster;
517
+ this._removeEventListener = scene.camera.changed.addEventListener(cluster);
518
+ };
519
+
520
+ Object.defineProperties(PrimitiveCluster.prototype, {
521
+ /**
522
+ * Gets or sets whether clustering is enabled.
523
+ * @memberof PrimitiveCluster.prototype
524
+ * @type {Boolean}
525
+ */
526
+ enabled: {
527
+ get: function () {
528
+ return this._enabled;
529
+ },
530
+ set: function (value) {
531
+ this._enabledDirty = value !== this._enabled;
532
+ this._enabled = value;
533
+ },
534
+ },
535
+ /**
536
+ * Gets or sets the pixel range to extend the screen space bounding box.
537
+ * @memberof PrimitiveCluster.prototype
538
+ * @type {Number}
539
+ */
540
+ pixelRange: {
541
+ get: function () {
542
+ return this._pixelRange;
543
+ },
544
+ set: function (value) {
545
+ this._clusterDirty = this._clusterDirty || value !== this._pixelRange;
546
+ this._pixelRange = value;
547
+ },
548
+ },
549
+ /**
550
+ * Gets or sets the minimum number of screen space objects that can be clustered.
551
+ * @memberof PrimitiveCluster.prototype
552
+ * @type {Number}
553
+ */
554
+ minimumClusterSize: {
555
+ get: function () {
556
+ return this._minimumClusterSize;
557
+ },
558
+ set: function (value) {
559
+ this._clusterDirty =
560
+ this._clusterDirty || value !== this._minimumClusterSize;
561
+ this._minimumClusterSize = value;
562
+ },
563
+ },
564
+ /**
565
+ * Gets the event that will be raised when a new cluster will be displayed. The signature of the event listener is {@link PrimitiveCluster.newClusterCallback}.
566
+ * @memberof PrimitiveCluster.prototype
567
+ * @type {Event<PrimitiveCluster.newClusterCallback>}
568
+ */
569
+ clusterEvent: {
570
+ get: function () {
571
+ return this._clusterEvent;
572
+ },
573
+ },
574
+ /**
575
+ * Gets or sets whether clustering billboard entities is enabled.
576
+ * @memberof PrimitiveCluster.prototype
577
+ * @type {Boolean}
578
+ */
579
+ clusterBillboards: {
580
+ get: function () {
581
+ return this._clusterBillboards;
582
+ },
583
+ set: function (value) {
584
+ this._clusterDirty =
585
+ this._clusterDirty || value !== this._clusterBillboards;
586
+ this._clusterBillboards = value;
587
+ },
588
+ },
589
+ /**
590
+ * Gets or sets whether clustering labels entities is enabled.
591
+ * @memberof PrimitiveCluster.prototype
592
+ * @type {Boolean}
593
+ */
594
+ clusterLabels: {
595
+ get: function () {
596
+ return this._clusterLabels;
597
+ },
598
+ set: function (value) {
599
+ this._clusterDirty = this._clusterDirty || value !== this._clusterLabels;
600
+ this._clusterLabels = value;
601
+ },
602
+ },
603
+ /**
604
+ * Gets or sets whether clustering point entities is enabled.
605
+ * @memberof PrimitiveCluster.prototype
606
+ * @type {Boolean}
607
+ */
608
+ clusterPoints: {
609
+ get: function () {
610
+ return this._clusterPoints;
611
+ },
612
+ set: function (value) {
613
+ this._clusterDirty = this._clusterDirty || value !== this._clusterPoints;
614
+ this._clusterPoints = value;
615
+ },
616
+ },
617
+ });
618
+
619
+ function createGetEntity(
620
+ collectionProperty,
621
+ CollectionConstructor,
622
+ unusedIndicesProperty,
623
+ entityIndexProperty
624
+ ) {
625
+ return function (entity) {
626
+ let collection = this[collectionProperty];
627
+
628
+ if (!defined(this._collectionIndicesByEntity)) {
629
+ this._collectionIndicesByEntity = {};
630
+ }
631
+
632
+ let entityIndices = this._collectionIndicesByEntity[entity.id];
633
+
634
+ if (!defined(entityIndices)) {
635
+ entityIndices = this._collectionIndicesByEntity[entity.id] = {
636
+ billboardIndex: undefined,
637
+ labelIndex: undefined,
638
+ pointIndex: undefined,
639
+ };
640
+ }
641
+
642
+ if (defined(collection) && defined(entityIndices[entityIndexProperty])) {
643
+ return collection.get(entityIndices[entityIndexProperty]);
644
+ }
645
+
646
+ if (!defined(collection)) {
647
+ collection = this[collectionProperty] = new CollectionConstructor({
648
+ scene: this._scene,
649
+ });
650
+ }
651
+
652
+ let index;
653
+ let entityItem;
654
+
655
+ const unusedIndices = this[unusedIndicesProperty];
656
+ if (unusedIndices.length > 0) {
657
+ index = unusedIndices.pop();
658
+ entityItem = collection.get(index);
659
+ } else {
660
+ entityItem = collection.add();
661
+ index = collection.length - 1;
662
+ }
663
+
664
+ entityIndices[entityIndexProperty] = index;
665
+
666
+ const that = this;
667
+ Promise.resolve().then(function () {
668
+ that._clusterDirty = true;
669
+ });
670
+
671
+ return entityItem;
672
+ };
673
+ }
674
+
675
+ function removeEntityIndicesIfUnused(entityCluster, entityId) {
676
+ const indices = entityCluster._collectionIndicesByEntity[entityId];
677
+
678
+ if (
679
+ !defined(indices.billboardIndex) &&
680
+ !defined(indices.labelIndex) &&
681
+ !defined(indices.pointIndex)
682
+ ) {
683
+ delete entityCluster._collectionIndicesByEntity[entityId];
684
+ }
685
+ }
686
+
687
+ /**
688
+ * Returns a new {@link Label}.
689
+ * @param {Entity} entity The entity that will use the returned {@link Label} for visualization.
690
+ * @returns {Label} The label that will be used to visualize an entity.
691
+ *
692
+ * @private
693
+ */
694
+ PrimitiveCluster.prototype.getLabel = createGetEntity(
695
+ "_labelCollection",
696
+ LabelCollection,
697
+ "_unusedLabelIndices",
698
+ "labelIndex"
699
+ );
700
+
701
+ /**
702
+ * Removes the {@link Label} associated with an entity so it can be reused by another entity.
703
+ * @param {Entity} entity The entity that will uses the returned {@link Label} for visualization.
704
+ *
705
+ * @private
706
+ */
707
+ PrimitiveCluster.prototype.removeLabel = function (entity) {
708
+ const entityIndices =
709
+ this._collectionIndicesByEntity &&
710
+ this._collectionIndicesByEntity[entity.id];
711
+ if (
712
+ !defined(this._labelCollection) ||
713
+ !defined(entityIndices) ||
714
+ !defined(entityIndices.labelIndex)
715
+ ) {
716
+ return;
717
+ }
718
+
719
+ const index = entityIndices.labelIndex;
720
+ entityIndices.labelIndex = undefined;
721
+ removeEntityIndicesIfUnused(this, entity.id);
722
+
723
+ const label = this._labelCollection.get(index);
724
+ label.show = false;
725
+ label.text = "";
726
+ label.id = undefined;
727
+
728
+ this._unusedLabelIndices.push(index);
729
+
730
+ this._clusterDirty = true;
731
+ };
732
+
733
+ /**
734
+ * Returns a new {@link Billboard}.
735
+ * @param {Entity} entity The entity that will use the returned {@link Billboard} for visualization.
736
+ * @returns {Billboard} The label that will be used to visualize an entity.
737
+ *
738
+ * @private
739
+ */
740
+ PrimitiveCluster.prototype.getBillboard = createGetEntity(
741
+ "_billboardCollection",
742
+ BillboardCollection,
743
+ "_unusedBillboardIndices",
744
+ "billboardIndex"
745
+ );
746
+
747
+ /**
748
+ * Removes the {@link Billboard} associated with an entity so it can be reused by another entity.
749
+ * @param {Entity} entity The entity that will uses the returned {@link Billboard} for visualization.
750
+ *
751
+ * @private
752
+ */
753
+ PrimitiveCluster.prototype.removeBillboard = function (entity) {
754
+ const entityIndices =
755
+ this._collectionIndicesByEntity &&
756
+ this._collectionIndicesByEntity[entity.id];
757
+ if (
758
+ !defined(this._billboardCollection) ||
759
+ !defined(entityIndices) ||
760
+ !defined(entityIndices.billboardIndex)
761
+ ) {
762
+ return;
763
+ }
764
+
765
+ const index = entityIndices.billboardIndex;
766
+ entityIndices.billboardIndex = undefined;
767
+ removeEntityIndicesIfUnused(this, entity.id);
768
+
769
+ const billboard = this._billboardCollection.get(index);
770
+ billboard.id = undefined;
771
+ billboard.show = false;
772
+ billboard.image = undefined;
773
+
774
+ this._unusedBillboardIndices.push(index);
775
+
776
+ this._clusterDirty = true;
777
+ };
778
+
779
+ /**
780
+ * Returns a new {@link Point}.
781
+ * @param {Entity} entity The entity that will use the returned {@link Point} for visualization.
782
+ * @returns {Point} The label that will be used to visualize an entity.
783
+ *
784
+ * @private
785
+ */
786
+ PrimitiveCluster.prototype.getPoint = createGetEntity(
787
+ "_pointCollection",
788
+ PointPrimitiveCollection,
789
+ "_unusedPointIndices",
790
+ "pointIndex"
791
+ );
792
+
793
+ /**
794
+ * Removes the {@link Point} associated with an entity so it can be reused by another entity.
795
+ * @param {Entity} entity The entity that will uses the returned {@link Point} for visualization.
796
+ *
797
+ * @private
798
+ */
799
+ PrimitiveCluster.prototype.removePoint = function (entity) {
800
+ const entityIndices =
801
+ this._collectionIndicesByEntity &&
802
+ this._collectionIndicesByEntity[entity.id];
803
+ if (
804
+ !defined(this._pointCollection) ||
805
+ !defined(entityIndices) ||
806
+ !defined(entityIndices.pointIndex)
807
+ ) {
808
+ return;
809
+ }
810
+
811
+ const index = entityIndices.pointIndex;
812
+ entityIndices.pointIndex = undefined;
813
+ removeEntityIndicesIfUnused(this, entity.id);
814
+
815
+ const point = this._pointCollection.get(index);
816
+ point.show = false;
817
+ point.id = undefined;
818
+
819
+ this._unusedPointIndices.push(index);
820
+
821
+ this._clusterDirty = true;
822
+ };
823
+
824
+ function disableCollectionClustering(collection) {
825
+ if (!defined(collection)) {
826
+ return;
827
+ }
828
+
829
+ const length = collection.length;
830
+ for (let i = 0; i < length; ++i) {
831
+ collection.get(i).clusterShow = true;
832
+ }
833
+ }
834
+
835
+ function updateEnable(entityCluster) {
836
+ if (entityCluster.enabled) {
837
+ return;
838
+ }
839
+
840
+ if (defined(entityCluster._clusterLabelCollection)) {
841
+ entityCluster._clusterLabelCollection.destroy();
842
+ }
843
+ if (defined(entityCluster._clusterBillboardCollection)) {
844
+ entityCluster._clusterBillboardCollection.destroy();
845
+ }
846
+ if (defined(entityCluster._clusterPointCollection)) {
847
+ entityCluster._clusterPointCollection.destroy();
848
+ }
849
+
850
+ entityCluster._clusterLabelCollection = undefined;
851
+ entityCluster._clusterBillboardCollection = undefined;
852
+ entityCluster._clusterPointCollection = undefined;
853
+
854
+ disableCollectionClustering(entityCluster._labelCollection);
855
+ disableCollectionClustering(entityCluster._billboardCollection);
856
+ disableCollectionClustering(entityCluster._pointCollection);
857
+ }
858
+
859
+ /**
860
+ * Gets the draw commands for the clustered billboards/points/labels if enabled, otherwise,
861
+ * queues the draw commands for billboards/points/labels created for entities.
862
+ * @private
863
+ */
864
+ PrimitiveCluster.prototype.update = function (frameState) {
865
+ if (!this.show) {
866
+ return;
867
+ }
868
+
869
+ // If clustering is enabled before the label collection is updated,
870
+ // the glyphs haven't been created so the screen space bounding boxes
871
+ // are incorrect.
872
+ let commandList;
873
+ if (
874
+ defined(this._labelCollection) &&
875
+ this._labelCollection.length > 0 &&
876
+ this._labelCollection.get(0)._glyphs.length === 0
877
+ ) {
878
+ commandList = frameState.commandList;
879
+ frameState.commandList = [];
880
+ this._labelCollection.update(frameState);
881
+ frameState.commandList = commandList;
882
+ }
883
+
884
+ // If clustering is enabled before the billboard collection is updated,
885
+ // the images haven't been added to the image atlas so the screen space bounding boxes
886
+ // are incorrect.
887
+ if (
888
+ defined(this._billboardCollection) &&
889
+ this._billboardCollection.length > 0 &&
890
+ !defined(this._billboardCollection.get(0).width)
891
+ ) {
892
+ commandList = frameState.commandList;
893
+ frameState.commandList = [];
894
+ this._billboardCollection.update(frameState);
895
+ frameState.commandList = commandList;
896
+ }
897
+
898
+ if (this._enabledDirty) {
899
+ this._enabledDirty = false;
900
+ updateEnable(this);
901
+ this._clusterDirty = true;
902
+ }
903
+
904
+ if (this._clusterDirty) {
905
+ this._clusterDirty = false;
906
+ this._cluster();
907
+ }
908
+
909
+ if (defined(this._clusterLabelCollection)) {
910
+ this._clusterLabelCollection.update(frameState);
911
+ }
912
+ if (defined(this._clusterBillboardCollection)) {
913
+ this._clusterBillboardCollection.update(frameState);
914
+ }
915
+ if (defined(this._clusterPointCollection)) {
916
+ this._clusterPointCollection.update(frameState);
917
+ }
918
+
919
+ if (defined(this._labelCollection)) {
920
+ this._labelCollection.update(frameState);
921
+ }
922
+ if (defined(this._billboardCollection)) {
923
+ this._billboardCollection.update(frameState);
924
+ }
925
+ if (defined(this._pointCollection)) {
926
+ this._pointCollection.update(frameState);
927
+ }
928
+ };
929
+
930
+ /**
931
+ * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
932
+ * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
933
+ * <p>
934
+ * Unlike other objects that use WebGL resources, this object can be reused. For example, if a data source is removed
935
+ * from a data source collection and added to another.
936
+ * </p>
937
+ */
938
+ PrimitiveCluster.prototype.destroy = function () {
939
+ this._labelCollection =
940
+ this._labelCollection && this._labelCollection.removeAll();
941
+ this._billboardCollection =
942
+ this._billboardCollection && this._billboardCollection.removeAll();
943
+ this._pointCollection =
944
+ this._pointCollection && this._pointCollection.removeAll();
945
+ this._clusterLabelCollection =
946
+ this._clusterLabelCollection && this._clusterLabelCollection.removeAll();
947
+ this._clusterBillboardCollection =
948
+ this._clusterBillboardCollection &&
949
+ this._clusterBillboardCollection.removeAll();
950
+ this._clusterPointCollection =
951
+ this._clusterPointCollection && this._clusterPointCollection.removeAll();
952
+
953
+ if (defined(this._removeEventListener)) {
954
+ this._removeEventListener();
955
+ this._removeEventListener = undefined;
956
+ }
957
+
958
+ this._labelCollection = undefined;
959
+ this._billboardCollection = undefined;
960
+ this._pointCollection = undefined;
961
+
962
+ this._clusterBillboardCollection = undefined;
963
+ this._clusterLabelCollection = undefined;
964
+ this._clusterPointCollection = undefined;
965
+
966
+ this._collectionIndicesByEntity = undefined;
967
+
968
+ this._unusedLabelIndices = [];
969
+ this._unusedBillboardIndices = [];
970
+ this._unusedPointIndices = [];
971
+
972
+ this._previousClusters = [];
973
+ this._previousHeight = undefined;
974
+
975
+ this._enabledDirty = false;
976
+ this._pixelRangeDirty = false;
977
+ this._minimumClusterSizeDirty = false;
978
+
979
+ return undefined;
980
+ };
981
+
982
+ /**
983
+ * A event listener function used to style clusters.
984
+ * @callback PrimitiveCluster.newClusterCallback
985
+ *
986
+ * @param {Entity[]} clusteredEntities An array of the entities contained in the cluster.
987
+ * @param {Object} cluster An object containing the Billboard, Label, and Point
988
+ * primitives that represent this cluster of entities.
989
+ * @param {Billboard} cluster.billboard
990
+ * @param {Label} cluster.label
991
+ * @param {PointPrimitive} cluster.point
992
+ *
993
+ * @example
994
+ * // The default cluster values.
995
+ * dataSource.clustering.clusterEvent.addEventListener(function(entities, cluster) {
996
+ * cluster.label.show = true;
997
+ * cluster.label.text = entities.length.toLocaleString();
998
+ * });
999
+ */
1000
+ export default PrimitiveCluster;