123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- import BoundingRectangle from "../Core/BoundingRectangle.js";
- import Cartesian2 from "../Core/Cartesian2.js";
- import Cartesian3 from "../Core/Cartesian3.js";
- import defaultValue from "../Core/defaultValue.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?src=Clustering.html|Cesium Sandcastle Clustering Demo}
- */
- function EntityCluster(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- this._enabled = defaultValue(options.enabled, false);
- this._pixelRange = defaultValue(options.pixelRange, 80);
- this._minimumClusterSize = defaultValue(options.minimumClusterSize, 2);
- this._clusterBillboards = defaultValue(options.clusterBillboards, true);
- this._clusterLabels = defaultValue(options.clusterLabels, true);
- this._clusterPoints = defaultValue(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 = defaultValue(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.mapProjection.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, Uint32Array);
- 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;
- },
- },
- });
- 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.pop();
- 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;
- if (
- defined(this._labelCollection) &&
- this._labelCollection.length > 0 &&
- this._labelCollection.get(0)._glyphs.length === 0
- ) {
- commandList = frameState.commandList;
- frameState.commandList = [];
- this._labelCollection.update(frameState);
- frameState.commandList = commandList;
- }
- // If clustering is enabled before the billboard collection is updated,
- // the images haven't been added to the image atlas so the screen space bounding boxes
- // are incorrect.
- if (
- defined(this._billboardCollection) &&
- this._billboardCollection.length > 0 &&
- !defined(this._billboardCollection.get(0).width)
- ) {
- commandList = frameState.commandList;
- frameState.commandList = [];
- this._billboardCollection.update(frameState);
- frameState.commandList = commandList;
- }
- if (this._enabledDirty) {
- this._enabledDirty = false;
- updateEnable(this);
- this._clusterDirty = true;
- }
- if (this._clusterDirty) {
- this._clusterDirty = false;
- this._cluster();
- }
- 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(this._labelCollection)) {
- this._labelCollection.update(frameState);
- }
- if (defined(this._billboardCollection)) {
- this._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 () {
- 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();
- if (defined(this._removeEventListener)) {
- this._removeEventListener();
- this._removeEventListener = undefined;
- }
- 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;
|