| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 | import createGuid from "../Core/createGuid.js";import defined from "../Core/defined.js";import DeveloperError from "../Core/DeveloperError.js";import CesiumMath from "../Core/Math.js";import Entity from "./Entity.js";import EntityCollection from "./EntityCollection.js";const entityOptionsScratch = {  id: undefined,};const entityIdScratch = new Array(2);function clean(entity) {  const propertyNames = entity.propertyNames;  const propertyNamesLength = propertyNames.length;  for (let i = 0; i < propertyNamesLength; i++) {    entity[propertyNames[i]] = undefined;  }  entity._name = undefined;  entity._availability = undefined;}function subscribeToEntity(that, eventHash, collectionId, entity) {  entityIdScratch[0] = collectionId;  entityIdScratch[1] = entity.id;  eventHash[    JSON.stringify(entityIdScratch)  ] = entity.definitionChanged.addEventListener(    CompositeEntityCollection.prototype._onDefinitionChanged,    that  );}function unsubscribeFromEntity(that, eventHash, collectionId, entity) {  entityIdScratch[0] = collectionId;  entityIdScratch[1] = entity.id;  const id = JSON.stringify(entityIdScratch);  eventHash[id]();  eventHash[id] = undefined;}function recomposite(that) {  that._shouldRecomposite = true;  if (that._suspendCount !== 0) {    return;  }  const collections = that._collections;  const collectionsLength = collections.length;  const collectionsCopy = that._collectionsCopy;  const collectionsCopyLength = collectionsCopy.length;  let i;  let entity;  let entities;  let iEntities;  let collection;  const composite = that._composite;  const newEntities = new EntityCollection(that);  const eventHash = that._eventHash;  let collectionId;  for (i = 0; i < collectionsCopyLength; i++) {    collection = collectionsCopy[i];    collection.collectionChanged.removeEventListener(      CompositeEntityCollection.prototype._onCollectionChanged,      that    );    entities = collection.values;    collectionId = collection.id;    for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {      entity = entities[iEntities];      unsubscribeFromEntity(that, eventHash, collectionId, entity);    }  }  for (i = collectionsLength - 1; i >= 0; i--) {    collection = collections[i];    collection.collectionChanged.addEventListener(      CompositeEntityCollection.prototype._onCollectionChanged,      that    );    //Merge all of the existing entities.    entities = collection.values;    collectionId = collection.id;    for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {      entity = entities[iEntities];      subscribeToEntity(that, eventHash, collectionId, entity);      let compositeEntity = newEntities.getById(entity.id);      if (!defined(compositeEntity)) {        compositeEntity = composite.getById(entity.id);        if (!defined(compositeEntity)) {          entityOptionsScratch.id = entity.id;          compositeEntity = new Entity(entityOptionsScratch);        } else {          clean(compositeEntity);        }        newEntities.add(compositeEntity);      }      compositeEntity.merge(entity);    }  }  that._collectionsCopy = collections.slice(0);  composite.suspendEvents();  composite.removeAll();  const newEntitiesArray = newEntities.values;  for (i = 0; i < newEntitiesArray.length; i++) {    composite.add(newEntitiesArray[i]);  }  composite.resumeEvents();}/** * Non-destructively composites multiple {@link EntityCollection} instances into a single collection. * If a Entity with the same ID exists in multiple collections, it is non-destructively * merged into a single new entity instance.  If an entity has the same property in multiple * collections, the property of the Entity in the last collection of the list it * belongs to is used.  CompositeEntityCollection can be used almost anywhere that a * EntityCollection is used. * * @alias CompositeEntityCollection * @constructor * * @param {EntityCollection[]} [collections] The initial list of EntityCollection instances to merge. * @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection. */function CompositeEntityCollection(collections, owner) {  this._owner = owner;  this._composite = new EntityCollection(this);  this._suspendCount = 0;  this._collections = defined(collections) ? collections.slice() : [];  this._collectionsCopy = [];  this._id = createGuid();  this._eventHash = {};  recomposite(this);  this._shouldRecomposite = false;}Object.defineProperties(CompositeEntityCollection.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 CompositeEntityCollection.prototype   * @readonly   * @type {Event}   */  collectionChanged: {    get: function () {      return this._composite._collectionChanged;    },  },  /**   * Gets a globally unique identifier for this collection.   * @memberof CompositeEntityCollection.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 CompositeEntityCollection.prototype   * @readonly   * @type {Entity[]}   */  values: {    get: function () {      return this._composite.values;    },  },  /**   * Gets the owner of this composite entity collection, ie. the data source or composite entity collection which created it.   * @memberof CompositeEntityCollection.prototype   * @readonly   * @type {DataSource|CompositeEntityCollection}   */  owner: {    get: function () {      return this._owner;    },  },});/** * Adds a collection to the composite. * * @param {EntityCollection} collection the collection to add. * @param {Number} [index] the index to add the collection at.  If omitted, the collection will *                         added on top of all existing collections. * * @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of collections. */CompositeEntityCollection.prototype.addCollection = function (  collection,  index) {  const hasIndex = defined(index);  //>>includeStart('debug', pragmas.debug);  if (!defined(collection)) {    throw new DeveloperError("collection is required.");  }  if (hasIndex) {    if (index < 0) {      throw new DeveloperError("index must be greater than or equal to zero.");    } else if (index > this._collections.length) {      throw new DeveloperError(        "index must be less than or equal to the number of collections."      );    }  }  //>>includeEnd('debug');  if (!hasIndex) {    index = this._collections.length;    this._collections.push(collection);  } else {    this._collections.splice(index, 0, collection);  }  recomposite(this);};/** * Removes a collection from this composite, if present. * * @param {EntityCollection} collection The collection to remove. * @returns {Boolean} true if the collection was in the composite and was removed, *                    false if the collection was not in the composite. */CompositeEntityCollection.prototype.removeCollection = function (collection) {  const index = this._collections.indexOf(collection);  if (index !== -1) {    this._collections.splice(index, 1);    recomposite(this);    return true;  }  return false;};/** * Removes all collections from this composite. */CompositeEntityCollection.prototype.removeAllCollections = function () {  this._collections.length = 0;  recomposite(this);};/** * Checks to see if the composite contains a given collection. * * @param {EntityCollection} collection the collection to check for. * @returns {Boolean} true if the composite contains the collection, false otherwise. */CompositeEntityCollection.prototype.containsCollection = function (collection) {  return this._collections.indexOf(collection) !== -1;};/** * 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. */CompositeEntityCollection.prototype.contains = function (entity) {  return this._composite.contains(entity);};/** * Determines the index of a given collection in the composite. * * @param {EntityCollection} collection The collection to find the index of. * @returns {Number} The index of the collection in the composite, or -1 if the collection does not exist in the composite. */CompositeEntityCollection.prototype.indexOfCollection = function (collection) {  return this._collections.indexOf(collection);};/** * Gets a collection by index from the composite. * * @param {Number} index the index to retrieve. */CompositeEntityCollection.prototype.getCollection = function (index) {  //>>includeStart('debug', pragmas.debug);  if (!defined(index)) {    throw new DeveloperError("index is required.", "index");  }  //>>includeEnd('debug');  return this._collections[index];};/** * Gets the number of collections in this composite. */CompositeEntityCollection.prototype.getCollectionsLength = function () {  return this._collections.length;};function getCollectionIndex(collections, collection) {  //>>includeStart('debug', pragmas.debug);  if (!defined(collection)) {    throw new DeveloperError("collection is required.");  }  //>>includeEnd('debug');  const index = collections.indexOf(collection);  //>>includeStart('debug', pragmas.debug);  if (index === -1) {    throw new DeveloperError("collection is not in this composite.");  }  //>>includeEnd('debug');  return index;}function swapCollections(composite, i, j) {  const arr = composite._collections;  i = CesiumMath.clamp(i, 0, arr.length - 1);  j = CesiumMath.clamp(j, 0, arr.length - 1);  if (i === j) {    return;  }  const temp = arr[i];  arr[i] = arr[j];  arr[j] = temp;  recomposite(composite);}/** * Raises a collection up one position in the composite. * * @param {EntityCollection} collection the collection to move. * * @exception {DeveloperError} collection is not in this composite. */CompositeEntityCollection.prototype.raiseCollection = function (collection) {  const index = getCollectionIndex(this._collections, collection);  swapCollections(this, index, index + 1);};/** * Lowers a collection down one position in the composite. * * @param {EntityCollection} collection the collection to move. * * @exception {DeveloperError} collection is not in this composite. */CompositeEntityCollection.prototype.lowerCollection = function (collection) {  const index = getCollectionIndex(this._collections, collection);  swapCollections(this, index, index - 1);};/** * Raises a collection to the top of the composite. * * @param {EntityCollection} collection the collection to move. * * @exception {DeveloperError} collection is not in this composite. */CompositeEntityCollection.prototype.raiseCollectionToTop = function (  collection) {  const index = getCollectionIndex(this._collections, collection);  if (index === this._collections.length - 1) {    return;  }  this._collections.splice(index, 1);  this._collections.push(collection);  recomposite(this);};/** * Lowers a collection to the bottom of the composite. * * @param {EntityCollection} collection the collection to move. * * @exception {DeveloperError} collection is not in this composite. */CompositeEntityCollection.prototype.lowerCollectionToBottom = function (  collection) {  const index = getCollectionIndex(this._collections, collection);  if (index === 0) {    return;  }  this._collections.splice(index, 1);  this._collections.splice(0, 0, collection);  recomposite(this);};/** * 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. * While events are suspended, recompositing of the collections will * also be suspended, as this can be a costly operation. * This function can be safely called multiple times as long as there * are corresponding calls to {@link EntityCollection#resumeEvents}. */CompositeEntityCollection.prototype.suspendEvents = function () {  this._suspendCount++;  this._composite.suspendEvents();};/** * 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 also ensures * the collection is recomposited if events are also resumed. * 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. */CompositeEntityCollection.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--;  // recomposite before triggering events (but only if required for performance) that might depend on a composited collection  if (this._shouldRecomposite && this._suspendCount === 0) {    recomposite(this);    this._shouldRecomposite = false;  }  this._composite.resumeEvents();};/** * 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. */CompositeEntityCollection.prototype.computeAvailability = function () {  return this._composite.computeAvailability();};/** * 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. */CompositeEntityCollection.prototype.getById = function (id) {  return this._composite.getById(id);};CompositeEntityCollection.prototype._onCollectionChanged = function (  collection,  added,  removed) {  const collections = this._collectionsCopy;  const collectionsLength = collections.length;  const composite = this._composite;  composite.suspendEvents();  let i;  let q;  let entity;  let compositeEntity;  const removedLength = removed.length;  const eventHash = this._eventHash;  const collectionId = collection.id;  for (i = 0; i < removedLength; i++) {    const removedEntity = removed[i];    unsubscribeFromEntity(this, eventHash, collectionId, removedEntity);    const removedId = removedEntity.id;    //Check if the removed entity exists in any of the remaining collections    //If so, we clean and remerge it.    for (q = collectionsLength - 1; q >= 0; q--) {      entity = collections[q].getById(removedId);      if (defined(entity)) {        if (!defined(compositeEntity)) {          compositeEntity = composite.getById(removedId);          clean(compositeEntity);        }        compositeEntity.merge(entity);      }    }    //We never retrieved the compositeEntity, which means it no longer    //exists in any of the collections, remove it from the composite.    if (!defined(compositeEntity)) {      composite.removeById(removedId);    }    compositeEntity = undefined;  }  const addedLength = added.length;  for (i = 0; i < addedLength; i++) {    const addedEntity = added[i];    subscribeToEntity(this, eventHash, collectionId, addedEntity);    const addedId = addedEntity.id;    //We know the added entity exists in at least one collection,    //but we need to check all collections and re-merge in order    //to maintain the priority of properties.    for (q = collectionsLength - 1; q >= 0; q--) {      entity = collections[q].getById(addedId);      if (defined(entity)) {        if (!defined(compositeEntity)) {          compositeEntity = composite.getById(addedId);          if (!defined(compositeEntity)) {            entityOptionsScratch.id = addedId;            compositeEntity = new Entity(entityOptionsScratch);            composite.add(compositeEntity);          } else {            clean(compositeEntity);          }        }        compositeEntity.merge(entity);      }    }    compositeEntity = undefined;  }  composite.resumeEvents();};CompositeEntityCollection.prototype._onDefinitionChanged = function (  entity,  propertyName,  newValue,  oldValue) {  const collections = this._collections;  const composite = this._composite;  const collectionsLength = collections.length;  const id = entity.id;  const compositeEntity = composite.getById(id);  let compositeProperty = compositeEntity[propertyName];  const newProperty = !defined(compositeProperty);  let firstTime = true;  for (let q = collectionsLength - 1; q >= 0; q--) {    const innerEntity = collections[q].getById(entity.id);    if (defined(innerEntity)) {      const property = innerEntity[propertyName];      if (defined(property)) {        if (firstTime) {          firstTime = false;          //We only want to clone if the property is also mergeable.          //This ensures that leaf properties are referenced and not copied,          //which is the entire point of compositing.          if (defined(property.merge) && defined(property.clone)) {            compositeProperty = property.clone(compositeProperty);          } else {            compositeProperty = property;            break;          }        }        compositeProperty.merge(property);      }    }  }  if (    newProperty &&    compositeEntity.propertyNames.indexOf(propertyName) === -1  ) {    compositeEntity.addProperty(propertyName);  }  compositeEntity[propertyName] = compositeProperty;};export default CompositeEntityCollection;
 |