CesiumTerrainProvider.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. import when from '../ThirdParty/when.js';
  2. import AttributeCompression from './AttributeCompression.js';
  3. import BoundingSphere from './BoundingSphere.js';
  4. import Cartesian3 from './Cartesian3.js';
  5. import Credit from './Credit.js';
  6. import defaultValue from './defaultValue.js';
  7. import defined from './defined.js';
  8. import defineProperties from './defineProperties.js';
  9. import DeveloperError from './DeveloperError.js';
  10. import Event from './Event.js';
  11. import GeographicTilingScheme from './GeographicTilingScheme.js';
  12. import getStringFromTypedArray from './getStringFromTypedArray.js';
  13. import HeightmapTerrainData from './HeightmapTerrainData.js';
  14. import IndexDatatype from './IndexDatatype.js';
  15. import CesiumMath from './Math.js';
  16. import OrientedBoundingBox from './OrientedBoundingBox.js';
  17. import QuantizedMeshTerrainData from './QuantizedMeshTerrainData.js';
  18. import Request from './Request.js';
  19. import RequestType from './RequestType.js';
  20. import Resource from './Resource.js';
  21. import RuntimeError from './RuntimeError.js';
  22. import TerrainProvider from './TerrainProvider.js';
  23. import TileAvailability from './TileAvailability.js';
  24. import TileProviderError from './TileProviderError.js';
  25. function LayerInformation(layer) {
  26. this.resource = layer.resource;
  27. this.version = layer.version;
  28. this.isHeightmap = layer.isHeightmap;
  29. this.tileUrlTemplates = layer.tileUrlTemplates;
  30. this.availability = layer.availability;
  31. this.hasVertexNormals = layer.hasVertexNormals;
  32. this.hasWaterMask = layer.hasWaterMask;
  33. this.hasMetadata = layer.hasMetadata;
  34. this.availabilityLevels = layer.availabilityLevels;
  35. this.availabilityTilesLoaded = layer.availabilityTilesLoaded;
  36. this.littleEndianExtensionSize = layer.littleEndianExtensionSize;
  37. this.availabilityTilesLoaded = layer.availabilityTilesLoaded;
  38. this.availabilityPromiseCache = {};
  39. }
  40. /**
  41. * A {@link TerrainProvider} that accesses terrain data in a Cesium terrain format.
  42. *
  43. * @alias CesiumTerrainProvider
  44. * @constructor
  45. *
  46. * @param {Object} options Object with the following properties:
  47. * @param {Resource|String|Promise<Resource>|Promise<String>} options.url The URL of the Cesium terrain server.
  48. * @param {Boolean} [options.requestVertexNormals=false] Flag that indicates if the client should request additional lighting information from the server, in the form of per vertex normals if available.
  49. * @param {Boolean} [options.requestWaterMask=false] Flag that indicates if the client should request per tile water masks from the server, if available.
  50. * @param {Boolean} [options.requestMetadata=true] Flag that indicates if the client should request per tile metadata from the server, if available.
  51. * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
  52. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
  53. *
  54. *
  55. * @example
  56. * // Create Arctic DEM terrain with normals.
  57. * var viewer = new Cesium.Viewer('cesiumContainer', {
  58. * terrainProvider : new Cesium.CesiumTerrainProvider({
  59. * url : Cesium.IonResource.fromAssetId(3956),
  60. * requestVertexNormals : true
  61. * })
  62. * });
  63. *
  64. * @see createWorldTerrain
  65. * @see TerrainProvider
  66. */
  67. function CesiumTerrainProvider(options) {
  68. //>>includeStart('debug', pragmas.debug)
  69. if (!defined(options) || !defined(options.url)) {
  70. throw new DeveloperError('options.url is required.');
  71. }
  72. //>>includeEnd('debug');
  73. this._tilingScheme = new GeographicTilingScheme({
  74. numberOfLevelZeroTilesX : 2,
  75. numberOfLevelZeroTilesY : 1,
  76. ellipsoid : options.ellipsoid
  77. });
  78. this._heightmapWidth = 65;
  79. this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._heightmapWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0));
  80. this._heightmapStructure = undefined;
  81. this._hasWaterMask = false;
  82. this._hasVertexNormals = false;
  83. /**
  84. * Boolean flag that indicates if the client should request vertex normals from the server.
  85. * @type {Boolean}
  86. * @default false
  87. * @private
  88. */
  89. this._requestVertexNormals = defaultValue(options.requestVertexNormals, false);
  90. /**
  91. * Boolean flag that indicates if the client should request tile watermasks from the server.
  92. * @type {Boolean}
  93. * @default false
  94. * @private
  95. */
  96. this._requestWaterMask = defaultValue(options.requestWaterMask, false);
  97. /**
  98. * Boolean flag that indicates if the client should request tile metadata from the server.
  99. * @type {Boolean}
  100. * @default true
  101. * @private
  102. */
  103. this._requestMetadata = defaultValue(options.requestMetadata, true);
  104. this._errorEvent = new Event();
  105. var credit = options.credit;
  106. if (typeof credit === 'string') {
  107. credit = new Credit(credit);
  108. }
  109. this._credit = credit;
  110. this._availability = undefined;
  111. var deferred = when.defer();
  112. this._ready = false;
  113. this._readyPromise = deferred;
  114. this._tileCredits = undefined;
  115. var that = this;
  116. var lastResource;
  117. var layerJsonResource;
  118. var metadataError;
  119. var layers = this._layers = [];
  120. var attribution = '';
  121. var overallAvailability = [];
  122. var overallMaxZoom = 0;
  123. when(options.url)
  124. .then(function(url) {
  125. var resource = Resource.createIfNeeded(url);
  126. resource.appendForwardSlash();
  127. lastResource = resource;
  128. layerJsonResource = lastResource.getDerivedResource({
  129. url: 'layer.json'
  130. });
  131. // ion resources have a credits property we can use for additional attribution.
  132. that._tileCredits = resource.credits;
  133. requestLayerJson();
  134. })
  135. .otherwise(function(e) {
  136. deferred.reject(e);
  137. });
  138. function parseMetadataSuccess(data) {
  139. var message;
  140. if (!data.format) {
  141. message = 'The tile format is not specified in the layer.json file.';
  142. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson);
  143. return;
  144. }
  145. if (!data.tiles || data.tiles.length === 0) {
  146. message = 'The layer.json file does not specify any tile URL templates.';
  147. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson);
  148. return;
  149. }
  150. var hasVertexNormals = false;
  151. var hasWaterMask = false;
  152. var hasMetadata = false;
  153. var littleEndianExtensionSize = true;
  154. var isHeightmap = false;
  155. if (data.format === 'heightmap-1.0') {
  156. isHeightmap = true;
  157. if (!defined(that._heightmapStructure)) {
  158. that._heightmapStructure = {
  159. heightScale : 1.0 / 5.0,
  160. heightOffset : -1000.0,
  161. elementsPerHeight : 1,
  162. stride : 1,
  163. elementMultiplier : 256.0,
  164. isBigEndian : false,
  165. lowestEncodedHeight : 0,
  166. highestEncodedHeight : 256 * 256 - 1
  167. };
  168. }
  169. hasWaterMask = true;
  170. that._requestWaterMask = true;
  171. } else if (data.format.indexOf('quantized-mesh-1.') !== 0) {
  172. message = 'The tile format "' + data.format + '" is invalid or not supported.';
  173. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson);
  174. return;
  175. }
  176. var tileUrlTemplates = data.tiles;
  177. var maxZoom = data.maxzoom;
  178. overallMaxZoom = Math.max(overallMaxZoom, maxZoom);
  179. // Keeps track of which of the availablity containing tiles have been loaded
  180. var availabilityTilesLoaded;
  181. // The vertex normals defined in the 'octvertexnormals' extension is identical to the original
  182. // contents of the original 'vertexnormals' extension. 'vertexnormals' extension is now
  183. // deprecated, as the extensionLength for this extension was incorrectly using big endian.
  184. // We maintain backwards compatibility with the legacy 'vertexnormal' implementation
  185. // by setting the _littleEndianExtensionSize to false. Always prefer 'octvertexnormals'
  186. // over 'vertexnormals' if both extensions are supported by the server.
  187. if (defined(data.extensions) && data.extensions.indexOf('octvertexnormals') !== -1) {
  188. hasVertexNormals = true;
  189. } else if (defined(data.extensions) && data.extensions.indexOf('vertexnormals') !== -1) {
  190. hasVertexNormals = true;
  191. littleEndianExtensionSize = false;
  192. }
  193. if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) {
  194. hasWaterMask = true;
  195. }
  196. if (defined(data.extensions) && data.extensions.indexOf('metadata') !== -1) {
  197. hasMetadata = true;
  198. }
  199. var availabilityLevels = data.metadataAvailability;
  200. var availableTiles = data.available;
  201. var availability;
  202. if (defined(availableTiles) && !defined(availabilityLevels)) {
  203. availability = new TileAvailability(that._tilingScheme, availableTiles.length);
  204. for (var level = 0; level < availableTiles.length; ++level) {
  205. var rangesAtLevel = availableTiles[level];
  206. var yTiles = that._tilingScheme.getNumberOfYTilesAtLevel(level);
  207. if (!defined(overallAvailability[level])) {
  208. overallAvailability[level] = [];
  209. }
  210. for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) {
  211. var range = rangesAtLevel[rangeIndex];
  212. var yStart = yTiles - range.endY - 1;
  213. var yEnd = yTiles - range.startY - 1;
  214. overallAvailability[level].push([range.startX, yStart, range.endX, yEnd]);
  215. availability.addAvailableTileRange(level, range.startX, yStart, range.endX, yEnd);
  216. }
  217. }
  218. } else if (defined(availabilityLevels)) {
  219. availabilityTilesLoaded = new TileAvailability(that._tilingScheme, maxZoom);
  220. availability = new TileAvailability(that._tilingScheme, maxZoom);
  221. overallAvailability[0] = [
  222. [0, 0, 1, 0]
  223. ];
  224. availability.addAvailableTileRange(0, 0, 0, 1, 0);
  225. }
  226. that._hasWaterMask = that._hasWaterMask || hasWaterMask;
  227. that._hasVertexNormals = that._hasVertexNormals || hasVertexNormals;
  228. that._hasMetadata = that._hasMetadata || hasMetadata;
  229. if (defined(data.attribution)) {
  230. if (attribution.length > 0) {
  231. attribution += ' ';
  232. }
  233. attribution += data.attribution;
  234. }
  235. layers.push(new LayerInformation({
  236. resource: lastResource,
  237. version: data.version,
  238. isHeightmap: isHeightmap,
  239. tileUrlTemplates: tileUrlTemplates,
  240. availability: availability,
  241. hasVertexNormals: hasVertexNormals,
  242. hasWaterMask: hasWaterMask,
  243. hasMetadata: hasMetadata,
  244. availabilityLevels: availabilityLevels,
  245. availabilityTilesLoaded: availabilityTilesLoaded,
  246. littleEndianExtensionSize: littleEndianExtensionSize
  247. }));
  248. var parentUrl = data.parentUrl;
  249. if (defined(parentUrl)) {
  250. if (!defined(availability)) {
  251. console.log('A layer.json can\'t have a parentUrl if it does\'t have an available array.');
  252. return when.resolve();
  253. }
  254. lastResource = lastResource.getDerivedResource({
  255. url: parentUrl
  256. });
  257. lastResource.appendForwardSlash(); // Terrain always expects a directory
  258. layerJsonResource = lastResource.getDerivedResource({
  259. url: 'layer.json'
  260. });
  261. var parentMetadata = layerJsonResource.fetchJson();
  262. return when(parentMetadata, parseMetadataSuccess, parseMetadataFailure);
  263. }
  264. return when.resolve();
  265. }
  266. function parseMetadataFailure(data) {
  267. var message = 'An error occurred while accessing ' + layerJsonResource.url + '.';
  268. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestLayerJson);
  269. }
  270. function metadataSuccess(data) {
  271. parseMetadataSuccess(data)
  272. .then(function() {
  273. if (defined(metadataError)) {
  274. return;
  275. }
  276. var length = overallAvailability.length;
  277. if (length > 0) {
  278. var availability = that._availability = new TileAvailability(that._tilingScheme, overallMaxZoom);
  279. for (var level = 0; level < length; ++level) {
  280. var levelRanges = overallAvailability[level];
  281. for (var i = 0; i < levelRanges.length; ++i) {
  282. var range = levelRanges[i];
  283. availability.addAvailableTileRange(level, range[0], range[1], range[2], range[3]);
  284. }
  285. }
  286. }
  287. if (attribution.length > 0) {
  288. var layerJsonCredit = new Credit(attribution);
  289. if (defined(that._tileCredits)) {
  290. that._tileCredits.push(layerJsonCredit);
  291. } else {
  292. that._tileCredits = [layerJsonCredit];
  293. }
  294. }
  295. that._ready = true;
  296. that._readyPromise.resolve(true);
  297. });
  298. }
  299. function metadataFailure(data) {
  300. // If the metadata is not found, assume this is a pre-metadata heightmap tileset.
  301. if (defined(data) && data.statusCode === 404) {
  302. metadataSuccess({
  303. tilejson: '2.1.0',
  304. format : 'heightmap-1.0',
  305. version : '1.0.0',
  306. scheme : 'tms',
  307. tiles : [
  308. '{z}/{x}/{y}.terrain?v={version}'
  309. ]
  310. });
  311. return;
  312. }
  313. parseMetadataFailure(data);
  314. }
  315. function requestLayerJson() {
  316. when(layerJsonResource.fetchJson())
  317. .then(metadataSuccess)
  318. .otherwise(metadataFailure);
  319. }
  320. }
  321. /**
  322. * When using the Quantized-Mesh format, a tile may be returned that includes additional extensions, such as PerVertexNormals, watermask, etc.
  323. * This enumeration defines the unique identifiers for each type of extension data that has been appended to the standard mesh data.
  324. *
  325. * @exports QuantizedMeshExtensionIds
  326. * @see CesiumTerrainProvider
  327. * @private
  328. */
  329. var QuantizedMeshExtensionIds = {
  330. /**
  331. * Oct-Encoded Per-Vertex Normals are included as an extension to the tile mesh
  332. *
  333. * @type {Number}
  334. * @constant
  335. * @default 1
  336. */
  337. OCT_VERTEX_NORMALS: 1,
  338. /**
  339. * A watermask is included as an extension to the tile mesh
  340. *
  341. * @type {Number}
  342. * @constant
  343. * @default 2
  344. */
  345. WATER_MASK: 2,
  346. /**
  347. * A json object contain metadata about the tile
  348. *
  349. * @type {Number}
  350. * @constant
  351. * @default 4
  352. */
  353. METADATA: 4
  354. };
  355. function getRequestHeader(extensionsList) {
  356. if (!defined(extensionsList) || extensionsList.length === 0) {
  357. return {
  358. Accept : 'application/vnd.quantized-mesh,application/octet-stream;q=0.9,*/*;q=0.01'
  359. };
  360. }
  361. var extensions = extensionsList.join('-');
  362. return {
  363. Accept : 'application/vnd.quantized-mesh;extensions=' + extensions + ',application/octet-stream;q=0.9,*/*;q=0.01'
  364. };
  365. }
  366. function createHeightmapTerrainData(provider, buffer, level, x, y, tmsY) {
  367. var heightBuffer = new Uint16Array(buffer, 0, provider._heightmapWidth * provider._heightmapWidth);
  368. return new HeightmapTerrainData({
  369. buffer : heightBuffer,
  370. childTileMask : new Uint8Array(buffer, heightBuffer.byteLength, 1)[0],
  371. waterMask : new Uint8Array(buffer, heightBuffer.byteLength + 1, buffer.byteLength - heightBuffer.byteLength - 1),
  372. width : provider._heightmapWidth,
  373. height : provider._heightmapWidth,
  374. structure : provider._heightmapStructure,
  375. credits: provider._tileCredits
  376. });
  377. }
  378. function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layer) {
  379. var littleEndianExtensionSize = layer.littleEndianExtensionSize;
  380. var pos = 0;
  381. var cartesian3Elements = 3;
  382. var boundingSphereElements = cartesian3Elements + 1;
  383. var cartesian3Length = Float64Array.BYTES_PER_ELEMENT * cartesian3Elements;
  384. var boundingSphereLength = Float64Array.BYTES_PER_ELEMENT * boundingSphereElements;
  385. var encodedVertexElements = 3;
  386. var encodedVertexLength = Uint16Array.BYTES_PER_ELEMENT * encodedVertexElements;
  387. var triangleElements = 3;
  388. var bytesPerIndex = Uint16Array.BYTES_PER_ELEMENT;
  389. var triangleLength = bytesPerIndex * triangleElements;
  390. var view = new DataView(buffer);
  391. var center = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true));
  392. pos += cartesian3Length;
  393. var minimumHeight = view.getFloat32(pos, true);
  394. pos += Float32Array.BYTES_PER_ELEMENT;
  395. var maximumHeight = view.getFloat32(pos, true);
  396. pos += Float32Array.BYTES_PER_ELEMENT;
  397. var boundingSphere = new BoundingSphere(
  398. new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true)),
  399. view.getFloat64(pos + cartesian3Length, true));
  400. pos += boundingSphereLength;
  401. var horizonOcclusionPoint = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true));
  402. pos += cartesian3Length;
  403. var vertexCount = view.getUint32(pos, true);
  404. pos += Uint32Array.BYTES_PER_ELEMENT;
  405. var encodedVertexBuffer = new Uint16Array(buffer, pos, vertexCount * 3);
  406. pos += vertexCount * encodedVertexLength;
  407. if (vertexCount > 64 * 1024) {
  408. // More than 64k vertices, so indices are 32-bit.
  409. bytesPerIndex = Uint32Array.BYTES_PER_ELEMENT;
  410. triangleLength = bytesPerIndex * triangleElements;
  411. }
  412. // Decode the vertex buffer.
  413. var uBuffer = encodedVertexBuffer.subarray(0, vertexCount);
  414. var vBuffer = encodedVertexBuffer.subarray(vertexCount, 2 * vertexCount);
  415. var heightBuffer = encodedVertexBuffer.subarray(vertexCount * 2, 3 * vertexCount);
  416. AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer);
  417. // skip over any additional padding that was added for 2/4 byte alignment
  418. if (pos % bytesPerIndex !== 0) {
  419. pos += (bytesPerIndex - (pos % bytesPerIndex));
  420. }
  421. var triangleCount = view.getUint32(pos, true);
  422. pos += Uint32Array.BYTES_PER_ELEMENT;
  423. var indices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, triangleCount * triangleElements);
  424. pos += triangleCount * triangleLength;
  425. // High water mark decoding based on decompressIndices_ in webgl-loader's loader.js.
  426. // https://code.google.com/p/webgl-loader/source/browse/trunk/samples/loader.js?r=99#55
  427. // Copyright 2012 Google Inc., Apache 2.0 license.
  428. var highest = 0;
  429. var length = indices.length;
  430. for (var i = 0; i < length; ++i) {
  431. var code = indices[i];
  432. indices[i] = highest - code;
  433. if (code === 0) {
  434. ++highest;
  435. }
  436. }
  437. var westVertexCount = view.getUint32(pos, true);
  438. pos += Uint32Array.BYTES_PER_ELEMENT;
  439. var westIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, westVertexCount);
  440. pos += westVertexCount * bytesPerIndex;
  441. var southVertexCount = view.getUint32(pos, true);
  442. pos += Uint32Array.BYTES_PER_ELEMENT;
  443. var southIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, southVertexCount);
  444. pos += southVertexCount * bytesPerIndex;
  445. var eastVertexCount = view.getUint32(pos, true);
  446. pos += Uint32Array.BYTES_PER_ELEMENT;
  447. var eastIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, eastVertexCount);
  448. pos += eastVertexCount * bytesPerIndex;
  449. var northVertexCount = view.getUint32(pos, true);
  450. pos += Uint32Array.BYTES_PER_ELEMENT;
  451. var northIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, northVertexCount);
  452. pos += northVertexCount * bytesPerIndex;
  453. var encodedNormalBuffer;
  454. var waterMaskBuffer;
  455. while (pos < view.byteLength) {
  456. var extensionId = view.getUint8(pos, true);
  457. pos += Uint8Array.BYTES_PER_ELEMENT;
  458. var extensionLength = view.getUint32(pos, littleEndianExtensionSize);
  459. pos += Uint32Array.BYTES_PER_ELEMENT;
  460. if (extensionId === QuantizedMeshExtensionIds.OCT_VERTEX_NORMALS && provider._requestVertexNormals) {
  461. encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2);
  462. } else if (extensionId === QuantizedMeshExtensionIds.WATER_MASK && provider._requestWaterMask) {
  463. waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength);
  464. } else if (extensionId === QuantizedMeshExtensionIds.METADATA && provider._requestMetadata) {
  465. var stringLength = view.getUint32(pos, true);
  466. if (stringLength > 0) {
  467. var jsonString =
  468. getStringFromTypedArray(new Uint8Array(buffer), pos + Uint32Array.BYTES_PER_ELEMENT, stringLength);
  469. var metadata = JSON.parse(jsonString);
  470. var availableTiles = metadata.available;
  471. if (defined(availableTiles)) {
  472. for (var offset = 0; offset < availableTiles.length; ++offset) {
  473. var availableLevel = level + offset + 1;
  474. var rangesAtLevel = availableTiles[offset];
  475. var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(availableLevel);
  476. for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) {
  477. var range = rangesAtLevel[rangeIndex];
  478. var yStart = yTiles - range.endY - 1;
  479. var yEnd = yTiles - range.startY - 1;
  480. provider.availability.addAvailableTileRange(availableLevel, range.startX, yStart, range.endX, yEnd);
  481. layer.availability.addAvailableTileRange(availableLevel, range.startX, yStart, range.endX, yEnd);
  482. }
  483. }
  484. }
  485. }
  486. layer.availabilityTilesLoaded.addAvailableTileRange(level, x, y, x, y);
  487. }
  488. pos += extensionLength;
  489. }
  490. var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0;
  491. var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level);
  492. var orientedBoundingBox;
  493. if (rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) {
  494. // Here, rectangle.width < pi/2, and rectangle.height < pi
  495. // (though it would still work with rectangle.width up to pi)
  496. // The skirt is not included in the OBB computation. If this ever
  497. // causes any rendering artifacts (cracks), they are expected to be
  498. // minor and in the corners of the screen. It's possible that this
  499. // might need to be changed - just change to `minimumHeight - skirtHeight`
  500. // A similar change might also be needed in `upsampleQuantizedTerrainMesh.js`.
  501. orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, provider._tilingScheme.ellipsoid);
  502. }
  503. return new QuantizedMeshTerrainData({
  504. center : center,
  505. minimumHeight : minimumHeight,
  506. maximumHeight : maximumHeight,
  507. boundingSphere : boundingSphere,
  508. orientedBoundingBox : orientedBoundingBox,
  509. horizonOcclusionPoint : horizonOcclusionPoint,
  510. quantizedVertices : encodedVertexBuffer,
  511. encodedNormals : encodedNormalBuffer,
  512. indices : indices,
  513. westIndices : westIndices,
  514. southIndices : southIndices,
  515. eastIndices : eastIndices,
  516. northIndices : northIndices,
  517. westSkirtHeight : skirtHeight,
  518. southSkirtHeight : skirtHeight,
  519. eastSkirtHeight : skirtHeight,
  520. northSkirtHeight : skirtHeight,
  521. childTileMask: provider.availability.computeChildMaskForTile(level, x, y),
  522. waterMask: waterMaskBuffer,
  523. credits: provider._tileCredits
  524. });
  525. }
  526. /**
  527. * Requests the geometry for a given tile. This function should not be called before
  528. * {@link CesiumTerrainProvider#ready} returns true. The result must include terrain data and
  529. * may optionally include a water mask and an indication of which child tiles are available.
  530. *
  531. * @param {Number} x The X coordinate of the tile for which to request geometry.
  532. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  533. * @param {Number} level The level of the tile for which to request geometry.
  534. * @param {Request} [request] The request object. Intended for internal use only.
  535. *
  536. * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry. If this method
  537. * returns undefined instead of a promise, it is an indication that too many requests are already
  538. * pending and the request will be retried later.
  539. *
  540. * @exception {DeveloperError} This function must not be called before {@link CesiumTerrainProvider#ready}
  541. * returns true.
  542. */
  543. CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) {
  544. //>>includeStart('debug', pragmas.debug)
  545. if (!this._ready) {
  546. throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.');
  547. }
  548. //>>includeEnd('debug');
  549. var layers = this._layers;
  550. var layerToUse;
  551. var layerCount = layers.length;
  552. if (layerCount === 1) { // Optimized path for single layers
  553. layerToUse = layers[0];
  554. } else {
  555. for (var i = 0; i < layerCount; ++i) {
  556. var layer = layers[i];
  557. if (!defined(layer.availability) || layer.availability.isTileAvailable(level, x, y)) {
  558. layerToUse = layer;
  559. break;
  560. }
  561. }
  562. }
  563. return requestTileGeometry(this, x, y, level, layerToUse, request);
  564. };
  565. function requestTileGeometry(provider, x, y, level, layerToUse, request) {
  566. if (!defined(layerToUse)) {
  567. return when.reject(new RuntimeError('Terrain tile doesn\'t exist'));
  568. }
  569. var urlTemplates = layerToUse.tileUrlTemplates;
  570. if (urlTemplates.length === 0) {
  571. return undefined;
  572. }
  573. var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(level);
  574. var tmsY = (yTiles - y - 1);
  575. var extensionList = [];
  576. if (provider._requestVertexNormals && layerToUse.hasVertexNormals) {
  577. extensionList.push(layerToUse.littleEndianExtensionSize ? 'octvertexnormals' : 'vertexnormals');
  578. }
  579. if (provider._requestWaterMask && layerToUse.hasWaterMask) {
  580. extensionList.push('watermask');
  581. }
  582. if (provider._requestMetadata && layerToUse.hasMetadata) {
  583. extensionList.push('metadata');
  584. }
  585. var headers;
  586. var query;
  587. var url = urlTemplates[(x + tmsY + level) % urlTemplates.length];
  588. var resource = layerToUse.resource;
  589. if (defined(resource._ionEndpoint) && !defined(resource._ionEndpoint.externalType)) {
  590. // ion uses query paremeters to request extensions
  591. if (extensionList.length !== 0) {
  592. query = { extensions: extensionList.join('-') };
  593. }
  594. headers = getRequestHeader(undefined);
  595. } else {
  596. //All other terrain servers
  597. headers = getRequestHeader(extensionList);
  598. }
  599. var promise = resource.getDerivedResource({
  600. url: url,
  601. templateValues: {
  602. version: layerToUse.version,
  603. z: level,
  604. x: x,
  605. y: tmsY
  606. },
  607. queryParameters: query,
  608. headers: headers,
  609. request: request
  610. }).fetchArrayBuffer();
  611. if (!defined(promise)) {
  612. return undefined;
  613. }
  614. return promise.then(function (buffer) {
  615. if (defined(provider._heightmapStructure)) {
  616. return createHeightmapTerrainData(provider, buffer, level, x, y, tmsY);
  617. }
  618. return createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layerToUse);
  619. });
  620. }
  621. defineProperties(CesiumTerrainProvider.prototype, {
  622. /**
  623. * Gets an event that is raised when the terrain provider encounters an asynchronous error. By subscribing
  624. * to the event, you will be notified of the error and can potentially recover from it. Event listeners
  625. * are passed an instance of {@link TileProviderError}.
  626. * @memberof CesiumTerrainProvider.prototype
  627. * @type {Event}
  628. */
  629. errorEvent : {
  630. get : function() {
  631. return this._errorEvent;
  632. }
  633. },
  634. /**
  635. * Gets the credit to display when this terrain provider is active. Typically this is used to credit
  636. * the source of the terrain. This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
  637. * @memberof CesiumTerrainProvider.prototype
  638. * @type {Credit}
  639. */
  640. credit : {
  641. get : function() {
  642. //>>includeStart('debug', pragmas.debug)
  643. if (!this._ready) {
  644. throw new DeveloperError('credit must not be called before the terrain provider is ready.');
  645. }
  646. //>>includeEnd('debug');
  647. return this._credit;
  648. }
  649. },
  650. /**
  651. * Gets the tiling scheme used by this provider. This function should
  652. * not be called before {@link CesiumTerrainProvider#ready} returns true.
  653. * @memberof CesiumTerrainProvider.prototype
  654. * @type {GeographicTilingScheme}
  655. */
  656. tilingScheme : {
  657. get : function() {
  658. //>>includeStart('debug', pragmas.debug)
  659. if (!this._ready) {
  660. throw new DeveloperError('tilingScheme must not be called before the terrain provider is ready.');
  661. }
  662. //>>includeEnd('debug');
  663. return this._tilingScheme;
  664. }
  665. },
  666. /**
  667. * Gets a value indicating whether or not the provider is ready for use.
  668. * @memberof CesiumTerrainProvider.prototype
  669. * @type {Boolean}
  670. */
  671. ready : {
  672. get : function() {
  673. return this._ready;
  674. }
  675. },
  676. /**
  677. * Gets a promise that resolves to true when the provider is ready for use.
  678. * @memberof CesiumTerrainProvider.prototype
  679. * @type {Promise.<Boolean>}
  680. * @readonly
  681. */
  682. readyPromise : {
  683. get : function() {
  684. return this._readyPromise.promise;
  685. }
  686. },
  687. /**
  688. * Gets a value indicating whether or not the provider includes a water mask. The water mask
  689. * indicates which areas of the globe are water rather than land, so they can be rendered
  690. * as a reflective surface with animated waves. This function should not be
  691. * called before {@link CesiumTerrainProvider#ready} returns true.
  692. * @memberof CesiumTerrainProvider.prototype
  693. * @type {Boolean}
  694. * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
  695. */
  696. hasWaterMask : {
  697. get : function() {
  698. //>>includeStart('debug', pragmas.debug)
  699. if (!this._ready) {
  700. throw new DeveloperError('hasWaterMask must not be called before the terrain provider is ready.');
  701. }
  702. //>>includeEnd('debug');
  703. return this._hasWaterMask && this._requestWaterMask;
  704. }
  705. },
  706. /**
  707. * Gets a value indicating whether or not the requested tiles include vertex normals.
  708. * This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
  709. * @memberof CesiumTerrainProvider.prototype
  710. * @type {Boolean}
  711. * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
  712. */
  713. hasVertexNormals : {
  714. get : function() {
  715. //>>includeStart('debug', pragmas.debug)
  716. if (!this._ready) {
  717. throw new DeveloperError('hasVertexNormals must not be called before the terrain provider is ready.');
  718. }
  719. //>>includeEnd('debug');
  720. // returns true if we can request vertex normals from the server
  721. return this._hasVertexNormals && this._requestVertexNormals;
  722. }
  723. },
  724. /**
  725. * Gets a value indicating whether or not the requested tiles include metadata.
  726. * This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
  727. * @memberof CesiumTerrainProvider.prototype
  728. * @type {Boolean}
  729. * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
  730. */
  731. hasMetadata : {
  732. get : function() {
  733. //>>includeStart('debug', pragmas.debug)
  734. if (!this._ready) {
  735. throw new DeveloperError('hasMetadata must not be called before the terrain provider is ready.');
  736. }
  737. //>>includeEnd('debug');
  738. // returns true if we can request metadata from the server
  739. return this._hasMetadata && this._requestMetadata;
  740. }
  741. },
  742. /**
  743. * Boolean flag that indicates if the client should request vertex normals from the server.
  744. * Vertex normals data is appended to the standard tile mesh data only if the client requests the vertex normals and
  745. * if the server provides vertex normals.
  746. * @memberof CesiumTerrainProvider.prototype
  747. * @type {Boolean}
  748. */
  749. requestVertexNormals : {
  750. get : function() {
  751. return this._requestVertexNormals;
  752. }
  753. },
  754. /**
  755. * Boolean flag that indicates if the client should request a watermask from the server.
  756. * Watermask data is appended to the standard tile mesh data only if the client requests the watermask and
  757. * if the server provides a watermask.
  758. * @memberof CesiumTerrainProvider.prototype
  759. * @type {Boolean}
  760. */
  761. requestWaterMask : {
  762. get : function() {
  763. return this._requestWaterMask;
  764. }
  765. },
  766. /**
  767. * Boolean flag that indicates if the client should request metadata from the server.
  768. * Metadata is appended to the standard tile mesh data only if the client requests the metadata and
  769. * if the server provides a metadata.
  770. * @memberof CesiumTerrainProvider.prototype
  771. * @type {Boolean}
  772. */
  773. requestMetadata : {
  774. get : function() {
  775. return this._requestMetadata;
  776. }
  777. },
  778. /**
  779. * Gets an object that can be used to determine availability of terrain from this provider, such as
  780. * at points and in rectangles. This function should not be called before
  781. * {@link CesiumTerrainProvider#ready} returns true. This property may be undefined if availability
  782. * information is not available. Note that this reflects tiles that are known to be available currently.
  783. * Additional tiles may be discovered to be available in the future, e.g. if availability information
  784. * exists deeper in the tree rather than it all being discoverable at the root. However, a tile that
  785. * is available now will not become unavailable in the future.
  786. * @memberof CesiumTerrainProvider.prototype
  787. * @type {TileAvailability}
  788. */
  789. availability : {
  790. get : function() {
  791. //>>includeStart('debug', pragmas.debug)
  792. if (!this._ready) {
  793. throw new DeveloperError('availability must not be called before the terrain provider is ready.');
  794. }
  795. //>>includeEnd('debug');
  796. return this._availability;
  797. }
  798. }
  799. });
  800. /**
  801. * Gets the maximum geometric error allowed in a tile at a given level.
  802. *
  803. * @param {Number} level The tile level for which to get the maximum geometric error.
  804. * @returns {Number} The maximum geometric error.
  805. */
  806. CesiumTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
  807. return this._levelZeroMaximumGeometricError / (1 << level);
  808. };
  809. /**
  810. * Determines whether data for a tile is available to be loaded.
  811. *
  812. * @param {Number} x The X coordinate of the tile for which to request geometry.
  813. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  814. * @param {Number} level The level of the tile for which to request geometry.
  815. * @returns {Boolean} Undefined if not supported or availability is unknown, otherwise true or false.
  816. */
  817. CesiumTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
  818. if (!defined(this._availability)) {
  819. return undefined;
  820. }
  821. if (level > this._availability._maximumLevel) {
  822. return false;
  823. }
  824. if (this._availability.isTileAvailable(level, x, y)) {
  825. // If the tile is listed as available, then we are done
  826. return true;
  827. }
  828. if (!this._hasMetadata) {
  829. // If we don't have any layers with the metadata extension then we don't have this tile
  830. return false;
  831. }
  832. var layers = this._layers;
  833. var count = layers.length;
  834. for (var i = 0; i < count; ++i) {
  835. var layerResult = checkLayer(this, x, y, level, layers[i], (i===0));
  836. if (layerResult.result) {
  837. // There is a layer that may or may not have the tile
  838. return undefined;
  839. }
  840. }
  841. return false;
  842. };
  843. /**
  844. * Makes sure we load availability data for a tile
  845. *
  846. * @param {Number} x The X coordinate of the tile for which to request geometry.
  847. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  848. * @param {Number} level The level of the tile for which to request geometry.
  849. * @returns {undefined|Promise} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded
  850. */
  851. CesiumTerrainProvider.prototype.loadTileDataAvailability = function(x, y, level) {
  852. if (!defined(this._availability) || (level > this._availability._maximumLevel) ||
  853. (this._availability.isTileAvailable(level, x, y) || (!this._hasMetadata))) {
  854. // We know the tile is either available or not available so nothing to wait on
  855. return undefined;
  856. }
  857. var layers = this._layers;
  858. var count = layers.length;
  859. for (var i = 0; i < count; ++i) {
  860. var layerResult = checkLayer(this, x, y, level, layers[i], (i===0));
  861. if (defined(layerResult.promise)) {
  862. return layerResult.promise;
  863. }
  864. }
  865. };
  866. function getAvailabilityTile(layer, x, y, level) {
  867. if (level === 0) {
  868. return;
  869. }
  870. var availabilityLevels = layer.availabilityLevels;
  871. var parentLevel = (level % availabilityLevels === 0) ?
  872. (level - availabilityLevels) : ((level / availabilityLevels) | 0) * availabilityLevels;
  873. var divisor = 1 << (level - parentLevel);
  874. var parentX = (x / divisor) | 0;
  875. var parentY = (y / divisor) | 0;
  876. return {
  877. level: parentLevel,
  878. x: parentX,
  879. y: parentY
  880. };
  881. }
  882. function checkLayer(provider, x, y, level, layer, topLayer) {
  883. if (!defined(layer.availabilityLevels)) {
  884. // It's definitely not in this layer
  885. return {
  886. result: false
  887. };
  888. }
  889. var cacheKey;
  890. var deleteFromCache = function () {
  891. delete layer.availabilityPromiseCache[cacheKey];
  892. };
  893. var availabilityTilesLoaded = layer.availabilityTilesLoaded;
  894. var availability = layer.availability;
  895. var tile = getAvailabilityTile(layer, x, y, level);
  896. while(defined(tile)) {
  897. if (availability.isTileAvailable(tile.level, tile.x, tile.y) &&
  898. !availabilityTilesLoaded.isTileAvailable(tile.level, tile.x, tile.y))
  899. {
  900. var requestPromise;
  901. if (!topLayer) {
  902. cacheKey = tile.level + '-' + tile.x + '-' + tile.y;
  903. requestPromise = layer.availabilityPromiseCache[cacheKey];
  904. if (!defined(requestPromise)) {
  905. // For cutout terrain, if this isn't the top layer the availability tiles
  906. // may never get loaded, so request it here.
  907. var request = new Request({
  908. throttle: true,
  909. throttleByServer: true,
  910. type: RequestType.TERRAIN
  911. });
  912. requestPromise = requestTileGeometry(provider, tile.x, tile.y, tile.level, layer, request);
  913. if (defined(requestPromise)) {
  914. layer.availabilityPromiseCache[cacheKey] = requestPromise;
  915. requestPromise.then(deleteFromCache);
  916. }
  917. }
  918. }
  919. // The availability tile is available, but not loaded, so there
  920. // is still a chance that it may become available at some point
  921. return {
  922. result: true,
  923. promise: requestPromise
  924. };
  925. }
  926. tile = getAvailabilityTile(layer, tile.x, tile.y, tile.level);
  927. }
  928. return {
  929. result: false
  930. };
  931. }
  932. // Used for testing
  933. CesiumTerrainProvider._getAvailabilityTile = getAvailabilityTile;
  934. export default CesiumTerrainProvider;