sampleTerrain.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import Check from "./Check.js";
  2. /**
  3. * Initiates a terrain height query for an array of {@link Cartographic} positions by
  4. * requesting tiles from a terrain provider, sampling, and interpolating. The interpolation
  5. * matches the triangles used to render the terrain at the specified level. The query
  6. * happens asynchronously, so this function returns a promise that is resolved when
  7. * the query completes. Each point height is modified in place. If a height can not be
  8. * determined because no terrain data is available for the specified level at that location,
  9. * or another error occurs, the height is set to undefined. As is typical of the
  10. * {@link Cartographic} type, the supplied height is a height above the reference ellipsoid
  11. * (such as {@link Ellipsoid.WGS84}) rather than an altitude above mean sea level. In other
  12. * words, it will not necessarily be 0.0 if sampled in the ocean. This function needs the
  13. * terrain level of detail as input, if you need to get the altitude of the terrain as precisely
  14. * as possible (i.e. with maximum level of detail) use {@link sampleTerrainMostDetailed}.
  15. *
  16. * @function sampleTerrain
  17. *
  18. * @param {TerrainProvider} terrainProvider The terrain provider from which to query heights.
  19. * @param {Number} level The terrain level-of-detail from which to query terrain heights.
  20. * @param {Cartographic[]} positions The positions to update with terrain heights.
  21. * @returns {Promise.<Cartographic[]>} A promise that resolves to the provided list of positions when terrain the query has completed.
  22. *
  23. * @see sampleTerrainMostDetailed
  24. *
  25. * @example
  26. * // Query the terrain height of two Cartographic positions
  27. * const terrainProvider = Cesium.createWorldTerrain();
  28. * const positions = [
  29. * Cesium.Cartographic.fromDegrees(86.925145, 27.988257),
  30. * Cesium.Cartographic.fromDegrees(87.0, 28.0)
  31. * ];
  32. * const promise = Cesium.sampleTerrain(terrainProvider, 11, positions);
  33. * Promise.resolve(promise).then(function(updatedPositions) {
  34. * // positions[0].height and positions[1].height have been updated.
  35. * // updatedPositions is just a reference to positions.
  36. * });
  37. */
  38. function sampleTerrain(terrainProvider, level, positions) {
  39. //>>includeStart('debug', pragmas.debug);
  40. Check.typeOf.object("terrainProvider", terrainProvider);
  41. Check.typeOf.number("level", level);
  42. Check.defined("positions", positions);
  43. //>>includeEnd('debug');
  44. return terrainProvider.readyPromise.then(function () {
  45. return doSampling(terrainProvider, level, positions);
  46. });
  47. }
  48. function doSampling(terrainProvider, level, positions) {
  49. const tilingScheme = terrainProvider.tilingScheme;
  50. let i;
  51. // Sort points into a set of tiles
  52. const tileRequests = []; // Result will be an Array as it's easier to work with
  53. const tileRequestSet = {}; // A unique set
  54. for (i = 0; i < positions.length; ++i) {
  55. const xy = tilingScheme.positionToTileXY(positions[i], level);
  56. const key = xy.toString();
  57. if (!tileRequestSet.hasOwnProperty(key)) {
  58. // When tile is requested for the first time
  59. const value = {
  60. x: xy.x,
  61. y: xy.y,
  62. level: level,
  63. tilingScheme: tilingScheme,
  64. terrainProvider: terrainProvider,
  65. positions: [],
  66. };
  67. tileRequestSet[key] = value;
  68. tileRequests.push(value);
  69. }
  70. // Now append to array of points for the tile
  71. tileRequestSet[key].positions.push(positions[i]);
  72. }
  73. // Send request for each required tile
  74. const tilePromises = [];
  75. for (i = 0; i < tileRequests.length; ++i) {
  76. const tileRequest = tileRequests[i];
  77. const requestPromise = tileRequest.terrainProvider.requestTileGeometry(
  78. tileRequest.x,
  79. tileRequest.y,
  80. tileRequest.level
  81. );
  82. const tilePromise = requestPromise
  83. .then(createInterpolateFunction(tileRequest))
  84. .catch(createMarkFailedFunction(tileRequest));
  85. tilePromises.push(tilePromise);
  86. }
  87. return Promise.all(tilePromises).then(function () {
  88. return positions;
  89. });
  90. }
  91. /**
  92. * Calls {@link TerrainData#interpolateHeight} on a given {@link TerrainData} for a given {@link Cartographic} and
  93. * will assign the height property if the return value is not undefined.
  94. *
  95. * If the return value is false; it's suggesting that you should call {@link TerrainData#createMesh} first.
  96. * @param {Cartographic} position The position to interpolate for and assign the height value to
  97. * @param {TerrainData} terrainData
  98. * @param {Rectangle} rectangle
  99. * @returns {Boolean} If the height was actually interpolated and assigned
  100. * @private
  101. */
  102. function interpolateAndAssignHeight(position, terrainData, rectangle) {
  103. const height = terrainData.interpolateHeight(
  104. rectangle,
  105. position.longitude,
  106. position.latitude
  107. );
  108. if (height === undefined) {
  109. // if height comes back as undefined, it may implicitly mean the terrain data
  110. // requires us to call TerrainData.createMesh() first (ArcGIS requires this in particular)
  111. // so we'll return false and do that next!
  112. return false;
  113. }
  114. position.height = height;
  115. return true;
  116. }
  117. function createInterpolateFunction(tileRequest) {
  118. const tilePositions = tileRequest.positions;
  119. const rectangle = tileRequest.tilingScheme.tileXYToRectangle(
  120. tileRequest.x,
  121. tileRequest.y,
  122. tileRequest.level
  123. );
  124. return function (terrainData) {
  125. let isMeshRequired = false;
  126. for (let i = 0; i < tilePositions.length; ++i) {
  127. const position = tilePositions[i];
  128. const isHeightAssigned = interpolateAndAssignHeight(
  129. position,
  130. terrainData,
  131. rectangle
  132. );
  133. // we've found a position which returned undefined - hinting to us
  134. // that we probably need to create a mesh for this terrain data.
  135. // so break out of this loop and create the mesh - then we'll interpolate all the heights again
  136. if (!isHeightAssigned) {
  137. isMeshRequired = true;
  138. break;
  139. }
  140. }
  141. if (!isMeshRequired) {
  142. // all position heights were interpolated - we don't need the mesh
  143. return Promise.resolve();
  144. }
  145. // create the mesh - and interpolate all the positions again
  146. // note: terrain exaggeration is not passed in - we are only interested in the raw data
  147. return terrainData
  148. .createMesh({
  149. tilingScheme: tileRequest.tilingScheme,
  150. x: tileRequest.x,
  151. y: tileRequest.y,
  152. level: tileRequest.level,
  153. // don't throttle this mesh creation because we've asked to sample these points;
  154. // so sample them! We don't care how many tiles that is!
  155. throttle: false,
  156. })
  157. .then(function () {
  158. // mesh has been created - so go through every position (maybe again)
  159. // and re-interpolate the heights - presumably using the mesh this time
  160. for (let i = 0; i < tilePositions.length; ++i) {
  161. const position = tilePositions[i];
  162. // if it doesn't work this time - that's fine, we tried.
  163. interpolateAndAssignHeight(position, terrainData, rectangle);
  164. }
  165. });
  166. };
  167. }
  168. function createMarkFailedFunction(tileRequest) {
  169. const tilePositions = tileRequest.positions;
  170. return function () {
  171. for (let i = 0; i < tilePositions.length; ++i) {
  172. const position = tilePositions[i];
  173. position.height = undefined;
  174. }
  175. };
  176. }
  177. export default sampleTerrain;