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

CompositeEntityCollection.js 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. import createGuid from "../Core/createGuid.js";
  2. import defined from "../Core/defined.js";
  3. import DeveloperError from "../Core/DeveloperError.js";
  4. import CesiumMath from "../Core/Math.js";
  5. import Entity from "./Entity.js";
  6. import EntityCollection from "./EntityCollection.js";
  7. const entityOptionsScratch = {
  8. id: undefined,
  9. };
  10. const entityIdScratch = new Array(2);
  11. function clean(entity) {
  12. const propertyNames = entity.propertyNames;
  13. const propertyNamesLength = propertyNames.length;
  14. for (let i = 0; i < propertyNamesLength; i++) {
  15. entity[propertyNames[i]] = undefined;
  16. }
  17. entity._name = undefined;
  18. entity._availability = undefined;
  19. }
  20. function subscribeToEntity(that, eventHash, collectionId, entity) {
  21. entityIdScratch[0] = collectionId;
  22. entityIdScratch[1] = entity.id;
  23. eventHash[JSON.stringify(entityIdScratch)] =
  24. entity.definitionChanged.addEventListener(
  25. CompositeEntityCollection.prototype._onDefinitionChanged,
  26. that,
  27. );
  28. }
  29. function unsubscribeFromEntity(that, eventHash, collectionId, entity) {
  30. entityIdScratch[0] = collectionId;
  31. entityIdScratch[1] = entity.id;
  32. const id = JSON.stringify(entityIdScratch);
  33. eventHash[id]();
  34. eventHash[id] = undefined;
  35. }
  36. function recomposite(that) {
  37. that._shouldRecomposite = true;
  38. if (that._suspendCount !== 0) {
  39. return;
  40. }
  41. const collections = that._collections;
  42. const collectionsLength = collections.length;
  43. const collectionsCopy = that._collectionsCopy;
  44. const collectionsCopyLength = collectionsCopy.length;
  45. let i;
  46. let entity;
  47. let entities;
  48. let iEntities;
  49. let collection;
  50. const composite = that._composite;
  51. const newEntities = new EntityCollection(that);
  52. const eventHash = that._eventHash;
  53. let collectionId;
  54. for (i = 0; i < collectionsCopyLength; i++) {
  55. collection = collectionsCopy[i];
  56. collection.collectionChanged.removeEventListener(
  57. CompositeEntityCollection.prototype._onCollectionChanged,
  58. that,
  59. );
  60. entities = collection.values;
  61. collectionId = collection.id;
  62. for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {
  63. entity = entities[iEntities];
  64. unsubscribeFromEntity(that, eventHash, collectionId, entity);
  65. }
  66. }
  67. for (i = collectionsLength - 1; i >= 0; i--) {
  68. collection = collections[i];
  69. collection.collectionChanged.addEventListener(
  70. CompositeEntityCollection.prototype._onCollectionChanged,
  71. that,
  72. );
  73. //Merge all of the existing entities.
  74. entities = collection.values;
  75. collectionId = collection.id;
  76. for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {
  77. entity = entities[iEntities];
  78. subscribeToEntity(that, eventHash, collectionId, entity);
  79. let compositeEntity = newEntities.getById(entity.id);
  80. if (!defined(compositeEntity)) {
  81. compositeEntity = composite.getById(entity.id);
  82. if (!defined(compositeEntity)) {
  83. entityOptionsScratch.id = entity.id;
  84. compositeEntity = new Entity(entityOptionsScratch);
  85. } else {
  86. clean(compositeEntity);
  87. }
  88. newEntities.add(compositeEntity);
  89. }
  90. compositeEntity.merge(entity);
  91. }
  92. }
  93. that._collectionsCopy = collections.slice(0);
  94. composite.suspendEvents();
  95. composite.removeAll();
  96. const newEntitiesArray = newEntities.values;
  97. for (i = 0; i < newEntitiesArray.length; i++) {
  98. composite.add(newEntitiesArray[i]);
  99. }
  100. composite.resumeEvents();
  101. }
  102. /**
  103. * Non-destructively composites multiple {@link EntityCollection} instances into a single collection.
  104. * If a Entity with the same ID exists in multiple collections, it is non-destructively
  105. * merged into a single new entity instance. If an entity has the same property in multiple
  106. * collections, the property of the Entity in the last collection of the list it
  107. * belongs to is used. CompositeEntityCollection can be used almost anywhere that a
  108. * EntityCollection is used.
  109. *
  110. * @alias CompositeEntityCollection
  111. * @constructor
  112. *
  113. * @param {EntityCollection[]} [collections] The initial list of EntityCollection instances to merge.
  114. * @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection.
  115. */
  116. function CompositeEntityCollection(collections, owner) {
  117. this._owner = owner;
  118. this._composite = new EntityCollection(this);
  119. this._suspendCount = 0;
  120. this._collections = defined(collections) ? collections.slice() : [];
  121. this._collectionsCopy = [];
  122. this._id = createGuid();
  123. this._eventHash = {};
  124. recomposite(this);
  125. this._shouldRecomposite = false;
  126. }
  127. Object.defineProperties(CompositeEntityCollection.prototype, {
  128. /**
  129. * Gets the event that is fired when entities are added or removed from the collection.
  130. * The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
  131. * @memberof CompositeEntityCollection.prototype
  132. * @readonly
  133. * @type {Event}
  134. */
  135. collectionChanged: {
  136. get: function () {
  137. return this._composite._collectionChanged;
  138. },
  139. },
  140. /**
  141. * Gets a globally unique identifier for this collection.
  142. * @memberof CompositeEntityCollection.prototype
  143. * @readonly
  144. * @type {string}
  145. */
  146. id: {
  147. get: function () {
  148. return this._id;
  149. },
  150. },
  151. /**
  152. * Gets the array of Entity instances in the collection.
  153. * This array should not be modified directly.
  154. * @memberof CompositeEntityCollection.prototype
  155. * @readonly
  156. * @type {Entity[]}
  157. */
  158. values: {
  159. get: function () {
  160. return this._composite.values;
  161. },
  162. },
  163. /**
  164. * Gets the owner of this composite entity collection, ie. the data source or composite entity collection which created it.
  165. * @memberof CompositeEntityCollection.prototype
  166. * @readonly
  167. * @type {DataSource|CompositeEntityCollection}
  168. */
  169. owner: {
  170. get: function () {
  171. return this._owner;
  172. },
  173. },
  174. });
  175. /**
  176. * Adds a collection to the composite.
  177. *
  178. * @param {EntityCollection} collection the collection to add.
  179. * @param {number} [index] the index to add the collection at. If omitted, the collection will
  180. * added on top of all existing collections.
  181. *
  182. * @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of collections.
  183. */
  184. CompositeEntityCollection.prototype.addCollection = function (
  185. collection,
  186. index,
  187. ) {
  188. const hasIndex = defined(index);
  189. //>>includeStart('debug', pragmas.debug);
  190. if (!defined(collection)) {
  191. throw new DeveloperError("collection is required.");
  192. }
  193. if (hasIndex) {
  194. if (index < 0) {
  195. throw new DeveloperError("index must be greater than or equal to zero.");
  196. } else if (index > this._collections.length) {
  197. throw new DeveloperError(
  198. "index must be less than or equal to the number of collections.",
  199. );
  200. }
  201. }
  202. //>>includeEnd('debug');
  203. if (!hasIndex) {
  204. index = this._collections.length;
  205. this._collections.push(collection);
  206. } else {
  207. this._collections.splice(index, 0, collection);
  208. }
  209. recomposite(this);
  210. };
  211. /**
  212. * Removes a collection from this composite, if present.
  213. *
  214. * @param {EntityCollection} collection The collection to remove.
  215. * @returns {boolean} true if the collection was in the composite and was removed,
  216. * false if the collection was not in the composite.
  217. */
  218. CompositeEntityCollection.prototype.removeCollection = function (collection) {
  219. const index = this._collections.indexOf(collection);
  220. if (index !== -1) {
  221. this._collections.splice(index, 1);
  222. recomposite(this);
  223. return true;
  224. }
  225. return false;
  226. };
  227. /**
  228. * Removes all collections from this composite.
  229. */
  230. CompositeEntityCollection.prototype.removeAllCollections = function () {
  231. this._collections.length = 0;
  232. recomposite(this);
  233. };
  234. /**
  235. * Checks to see if the composite contains a given collection.
  236. *
  237. * @param {EntityCollection} collection the collection to check for.
  238. * @returns {boolean} true if the composite contains the collection, false otherwise.
  239. */
  240. CompositeEntityCollection.prototype.containsCollection = function (collection) {
  241. return this._collections.indexOf(collection) !== -1;
  242. };
  243. /**
  244. * Returns true if the provided entity is in this collection, false otherwise.
  245. *
  246. * @param {Entity} entity The entity.
  247. * @returns {boolean} true if the provided entity is in this collection, false otherwise.
  248. */
  249. CompositeEntityCollection.prototype.contains = function (entity) {
  250. return this._composite.contains(entity);
  251. };
  252. /**
  253. * Determines the index of a given collection in the composite.
  254. *
  255. * @param {EntityCollection} collection The collection to find the index of.
  256. * @returns {number} The index of the collection in the composite, or -1 if the collection does not exist in the composite.
  257. */
  258. CompositeEntityCollection.prototype.indexOfCollection = function (collection) {
  259. return this._collections.indexOf(collection);
  260. };
  261. /**
  262. * Gets a collection by index from the composite.
  263. *
  264. * @param {number} index the index to retrieve.
  265. */
  266. CompositeEntityCollection.prototype.getCollection = function (index) {
  267. //>>includeStart('debug', pragmas.debug);
  268. if (!defined(index)) {
  269. throw new DeveloperError("index is required.", "index");
  270. }
  271. //>>includeEnd('debug');
  272. return this._collections[index];
  273. };
  274. /**
  275. * Gets the number of collections in this composite.
  276. */
  277. CompositeEntityCollection.prototype.getCollectionsLength = function () {
  278. return this._collections.length;
  279. };
  280. function getCollectionIndex(collections, collection) {
  281. //>>includeStart('debug', pragmas.debug);
  282. if (!defined(collection)) {
  283. throw new DeveloperError("collection is required.");
  284. }
  285. //>>includeEnd('debug');
  286. const index = collections.indexOf(collection);
  287. //>>includeStart('debug', pragmas.debug);
  288. if (index === -1) {
  289. throw new DeveloperError("collection is not in this composite.");
  290. }
  291. //>>includeEnd('debug');
  292. return index;
  293. }
  294. function swapCollections(composite, i, j) {
  295. const arr = composite._collections;
  296. i = CesiumMath.clamp(i, 0, arr.length - 1);
  297. j = CesiumMath.clamp(j, 0, arr.length - 1);
  298. if (i === j) {
  299. return;
  300. }
  301. const temp = arr[i];
  302. arr[i] = arr[j];
  303. arr[j] = temp;
  304. recomposite(composite);
  305. }
  306. /**
  307. * Raises a collection up one position in the composite.
  308. *
  309. * @param {EntityCollection} collection the collection to move.
  310. *
  311. * @exception {DeveloperError} collection is not in this composite.
  312. */
  313. CompositeEntityCollection.prototype.raiseCollection = function (collection) {
  314. const index = getCollectionIndex(this._collections, collection);
  315. swapCollections(this, index, index + 1);
  316. };
  317. /**
  318. * Lowers a collection down one position in the composite.
  319. *
  320. * @param {EntityCollection} collection the collection to move.
  321. *
  322. * @exception {DeveloperError} collection is not in this composite.
  323. */
  324. CompositeEntityCollection.prototype.lowerCollection = function (collection) {
  325. const index = getCollectionIndex(this._collections, collection);
  326. swapCollections(this, index, index - 1);
  327. };
  328. /**
  329. * Raises a collection to the top of the composite.
  330. *
  331. * @param {EntityCollection} collection the collection to move.
  332. *
  333. * @exception {DeveloperError} collection is not in this composite.
  334. */
  335. CompositeEntityCollection.prototype.raiseCollectionToTop = function (
  336. collection,
  337. ) {
  338. const index = getCollectionIndex(this._collections, collection);
  339. if (index === this._collections.length - 1) {
  340. return;
  341. }
  342. this._collections.splice(index, 1);
  343. this._collections.push(collection);
  344. recomposite(this);
  345. };
  346. /**
  347. * Lowers a collection to the bottom of the composite.
  348. *
  349. * @param {EntityCollection} collection the collection to move.
  350. *
  351. * @exception {DeveloperError} collection is not in this composite.
  352. */
  353. CompositeEntityCollection.prototype.lowerCollectionToBottom = function (
  354. collection,
  355. ) {
  356. const index = getCollectionIndex(this._collections, collection);
  357. if (index === 0) {
  358. return;
  359. }
  360. this._collections.splice(index, 1);
  361. this._collections.splice(0, 0, collection);
  362. recomposite(this);
  363. };
  364. /**
  365. * Prevents {@link EntityCollection#collectionChanged} events from being raised
  366. * until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
  367. * point a single event will be raised that covers all suspended operations.
  368. * This allows for many items to be added and removed efficiently.
  369. * While events are suspended, recompositing of the collections will
  370. * also be suspended, as this can be a costly operation.
  371. * This function can be safely called multiple times as long as there
  372. * are corresponding calls to {@link EntityCollection#resumeEvents}.
  373. */
  374. CompositeEntityCollection.prototype.suspendEvents = function () {
  375. this._suspendCount++;
  376. this._composite.suspendEvents();
  377. };
  378. /**
  379. * Resumes raising {@link EntityCollection#collectionChanged} events immediately
  380. * when an item is added or removed. Any modifications made while while events were suspended
  381. * will be triggered as a single event when this function is called. This function also ensures
  382. * the collection is recomposited if events are also resumed.
  383. * This function is reference counted and can safely be called multiple times as long as there
  384. * are corresponding calls to {@link EntityCollection#resumeEvents}.
  385. *
  386. * @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
  387. */
  388. CompositeEntityCollection.prototype.resumeEvents = function () {
  389. //>>includeStart('debug', pragmas.debug);
  390. if (this._suspendCount === 0) {
  391. throw new DeveloperError(
  392. "resumeEvents can not be called before suspendEvents.",
  393. );
  394. }
  395. //>>includeEnd('debug');
  396. this._suspendCount--;
  397. // recomposite before triggering events (but only if required for performance) that might depend on a composited collection
  398. if (this._shouldRecomposite && this._suspendCount === 0) {
  399. recomposite(this);
  400. this._shouldRecomposite = false;
  401. }
  402. this._composite.resumeEvents();
  403. };
  404. /**
  405. * Computes the maximum availability of the entities in the collection.
  406. * If the collection contains a mix of infinitely available data and non-infinite data,
  407. * It will return the interval pertaining to the non-infinite data only. If all
  408. * data is infinite, an infinite interval will be returned.
  409. *
  410. * @returns {TimeInterval} The availability of entities in the collection.
  411. */
  412. CompositeEntityCollection.prototype.computeAvailability = function () {
  413. return this._composite.computeAvailability();
  414. };
  415. /**
  416. * Gets an entity with the specified id.
  417. *
  418. * @param {string} id The id of the entity to retrieve.
  419. * @returns {Entity|undefined} The entity with the provided id or undefined if the id did not exist in the collection.
  420. */
  421. CompositeEntityCollection.prototype.getById = function (id) {
  422. return this._composite.getById(id);
  423. };
  424. CompositeEntityCollection.prototype._onCollectionChanged = function (
  425. collection,
  426. added,
  427. removed,
  428. ) {
  429. const collections = this._collectionsCopy;
  430. const collectionsLength = collections.length;
  431. const composite = this._composite;
  432. composite.suspendEvents();
  433. let i;
  434. let q;
  435. let entity;
  436. let compositeEntity;
  437. const removedLength = removed.length;
  438. const eventHash = this._eventHash;
  439. const collectionId = collection.id;
  440. for (i = 0; i < removedLength; i++) {
  441. const removedEntity = removed[i];
  442. unsubscribeFromEntity(this, eventHash, collectionId, removedEntity);
  443. const removedId = removedEntity.id;
  444. //Check if the removed entity exists in any of the remaining collections
  445. //If so, we clean and remerge it.
  446. for (q = collectionsLength - 1; q >= 0; q--) {
  447. entity = collections[q].getById(removedId);
  448. if (defined(entity)) {
  449. if (!defined(compositeEntity)) {
  450. compositeEntity = composite.getById(removedId);
  451. clean(compositeEntity);
  452. }
  453. compositeEntity.merge(entity);
  454. }
  455. }
  456. //We never retrieved the compositeEntity, which means it no longer
  457. //exists in any of the collections, remove it from the composite.
  458. if (!defined(compositeEntity)) {
  459. composite.removeById(removedId);
  460. }
  461. compositeEntity = undefined;
  462. }
  463. const addedLength = added.length;
  464. for (i = 0; i < addedLength; i++) {
  465. const addedEntity = added[i];
  466. subscribeToEntity(this, eventHash, collectionId, addedEntity);
  467. const addedId = addedEntity.id;
  468. //We know the added entity exists in at least one collection,
  469. //but we need to check all collections and re-merge in order
  470. //to maintain the priority of properties.
  471. for (q = collectionsLength - 1; q >= 0; q--) {
  472. entity = collections[q].getById(addedId);
  473. if (defined(entity)) {
  474. if (!defined(compositeEntity)) {
  475. compositeEntity = composite.getById(addedId);
  476. if (!defined(compositeEntity)) {
  477. entityOptionsScratch.id = addedId;
  478. compositeEntity = new Entity(entityOptionsScratch);
  479. composite.add(compositeEntity);
  480. } else {
  481. clean(compositeEntity);
  482. }
  483. }
  484. compositeEntity.merge(entity);
  485. }
  486. }
  487. compositeEntity = undefined;
  488. }
  489. composite.resumeEvents();
  490. };
  491. CompositeEntityCollection.prototype._onDefinitionChanged = function (
  492. entity,
  493. propertyName,
  494. newValue,
  495. oldValue,
  496. ) {
  497. const collections = this._collections;
  498. const composite = this._composite;
  499. const collectionsLength = collections.length;
  500. const id = entity.id;
  501. const compositeEntity = composite.getById(id);
  502. let compositeProperty = compositeEntity[propertyName];
  503. const newProperty = !defined(compositeProperty);
  504. let firstTime = true;
  505. for (let q = collectionsLength - 1; q >= 0; q--) {
  506. const innerEntity = collections[q].getById(entity.id);
  507. if (defined(innerEntity)) {
  508. const property = innerEntity[propertyName];
  509. if (defined(property)) {
  510. if (firstTime) {
  511. firstTime = false;
  512. //We only want to clone if the property is also mergeable.
  513. //This ensures that leaf properties are referenced and not copied,
  514. //which is the entire point of compositing.
  515. if (defined(property.merge) && defined(property.clone)) {
  516. compositeProperty = property.clone(compositeProperty);
  517. } else {
  518. compositeProperty = property;
  519. break;
  520. }
  521. }
  522. compositeProperty.merge(property);
  523. }
  524. }
  525. }
  526. if (
  527. newProperty &&
  528. compositeEntity.propertyNames.indexOf(propertyName) === -1
  529. ) {
  530. compositeEntity.addProperty(propertyName);
  531. }
  532. compositeEntity[propertyName] = compositeProperty;
  533. };
  534. export default CompositeEntityCollection;