ImageryLayerCollection.js 20 KB

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