| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 | import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import DeveloperError from "../Core/DeveloperError.js";import Event from "../Core/Event.js";import CesiumMath from "../Core/Math.js";import Rectangle from "../Core/Rectangle.js";import ImageryLayer from "./ImageryLayer.js";/** * An ordered collection of imagery layers. * * @alias ImageryLayerCollection * @constructor * * @demo {@link https://sandcastle.cesium.com/index.html?src=Imagery%20Adjustment.html|Cesium Sandcastle Imagery Adjustment Demo} * @demo {@link https://sandcastle.cesium.com/index.html?src=Imagery%20Layers%20Manipulation.html|Cesium Sandcastle Imagery Manipulation Demo} */function ImageryLayerCollection() {  this._layers = [];  /**   * An event that is raised when a layer is added to the collection.  Event handlers are passed the layer that   * was added and the index at which it was added.   * @type {Event}   * @default Event()   */  this.layerAdded = new Event();  /**   * An event that is raised when a layer is removed from the collection.  Event handlers are passed the layer that   * was removed and the index from which it was removed.   * @type {Event}   * @default Event()   */  this.layerRemoved = new Event();  /**   * An event that is raised when a layer changes position in the collection.  Event handlers are passed the layer that   * was moved, its new index after the move, and its old index prior to the move.   * @type {Event}   * @default Event()   */  this.layerMoved = new Event();  /**   * An event that is raised when a layer is shown or hidden by setting the   * {@link ImageryLayer#show} property.  Event handlers are passed a reference to this layer,   * the index of the layer in the collection, and a flag that is true if the layer is now   * shown or false if it is now hidden.   *   * @type {Event}   * @default Event()   */  this.layerShownOrHidden = new Event();}Object.defineProperties(ImageryLayerCollection.prototype, {  /**   * Gets the number of layers in this collection.   * @memberof ImageryLayerCollection.prototype   * @type {Number}   */  length: {    get: function () {      return this._layers.length;    },  },});/** * Adds a layer to the collection. * * @param {ImageryLayer} layer the layer to add. * @param {Number} [index] the index to add the layer at.  If omitted, the layer will *                         be added on top of all existing layers. * * @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of the layers. */ImageryLayerCollection.prototype.add = function (layer, index) {  const hasIndex = defined(index);  //>>includeStart('debug', pragmas.debug);  if (!defined(layer)) {    throw new DeveloperError("layer is required.");  }  if (hasIndex) {    if (index < 0) {      throw new DeveloperError("index must be greater than or equal to zero.");    } else if (index > this._layers.length) {      throw new DeveloperError(        "index must be less than or equal to the number of layers."      );    }  }  //>>includeEnd('debug');  if (!hasIndex) {    index = this._layers.length;    this._layers.push(layer);  } else {    this._layers.splice(index, 0, layer);  }  this._update();  this.layerAdded.raiseEvent(layer, index);};/** * Creates a new layer using the given ImageryProvider and adds it to the collection. * * @param {ImageryProvider} imageryProvider the imagery provider to create a new layer for. * @param {Number} [index] the index to add the layer at.  If omitted, the layer will *                         added on top of all existing layers. * @returns {ImageryLayer} The newly created layer. */ImageryLayerCollection.prototype.addImageryProvider = function (  imageryProvider,  index) {  //>>includeStart('debug', pragmas.debug);  if (!defined(imageryProvider)) {    throw new DeveloperError("imageryProvider is required.");  }  //>>includeEnd('debug');  const layer = new ImageryLayer(imageryProvider);  this.add(layer, index);  return layer;};/** * Removes a layer from this collection, if present. * * @param {ImageryLayer} layer The layer to remove. * @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them. * @returns {Boolean} true if the layer was in the collection and was removed, *                    false if the layer was not in the collection. */ImageryLayerCollection.prototype.remove = function (layer, destroy) {  destroy = defaultValue(destroy, true);  const index = this._layers.indexOf(layer);  if (index !== -1) {    this._layers.splice(index, 1);    this._update();    this.layerRemoved.raiseEvent(layer, index);    if (destroy) {      layer.destroy();    }    return true;  }  return false;};/** * Removes all layers from this collection. * * @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them. */ImageryLayerCollection.prototype.removeAll = function (destroy) {  destroy = defaultValue(destroy, true);  const layers = this._layers;  for (let i = 0, len = layers.length; i < len; i++) {    const layer = layers[i];    this.layerRemoved.raiseEvent(layer, i);    if (destroy) {      layer.destroy();    }  }  this._layers = [];};/** * Checks to see if the collection contains a given layer. * * @param {ImageryLayer} layer the layer to check for. * * @returns {Boolean} true if the collection contains the layer, false otherwise. */ImageryLayerCollection.prototype.contains = function (layer) {  return this.indexOf(layer) !== -1;};/** * Determines the index of a given layer in the collection. * * @param {ImageryLayer} layer The layer to find the index of. * * @returns {Number} The index of the layer in the collection, or -1 if the layer does not exist in the collection. */ImageryLayerCollection.prototype.indexOf = function (layer) {  return this._layers.indexOf(layer);};/** * Gets a layer by index from the collection. * * @param {Number} index the index to retrieve. * * @returns {ImageryLayer} The imagery layer at the given index. */ImageryLayerCollection.prototype.get = function (index) {  //>>includeStart('debug', pragmas.debug);  if (!defined(index)) {    throw new DeveloperError("index is required.", "index");  }  //>>includeEnd('debug');  return this._layers[index];};function getLayerIndex(layers, layer) {  //>>includeStart('debug', pragmas.debug);  if (!defined(layer)) {    throw new DeveloperError("layer is required.");  }  //>>includeEnd('debug');  const index = layers.indexOf(layer);  //>>includeStart('debug', pragmas.debug);  if (index === -1) {    throw new DeveloperError("layer is not in this collection.");  }  //>>includeEnd('debug');  return index;}function swapLayers(collection, i, j) {  const arr = collection._layers;  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;  collection._update();  collection.layerMoved.raiseEvent(temp, j, i);}/** * Raises a layer up one position in the collection. * * @param {ImageryLayer} layer the layer to move. * * @exception {DeveloperError} layer is not in this collection. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ImageryLayerCollection.prototype.raise = function (layer) {  const index = getLayerIndex(this._layers, layer);  swapLayers(this, index, index + 1);};/** * Lowers a layer down one position in the collection. * * @param {ImageryLayer} layer the layer to move. * * @exception {DeveloperError} layer is not in this collection. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ImageryLayerCollection.prototype.lower = function (layer) {  const index = getLayerIndex(this._layers, layer);  swapLayers(this, index, index - 1);};/** * Raises a layer to the top of the collection. * * @param {ImageryLayer} layer the layer to move. * * @exception {DeveloperError} layer is not in this collection. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ImageryLayerCollection.prototype.raiseToTop = function (layer) {  const index = getLayerIndex(this._layers, layer);  if (index === this._layers.length - 1) {    return;  }  this._layers.splice(index, 1);  this._layers.push(layer);  this._update();  this.layerMoved.raiseEvent(layer, this._layers.length - 1, index);};/** * Lowers a layer to the bottom of the collection. * * @param {ImageryLayer} layer the layer to move. * * @exception {DeveloperError} layer is not in this collection. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ImageryLayerCollection.prototype.lowerToBottom = function (layer) {  const index = getLayerIndex(this._layers, layer);  if (index === 0) {    return;  }  this._layers.splice(index, 1);  this._layers.splice(0, 0, layer);  this._update();  this.layerMoved.raiseEvent(layer, 0, index);};const applicableRectangleScratch = new Rectangle();function pickImageryHelper(scene, pickedLocation, pickFeatures, callback) {  // Find the terrain tile containing the picked location.  const tilesToRender = scene.globe._surface._tilesToRender;  let pickedTile;  for (    let textureIndex = 0;    !defined(pickedTile) && textureIndex < tilesToRender.length;    ++textureIndex  ) {    const tile = tilesToRender[textureIndex];    if (Rectangle.contains(tile.rectangle, pickedLocation)) {      pickedTile = tile;    }  }  if (!defined(pickedTile)) {    return;  }  // Pick against all attached imagery tiles containing the pickedLocation.  const imageryTiles = pickedTile.data.imagery;  for (let i = imageryTiles.length - 1; i >= 0; --i) {    const terrainImagery = imageryTiles[i];    const imagery = terrainImagery.readyImagery;    if (!defined(imagery)) {      continue;    }    const provider = imagery.imageryLayer.imageryProvider;    if (pickFeatures && !defined(provider.pickFeatures)) {      continue;    }    if (!Rectangle.contains(imagery.rectangle, pickedLocation)) {      continue;    }    // If this imagery came from a parent, it may not be applicable to its entire rectangle.    // Check the textureCoordinateRectangle.    const applicableRectangle = applicableRectangleScratch;    const epsilon = 1 / 1024; // 1/4 of a pixel in a typical 256x256 tile.    applicableRectangle.west = CesiumMath.lerp(      pickedTile.rectangle.west,      pickedTile.rectangle.east,      terrainImagery.textureCoordinateRectangle.x - epsilon    );    applicableRectangle.east = CesiumMath.lerp(      pickedTile.rectangle.west,      pickedTile.rectangle.east,      terrainImagery.textureCoordinateRectangle.z + epsilon    );    applicableRectangle.south = CesiumMath.lerp(      pickedTile.rectangle.south,      pickedTile.rectangle.north,      terrainImagery.textureCoordinateRectangle.y - epsilon    );    applicableRectangle.north = CesiumMath.lerp(      pickedTile.rectangle.south,      pickedTile.rectangle.north,      terrainImagery.textureCoordinateRectangle.w + epsilon    );    if (!Rectangle.contains(applicableRectangle, pickedLocation)) {      continue;    }    callback(imagery);  }}/** * Determines the imagery layers that are intersected by a pick ray. To compute a pick ray from a * location on the screen, use {@link Camera.getPickRay}. * * @param {Ray} ray The ray to test for intersection. * @param {Scene} scene The scene. * @return {ImageryLayer[]|undefined} An array that includes all of *                                 the layers that are intersected by a given pick ray. Undefined if *                                 no layers are selected. * */ImageryLayerCollection.prototype.pickImageryLayers = function (ray, scene) {  // Find the picked location on the globe.  const pickedPosition = scene.globe.pick(ray, scene);  if (!defined(pickedPosition)) {    return;  }  const pickedLocation = scene.globe.ellipsoid.cartesianToCartographic(    pickedPosition  );  const imageryLayers = [];  pickImageryHelper(scene, pickedLocation, false, function (imagery) {    imageryLayers.push(imagery.imageryLayer);  });  if (imageryLayers.length === 0) {    return undefined;  }  return imageryLayers;};/** * Asynchronously determines the imagery layer features that are intersected by a pick ray.  The intersected imagery * layer features are found by invoking {@link ImageryProvider#pickFeatures} for each imagery layer tile intersected * by the pick ray.  To compute a pick ray from a location on the screen, use {@link Camera.getPickRay}. * * @param {Ray} ray The ray to test for intersection. * @param {Scene} scene The scene. * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise that resolves to an array of features intersected by the pick ray. *                                             If it can be quickly determined that no features are intersected (for example, *                                             because no active imagery providers support {@link ImageryProvider#pickFeatures} *                                             or because the pick ray does not intersect the surface), this function will *                                             return undefined. * * @example * const pickRay = viewer.camera.getPickRay(windowPosition); * const featuresPromise = viewer.imageryLayers.pickImageryLayerFeatures(pickRay, viewer.scene); * if (!Cesium.defined(featuresPromise)) { *     console.log('No features picked.'); * } else { *     Promise.resolve(featuresPromise).then(function(features) { *         // This function is called asynchronously when the list if picked features is available. *         console.log('Number of features: ' + features.length); *         if (features.length > 0) { *             console.log('First feature name: ' + features[0].name); *         } *     }); * } */ImageryLayerCollection.prototype.pickImageryLayerFeatures = function (  ray,  scene) {  // Find the picked location on the globe.  const pickedPosition = scene.globe.pick(ray, scene);  if (!defined(pickedPosition)) {    return;  }  const pickedLocation = scene.globe.ellipsoid.cartesianToCartographic(    pickedPosition  );  const promises = [];  const imageryLayers = [];  pickImageryHelper(scene, pickedLocation, true, function (imagery) {    const provider = imagery.imageryLayer.imageryProvider;    const promise = provider.pickFeatures(      imagery.x,      imagery.y,      imagery.level,      pickedLocation.longitude,      pickedLocation.latitude    );    if (defined(promise)) {      promises.push(promise);      imageryLayers.push(imagery.imageryLayer);    }  });  if (promises.length === 0) {    return undefined;  }  return Promise.all(promises).then(function (results) {    const features = [];    for (let resultIndex = 0; resultIndex < results.length; ++resultIndex) {      const result = results[resultIndex];      const image = imageryLayers[resultIndex];      if (defined(result) && result.length > 0) {        for (          let featureIndex = 0;          featureIndex < result.length;          ++featureIndex        ) {          const feature = result[featureIndex];          feature.imageryLayer = image;          // For features without a position, use the picked location.          if (!defined(feature.position)) {            feature.position = pickedLocation;          }          features.push(feature);        }      }    }    return features;  });};/** * Updates frame state to execute any queued texture re-projections. * * @private * * @param {FrameState} frameState The frameState. */ImageryLayerCollection.prototype.queueReprojectionCommands = function (  frameState) {  const layers = this._layers;  for (let i = 0, len = layers.length; i < len; ++i) {    layers[i].queueReprojectionCommands(frameState);  }};/** * Cancels re-projection commands queued for the next frame. * * @private */ImageryLayerCollection.prototype.cancelReprojections = function () {  const layers = this._layers;  for (let i = 0, len = layers.length; i < len; ++i) {    layers[i].cancelReprojections();  }};/** * Returns true if this object was destroyed; otherwise, false. * <br /><br /> * If this object was destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. * * @returns {Boolean} true if this object was destroyed; otherwise, false. * * @see ImageryLayerCollection#destroy */ImageryLayerCollection.prototype.isDestroyed = function () {  return false;};/** * Destroys the WebGL resources held by all layers in this collection.  Explicitly destroying this * object allows for deterministic release of WebGL resources, instead of relying on the garbage * collector. * <br /><br /> * Once this object is destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore, * assign the return value (<code>undefined</code>) to the object as done in the example. * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * * @example * layerCollection = layerCollection && layerCollection.destroy(); * * @see ImageryLayerCollection#isDestroyed */ImageryLayerCollection.prototype.destroy = function () {  this.removeAll(true);  return destroyObject(this);};ImageryLayerCollection.prototype._update = function () {  let isBaseLayer = true;  const layers = this._layers;  let layersShownOrHidden;  let layer;  let i, len;  for (i = 0, len = layers.length; i < len; ++i) {    layer = layers[i];    layer._layerIndex = i;    if (layer.show) {      layer._isBaseLayer = isBaseLayer;      isBaseLayer = false;    } else {      layer._isBaseLayer = false;    }    if (layer.show !== layer._show) {      if (defined(layer._show)) {        if (!defined(layersShownOrHidden)) {          layersShownOrHidden = [];        }        layersShownOrHidden.push(layer);      }      layer._show = layer.show;    }  }  if (defined(layersShownOrHidden)) {    for (i = 0, len = layersShownOrHidden.length; i < len; ++i) {      layer = layersShownOrHidden[i];      this.layerShownOrHidden.raiseEvent(layer, layer._layerIndex, layer.show);    }  }};export default ImageryLayerCollection;
 |