ImageryLayerCollection.js 18 KB

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