GoogleEarthEnterpriseTerrainData.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import BoundingSphere from './BoundingSphere.js';
  2. import Cartesian2 from './Cartesian2.js';
  3. import Cartesian3 from './Cartesian3.js';
  4. import Check from './Check.js';
  5. import defaultValue from './defaultValue.js';
  6. import defined from './defined.js';
  7. import defineProperties from './defineProperties.js';
  8. import DeveloperError from './DeveloperError.js';
  9. import IndexDatatype from './IndexDatatype.js';
  10. import Intersections2D from './Intersections2D.js';
  11. import CesiumMath from './Math.js';
  12. import OrientedBoundingBox from './OrientedBoundingBox.js';
  13. import QuantizedMeshTerrainData from './QuantizedMeshTerrainData.js';
  14. import Rectangle from './Rectangle.js';
  15. import TaskProcessor from './TaskProcessor.js';
  16. import TerrainEncoding from './TerrainEncoding.js';
  17. import TerrainMesh from './TerrainMesh.js';
  18. /**
  19. * Terrain data for a single tile from a Google Earth Enterprise server.
  20. *
  21. * @alias GoogleEarthEnterpriseTerrainData
  22. * @constructor
  23. *
  24. * @param {Object} options Object with the following properties:
  25. * @param {ArrayBuffer} options.buffer The buffer containing terrain data.
  26. * @param {Number} options.negativeAltitudeExponentBias Multiplier for negative terrain heights that are encoded as very small positive values.
  27. * @param {Number} options.negativeElevationThreshold Threshold for negative values
  28. * @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
  29. * If a child's bit is set, geometry will be requested for that tile as well when it
  30. * is needed. If the bit is cleared, the child tile is not requested and geometry is
  31. * instead upsampled from the parent. The bit values are as follows:
  32. * <table>
  33. * <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
  34. * <tr><td>0</td><td>1</td><td>Southwest</td></tr>
  35. * <tr><td>1</td><td>2</td><td>Southeast</td></tr>
  36. * <tr><td>2</td><td>4</td><td>Northeast</td></tr>
  37. * <tr><td>3</td><td>8</td><td>Northwest</td></tr>
  38. * </table>
  39. * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance;
  40. * otherwise, false.
  41. * @param {Credit[]} [options.credits] Array of credits for this tile.
  42. *
  43. *
  44. * @example
  45. * var buffer = ...
  46. * var childTileMask = ...
  47. * var terrainData = new Cesium.GoogleEarthEnterpriseTerrainData({
  48. * buffer : heightBuffer,
  49. * childTileMask : childTileMask
  50. * });
  51. *
  52. * @see TerrainData
  53. * @see HeightTerrainData
  54. * @see QuantizedMeshTerrainData
  55. */
  56. function GoogleEarthEnterpriseTerrainData(options) {
  57. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  58. //>>includeStart('debug', pragmas.debug);
  59. Check.typeOf.object('options.buffer', options.buffer);
  60. Check.typeOf.number('options.negativeAltitudeExponentBias', options.negativeAltitudeExponentBias);
  61. Check.typeOf.number('options.negativeElevationThreshold', options.negativeElevationThreshold);
  62. //>>includeEnd('debug');
  63. this._buffer = options.buffer;
  64. this._credits = options.credits;
  65. this._negativeAltitudeExponentBias = options.negativeAltitudeExponentBias;
  66. this._negativeElevationThreshold = options.negativeElevationThreshold;
  67. // Convert from google layout to layout of other providers
  68. // 3 2 -> 2 3
  69. // 0 1 -> 0 1
  70. var googleChildTileMask = defaultValue(options.childTileMask, 15);
  71. var childTileMask = googleChildTileMask & 3; // Bottom row is identical
  72. childTileMask |= (googleChildTileMask & 4) ? 8 : 0; // NE
  73. childTileMask |= (googleChildTileMask & 8) ? 4 : 0; // NW
  74. this._childTileMask = childTileMask;
  75. this._createdByUpsampling = defaultValue(options.createdByUpsampling, false);
  76. this._skirtHeight = undefined;
  77. this._bufferType = this._buffer.constructor;
  78. this._mesh = undefined;
  79. this._minimumHeight = undefined;
  80. this._maximumHeight = undefined;
  81. this._vertexCountWithoutSkirts = undefined;
  82. this._skirtIndex = undefined;
  83. }
  84. defineProperties(GoogleEarthEnterpriseTerrainData.prototype, {
  85. /**
  86. * An array of credits for this tile
  87. * @memberof GoogleEarthEnterpriseTerrainData.prototype
  88. * @type {Credit[]}
  89. */
  90. credits : {
  91. get : function() {
  92. return this._credits;
  93. }
  94. },
  95. /**
  96. * The water mask included in this terrain data, if any. A water mask is a rectangular
  97. * Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
  98. * Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
  99. * @memberof GoogleEarthEnterpriseTerrainData.prototype
  100. * @type {Uint8Array|Image|Canvas}
  101. */
  102. waterMask : {
  103. get : function() {
  104. return undefined;
  105. }
  106. }
  107. });
  108. var taskProcessor = new TaskProcessor('createVerticesFromGoogleEarthEnterpriseBuffer');
  109. var nativeRectangleScratch = new Rectangle();
  110. var rectangleScratch = new Rectangle();
  111. /**
  112. * Creates a {@link TerrainMesh} from this terrain data.
  113. *
  114. * @private
  115. *
  116. * @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
  117. * @param {Number} x The X coordinate of the tile for which to create the terrain data.
  118. * @param {Number} y The Y coordinate of the tile for which to create the terrain data.
  119. * @param {Number} level The level of the tile for which to create the terrain data.
  120. * @param {Number} [exaggeration=1.0] The scale used to exaggerate the terrain.
  121. * @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
  122. * asynchronous mesh creations are already in progress and the operation should
  123. * be retried later.
  124. */
  125. GoogleEarthEnterpriseTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) {
  126. //>>includeStart('debug', pragmas.debug);
  127. Check.typeOf.object('tilingScheme', tilingScheme);
  128. Check.typeOf.number('x', x);
  129. Check.typeOf.number('y', y);
  130. Check.typeOf.number('level', level);
  131. //>>includeEnd('debug');
  132. var ellipsoid = tilingScheme.ellipsoid;
  133. tilingScheme.tileXYToNativeRectangle(x, y, level, nativeRectangleScratch);
  134. tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch);
  135. exaggeration = defaultValue(exaggeration, 1.0);
  136. // Compute the center of the tile for RTC rendering.
  137. var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangleScratch));
  138. var levelZeroMaxError = 40075.16; // From Google's Doc
  139. var thisLevelMaxError = levelZeroMaxError / (1 << level);
  140. this._skirtHeight = Math.min(thisLevelMaxError * 8.0, 1000.0);
  141. var verticesPromise = taskProcessor.scheduleTask({
  142. buffer : this._buffer,
  143. nativeRectangle : nativeRectangleScratch,
  144. rectangle : rectangleScratch,
  145. relativeToCenter : center,
  146. ellipsoid : ellipsoid,
  147. skirtHeight : this._skirtHeight,
  148. exaggeration : exaggeration,
  149. includeWebMercatorT : true,
  150. negativeAltitudeExponentBias: this._negativeAltitudeExponentBias,
  151. negativeElevationThreshold: this._negativeElevationThreshold
  152. });
  153. if (!defined(verticesPromise)) {
  154. // Postponed
  155. return undefined;
  156. }
  157. var that = this;
  158. return verticesPromise
  159. .then(function(result) {
  160. // Clone complex result objects because the transfer from the web worker
  161. // has stripped them down to JSON-style objects.
  162. that._mesh = new TerrainMesh(
  163. center,
  164. new Float32Array(result.vertices),
  165. new Uint16Array(result.indices),
  166. result.minimumHeight,
  167. result.maximumHeight,
  168. BoundingSphere.clone(result.boundingSphere3D),
  169. Cartesian3.clone(result.occludeePointInScaledSpace),
  170. result.numberOfAttributes,
  171. OrientedBoundingBox.clone(result.orientedBoundingBox),
  172. TerrainEncoding.clone(result.encoding),
  173. exaggeration,
  174. result.westIndicesSouthToNorth,
  175. result.southIndicesEastToWest,
  176. result.eastIndicesNorthToSouth,
  177. result.northIndicesWestToEast);
  178. that._vertexCountWithoutSkirts = result.vertexCountWithoutSkirts;
  179. that._skirtIndex = result.skirtIndex;
  180. that._minimumHeight = result.minimumHeight;
  181. that._maximumHeight = result.maximumHeight;
  182. // Free memory received from server after mesh is created.
  183. that._buffer = undefined;
  184. return that._mesh;
  185. });
  186. };
  187. /**
  188. * Computes the terrain height at a specified longitude and latitude.
  189. *
  190. * @param {Rectangle} rectangle The rectangle covered by this terrain data.
  191. * @param {Number} longitude The longitude in radians.
  192. * @param {Number} latitude The latitude in radians.
  193. * @returns {Number} The terrain height at the specified position. If the position
  194. * is outside the rectangle, this method will extrapolate the height, which is likely to be wildly
  195. * incorrect for positions far outside the rectangle.
  196. */
  197. GoogleEarthEnterpriseTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) {
  198. var u = CesiumMath.clamp((longitude - rectangle.west) / rectangle.width, 0.0, 1.0);
  199. var v = CesiumMath.clamp((latitude - rectangle.south) / rectangle.height, 0.0, 1.0);
  200. if (!defined(this._mesh)) {
  201. return interpolateHeight(this, u, v, rectangle);
  202. }
  203. return interpolateMeshHeight(this, u, v);
  204. };
  205. var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh');
  206. /**
  207. * Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the
  208. * height samples in this instance, interpolated if necessary.
  209. *
  210. * @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
  211. * @param {Number} thisX The X coordinate of this tile in the tiling scheme.
  212. * @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
  213. * @param {Number} thisLevel The level of this tile in the tiling scheme.
  214. * @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
  215. * @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
  216. * @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
  217. * @returns {Promise.<HeightmapTerrainData>|undefined} A promise for upsampled heightmap terrain data for the descendant tile,
  218. * or undefined if too many asynchronous upsample operations are in progress and the request has been
  219. * deferred.
  220. */
  221. GoogleEarthEnterpriseTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) {
  222. //>>includeStart('debug', pragmas.debug);
  223. Check.typeOf.object('tilingScheme', tilingScheme);
  224. Check.typeOf.number('thisX', thisX);
  225. Check.typeOf.number('thisY', thisY);
  226. Check.typeOf.number('thisLevel', thisLevel);
  227. Check.typeOf.number('descendantX', descendantX);
  228. Check.typeOf.number('descendantY', descendantY);
  229. Check.typeOf.number('descendantLevel', descendantLevel);
  230. var levelDifference = descendantLevel - thisLevel;
  231. if (levelDifference > 1) {
  232. throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.');
  233. }
  234. //>>includeEnd('debug');
  235. var mesh = this._mesh;
  236. if (!defined(this._mesh)) {
  237. return undefined;
  238. }
  239. var isEastChild = thisX * 2 !== descendantX;
  240. var isNorthChild = thisY * 2 === descendantY;
  241. var ellipsoid = tilingScheme.ellipsoid;
  242. var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel);
  243. var upsamplePromise = upsampleTaskProcessor.scheduleTask({
  244. vertices : mesh.vertices,
  245. vertexCountWithoutSkirts : this._vertexCountWithoutSkirts,
  246. indices : mesh.indices,
  247. skirtIndex : this._skirtIndex,
  248. encoding : mesh.encoding,
  249. minimumHeight : this._minimumHeight,
  250. maximumHeight : this._maximumHeight,
  251. isEastChild : isEastChild,
  252. isNorthChild : isNorthChild,
  253. childRectangle : childRectangle,
  254. ellipsoid : ellipsoid,
  255. exaggeration : mesh.exaggeration
  256. });
  257. if (!defined(upsamplePromise)) {
  258. // Postponed
  259. return undefined;
  260. }
  261. var that = this;
  262. return upsamplePromise
  263. .then(function(result) {
  264. var quantizedVertices = new Uint16Array(result.vertices);
  265. var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices);
  266. var skirtHeight = that._skirtHeight;
  267. // Use QuantizedMeshTerrainData since we have what we need already parsed
  268. return new QuantizedMeshTerrainData({
  269. quantizedVertices : quantizedVertices,
  270. indices : indicesTypedArray,
  271. minimumHeight : result.minimumHeight,
  272. maximumHeight : result.maximumHeight,
  273. boundingSphere : BoundingSphere.clone(result.boundingSphere),
  274. orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox),
  275. horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint),
  276. westIndices : result.westIndices,
  277. southIndices : result.southIndices,
  278. eastIndices : result.eastIndices,
  279. northIndices : result.northIndices,
  280. westSkirtHeight : skirtHeight,
  281. southSkirtHeight : skirtHeight,
  282. eastSkirtHeight : skirtHeight,
  283. northSkirtHeight : skirtHeight,
  284. childTileMask : 0,
  285. createdByUpsampling : true,
  286. credits : that._credits
  287. });
  288. });
  289. };
  290. /**
  291. * Determines if a given child tile is available, based on the
  292. * {@link HeightmapTerrainData.childTileMask}. The given child tile coordinates are assumed
  293. * to be one of the four children of this tile. If non-child tile coordinates are
  294. * given, the availability of the southeast child tile is returned.
  295. *
  296. * @param {Number} thisX The tile X coordinate of this (the parent) tile.
  297. * @param {Number} thisY The tile Y coordinate of this (the parent) tile.
  298. * @param {Number} childX The tile X coordinate of the child tile to check for availability.
  299. * @param {Number} childY The tile Y coordinate of the child tile to check for availability.
  300. * @returns {Boolean} True if the child tile is available; otherwise, false.
  301. */
  302. GoogleEarthEnterpriseTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) {
  303. //>>includeStart('debug', pragmas.debug);
  304. Check.typeOf.number('thisX', thisX);
  305. Check.typeOf.number('thisY', thisY);
  306. Check.typeOf.number('childX', childX);
  307. Check.typeOf.number('childY', childY);
  308. //>>includeEnd('debug');
  309. var bitNumber = 2; // northwest child
  310. if (childX !== thisX * 2) {
  311. ++bitNumber; // east child
  312. }
  313. if (childY !== thisY * 2) {
  314. bitNumber -= 2; // south child
  315. }
  316. return (this._childTileMask & (1 << bitNumber)) !== 0;
  317. };
  318. /**
  319. * Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
  320. * terrain data. If this value is false, the data was obtained from some other source, such
  321. * as by downloading it from a remote server. This method should return true for instances
  322. * returned from a call to {@link HeightmapTerrainData#upsample}.
  323. *
  324. * @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
  325. */
  326. GoogleEarthEnterpriseTerrainData.prototype.wasCreatedByUpsampling = function() {
  327. return this._createdByUpsampling;
  328. };
  329. var texCoordScratch0 = new Cartesian2();
  330. var texCoordScratch1 = new Cartesian2();
  331. var texCoordScratch2 = new Cartesian2();
  332. var barycentricCoordinateScratch = new Cartesian3();
  333. function interpolateMeshHeight(terrainData, u, v) {
  334. var mesh = terrainData._mesh;
  335. var vertices = mesh.vertices;
  336. var encoding = mesh.encoding;
  337. var indices = mesh.indices;
  338. for (var i = 0, len = indices.length; i < len; i += 3) {
  339. var i0 = indices[i];
  340. var i1 = indices[i + 1];
  341. var i2 = indices[i + 2];
  342. var uv0 = encoding.decodeTextureCoordinates(vertices, i0, texCoordScratch0);
  343. var uv1 = encoding.decodeTextureCoordinates(vertices, i1, texCoordScratch1);
  344. var uv2 = encoding.decodeTextureCoordinates(vertices, i2, texCoordScratch2);
  345. var barycentric = Intersections2D.computeBarycentricCoordinates(u, v, uv0.x, uv0.y, uv1.x, uv1.y, uv2.x, uv2.y, barycentricCoordinateScratch);
  346. if (barycentric.x >= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) {
  347. var h0 = encoding.decodeHeight(vertices, i0);
  348. var h1 = encoding.decodeHeight(vertices, i1);
  349. var h2 = encoding.decodeHeight(vertices, i2);
  350. return barycentric.x * h0 + barycentric.y * h1 + barycentric.z * h2;
  351. }
  352. }
  353. // Position does not lie in any triangle in this mesh.
  354. return undefined;
  355. }
  356. var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
  357. var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  358. var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
  359. var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT;
  360. var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT;
  361. function interpolateHeight(terrainData, u, v, rectangle) {
  362. var buffer = terrainData._buffer;
  363. var quad = 0; // SW
  364. var uStart = 0.0;
  365. var vStart = 0.0;
  366. if (v > 0.5) { // Upper row
  367. if (u > 0.5) { // NE
  368. quad = 2;
  369. uStart = 0.5;
  370. } else { // NW
  371. quad = 3;
  372. }
  373. vStart = 0.5;
  374. } else if (u > 0.5) { // SE
  375. quad = 1;
  376. uStart = 0.5;
  377. }
  378. var dv = new DataView(buffer);
  379. var offset = 0;
  380. for (var q = 0; q < quad; ++q) {
  381. offset += dv.getUint32(offset, true);
  382. offset += sizeOfUint32;
  383. }
  384. offset += sizeOfUint32; // Skip length of quad
  385. offset += 2 * sizeOfDouble; // Skip origin
  386. // Read sizes
  387. var xSize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
  388. offset += sizeOfDouble;
  389. var ySize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
  390. offset += sizeOfDouble;
  391. // Samples per quad
  392. var xScale = rectangle.width / xSize / 2;
  393. var yScale = rectangle.height / ySize / 2;
  394. // Number of points
  395. var numPoints = dv.getInt32(offset, true);
  396. offset += sizeOfInt32;
  397. // Number of faces
  398. var numIndices = dv.getInt32(offset, true) * 3;
  399. offset += sizeOfInt32;
  400. offset += sizeOfInt32; // Skip Level
  401. var uBuffer = new Array(numPoints);
  402. var vBuffer = new Array(numPoints);
  403. var heights = new Array(numPoints);
  404. var i;
  405. for (i = 0; i < numPoints; ++i) {
  406. uBuffer[i] = uStart + (dv.getUint8(offset++) * xScale);
  407. vBuffer[i] = vStart + (dv.getUint8(offset++) * yScale);
  408. // Height is stored in units of (1/EarthRadius) or (1/6371010.0)
  409. heights[i] = (dv.getFloat32(offset, true) * 6371010.0);
  410. offset += sizeOfFloat;
  411. }
  412. var indices = new Array(numIndices);
  413. for (i = 0; i < numIndices; ++i) {
  414. indices[i] = dv.getUint16(offset, true);
  415. offset += sizeOfUint16;
  416. }
  417. for (i = 0; i < numIndices; i += 3) {
  418. var i0 = indices[i];
  419. var i1 = indices[i + 1];
  420. var i2 = indices[i + 2];
  421. var u0 = uBuffer[i0];
  422. var u1 = uBuffer[i1];
  423. var u2 = uBuffer[i2];
  424. var v0 = vBuffer[i0];
  425. var v1 = vBuffer[i1];
  426. var v2 = vBuffer[i2];
  427. var barycentric = Intersections2D.computeBarycentricCoordinates(u, v, u0, v0, u1, v1, u2, v2, barycentricCoordinateScratch);
  428. if (barycentric.x >= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) {
  429. return barycentric.x * heights[i0] +
  430. barycentric.y * heights[i1] +
  431. barycentric.z * heights[i2];
  432. }
  433. }
  434. // Position does not lie in any triangle in this mesh.
  435. return undefined;
  436. }
  437. export default GoogleEarthEnterpriseTerrainData;