EntityCollection.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. import AssociativeArray from "../Core/AssociativeArray.js";
  2. import createGuid from "../Core/createGuid.js";
  3. import defined from "../Core/defined.js";
  4. import DeveloperError from "../Core/DeveloperError.js";
  5. import Event from "../Core/Event.js";
  6. import Iso8601 from "../Core/Iso8601.js";
  7. import JulianDate from "../Core/JulianDate.js";
  8. import RuntimeError from "../Core/RuntimeError.js";
  9. import TimeInterval from "../Core/TimeInterval.js";
  10. import Entity from "./Entity.js";
  11. var entityOptionsScratch = {
  12. id: undefined,
  13. };
  14. function fireChangedEvent(collection) {
  15. if (collection._firing) {
  16. collection._refire = true;
  17. return;
  18. }
  19. if (collection._suspendCount === 0) {
  20. var added = collection._addedEntities;
  21. var removed = collection._removedEntities;
  22. var changed = collection._changedEntities;
  23. if (changed.length !== 0 || added.length !== 0 || removed.length !== 0) {
  24. collection._firing = true;
  25. do {
  26. collection._refire = false;
  27. var addedArray = added.values.slice(0);
  28. var removedArray = removed.values.slice(0);
  29. var changedArray = changed.values.slice(0);
  30. added.removeAll();
  31. removed.removeAll();
  32. changed.removeAll();
  33. collection._collectionChanged.raiseEvent(
  34. collection,
  35. addedArray,
  36. removedArray,
  37. changedArray
  38. );
  39. } while (collection._refire);
  40. collection._firing = false;
  41. }
  42. }
  43. }
  44. /**
  45. * An observable collection of {@link Entity} instances where each entity has a unique id.
  46. * @alias EntityCollection
  47. * @constructor
  48. *
  49. * @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection.
  50. */
  51. function EntityCollection(owner) {
  52. this._owner = owner;
  53. this._entities = new AssociativeArray();
  54. this._addedEntities = new AssociativeArray();
  55. this._removedEntities = new AssociativeArray();
  56. this._changedEntities = new AssociativeArray();
  57. this._suspendCount = 0;
  58. this._collectionChanged = new Event();
  59. this._id = createGuid();
  60. this._show = true;
  61. this._firing = false;
  62. this._refire = false;
  63. }
  64. /**
  65. * Prevents {@link EntityCollection#collectionChanged} events from being raised
  66. * until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
  67. * point a single event will be raised that covers all suspended operations.
  68. * This allows for many items to be added and removed efficiently.
  69. * This function can be safely called multiple times as long as there
  70. * are corresponding calls to {@link EntityCollection#resumeEvents}.
  71. */
  72. EntityCollection.prototype.suspendEvents = function () {
  73. this._suspendCount++;
  74. };
  75. /**
  76. * Resumes raising {@link EntityCollection#collectionChanged} events immediately
  77. * when an item is added or removed. Any modifications made while while events were suspended
  78. * will be triggered as a single event when this function is called.
  79. * This function is reference counted and can safely be called multiple times as long as there
  80. * are corresponding calls to {@link EntityCollection#resumeEvents}.
  81. *
  82. * @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
  83. */
  84. EntityCollection.prototype.resumeEvents = function () {
  85. //>>includeStart('debug', pragmas.debug);
  86. if (this._suspendCount === 0) {
  87. throw new DeveloperError(
  88. "resumeEvents can not be called before suspendEvents."
  89. );
  90. }
  91. //>>includeEnd('debug');
  92. this._suspendCount--;
  93. fireChangedEvent(this);
  94. };
  95. /**
  96. * The signature of the event generated by {@link EntityCollection#collectionChanged}.
  97. * @function
  98. *
  99. * @param {EntityCollection} collection The collection that triggered the event.
  100. * @param {Entity[]} added The array of {@link Entity} instances that have been added to the collection.
  101. * @param {Entity[]} removed The array of {@link Entity} instances that have been removed from the collection.
  102. * @param {Entity[]} changed The array of {@link Entity} instances that have been modified.
  103. */
  104. EntityCollection.collectionChangedEventCallback = undefined;
  105. Object.defineProperties(EntityCollection.prototype, {
  106. /**
  107. * Gets the event that is fired when entities are added or removed from the collection.
  108. * The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
  109. * @memberof EntityCollection.prototype
  110. * @readonly
  111. * @type {Event}
  112. */
  113. collectionChanged: {
  114. get: function () {
  115. return this._collectionChanged;
  116. },
  117. },
  118. /**
  119. * Gets a globally unique identifier for this collection.
  120. * @memberof EntityCollection.prototype
  121. * @readonly
  122. * @type {String}
  123. */
  124. id: {
  125. get: function () {
  126. return this._id;
  127. },
  128. },
  129. /**
  130. * Gets the array of Entity instances in the collection.
  131. * This array should not be modified directly.
  132. * @memberof EntityCollection.prototype
  133. * @readonly
  134. * @type {Entity[]}
  135. */
  136. values: {
  137. get: function () {
  138. return this._entities.values;
  139. },
  140. },
  141. /**
  142. * Gets whether or not this entity collection should be
  143. * displayed. When true, each entity is only displayed if
  144. * its own show property is also true.
  145. * @memberof EntityCollection.prototype
  146. * @type {Boolean}
  147. */
  148. show: {
  149. get: function () {
  150. return this._show;
  151. },
  152. set: function (value) {
  153. //>>includeStart('debug', pragmas.debug);
  154. if (!defined(value)) {
  155. throw new DeveloperError("value is required.");
  156. }
  157. //>>includeEnd('debug');
  158. if (value === this._show) {
  159. return;
  160. }
  161. //Since entity.isShowing includes the EntityCollection.show state
  162. //in its calculation, we need to loop over the entities array
  163. //twice, once to get the old showing value and a second time
  164. //to raise the changed event.
  165. this.suspendEvents();
  166. var i;
  167. var oldShows = [];
  168. var entities = this._entities.values;
  169. var entitiesLength = entities.length;
  170. for (i = 0; i < entitiesLength; i++) {
  171. oldShows.push(entities[i].isShowing);
  172. }
  173. this._show = value;
  174. for (i = 0; i < entitiesLength; i++) {
  175. var oldShow = oldShows[i];
  176. var entity = entities[i];
  177. if (oldShow !== entity.isShowing) {
  178. entity.definitionChanged.raiseEvent(
  179. entity,
  180. "isShowing",
  181. entity.isShowing,
  182. oldShow
  183. );
  184. }
  185. }
  186. this.resumeEvents();
  187. },
  188. },
  189. /**
  190. * Gets the owner of this entity collection, ie. the data source or composite entity collection which created it.
  191. * @memberof EntityCollection.prototype
  192. * @readonly
  193. * @type {DataSource|CompositeEntityCollection}
  194. */
  195. owner: {
  196. get: function () {
  197. return this._owner;
  198. },
  199. },
  200. });
  201. /**
  202. * Computes the maximum availability of the entities in the collection.
  203. * If the collection contains a mix of infinitely available data and non-infinite data,
  204. * it will return the interval pertaining to the non-infinite data only. If all
  205. * data is infinite, an infinite interval will be returned.
  206. *
  207. * @returns {TimeInterval} The availability of entities in the collection.
  208. */
  209. EntityCollection.prototype.computeAvailability = function () {
  210. var startTime = Iso8601.MAXIMUM_VALUE;
  211. var stopTime = Iso8601.MINIMUM_VALUE;
  212. var entities = this._entities.values;
  213. for (var i = 0, len = entities.length; i < len; i++) {
  214. var entity = entities[i];
  215. var availability = entity.availability;
  216. if (defined(availability)) {
  217. var start = availability.start;
  218. var stop = availability.stop;
  219. if (
  220. JulianDate.lessThan(start, startTime) &&
  221. !start.equals(Iso8601.MINIMUM_VALUE)
  222. ) {
  223. startTime = start;
  224. }
  225. if (
  226. JulianDate.greaterThan(stop, stopTime) &&
  227. !stop.equals(Iso8601.MAXIMUM_VALUE)
  228. ) {
  229. stopTime = stop;
  230. }
  231. }
  232. }
  233. if (Iso8601.MAXIMUM_VALUE.equals(startTime)) {
  234. startTime = Iso8601.MINIMUM_VALUE;
  235. }
  236. if (Iso8601.MINIMUM_VALUE.equals(stopTime)) {
  237. stopTime = Iso8601.MAXIMUM_VALUE;
  238. }
  239. return new TimeInterval({
  240. start: startTime,
  241. stop: stopTime,
  242. });
  243. };
  244. /**
  245. * Add an entity to the collection.
  246. *
  247. * @param {Entity | Entity.ConstructorOptions} entity The entity to be added.
  248. * @returns {Entity} The entity that was added.
  249. * @exception {DeveloperError} An entity with <entity.id> already exists in this collection.
  250. */
  251. EntityCollection.prototype.add = function (entity) {
  252. //>>includeStart('debug', pragmas.debug);
  253. if (!defined(entity)) {
  254. throw new DeveloperError("entity is required.");
  255. }
  256. //>>includeEnd('debug');
  257. if (!(entity instanceof Entity)) {
  258. entity = new Entity(entity);
  259. }
  260. var id = entity.id;
  261. var entities = this._entities;
  262. if (entities.contains(id)) {
  263. throw new RuntimeError(
  264. "An entity with id " + id + " already exists in this collection."
  265. );
  266. }
  267. entity.entityCollection = this;
  268. entities.set(id, entity);
  269. if (!this._removedEntities.remove(id)) {
  270. this._addedEntities.set(id, entity);
  271. }
  272. entity.definitionChanged.addEventListener(
  273. EntityCollection.prototype._onEntityDefinitionChanged,
  274. this
  275. );
  276. fireChangedEvent(this);
  277. return entity;
  278. };
  279. /**
  280. * Removes an entity from the collection.
  281. *
  282. * @param {Entity} entity The entity to be removed.
  283. * @returns {Boolean} true if the item was removed, false if it did not exist in the collection.
  284. */
  285. EntityCollection.prototype.remove = function (entity) {
  286. if (!defined(entity)) {
  287. return false;
  288. }
  289. return this.removeById(entity.id);
  290. };
  291. /**
  292. * Returns true if the provided entity is in this collection, false otherwise.
  293. *
  294. * @param {Entity} entity The entity.
  295. * @returns {Boolean} true if the provided entity is in this collection, false otherwise.
  296. */
  297. EntityCollection.prototype.contains = function (entity) {
  298. //>>includeStart('debug', pragmas.debug);
  299. if (!defined(entity)) {
  300. throw new DeveloperError("entity is required");
  301. }
  302. //>>includeEnd('debug');
  303. return this._entities.get(entity.id) === entity;
  304. };
  305. /**
  306. * Removes an entity with the provided id from the collection.
  307. *
  308. * @param {String} id The id of the entity to remove.
  309. * @returns {Boolean} true if the item was removed, false if no item with the provided id existed in the collection.
  310. */
  311. EntityCollection.prototype.removeById = function (id) {
  312. if (!defined(id)) {
  313. return false;
  314. }
  315. var entities = this._entities;
  316. var entity = entities.get(id);
  317. if (!this._entities.remove(id)) {
  318. return false;
  319. }
  320. if (!this._addedEntities.remove(id)) {
  321. this._removedEntities.set(id, entity);
  322. this._changedEntities.remove(id);
  323. }
  324. this._entities.remove(id);
  325. entity.definitionChanged.removeEventListener(
  326. EntityCollection.prototype._onEntityDefinitionChanged,
  327. this
  328. );
  329. fireChangedEvent(this);
  330. return true;
  331. };
  332. /**
  333. * Removes all Entities from the collection.
  334. */
  335. EntityCollection.prototype.removeAll = function () {
  336. //The event should only contain items added before events were suspended
  337. //and the contents of the collection.
  338. var entities = this._entities;
  339. var entitiesLength = entities.length;
  340. var array = entities.values;
  341. var addedEntities = this._addedEntities;
  342. var removed = this._removedEntities;
  343. for (var i = 0; i < entitiesLength; i++) {
  344. var existingItem = array[i];
  345. var existingItemId = existingItem.id;
  346. var addedItem = addedEntities.get(existingItemId);
  347. if (!defined(addedItem)) {
  348. existingItem.definitionChanged.removeEventListener(
  349. EntityCollection.prototype._onEntityDefinitionChanged,
  350. this
  351. );
  352. removed.set(existingItemId, existingItem);
  353. }
  354. }
  355. entities.removeAll();
  356. addedEntities.removeAll();
  357. this._changedEntities.removeAll();
  358. fireChangedEvent(this);
  359. };
  360. /**
  361. * Gets an entity with the specified id.
  362. *
  363. * @param {String} id The id of the entity to retrieve.
  364. * @returns {Entity|undefined} The entity with the provided id or undefined if the id did not exist in the collection.
  365. */
  366. EntityCollection.prototype.getById = function (id) {
  367. //>>includeStart('debug', pragmas.debug);
  368. if (!defined(id)) {
  369. throw new DeveloperError("id is required.");
  370. }
  371. //>>includeEnd('debug');
  372. return this._entities.get(id);
  373. };
  374. /**
  375. * Gets an entity with the specified id or creates it and adds it to the collection if it does not exist.
  376. *
  377. * @param {String} id The id of the entity to retrieve or create.
  378. * @returns {Entity} The new or existing object.
  379. */
  380. EntityCollection.prototype.getOrCreateEntity = function (id) {
  381. //>>includeStart('debug', pragmas.debug);
  382. if (!defined(id)) {
  383. throw new DeveloperError("id is required.");
  384. }
  385. //>>includeEnd('debug');
  386. var entity = this._entities.get(id);
  387. if (!defined(entity)) {
  388. entityOptionsScratch.id = id;
  389. entity = new Entity(entityOptionsScratch);
  390. this.add(entity);
  391. }
  392. return entity;
  393. };
  394. EntityCollection.prototype._onEntityDefinitionChanged = function (entity) {
  395. var id = entity.id;
  396. if (!this._addedEntities.contains(id)) {
  397. this._changedEntities.set(id, entity);
  398. }
  399. fireChangedEvent(this);
  400. };
  401. export default EntityCollection;