智慧水务管理系统 - 精河县供水工程综合管理平台

ImageryLayerCollection.js 19KB

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