EntityCollection.js 15 KB

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