智慧水务管理系统 - 精河县供水工程综合管理平台

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