| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022 |
- import BoundingRectangle from "../Core/BoundingRectangle.js";
- import Cartesian2 from "../Core/Cartesian2.js";
- import Cartesian3 from "../Core/Cartesian3.js";
- import Frozen from "../Core/Frozen.js";
- import defined from "../Core/defined.js";
- import EllipsoidalOccluder from "../Core/EllipsoidalOccluder.js";
- import Event from "../Core/Event.js";
- import Matrix4 from "../Core/Matrix4.js";
- import Billboard from "../Scene/Billboard.js";
- import BillboardCollection from "../Scene/BillboardCollection.js";
- import Label from "../Scene/Label.js";
- import LabelCollection from "../Scene/LabelCollection.js";
- import PointPrimitive from "../Scene/PointPrimitive.js";
- import PointPrimitiveCollection from "../Scene/PointPrimitiveCollection.js";
- import SceneMode from "../Scene/SceneMode.js";
- import KDBush from "kdbush";
-
- /**
- * Defines how screen space objects (billboards, points, labels) are clustered.
- *
- * @param {object} [options] An object with the following properties:
- * @param {boolean} [options.enabled=false] Whether or not to enable clustering.
- * @param {number} [options.pixelRange=80] The pixel range to extend the screen space bounding box.
- * @param {number} [options.minimumClusterSize=2] The minimum number of screen space objects that can be clustered.
- * @param {boolean} [options.clusterBillboards=true] Whether or not to cluster the billboards of an entity.
- * @param {boolean} [options.clusterLabels=true] Whether or not to cluster the labels of an entity.
- * @param {boolean} [options.clusterPoints=true] Whether or not to cluster the points of an entity.
- * @param {boolean} [options.show=true] Determines if the entities in the cluster will be shown.
- *
- * @alias EntityCluster
- * @constructor
- *
- * @demo {@link https://sandcastle.cesium.com/index.html?id=clustering|Cesium Sandcastle Clustering Demo}
- */
- function EntityCluster(options) {
- options = options ?? Frozen.EMPTY_OBJECT;
-
- this._enabled = options.enabled ?? false;
- this._pixelRange = options.pixelRange ?? 80;
- this._minimumClusterSize = options.minimumClusterSize ?? 2;
- this._clusterBillboards = options.clusterBillboards ?? true;
- this._clusterLabels = options.clusterLabels ?? true;
- this._clusterPoints = options.clusterPoints ?? true;
-
- this._labelCollection = undefined;
- this._billboardCollection = undefined;
- this._pointCollection = undefined;
-
- this._clusterBillboardCollection = undefined;
- this._clusterLabelCollection = undefined;
- this._clusterPointCollection = undefined;
-
- this._collectionIndicesByEntity = {};
-
- this._unusedLabelIndices = [];
- this._unusedBillboardIndices = [];
- this._unusedPointIndices = [];
-
- this._previousClusters = [];
- this._previousHeight = undefined;
-
- this._enabledDirty = false;
- this._clusterDirty = false;
-
- this._cluster = undefined;
- this._removeEventListener = undefined;
-
- this._clusterEvent = new Event();
-
- /**
- * Determines if entities in this collection will be shown.
- *
- * @type {boolean}
- * @default true
- */
- this.show = options.show ?? true;
- }
-
- function expandBoundingBox(bbox, pixelRange) {
- bbox.x -= pixelRange;
- bbox.y -= pixelRange;
- bbox.width += pixelRange * 2.0;
- bbox.height += pixelRange * 2.0;
- }
-
- const labelBoundingBoxScratch = new BoundingRectangle();
-
- function getBoundingBox(item, coord, pixelRange, entityCluster, result) {
- if (defined(item._labelCollection) && entityCluster._clusterLabels) {
- result = Label.getScreenSpaceBoundingBox(item, coord, result);
- } else if (
- defined(item._billboardCollection) &&
- entityCluster._clusterBillboards
- ) {
- result = Billboard.getScreenSpaceBoundingBox(item, coord, result);
- } else if (
- defined(item._pointPrimitiveCollection) &&
- entityCluster._clusterPoints
- ) {
- result = PointPrimitive.getScreenSpaceBoundingBox(item, coord, result);
- }
-
- expandBoundingBox(result, pixelRange);
-
- if (
- entityCluster._clusterLabels &&
- !defined(item._labelCollection) &&
- defined(item.id) &&
- hasLabelIndex(entityCluster, item.id.id) &&
- defined(item.id._label)
- ) {
- const labelIndex =
- entityCluster._collectionIndicesByEntity[item.id.id].labelIndex;
- const label = entityCluster._labelCollection.get(labelIndex);
- const labelBBox = Label.getScreenSpaceBoundingBox(
- label,
- coord,
- labelBoundingBoxScratch,
- );
- expandBoundingBox(labelBBox, pixelRange);
- result = BoundingRectangle.union(result, labelBBox, result);
- }
-
- return result;
- }
-
- function addNonClusteredItem(item, entityCluster) {
- item.clusterShow = true;
-
- if (
- !defined(item._labelCollection) &&
- defined(item.id) &&
- hasLabelIndex(entityCluster, item.id.id) &&
- defined(item.id._label)
- ) {
- const labelIndex =
- entityCluster._collectionIndicesByEntity[item.id.id].labelIndex;
- const label = entityCluster._labelCollection.get(labelIndex);
- label.clusterShow = true;
- }
- }
-
- function addCluster(position, numPoints, ids, entityCluster) {
- const cluster = {
- billboard: entityCluster._clusterBillboardCollection.add(),
- label: entityCluster._clusterLabelCollection.add(),
- point: entityCluster._clusterPointCollection.add(),
- };
-
- cluster.billboard.show = false;
- cluster.point.show = false;
- cluster.label.show = true;
- cluster.label.text = numPoints.toLocaleString();
- cluster.label.id = ids;
- cluster.billboard.position =
- cluster.label.position =
- cluster.point.position =
- position;
-
- entityCluster._clusterEvent.raiseEvent(ids, cluster);
- }
-
- function hasLabelIndex(entityCluster, entityId) {
- return (
- defined(entityCluster) &&
- defined(entityCluster._collectionIndicesByEntity[entityId]) &&
- defined(entityCluster._collectionIndicesByEntity[entityId].labelIndex)
- );
- }
-
- function getScreenSpacePositions(
- collection,
- points,
- scene,
- occluder,
- entityCluster,
- ) {
- if (!defined(collection)) {
- return;
- }
-
- const length = collection.length;
- for (let i = 0; i < length; ++i) {
- const item = collection.get(i);
- item.clusterShow = false;
-
- if (
- !item.show ||
- (entityCluster._scene.mode === SceneMode.SCENE3D &&
- !occluder.isPointVisible(item.position))
- ) {
- continue;
- }
-
- const canClusterLabels =
- entityCluster._clusterLabels && defined(item._labelCollection);
- const canClusterBillboards =
- entityCluster._clusterBillboards && defined(item.id._billboard);
- const canClusterPoints =
- entityCluster._clusterPoints && defined(item.id._point);
- if (canClusterLabels && (canClusterPoints || canClusterBillboards)) {
- continue;
- }
-
- const coord = item.computeScreenSpacePosition(scene);
- if (!defined(coord)) {
- continue;
- }
-
- points.push({
- index: i,
- collection: collection,
- clustered: false,
- coord: coord,
- });
- }
- }
-
- const pointBoundinRectangleScratch = new BoundingRectangle();
- const totalBoundingRectangleScratch = new BoundingRectangle();
- const neighborBoundingRectangleScratch = new BoundingRectangle();
-
- function createDeclutterCallback(entityCluster) {
- return function (amount) {
- if ((defined(amount) && amount < 0.05) || !entityCluster.enabled) {
- return;
- }
-
- const scene = entityCluster._scene;
-
- const labelCollection = entityCluster._labelCollection;
- const billboardCollection = entityCluster._billboardCollection;
- const pointCollection = entityCluster._pointCollection;
-
- if (
- (!defined(labelCollection) &&
- !defined(billboardCollection) &&
- !defined(pointCollection)) ||
- (!entityCluster._clusterBillboards &&
- !entityCluster._clusterLabels &&
- !entityCluster._clusterPoints)
- ) {
- return;
- }
-
- let clusteredLabelCollection = entityCluster._clusterLabelCollection;
- let clusteredBillboardCollection =
- entityCluster._clusterBillboardCollection;
- let clusteredPointCollection = entityCluster._clusterPointCollection;
-
- if (defined(clusteredLabelCollection)) {
- clusteredLabelCollection.removeAll();
- } else {
- clusteredLabelCollection = entityCluster._clusterLabelCollection =
- new LabelCollection({
- scene: scene,
- });
- }
-
- if (defined(clusteredBillboardCollection)) {
- clusteredBillboardCollection.removeAll();
- } else {
- clusteredBillboardCollection = entityCluster._clusterBillboardCollection =
- new BillboardCollection({
- scene: scene,
- });
- }
-
- if (defined(clusteredPointCollection)) {
- clusteredPointCollection.removeAll();
- } else {
- clusteredPointCollection = entityCluster._clusterPointCollection =
- new PointPrimitiveCollection();
- }
-
- const pixelRange = entityCluster._pixelRange;
- const minimumClusterSize = entityCluster._minimumClusterSize;
-
- const clusters = entityCluster._previousClusters;
- const newClusters = [];
-
- const previousHeight = entityCluster._previousHeight;
- const currentHeight = scene.camera.positionCartographic.height;
-
- const ellipsoid = scene.ellipsoid;
- const cameraPosition = scene.camera.positionWC;
- const occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition);
-
- const points = [];
- if (entityCluster._clusterLabels) {
- getScreenSpacePositions(
- labelCollection,
- points,
- scene,
- occluder,
- entityCluster,
- );
- }
- if (entityCluster._clusterBillboards) {
- getScreenSpacePositions(
- billboardCollection,
- points,
- scene,
- occluder,
- entityCluster,
- );
- }
- if (entityCluster._clusterPoints) {
- getScreenSpacePositions(
- pointCollection,
- points,
- scene,
- occluder,
- entityCluster,
- );
- }
-
- let i;
- let j;
- let length;
- let bbox;
- let neighbors;
- let neighborLength;
- let neighborIndex;
- let neighborPoint;
- let ids;
- let numPoints;
-
- let collection;
- let collectionIndex;
-
- if (points.length > 0) {
- const index = new KDBush(points.length, 64, Float64Array);
- for (let p = 0; p < points.length; ++p) {
- index.add(points[p].coord.x, points[p].coord.y);
- }
- index.finish();
-
- if (currentHeight < previousHeight) {
- length = clusters.length;
- for (i = 0; i < length; ++i) {
- const cluster = clusters[i];
-
- if (!occluder.isPointVisible(cluster.position)) {
- continue;
- }
-
- const coord = Billboard._computeScreenSpacePosition(
- Matrix4.IDENTITY,
- cluster.position,
- Cartesian3.ZERO,
- Cartesian2.ZERO,
- scene,
- );
- if (!defined(coord)) {
- continue;
- }
-
- const factor = 1.0 - currentHeight / previousHeight;
- let width = (cluster.width = cluster.width * factor);
- let height = (cluster.height = cluster.height * factor);
-
- width = Math.max(width, cluster.minimumWidth);
- height = Math.max(height, cluster.minimumHeight);
-
- const minX = coord.x - width * 0.5;
- const minY = coord.y - height * 0.5;
- const maxX = coord.x + width;
- const maxY = coord.y + height;
-
- neighbors = index.range(minX, minY, maxX, maxY);
- neighborLength = neighbors.length;
- numPoints = 0;
- ids = [];
-
- for (j = 0; j < neighborLength; ++j) {
- neighborIndex = neighbors[j];
- neighborPoint = points[neighborIndex];
- if (!neighborPoint.clustered) {
- ++numPoints;
-
- collection = neighborPoint.collection;
- collectionIndex = neighborPoint.index;
- ids.push(collection.get(collectionIndex).id);
- }
- }
-
- if (numPoints >= minimumClusterSize) {
- addCluster(cluster.position, numPoints, ids, entityCluster);
- newClusters.push(cluster);
-
- for (j = 0; j < neighborLength; ++j) {
- points[neighbors[j]].clustered = true;
- }
- }
- }
- }
-
- length = points.length;
- for (i = 0; i < length; ++i) {
- const point = points[i];
- if (point.clustered) {
- continue;
- }
-
- point.clustered = true;
-
- collection = point.collection;
- collectionIndex = point.index;
-
- const item = collection.get(collectionIndex);
- bbox = getBoundingBox(
- item,
- point.coord,
- pixelRange,
- entityCluster,
- pointBoundinRectangleScratch,
- );
- const totalBBox = BoundingRectangle.clone(
- bbox,
- totalBoundingRectangleScratch,
- );
-
- neighbors = index.range(
- bbox.x,
- bbox.y,
- bbox.x + bbox.width,
- bbox.y + bbox.height,
- );
- neighborLength = neighbors.length;
-
- const clusterPosition = Cartesian3.clone(item.position);
- numPoints = 1;
- ids = [item.id];
-
- for (j = 0; j < neighborLength; ++j) {
- neighborIndex = neighbors[j];
- neighborPoint = points[neighborIndex];
- if (!neighborPoint.clustered) {
- const neighborItem = neighborPoint.collection.get(
- neighborPoint.index,
- );
- const neighborBBox = getBoundingBox(
- neighborItem,
- neighborPoint.coord,
- pixelRange,
- entityCluster,
- neighborBoundingRectangleScratch,
- );
-
- Cartesian3.add(
- neighborItem.position,
- clusterPosition,
- clusterPosition,
- );
-
- BoundingRectangle.union(totalBBox, neighborBBox, totalBBox);
- ++numPoints;
-
- ids.push(neighborItem.id);
- }
- }
-
- if (numPoints >= minimumClusterSize) {
- const position = Cartesian3.multiplyByScalar(
- clusterPosition,
- 1.0 / numPoints,
- clusterPosition,
- );
- addCluster(position, numPoints, ids, entityCluster);
- newClusters.push({
- position: position,
- width: totalBBox.width,
- height: totalBBox.height,
- minimumWidth: bbox.width,
- minimumHeight: bbox.height,
- });
-
- for (j = 0; j < neighborLength; ++j) {
- points[neighbors[j]].clustered = true;
- }
- } else {
- addNonClusteredItem(item, entityCluster);
- }
- }
- }
-
- if (clusteredLabelCollection.length === 0) {
- clusteredLabelCollection.destroy();
- entityCluster._clusterLabelCollection = undefined;
- }
-
- if (clusteredBillboardCollection.length === 0) {
- clusteredBillboardCollection.destroy();
- entityCluster._clusterBillboardCollection = undefined;
- }
-
- if (clusteredPointCollection.length === 0) {
- clusteredPointCollection.destroy();
- entityCluster._clusterPointCollection = undefined;
- }
-
- entityCluster._previousClusters = newClusters;
- entityCluster._previousHeight = currentHeight;
- };
- }
-
- EntityCluster.prototype._initialize = function (scene) {
- this._scene = scene;
-
- const cluster = createDeclutterCallback(this);
- this._cluster = cluster;
- this._removeEventListener = scene.camera.changed.addEventListener(cluster);
- };
-
- Object.defineProperties(EntityCluster.prototype, {
- /**
- * Gets or sets whether clustering is enabled.
- * @memberof EntityCluster.prototype
- * @type {boolean}
- */
- enabled: {
- get: function () {
- return this._enabled;
- },
- set: function (value) {
- this._enabledDirty = value !== this._enabled;
- this._enabled = value;
- },
- },
- /**
- * Gets or sets the pixel range to extend the screen space bounding box.
- * @memberof EntityCluster.prototype
- * @type {number}
- */
- pixelRange: {
- get: function () {
- return this._pixelRange;
- },
- set: function (value) {
- this._clusterDirty = this._clusterDirty || value !== this._pixelRange;
- this._pixelRange = value;
- },
- },
- /**
- * Gets or sets the minimum number of screen space objects that can be clustered.
- * @memberof EntityCluster.prototype
- * @type {number}
- */
- minimumClusterSize: {
- get: function () {
- return this._minimumClusterSize;
- },
- set: function (value) {
- this._clusterDirty =
- this._clusterDirty || value !== this._minimumClusterSize;
- this._minimumClusterSize = value;
- },
- },
- /**
- * Gets the event that will be raised when a new cluster will be displayed. The signature of the event listener is {@link EntityCluster.newClusterCallback}.
- * @memberof EntityCluster.prototype
- * @type {Event<EntityCluster.newClusterCallback>}
- */
- clusterEvent: {
- get: function () {
- return this._clusterEvent;
- },
- },
- /**
- * Gets or sets whether clustering billboard entities is enabled.
- * @memberof EntityCluster.prototype
- * @type {boolean}
- */
- clusterBillboards: {
- get: function () {
- return this._clusterBillboards;
- },
- set: function (value) {
- this._clusterDirty =
- this._clusterDirty || value !== this._clusterBillboards;
- this._clusterBillboards = value;
- },
- },
- /**
- * Gets or sets whether clustering labels entities is enabled.
- * @memberof EntityCluster.prototype
- * @type {boolean}
- */
- clusterLabels: {
- get: function () {
- return this._clusterLabels;
- },
- set: function (value) {
- this._clusterDirty = this._clusterDirty || value !== this._clusterLabels;
- this._clusterLabels = value;
- },
- },
- /**
- * Gets or sets whether clustering point entities is enabled.
- * @memberof EntityCluster.prototype
- * @type {boolean}
- */
- clusterPoints: {
- get: function () {
- return this._clusterPoints;
- },
- set: function (value) {
- this._clusterDirty = this._clusterDirty || value !== this._clusterPoints;
- this._clusterPoints = value;
- },
- },
-
- /**
- * Returns true when all clustered data has been rendered.
- * @memberof EntityCluster.prototype
- * @type {boolean}
- * @readonly
- * @private
- */
- ready: {
- get: function () {
- return (
- !this._enabledDirty &&
- !this._clusterDirty &&
- (!defined(this._billboardCollection) ||
- this._billboardCollection.ready) &&
- (!defined(this._labelCollection) || this._labelCollection.ready)
- );
- },
- },
- });
-
- function createGetEntity(
- collectionProperty,
- CollectionConstructor,
- unusedIndicesProperty,
- entityIndexProperty,
- ) {
- return function (entity) {
- let collection = this[collectionProperty];
-
- if (!defined(this._collectionIndicesByEntity)) {
- this._collectionIndicesByEntity = {};
- }
-
- let entityIndices = this._collectionIndicesByEntity[entity.id];
-
- if (!defined(entityIndices)) {
- entityIndices = this._collectionIndicesByEntity[entity.id] = {
- billboardIndex: undefined,
- labelIndex: undefined,
- pointIndex: undefined,
- };
- }
-
- if (defined(collection) && defined(entityIndices[entityIndexProperty])) {
- return collection.get(entityIndices[entityIndexProperty]);
- }
-
- if (!defined(collection)) {
- collection = this[collectionProperty] = new CollectionConstructor({
- scene: this._scene,
- });
- }
-
- let index;
- let entityItem;
-
- const unusedIndices = this[unusedIndicesProperty];
- if (unusedIndices.length > 0) {
- index = unusedIndices.shift();
- entityItem = collection.get(index);
- } else {
- entityItem = collection.add();
- index = collection.length - 1;
- }
-
- entityIndices[entityIndexProperty] = index;
-
- const that = this;
- Promise.resolve().then(function () {
- that._clusterDirty = true;
- });
-
- return entityItem;
- };
- }
-
- function removeEntityIndicesIfUnused(entityCluster, entityId) {
- const indices = entityCluster._collectionIndicesByEntity[entityId];
-
- if (
- !defined(indices.billboardIndex) &&
- !defined(indices.labelIndex) &&
- !defined(indices.pointIndex)
- ) {
- delete entityCluster._collectionIndicesByEntity[entityId];
- }
- }
-
- /**
- * Returns a new {@link Label}.
- * @param {Entity} entity The entity that will use the returned {@link Label} for visualization.
- * @returns {Label} The label that will be used to visualize an entity.
- *
- * @private
- */
- EntityCluster.prototype.getLabel = createGetEntity(
- "_labelCollection",
- LabelCollection,
- "_unusedLabelIndices",
- "labelIndex",
- );
-
- /**
- * Removes the {@link Label} associated with an entity so it can be reused by another entity.
- * @param {Entity} entity The entity that will uses the returned {@link Label} for visualization.
- *
- * @private
- */
- EntityCluster.prototype.removeLabel = function (entity) {
- const entityIndices =
- this._collectionIndicesByEntity &&
- this._collectionIndicesByEntity[entity.id];
- if (
- !defined(this._labelCollection) ||
- !defined(entityIndices) ||
- !defined(entityIndices.labelIndex)
- ) {
- return;
- }
-
- const index = entityIndices.labelIndex;
- entityIndices.labelIndex = undefined;
- removeEntityIndicesIfUnused(this, entity.id);
-
- const label = this._labelCollection.get(index);
- label.show = false;
- label.text = "";
- label.id = undefined;
-
- this._unusedLabelIndices.push(index);
-
- this._clusterDirty = true;
- };
-
- /**
- * Returns a new {@link Billboard}.
- * @param {Entity} entity The entity that will use the returned {@link Billboard} for visualization.
- * @returns {Billboard} The label that will be used to visualize an entity.
- *
- * @private
- */
- EntityCluster.prototype.getBillboard = createGetEntity(
- "_billboardCollection",
- BillboardCollection,
- "_unusedBillboardIndices",
- "billboardIndex",
- );
-
- /**
- * Removes the {@link Billboard} associated with an entity so it can be reused by another entity.
- * @param {Entity} entity The entity that will uses the returned {@link Billboard} for visualization.
- *
- * @private
- */
- EntityCluster.prototype.removeBillboard = function (entity) {
- const entityIndices =
- this._collectionIndicesByEntity &&
- this._collectionIndicesByEntity[entity.id];
- if (
- !defined(this._billboardCollection) ||
- !defined(entityIndices) ||
- !defined(entityIndices.billboardIndex)
- ) {
- return;
- }
-
- const index = entityIndices.billboardIndex;
- entityIndices.billboardIndex = undefined;
- removeEntityIndicesIfUnused(this, entity.id);
-
- const billboard = this._billboardCollection.get(index);
- billboard.id = undefined;
- billboard.show = false;
- billboard.image = undefined;
-
- this._unusedBillboardIndices.push(index);
-
- this._clusterDirty = true;
- };
-
- /**
- * Returns a new {@link Point}.
- * @param {Entity} entity The entity that will use the returned {@link Point} for visualization.
- * @returns {Point} The label that will be used to visualize an entity.
- *
- * @private
- */
- EntityCluster.prototype.getPoint = createGetEntity(
- "_pointCollection",
- PointPrimitiveCollection,
- "_unusedPointIndices",
- "pointIndex",
- );
-
- /**
- * Removes the {@link Point} associated with an entity so it can be reused by another entity.
- * @param {Entity} entity The entity that will uses the returned {@link Point} for visualization.
- *
- * @private
- */
- EntityCluster.prototype.removePoint = function (entity) {
- const entityIndices =
- this._collectionIndicesByEntity &&
- this._collectionIndicesByEntity[entity.id];
- if (
- !defined(this._pointCollection) ||
- !defined(entityIndices) ||
- !defined(entityIndices.pointIndex)
- ) {
- return;
- }
-
- const index = entityIndices.pointIndex;
- entityIndices.pointIndex = undefined;
- removeEntityIndicesIfUnused(this, entity.id);
-
- const point = this._pointCollection.get(index);
- point.show = false;
- point.id = undefined;
-
- this._unusedPointIndices.push(index);
-
- this._clusterDirty = true;
- };
-
- function disableCollectionClustering(collection) {
- if (!defined(collection)) {
- return;
- }
-
- const length = collection.length;
- for (let i = 0; i < length; ++i) {
- collection.get(i).clusterShow = true;
- }
- }
-
- function updateEnable(entityCluster) {
- if (entityCluster.enabled) {
- return;
- }
-
- if (defined(entityCluster._clusterLabelCollection)) {
- entityCluster._clusterLabelCollection.destroy();
- }
- if (defined(entityCluster._clusterBillboardCollection)) {
- entityCluster._clusterBillboardCollection.destroy();
- }
- if (defined(entityCluster._clusterPointCollection)) {
- entityCluster._clusterPointCollection.destroy();
- }
-
- entityCluster._clusterLabelCollection = undefined;
- entityCluster._clusterBillboardCollection = undefined;
- entityCluster._clusterPointCollection = undefined;
-
- disableCollectionClustering(entityCluster._labelCollection);
- disableCollectionClustering(entityCluster._billboardCollection);
- disableCollectionClustering(entityCluster._pointCollection);
- }
-
- /**
- * Gets the draw commands for the clustered billboards/points/labels if enabled, otherwise,
- * queues the draw commands for billboards/points/labels created for entities.
- * @private
- */
- EntityCluster.prototype.update = function (frameState) {
- if (!this.show) {
- return;
- }
-
- // If clustering is enabled before the label collection is updated,
- // the glyphs haven't been created so the screen space bounding boxes
- // are incorrect.
- let commandList;
- const labelCollection = this._labelCollection;
- if (
- defined(labelCollection) &&
- labelCollection.length > 0 &&
- !labelCollection.ready
- ) {
- commandList = frameState.commandList;
- frameState.commandList = [];
- labelCollection.update(frameState);
- frameState.commandList = commandList;
- }
-
- // If clustering is enabled before the billboard collections are updated,
- // the images haven't been added to the image atlas so the screen space bounding boxes
- // are incorrect.
- const billboardCollection = this._billboardCollection;
- if (
- defined(billboardCollection) &&
- billboardCollection.length > 0 &&
- !billboardCollection.ready
- ) {
- commandList = frameState.commandList;
- frameState.commandList = [];
- billboardCollection.update(frameState);
- frameState.commandList = commandList;
- }
-
- if (this._enabledDirty) {
- this._enabledDirty = false;
- updateEnable(this);
- this._clusterDirty = true;
- }
-
- if (this._clusterDirty) {
- this._cluster();
-
- // Unless all existing billboards and labels were clustered, clustering will need to execute again next frame
- this._clusterDirty =
- (defined(labelCollection) && !labelCollection.ready) ||
- (defined(billboardCollection) && !billboardCollection.ready);
- }
-
- if (defined(this._clusterLabelCollection)) {
- this._clusterLabelCollection.update(frameState);
- }
- if (defined(this._clusterBillboardCollection)) {
- this._clusterBillboardCollection.update(frameState);
- }
- if (defined(this._clusterPointCollection)) {
- this._clusterPointCollection.update(frameState);
- }
-
- if (defined(labelCollection)) {
- labelCollection.update(frameState);
- }
- if (defined(billboardCollection)) {
- billboardCollection.update(frameState);
- }
- if (defined(this._pointCollection)) {
- this._pointCollection.update(frameState);
- }
- };
-
- /**
- * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
- * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
- * <p>
- * Unlike other objects that use WebGL resources, this object can be reused. For example, if a data source is removed
- * from a data source collection and added to another.
- * </p>
- */
- EntityCluster.prototype.destroy = function () {
- if (defined(this._removeEventListener)) {
- this._removeEventListener();
- this._removeEventListener = undefined;
- }
-
- this._labelCollection =
- this._labelCollection && this._labelCollection.destroy();
- this._billboardCollection =
- this._billboardCollection && this._billboardCollection.destroy();
- this._pointCollection =
- this._pointCollection && this._pointCollection.destroy();
-
- this._clusterLabelCollection =
- this._clusterLabelCollection && this._clusterLabelCollection.destroy();
- this._clusterBillboardCollection =
- this._clusterBillboardCollection &&
- this._clusterBillboardCollection.destroy();
- this._clusterPointCollection =
- this._clusterPointCollection && this._clusterPointCollection.destroy();
-
- this._labelCollection = undefined;
- this._billboardCollection = undefined;
- this._pointCollection = undefined;
-
- this._clusterBillboardCollection = undefined;
- this._clusterLabelCollection = undefined;
- this._clusterPointCollection = undefined;
-
- this._collectionIndicesByEntity = undefined;
-
- this._unusedLabelIndices = [];
- this._unusedBillboardIndices = [];
- this._unusedPointIndices = [];
-
- this._previousClusters = [];
- this._previousHeight = undefined;
-
- this._enabledDirty = false;
- this._pixelRangeDirty = false;
- this._minimumClusterSizeDirty = false;
-
- return undefined;
- };
-
- /**
- * A event listener function used to style clusters.
- * @callback EntityCluster.newClusterCallback
- *
- * @param {Entity[]} clusteredEntities An array of the entities contained in the cluster.
- * @param {object} cluster An object containing the Billboard, Label, and Point
- * primitives that represent this cluster of entities.
- * @param {Billboard} cluster.billboard
- * @param {Label} cluster.label
- * @param {PointPrimitive} cluster.point
- *
- * @example
- * // The default cluster values.
- * dataSource.clustering.clusterEvent.addEventListener(function(entities, cluster) {
- * cluster.label.show = true;
- * cluster.label.text = entities.length.toLocaleString();
- * });
- */
- export default EntityCluster;
|