123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323 |
- import AttributeCompression from "./AttributeCompression.js";
- import BoundingSphere from "./BoundingSphere.js";
- import Cartesian3 from "./Cartesian3.js";
- import Credit from "./Credit.js";
- import defaultValue from "./defaultValue.js";
- import defer from "./defer.js";
- import defined from "./defined.js";
- import DeveloperError from "./DeveloperError.js";
- import Event from "./Event.js";
- import GeographicTilingScheme from "./GeographicTilingScheme.js";
- import WebMercatorTilingScheme from "./WebMercatorTilingScheme.js";
- import getJsonFromTypedArray from "./getJsonFromTypedArray.js";
- import HeightmapTerrainData from "./HeightmapTerrainData.js";
- import IndexDatatype from "./IndexDatatype.js";
- import OrientedBoundingBox from "./OrientedBoundingBox.js";
- import QuantizedMeshTerrainData from "./QuantizedMeshTerrainData.js";
- import Request from "./Request.js";
- import RequestType from "./RequestType.js";
- import Resource from "./Resource.js";
- import RuntimeError from "./RuntimeError.js";
- import TerrainProvider from "./TerrainProvider.js";
- import TileAvailability from "./TileAvailability.js";
- import TileProviderError from "./TileProviderError.js";
- function LayerInformation(layer) {
- this.resource = layer.resource;
- this.version = layer.version;
- this.isHeightmap = layer.isHeightmap;
- this.tileUrlTemplates = layer.tileUrlTemplates;
- this.availability = layer.availability;
- this.hasVertexNormals = layer.hasVertexNormals;
- this.hasWaterMask = layer.hasWaterMask;
- this.hasMetadata = layer.hasMetadata;
- this.availabilityLevels = layer.availabilityLevels;
- this.availabilityTilesLoaded = layer.availabilityTilesLoaded;
- this.littleEndianExtensionSize = layer.littleEndianExtensionSize;
- this.availabilityPromiseCache = {};
- }
- /**
- * A {@link TerrainProvider} that accesses terrain data in a Cesium terrain format.
- *
- * @alias CesiumTerrainProvider
- * @constructor
- *
- * @param {Object} options Object with the following properties:
- * @param {Resource|String|Promise<Resource>|Promise<String>} options.url The URL of the Cesium terrain server.
- * @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.
- * @param {Boolean} [options.requestWaterMask=false] Flag that indicates if the client should request per tile water masks from the server, if available.
- * @param {Boolean} [options.requestMetadata=true] Flag that indicates if the client should request per tile metadata from the server, if available.
- * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
- * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
- *
- *
- * @example
- * // Create Arctic DEM terrain with normals.
- * const viewer = new Cesium.Viewer('cesiumContainer', {
- * terrainProvider : new Cesium.CesiumTerrainProvider({
- * url : Cesium.IonResource.fromAssetId(3956),
- * requestVertexNormals : true
- * })
- * });
- *
- * @see createWorldTerrain
- * @see TerrainProvider
- */
- function CesiumTerrainProvider(options) {
- //>>includeStart('debug', pragmas.debug)
- if (!defined(options) || !defined(options.url)) {
- throw new DeveloperError("options.url is required.");
- }
- //>>includeEnd('debug');
- this._heightmapWidth = 65;
- this._heightmapStructure = undefined;
- this._hasWaterMask = false;
- this._hasVertexNormals = false;
- this._ellipsoid = options.ellipsoid;
- /**
- * Boolean flag that indicates if the client should request vertex normals from the server.
- * @type {Boolean}
- * @default false
- * @private
- */
- this._requestVertexNormals = defaultValue(
- options.requestVertexNormals,
- false
- );
- /**
- * Boolean flag that indicates if the client should request tile watermasks from the server.
- * @type {Boolean}
- * @default false
- * @private
- */
- this._requestWaterMask = defaultValue(options.requestWaterMask, false);
- /**
- * Boolean flag that indicates if the client should request tile metadata from the server.
- * @type {Boolean}
- * @default true
- * @private
- */
- this._requestMetadata = defaultValue(options.requestMetadata, true);
- this._errorEvent = new Event();
- let credit = options.credit;
- if (typeof credit === "string") {
- credit = new Credit(credit);
- }
- this._credit = credit;
- this._availability = undefined;
- const deferred = defer();
- this._ready = false;
- this._readyPromise = deferred;
- this._tileCredits = undefined;
- const that = this;
- let lastResource;
- let layerJsonResource;
- let metadataError;
- const layers = (this._layers = []);
- let attribution = "";
- const overallAvailability = [];
- let overallMaxZoom = 0;
- Promise.resolve(options.url)
- .then(function (url) {
- const resource = Resource.createIfNeeded(url);
- resource.appendForwardSlash();
- lastResource = resource;
- layerJsonResource = lastResource.getDerivedResource({
- url: "layer.json",
- });
- // ion resources have a credits property we can use for additional attribution.
- that._tileCredits = resource.credits;
- requestLayerJson();
- })
- .catch(function (e) {
- deferred.reject(e);
- });
- function parseMetadataSuccess(data) {
- let message;
- if (!data.format) {
- message = "The tile format is not specified in the layer.json file.";
- metadataError = TileProviderError.handleError(
- metadataError,
- that,
- that._errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestLayerJson
- );
- return;
- }
- if (!data.tiles || data.tiles.length === 0) {
- message = "The layer.json file does not specify any tile URL templates.";
- metadataError = TileProviderError.handleError(
- metadataError,
- that,
- that._errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestLayerJson
- );
- return;
- }
- let hasVertexNormals = false;
- let hasWaterMask = false;
- let hasMetadata = false;
- let littleEndianExtensionSize = true;
- let isHeightmap = false;
- if (data.format === "heightmap-1.0") {
- isHeightmap = true;
- if (!defined(that._heightmapStructure)) {
- that._heightmapStructure = {
- heightScale: 1.0 / 5.0,
- heightOffset: -1000.0,
- elementsPerHeight: 1,
- stride: 1,
- elementMultiplier: 256.0,
- isBigEndian: false,
- lowestEncodedHeight: 0,
- highestEncodedHeight: 256 * 256 - 1,
- };
- }
- hasWaterMask = true;
- that._requestWaterMask = true;
- } else if (data.format.indexOf("quantized-mesh-1.") !== 0) {
- message = `The tile format "${data.format}" is invalid or not supported.`;
- metadataError = TileProviderError.handleError(
- metadataError,
- that,
- that._errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestLayerJson
- );
- return;
- }
- const tileUrlTemplates = data.tiles;
- const maxZoom = data.maxzoom;
- overallMaxZoom = Math.max(overallMaxZoom, maxZoom);
- // Keeps track of which of the availablity containing tiles have been loaded
- if (!data.projection || data.projection === "EPSG:4326") {
- that._tilingScheme = new GeographicTilingScheme({
- numberOfLevelZeroTilesX: 2,
- numberOfLevelZeroTilesY: 1,
- ellipsoid: that._ellipsoid,
- });
- } else if (data.projection === "EPSG:3857") {
- that._tilingScheme = new WebMercatorTilingScheme({
- numberOfLevelZeroTilesX: 1,
- numberOfLevelZeroTilesY: 1,
- ellipsoid: that._ellipsoid,
- });
- } else {
- message = `The projection "${data.projection}" is invalid or not supported.`;
- metadataError = TileProviderError.handleError(
- metadataError,
- that,
- that._errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestLayerJson
- );
- return;
- }
- that._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(
- that._tilingScheme.ellipsoid,
- that._heightmapWidth,
- that._tilingScheme.getNumberOfXTilesAtLevel(0)
- );
- if (!data.scheme || data.scheme === "tms" || data.scheme === "slippyMap") {
- that._scheme = data.scheme;
- } else {
- message = `The scheme "${data.scheme}" is invalid or not supported.`;
- metadataError = TileProviderError.handleError(
- metadataError,
- that,
- that._errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestLayerJson
- );
- return;
- }
- let availabilityTilesLoaded;
- // The vertex normals defined in the 'octvertexnormals' extension is identical to the original
- // contents of the original 'vertexnormals' extension. 'vertexnormals' extension is now
- // deprecated, as the extensionLength for this extension was incorrectly using big endian.
- // We maintain backwards compatibility with the legacy 'vertexnormal' implementation
- // by setting the _littleEndianExtensionSize to false. Always prefer 'octvertexnormals'
- // over 'vertexnormals' if both extensions are supported by the server.
- if (
- defined(data.extensions) &&
- data.extensions.indexOf("octvertexnormals") !== -1
- ) {
- hasVertexNormals = true;
- } else if (
- defined(data.extensions) &&
- data.extensions.indexOf("vertexnormals") !== -1
- ) {
- hasVertexNormals = true;
- littleEndianExtensionSize = false;
- }
- if (
- defined(data.extensions) &&
- data.extensions.indexOf("watermask") !== -1
- ) {
- hasWaterMask = true;
- }
- if (
- defined(data.extensions) &&
- data.extensions.indexOf("metadata") !== -1
- ) {
- hasMetadata = true;
- }
- const availabilityLevels = data.metadataAvailability;
- const availableTiles = data.available;
- let availability;
- if (defined(availableTiles) && !defined(availabilityLevels)) {
- availability = new TileAvailability(
- that._tilingScheme,
- availableTiles.length
- );
- for (let level = 0; level < availableTiles.length; ++level) {
- const rangesAtLevel = availableTiles[level];
- const yTiles = that._tilingScheme.getNumberOfYTilesAtLevel(level);
- if (!defined(overallAvailability[level])) {
- overallAvailability[level] = [];
- }
- for (
- let rangeIndex = 0;
- rangeIndex < rangesAtLevel.length;
- ++rangeIndex
- ) {
- const range = rangesAtLevel[rangeIndex];
- const yStart = yTiles - range.endY - 1;
- const yEnd = yTiles - range.startY - 1;
- overallAvailability[level].push([
- range.startX,
- yStart,
- range.endX,
- yEnd,
- ]);
- availability.addAvailableTileRange(
- level,
- range.startX,
- yStart,
- range.endX,
- yEnd
- );
- }
- }
- } else if (defined(availabilityLevels)) {
- availabilityTilesLoaded = new TileAvailability(
- that._tilingScheme,
- maxZoom
- );
- availability = new TileAvailability(that._tilingScheme, maxZoom);
- overallAvailability[0] = [[0, 0, 1, 0]];
- availability.addAvailableTileRange(0, 0, 0, 1, 0);
- }
- that._hasWaterMask = that._hasWaterMask || hasWaterMask;
- that._hasVertexNormals = that._hasVertexNormals || hasVertexNormals;
- that._hasMetadata = that._hasMetadata || hasMetadata;
- if (defined(data.attribution)) {
- if (attribution.length > 0) {
- attribution += " ";
- }
- attribution += data.attribution;
- }
- layers.push(
- new LayerInformation({
- resource: lastResource,
- version: data.version,
- isHeightmap: isHeightmap,
- tileUrlTemplates: tileUrlTemplates,
- availability: availability,
- hasVertexNormals: hasVertexNormals,
- hasWaterMask: hasWaterMask,
- hasMetadata: hasMetadata,
- availabilityLevels: availabilityLevels,
- availabilityTilesLoaded: availabilityTilesLoaded,
- littleEndianExtensionSize: littleEndianExtensionSize,
- })
- );
- const parentUrl = data.parentUrl;
- if (defined(parentUrl)) {
- if (!defined(availability)) {
- console.log(
- "A layer.json can't have a parentUrl if it does't have an available array."
- );
- return Promise.resolve();
- }
- lastResource = lastResource.getDerivedResource({
- url: parentUrl,
- });
- lastResource.appendForwardSlash(); // Terrain always expects a directory
- layerJsonResource = lastResource.getDerivedResource({
- url: "layer.json",
- });
- const parentMetadata = layerJsonResource.fetchJson();
- return Promise.resolve(parentMetadata)
- .then(parseMetadataSuccess)
- .catch(parseMetadataFailure);
- }
- return Promise.resolve();
- }
- function parseMetadataFailure(data) {
- const message = `An error occurred while accessing ${layerJsonResource.url}.`;
- metadataError = TileProviderError.handleError(
- metadataError,
- that,
- that._errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestLayerJson
- );
- }
- function metadataSuccess(data) {
- parseMetadataSuccess(data).then(function () {
- if (defined(metadataError)) {
- return;
- }
- const length = overallAvailability.length;
- if (length > 0) {
- const availability = (that._availability = new TileAvailability(
- that._tilingScheme,
- overallMaxZoom
- ));
- for (let level = 0; level < length; ++level) {
- const levelRanges = overallAvailability[level];
- for (let i = 0; i < levelRanges.length; ++i) {
- const range = levelRanges[i];
- availability.addAvailableTileRange(
- level,
- range[0],
- range[1],
- range[2],
- range[3]
- );
- }
- }
- }
- if (attribution.length > 0) {
- const layerJsonCredit = new Credit(attribution);
- if (defined(that._tileCredits)) {
- that._tileCredits.push(layerJsonCredit);
- } else {
- that._tileCredits = [layerJsonCredit];
- }
- }
- that._ready = true;
- that._readyPromise.resolve(true);
- });
- }
- function metadataFailure(data) {
- // If the metadata is not found, assume this is a pre-metadata heightmap tileset.
- if (defined(data) && data.statusCode === 404) {
- metadataSuccess({
- tilejson: "2.1.0",
- format: "heightmap-1.0",
- version: "1.0.0",
- scheme: "tms",
- tiles: ["{z}/{x}/{y}.terrain?v={version}"],
- });
- return;
- }
- parseMetadataFailure(data);
- }
- function requestLayerJson() {
- Promise.resolve(layerJsonResource.fetchJson())
- .then(metadataSuccess)
- .catch(metadataFailure);
- }
- }
- /**
- * When using the Quantized-Mesh format, a tile may be returned that includes additional extensions, such as PerVertexNormals, watermask, etc.
- * This enumeration defines the unique identifiers for each type of extension data that has been appended to the standard mesh data.
- *
- * @namespace QuantizedMeshExtensionIds
- * @see CesiumTerrainProvider
- * @private
- */
- const QuantizedMeshExtensionIds = {
- /**
- * Oct-Encoded Per-Vertex Normals are included as an extension to the tile mesh
- *
- * @type {Number}
- * @constant
- * @default 1
- */
- OCT_VERTEX_NORMALS: 1,
- /**
- * A watermask is included as an extension to the tile mesh
- *
- * @type {Number}
- * @constant
- * @default 2
- */
- WATER_MASK: 2,
- /**
- * A json object contain metadata about the tile
- *
- * @type {Number}
- * @constant
- * @default 4
- */
- METADATA: 4,
- };
- function getRequestHeader(extensionsList) {
- if (!defined(extensionsList) || extensionsList.length === 0) {
- return {
- Accept:
- "application/vnd.quantized-mesh,application/octet-stream;q=0.9,*/*;q=0.01",
- };
- }
- const extensions = extensionsList.join("-");
- return {
- Accept: `application/vnd.quantized-mesh;extensions=${extensions},application/octet-stream;q=0.9,*/*;q=0.01`,
- };
- }
- function createHeightmapTerrainData(provider, buffer, level, x, y) {
- const heightBuffer = new Uint16Array(
- buffer,
- 0,
- provider._heightmapWidth * provider._heightmapWidth
- );
- return new HeightmapTerrainData({
- buffer: heightBuffer,
- childTileMask: new Uint8Array(buffer, heightBuffer.byteLength, 1)[0],
- waterMask: new Uint8Array(
- buffer,
- heightBuffer.byteLength + 1,
- buffer.byteLength - heightBuffer.byteLength - 1
- ),
- width: provider._heightmapWidth,
- height: provider._heightmapWidth,
- structure: provider._heightmapStructure,
- credits: provider._tileCredits,
- });
- }
- function createQuantizedMeshTerrainData(provider, buffer, level, x, y, layer) {
- const littleEndianExtensionSize = layer.littleEndianExtensionSize;
- let pos = 0;
- const cartesian3Elements = 3;
- const boundingSphereElements = cartesian3Elements + 1;
- const cartesian3Length = Float64Array.BYTES_PER_ELEMENT * cartesian3Elements;
- const boundingSphereLength =
- Float64Array.BYTES_PER_ELEMENT * boundingSphereElements;
- const encodedVertexElements = 3;
- const encodedVertexLength =
- Uint16Array.BYTES_PER_ELEMENT * encodedVertexElements;
- const triangleElements = 3;
- let bytesPerIndex = Uint16Array.BYTES_PER_ELEMENT;
- let triangleLength = bytesPerIndex * triangleElements;
- const view = new DataView(buffer);
- const center = new Cartesian3(
- view.getFloat64(pos, true),
- view.getFloat64(pos + 8, true),
- view.getFloat64(pos + 16, true)
- );
- pos += cartesian3Length;
- const minimumHeight = view.getFloat32(pos, true);
- pos += Float32Array.BYTES_PER_ELEMENT;
- const maximumHeight = view.getFloat32(pos, true);
- pos += Float32Array.BYTES_PER_ELEMENT;
- const boundingSphere = new BoundingSphere(
- new Cartesian3(
- view.getFloat64(pos, true),
- view.getFloat64(pos + 8, true),
- view.getFloat64(pos + 16, true)
- ),
- view.getFloat64(pos + cartesian3Length, true)
- );
- pos += boundingSphereLength;
- const horizonOcclusionPoint = new Cartesian3(
- view.getFloat64(pos, true),
- view.getFloat64(pos + 8, true),
- view.getFloat64(pos + 16, true)
- );
- pos += cartesian3Length;
- const vertexCount = view.getUint32(pos, true);
- pos += Uint32Array.BYTES_PER_ELEMENT;
- const encodedVertexBuffer = new Uint16Array(buffer, pos, vertexCount * 3);
- pos += vertexCount * encodedVertexLength;
- if (vertexCount > 64 * 1024) {
- // More than 64k vertices, so indices are 32-bit.
- bytesPerIndex = Uint32Array.BYTES_PER_ELEMENT;
- triangleLength = bytesPerIndex * triangleElements;
- }
- // Decode the vertex buffer.
- const uBuffer = encodedVertexBuffer.subarray(0, vertexCount);
- const vBuffer = encodedVertexBuffer.subarray(vertexCount, 2 * vertexCount);
- const heightBuffer = encodedVertexBuffer.subarray(
- vertexCount * 2,
- 3 * vertexCount
- );
- AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer);
- // skip over any additional padding that was added for 2/4 byte alignment
- if (pos % bytesPerIndex !== 0) {
- pos += bytesPerIndex - (pos % bytesPerIndex);
- }
- const triangleCount = view.getUint32(pos, true);
- pos += Uint32Array.BYTES_PER_ELEMENT;
- const indices = IndexDatatype.createTypedArrayFromArrayBuffer(
- vertexCount,
- buffer,
- pos,
- triangleCount * triangleElements
- );
- pos += triangleCount * triangleLength;
- // High water mark decoding based on decompressIndices_ in webgl-loader's loader.js.
- // https://code.google.com/p/webgl-loader/source/browse/trunk/samples/loader.js?r=99#55
- // Copyright 2012 Google Inc., Apache 2.0 license.
- let highest = 0;
- const length = indices.length;
- for (let i = 0; i < length; ++i) {
- const code = indices[i];
- indices[i] = highest - code;
- if (code === 0) {
- ++highest;
- }
- }
- const westVertexCount = view.getUint32(pos, true);
- pos += Uint32Array.BYTES_PER_ELEMENT;
- const westIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
- vertexCount,
- buffer,
- pos,
- westVertexCount
- );
- pos += westVertexCount * bytesPerIndex;
- const southVertexCount = view.getUint32(pos, true);
- pos += Uint32Array.BYTES_PER_ELEMENT;
- const southIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
- vertexCount,
- buffer,
- pos,
- southVertexCount
- );
- pos += southVertexCount * bytesPerIndex;
- const eastVertexCount = view.getUint32(pos, true);
- pos += Uint32Array.BYTES_PER_ELEMENT;
- const eastIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
- vertexCount,
- buffer,
- pos,
- eastVertexCount
- );
- pos += eastVertexCount * bytesPerIndex;
- const northVertexCount = view.getUint32(pos, true);
- pos += Uint32Array.BYTES_PER_ELEMENT;
- const northIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
- vertexCount,
- buffer,
- pos,
- northVertexCount
- );
- pos += northVertexCount * bytesPerIndex;
- let encodedNormalBuffer;
- let waterMaskBuffer;
- while (pos < view.byteLength) {
- const extensionId = view.getUint8(pos, true);
- pos += Uint8Array.BYTES_PER_ELEMENT;
- const extensionLength = view.getUint32(pos, littleEndianExtensionSize);
- pos += Uint32Array.BYTES_PER_ELEMENT;
- if (
- extensionId === QuantizedMeshExtensionIds.OCT_VERTEX_NORMALS &&
- provider._requestVertexNormals
- ) {
- encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2);
- } else if (
- extensionId === QuantizedMeshExtensionIds.WATER_MASK &&
- provider._requestWaterMask
- ) {
- waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength);
- } else if (
- extensionId === QuantizedMeshExtensionIds.METADATA &&
- provider._requestMetadata
- ) {
- const stringLength = view.getUint32(pos, true);
- if (stringLength > 0) {
- const metadata = getJsonFromTypedArray(
- new Uint8Array(buffer),
- pos + Uint32Array.BYTES_PER_ELEMENT,
- stringLength
- );
- const availableTiles = metadata.available;
- if (defined(availableTiles)) {
- for (let offset = 0; offset < availableTiles.length; ++offset) {
- const availableLevel = level + offset + 1;
- const rangesAtLevel = availableTiles[offset];
- const yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(
- availableLevel
- );
- for (
- let rangeIndex = 0;
- rangeIndex < rangesAtLevel.length;
- ++rangeIndex
- ) {
- const range = rangesAtLevel[rangeIndex];
- const yStart = yTiles - range.endY - 1;
- const yEnd = yTiles - range.startY - 1;
- provider.availability.addAvailableTileRange(
- availableLevel,
- range.startX,
- yStart,
- range.endX,
- yEnd
- );
- layer.availability.addAvailableTileRange(
- availableLevel,
- range.startX,
- yStart,
- range.endX,
- yEnd
- );
- }
- }
- }
- }
- layer.availabilityTilesLoaded.addAvailableTileRange(level, x, y, x, y);
- }
- pos += extensionLength;
- }
- const skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0;
- // The skirt is not included in the OBB computation. If this ever
- // causes any rendering artifacts (cracks), they are expected to be
- // minor and in the corners of the screen. It's possible that this
- // might need to be changed - just change to `minimumHeight - skirtHeight`
- // A similar change might also be needed in `upsampleQuantizedTerrainMesh.js`.
- const rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level);
- const orientedBoundingBox = OrientedBoundingBox.fromRectangle(
- rectangle,
- minimumHeight,
- maximumHeight,
- provider._tilingScheme.ellipsoid
- );
- return new QuantizedMeshTerrainData({
- center: center,
- minimumHeight: minimumHeight,
- maximumHeight: maximumHeight,
- boundingSphere: boundingSphere,
- orientedBoundingBox: orientedBoundingBox,
- horizonOcclusionPoint: horizonOcclusionPoint,
- quantizedVertices: encodedVertexBuffer,
- encodedNormals: encodedNormalBuffer,
- indices: indices,
- westIndices: westIndices,
- southIndices: southIndices,
- eastIndices: eastIndices,
- northIndices: northIndices,
- westSkirtHeight: skirtHeight,
- southSkirtHeight: skirtHeight,
- eastSkirtHeight: skirtHeight,
- northSkirtHeight: skirtHeight,
- childTileMask: provider.availability.computeChildMaskForTile(level, x, y),
- waterMask: waterMaskBuffer,
- credits: provider._tileCredits,
- });
- }
- /**
- * Requests the geometry for a given tile. This function should not be called before
- * {@link CesiumTerrainProvider#ready} returns true. The result must include terrain data and
- * may optionally include a water mask and an indication of which child tiles are available.
- *
- * @param {Number} x The X coordinate of the tile for which to request geometry.
- * @param {Number} y The Y coordinate of the tile for which to request geometry.
- * @param {Number} level The level of the tile for which to request geometry.
- * @param {Request} [request] The request object. Intended for internal use only.
- *
- * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry. If this method
- * returns undefined instead of a promise, it is an indication that too many requests are already
- * pending and the request will be retried later.
- *
- * @exception {DeveloperError} This function must not be called before {@link CesiumTerrainProvider#ready}
- * returns true.
- */
- CesiumTerrainProvider.prototype.requestTileGeometry = function (
- x,
- y,
- level,
- request
- ) {
- //>>includeStart('debug', pragmas.debug)
- if (!this._ready) {
- throw new DeveloperError(
- "requestTileGeometry must not be called before the terrain provider is ready."
- );
- }
- //>>includeEnd('debug');
- const layers = this._layers;
- let layerToUse;
- const layerCount = layers.length;
- if (layerCount === 1) {
- // Optimized path for single layers
- layerToUse = layers[0];
- } else {
- for (let i = 0; i < layerCount; ++i) {
- const layer = layers[i];
- if (
- !defined(layer.availability) ||
- layer.availability.isTileAvailable(level, x, y)
- ) {
- layerToUse = layer;
- break;
- }
- }
- }
- return requestTileGeometry(this, x, y, level, layerToUse, request);
- };
- function requestTileGeometry(provider, x, y, level, layerToUse, request) {
- if (!defined(layerToUse)) {
- return Promise.reject(new RuntimeError("Terrain tile doesn't exist"));
- }
- const urlTemplates = layerToUse.tileUrlTemplates;
- if (urlTemplates.length === 0) {
- return undefined;
- }
- // The TileMapService scheme counts from the bottom left
- let terrainY;
- if (!provider._scheme || provider._scheme === "tms") {
- const yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(level);
- terrainY = yTiles - y - 1;
- } else {
- terrainY = y;
- }
- const extensionList = [];
- if (provider._requestVertexNormals && layerToUse.hasVertexNormals) {
- extensionList.push(
- layerToUse.littleEndianExtensionSize
- ? "octvertexnormals"
- : "vertexnormals"
- );
- }
- if (provider._requestWaterMask && layerToUse.hasWaterMask) {
- extensionList.push("watermask");
- }
- if (provider._requestMetadata && layerToUse.hasMetadata) {
- extensionList.push("metadata");
- }
- let headers;
- let query;
- const url = urlTemplates[(x + terrainY + level) % urlTemplates.length];
- const resource = layerToUse.resource;
- if (
- defined(resource._ionEndpoint) &&
- !defined(resource._ionEndpoint.externalType)
- ) {
- // ion uses query paremeters to request extensions
- if (extensionList.length !== 0) {
- query = { extensions: extensionList.join("-") };
- }
- headers = getRequestHeader(undefined);
- } else {
- //All other terrain servers
- headers = getRequestHeader(extensionList);
- }
- const promise = resource
- .getDerivedResource({
- url: url,
- templateValues: {
- version: layerToUse.version,
- z: level,
- x: x,
- y: terrainY,
- },
- queryParameters: query,
- headers: headers,
- request: request,
- })
- .fetchArrayBuffer();
- if (!defined(promise)) {
- return undefined;
- }
- return promise.then(function (buffer) {
- if (defined(provider._heightmapStructure)) {
- return createHeightmapTerrainData(provider, buffer, level, x, y);
- }
- return createQuantizedMeshTerrainData(
- provider,
- buffer,
- level,
- x,
- y,
- layerToUse
- );
- });
- }
- Object.defineProperties(CesiumTerrainProvider.prototype, {
- /**
- * Gets an event that is raised when the terrain provider encounters an asynchronous error. By subscribing
- * to the event, you will be notified of the error and can potentially recover from it. Event listeners
- * are passed an instance of {@link TileProviderError}.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Event}
- * @readonly
- */
- errorEvent: {
- get: function () {
- return this._errorEvent;
- },
- },
- /**
- * Gets the credit to display when this terrain provider is active. Typically this is used to credit
- * the source of the terrain. This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Credit}
- * @readonly
- */
- credit: {
- get: function () {
- //>>includeStart('debug', pragmas.debug)
- if (!this._ready) {
- throw new DeveloperError(
- "credit must not be called before the terrain provider is ready."
- );
- }
- //>>includeEnd('debug');
- return this._credit;
- },
- },
- /**
- * Gets the tiling scheme used by this provider. This function should
- * not be called before {@link CesiumTerrainProvider#ready} returns true.
- * @memberof CesiumTerrainProvider.prototype
- * @type {GeographicTilingScheme}
- * @readonly
- */
- tilingScheme: {
- get: function () {
- //>>includeStart('debug', pragmas.debug)
- if (!this._ready) {
- throw new DeveloperError(
- "tilingScheme must not be called before the terrain provider is ready."
- );
- }
- //>>includeEnd('debug');
- return this._tilingScheme;
- },
- },
- /**
- * Gets a value indicating whether or not the provider is ready for use.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Boolean}
- * @readonly
- */
- ready: {
- get: function () {
- return this._ready;
- },
- },
- /**
- * Gets a promise that resolves to true when the provider is ready for use.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Promise.<Boolean>}
- * @readonly
- */
- readyPromise: {
- get: function () {
- return this._readyPromise.promise;
- },
- },
- /**
- * Gets a value indicating whether or not the provider includes a water mask. The water mask
- * indicates which areas of the globe are water rather than land, so they can be rendered
- * as a reflective surface with animated waves. This function should not be
- * called before {@link CesiumTerrainProvider#ready} returns true.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Boolean}
- * @readonly
- * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
- */
- hasWaterMask: {
- get: function () {
- //>>includeStart('debug', pragmas.debug)
- if (!this._ready) {
- throw new DeveloperError(
- "hasWaterMask must not be called before the terrain provider is ready."
- );
- }
- //>>includeEnd('debug');
- return this._hasWaterMask && this._requestWaterMask;
- },
- },
- /**
- * Gets a value indicating whether or not the requested tiles include vertex normals.
- * This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Boolean}
- * @readonly
- * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
- */
- hasVertexNormals: {
- get: function () {
- //>>includeStart('debug', pragmas.debug)
- if (!this._ready) {
- throw new DeveloperError(
- "hasVertexNormals must not be called before the terrain provider is ready."
- );
- }
- //>>includeEnd('debug');
- // returns true if we can request vertex normals from the server
- return this._hasVertexNormals && this._requestVertexNormals;
- },
- },
- /**
- * Gets a value indicating whether or not the requested tiles include metadata.
- * This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Boolean}
- * @readonly
- * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
- */
- hasMetadata: {
- get: function () {
- //>>includeStart('debug', pragmas.debug)
- if (!this._ready) {
- throw new DeveloperError(
- "hasMetadata must not be called before the terrain provider is ready."
- );
- }
- //>>includeEnd('debug');
- // returns true if we can request metadata from the server
- return this._hasMetadata && this._requestMetadata;
- },
- },
- /**
- * Boolean flag that indicates if the client should request vertex normals from the server.
- * Vertex normals data is appended to the standard tile mesh data only if the client requests the vertex normals and
- * if the server provides vertex normals.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Boolean}
- * @readonly
- */
- requestVertexNormals: {
- get: function () {
- return this._requestVertexNormals;
- },
- },
- /**
- * Boolean flag that indicates if the client should request a watermask from the server.
- * Watermask data is appended to the standard tile mesh data only if the client requests the watermask and
- * if the server provides a watermask.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Boolean}
- * @readonly
- */
- requestWaterMask: {
- get: function () {
- return this._requestWaterMask;
- },
- },
- /**
- * Boolean flag that indicates if the client should request metadata from the server.
- * Metadata is appended to the standard tile mesh data only if the client requests the metadata and
- * if the server provides a metadata.
- * @memberof CesiumTerrainProvider.prototype
- * @type {Boolean}
- * @readonly
- */
- requestMetadata: {
- get: function () {
- return this._requestMetadata;
- },
- },
- /**
- * Gets an object that can be used to determine availability of terrain from this provider, such as
- * at points and in rectangles. This function should not be called before
- * {@link CesiumTerrainProvider#ready} returns true. This property may be undefined if availability
- * information is not available. Note that this reflects tiles that are known to be available currently.
- * Additional tiles may be discovered to be available in the future, e.g. if availability information
- * exists deeper in the tree rather than it all being discoverable at the root. However, a tile that
- * is available now will not become unavailable in the future.
- * @memberof CesiumTerrainProvider.prototype
- * @type {TileAvailability}
- * @readonly
- */
- availability: {
- get: function () {
- //>>includeStart('debug', pragmas.debug)
- if (!this._ready) {
- throw new DeveloperError(
- "availability must not be called before the terrain provider is ready."
- );
- }
- //>>includeEnd('debug');
- return this._availability;
- },
- },
- });
- /**
- * Gets the maximum geometric error allowed in a tile at a given level.
- *
- * @param {Number} level The tile level for which to get the maximum geometric error.
- * @returns {Number} The maximum geometric error.
- */
- CesiumTerrainProvider.prototype.getLevelMaximumGeometricError = function (
- level
- ) {
- return this._levelZeroMaximumGeometricError / (1 << level);
- };
- /**
- * Determines whether data for a tile is available to be loaded.
- *
- * @param {Number} x The X coordinate of the tile for which to request geometry.
- * @param {Number} y The Y coordinate of the tile for which to request geometry.
- * @param {Number} level The level of the tile for which to request geometry.
- * @returns {Boolean|undefined} Undefined if not supported or availability is unknown, otherwise true or false.
- */
- CesiumTerrainProvider.prototype.getTileDataAvailable = function (x, y, level) {
- if (!defined(this._availability)) {
- return undefined;
- }
- if (level > this._availability._maximumLevel) {
- return false;
- }
- if (this._availability.isTileAvailable(level, x, y)) {
- // If the tile is listed as available, then we are done
- return true;
- }
- if (!this._hasMetadata) {
- // If we don't have any layers with the metadata extension then we don't have this tile
- return false;
- }
- const layers = this._layers;
- const count = layers.length;
- for (let i = 0; i < count; ++i) {
- const layerResult = checkLayer(this, x, y, level, layers[i], i === 0);
- if (layerResult.result) {
- // There is a layer that may or may not have the tile
- return undefined;
- }
- }
- return false;
- };
- /**
- * Makes sure we load availability data for a tile
- *
- * @param {Number} x The X coordinate of the tile for which to request geometry.
- * @param {Number} y The Y coordinate of the tile for which to request geometry.
- * @param {Number} level The level of the tile for which to request geometry.
- * @returns {undefined|Promise<void>} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded
- */
- CesiumTerrainProvider.prototype.loadTileDataAvailability = function (
- x,
- y,
- level
- ) {
- if (
- !defined(this._availability) ||
- level > this._availability._maximumLevel ||
- this._availability.isTileAvailable(level, x, y) ||
- !this._hasMetadata
- ) {
- // We know the tile is either available or not available so nothing to wait on
- return undefined;
- }
- const layers = this._layers;
- const count = layers.length;
- for (let i = 0; i < count; ++i) {
- const layerResult = checkLayer(this, x, y, level, layers[i], i === 0);
- if (defined(layerResult.promise)) {
- return layerResult.promise;
- }
- }
- };
- function getAvailabilityTile(layer, x, y, level) {
- if (level === 0) {
- return;
- }
- const availabilityLevels = layer.availabilityLevels;
- const parentLevel =
- level % availabilityLevels === 0
- ? level - availabilityLevels
- : ((level / availabilityLevels) | 0) * availabilityLevels;
- const divisor = 1 << (level - parentLevel);
- const parentX = (x / divisor) | 0;
- const parentY = (y / divisor) | 0;
- return {
- level: parentLevel,
- x: parentX,
- y: parentY,
- };
- }
- function checkLayer(provider, x, y, level, layer, topLayer) {
- if (!defined(layer.availabilityLevels)) {
- // It's definitely not in this layer
- return {
- result: false,
- };
- }
- let cacheKey;
- const deleteFromCache = function () {
- delete layer.availabilityPromiseCache[cacheKey];
- };
- const availabilityTilesLoaded = layer.availabilityTilesLoaded;
- const availability = layer.availability;
- let tile = getAvailabilityTile(layer, x, y, level);
- while (defined(tile)) {
- if (
- availability.isTileAvailable(tile.level, tile.x, tile.y) &&
- !availabilityTilesLoaded.isTileAvailable(tile.level, tile.x, tile.y)
- ) {
- let requestPromise;
- if (!topLayer) {
- cacheKey = `${tile.level}-${tile.x}-${tile.y}`;
- requestPromise = layer.availabilityPromiseCache[cacheKey];
- if (!defined(requestPromise)) {
- // For cutout terrain, if this isn't the top layer the availability tiles
- // may never get loaded, so request it here.
- const request = new Request({
- throttle: false,
- throttleByServer: true,
- type: RequestType.TERRAIN,
- });
- requestPromise = requestTileGeometry(
- provider,
- tile.x,
- tile.y,
- tile.level,
- layer,
- request
- );
- if (defined(requestPromise)) {
- layer.availabilityPromiseCache[cacheKey] = requestPromise;
- requestPromise.then(deleteFromCache);
- }
- }
- }
- // The availability tile is available, but not loaded, so there
- // is still a chance that it may become available at some point
- return {
- result: true,
- promise: requestPromise,
- };
- }
- tile = getAvailabilityTile(layer, tile.x, tile.y, tile.level);
- }
- return {
- result: false,
- };
- }
- // Used for testing
- CesiumTerrainProvider._getAvailabilityTile = getAvailabilityTile;
- export default CesiumTerrainProvider;
|