CesiumTerrainProvider.js 41 KB

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