You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
442 lines
13 KiB
JavaScript
442 lines
13 KiB
JavaScript
import AssociativeArray from "../Core/AssociativeArray.js";
|
|
import createGuid from "../Core/createGuid.js";
|
|
import defined from "../Core/defined.js";
|
|
import DeveloperError from "../Core/DeveloperError.js";
|
|
import Event from "../Core/Event.js";
|
|
import Iso8601 from "../Core/Iso8601.js";
|
|
import JulianDate from "../Core/JulianDate.js";
|
|
import RuntimeError from "../Core/RuntimeError.js";
|
|
import TimeInterval from "../Core/TimeInterval.js";
|
|
import Entity from "./Entity.js";
|
|
|
|
var entityOptionsScratch = {
|
|
id: undefined,
|
|
};
|
|
|
|
function fireChangedEvent(collection) {
|
|
if (collection._firing) {
|
|
collection._refire = true;
|
|
return;
|
|
}
|
|
|
|
if (collection._suspendCount === 0) {
|
|
var added = collection._addedEntities;
|
|
var removed = collection._removedEntities;
|
|
var changed = collection._changedEntities;
|
|
if (changed.length !== 0 || added.length !== 0 || removed.length !== 0) {
|
|
collection._firing = true;
|
|
do {
|
|
collection._refire = false;
|
|
var addedArray = added.values.slice(0);
|
|
var removedArray = removed.values.slice(0);
|
|
var changedArray = changed.values.slice(0);
|
|
|
|
added.removeAll();
|
|
removed.removeAll();
|
|
changed.removeAll();
|
|
collection._collectionChanged.raiseEvent(
|
|
collection,
|
|
addedArray,
|
|
removedArray,
|
|
changedArray
|
|
);
|
|
} while (collection._refire);
|
|
collection._firing = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An observable collection of {@link Entity} instances where each entity has a unique id.
|
|
* @alias EntityCollection
|
|
* @constructor
|
|
*
|
|
* @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection.
|
|
*/
|
|
function EntityCollection(owner) {
|
|
this._owner = owner;
|
|
this._entities = new AssociativeArray();
|
|
this._addedEntities = new AssociativeArray();
|
|
this._removedEntities = new AssociativeArray();
|
|
this._changedEntities = new AssociativeArray();
|
|
this._suspendCount = 0;
|
|
this._collectionChanged = new Event();
|
|
this._id = createGuid();
|
|
this._show = true;
|
|
this._firing = false;
|
|
this._refire = false;
|
|
}
|
|
|
|
/**
|
|
* Prevents {@link EntityCollection#collectionChanged} events from being raised
|
|
* until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
|
|
* point a single event will be raised that covers all suspended operations.
|
|
* This allows for many items to be added and removed efficiently.
|
|
* This function can be safely called multiple times as long as there
|
|
* are corresponding calls to {@link EntityCollection#resumeEvents}.
|
|
*/
|
|
EntityCollection.prototype.suspendEvents = function () {
|
|
this._suspendCount++;
|
|
};
|
|
|
|
/**
|
|
* Resumes raising {@link EntityCollection#collectionChanged} events immediately
|
|
* when an item is added or removed. Any modifications made while while events were suspended
|
|
* will be triggered as a single event when this function is called.
|
|
* This function is reference counted and can safely be called multiple times as long as there
|
|
* are corresponding calls to {@link EntityCollection#resumeEvents}.
|
|
*
|
|
* @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
|
|
*/
|
|
EntityCollection.prototype.resumeEvents = function () {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (this._suspendCount === 0) {
|
|
throw new DeveloperError(
|
|
"resumeEvents can not be called before suspendEvents."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
this._suspendCount--;
|
|
fireChangedEvent(this);
|
|
};
|
|
|
|
/**
|
|
* The signature of the event generated by {@link EntityCollection#collectionChanged}.
|
|
* @function
|
|
*
|
|
* @param {EntityCollection} collection The collection that triggered the event.
|
|
* @param {Entity[]} added The array of {@link Entity} instances that have been added to the collection.
|
|
* @param {Entity[]} removed The array of {@link Entity} instances that have been removed from the collection.
|
|
* @param {Entity[]} changed The array of {@link Entity} instances that have been modified.
|
|
*/
|
|
EntityCollection.collectionChangedEventCallback = undefined;
|
|
|
|
Object.defineProperties(EntityCollection.prototype, {
|
|
/**
|
|
* Gets the event that is fired when entities are added or removed from the collection.
|
|
* The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
|
|
* @memberof EntityCollection.prototype
|
|
* @readonly
|
|
* @type {Event}
|
|
*/
|
|
collectionChanged: {
|
|
get: function () {
|
|
return this._collectionChanged;
|
|
},
|
|
},
|
|
/**
|
|
* Gets a globally unique identifier for this collection.
|
|
* @memberof EntityCollection.prototype
|
|
* @readonly
|
|
* @type {String}
|
|
*/
|
|
id: {
|
|
get: function () {
|
|
return this._id;
|
|
},
|
|
},
|
|
/**
|
|
* Gets the array of Entity instances in the collection.
|
|
* This array should not be modified directly.
|
|
* @memberof EntityCollection.prototype
|
|
* @readonly
|
|
* @type {Entity[]}
|
|
*/
|
|
values: {
|
|
get: function () {
|
|
return this._entities.values;
|
|
},
|
|
},
|
|
/**
|
|
* Gets whether or not this entity collection should be
|
|
* displayed. When true, each entity is only displayed if
|
|
* its own show property is also true.
|
|
* @memberof EntityCollection.prototype
|
|
* @type {Boolean}
|
|
*/
|
|
show: {
|
|
get: function () {
|
|
return this._show;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(value)) {
|
|
throw new DeveloperError("value is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
if (value === this._show) {
|
|
return;
|
|
}
|
|
|
|
//Since entity.isShowing includes the EntityCollection.show state
|
|
//in its calculation, we need to loop over the entities array
|
|
//twice, once to get the old showing value and a second time
|
|
//to raise the changed event.
|
|
this.suspendEvents();
|
|
|
|
var i;
|
|
var oldShows = [];
|
|
var entities = this._entities.values;
|
|
var entitiesLength = entities.length;
|
|
|
|
for (i = 0; i < entitiesLength; i++) {
|
|
oldShows.push(entities[i].isShowing);
|
|
}
|
|
|
|
this._show = value;
|
|
|
|
for (i = 0; i < entitiesLength; i++) {
|
|
var oldShow = oldShows[i];
|
|
var entity = entities[i];
|
|
if (oldShow !== entity.isShowing) {
|
|
entity.definitionChanged.raiseEvent(
|
|
entity,
|
|
"isShowing",
|
|
entity.isShowing,
|
|
oldShow
|
|
);
|
|
}
|
|
}
|
|
|
|
this.resumeEvents();
|
|
},
|
|
},
|
|
/**
|
|
* Gets the owner of this entity collection, ie. the data source or composite entity collection which created it.
|
|
* @memberof EntityCollection.prototype
|
|
* @readonly
|
|
* @type {DataSource|CompositeEntityCollection}
|
|
*/
|
|
owner: {
|
|
get: function () {
|
|
return this._owner;
|
|
},
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Computes the maximum availability of the entities in the collection.
|
|
* If the collection contains a mix of infinitely available data and non-infinite data,
|
|
* it will return the interval pertaining to the non-infinite data only. If all
|
|
* data is infinite, an infinite interval will be returned.
|
|
*
|
|
* @returns {TimeInterval} The availability of entities in the collection.
|
|
*/
|
|
EntityCollection.prototype.computeAvailability = function () {
|
|
var startTime = Iso8601.MAXIMUM_VALUE;
|
|
var stopTime = Iso8601.MINIMUM_VALUE;
|
|
var entities = this._entities.values;
|
|
for (var i = 0, len = entities.length; i < len; i++) {
|
|
var entity = entities[i];
|
|
var availability = entity.availability;
|
|
if (defined(availability)) {
|
|
var start = availability.start;
|
|
var stop = availability.stop;
|
|
if (
|
|
JulianDate.lessThan(start, startTime) &&
|
|
!start.equals(Iso8601.MINIMUM_VALUE)
|
|
) {
|
|
startTime = start;
|
|
}
|
|
if (
|
|
JulianDate.greaterThan(stop, stopTime) &&
|
|
!stop.equals(Iso8601.MAXIMUM_VALUE)
|
|
) {
|
|
stopTime = stop;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Iso8601.MAXIMUM_VALUE.equals(startTime)) {
|
|
startTime = Iso8601.MINIMUM_VALUE;
|
|
}
|
|
if (Iso8601.MINIMUM_VALUE.equals(stopTime)) {
|
|
stopTime = Iso8601.MAXIMUM_VALUE;
|
|
}
|
|
return new TimeInterval({
|
|
start: startTime,
|
|
stop: stopTime,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Add an entity to the collection.
|
|
*
|
|
* @param {Entity | Entity.ConstructorOptions} entity The entity to be added.
|
|
* @returns {Entity} The entity that was added.
|
|
* @exception {DeveloperError} An entity with <entity.id> already exists in this collection.
|
|
*/
|
|
EntityCollection.prototype.add = function (entity) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(entity)) {
|
|
throw new DeveloperError("entity is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
if (!(entity instanceof Entity)) {
|
|
entity = new Entity(entity);
|
|
}
|
|
|
|
var id = entity.id;
|
|
var entities = this._entities;
|
|
if (entities.contains(id)) {
|
|
throw new RuntimeError(
|
|
"An entity with id " + id + " already exists in this collection."
|
|
);
|
|
}
|
|
|
|
entity.entityCollection = this;
|
|
entities.set(id, entity);
|
|
|
|
if (!this._removedEntities.remove(id)) {
|
|
this._addedEntities.set(id, entity);
|
|
}
|
|
entity.definitionChanged.addEventListener(
|
|
EntityCollection.prototype._onEntityDefinitionChanged,
|
|
this
|
|
);
|
|
|
|
fireChangedEvent(this);
|
|
return entity;
|
|
};
|
|
|
|
/**
|
|
* Removes an entity from the collection.
|
|
*
|
|
* @param {Entity} entity The entity to be removed.
|
|
* @returns {Boolean} true if the item was removed, false if it did not exist in the collection.
|
|
*/
|
|
EntityCollection.prototype.remove = function (entity) {
|
|
if (!defined(entity)) {
|
|
return false;
|
|
}
|
|
return this.removeById(entity.id);
|
|
};
|
|
|
|
/**
|
|
* Returns true if the provided entity is in this collection, false otherwise.
|
|
*
|
|
* @param {Entity} entity The entity.
|
|
* @returns {Boolean} true if the provided entity is in this collection, false otherwise.
|
|
*/
|
|
EntityCollection.prototype.contains = function (entity) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(entity)) {
|
|
throw new DeveloperError("entity is required");
|
|
}
|
|
//>>includeEnd('debug');
|
|
return this._entities.get(entity.id) === entity;
|
|
};
|
|
|
|
/**
|
|
* Removes an entity with the provided id from the collection.
|
|
*
|
|
* @param {String} id The id of the entity to remove.
|
|
* @returns {Boolean} true if the item was removed, false if no item with the provided id existed in the collection.
|
|
*/
|
|
EntityCollection.prototype.removeById = function (id) {
|
|
if (!defined(id)) {
|
|
return false;
|
|
}
|
|
|
|
var entities = this._entities;
|
|
var entity = entities.get(id);
|
|
if (!this._entities.remove(id)) {
|
|
return false;
|
|
}
|
|
|
|
if (!this._addedEntities.remove(id)) {
|
|
this._removedEntities.set(id, entity);
|
|
this._changedEntities.remove(id);
|
|
}
|
|
this._entities.remove(id);
|
|
entity.definitionChanged.removeEventListener(
|
|
EntityCollection.prototype._onEntityDefinitionChanged,
|
|
this
|
|
);
|
|
fireChangedEvent(this);
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Removes all Entities from the collection.
|
|
*/
|
|
EntityCollection.prototype.removeAll = function () {
|
|
//The event should only contain items added before events were suspended
|
|
//and the contents of the collection.
|
|
var entities = this._entities;
|
|
var entitiesLength = entities.length;
|
|
var array = entities.values;
|
|
|
|
var addedEntities = this._addedEntities;
|
|
var removed = this._removedEntities;
|
|
|
|
for (var i = 0; i < entitiesLength; i++) {
|
|
var existingItem = array[i];
|
|
var existingItemId = existingItem.id;
|
|
var addedItem = addedEntities.get(existingItemId);
|
|
if (!defined(addedItem)) {
|
|
existingItem.definitionChanged.removeEventListener(
|
|
EntityCollection.prototype._onEntityDefinitionChanged,
|
|
this
|
|
);
|
|
removed.set(existingItemId, existingItem);
|
|
}
|
|
}
|
|
|
|
entities.removeAll();
|
|
addedEntities.removeAll();
|
|
this._changedEntities.removeAll();
|
|
fireChangedEvent(this);
|
|
};
|
|
|
|
/**
|
|
* Gets an entity with the specified id.
|
|
*
|
|
* @param {String} id The id of the entity to retrieve.
|
|
* @returns {Entity|undefined} The entity with the provided id or undefined if the id did not exist in the collection.
|
|
*/
|
|
EntityCollection.prototype.getById = function (id) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(id)) {
|
|
throw new DeveloperError("id is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
return this._entities.get(id);
|
|
};
|
|
|
|
/**
|
|
* Gets an entity with the specified id or creates it and adds it to the collection if it does not exist.
|
|
*
|
|
* @param {String} id The id of the entity to retrieve or create.
|
|
* @returns {Entity} The new or existing object.
|
|
*/
|
|
EntityCollection.prototype.getOrCreateEntity = function (id) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(id)) {
|
|
throw new DeveloperError("id is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var entity = this._entities.get(id);
|
|
if (!defined(entity)) {
|
|
entityOptionsScratch.id = id;
|
|
entity = new Entity(entityOptionsScratch);
|
|
this.add(entity);
|
|
}
|
|
return entity;
|
|
};
|
|
|
|
EntityCollection.prototype._onEntityDefinitionChanged = function (entity) {
|
|
var id = entity.id;
|
|
if (!this._addedEntities.contains(id)) {
|
|
this._changedEntities.set(id, entity);
|
|
}
|
|
fireChangedEvent(this);
|
|
};
|
|
export default EntityCollection;
|