CesiumTerrainProvider.js 40 KB

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