CesiumTerrainProvider.js 46 KB

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