ImageryLayerCollection.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. import defaultValue from "../Core/defaultValue.js";
  2. import defined from "../Core/defined.js";
  3. import destroyObject from "../Core/destroyObject.js";
  4. import DeveloperError from "../Core/DeveloperError.js";
  5. import Event from "../Core/Event.js";
  6. import CesiumMath from "../Core/Math.js";
  7. import Rectangle from "../Core/Rectangle.js";
  8. import when from "../ThirdParty/when.js";
  9. import ImageryLayer from "./ImageryLayer.js";
  10. /**
  11. * An ordered collection of imagery layers.
  12. *
  13. * @alias ImageryLayerCollection
  14. * @constructor
  15. *
  16. * @demo {@link https://sandcastle.cesium.com/index.html?src=Imagery%20Adjustment.html|Cesium Sandcastle Imagery Adjustment Demo}
  17. * @demo {@link https://sandcastle.cesium.com/index.html?src=Imagery%20Layers%20Manipulation.html|Cesium Sandcastle Imagery Manipulation Demo}
  18. */
  19. function ImageryLayerCollection() {
  20. this._layers = [];
  21. /**
  22. * An event that is raised when a layer is added to the collection. Event handlers are passed the layer that
  23. * was added and the index at which it was added.
  24. * @type {Event}
  25. * @default Event()
  26. */
  27. this.layerAdded = new Event();
  28. /**
  29. * An event that is raised when a layer is removed from the collection. Event handlers are passed the layer that
  30. * was removed and the index from which it was removed.
  31. * @type {Event}
  32. * @default Event()
  33. */
  34. this.layerRemoved = new Event();
  35. /**
  36. * An event that is raised when a layer changes position in the collection. Event handlers are passed the layer that
  37. * was moved, its new index after the move, and its old index prior to the move.
  38. * @type {Event}
  39. * @default Event()
  40. */
  41. this.layerMoved = new Event();
  42. /**
  43. * An event that is raised when a layer is shown or hidden by setting the
  44. * {@link ImageryLayer#show} property. Event handlers are passed a reference to this layer,
  45. * the index of the layer in the collection, and a flag that is true if the layer is now
  46. * shown or false if it is now hidden.
  47. *
  48. * @type {Event}
  49. * @default Event()
  50. */
  51. this.layerShownOrHidden = new Event();
  52. }
  53. Object.defineProperties(ImageryLayerCollection.prototype, {
  54. /**
  55. * Gets the number of layers in this collection.
  56. * @memberof ImageryLayerCollection.prototype
  57. * @type {Number}
  58. */
  59. length: {
  60. get: function () {
  61. return this._layers.length;
  62. },
  63. },
  64. });
  65. /**
  66. * Adds a layer to the collection.
  67. *
  68. * @param {ImageryLayer} layer the layer to add.
  69. * @param {Number} [index] the index to add the layer at. If omitted, the layer will
  70. * be added on top of all existing layers.
  71. *
  72. * @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of the layers.
  73. */
  74. ImageryLayerCollection.prototype.add = function (layer, index) {
  75. var hasIndex = defined(index);
  76. //>>includeStart('debug', pragmas.debug);
  77. if (!defined(layer)) {
  78. throw new DeveloperError("layer is required.");
  79. }
  80. if (hasIndex) {
  81. if (index < 0) {
  82. throw new DeveloperError("index must be greater than or equal to zero.");
  83. } else if (index > this._layers.length) {
  84. throw new DeveloperError(
  85. "index must be less than or equal to the number of layers."
  86. );
  87. }
  88. }
  89. //>>includeEnd('debug');
  90. if (!hasIndex) {
  91. index = this._layers.length;
  92. this._layers.push(layer);
  93. } else {
  94. this._layers.splice(index, 0, layer);
  95. }
  96. this._update();
  97. this.layerAdded.raiseEvent(layer, index);
  98. };
  99. /**
  100. * Creates a new layer using the given ImageryProvider and adds it to the collection.
  101. *
  102. * @param {ImageryProvider} imageryProvider the imagery provider to create a new layer for.
  103. * @param {Number} [index] the index to add the layer at. If omitted, the layer will
  104. * added on top of all existing layers.
  105. * @returns {ImageryLayer} The newly created layer.
  106. */
  107. ImageryLayerCollection.prototype.addImageryProvider = function (
  108. imageryProvider,
  109. index
  110. ) {
  111. //>>includeStart('debug', pragmas.debug);
  112. if (!defined(imageryProvider)) {
  113. throw new DeveloperError("imageryProvider is required.");
  114. }
  115. //>>includeEnd('debug');
  116. var layer = new ImageryLayer(imageryProvider);
  117. this.add(layer, index);
  118. return layer;
  119. };
  120. /**
  121. * Removes a layer from this collection, if present.
  122. *
  123. * @param {ImageryLayer} layer The layer to remove.
  124. * @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them.
  125. * @returns {Boolean} true if the layer was in the collection and was removed,
  126. * false if the layer was not in the collection.
  127. */
  128. ImageryLayerCollection.prototype.remove = function (layer, destroy) {
  129. destroy = defaultValue(destroy, true);
  130. var index = this._layers.indexOf(layer);
  131. if (index !== -1) {
  132. this._layers.splice(index, 1);
  133. this._update();
  134. this.layerRemoved.raiseEvent(layer, index);
  135. if (destroy) {
  136. layer.destroy();
  137. }
  138. return true;
  139. }
  140. return false;
  141. };
  142. /**
  143. * Removes all layers from this collection.
  144. *
  145. * @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them.
  146. */
  147. ImageryLayerCollection.prototype.removeAll = function (destroy) {
  148. destroy = defaultValue(destroy, true);
  149. var layers = this._layers;
  150. for (var i = 0, len = layers.length; i < len; i++) {
  151. var layer = layers[i];
  152. this.layerRemoved.raiseEvent(layer, i);
  153. if (destroy) {
  154. layer.destroy();
  155. }
  156. }
  157. this._layers = [];
  158. };
  159. /**
  160. * Checks to see if the collection contains a given layer.
  161. *
  162. * @param {ImageryLayer} layer the layer to check for.
  163. *
  164. * @returns {Boolean} true if the collection contains the layer, false otherwise.
  165. */
  166. ImageryLayerCollection.prototype.contains = function (layer) {
  167. return this.indexOf(layer) !== -1;
  168. };
  169. /**
  170. * Determines the index of a given layer in the collection.
  171. *
  172. * @param {ImageryLayer} layer The layer to find the index of.
  173. *
  174. * @returns {Number} The index of the layer in the collection, or -1 if the layer does not exist in the collection.
  175. */
  176. ImageryLayerCollection.prototype.indexOf = function (layer) {
  177. return this._layers.indexOf(layer);
  178. };
  179. /**
  180. * Gets a layer by index from the collection.
  181. *
  182. * @param {Number} index the index to retrieve.
  183. *
  184. * @returns {ImageryLayer} The imagery layer at the given index.
  185. */
  186. ImageryLayerCollection.prototype.get = function (index) {
  187. //>>includeStart('debug', pragmas.debug);
  188. if (!defined(index)) {
  189. throw new DeveloperError("index is required.", "index");
  190. }
  191. //>>includeEnd('debug');
  192. return this._layers[index];
  193. };
  194. function getLayerIndex(layers, layer) {
  195. //>>includeStart('debug', pragmas.debug);
  196. if (!defined(layer)) {
  197. throw new DeveloperError("layer is required.");
  198. }
  199. //>>includeEnd('debug');
  200. var index = layers.indexOf(layer);
  201. //>>includeStart('debug', pragmas.debug);
  202. if (index === -1) {
  203. throw new DeveloperError("layer is not in this collection.");
  204. }
  205. //>>includeEnd('debug');
  206. return index;
  207. }
  208. function swapLayers(collection, i, j) {
  209. var arr = collection._layers;
  210. i = CesiumMath.clamp(i, 0, arr.length - 1);
  211. j = CesiumMath.clamp(j, 0, arr.length - 1);
  212. if (i === j) {
  213. return;
  214. }
  215. var temp = arr[i];
  216. arr[i] = arr[j];
  217. arr[j] = temp;
  218. collection._update();
  219. collection.layerMoved.raiseEvent(temp, j, i);
  220. }
  221. /**
  222. * Raises a layer up one position in the collection.
  223. *
  224. * @param {ImageryLayer} layer the layer to move.
  225. *
  226. * @exception {DeveloperError} layer is not in this collection.
  227. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  228. */
  229. ImageryLayerCollection.prototype.raise = function (layer) {
  230. var index = getLayerIndex(this._layers, layer);
  231. swapLayers(this, index, index + 1);
  232. };
  233. /**
  234. * Lowers a layer down one position in the collection.
  235. *
  236. * @param {ImageryLayer} layer the layer to move.
  237. *
  238. * @exception {DeveloperError} layer is not in this collection.
  239. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  240. */
  241. ImageryLayerCollection.prototype.lower = function (layer) {
  242. var index = getLayerIndex(this._layers, layer);
  243. swapLayers(this, index, index - 1);
  244. };
  245. /**
  246. * Raises a layer to the top of the collection.
  247. *
  248. * @param {ImageryLayer} layer the layer to move.
  249. *
  250. * @exception {DeveloperError} layer is not in this collection.
  251. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  252. */
  253. ImageryLayerCollection.prototype.raiseToTop = function (layer) {
  254. var index = getLayerIndex(this._layers, layer);
  255. if (index === this._layers.length - 1) {
  256. return;
  257. }
  258. this._layers.splice(index, 1);
  259. this._layers.push(layer);
  260. this._update();
  261. this.layerMoved.raiseEvent(layer, this._layers.length - 1, index);
  262. };
  263. /**
  264. * Lowers a layer to the bottom of the collection.
  265. *
  266. * @param {ImageryLayer} layer the layer to move.
  267. *
  268. * @exception {DeveloperError} layer is not in this collection.
  269. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  270. */
  271. ImageryLayerCollection.prototype.lowerToBottom = function (layer) {
  272. var index = getLayerIndex(this._layers, layer);
  273. if (index === 0) {
  274. return;
  275. }
  276. this._layers.splice(index, 1);
  277. this._layers.splice(0, 0, layer);
  278. this._update();
  279. this.layerMoved.raiseEvent(layer, 0, index);
  280. };
  281. var applicableRectangleScratch = new Rectangle();
  282. /**
  283. * Asynchronously determines the imagery layer features that are intersected by a pick ray. The intersected imagery
  284. * layer features are found by invoking {@link ImageryProvider#pickFeatures} for each imagery layer tile intersected
  285. * by the pick ray. To compute a pick ray from a location on the screen, use {@link Camera.getPickRay}.
  286. *
  287. * @param {Ray} ray The ray to test for intersection.
  288. * @param {Scene} scene The scene.
  289. * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise that resolves to an array of features intersected by the pick ray.
  290. * If it can be quickly determined that no features are intersected (for example,
  291. * because no active imagery providers support {@link ImageryProvider#pickFeatures}
  292. * or because the pick ray does not intersect the surface), this function will
  293. * return undefined.
  294. *
  295. * @example
  296. * var pickRay = viewer.camera.getPickRay(windowPosition);
  297. * var featuresPromise = viewer.imageryLayers.pickImageryLayerFeatures(pickRay, viewer.scene);
  298. * if (!Cesium.defined(featuresPromise)) {
  299. * console.log('No features picked.');
  300. * } else {
  301. * Cesium.when(featuresPromise, function(features) {
  302. * // This function is called asynchronously when the list if picked features is available.
  303. * console.log('Number of features: ' + features.length);
  304. * if (features.length > 0) {
  305. * console.log('First feature name: ' + features[0].name);
  306. * }
  307. * });
  308. * }
  309. */
  310. ImageryLayerCollection.prototype.pickImageryLayerFeatures = function (
  311. ray,
  312. scene
  313. ) {
  314. // Find the picked location on the globe.
  315. var pickedPosition = scene.globe.pick(ray, scene);
  316. if (!defined(pickedPosition)) {
  317. return undefined;
  318. }
  319. var pickedLocation = scene.globe.ellipsoid.cartesianToCartographic(
  320. pickedPosition
  321. );
  322. // Find the terrain tile containing the picked location.
  323. var tilesToRender = scene.globe._surface._tilesToRender;
  324. var pickedTile;
  325. for (
  326. var textureIndex = 0;
  327. !defined(pickedTile) && textureIndex < tilesToRender.length;
  328. ++textureIndex
  329. ) {
  330. var tile = tilesToRender[textureIndex];
  331. if (Rectangle.contains(tile.rectangle, pickedLocation)) {
  332. pickedTile = tile;
  333. }
  334. }
  335. if (!defined(pickedTile)) {
  336. return undefined;
  337. }
  338. // Pick against all attached imagery tiles containing the pickedLocation.
  339. var imageryTiles = pickedTile.data.imagery;
  340. var promises = [];
  341. var imageryLayers = [];
  342. for (var i = imageryTiles.length - 1; i >= 0; --i) {
  343. var terrainImagery = imageryTiles[i];
  344. var imagery = terrainImagery.readyImagery;
  345. if (!defined(imagery)) {
  346. continue;
  347. }
  348. var provider = imagery.imageryLayer.imageryProvider;
  349. if (!defined(provider.pickFeatures)) {
  350. continue;
  351. }
  352. if (!Rectangle.contains(imagery.rectangle, pickedLocation)) {
  353. continue;
  354. }
  355. // If this imagery came from a parent, it may not be applicable to its entire rectangle.
  356. // Check the textureCoordinateRectangle.
  357. var applicableRectangle = applicableRectangleScratch;
  358. var epsilon = 1 / 1024; // 1/4 of a pixel in a typical 256x256 tile.
  359. applicableRectangle.west = CesiumMath.lerp(
  360. pickedTile.rectangle.west,
  361. pickedTile.rectangle.east,
  362. terrainImagery.textureCoordinateRectangle.x - epsilon
  363. );
  364. applicableRectangle.east = CesiumMath.lerp(
  365. pickedTile.rectangle.west,
  366. pickedTile.rectangle.east,
  367. terrainImagery.textureCoordinateRectangle.z + epsilon
  368. );
  369. applicableRectangle.south = CesiumMath.lerp(
  370. pickedTile.rectangle.south,
  371. pickedTile.rectangle.north,
  372. terrainImagery.textureCoordinateRectangle.y - epsilon
  373. );
  374. applicableRectangle.north = CesiumMath.lerp(
  375. pickedTile.rectangle.south,
  376. pickedTile.rectangle.north,
  377. terrainImagery.textureCoordinateRectangle.w + epsilon
  378. );
  379. if (!Rectangle.contains(applicableRectangle, pickedLocation)) {
  380. continue;
  381. }
  382. var promise = provider.pickFeatures(
  383. imagery.x,
  384. imagery.y,
  385. imagery.level,
  386. pickedLocation.longitude,
  387. pickedLocation.latitude
  388. );
  389. if (!defined(promise)) {
  390. continue;
  391. }
  392. promises.push(promise);
  393. imageryLayers.push(imagery.imageryLayer);
  394. }
  395. if (promises.length === 0) {
  396. return undefined;
  397. }
  398. return when.all(promises, function (results) {
  399. var features = [];
  400. for (var resultIndex = 0; resultIndex < results.length; ++resultIndex) {
  401. var result = results[resultIndex];
  402. var image = imageryLayers[resultIndex];
  403. if (defined(result) && result.length > 0) {
  404. for (
  405. var featureIndex = 0;
  406. featureIndex < result.length;
  407. ++featureIndex
  408. ) {
  409. var feature = result[featureIndex];
  410. feature.imageryLayer = image;
  411. // For features without a position, use the picked location.
  412. if (!defined(feature.position)) {
  413. feature.position = pickedLocation;
  414. }
  415. features.push(feature);
  416. }
  417. }
  418. }
  419. return features;
  420. });
  421. };
  422. /**
  423. * Updates frame state to execute any queued texture re-projections.
  424. *
  425. * @private
  426. *
  427. * @param {FrameState} frameState The frameState.
  428. */
  429. ImageryLayerCollection.prototype.queueReprojectionCommands = function (
  430. frameState
  431. ) {
  432. var layers = this._layers;
  433. for (var i = 0, len = layers.length; i < len; ++i) {
  434. layers[i].queueReprojectionCommands(frameState);
  435. }
  436. };
  437. /**
  438. * Cancels re-projection commands queued for the next frame.
  439. *
  440. * @private
  441. */
  442. ImageryLayerCollection.prototype.cancelReprojections = function () {
  443. var layers = this._layers;
  444. for (var i = 0, len = layers.length; i < len; ++i) {
  445. layers[i].cancelReprojections();
  446. }
  447. };
  448. /**
  449. * Returns true if this object was destroyed; otherwise, false.
  450. * <br /><br />
  451. * If this object was destroyed, it should not be used; calling any function other than
  452. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  453. *
  454. * @returns {Boolean} true if this object was destroyed; otherwise, false.
  455. *
  456. * @see ImageryLayerCollection#destroy
  457. */
  458. ImageryLayerCollection.prototype.isDestroyed = function () {
  459. return false;
  460. };
  461. /**
  462. * Destroys the WebGL resources held by all layers in this collection. Explicitly destroying this
  463. * object allows for deterministic release of WebGL resources, instead of relying on the garbage
  464. * collector.
  465. * <br /><br />
  466. * Once this object is destroyed, it should not be used; calling any function other than
  467. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  468. * assign the return value (<code>undefined</code>) to the object as done in the example.
  469. *
  470. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  471. *
  472. *
  473. * @example
  474. * layerCollection = layerCollection && layerCollection.destroy();
  475. *
  476. * @see ImageryLayerCollection#isDestroyed
  477. */
  478. ImageryLayerCollection.prototype.destroy = function () {
  479. this.removeAll(true);
  480. return destroyObject(this);
  481. };
  482. ImageryLayerCollection.prototype._update = function () {
  483. var isBaseLayer = true;
  484. var layers = this._layers;
  485. var layersShownOrHidden;
  486. var layer;
  487. var i, len;
  488. for (i = 0, len = layers.length; i < len; ++i) {
  489. layer = layers[i];
  490. layer._layerIndex = i;
  491. if (layer.show) {
  492. layer._isBaseLayer = isBaseLayer;
  493. isBaseLayer = false;
  494. } else {
  495. layer._isBaseLayer = false;
  496. }
  497. if (layer.show !== layer._show) {
  498. if (defined(layer._show)) {
  499. if (!defined(layersShownOrHidden)) {
  500. layersShownOrHidden = [];
  501. }
  502. layersShownOrHidden.push(layer);
  503. }
  504. layer._show = layer.show;
  505. }
  506. }
  507. if (defined(layersShownOrHidden)) {
  508. for (i = 0, len = layersShownOrHidden.length; i < len; ++i) {
  509. layer = layersShownOrHidden[i];
  510. this.layerShownOrHidden.raiseEvent(layer, layer._layerIndex, layer.show);
  511. }
  512. }
  513. };
  514. export default ImageryLayerCollection;