import when from '../ThirdParty/when.js'; 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 defined from './defined.js'; import defineProperties from './defineProperties.js'; import DeveloperError from './DeveloperError.js'; import Event from './Event.js'; import GeographicTilingScheme from './GeographicTilingScheme.js'; import getStringFromTypedArray from './getStringFromTypedArray.js'; import HeightmapTerrainData from './HeightmapTerrainData.js'; import IndexDatatype from './IndexDatatype.js'; import CesiumMath from './Math.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.availabilityTilesLoaded = layer.availabilityTilesLoaded; 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|Promise} 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. * var 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._tilingScheme = new GeographicTilingScheme({ numberOfLevelZeroTilesX : 2, numberOfLevelZeroTilesY : 1, ellipsoid : options.ellipsoid }); this._heightmapWidth = 65; this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._heightmapWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0)); this._heightmapStructure = undefined; this._hasWaterMask = false; this._hasVertexNormals = false; /** * 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(); var credit = options.credit; if (typeof credit === 'string') { credit = new Credit(credit); } this._credit = credit; this._availability = undefined; var deferred = when.defer(); this._ready = false; this._readyPromise = deferred; this._tileCredits = undefined; var that = this; var lastResource; var layerJsonResource; var metadataError; var layers = this._layers = []; var attribution = ''; var overallAvailability = []; var overallMaxZoom = 0; when(options.url) .then(function(url) { var 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(); }) .otherwise(function(e) { deferred.reject(e); }); function parseMetadataSuccess(data) { var 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; } var hasVertexNormals = false; var hasWaterMask = false; var hasMetadata = false; var littleEndianExtensionSize = true; var 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; } var tileUrlTemplates = data.tiles; var maxZoom = data.maxzoom; overallMaxZoom = Math.max(overallMaxZoom, maxZoom); // Keeps track of which of the availablity containing tiles have been loaded var 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; } var availabilityLevels = data.metadataAvailability; var availableTiles = data.available; var availability; if (defined(availableTiles) && !defined(availabilityLevels)) { availability = new TileAvailability(that._tilingScheme, availableTiles.length); for (var level = 0; level < availableTiles.length; ++level) { var rangesAtLevel = availableTiles[level]; var yTiles = that._tilingScheme.getNumberOfYTilesAtLevel(level); if (!defined(overallAvailability[level])) { overallAvailability[level] = []; } for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) { var range = rangesAtLevel[rangeIndex]; var yStart = yTiles - range.endY - 1; var 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 })); var 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 when.resolve(); } lastResource = lastResource.getDerivedResource({ url: parentUrl }); lastResource.appendForwardSlash(); // Terrain always expects a directory layerJsonResource = lastResource.getDerivedResource({ url: 'layer.json' }); var parentMetadata = layerJsonResource.fetchJson(); return when(parentMetadata, parseMetadataSuccess, parseMetadataFailure); } return when.resolve(); } function parseMetadataFailure(data) { var 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; } var length = overallAvailability.length; if (length > 0) { var availability = that._availability = new TileAvailability(that._tilingScheme, overallMaxZoom); for (var level = 0; level < length; ++level) { var levelRanges = overallAvailability[level]; for (var i = 0; i < levelRanges.length; ++i) { var range = levelRanges[i]; availability.addAvailableTileRange(level, range[0], range[1], range[2], range[3]); } } } if (attribution.length > 0) { var 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() { when(layerJsonResource.fetchJson()) .then(metadataSuccess) .otherwise(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. * * @exports QuantizedMeshExtensionIds * @see CesiumTerrainProvider * @private */ var 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' }; } var 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, tmsY) { var 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, tmsY, layer) { var littleEndianExtensionSize = layer.littleEndianExtensionSize; var pos = 0; var cartesian3Elements = 3; var boundingSphereElements = cartesian3Elements + 1; var cartesian3Length = Float64Array.BYTES_PER_ELEMENT * cartesian3Elements; var boundingSphereLength = Float64Array.BYTES_PER_ELEMENT * boundingSphereElements; var encodedVertexElements = 3; var encodedVertexLength = Uint16Array.BYTES_PER_ELEMENT * encodedVertexElements; var triangleElements = 3; var bytesPerIndex = Uint16Array.BYTES_PER_ELEMENT; var triangleLength = bytesPerIndex * triangleElements; var view = new DataView(buffer); var center = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true)); pos += cartesian3Length; var minimumHeight = view.getFloat32(pos, true); pos += Float32Array.BYTES_PER_ELEMENT; var maximumHeight = view.getFloat32(pos, true); pos += Float32Array.BYTES_PER_ELEMENT; var 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; var horizonOcclusionPoint = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true)); pos += cartesian3Length; var vertexCount = view.getUint32(pos, true); pos += Uint32Array.BYTES_PER_ELEMENT; var 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. var uBuffer = encodedVertexBuffer.subarray(0, vertexCount); var vBuffer = encodedVertexBuffer.subarray(vertexCount, 2 * vertexCount); var 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)); } var triangleCount = view.getUint32(pos, true); pos += Uint32Array.BYTES_PER_ELEMENT; var 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. var highest = 0; var length = indices.length; for (var i = 0; i < length; ++i) { var code = indices[i]; indices[i] = highest - code; if (code === 0) { ++highest; } } var westVertexCount = view.getUint32(pos, true); pos += Uint32Array.BYTES_PER_ELEMENT; var westIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, westVertexCount); pos += westVertexCount * bytesPerIndex; var southVertexCount = view.getUint32(pos, true); pos += Uint32Array.BYTES_PER_ELEMENT; var southIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, southVertexCount); pos += southVertexCount * bytesPerIndex; var eastVertexCount = view.getUint32(pos, true); pos += Uint32Array.BYTES_PER_ELEMENT; var eastIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, eastVertexCount); pos += eastVertexCount * bytesPerIndex; var northVertexCount = view.getUint32(pos, true); pos += Uint32Array.BYTES_PER_ELEMENT; var northIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, northVertexCount); pos += northVertexCount * bytesPerIndex; var encodedNormalBuffer; var waterMaskBuffer; while (pos < view.byteLength) { var extensionId = view.getUint8(pos, true); pos += Uint8Array.BYTES_PER_ELEMENT; var 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) { var stringLength = view.getUint32(pos, true); if (stringLength > 0) { var jsonString = getStringFromTypedArray(new Uint8Array(buffer), pos + Uint32Array.BYTES_PER_ELEMENT, stringLength); var metadata = JSON.parse(jsonString); var availableTiles = metadata.available; if (defined(availableTiles)) { for (var offset = 0; offset < availableTiles.length; ++offset) { var availableLevel = level + offset + 1; var rangesAtLevel = availableTiles[offset]; var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(availableLevel); for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) { var range = rangesAtLevel[rangeIndex]; var yStart = yTiles - range.endY - 1; var 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; } var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0; var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level); var orientedBoundingBox; if (rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) { // Here, rectangle.width < pi/2, and rectangle.height < pi // (though it would still work with rectangle.width up to pi) // 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`. 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.|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'); var layers = this._layers; var layerToUse; var layerCount = layers.length; if (layerCount === 1) { // Optimized path for single layers layerToUse = layers[0]; } else { for (var i = 0; i < layerCount; ++i) { var 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 when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } var urlTemplates = layerToUse.tileUrlTemplates; if (urlTemplates.length === 0) { return undefined; } var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(level); var tmsY = (yTiles - y - 1); var 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'); } var headers; var query; var url = urlTemplates[(x + tmsY + level) % urlTemplates.length]; var 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); } var promise = resource.getDerivedResource({ url: url, templateValues: { version: layerToUse.version, z: level, x: x, y: tmsY }, 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, tmsY); } return createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, layerToUse); }); } 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} */ 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} */ 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} */ 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} */ 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.} * @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} * @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} * @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} * @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} */ 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} */ 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} */ 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} */ 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 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; } var layers = this._layers; var count = layers.length; for (var i = 0; i < count; ++i) { var 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} 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; } var layers = this._layers; var count = layers.length; for (var i = 0; i < count; ++i) { var 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; } var availabilityLevels = layer.availabilityLevels; var parentLevel = (level % availabilityLevels === 0) ? (level - availabilityLevels) : ((level / availabilityLevels) | 0) * availabilityLevels; var divisor = 1 << (level - parentLevel); var parentX = (x / divisor) | 0; var 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 }; } var cacheKey; var deleteFromCache = function () { delete layer.availabilityPromiseCache[cacheKey]; }; var availabilityTilesLoaded = layer.availabilityTilesLoaded; var availability = layer.availability; var 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)) { var 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. var request = new Request({ throttle: true, 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;