You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CesiumTerrainProvider.js 44KB

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