| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997 | 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 "../ThirdParty/kdbush.js";/** * 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 getX(point) {  return point.coord.x;}function getY(point) {  return point.coord.y;}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;    const index = new KDBush(points, getX, getY, 64, Int32Array);    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;
 |