EntityCollection.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*global define*/
  2. define([
  3. '../Core/AssociativeArray',
  4. '../Core/createGuid',
  5. '../Core/defined',
  6. '../Core/defineProperties',
  7. '../Core/DeveloperError',
  8. '../Core/Event',
  9. '../Core/Iso8601',
  10. '../Core/JulianDate',
  11. '../Core/RuntimeError',
  12. '../Core/TimeInterval',
  13. './Entity'
  14. ], function(
  15. AssociativeArray,
  16. createGuid,
  17. defined,
  18. defineProperties,
  19. DeveloperError,
  20. Event,
  21. Iso8601,
  22. JulianDate,
  23. RuntimeError,
  24. TimeInterval,
  25. Entity) {
  26. "use strict";
  27. function fireChangedEvent(collection) {
  28. if (collection._suspendCount === 0) {
  29. var added = collection._addedEntities;
  30. var removed = collection._removedEntities;
  31. var changed = collection._changedEntities;
  32. if (changed.length !== 0 || added.length !== 0 || removed.length !== 0) {
  33. collection._collectionChanged.raiseEvent(collection, added.values, removed.values, changed.values);
  34. added.removeAll();
  35. removed.removeAll();
  36. changed.removeAll();
  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. var EntityCollection = function() {
  46. this._entities = new AssociativeArray();
  47. this._addedEntities = new AssociativeArray();
  48. this._removedEntities = new AssociativeArray();
  49. this._changedEntities = new AssociativeArray();
  50. this._suspendCount = 0;
  51. this._collectionChanged = new Event();
  52. this._id = createGuid();
  53. };
  54. /**
  55. * Prevents {@link EntityCollection#collectionChanged} events from being raised
  56. * until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
  57. * point a single event will be raised that covers all suspended operations.
  58. * This allows for many items to be added and removed efficiently.
  59. * This function can be safely called multiple times as long as there
  60. * are corresponding calls to {@link EntityCollection#resumeEvents}.
  61. */
  62. EntityCollection.prototype.suspendEvents = function() {
  63. this._suspendCount++;
  64. };
  65. /**
  66. * Resumes raising {@link EntityCollection#collectionChanged} events immediately
  67. * when an item is added or removed. Any modifications made while while events were suspended
  68. * will be triggered as a single event when this function is called.
  69. * This function is reference counted and can safely be called multiple times as long as there
  70. * are corresponding calls to {@link EntityCollection#resumeEvents}.
  71. *
  72. * @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
  73. */
  74. EntityCollection.prototype.resumeEvents = function() {
  75. //>>includeStart('debug', pragmas.debug);
  76. if (this._suspendCount === 0) {
  77. throw new DeveloperError('resumeEvents can not be called before suspendEvents.');
  78. }
  79. //>>includeEnd('debug');
  80. this._suspendCount--;
  81. fireChangedEvent(this);
  82. };
  83. /**
  84. * The signature of the event generated by {@link EntityCollection#collectionChanged}.
  85. * @function
  86. *
  87. * @param {EntityCollection} collection The collection that triggered the event.
  88. * @param {Entity[]} added The array of {@link Entity} instances that have been added to the collection.
  89. * @param {Entity[]} removed The array of {@link Entity} instances that have been removed from the collection.
  90. * @param {Entity[]} changed The array of {@link Entity} instances that have been modified.
  91. */
  92. EntityCollection.collectionChangedEventCallback = undefined;
  93. defineProperties(EntityCollection.prototype, {
  94. /**
  95. * Gets the event that is fired when entities are added or removed from the collection.
  96. * The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
  97. * @memberof EntityCollection.prototype
  98. * @readonly
  99. * @type {Event}
  100. */
  101. collectionChanged : {
  102. get : function() {
  103. return this._collectionChanged;
  104. }
  105. },
  106. /**
  107. * Gets a globally unique identifier for this collection.
  108. * @memberof EntityCollection.prototype
  109. * @readonly
  110. * @type {String}
  111. */
  112. id : {
  113. get : function() {
  114. return this._id;
  115. }
  116. },
  117. /**
  118. * Gets the array of Entity instances in the collection.
  119. * This array should not be modified directly.
  120. * @memberof EntityCollection.prototype
  121. * @readonly
  122. * @type {Entity[]}
  123. */
  124. entities : {
  125. get : function() {
  126. return this._entities.values;
  127. }
  128. }
  129. });
  130. /**
  131. * Computes the maximum availability of the entities in the collection.
  132. * If the collection contains a mix of infinitely available data and non-infinite data,
  133. * it will return the interval pertaining to the non-infinite data only. If all
  134. * data is infinite, an infinite interval will be returned.
  135. *
  136. * @returns {TimeInterval} The availability of entities in the collection.
  137. */
  138. EntityCollection.prototype.computeAvailability = function() {
  139. var startTime = Iso8601.MAXIMUM_VALUE;
  140. var stopTime = Iso8601.MINIMUM_VALUE;
  141. var entities = this._entities.values;
  142. for (var i = 0, len = entities.length; i < len; i++) {
  143. var entity = entities[i];
  144. var availability = entity.availability;
  145. if (defined(availability)) {
  146. var start = availability.start;
  147. var stop = availability.stop;
  148. if (JulianDate.lessThan(start, startTime) && !start.equals(Iso8601.MINIMUM_VALUE)) {
  149. startTime = start;
  150. }
  151. if (JulianDate.greaterThan(stop, stopTime) && !stop.equals(Iso8601.MAXIMUM_VALUE)) {
  152. stopTime = stop;
  153. }
  154. }
  155. }
  156. if (Iso8601.MAXIMUM_VALUE.equals(startTime)) {
  157. startTime = Iso8601.MINIMUM_VALUE;
  158. }
  159. if (Iso8601.MINIMUM_VALUE.equals(stopTime)) {
  160. stopTime = Iso8601.MAXIMUM_VALUE;
  161. }
  162. return new TimeInterval({
  163. start : startTime,
  164. stop : stopTime
  165. });
  166. };
  167. /**
  168. * Add an entity to the collection.
  169. *
  170. * @param {Entity} entity The entity to be added.
  171. * @exception {DeveloperError} An entity with <entity.id> already exists in this collection.
  172. */
  173. EntityCollection.prototype.add = function(entity) {
  174. //>>includeStart('debug', pragmas.debug);
  175. if (!defined(entity)) {
  176. throw new DeveloperError('entity is required.');
  177. }
  178. //>>includeEnd('debug');
  179. var id = entity.id;
  180. var entities = this._entities;
  181. if (entities.contains(id)) {
  182. throw new RuntimeError('An entity with id ' + id + ' already exists in this collection.');
  183. }
  184. entities.set(id, entity);
  185. var removedEntities = this._removedEntities;
  186. if (!this._removedEntities.remove(id)) {
  187. this._addedEntities.set(id, entity);
  188. }
  189. entity.definitionChanged.addEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
  190. fireChangedEvent(this);
  191. };
  192. /**
  193. * Removes an entity from the collection.
  194. *
  195. * @param {Entity} entity The entity to be added.
  196. * @returns {Boolean} true if the item was removed, false if it did not exist in the collection.
  197. */
  198. EntityCollection.prototype.remove = function(entity) {
  199. //>>includeStart('debug', pragmas.debug);
  200. if (!defined(entity)) {
  201. throw new DeveloperError('entity is required');
  202. }
  203. //>>includeEnd('debug');
  204. return this.removeById(entity.id);
  205. };
  206. /**
  207. * Removes an entity with the provided id from the collection.
  208. *
  209. * @param {Object} id The id of the entity to remove.
  210. * @returns {Boolean} true if the item was removed, false if no item with the provided id existed in the collection.
  211. */
  212. EntityCollection.prototype.removeById = function(id) {
  213. //>>includeStart('debug', pragmas.debug);
  214. if (!defined(id)) {
  215. throw new DeveloperError('id is required.');
  216. }
  217. //>>includeEnd('debug');
  218. var entities = this._entities;
  219. var entity = entities.get(id);
  220. if (!this._entities.remove(id)) {
  221. return false;
  222. }
  223. if (!this._addedEntities.remove(id)) {
  224. this._removedEntities.set(id, entity);
  225. this._changedEntities.remove(id);
  226. }
  227. this._entities.remove(id);
  228. entity.definitionChanged.removeEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
  229. fireChangedEvent(this);
  230. return true;
  231. };
  232. /**
  233. * Removes all Entities from the collection.
  234. */
  235. EntityCollection.prototype.removeAll = function() {
  236. //The event should only contain items added before events were suspended
  237. //and the contents of the collection.
  238. var entities = this._entities;
  239. var entitiesLength = entities.length;
  240. var array = entities.values;
  241. var addedEntities = this._addedEntities;
  242. var removed = this._removedEntities;
  243. for (var i = 0; i < entitiesLength; i++) {
  244. var existingItem = array[i];
  245. var existingItemId = existingItem.id;
  246. var addedItem = addedEntities.get(existingItemId);
  247. if (!defined(addedItem)) {
  248. existingItem.definitionChanged.removeEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
  249. removed.set(existingItemId, existingItem);
  250. }
  251. }
  252. entities.removeAll();
  253. addedEntities.removeAll();
  254. this._changedEntities.removeAll();
  255. fireChangedEvent(this);
  256. };
  257. /**
  258. * Gets an entity with the specified id.
  259. *
  260. * @param {Object} id The id of the entity to retrieve.
  261. * @returns {Entity} The entity with the provided id or undefined if the id did not exist in the collection.
  262. */
  263. EntityCollection.prototype.getById = function(id) {
  264. //>>includeStart('debug', pragmas.debug);
  265. if (!defined(id)) {
  266. throw new DeveloperError('id is required.');
  267. }
  268. //>>includeEnd('debug');
  269. return this._entities.get(id);
  270. };
  271. /**
  272. * Gets an entity with the specified id or creates it and adds it to the collection if it does not exist.
  273. *
  274. * @param {Object} id The id of the entity to retrieve or create.
  275. * @returns {Entity} The new or existing object.
  276. */
  277. EntityCollection.prototype.getOrCreateEntity = function(id) {
  278. //>>includeStart('debug', pragmas.debug);
  279. if (!defined(id)) {
  280. throw new DeveloperError('id is required.');
  281. }
  282. //>>includeEnd('debug');
  283. var entity = this._entities.get(id);
  284. if (!defined(entity)) {
  285. entity = new Entity(id);
  286. this.add(entity);
  287. }
  288. return entity;
  289. };
  290. EntityCollection.prototype._onEntityDefinitionChanged = function(entity) {
  291. var id = entity.id;
  292. if (!this._addedEntities.contains(id)) {
  293. this._changedEntities.set(id, entity);
  294. }
  295. fireChangedEvent(this);
  296. };
  297. return EntityCollection;
  298. });