From 960945fad726580cee661d3488ec902c6762e8d9 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 27 Mar 2017 16:02:43 -0400 Subject: [PATCH 01/60] Added Imagery provider for Google Earth Enterprise servers. --- .../GoogleEarthEnterpriseImageryProvider.js | 1088 ++++++ Source/ThirdParty/pako_inflate.js | 3125 +++++++++++++++++ Source/ThirdParty/protobuf-minimal.js | 2465 +++++++++++++ ...oogleEarthEnterpriseImageryProviderSpec.js | 485 +++ 4 files changed, 7163 insertions(+) create mode 100644 Source/Scene/GoogleEarthEnterpriseImageryProvider.js create mode 100644 Source/ThirdParty/pako_inflate.js create mode 100644 Source/ThirdParty/protobuf-minimal.js create mode 100644 Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js new file mode 100644 index 000000000000..c77e9db78304 --- /dev/null +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -0,0 +1,1088 @@ +/*global define*/ +define([ + '../Core/appendForwardSlash', + '../Core/Cartesian2', + '../Core/Credit', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/loadArrayBuffer', + '../Core/loadImageFromTypedArray', + '../Core/Math', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/TerrainProvider', + '../Core/TileProviderError', + '../Core/GeographicTilingScheme', + '../ThirdParty/pako_inflate', + '../ThirdParty/protobuf-minimal', + '../ThirdParty/when', + './DiscardMissingTileImagePolicy' + ], function( + appendForwardSlash, + Cartesian2, + Credit, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + loadArrayBuffer, + loadImageFromTypedArray, + CesiumMath, + Rectangle, + RuntimeError, + TerrainProvider, + TileProviderError, + GeographicTilingScheme, + pako, + protobuf, + when, + DiscardMissingTileImagePolicy) { + 'use strict'; + + // Bitmask for checking tile properties + var childrenBitmasks = [0x01, 0x02, 0x04, 0x08]; + var cacheFlagBitmask = 0x10; // True if there is a child subtree + // var vectorDataBitmask = 0x20; + var imageBitmask = 0x40; + var terrainBitmask = 0x80; + + /** + * Provides tiled imagery using the Google Earth Enterprise Imagery REST API. + * + * @alias GoogleEarthEnterpriseImageryProvider + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {String} [options.tileProtocol] The protocol to use when loading tiles, e.g. 'http:' or 'https:'. + * By default, tiles are loaded using the same protocol as the page. + * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. + * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile + * is invalid and should be discarded. If this value is not specified, a default + * {@link DiscardMissingTileImagePolicy} is used which requests + * tile 0,0 at the maximum tile level and checks pixels (0,0), (120,140), (130,160), + * (200,50), and (200,200). If all of these pixels are transparent, the discard check is + * disabled and no tiles are discarded. If any of them have a non-transparent color, any + * tile that has the same values in these pixel locations is discarded. The end result of + * these defaults should be correct tile discarding for a standard Google Earth Enterprise server. To ensure + * that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this + * parameter. + * @param {Proxy} [options.proxy] A proxy to use for requests. This object is + * expected to have a getURL function which returns the proxied URL, if needed. + * + * @see ArcGisMapServerImageryProvider + * @see GoogleEarthImageryProvider + * @see createOpenStreetMapImageryProvider + * @see SingleTileImageryProvider + * @see createTileMapServiceImageryProvider + * @see WebMapServiceImageryProvider + * @see WebMapTileServiceImageryProvider + * @see UrlTemplateImageryProvider + * + * + * @example + * var gee = new Cesium.GoogleEarthEnterpriseImageryProvider({ + * url : 'https://dev.virtualearth.net' + * }); + * + * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} + */ + function GoogleEarthEnterpriseImageryProvider(options) { + options = defaultValue(options, {}); + + //>>includeStart('debug', pragmas.debug); + if (!defined(options.url)) { + throw new DeveloperError('options.url is required.'); + } + //>>includeEnd('debug'); + + this._url = appendForwardSlash(options.url); + this._tileProtocol = options.tileProtocol; + this._tileDiscardPolicy = options.tileDiscardPolicy; + this._proxy = options.proxy; + this._credit = new Credit('Google Imagery', GoogleEarthEnterpriseImageryProvider._logoData, 'http://www.google.com'); + + this._tilingScheme = new GeographicTilingScheme({ + numberOfLevelZeroTilesX : 2, + numberOfLevelZeroTilesY : 2, + rectangle : new Rectangle(-CesiumMath.PI, -CesiumMath.PI, CesiumMath.PI, CesiumMath.PI), + ellipsoid : options.ellipsoid + }); + + this._tileWidth = 256; + this._tileHeight = 256; + this._maximumLevel = 23; + this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._tileWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0)); + + // Install the default tile discard policy if none has been supplied. + if (!defined(this._tileDiscardPolicy)) { + this._tileDiscardPolicy = new DiscardMissingTileImagePolicy({ + // TODO - missing image url + missingImageUrl : buildImageUrl(this, GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(0, 0, 1)), + pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(120, 140), new Cartesian2(130, 160), new Cartesian2(200, 50), new Cartesian2(200, 200)], + disableCheckIfAllPixelsAreTransparent : true + }); + } + + this._tileInfo = {}; + this._subtreePromises = {}; + + this._errorEvent = new Event(); + + this._ready = false; + var that = this; + this._readyPromise = this._getQuadTreePacket() + .then(function() { + that._ready = true; + }); + } + + defineProperties(GoogleEarthEnterpriseImageryProvider.prototype, { + /** + * Gets the name of the Google Earth Enterprise server url hosting the imagery. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._url; + } + }, + + /** + * Gets the proxy used by this provider. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : function() { + return this._proxy; + } + }, + + /** + * Gets the width of each tile, in pixels. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + tileWidth : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tileWidth; + } + }, + + /** + * Gets the height of each tile, in pixels. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + tileHeight: { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tileHeight; + } + }, + + + /** + * Gets the maximum level-of-detail that can be requested. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + maximumLevel : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._maximumLevel; + } + }, + + /** + * Gets the minimum level-of-detail that can be requested. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + minimumLevel : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return 0; + } + }, + + /** + * Gets the tiling scheme used by this provider. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {TilingScheme} + * @readonly + */ + tilingScheme : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tilingScheme; + } + }, + + /** + * Gets the rectangle, in radians, of the imagery provided by this instance. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Rectangle} + * @readonly + */ + rectangle : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('rectangle must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tilingScheme.rectangle; + } + }, + + /** + * Gets the tile discard policy. If not undefined, the discard policy is responsible + * for filtering out "missing" tiles via its shouldDiscardImage function. If this function + * returns undefined, no tiles are filtered. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {TileDiscardPolicy} + * @readonly + */ + tileDiscardPolicy : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tileDiscardPolicy; + } + }, + + /** + * Gets an event that is raised when the imagery 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 GoogleEarthEnterpriseImageryProvider.prototype + * @type {Event} + * @readonly + */ + errorEvent : { + get : function() { + return this._errorEvent; + } + }, + + /** + * Gets a value indicating whether or not the provider is ready for use. + * @memberof GoogleEarthEnterpriseImageryProvider.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 GoogleEarthEnterpriseImageryProvider.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + }, + + /** + * Gets the credit to display when this imagery provider is active. Typically this is used to credit + * the source of the imagery. This function should not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Credit} + * @readonly + */ + credit : { + get : function() { + return this._credit; + } + }, + + /** + * Gets a value indicating whether or not the images provided by this imagery provider + * include an alpha channel. If this property is false, an alpha channel, if present, will + * be ignored. If this property is true, any images without an alpha channel will be treated + * as if their alpha is 1.0 everywhere. Setting this property to false reduces memory usage + * and texture upload time. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Boolean} + * @readonly + */ + hasAlphaChannel : { + get : function() { + return false; + } + }, + + /** + * 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 GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Boolean} + */ + hasWaterMask : { + get : function() { + return false; + } + }, + + /** + * Gets a value indicating whether or not the requested tiles include vertex normals. + * This function should not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Boolean} + */ + hasVertexNormals : { + get : function() { + return false; + } + }, + + /** + * 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 GoogleEarthEnterpriseImageryProvider#ready} returns true. This property may be undefined if availability + * information is not available. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {TileAvailability} + */ + availability : { + get : function() { + return undefined; + } + } + }); + + /** + * Gets the credits to be displayed when a given tile is displayed. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level; + * @returns {Credit[]} The credits to be displayed when the tile is displayed. + * + * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. + */ + GoogleEarthEnterpriseImageryProvider.prototype.getTileCredits = function(x, y, level) { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return undefined; + }; + + /** + * Requests the image for a given tile. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or + * undefined if there are too many active requests to the server, and the request + * should be retried later. The resolved image may be either an + * Image or a Canvas DOM object. + * + * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. + */ + GoogleEarthEnterpriseImageryProvider.prototype.requestImage = function(x, y, level) { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + var that = this; + var tileInfo = this._tileInfo; + var quadKey = GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(x, y, level); + return populateSubtree(this, quadKey) + .then(function(exists){ + if (exists) { + var info = tileInfo[quadKey]; + if (info.bits & imageBitmask) { + var url = buildImageUrl(that, quadKey, info.imageryVersion); + return loadArrayBuffer(url) + .then(function(image) { + if (defined(image)) { + GoogleEarthEnterpriseImageryProvider._decode(image); + var a = new Uint8Array(image); + var type = getImageType(a); + if (!defined(type)) { + var message = decodeEarthImageryPacket(a); + type = message.imageType; + a = message.imageData; + } + + if (!defined(type) || !defined(a)) { + throw new RuntimeError('Invalid image'); + } + + return loadImageFromTypedArray(a, type); + } + }) + .otherwise(function(error) { + // Just ignore failures and return undefined + }); + } + } + }); + }; + + /** + * Picking features is not currently supported by this imagery provider, so this function simply returns + * undefined. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @param {Number} longitude The longitude at which to pick features. + * @param {Number} latitude The latitude at which to pick features. + * @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous + * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo} + * instances. The array may be empty if no features are found at the given location. + * It may also be undefined if picking is not supported. + */ + GoogleEarthEnterpriseImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { + return undefined; + }; + + GoogleEarthEnterpriseImageryProvider._logoData = 'data:image/png;base64,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'; + + /** + * Converts a tiles (x, y, level) position into a quadkey used to request an image + * from a Google Earth Enterprise server. + * + * @param {Number} x The tile's x coordinate. + * @param {Number} y The tile's y coordinate. + * @param {Number} level The tile's zoom level. + * + * @see GoogleEarthEnterpriseImageryProvider#quadKeyToTileXY + */ + GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey = function(x, y, level) { + var quadkey = ''; + for ( var i = level; i >= 0; --i) { + var bitmask = 1 << i; + var digit = 0; + + // Tile Layout + // ___ ___ + //| | | + //| 3 | 2 | + //|-------| + //| 0 | 1 | + //|___|___| + // + + if ((y & bitmask) === 0) { // Top Row + digit |= 2; + if ((x & bitmask) === 0) { // Right to left + digit |= 1; + } + } else { + if ((x & bitmask) !== 0) { // Left to right + digit |= 1; + } + } + + quadkey += digit; + } + return quadkey; + }; + + /** + * Converts a tile's quadkey used to request an image from a Google Earth Enterprise server into the + * (x, y, level) position. + * + * @param {String} quadkey The tile's quad key + * + * @see GoogleEarthEnterpriseImageryProvider#tileXYToQuadKey + */ + GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY = function(quadkey) { + var x = 0; + var y = 0; + var level = quadkey.length - 1; + for ( var i = level; i >= 0; --i) { + var bitmask = 1 << i; + var digit = +quadkey[level - i]; + + if ((digit & 2) !== 0) { // Top Row + if ((digit & 1) === 0) { // // Right to left + x |= bitmask; + } + } else { + y |= bitmask; + if ((digit & 1) !== 0) { // Left to right + x |= bitmask; + } + } + } + return { + x : x, + y : y, + level : level + }; + }; + + /** + * Requests the geometry for a given tile. This function should not be called before + * {@link GoogleEarthEnterpriseImageryProvider#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 {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited, + * or false if the request should be initiated regardless of the number of requests + * already in progress. + * @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 GoogleEarthEnterpriseImageryProvider#ready} + * returns true. + */ + GoogleEarthEnterpriseImageryProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { + //>>includeStart('debug', pragmas.debug) + if (!this._ready) { + throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.'); + } + //>>includeEnd('debug'); + + // var urlTemplates = this._tileUrlTemplates; + // if (urlTemplates.length === 0) { + // return undefined; + // } + // + // var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level); + // + // var tmsY = (yTiles - y - 1); + // + // var url = urlTemplates[(x + tmsY + level) % urlTemplates.length].replace('{z}', level).replace('{x}', x).replace('{y}', tmsY); + // + // var proxy = this._proxy; + // if (defined(proxy)) { + // url = proxy.getURL(url); + // } + // + // var promise; + // + // var extensionList = []; + // if (this._requestVertexNormals && this._hasVertexNormals) { + // extensionList.push(this._littleEndianExtensionSize ? 'octvertexnormals' : 'vertexnormals'); + // } + // if (this._requestWaterMask && this._hasWaterMask) { + // extensionList.push('watermask'); + // } + // + // function tileLoader(tileUrl) { + // return loadArrayBuffer(tileUrl, getRequestHeader(extensionList)); + // } + // throttleRequests = defaultValue(throttleRequests, true); + // if (throttleRequests) { + // promise = throttleRequestByServer(url, tileLoader); + // if (!defined(promise)) { + // return undefined; + // } + // } else { + // promise = tileLoader(url); + // } + // + // var that = this; + // return when(promise, function(buffer) { + // if (defined(that._heightmapStructure)) { + // return createHeightmapTerrainData(that, buffer, level, x, y, tmsY); + // } else { + // return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY); + // } + // }); + }; + + /** + * 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. + */ + GoogleEarthEnterpriseImageryProvider.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, otherwise true or false. + */ + GoogleEarthEnterpriseImageryProvider.prototype.getTileDataAvailable = function(x, y, level) { + return undefined; + }; + + // Decodes packet with a key that has been around since the beginning of Google Earth Enterprise + var key = "\x45\xf4\xbd\x0b\x79\xe2\x6a\x45\x22\x05\x92\x2c\x17\xcd\x06\x71\xf8\x49\x10\x46\x67\x51\x00\x42\x25\xc6\xe8\x61\x2c\x66\x29\x08\xc6\x34\xdc\x6a\x62\x25\x79\x0a\x77\x1d\x6d\x69\xd6\xf0\x9c\x6b\x93\xa1\xbd\x4e\x75\xe0\x41\x04\x5b\xdf\x40\x56\x0c\xd9\xbb\x72\x9b\x81\x7c\x10\x33\x53\xee\x4f\x6c\xd4\x71\x05\xb0\x7b\xc0\x7f\x45\x03\x56\x5a\xad\x77\x55\x65\x0b\x33\x92\x2a\xac\x19\x6c\x35\x14\xc5\x1d\x30\x73\xf8\x33\x3e\x6d\x46\x38\x4a\xb4\xdd\xf0\x2e\xdd\x17\x75\x16\xda\x8c\x44\x74\x22\x06\xfa\x61\x22\x0c\x33\x22\x53\x6f\xaf\x39\x44\x0b\x8c\x0e\x39\xd9\x39\x13\x4c\xb9\xbf\x7f\xab\x5c\x8c\x50\x5f\x9f\x22\x75\x78\x1f\xe9\x07\x71\x91\x68\x3b\xc1\xc4\x9b\x7f\xf0\x3c\x56\x71\x48\x82\x05\x27\x55\x66\x59\x4e\x65\x1d\x98\x75\xa3\x61\x46\x7d\x61\x3f\x15\x41\x00\x9f\x14\x06\xd7\xb4\x34\x4d\xce\x13\x87\x46\xb0\x1a\xd5\x05\x1c\xb8\x8a\x27\x7b\x8b\xdc\x2b\xbb\x4d\x67\x30\xc8\xd1\xf6\x5c\x8f\x50\xfa\x5b\x2f\x46\x9b\x6e\x35\x18\x2f\x27\x43\x2e\xeb\x0a\x0c\x5e\x10\x05\x10\xa5\x73\x1b\x65\x34\xe5\x6c\x2e\x6a\x43\x27\x63\x14\x23\x55\xa9\x3f\x71\x7b\x67\x43\x7d\x3a\xaf\xcd\xe2\x54\x55\x9c\xfd\x4b\xc6\xe2\x9f\x2f\x28\xed\xcb\x5c\xc6\x2d\x66\x07\x88\xa7\x3b\x2f\x18\x2a\x22\x4e\x0e\xb0\x6b\x2e\xdd\x0d\x95\x7d\x7d\x47\xba\x43\xb2\x11\xb2\x2b\x3e\x4d\xaa\x3e\x7d\xe6\xce\x49\x89\xc6\xe6\x78\x0c\x61\x31\x05\x2d\x01\xa4\x4f\xa5\x7e\x71\x20\x88\xec\x0d\x31\xe8\x4e\x0b\x00\x6e\x50\x68\x7d\x17\x3d\x08\x0d\x17\x95\xa6\x6e\xa3\x68\x97\x24\x5b\x6b\xf3\x17\x23\xf3\xb6\x73\xb3\x0d\x0b\x40\xc0\x9f\xd8\x04\x51\x5d\xfa\x1a\x17\x22\x2e\x15\x6a\xdf\x49\x00\xb9\xa0\x77\x55\xc6\xef\x10\x6a\xbf\x7b\x47\x4c\x7f\x83\x17\x05\xee\xdc\xdc\x46\x85\xa9\xad\x53\x07\x2b\x53\x34\x06\x07\xff\x14\x94\x59\x19\x02\xe4\x38\xe8\x31\x83\x4e\xb9\x58\x46\x6b\xcb\x2d\x23\x86\x92\x70\x00\x35\x88\x22\xcf\x31\xb2\x26\x2f\xe7\xc3\x75\x2d\x36\x2c\x72\x74\xb0\x23\x47\xb7\xd3\xd1\x26\x16\x85\x37\x72\xe2\x00\x8c\x44\xcf\x10\xda\x33\x2d\x1a\xde\x60\x86\x69\x23\x69\x2a\x7c\xcd\x4b\x51\x0d\x95\x54\x39\x77\x2e\x29\xea\x1b\xa6\x50\xa2\x6a\x8f\x6f\x50\x99\x5c\x3e\x54\xfb\xef\x50\x5b\x0b\x07\x45\x17\x89\x6d\x28\x13\x77\x37\x1d\xdb\x8e\x1e\x4a\x05\x66\x4a\x6f\x99\x20\xe5\x70\xe2\xb9\x71\x7e\x0c\x6d\x49\x04\x2d\x7a\xfe\x72\xc7\xf2\x59\x30\x8f\xbb\x02\x5d\x73\xe5\xc9\x20\xea\x78\xec\x20\x90\xf0\x8a\x7f\x42\x17\x7c\x47\x19\x60\xb0\x16\xbd\x26\xb7\x71\xb6\xc7\x9f\x0e\xd1\x33\x82\x3d\xd3\xab\xee\x63\x99\xc8\x2b\x53\xa0\x44\x5c\x71\x01\xc6\xcc\x44\x1f\x32\x4f\x3c\xca\xc0\x29\x3d\x52\xd3\x61\x19\x58\xa9\x7d\x65\xb4\xdc\xcf\x0d\xf4\x3d\xf1\x08\xa9\x42\xda\x23\x09\xd8\xbf\x5e\x50\x49\xf8\x4d\xc0\xcb\x47\x4c\x1c\x4f\xf7\x7b\x2b\xd8\x16\x18\xc5\x31\x92\x3b\xb5\x6f\xdc\x6c\x0d\x92\x88\x16\xd1\x9e\xdb\x3f\xe2\xe9\xda\x5f\xd4\x84\xe2\x46\x61\x5a\xde\x1c\x55\xcf\xa4\x00\xbe\xfd\xce\x67\xf1\x4a\x69\x1c\x97\xe6\x20\x48\xd8\x5d\x7f\x7e\xae\x71\x20\x0e\x4e\xae\xc0\x56\xa9\x91\x01\x3c\x82\x1d\x0f\x72\xe7\x76\xec\x29\x49\xd6\x5d\x2d\x83\xe3\xdb\x36\x06\xa9\x3b\x66\x13\x97\x87\x6a\xd5\xb6\x3d\x50\x5e\x52\xb9\x4b\xc7\x73\x57\x78\xc9\xf4\x2e\x59\x07\x95\x93\x6f\xd0\x4b\x17\x57\x19\x3e\x27\x27\xc7\x60\xdb\x3b\xed\x9a\x0e\x53\x44\x16\x3e\x3f\x8d\x92\x6d\x77\xa2\x0a\xeb\x3f\x52\xa8\xc6\x55\x5e\x31\x49\x37\x85\xf4\xc5\x1f\x26\x2d\xa9\x1c\xbf\x8b\x27\x54\xda\xc3\x6a\x20\xe5\x2a\x78\x04\xb0\xd6\x90\x70\x72\xaa\x8b\x68\xbd\x88\xf7\x02\x5f\x48\xb1\x7e\xc0\x58\x4c\x3f\x66\x1a\xf9\x3e\xe1\x65\xc0\x70\xa7\xcf\x38\x69\xaf\xf0\x56\x6c\x64\x49\x9c\x27\xad\x78\x74\x4f\xc2\x87\xde\x56\x39\x00\xda\x77\x0b\xcb\x2d\x1b\x89\xfb\x35\x4f\x02\xf5\x08\x51\x13\x60\xc1\x0a\x5a\x47\x4d\x26\x1c\x33\x30\x78\xda\xc0\x9c\x46\x47\xe2\x5b\x79\x60\x49\x6e\x37\x67\x53\x0a\x3e\xe9\xec\x46\x39\xb2\xf1\x34\x0d\xc6\x84\x53\x75\x6e\xe1\x0c\x59\xd9\x1e\xde\x29\x85\x10\x7b\x49\x49\xa5\x77\x79\xbe\x49\x56\x2e\x36\xe7\x0b\x3a\xbb\x4f\x03\x62\x7b\xd2\x4d\x31\x95\x2f\xbd\x38\x7b\xa8\x4f\x21\xe1\xec\x46\x70\x76\x95\x7d\x29\x22\x78\x88\x0a\x90\xdd\x9d\x5c\xda\xde\x19\x51\xcf\xf0\xfc\x59\x52\x65\x7c\x33\x13\xdf\xf3\x48\xda\xbb\x2a\x75\xdb\x60\xb2\x02\x15\xd4\xfc\x19\xed\x1b\xec\x7f\x35\xa8\xff\x28\x31\x07\x2d\x12\xc8\xdc\x88\x46\x7c\x8a\x5b\x22"; + var keyBuffer; + GoogleEarthEnterpriseImageryProvider._decode = function(data) { + if (!defined(data)) { + throw new DeveloperError('data is required.'); + } + + var keylen = key.length; + if (!defined(keyBuffer)) { + keyBuffer = new ArrayBuffer(keylen); + var ui8 = new Uint8Array(keyBuffer); + for (var i=0; i < keylen; ++i) { + ui8[i] = key.charCodeAt(i); + } + } + + var dataView = new DataView(data); + var keyView = new DataView(keyBuffer); + + var dp = 0; + var dpend = data.byteLength; + var dpend64 = dpend - (dpend % 8); + var kpend = keylen; + var kp; + var off = 8; + + // This algorithm is intentionally asymmetric to make it more difficult to + // guess. Security through obscurity. :-( + + // while we have a full uint64 (8 bytes) left to do + // assumes buffer is 64bit aligned (or processor doesn't care) + while (dp < dpend64) { + // rotate the key each time through by using the offets 16,0,8,16,0,8,... + off = (off + 8) % 24; + kp = off; + + // run through one key length xor'ing one uint64 at a time + // then drop out to rotate the key for the next bit + while ((dp < dpend64) && (kp < kpend)) { + dataView.setUint32(dp, dataView.getUint32(dp, true) ^ keyView.getUint32(kp, true), true); + dataView.setUint32(dp+4, dataView.getUint32(dp+4, true) ^ keyView.getUint32(kp+4, true), true); + dp += 8; + kp += 24; + } + } + + // now the remaining 1 to 7 bytes + if (dp < dpend) { + if (kp >= kpend) { + // rotate the key one last time (if necessary) + off = (off + 8) % 24; + kp = off; + } + + while (dp < dpend) { + dataView.setUint8(dp, dataView.getUint8(dp) ^ keyView.getUint8(kp)); + dp++; + kp++; + } + } + }; + + // + // Functions to handle quadtree packets + // + var qtMagic = 32301; + var compressedMagic = 0x7468dead; + var compressedMagicSwap = 0xadde6874; + var Uint32Size = 4; + var Int32Size = 4; + var Uint16Size = 2; + + // Requests quadtree packet and populates _tileInfo with results + GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket = function(quadKey, version) { + quadKey = defaultValue(quadKey, ''); + version = Math.max(1, defaultValue(version, 1)); + var url = this._url + 'flatfile?q2-0' + quadKey + '-q.' + version.toString(); + var proxy = this._proxy; + if (defined(proxy)) { + url = proxy.getURL(url); + } + + var that = this; + return loadArrayBuffer(url) + .then(function(metadata) { + GoogleEarthEnterpriseImageryProvider._decode(metadata); + + // The layout of this decoded data is + // Magic Uint32 + // Size Uint32 + // [GZipped chunk of Size bytes] + + // Pullout magic and verify we have the correct data + var dv = new DataView(metadata); + var offset = 0; + var magic = dv.getUint32(offset, true); + offset += Uint32Size; + if (magic !== compressedMagic && magic !== compressedMagicSwap) { + throw new RuntimeError('Invalid magic'); + } + + // Get the size of the compressed buffer + var size = dv.getUint32(offset, true); + offset += Uint32Size; + if (magic === compressedMagicSwap) { + var v = ((size >>> 24) & 0x000000ff) | + ((size >>> 8) & 0x0000ff00) | + ((size << 8) & 0x00ff0000) | + ((size << 24) & 0xff000000); + size = v; + } + + var compressedPacket = new Uint8Array(metadata, offset); + var uncompressedPacket = pako.inflate(compressedPacket); + + if (uncompressedPacket.length !== size) { + throw new RuntimeError('Size of packet doesn\'t match header'); + } + + dv = new DataView(uncompressedPacket.buffer); + offset = 0; + magic = dv.getUint32(offset, true); + offset += Uint32Size; + if (magic !== qtMagic) { + throw new RuntimeError('Invalid magic'); + } + + var dataTypeId = dv.getUint32(offset, true); + offset += Uint32Size; + if (dataTypeId !== 1) { + throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); + } + + var version = dv.getUint32(offset, true); + offset += Uint32Size; + if (version !== 2) { + throw new RuntimeError('Invalid version. Only QuadTreePacket version 2 supported.'); + } + + var numInstances = dv.getInt32(offset, true); + offset += Int32Size; + + var dataInstanceSize = dv.getInt32(offset, true); + offset += Int32Size; + if (dataInstanceSize !== 32) { + throw new RuntimeError('Invalid instance size.'); + } + + var dataBufferOffset = dv.getInt32(offset, true); + offset += Int32Size; + + var dataBufferSize = dv.getInt32(offset, true); + offset += Int32Size; + + var metaBufferSize = dv.getInt32(offset, true); + offset += Int32Size; + + // Offset from beginning of packet (instances + current offset) + if (dataBufferOffset !== (numInstances * dataInstanceSize + offset)) { + throw new RuntimeError('Invalid dataBufferOffset'); + } + + // Verify the packets is all there header + instances + dataBuffer + metaBuffer + if (dataBufferOffset + dataBufferSize + metaBufferSize !== size) { + throw new RuntimeError('Invalid packet offsets'); + } + + // Read all the instances + var instances = []; + for (var i = 0; i < numInstances; ++i) { + var bitfield = dv.getUint8(offset); + ++offset; + + ++offset; // 2 byte align + + var cnodeVersion = dv.getUint16(offset, true); + offset += Uint16Size; + + var imageVersion = dv.getUint16(offset, true); + offset += Uint16Size; + + var terrainVersion = dv.getUint16(offset, true); + offset += Uint16Size; + + // Number of channels stored in the dataBuffer + //var numChannels = dv.getUint16(offset, true); + offset += Uint16Size; + + offset += Uint16Size; // 4 byte align + + // Channel type offset into dataBuffer + //var typeOffset = dv.getInt32(offset, true); + offset += Int32Size; + + // Channel version offset into dataBuffer + //var versionOffset = dv.getInt32(offset, true); + offset += Int32Size; + + offset += 8; // Ignore image neighbors for now + + // Data providers aren't used + ++offset; // Image provider + ++offset; // Terrain provider + offset += Uint16Size; // 4 byte align + + instances.push({ + bits : bitfield, + cnodeVersion : cnodeVersion, + imageryVersion : imageVersion, + terrainVersion : terrainVersion + }); + } + + var tileInfo = that._tileInfo; + var index = 0; + + function populateTiles(parentKey, parent, level) { + var bits = parent.bits; + var isLeaf = false; + if (level === 4) { + if ((bits & cacheFlagBitmask) !== 0) { + return; // We have a subtree, so just return + } + + isLeaf = true; // No subtree, so set all children to null + } + for (var i = 0; i < 4; ++i) { + var childKey = parentKey + i.toString(); + if (isLeaf) { + // No subtree so set all children to null + tileInfo[childKey] = null; + } else if (level < 4) { + // We are still in the middle of the subtree, so add child + // only if their bits are set, otherwise set child to null. + if ((bits & childrenBitmasks[i]) === 0) { + tileInfo[childKey] = null; + } else { + if (index === numInstances) { + console.log('Incorrect number of instances'); + return; + } + + var instance = instances[index++]; + tileInfo[childKey] = instance; + populateTiles(childKey, instance, level + 1); + } + } + } + } + + var level = 0; + var root; + if (quadKey === '') { + // Root tile has data at its root, all others don't + root = instances[index++]; + ++level; + } else { + // Root tile has no data except children bits, so put them into the tile info + var top = instances[index++]; + root = tileInfo[quadKey]; + root.bits |= top.bits; + } + + populateTiles(quadKey, root, level); + }); + }; + + // Verifies there is tileInfo for a quadKey. If not it requests the subtrees required to get it. + // Returns promise that resolves to true if the tile info is available, false otherwise. + function populateSubtree(that, quadKey) { + var tileInfo = that._tileInfo; + var q = quadKey; + var t = tileInfo[q]; + if (defined(t)) { + return when(true); + } + + while((t === undefined) && q.length > 1) { + q = q.substring(0, q.length-1); + t = tileInfo[q]; + } + + // t is either + // null so one of its parents was a leaf node, so this tile doesn't exist + // undefined so no parent exists - this shouldn't ever happen once the provider is ready + if (!defined(t)) { + return when(false); + } + + var subtreePromises = that._subtreePromises; + var promise = subtreePromises[q]; + if (defined(promise)) { + return promise; + } + + // We need to split up the promise here because when will execute syncronously if _getQuadTreePacket + // is already resolved (like in the tests), so subtreePromises will never get cleared out. + promise = that._getQuadTreePacket(q, t.cnodeVersion); + + subtreePromises[q] = promise + .then(function() { + return true; + }); + return promise + .then(function() { + delete subtreePromises[q]; + // Recursively call this incase we need multiple subtree requests + return populateSubtree(that, quadKey); + }); + } + + // + // Functions to handle imagery packets + // + function buildImageUrl(imageryProvider, quadKey, version) { + version = (defined(version) && version > 0) ? version : 1; + var imageUrl = imageryProvider._url + 'flatfile?f1-0' + quadKey + '-i.' + version.toString(); + + var proxy = imageryProvider._proxy; + if (defined(proxy)) { + imageUrl = proxy.getURL(imageUrl); + } + + return imageUrl; + } + + // Detects if a Uint8Array is a JPEG or PNG + function getImageType(image) { + var jpeg = 'JFIF'; + if (image[6] === jpeg.charCodeAt(0) && image[7] === jpeg.charCodeAt(1) && + image[8] === jpeg.charCodeAt(2) && image[9] === jpeg.charCodeAt(3)) { + return 'image/jpeg'; + } + + var png = 'PNG'; + if (image[1] === png.charCodeAt(0) && image[2] === png.charCodeAt(1) && image[3] === png.charCodeAt(2)) { + return 'image/png'; + } + } + + // Decodes an Imagery protobuf into the message + // Partially generated with the help of protobuf.js static generator + function decodeEarthImageryPacket(data) { + var reader = protobuf.Reader.create(data); + var end = reader.len; + var message = {}; + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.imageType = reader.uint32(); + break; + case 2: + message.imageData = reader.bytes(); + break; + case 3: + message.alphaType = reader.uint32(); + break; + case 4: + message.imageAlpha = reader.bytes(); + break; + case 5: + var copyrightIds = message.copyrightIds; + if (!defined(copyrightIds)) { + copyrightIds = message.copyrightIds = []; + } + if ((tag & 7) === 2) { + var end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + copyrightIds.push(reader.uint32()); + } + } else { + copyrightIds.push(reader.uint32()); + } + break; + default: + reader.skipType(tag & 7); + break; + } + } + + var imageType = message.imageType; + if (defined(imageType)) { + switch(imageType) { + case 0: + message.imageType = 'image/jpeg'; + break; + case 4: + message.imageType = 'image/png'; + break; + default: + throw new RuntimeError('GoogleEarthEnterpriseImageryProvider: Unsupported image type.'); + } + } + + var alphaType = message.alphaType; + if (defined(alphaType) && alphaType !== 0) { + console.log('GoogleEarthEnterpriseImageryProvider: External alpha not supported.'); + delete message.alphaType; + delete message.imageAlpha; + } + + return message; + } + + return GoogleEarthEnterpriseImageryProvider; +}); diff --git a/Source/ThirdParty/pako_inflate.js b/Source/ThirdParty/pako_inflate.js new file mode 100644 index 000000000000..b46f693c763c --- /dev/null +++ b/Source/ThirdParty/pako_inflate.js @@ -0,0 +1,3125 @@ +/* pako 1.0.4 nodeca/pako */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pako = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); + } + _utf8len[254] = _utf8len[254] = 1; // Invalid sequence start + + +// convert string to array (typed, when possible) + exports.string2buf = function (str) { + var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + buf = new utils.Buf8(buf_len); + + // convert + for (i = 0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; + }; + +// Helper (used in 2 places) + function buf2binstring(buf, len) { + // use fallback for big arrays to avoid stack overflow + if (len < 65537) { + if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) { + return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len)); + } + } + + var result = ''; + for (var i = 0; i < len; i++) { + result += String.fromCharCode(buf[i]); + } + return result; + } + + +// Convert byte array to binary string + exports.buf2binstring = function (buf) { + return buf2binstring(buf, buf.length); + }; + + +// Convert binary string (typed, when possible) + exports.binstring2buf = function (str) { + var buf = new utils.Buf8(str.length); + for (var i = 0, len = buf.length; i < len; i++) { + buf[i] = str.charCodeAt(i); + } + return buf; + }; + + +// convert array to string + exports.buf2string = function (buf, max) { + var i, out, c, c_len; + var len = max || buf.length; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + var utf16buf = new Array(len * 2); + + for (out = 0, i = 0; i < len;) { + c = buf[i++]; + // quick process ascii + if (c < 0x80) { utf16buf[out++] = c; continue; } + + c_len = _utf8len[c]; + // skip 5 & 6 byte codes + if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + return buf2binstring(utf16buf, out); + }; + + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); + exports.utf8border = function (buf, max) { + var pos; + + max = max || buf.length; + if (max > buf.length) { max = buf.length; } + + // go back from last position, until start of sequence found + pos = max - 1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } + + // Fuckup - very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } + + // If we came to start of buffer - that means vuffer is too small, + // return max too. + if (pos === 0) { return max; } + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; + }; + +},{"./common":1}],3:[function(require,module,exports){ + 'use strict'; + +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It doesn't worth to make additional optimizationa as in original. +// Small size is preferable. + + function adler32(adler, buf, len, pos) { + var s1 = (adler & 0xffff) |0, + s2 = ((adler >>> 16) & 0xffff) |0, + n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) |0; + } + + + module.exports = adler32; + +},{}],4:[function(require,module,exports){ + 'use strict'; + + + module.exports = { + + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + //Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + //Z_VERSION_ERROR: -6, + + /* compression levels */ + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + + + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + + /* Possible values of the data_type field (though see inflate()) */ + Z_BINARY: 0, + Z_TEXT: 1, + //Z_ASCII: 1, // = Z_TEXT (deprecated) + Z_UNKNOWN: 2, + + /* The deflate compression method */ + Z_DEFLATED: 8 + //Z_NULL: null // Use -1 or null inline, depending on var type + }; + +},{}],5:[function(require,module,exports){ + 'use strict'; + +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + + +// Use ordinary array, since untyped makes no boost here + function makeTable() { + var c, table = []; + + for (var n = 0; n < 256; n++) { + c = n; + for (var k = 0; k < 8; k++) { + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; + } + +// Create table on load. Just 255 signed longs. Not a problem. + var crcTable = makeTable(); + + + function crc32(crc, buf, len, pos) { + var t = crcTable, + end = pos + len; + + crc ^= -1; + + for (var i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; + } + + + module.exports = crc32; + +},{}],6:[function(require,module,exports){ + 'use strict'; + + + function GZheader() { + /* true if compressed data believed to be text */ + this.text = 0; + /* modification time */ + this.time = 0; + /* extra flags (not used when writing a gzip file) */ + this.xflags = 0; + /* operating system */ + this.os = 0; + /* pointer to extra field or Z_NULL if none */ + this.extra = null; + /* extra field length (valid if extra != Z_NULL) */ + this.extra_len = 0; // Actually, we don't need it in JS, + // but leave for few code modifications + + // + // Setup limits is not necessary because in js we should not preallocate memory + // for inflate use constant limit in 65536 bytes + // + + /* space at extra (only when reading header) */ + // this.extra_max = 0; + /* pointer to zero-terminated file name or Z_NULL */ + this.name = ''; + /* space at name (only when reading header) */ + // this.name_max = 0; + /* pointer to zero-terminated comment or Z_NULL */ + this.comment = ''; + /* space at comment (only when reading header) */ + // this.comm_max = 0; + /* true if there was or will be a header crc */ + this.hcrc = 0; + /* true when done reading gzip header (not used when writing a gzip file) */ + this.done = false; + } + + module.exports = GZheader; + +},{}],7:[function(require,module,exports){ + 'use strict'; + +// See state defs from inflate.js + var BAD = 30; /* got a data error -- remain here until reset */ + var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ + + /* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ + module.exports = function inflate_fast(strm, start) { + var state; + var _in; /* local strm.input */ + var last; /* have enough input while in < last */ + var _out; /* local strm.output */ + var beg; /* inflate()'s initial strm.output */ + var end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + var dmax; /* maximum distance from zlib header */ +//#endif + var wsize; /* window size or zero if not using window */ + var whave; /* valid bytes in the window */ + var wnext; /* window write index */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + var s_window; /* allocated sliding window, if wsize != 0 */ + var hold; /* local strm.hold */ + var bits; /* local strm.bits */ + var lcode; /* local strm.lencode */ + var dcode; /* local strm.distcode */ + var lmask; /* mask for first level of length codes */ + var dmask; /* mask for first level of distance codes */ + var here; /* retrieved table entry */ + var op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + var len; /* match length, unused bytes */ + var dist; /* match distance */ + var from; /* where to copy match from */ + var from_source; + + + var input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + s_window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } + +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = s_window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; + }; + +},{}],8:[function(require,module,exports){ + 'use strict'; + + + var utils = require('../utils/common'); + var adler32 = require('./adler32'); + var crc32 = require('./crc32'); + var inflate_fast = require('./inffast'); + var inflate_table = require('./inftrees'); + + var CODES = 0; + var LENS = 1; + var DISTS = 2; + + /* Public constants ==========================================================*/ + /* ===========================================================================*/ + + + /* Allowed flush values; see deflate() and inflate() below for details */ +//var Z_NO_FLUSH = 0; +//var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +//var Z_FULL_FLUSH = 3; + var Z_FINISH = 4; + var Z_BLOCK = 5; + var Z_TREES = 6; + + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + var Z_OK = 0; + var Z_STREAM_END = 1; + var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; + var Z_STREAM_ERROR = -2; + var Z_DATA_ERROR = -3; + var Z_MEM_ERROR = -4; + var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + + /* The deflate compression method */ + var Z_DEFLATED = 8; + + + /* STATES ====================================================================*/ + /* ===========================================================================*/ + + + var HEAD = 1; /* i: waiting for magic header */ + var FLAGS = 2; /* i: waiting for method and flags (gzip) */ + var TIME = 3; /* i: waiting for modification time (gzip) */ + var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ + var EXLEN = 5; /* i: waiting for extra length (gzip) */ + var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ + var NAME = 7; /* i: waiting for end of file name (gzip) */ + var COMMENT = 8; /* i: waiting for end of comment (gzip) */ + var HCRC = 9; /* i: waiting for header crc (gzip) */ + var DICTID = 10; /* i: waiting for dictionary check value */ + var DICT = 11; /* waiting for inflateSetDictionary() call */ + var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ + var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ + var STORED = 14; /* i: waiting for stored size (length and complement) */ + var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ + var COPY = 16; /* i/o: waiting for input or output to copy stored block */ + var TABLE = 17; /* i: waiting for dynamic block table lengths */ + var LENLENS = 18; /* i: waiting for code length code lengths */ + var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ + var LEN_ = 20; /* i: same as LEN below, but only first time in */ + var LEN = 21; /* i: waiting for length/lit/eob code */ + var LENEXT = 22; /* i: waiting for length extra bits */ + var DIST = 23; /* i: waiting for distance code */ + var DISTEXT = 24; /* i: waiting for distance extra bits */ + var MATCH = 25; /* o: waiting for output space to copy string */ + var LIT = 26; /* o: waiting for output space to write literal */ + var CHECK = 27; /* i: waiting for 32-bit check value */ + var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ + var DONE = 29; /* finished check, done -- remain here until reset */ + var BAD = 30; /* got a data error -- remain here until reset */ + var MEM = 31; /* got an inflate() memory error -- remain here until reset */ + var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ + + /* ===========================================================================*/ + + + + var ENOUGH_LENS = 852; + var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + + var MAX_WBITS = 15; + /* 32K LZ77 window */ + var DEF_WBITS = MAX_WBITS; + + + function zswap32(q) { + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); + } + + + function InflateState() { + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib) */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ + this.work = new utils.Buf16(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ + } + + function inflateResetKeep(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); + state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK; + } + + function inflateReset(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); + + } + + function inflateReset2(strm, windowBits) { + var wrap; + var state; + + /* get the state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 1; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); + } + + function inflateInit2(strm, windowBits) { + var ret; + var state; + + if (!strm) { return Z_STREAM_ERROR; } + //strm.msg = Z_NULL; /* in case we return an error */ + + state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.window = null/*Z_NULL*/; + ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK) { + strm.state = null/*Z_NULL*/; + } + return ret; + } + + function inflateInit(strm) { + return inflateInit2(strm, DEF_WBITS); + } + + + /* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ + var virgin = true; + + var lenfix, distfix; // We have no pointers in JS, so keep tables separate + + function fixedtables(state) { + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + var sym; + + lenfix = new utils.Buf32(512); + distfix = new utils.Buf32(32); + + /* literal/length table */ + sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } + + inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); + + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } + + inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; + } + + + /* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ + function updatewindow(strm, src, end, copy) { + var dist; + var state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new utils.Buf8(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + utils.arraySet(state.window, src, end - copy, dist, state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + utils.arraySet(state.window, src, end - copy, copy, 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; + } + + function inflate(strm, flush) { + var state; + var input, output; // input/output buffers + var next; /* next input INDEX */ + var put; /* next output INDEX */ + var have, left; /* available input and output */ + var hold; /* bit buffer */ + var bits; /* bits in bit buffer */ + var _in, _out; /* save starting available input and output */ + var copy; /* number of stored or match bytes to copy */ + var from; /* where to copy match bytes from */ + var from_source; + var here = 0; /* current decoding table entry */ + var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //var last; /* parent table entry */ + var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + var len; /* length to copy for repeats, bits to drop */ + var ret; /* return code */ + var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ + var opts; + + var n; // temporary var for NEED_BITS + + var order = /* permutation of code lengths */ + [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; + + + if (!strm || !strm.state || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR; + } + + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + state.flags = 0; /* expect zlib header */ + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + else if (len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } + state.dmax = 1 << len; + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if (state.flags & 0x0200) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more conveniend processing later + state.head.extra = new Array(state.head.extra_len); + } + utils.arraySet( + state.head.extra, + input, + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + copy, + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = zswap32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + utils.arraySet(output, input, next, copy, put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = { bits: state.lenbits }; + ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) { break; } + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = { bits: state.lenbits }; + ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = { bits: state.distbits }; + ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inflate_fast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' insdead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if (_out) { + strm.adler = state.check = + /*UPDATE(state.check, put - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.flags ? hold : zswap32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR; + break inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { + state.mode = MEM; + return Z_MEM_ERROR; + } + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if (state.wrap && _out) { + strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { + ret = Z_BUF_ERROR; + } + return ret; + } + + function inflateEnd(strm) { + + if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { + return Z_STREAM_ERROR; + } + + var state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK; + } + + function inflateGetHeader(strm, head) { + var state; + + /* check state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK; + } + + function inflateSetDictionary(strm, dictionary) { + var dictLength = dictionary.length; + + var state; + var dictid; + var ret; + + /* check state */ + if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; + } + + exports.inflateReset = inflateReset; + exports.inflateReset2 = inflateReset2; + exports.inflateResetKeep = inflateResetKeep; + exports.inflateInit = inflateInit; + exports.inflateInit2 = inflateInit2; + exports.inflate = inflate; + exports.inflateEnd = inflateEnd; + exports.inflateGetHeader = inflateGetHeader; + exports.inflateSetDictionary = inflateSetDictionary; + exports.inflateInfo = 'pako inflate (from Nodeca project)'; + + /* Not implemented + exports.inflateCopy = inflateCopy; + exports.inflateGetDictionary = inflateGetDictionary; + exports.inflateMark = inflateMark; + exports.inflatePrime = inflatePrime; + exports.inflateSync = inflateSync; + exports.inflateSyncPoint = inflateSyncPoint; + exports.inflateUndermine = inflateUndermine; + */ + +},{"../utils/common":1,"./adler32":3,"./crc32":5,"./inffast":7,"./inftrees":9}],9:[function(require,module,exports){ + 'use strict'; + + + var utils = require('../utils/common'); + + var MAXBITS = 15; + var ENOUGH_LENS = 852; + var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + + var CODES = 0; + var LENS = 1; + var DISTS = 2; + + var lbase = [ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 + ]; + + var lext = [ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 + ]; + + var dbase = [ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 + ]; + + var dext = [ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 + ]; + + module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) + { + var bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + var len = 0; /* a code's length in bits */ + var sym = 0; /* index of code symbols */ + var min = 0, max = 0; /* minimum and maximum code lengths */ + var root = 0; /* number of index bits for root table */ + var curr = 0; /* number of index bits for current table */ + var drop = 0; /* code bits to drop for sub-table */ + var left = 0; /* number of prefix codes available */ + var used = 0; /* code entries in table used */ + var huff = 0; /* Huffman code */ + var incr; /* for incrementing code, index */ + var fill; /* index for replicating entries */ + var low; /* low bits for current root entry */ + var mask; /* mask for low root bits */ + var next; /* next available space in table */ + var base = null; /* base value table to use */ + var base_index = 0; +// var shoextra; /* extra bits table to use */ + var end; /* use base and extra for symbol > end */ + var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ + var extra = null; + var extra_index = 0; + + var here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES) { + base = extra = work; /* dummy value--not used */ + end = 19; + + } else if (type === LENS) { + base = lbase; + base_index -= 257; + extra = lext; + extra_index -= 257; + end = 256; + + } else { /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here_bits = len - drop; + if (work[sym] < end) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] > end) { + here_op = extra[extra_index + work[sym]]; + here_val = base[base_index + work[sym]]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; + }; + +},{"../utils/common":1}],10:[function(require,module,exports){ + 'use strict'; + + module.exports = { + 2: 'need dictionary', /* Z_NEED_DICT 2 */ + 1: 'stream end', /* Z_STREAM_END 1 */ + 0: '', /* Z_OK 0 */ + '-1': 'file error', /* Z_ERRNO (-1) */ + '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ + '-3': 'data error', /* Z_DATA_ERROR (-3) */ + '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ + '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ + '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ + }; + +},{}],11:[function(require,module,exports){ + 'use strict'; + + + function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; + } + + module.exports = ZStream; + +},{}],"/lib/inflate.js":[function(require,module,exports){ + 'use strict'; + + + var zlib_inflate = require('./zlib/inflate'); + var utils = require('./utils/common'); + var strings = require('./utils/strings'); + var c = require('./zlib/constants'); + var msg = require('./zlib/messages'); + var ZStream = require('./zlib/zstream'); + var GZheader = require('./zlib/gzheader'); + + var toString = Object.prototype.toString; + + /** + * class Inflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[inflate]] + * and [[inflateRaw]]. + **/ + + /* internal + * inflate.chunks -> Array + * + * Chunks of output data, if [[Inflate#onData]] not overriden. + **/ + + /** + * Inflate.result -> Uint8Array|Array|String + * + * Uncompressed result, generated by default [[Inflate#onData]] + * and [[Inflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Inflate#push]] with `Z_FINISH` / `true` param) or if you + * push a chunk with explicit flush (call [[Inflate#push]] with + * `Z_SYNC_FLUSH` param). + **/ + + /** + * Inflate.err -> Number + * + * Error code after inflate finished. 0 (Z_OK) on success. + * Should be checked if broken data possible. + **/ + + /** + * Inflate.msg -> String + * + * Error message, if [[Inflate.err]] != 0 + **/ + + + /** + * new Inflate(options) + * - options (Object): zlib inflate options. + * + * Creates new inflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `windowBits` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw inflate + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * By default, when no options set, autodetect deflate/gzip data format via + * wrapper header. + * + * ##### Example: + * + * ```javascript + * var pako = require('pako') + * , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9]) + * , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * var inflate = new pako.Inflate({ level: 3}); + * + * inflate.push(chunk1, false); + * inflate.push(chunk2, true); // true -> last chunk + * + * if (inflate.err) { throw new Error(inflate.err); } + * + * console.log(inflate.result); + * ``` + **/ + function Inflate(options) { + if (!(this instanceof Inflate)) return new Inflate(options); + + this.options = utils.assign({ + chunkSize: 16384, + windowBits: 0, + to: '' + }, options || {}); + + var opt = this.options; + + // Force window size for `raw` data, if not set directly, + // because we have no header for autodetect. + if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { + opt.windowBits = -opt.windowBits; + if (opt.windowBits === 0) { opt.windowBits = -15; } + } + + // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate + if ((opt.windowBits >= 0) && (opt.windowBits < 16) && + !(options && options.windowBits)) { + opt.windowBits += 32; + } + + // Gzip header has no info about windows size, we can do autodetect only + // for deflate. So, if window size not set, force it to max when gzip possible + if ((opt.windowBits > 15) && (opt.windowBits < 48)) { + // bit 3 (16) -> gzipped data + // bit 4 (32) -> autodetect gzip/deflate + if ((opt.windowBits & 15) === 0) { + opt.windowBits |= 15; + } + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new ZStream(); + this.strm.avail_out = 0; + + var status = zlib_inflate.inflateInit2( + this.strm, + opt.windowBits + ); + + if (status !== c.Z_OK) { + throw new Error(msg[status]); + } + + this.header = new GZheader(); + + zlib_inflate.inflateGetHeader(this.strm, this.header); + } + + /** + * Inflate#push(data[, mode]) -> Boolean + * - data (Uint8Array|Array|ArrayBuffer|String): input data + * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. + * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. + * + * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with + * new output chunks. Returns `true` on success. The last data block must have + * mode Z_FINISH (or `true`). That will flush internal pending buffers and call + * [[Inflate#onEnd]]. For interim explicit flushes (without ending the stream) you + * can use mode Z_SYNC_FLUSH, keeping the decompression context. + * + * On fail call [[Inflate#onEnd]] with error code and return false. + * + * We strongly recommend to use `Uint8Array` on input for best speed (output + * format is detected automatically). Also, don't skip last param and always + * use the same type in your code (boolean or number). That will improve JS speed. + * + * For regular `Array`-s make sure all elements are [0..255]. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ + Inflate.prototype.push = function (data, mode) { + var strm = this.strm; + var chunkSize = this.options.chunkSize; + var dictionary = this.options.dictionary; + var status, _mode; + var next_out_utf8, tail, utf8str; + var dict; + + // Flag to properly process Z_BUF_ERROR on testing inflate call + // when we check that all output data was flushed. + var allowBufError = false; + + if (this.ended) { return false; } + _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH); + + // Convert data if needed + if (typeof data === 'string') { + // Only binary strings can be decompressed on practice + strm.input = strings.binstring2buf(data); + } else if (toString.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + do { + if (strm.avail_out === 0) { + strm.output = new utils.Buf8(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH); /* no bad return value */ + + if (status === c.Z_NEED_DICT && dictionary) { + // Convert data if needed + if (typeof dictionary === 'string') { + dict = strings.string2buf(dictionary); + } else if (toString.call(dictionary) === '[object ArrayBuffer]') { + dict = new Uint8Array(dictionary); + } else { + dict = dictionary; + } + + status = zlib_inflate.inflateSetDictionary(this.strm, dict); + + } + + if (status === c.Z_BUF_ERROR && allowBufError === true) { + status = c.Z_OK; + allowBufError = false; + } + + if (status !== c.Z_STREAM_END && status !== c.Z_OK) { + this.onEnd(status); + this.ended = true; + return false; + } + + if (strm.next_out) { + if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && (_mode === c.Z_FINISH || _mode === c.Z_SYNC_FLUSH))) { + + if (this.options.to === 'string') { + + next_out_utf8 = strings.utf8border(strm.output, strm.next_out); + + tail = strm.next_out - next_out_utf8; + utf8str = strings.buf2string(strm.output, next_out_utf8); + + // move tail + strm.next_out = tail; + strm.avail_out = chunkSize - tail; + if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); } + + this.onData(utf8str); + + } else { + this.onData(utils.shrinkBuf(strm.output, strm.next_out)); + } + } + } + + // When no more input data, we should check that internal inflate buffers + // are flushed. The only way to do it when avail_out = 0 - run one more + // inflate pass. But if output data not exists, inflate return Z_BUF_ERROR. + // Here we set flag to process this error properly. + // + // NOTE. Deflate does not return error in this case and does not needs such + // logic. + if (strm.avail_in === 0 && strm.avail_out === 0) { + allowBufError = true; + } + + } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== c.Z_STREAM_END); + + if (status === c.Z_STREAM_END) { + _mode = c.Z_FINISH; + } + + // Finalize on the last chunk. + if (_mode === c.Z_FINISH) { + status = zlib_inflate.inflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return status === c.Z_OK; + } + + // callback interim results if Z_SYNC_FLUSH. + if (_mode === c.Z_SYNC_FLUSH) { + this.onEnd(c.Z_OK); + strm.avail_out = 0; + return true; + } + + return true; + }; + + + /** + * Inflate#onData(chunk) -> Void + * - chunk (Uint8Array|Array|String): ouput data. Type of array depends + * on js engine support. When string output requested, each chunk + * will be string. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ + Inflate.prototype.onData = function (chunk) { + this.chunks.push(chunk); + }; + + + /** + * Inflate#onEnd(status) -> Void + * - status (Number): inflate status. 0 (Z_OK) on success, + * other if not. + * + * Called either after you tell inflate that the input stream is + * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH) + * or if an error happened. By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ + Inflate.prototype.onEnd = function (status) { + // On success - join + if (status === c.Z_OK) { + if (this.options.to === 'string') { + // Glue & convert here, until we teach pako to send + // utf8 alligned strings to onData + this.result = this.chunks.join(''); + } else { + this.result = utils.flattenChunks(this.chunks); + } + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; + }; + + + /** + * inflate(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to decompress. + * - options (Object): zlib inflate options. + * + * Decompress `data` with inflate/ungzip and `options`. Autodetect + * format via wrapper header by default. That's why we don't provide + * separate `ungzip` method. + * + * Supported options are: + * + * - windowBits + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * + * ##### Example: + * + * ```javascript + * var pako = require('pako') + * , input = pako.deflate([1,2,3,4,5,6,7,8,9]) + * , output; + * + * try { + * output = pako.inflate(input); + * } catch (err) + * console.log(err); + * } + * ``` + **/ + function inflate(input, options) { + var inflator = new Inflate(options); + + inflator.push(input, true); + + // That will never happens, if you don't cheat with options :) + if (inflator.err) { throw inflator.msg || msg[inflator.err]; } + + return inflator.result; + } + + + /** + * inflateRaw(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to decompress. + * - options (Object): zlib inflate options. + * + * The same as [[inflate]], but creates raw data, without wrapper + * (header and adler32 crc). + **/ + function inflateRaw(input, options) { + options = options || {}; + options.raw = true; + return inflate(input, options); + } + + + /** + * ungzip(data[, options]) -> Uint8Array|Array|String + * - data (Uint8Array|Array|String): input data to decompress. + * - options (Object): zlib inflate options. + * + * Just shortcut to [[inflate]], because it autodetects format + * by header.content. Done for convenience. + **/ + + + exports.Inflate = Inflate; + exports.inflate = inflate; + exports.inflateRaw = inflateRaw; + exports.ungzip = inflate; + +},{"./utils/common":1,"./utils/strings":2,"./zlib/constants":4,"./zlib/gzheader":6,"./zlib/inflate":8,"./zlib/messages":10,"./zlib/zstream":11}]},{},[])("/lib/inflate.js") +}); diff --git a/Source/ThirdParty/protobuf-minimal.js b/Source/ThirdParty/protobuf-minimal.js new file mode 100644 index 000000000000..7719d7e6c2bb --- /dev/null +++ b/Source/ThirdParty/protobuf-minimal.js @@ -0,0 +1,2465 @@ +/*! + * protobuf.js v6.7.0 (c) 2016, Daniel Wirtz + * Compiled Wed, 22 Mar 2017 17:30:26 UTC + * Licensed under the BSD-3-Clause License + * see: https://github.com/dcodeIO/protobuf.js for details + */ +(function(global,undefined){"use strict";(function prelude(modules, cache, entries) { + + // This is the prelude used to bundle protobuf.js for the browser. Wraps up the CommonJS + // sources through a conflict-free require shim and is again wrapped within an iife that + // provides a unified `global` and a minification-friendly `undefined` var plus a global + // "use strict" directive so that minification can remove the directives of each module. + + function $require(name) { + var $module = cache[name]; + if (!$module) + modules[name][0].call($module = cache[name] = { exports: {} }, $require, $module, $module.exports); + return $module.exports; + } + + // Expose globally + var protobuf = global.protobuf = $require(entries[0]); + + // Be nice to AMD + if (typeof define === "function" && define.amd) + define([], function() { + protobuf.configure(); + return protobuf; + }); + + // Be nice to CommonJS + if (typeof module === "object" && module && module.exports) + module.exports = protobuf; + +})/* end of prelude */({1:[function(require,module,exports){ +"use strict"; +module.exports = asPromise; + +/** + * Returns a promise from a node-style callback function. + * @memberof util + * @param {function(?Error, ...*)} fn Function to call + * @param {*} ctx Function context + * @param {...*} params Function arguments + * @returns {Promise<*>} Promisified function + */ +function asPromise(fn, ctx/*, varargs */) { + var params = []; + for (var i = 2; i < arguments.length;) + params.push(arguments[i++]); + var pending = true; + return new Promise(function asPromiseExecutor(resolve, reject) { + params.push(function asPromiseCallback(err/*, varargs */) { + if (pending) { + pending = false; + if (err) + reject(err); + else { + var args = []; + for (var i = 1; i < arguments.length;) + args.push(arguments[i++]); + resolve.apply(null, args); + } + } + }); + try { + fn.apply(ctx || this, params); // eslint-disable-line no-invalid-this + } catch (err) { + if (pending) { + pending = false; + reject(err); + } + } + }); +} + +},{}],2:[function(require,module,exports){ +"use strict"; + +/** + * A minimal base64 implementation for number arrays. + * @memberof util + * @namespace + */ +var base64 = exports; + +/** + * Calculates the byte length of a base64 encoded string. + * @param {string} string Base64 encoded string + * @returns {number} Byte length + */ +base64.length = function length(string) { + var p = string.length; + if (!p) + return 0; + var n = 0; + while (--p % 4 > 1 && string.charAt(p) === "=") + ++n; + return Math.ceil(string.length * 3) / 4 - n; +}; + +// Base64 encoding table +var b64 = new Array(64); + +// Base64 decoding table +var s64 = new Array(123); + +// 65..90, 97..122, 48..57, 43, 47 +for (var i = 0; i < 64;) + s64[b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; + +/** + * Encodes a buffer to a base64 encoded string. + * @param {Uint8Array} buffer Source buffer + * @param {number} start Source start + * @param {number} end Source end + * @returns {string} Base64 encoded string + */ +base64.encode = function encode(buffer, start, end) { + var string = []; // alt: new Array(Math.ceil((end - start) / 3) * 4); + var i = 0, // output index + j = 0, // goto index + t; // temporary + while (start < end) { + var b = buffer[start++]; + switch (j) { + case 0: + string[i++] = b64[b >> 2]; + t = (b & 3) << 4; + j = 1; + break; + case 1: + string[i++] = b64[t | b >> 4]; + t = (b & 15) << 2; + j = 2; + break; + case 2: + string[i++] = b64[t | b >> 6]; + string[i++] = b64[b & 63]; + j = 0; + break; + } + } + if (j) { + string[i++] = b64[t]; + string[i ] = 61; + if (j === 1) + string[i + 1] = 61; + } + return String.fromCharCode.apply(String, string); +}; + +var invalidEncoding = "invalid encoding"; + +/** + * Decodes a base64 encoded string to a buffer. + * @param {string} string Source string + * @param {Uint8Array} buffer Destination buffer + * @param {number} offset Destination offset + * @returns {number} Number of bytes written + * @throws {Error} If encoding is invalid + */ +base64.decode = function decode(string, buffer, offset) { + var start = offset; + var j = 0, // goto index + t; // temporary + for (var i = 0; i < string.length;) { + var c = string.charCodeAt(i++); + if (c === 61 && j > 1) + break; + if ((c = s64[c]) === undefined) + throw Error(invalidEncoding); + switch (j) { + case 0: + t = c; + j = 1; + break; + case 1: + buffer[offset++] = t << 2 | (c & 48) >> 4; + t = c; + j = 2; + break; + case 2: + buffer[offset++] = (t & 15) << 4 | (c & 60) >> 2; + t = c; + j = 3; + break; + case 3: + buffer[offset++] = (t & 3) << 6 | c; + j = 0; + break; + } + } + if (j === 1) + throw Error(invalidEncoding); + return offset - start; +}; + +/** + * Tests if the specified string appears to be base64 encoded. + * @param {string} string String to test + * @returns {boolean} `true` if probably base64 encoded, otherwise false + */ +base64.test = function test(string) { + return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(string); +}; + +},{}],3:[function(require,module,exports){ +"use strict"; +module.exports = EventEmitter; + +/** + * Constructs a new event emitter instance. + * @classdesc A minimal event emitter. + * @memberof util + * @constructor + */ +function EventEmitter() { + + /** + * Registered listeners. + * @type {Object.} + * @private + */ + this._listeners = {}; +} + +/** + * Registers an event listener. + * @param {string} evt Event name + * @param {function} fn Listener + * @param {*} [ctx] Listener context + * @returns {util.EventEmitter} `this` + */ +EventEmitter.prototype.on = function on(evt, fn, ctx) { + (this._listeners[evt] || (this._listeners[evt] = [])).push({ + fn : fn, + ctx : ctx || this + }); + return this; +}; + +/** + * Removes an event listener or any matching listeners if arguments are omitted. + * @param {string} [evt] Event name. Removes all listeners if omitted. + * @param {function} [fn] Listener to remove. Removes all listeners of `evt` if omitted. + * @returns {util.EventEmitter} `this` + */ +EventEmitter.prototype.off = function off(evt, fn) { + if (evt === undefined) + this._listeners = {}; + else { + if (fn === undefined) + this._listeners[evt] = []; + else { + var listeners = this._listeners[evt]; + for (var i = 0; i < listeners.length;) + if (listeners[i].fn === fn) + listeners.splice(i, 1); + else + ++i; + } + } + return this; +}; + +/** + * Emits an event by calling its listeners with the specified arguments. + * @param {string} evt Event name + * @param {...*} args Arguments + * @returns {util.EventEmitter} `this` + */ +EventEmitter.prototype.emit = function emit(evt) { + var listeners = this._listeners[evt]; + if (listeners) { + var args = [], + i = 1; + for (; i < arguments.length;) + args.push(arguments[i++]); + for (i = 0; i < listeners.length;) + listeners[i].fn.apply(listeners[i++].ctx, args); + } + return this; +}; + +},{}],4:[function(require,module,exports){ +"use strict"; +module.exports = inquire; + +/** + * Requires a module only if available. + * @memberof util + * @param {string} moduleName Module to require + * @returns {?Object} Required module if available and not empty, otherwise `null` + */ +function inquire(moduleName) { + try { + var mod = eval("quire".replace(/^/,"re"))(moduleName); // eslint-disable-line no-eval + if (mod && (mod.length || Object.keys(mod).length)) + return mod; + } catch (e) {} // eslint-disable-line no-empty + return null; +} + +},{}],5:[function(require,module,exports){ +"use strict"; +module.exports = pool; + +/** + * An allocator as used by {@link util.pool}. + * @typedef PoolAllocator + * @type {function} + * @param {number} size Buffer size + * @returns {Uint8Array} Buffer + */ + +/** + * A slicer as used by {@link util.pool}. + * @typedef PoolSlicer + * @type {function} + * @param {number} start Start offset + * @param {number} end End offset + * @returns {Uint8Array} Buffer slice + * @this {Uint8Array} + */ + +/** + * A general purpose buffer pool. + * @memberof util + * @function + * @param {PoolAllocator} alloc Allocator + * @param {PoolSlicer} slice Slicer + * @param {number} [size=8192] Slab size + * @returns {PoolAllocator} Pooled allocator + */ +function pool(alloc, slice, size) { + var SIZE = size || 8192; + var MAX = SIZE >>> 1; + var slab = null; + var offset = SIZE; + return function pool_alloc(size) { + if (size < 1 || size > MAX) + return alloc(size); + if (offset + size > SIZE) { + slab = alloc(SIZE); + offset = 0; + } + var buf = slice.call(slab, offset, offset += size); + if (offset & 7) // align to 32 bit + offset = (offset | 7) + 1; + return buf; + }; +} + +},{}],6:[function(require,module,exports){ +"use strict"; + +/** + * A minimal UTF8 implementation for number arrays. + * @memberof util + * @namespace + */ +var utf8 = exports; + +/** + * Calculates the UTF8 byte length of a string. + * @param {string} string String + * @returns {number} Byte length + */ +utf8.length = function utf8_length(string) { + var len = 0, + c = 0; + for (var i = 0; i < string.length; ++i) { + c = string.charCodeAt(i); + if (c < 128) + len += 1; + else if (c < 2048) + len += 2; + else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) { + ++i; + len += 4; + } else + len += 3; + } + return len; +}; + +/** + * Reads UTF8 bytes as a string. + * @param {Uint8Array} buffer Source buffer + * @param {number} start Source start + * @param {number} end Source end + * @returns {string} String read + */ +utf8.read = function utf8_read(buffer, start, end) { + var len = end - start; + if (len < 1) + return ""; + var parts = null, + chunk = [], + i = 0, // char offset + t; // temporary + while (start < end) { + t = buffer[start++]; + if (t < 128) + chunk[i++] = t; + else if (t > 191 && t < 224) + chunk[i++] = (t & 31) << 6 | buffer[start++] & 63; + else if (t > 239 && t < 365) { + t = ((t & 7) << 18 | (buffer[start++] & 63) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63) - 0x10000; + chunk[i++] = 0xD800 + (t >> 10); + chunk[i++] = 0xDC00 + (t & 1023); + } else + chunk[i++] = (t & 15) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63; + if (i > 8191) { + (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); + i = 0; + } + } + if (parts) { + if (i) + parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); + return parts.join(""); + } + return String.fromCharCode.apply(String, chunk.slice(0, i)); +}; + +/** + * Writes a string as UTF8 bytes. + * @param {string} string Source string + * @param {Uint8Array} buffer Destination buffer + * @param {number} offset Destination offset + * @returns {number} Bytes written + */ +utf8.write = function utf8_write(string, buffer, offset) { + var start = offset, + c1, // character 1 + c2; // character 2 + for (var i = 0; i < string.length; ++i) { + c1 = string.charCodeAt(i); + if (c1 < 128) { + buffer[offset++] = c1; + } else if (c1 < 2048) { + buffer[offset++] = c1 >> 6 | 192; + buffer[offset++] = c1 & 63 | 128; + } else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) { + c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF); + ++i; + buffer[offset++] = c1 >> 18 | 240; + buffer[offset++] = c1 >> 12 & 63 | 128; + buffer[offset++] = c1 >> 6 & 63 | 128; + buffer[offset++] = c1 & 63 | 128; + } else { + buffer[offset++] = c1 >> 12 | 224; + buffer[offset++] = c1 >> 6 & 63 | 128; + buffer[offset++] = c1 & 63 | 128; + } + } + return offset - start; +}; + +},{}],7:[function(require,module,exports){ +"use strict"; +var protobuf = exports; + +/** + * Build type, one of `"full"`, `"light"` or `"minimal"`. + * @name build + * @type {string} + * @const + */ +protobuf.build = "minimal"; + +/** + * Named roots. + * This is where pbjs stores generated structures (the option `-r, --root` specifies a name). + * Can also be used manually to make roots available accross modules. + * @name roots + * @type {Object.} + * @example + * // pbjs -r myroot -o compiled.js ... + * + * // in another module: + * require("./compiled.js"); + * + * // in any subsequent module: + * var root = protobuf.roots["myroot"]; + */ +protobuf.roots = {}; + +// Serialization +protobuf.Writer = require(14); +protobuf.BufferWriter = require(15); +protobuf.Reader = require(8); +protobuf.BufferReader = require(9); + +// Utility +protobuf.util = require(13); +protobuf.rpc = require(10); +protobuf.configure = configure; + +/* istanbul ignore next */ +/** + * Reconfigures the library according to the environment. + * @returns {undefined} + */ +function configure() { + protobuf.Reader._configure(protobuf.BufferReader); + protobuf.util._configure(); +} + +// Configure serialization +protobuf.Writer._configure(protobuf.BufferWriter); +configure(); + +},{"10":10,"13":13,"14":14,"15":15,"8":8,"9":9}],8:[function(require,module,exports){ +"use strict"; +module.exports = Reader; + +var util = require(13); + +var BufferReader; // cyclic + +var LongBits = util.LongBits, + utf8 = util.utf8; + +/* istanbul ignore next */ +function indexOutOfRange(reader, writeLength) { + return RangeError("index out of range: " + reader.pos + " + " + (writeLength || 1) + " > " + reader.len); +} + +/** + * Constructs a new reader instance using the specified buffer. + * @classdesc Wire format reader using `Uint8Array` if available, otherwise `Array`. + * @constructor + * @param {Uint8Array} buffer Buffer to read from + */ +function Reader(buffer) { + + /** + * Read buffer. + * @type {Uint8Array} + */ + this.buf = buffer; + + /** + * Read buffer position. + * @type {number} + */ + this.pos = 0; + + /** + * Read buffer length. + * @type {number} + */ + this.len = buffer.length; +} + +var create_array = typeof Uint8Array !== "undefined" + ? function create_typed_array(buffer) { + if (buffer instanceof Uint8Array || Array.isArray(buffer)) + return new Reader(buffer); + throw Error("illegal buffer"); + } + /* istanbul ignore next */ + : function create_array(buffer) { + if (Array.isArray(buffer)) + return new Reader(buffer); + throw Error("illegal buffer"); + }; + +/** + * Creates a new reader using the specified buffer. + * @function + * @param {Uint8Array|Buffer} buffer Buffer to read from + * @returns {Reader|BufferReader} A {@link BufferReader} if `buffer` is a Buffer, otherwise a {@link Reader} + * @throws {Error} If `buffer` is not a valid buffer + */ +Reader.create = util.Buffer + ? function create_buffer_setup(buffer) { + return (Reader.create = function create_buffer(buffer) { + return util.Buffer.isBuffer(buffer) + ? new BufferReader(buffer) + /* istanbul ignore next */ + : create_array(buffer); + })(buffer); + } + /* istanbul ignore next */ + : create_array; + +Reader.prototype._slice = util.Array.prototype.subarray || /* istanbul ignore next */ util.Array.prototype.slice; + +/** + * Reads a varint as an unsigned 32 bit value. + * @function + * @returns {number} Value read + */ +Reader.prototype.uint32 = (function read_uint32_setup() { + var value = 4294967295; // optimizer type-hint, tends to deopt otherwise (?!) + return function read_uint32() { + value = ( this.buf[this.pos] & 127 ) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 127) << 7) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 127) << 14) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 127) << 21) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 15) << 28) >>> 0; if (this.buf[this.pos++] < 128) return value; + + /* istanbul ignore next */ + if ((this.pos += 5) > this.len) { + this.pos = this.len; + throw indexOutOfRange(this, 10); + } + return value; + }; +})(); + +/** + * Reads a varint as a signed 32 bit value. + * @returns {number} Value read + */ +Reader.prototype.int32 = function read_int32() { + return this.uint32() | 0; +}; + +/** + * Reads a zig-zag encoded varint as a signed 32 bit value. + * @returns {number} Value read + */ +Reader.prototype.sint32 = function read_sint32() { + var value = this.uint32(); + return value >>> 1 ^ -(value & 1) | 0; +}; + +/* eslint-disable no-invalid-this */ + +function readLongVarint() { + // tends to deopt with local vars for octet etc. + var bits = new LongBits(0, 0); + var i = 0; + if (this.len - this.pos > 4) { // fast route (lo) + for (; i < 4; ++i) { + // 1st..4th + bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + // 5th + bits.lo = (bits.lo | (this.buf[this.pos] & 127) << 28) >>> 0; + bits.hi = (bits.hi | (this.buf[this.pos] & 127) >> 4) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + i = 0; + } else { + for (; i < 3; ++i) { + /* istanbul ignore next */ + if (this.pos >= this.len) + throw indexOutOfRange(this); + // 1st..3th + bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + // 4th + bits.lo = (bits.lo | (this.buf[this.pos++] & 127) << i * 7) >>> 0; + return bits; + } + if (this.len - this.pos > 4) { // fast route (hi) + for (; i < 5; ++i) { + // 6th..10th + bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + } else { + for (; i < 5; ++i) { + /* istanbul ignore next */ + if (this.pos >= this.len) + throw indexOutOfRange(this); + // 6th..10th + bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + } + /* istanbul ignore next */ + throw Error("invalid varint encoding"); +} + +/* eslint-enable no-invalid-this */ + +/** + * Reads a varint as a signed 64 bit value. + * @name Reader#int64 + * @function + * @returns {Long|number} Value read + */ + +/** + * Reads a varint as an unsigned 64 bit value. + * @name Reader#uint64 + * @function + * @returns {Long|number} Value read + */ + +/** + * Reads a zig-zag encoded varint as a signed 64 bit value. + * @name Reader#sint64 + * @function + * @returns {Long|number} Value read + */ + +/** + * Reads a varint as a boolean. + * @returns {boolean} Value read + */ +Reader.prototype.bool = function read_bool() { + return this.uint32() !== 0; +}; + +function readFixed32(buf, end) { + return (buf[end - 4] + | buf[end - 3] << 8 + | buf[end - 2] << 16 + | buf[end - 1] << 24) >>> 0; +} + +/** + * Reads fixed 32 bits as an unsigned 32 bit integer. + * @returns {number} Value read + */ +Reader.prototype.fixed32 = function read_fixed32() { + + /* istanbul ignore next */ + if (this.pos + 4 > this.len) + throw indexOutOfRange(this, 4); + + return readFixed32(this.buf, this.pos += 4); +}; + +/** + * Reads fixed 32 bits as a signed 32 bit integer. + * @returns {number} Value read + */ +Reader.prototype.sfixed32 = function read_sfixed32() { + + /* istanbul ignore next */ + if (this.pos + 4 > this.len) + throw indexOutOfRange(this, 4); + + return readFixed32(this.buf, this.pos += 4) | 0; +}; + +/* eslint-disable no-invalid-this */ + +function readFixed64(/* this: Reader */) { + + /* istanbul ignore next */ + if (this.pos + 8 > this.len) + throw indexOutOfRange(this, 8); + + return new LongBits(readFixed32(this.buf, this.pos += 4), readFixed32(this.buf, this.pos += 4)); +} + +/* eslint-enable no-invalid-this */ + +/** + * Reads fixed 64 bits. + * @name Reader#fixed64 + * @function + * @returns {Long|number} Value read + */ + +/** + * Reads zig-zag encoded fixed 64 bits. + * @name Reader#sfixed64 + * @function + * @returns {Long|number} Value read + */ + +var readFloat = typeof Float32Array !== "undefined" + ? (function() { + var f32 = new Float32Array(1), + f8b = new Uint8Array(f32.buffer); + f32[0] = -0; + return f8b[3] // already le? + ? function readFloat_f32(buf, pos) { + f8b[0] = buf[pos ]; + f8b[1] = buf[pos + 1]; + f8b[2] = buf[pos + 2]; + f8b[3] = buf[pos + 3]; + return f32[0]; + } + /* istanbul ignore next */ + : function readFloat_f32_le(buf, pos) { + f8b[0] = buf[pos + 3]; + f8b[1] = buf[pos + 2]; + f8b[2] = buf[pos + 1]; + f8b[3] = buf[pos ]; + return f32[0]; + }; + })() + /* istanbul ignore next */ + : function readFloat_ieee754(buf, pos) { + var uint = readFixed32(buf, pos + 4), + sign = (uint >> 31) * 2 + 1, + exponent = uint >>> 23 & 255, + mantissa = uint & 8388607; + return exponent === 255 + ? mantissa + ? NaN + : sign * Infinity + : exponent === 0 // denormal + ? sign * 1.401298464324817e-45 * mantissa + : sign * Math.pow(2, exponent - 150) * (mantissa + 8388608); + }; + +/** + * Reads a float (32 bit) as a number. + * @function + * @returns {number} Value read + */ +Reader.prototype.float = function read_float() { + + /* istanbul ignore next */ + if (this.pos + 4 > this.len) + throw indexOutOfRange(this, 4); + + var value = readFloat(this.buf, this.pos); + this.pos += 4; + return value; +}; + +var readDouble = typeof Float64Array !== "undefined" + ? (function() { + var f64 = new Float64Array(1), + f8b = new Uint8Array(f64.buffer); + f64[0] = -0; + return f8b[7] // already le? + ? function readDouble_f64(buf, pos) { + f8b[0] = buf[pos ]; + f8b[1] = buf[pos + 1]; + f8b[2] = buf[pos + 2]; + f8b[3] = buf[pos + 3]; + f8b[4] = buf[pos + 4]; + f8b[5] = buf[pos + 5]; + f8b[6] = buf[pos + 6]; + f8b[7] = buf[pos + 7]; + return f64[0]; + } + /* istanbul ignore next */ + : function readDouble_f64_le(buf, pos) { + f8b[0] = buf[pos + 7]; + f8b[1] = buf[pos + 6]; + f8b[2] = buf[pos + 5]; + f8b[3] = buf[pos + 4]; + f8b[4] = buf[pos + 3]; + f8b[5] = buf[pos + 2]; + f8b[6] = buf[pos + 1]; + f8b[7] = buf[pos ]; + return f64[0]; + }; + })() + /* istanbul ignore next */ + : function readDouble_ieee754(buf, pos) { + var lo = readFixed32(buf, pos + 4), + hi = readFixed32(buf, pos + 8); + var sign = (hi >> 31) * 2 + 1, + exponent = hi >>> 20 & 2047, + mantissa = 4294967296 * (hi & 1048575) + lo; + return exponent === 2047 + ? mantissa + ? NaN + : sign * Infinity + : exponent === 0 // denormal + ? sign * 5e-324 * mantissa + : sign * Math.pow(2, exponent - 1075) * (mantissa + 4503599627370496); + }; + +/** + * Reads a double (64 bit float) as a number. + * @function + * @returns {number} Value read + */ +Reader.prototype.double = function read_double() { + + /* istanbul ignore next */ + if (this.pos + 8 > this.len) + throw indexOutOfRange(this, 4); + + var value = readDouble(this.buf, this.pos); + this.pos += 8; + return value; +}; + +/** + * Reads a sequence of bytes preceeded by its length as a varint. + * @returns {Uint8Array} Value read + */ +Reader.prototype.bytes = function read_bytes() { + var length = this.uint32(), + start = this.pos, + end = this.pos + length; + + /* istanbul ignore next */ + if (end > this.len) + throw indexOutOfRange(this, length); + + this.pos += length; + return start === end // fix for IE 10/Win8 and others' subarray returning array of size 1 + ? new this.buf.constructor(0) + : this._slice.call(this.buf, start, end); +}; + +/** + * Reads a string preceeded by its byte length as a varint. + * @returns {string} Value read + */ +Reader.prototype.string = function read_string() { + var bytes = this.bytes(); + return utf8.read(bytes, 0, bytes.length); +}; + +/** + * Skips the specified number of bytes if specified, otherwise skips a varint. + * @param {number} [length] Length if known, otherwise a varint is assumed + * @returns {Reader} `this` + */ +Reader.prototype.skip = function skip(length) { + if (typeof length === "number") { + /* istanbul ignore next */ + if (this.pos + length > this.len) + throw indexOutOfRange(this, length); + this.pos += length; + } else { + /* istanbul ignore next */ + do { + if (this.pos >= this.len) + throw indexOutOfRange(this); + } while (this.buf[this.pos++] & 128); + } + return this; +}; + +/** + * Skips the next element of the specified wire type. + * @param {number} wireType Wire type received + * @returns {Reader} `this` + */ +Reader.prototype.skipType = function(wireType) { + switch (wireType) { + case 0: + this.skip(); + break; + case 1: + this.skip(8); + break; + case 2: + this.skip(this.uint32()); + break; + case 3: + do { // eslint-disable-line no-constant-condition + if ((wireType = this.uint32() & 7) === 4) + break; + this.skipType(wireType); + } while (true); + break; + case 5: + this.skip(4); + break; + + /* istanbul ignore next */ + default: + throw Error("invalid wire type " + wireType + " at offset " + this.pos); + } + return this; +}; + +Reader._configure = function(BufferReader_) { + BufferReader = BufferReader_; + + var fn = util.Long ? "toLong" : /* istanbul ignore next */ "toNumber"; + util.merge(Reader.prototype, { + + int64: function read_int64() { + return readLongVarint.call(this)[fn](false); + }, + + uint64: function read_uint64() { + return readLongVarint.call(this)[fn](true); + }, + + sint64: function read_sint64() { + return readLongVarint.call(this).zzDecode()[fn](false); + }, + + fixed64: function read_fixed64() { + return readFixed64.call(this)[fn](true); + }, + + sfixed64: function read_sfixed64() { + return readFixed64.call(this)[fn](false); + } + + }); +}; + +},{"13":13}],9:[function(require,module,exports){ +"use strict"; +module.exports = BufferReader; + +// extends Reader +var Reader = require(8); +(BufferReader.prototype = Object.create(Reader.prototype)).constructor = BufferReader; + +var util = require(13); + +/** + * Constructs a new buffer reader instance. + * @classdesc Wire format reader using node buffers. + * @extends Reader + * @constructor + * @param {Buffer} buffer Buffer to read from + */ +function BufferReader(buffer) { + Reader.call(this, buffer); + + /** + * Read buffer. + * @name BufferReader#buf + * @type {Buffer} + */ +} + +/* istanbul ignore else */ +if (util.Buffer) + BufferReader.prototype._slice = util.Buffer.prototype.slice; + +/** + * @override + */ +BufferReader.prototype.string = function read_string_buffer() { + var len = this.uint32(); // modifies pos + return this.buf.utf8Slice(this.pos, this.pos = Math.min(this.pos + len, this.len)); +}; + +/** + * Reads a sequence of bytes preceeded by its length as a varint. + * @name BufferReader#bytes + * @function + * @returns {Buffer} Value read + */ + +},{"13":13,"8":8}],10:[function(require,module,exports){ +"use strict"; + +/** + * Streaming RPC helpers. + * @namespace + */ +var rpc = exports; + +/** + * RPC implementation passed to {@link Service#create} performing a service request on network level, i.e. by utilizing http requests or websockets. + * @typedef RPCImpl + * @type {function} + * @param {Method|rpc.ServiceMethod} method Reflected or static method being called + * @param {Uint8Array} requestData Request data + * @param {RPCImplCallback} callback Callback function + * @returns {undefined} + * @example + * function rpcImpl(method, requestData, callback) { + * if (protobuf.util.lcFirst(method.name) !== "myMethod") // compatible with static code + * throw Error("no such method"); + * asynchronouslyObtainAResponse(requestData, function(err, responseData) { + * callback(err, responseData); + * }); + * } + */ + +/** + * Node-style callback as used by {@link RPCImpl}. + * @typedef RPCImplCallback + * @type {function} + * @param {?Error} error Error, if any, otherwise `null` + * @param {?Uint8Array} [response] Response data or `null` to signal end of stream, if there hasn't been an error + * @returns {undefined} + */ + +rpc.Service = require(11); + +},{"11":11}],11:[function(require,module,exports){ +"use strict"; +module.exports = Service; + +var util = require(13); + +// Extends EventEmitter +(Service.prototype = Object.create(util.EventEmitter.prototype)).constructor = Service; + +/** + * A service method callback as used by {@link rpc.ServiceMethod|ServiceMethod}. + * + * Differs from {@link RPCImplCallback} in that it is an actual callback of a service method which may not return `response = null`. + * @typedef rpc.ServiceMethodCallback + * @type {function} + * @param {?Error} error Error, if any + * @param {?Message} [response] Response message + * @returns {undefined} + */ + +/** + * A service method part of a {@link rpc.ServiceMethodMixin|ServiceMethodMixin} and thus {@link rpc.Service} as created by {@link Service.create}. + * @typedef rpc.ServiceMethod + * @type {function} + * @param {Message|Object.} request Request message or plain object + * @param {rpc.ServiceMethodCallback} [callback] Node-style callback called with the error, if any, and the response message + * @returns {Promise} Promise if `callback` has been omitted, otherwise `undefined` + */ + +/** + * A service method mixin. + * + * When using TypeScript, mixed in service methods are only supported directly with a type definition of a static module (used with reflection). Otherwise, explicit casting is required. + * @typedef rpc.ServiceMethodMixin + * @type {Object.} + * @example + * // Explicit casting with TypeScript + * (myRpcService["myMethod"] as protobuf.rpc.ServiceMethod)(...) + */ + +/** + * Constructs a new RPC service instance. + * @classdesc An RPC service as returned by {@link Service#create}. + * @exports rpc.Service + * @extends util.EventEmitter + * @augments rpc.ServiceMethodMixin + * @constructor + * @param {RPCImpl} rpcImpl RPC implementation + * @param {boolean} [requestDelimited=false] Whether requests are length-delimited + * @param {boolean} [responseDelimited=false] Whether responses are length-delimited + */ +function Service(rpcImpl, requestDelimited, responseDelimited) { + + if (typeof rpcImpl !== "function") + throw TypeError("rpcImpl must be a function"); + + util.EventEmitter.call(this); + + /** + * RPC implementation. Becomes `null` once the service is ended. + * @type {?RPCImpl} + */ + this.rpcImpl = rpcImpl; + + /** + * Whether requests are length-delimited. + * @type {boolean} + */ + this.requestDelimited = Boolean(requestDelimited); + + /** + * Whether responses are length-delimited. + * @type {boolean} + */ + this.responseDelimited = Boolean(responseDelimited); +} + +/** + * Calls a service method through {@link rpc.Service#rpcImpl|rpcImpl}. + * @param {Method|rpc.ServiceMethod} method Reflected or static method + * @param {function} requestCtor Request constructor + * @param {function} responseCtor Response constructor + * @param {Message|Object.} request Request message or plain object + * @param {rpc.ServiceMethodCallback} callback Service callback + * @returns {undefined} + */ +Service.prototype.rpcCall = function rpcCall(method, requestCtor, responseCtor, request, callback) { + + if (!request) + throw TypeError("request must be specified"); + + var self = this; + if (!callback) + return util.asPromise(rpcCall, self, method, requestCtor, responseCtor, request); + + if (!self.rpcImpl) { + setTimeout(function() { callback(Error("already ended")); }, 0); + return undefined; + } + + try { + return self.rpcImpl( + method, + requestCtor[self.requestDelimited ? "encodeDelimited" : "encode"](request).finish(), + function rpcCallback(err, response) { + + if (err) { + self.emit("error", err, method); + return callback(err); + } + + if (response === null) { + self.end(/* endedByRPC */ true); + return undefined; + } + + if (!(response instanceof responseCtor)) { + try { + response = responseCtor[self.responseDelimited ? "decodeDelimited" : "decode"](response); + } catch (err) { + self.emit("error", err, method); + return callback(err); + } + } + + self.emit("data", response, method); + return callback(null, response); + } + ); + } catch (err) { + self.emit("error", err, method); + setTimeout(function() { callback(err); }, 0); + return undefined; + } +}; + +/** + * Ends this service and emits the `end` event. + * @param {boolean} [endedByRPC=false] Whether the service has been ended by the RPC implementation. + * @returns {rpc.Service} `this` + */ +Service.prototype.end = function end(endedByRPC) { + if (this.rpcImpl) { + if (!endedByRPC) // signal end to rpcImpl + this.rpcImpl(null, null, null); + this.rpcImpl = null; + this.emit("end").off(); + } + return this; +}; + +},{"13":13}],12:[function(require,module,exports){ +"use strict"; +module.exports = LongBits; + +var util = require(13); + +/** + * Any compatible Long instance. + * + * This is a minimal stand-alone definition of a Long instance. The actual type is that exported by long.js. + * @typedef Long + * @type {Object} + * @property {number} low Low bits + * @property {number} high High bits + * @property {boolean} unsigned Whether unsigned or not + */ + +/** + * Constructs new long bits. + * @classdesc Helper class for working with the low and high bits of a 64 bit value. + * @memberof util + * @constructor + * @param {number} lo Low 32 bits, unsigned + * @param {number} hi High 32 bits, unsigned + */ +function LongBits(lo, hi) { + + // note that the casts below are theoretically unnecessary as of today, but older statically + // generated converter code might still call the ctor with signed 32bits. kept for compat. + + /** + * Low bits. + * @type {number} + */ + this.lo = lo >>> 0; + + /** + * High bits. + * @type {number} + */ + this.hi = hi >>> 0; +} + +/** + * Zero bits. + * @memberof util.LongBits + * @type {util.LongBits} + */ +var zero = LongBits.zero = new LongBits(0, 0); + +zero.toNumber = function() { return 0; }; +zero.zzEncode = zero.zzDecode = function() { return this; }; +zero.length = function() { return 1; }; + +/** + * Zero hash. + * @memberof util.LongBits + * @type {string} + */ +var zeroHash = LongBits.zeroHash = "\0\0\0\0\0\0\0\0"; + +/** + * Constructs new long bits from the specified number. + * @param {number} value Value + * @returns {util.LongBits} Instance + */ +LongBits.fromNumber = function fromNumber(value) { + if (value === 0) + return zero; + var sign = value < 0; + if (sign) + value = -value; + var lo = value >>> 0, + hi = (value - lo) / 4294967296 >>> 0; + if (sign) { + hi = ~hi >>> 0; + lo = ~lo >>> 0; + if (++lo > 4294967295) { + lo = 0; + if (++hi > 4294967295) + hi = 0; + } + } + return new LongBits(lo, hi); +}; + +/** + * Constructs new long bits from a number, long or string. + * @param {Long|number|string} value Value + * @returns {util.LongBits} Instance + */ +LongBits.from = function from(value) { + if (typeof value === "number") + return LongBits.fromNumber(value); + if (util.isString(value)) { + /* istanbul ignore else */ + if (util.Long) + value = util.Long.fromString(value); + else + return LongBits.fromNumber(parseInt(value, 10)); + } + return value.low || value.high ? new LongBits(value.low >>> 0, value.high >>> 0) : zero; +}; + +/** + * Converts this long bits to a possibly unsafe JavaScript number. + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {number} Possibly unsafe number + */ +LongBits.prototype.toNumber = function toNumber(unsigned) { + if (!unsigned && this.hi >>> 31) { + var lo = ~this.lo + 1 >>> 0, + hi = ~this.hi >>> 0; + if (!lo) + hi = hi + 1 >>> 0; + return -(lo + hi * 4294967296); + } + return this.lo + this.hi * 4294967296; +}; + +/** + * Converts this long bits to a long. + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {Long} Long + */ +LongBits.prototype.toLong = function toLong(unsigned) { + return util.Long + ? new util.Long(this.lo | 0, this.hi | 0, Boolean(unsigned)) + /* istanbul ignore next */ + : { low: this.lo | 0, high: this.hi | 0, unsigned: Boolean(unsigned) }; +}; + +var charCodeAt = String.prototype.charCodeAt; + +/** + * Constructs new long bits from the specified 8 characters long hash. + * @param {string} hash Hash + * @returns {util.LongBits} Bits + */ +LongBits.fromHash = function fromHash(hash) { + if (hash === zeroHash) + return zero; + return new LongBits( + ( charCodeAt.call(hash, 0) + | charCodeAt.call(hash, 1) << 8 + | charCodeAt.call(hash, 2) << 16 + | charCodeAt.call(hash, 3) << 24) >>> 0 + , + ( charCodeAt.call(hash, 4) + | charCodeAt.call(hash, 5) << 8 + | charCodeAt.call(hash, 6) << 16 + | charCodeAt.call(hash, 7) << 24) >>> 0 + ); +}; + +/** + * Converts this long bits to a 8 characters long hash. + * @returns {string} Hash + */ +LongBits.prototype.toHash = function toHash() { + return String.fromCharCode( + this.lo & 255, + this.lo >>> 8 & 255, + this.lo >>> 16 & 255, + this.lo >>> 24 , + this.hi & 255, + this.hi >>> 8 & 255, + this.hi >>> 16 & 255, + this.hi >>> 24 + ); +}; + +/** + * Zig-zag encodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzEncode = function zzEncode() { + var mask = this.hi >> 31; + this.hi = ((this.hi << 1 | this.lo >>> 31) ^ mask) >>> 0; + this.lo = ( this.lo << 1 ^ mask) >>> 0; + return this; +}; + +/** + * Zig-zag decodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzDecode = function zzDecode() { + var mask = -(this.lo & 1); + this.lo = ((this.lo >>> 1 | this.hi << 31) ^ mask) >>> 0; + this.hi = ( this.hi >>> 1 ^ mask) >>> 0; + return this; +}; + +/** + * Calculates the length of this longbits when encoded as a varint. + * @returns {number} Length + */ +LongBits.prototype.length = function length() { + var part0 = this.lo, + part1 = (this.lo >>> 28 | this.hi << 4) >>> 0, + part2 = this.hi >>> 24; + return part2 === 0 + ? part1 === 0 + ? part0 < 16384 + ? part0 < 128 ? 1 : 2 + : part0 < 2097152 ? 3 : 4 + : part1 < 16384 + ? part1 < 128 ? 5 : 6 + : part1 < 2097152 ? 7 : 8 + : part2 < 128 ? 9 : 10; +}; + +},{"13":13}],13:[function(require,module,exports){ +"use strict"; +var util = exports; + +// used to return a Promise where callback is omitted +util.asPromise = require(1); + +// converts to / from base64 encoded strings +util.base64 = require(2); + +// base class of rpc.Service +util.EventEmitter = require(3); + +// requires modules optionally and hides the call from bundlers +util.inquire = require(4); + +// converts to / from utf8 encoded strings +util.utf8 = require(6); + +// provides a node-like buffer pool in the browser +util.pool = require(5); + +// utility to work with the low and high bits of a 64 bit value +util.LongBits = require(12); + +/** + * An immuable empty array. + * @memberof util + * @type {Array.<*>} + */ +util.emptyArray = Object.freeze ? Object.freeze([]) : /* istanbul ignore next */ []; // used on prototypes + +/** + * An immutable empty object. + * @type {Object} + */ +util.emptyObject = Object.freeze ? Object.freeze({}) : /* istanbul ignore next */ {}; // used on prototypes + +/** + * Whether running within node or not. + * @memberof util + * @type {boolean} + */ +util.isNode = Boolean(global.process && global.process.versions && global.process.versions.node); + +/** + * Tests if the specified value is an integer. + * @function + * @param {*} value Value to test + * @returns {boolean} `true` if the value is an integer + */ +util.isInteger = Number.isInteger || /* istanbul ignore next */ function isInteger(value) { + return typeof value === "number" && isFinite(value) && Math.floor(value) === value; +}; + +/** + * Tests if the specified value is a string. + * @param {*} value Value to test + * @returns {boolean} `true` if the value is a string + */ +util.isString = function isString(value) { + return typeof value === "string" || value instanceof String; +}; + +/** + * Tests if the specified value is a non-null object. + * @param {*} value Value to test + * @returns {boolean} `true` if the value is a non-null object + */ +util.isObject = function isObject(value) { + return value && typeof value === "object"; +}; + +/** + * Node's Buffer class if available. + * @type {?function(new: Buffer)} + */ +util.Buffer = (function() { + try { + var Buffer = util.inquire("buffer").Buffer; + // refuse to use non-node buffers if not explicitly assigned (perf reasons): + return Buffer.prototype.utf8Write ? Buffer : /* istanbul ignore next */ null; + } catch (e) { + /* istanbul ignore next */ + return null; + } +})(); + +/** + * Internal alias of or polyfull for Buffer.from. + * @type {?function} + * @param {string|number[]} value Value + * @param {string} [encoding] Encoding if value is a string + * @returns {Uint8Array} + * @private + */ +util._Buffer_from = null; + +/** + * Internal alias of or polyfill for Buffer.allocUnsafe. + * @type {?function} + * @param {number} size Buffer size + * @returns {Uint8Array} + * @private + */ +util._Buffer_allocUnsafe = null; + +/** + * Creates a new buffer of whatever type supported by the environment. + * @param {number|number[]} [sizeOrArray=0] Buffer size or number array + * @returns {Uint8Array|Buffer} Buffer + */ +util.newBuffer = function newBuffer(sizeOrArray) { + /* istanbul ignore next */ + return typeof sizeOrArray === "number" + ? util.Buffer + ? util._Buffer_allocUnsafe(sizeOrArray) + : new util.Array(sizeOrArray) + : util.Buffer + ? util._Buffer_from(sizeOrArray) + : typeof Uint8Array === "undefined" + ? sizeOrArray + : new Uint8Array(sizeOrArray); +}; + +/** + * Array implementation used in the browser. `Uint8Array` if supported, otherwise `Array`. + * @type {?function(new: Uint8Array, *)} + */ +util.Array = typeof Uint8Array !== "undefined" ? Uint8Array /* istanbul ignore next */ : Array; + +/** + * Long.js's Long class if available. + * @type {?function(new: Long)} + */ +util.Long = /* istanbul ignore next */ global.dcodeIO && /* istanbul ignore next */ global.dcodeIO.Long || util.inquire("long"); + +/** + * Regular expression used to verify 2 bit (`bool`) map keys. + * @type {RegExp} + */ +util.key2Re = /^true|false|0|1$/; + +/** + * Regular expression used to verify 32 bit (`int32` etc.) map keys. + * @type {RegExp} + */ +util.key32Re = /^-?(?:0|[1-9][0-9]*)$/; + +/** + * Regular expression used to verify 64 bit (`int64` etc.) map keys. + * @type {RegExp} + */ +util.key64Re = /^(?:[\\x00-\\xff]{8}|-?(?:0|[1-9][0-9]*))$/; + +/** + * Converts a number or long to an 8 characters long hash string. + * @param {Long|number} value Value to convert + * @returns {string} Hash + */ +util.longToHash = function longToHash(value) { + return value + ? util.LongBits.from(value).toHash() + : util.LongBits.zeroHash; +}; + +/** + * Converts an 8 characters long hash string to a long or number. + * @param {string} hash Hash + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {Long|number} Original value + */ +util.longFromHash = function longFromHash(hash, unsigned) { + var bits = util.LongBits.fromHash(hash); + if (util.Long) + return util.Long.fromBits(bits.lo, bits.hi, unsigned); + return bits.toNumber(Boolean(unsigned)); +}; + +/** + * Merges the properties of the source object into the destination object. + * @memberof util + * @param {Object.} dst Destination object + * @param {Object.} src Source object + * @param {boolean} [ifNotSet=false] Merges only if the key is not already set + * @returns {Object.} Destination object + */ +function merge(dst, src, ifNotSet) { // used by converters + for (var keys = Object.keys(src), i = 0; i < keys.length; ++i) + if (dst[keys[i]] === undefined || !ifNotSet) + dst[keys[i]] = src[keys[i]]; + return dst; +} + +util.merge = merge; + +/** + * Converts the first character of a string to lower case. + * @param {string} str String to convert + * @returns {string} Converted string + */ +util.lcFirst = function lcFirst(str) { + return str.charAt(0).toLowerCase() + str.substring(1); +}; + +/** + * Creates a custom error constructor. + * @memberof util + * @param {string} name Error name + * @returns {function} Custom error constructor + */ +function newError(name) { + + function CustomError(message, properties) { + + if (!(this instanceof CustomError)) + return new CustomError(message, properties); + + // Error.call(this, message); + // ^ just returns a new error instance because the ctor can be called as a function + + Object.defineProperty(this, "message", { get: function() { return message; } }); + + /* istanbul ignore next */ + if (Error.captureStackTrace) // node + Error.captureStackTrace(this, CustomError); + else + Object.defineProperty(this, "stack", { value: (new Error()).stack || "" }); + + if (properties) + merge(this, properties); + } + + (CustomError.prototype = Object.create(Error.prototype)).constructor = CustomError; + + Object.defineProperty(CustomError.prototype, "name", { get: function() { return name; } }); + + CustomError.prototype.toString = function toString() { + return this.name + ": " + this.message; + }; + + return CustomError; +} + +util.newError = newError; + +/** + * Constructs a new protocol error. + * @classdesc Error subclass indicating a protocol specifc error. + * @memberof util + * @extends Error + * @constructor + * @param {string} message Error message + * @param {Object.=} properties Additional properties + * @example + * try { + * MyMessage.decode(someBuffer); // throws if required fields are missing + * } catch (e) { + * if (e instanceof ProtocolError && e.instance) + * console.log("decoded so far: " + JSON.stringify(e.instance)); + * } + */ +util.ProtocolError = newError("ProtocolError"); + +/** + * So far decoded message instance. + * @name util.ProtocolError#instance + * @type {Message} + */ + +/** + * Builds a getter for a oneof's present field name. + * @param {string[]} fieldNames Field names + * @returns {function():string|undefined} Unbound getter + */ +util.oneOfGetter = function getOneOf(fieldNames) { + var fieldMap = {}; + for (var i = 0; i < fieldNames.length; ++i) + fieldMap[fieldNames[i]] = 1; + + /** + * @returns {string|undefined} Set field name, if any + * @this Object + * @ignore + */ + return function() { // eslint-disable-line consistent-return + for (var keys = Object.keys(this), i = keys.length - 1; i > -1; --i) + if (fieldMap[keys[i]] === 1 && this[keys[i]] !== undefined && this[keys[i]] !== null) + return keys[i]; + }; +}; + +/** + * Builds a setter for a oneof's present field name. + * @param {string[]} fieldNames Field names + * @returns {function(?string):undefined} Unbound setter + */ +util.oneOfSetter = function setOneOf(fieldNames) { + + /** + * @param {string} name Field name + * @returns {undefined} + * @this Object + * @ignore + */ + return function(name) { + for (var i = 0; i < fieldNames.length; ++i) + if (fieldNames[i] !== name) + delete this[fieldNames[i]]; + }; +}; + +/** + * Lazily resolves fully qualified type names against the specified root. + * @param {Root} root Root instanceof + * @param {Object.} lazyTypes Type names + * @returns {undefined} + */ +util.lazyResolve = function lazyResolve(root, lazyTypes) { + for (var i = 0; i < lazyTypes.length; ++i) { + for (var keys = Object.keys(lazyTypes[i]), j = 0; j < keys.length; ++j) { + var path = lazyTypes[i][keys[j]].split("."), + ptr = root; + while (path.length) + ptr = ptr[path.shift()]; + lazyTypes[i][keys[j]] = ptr; + } + } +}; + +/** + * Default conversion options used for {@link Message#toJSON} implementations. Longs, enums and bytes are converted to strings by default. + * @type {ConversionOptions} + */ +util.toJSONOptions = { + longs: String, + enums: String, + bytes: String +}; + +util._configure = function() { + var Buffer = util.Buffer; + /* istanbul ignore if */ + if (!Buffer) { + util._Buffer_from = util._Buffer_allocUnsafe = null; + return; + } + // because node 4.x buffers are incompatible & immutable + // see: https://github.com/dcodeIO/protobuf.js/pull/665 + util._Buffer_from = Buffer.from !== Uint8Array.from && Buffer.from || + /* istanbul ignore next */ + function Buffer_from(value, encoding) { + return new Buffer(value, encoding); + }; + util._Buffer_allocUnsafe = Buffer.allocUnsafe || + /* istanbul ignore next */ + function Buffer_allocUnsafe(size) { + return new Buffer(size); + }; +}; + +},{"1":1,"12":12,"2":2,"3":3,"4":4,"5":5,"6":6}],14:[function(require,module,exports){ +"use strict"; +module.exports = Writer; + +var util = require(13); + +var BufferWriter; // cyclic + +var LongBits = util.LongBits, + base64 = util.base64, + utf8 = util.utf8; + +/** + * Constructs a new writer operation instance. + * @classdesc Scheduled writer operation. + * @constructor + * @param {function(*, Uint8Array, number)} fn Function to call + * @param {number} len Value byte length + * @param {*} val Value to write + * @ignore + */ +function Op(fn, len, val) { + + /** + * Function to call. + * @type {function(Uint8Array, number, *)} + */ + this.fn = fn; + + /** + * Value byte length. + * @type {number} + */ + this.len = len; + + /** + * Next operation. + * @type {Writer.Op|undefined} + */ + this.next = undefined; + + /** + * Value to write. + * @type {*} + */ + this.val = val; // type varies +} + +/* istanbul ignore next */ +function noop() {} // eslint-disable-line no-empty-function + +/** + * Constructs a new writer state instance. + * @classdesc Copied writer state. + * @memberof Writer + * @constructor + * @param {Writer} writer Writer to copy state from + * @private + * @ignore + */ +function State(writer) { + + /** + * Current head. + * @type {Writer.Op} + */ + this.head = writer.head; + + /** + * Current tail. + * @type {Writer.Op} + */ + this.tail = writer.tail; + + /** + * Current buffer length. + * @type {number} + */ + this.len = writer.len; + + /** + * Next state. + * @type {?State} + */ + this.next = writer.states; +} + +/** + * Constructs a new writer instance. + * @classdesc Wire format writer using `Uint8Array` if available, otherwise `Array`. + * @constructor + */ +function Writer() { + + /** + * Current length. + * @type {number} + */ + this.len = 0; + + /** + * Operations head. + * @type {Object} + */ + this.head = new Op(noop, 0, 0); + + /** + * Operations tail + * @type {Object} + */ + this.tail = this.head; + + /** + * Linked forked states. + * @type {?Object} + */ + this.states = null; + + // When a value is written, the writer calculates its byte length and puts it into a linked + // list of operations to perform when finish() is called. This both allows us to allocate + // buffers of the exact required size and reduces the amount of work we have to do compared + // to first calculating over objects and then encoding over objects. In our case, the encoding + // part is just a linked list walk calling operations with already prepared values. +} + +/** + * Creates a new writer. + * @function + * @returns {BufferWriter|Writer} A {@link BufferWriter} when Buffers are supported, otherwise a {@link Writer} + */ +Writer.create = util.Buffer + ? function create_buffer_setup() { + return (Writer.create = function create_buffer() { + return new BufferWriter(); + })(); + } + /* istanbul ignore next */ + : function create_array() { + return new Writer(); + }; + +/** + * Allocates a buffer of the specified size. + * @param {number} size Buffer size + * @returns {Uint8Array} Buffer + */ +Writer.alloc = function alloc(size) { + return new util.Array(size); +}; + +// Use Uint8Array buffer pool in the browser, just like node does with buffers +/* istanbul ignore else */ +if (util.Array !== Array) + Writer.alloc = util.pool(Writer.alloc, util.Array.prototype.subarray); + +/** + * Pushes a new operation to the queue. + * @param {function(Uint8Array, number, *)} fn Function to call + * @param {number} len Value byte length + * @param {number} val Value to write + * @returns {Writer} `this` + */ +Writer.prototype.push = function push(fn, len, val) { + this.tail = this.tail.next = new Op(fn, len, val); + this.len += len; + return this; +}; + +function writeByte(val, buf, pos) { + buf[pos] = val & 255; +} + +function writeVarint32(val, buf, pos) { + while (val > 127) { + buf[pos++] = val & 127 | 128; + val >>>= 7; + } + buf[pos] = val; +} + +/** + * Constructs a new varint writer operation instance. + * @classdesc Scheduled varint writer operation. + * @extends Op + * @constructor + * @param {number} len Value byte length + * @param {number} val Value to write + * @ignore + */ +function VarintOp(len, val) { + this.len = len; + this.next = undefined; + this.val = val; +} + +VarintOp.prototype = Object.create(Op.prototype); +VarintOp.prototype.fn = writeVarint32; + +/** + * Writes an unsigned 32 bit value as a varint. + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.uint32 = function write_uint32(value) { + // here, the call to this.push has been inlined and a varint specific Op subclass is used. + // uint32 is by far the most frequently used operation and benefits significantly from this. + this.len += (this.tail = this.tail.next = new VarintOp( + (value = value >>> 0) + < 128 ? 1 + : value < 16384 ? 2 + : value < 2097152 ? 3 + : value < 268435456 ? 4 + : 5, + value)).len; + return this; +}; + +/** + * Writes a signed 32 bit value as a varint. + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.int32 = function write_int32(value) { + return value < 0 + ? this.push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec + : this.uint32(value); +}; + +/** + * Writes a 32 bit value as a varint, zig-zag encoded. + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.sint32 = function write_sint32(value) { + return this.uint32((value << 1 ^ value >> 31) >>> 0); +}; + +function writeVarint64(val, buf, pos) { + while (val.hi) { + buf[pos++] = val.lo & 127 | 128; + val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0; + val.hi >>>= 7; + } + while (val.lo > 127) { + buf[pos++] = val.lo & 127 | 128; + val.lo = val.lo >>> 7; + } + buf[pos++] = val.lo; +} + +/** + * Writes an unsigned 64 bit value as a varint. + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.uint64 = function write_uint64(value) { + var bits = LongBits.from(value); + return this.push(writeVarint64, bits.length(), bits); +}; + +/** + * Writes a signed 64 bit value as a varint. + * @function + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.int64 = Writer.prototype.uint64; + +/** + * Writes a signed 64 bit value as a varint, zig-zag encoded. + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.sint64 = function write_sint64(value) { + var bits = LongBits.from(value).zzEncode(); + return this.push(writeVarint64, bits.length(), bits); +}; + +/** + * Writes a boolish value as a varint. + * @param {boolean} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.bool = function write_bool(value) { + return this.push(writeByte, 1, value ? 1 : 0); +}; + +function writeFixed32(val, buf, pos) { + buf[pos++] = val & 255; + buf[pos++] = val >>> 8 & 255; + buf[pos++] = val >>> 16 & 255; + buf[pos ] = val >>> 24; +} + +/** + * Writes an unsigned 32 bit value as fixed 32 bits. + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.fixed32 = function write_fixed32(value) { + return this.push(writeFixed32, 4, value >>> 0); +}; + +/** + * Writes a signed 32 bit value as fixed 32 bits. + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.sfixed32 = Writer.prototype.fixed32; + +/** + * Writes an unsigned 64 bit value as fixed 64 bits. + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.fixed64 = function write_fixed64(value) { + var bits = LongBits.from(value); + return this.push(writeFixed32, 4, bits.lo).push(writeFixed32, 4, bits.hi); +}; + +/** + * Writes a signed 64 bit value as fixed 64 bits. + * @function + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.sfixed64 = Writer.prototype.fixed64; + +var writeFloat = typeof Float32Array !== "undefined" + ? (function() { + var f32 = new Float32Array(1), + f8b = new Uint8Array(f32.buffer); + f32[0] = -0; + return f8b[3] // already le? + ? function writeFloat_f32(val, buf, pos) { + f32[0] = val; + buf[pos++] = f8b[0]; + buf[pos++] = f8b[1]; + buf[pos++] = f8b[2]; + buf[pos ] = f8b[3]; + } + /* istanbul ignore next */ + : function writeFloat_f32_le(val, buf, pos) { + f32[0] = val; + buf[pos++] = f8b[3]; + buf[pos++] = f8b[2]; + buf[pos++] = f8b[1]; + buf[pos ] = f8b[0]; + }; + })() + /* istanbul ignore next */ + : function writeFloat_ieee754(value, buf, pos) { + var sign = value < 0 ? 1 : 0; + if (sign) + value = -value; + if (value === 0) + writeFixed32(1 / value > 0 ? /* positive */ 0 : /* negative 0 */ 2147483648, buf, pos); + else if (isNaN(value)) + writeFixed32(2147483647, buf, pos); + else if (value > 3.4028234663852886e+38) // +-Infinity + writeFixed32((sign << 31 | 2139095040) >>> 0, buf, pos); + else if (value < 1.1754943508222875e-38) // denormal + writeFixed32((sign << 31 | Math.round(value / 1.401298464324817e-45)) >>> 0, buf, pos); + else { + var exponent = Math.floor(Math.log(value) / Math.LN2), + mantissa = Math.round(value * Math.pow(2, -exponent) * 8388608) & 8388607; + writeFixed32((sign << 31 | exponent + 127 << 23 | mantissa) >>> 0, buf, pos); + } + }; + +/** + * Writes a float (32 bit). + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.float = function write_float(value) { + return this.push(writeFloat, 4, value); +}; + +var writeDouble = typeof Float64Array !== "undefined" + ? (function() { + var f64 = new Float64Array(1), + f8b = new Uint8Array(f64.buffer); + f64[0] = -0; + return f8b[7] // already le? + ? function writeDouble_f64(val, buf, pos) { + f64[0] = val; + buf[pos++] = f8b[0]; + buf[pos++] = f8b[1]; + buf[pos++] = f8b[2]; + buf[pos++] = f8b[3]; + buf[pos++] = f8b[4]; + buf[pos++] = f8b[5]; + buf[pos++] = f8b[6]; + buf[pos ] = f8b[7]; + } + /* istanbul ignore next */ + : function writeDouble_f64_le(val, buf, pos) { + f64[0] = val; + buf[pos++] = f8b[7]; + buf[pos++] = f8b[6]; + buf[pos++] = f8b[5]; + buf[pos++] = f8b[4]; + buf[pos++] = f8b[3]; + buf[pos++] = f8b[2]; + buf[pos++] = f8b[1]; + buf[pos ] = f8b[0]; + }; + })() + /* istanbul ignore next */ + : function writeDouble_ieee754(value, buf, pos) { + var sign = value < 0 ? 1 : 0; + if (sign) + value = -value; + if (value === 0) { + writeFixed32(0, buf, pos); + writeFixed32(1 / value > 0 ? /* positive */ 0 : /* negative 0 */ 2147483648, buf, pos + 4); + } else if (isNaN(value)) { + writeFixed32(4294967295, buf, pos); + writeFixed32(2147483647, buf, pos + 4); + } else if (value > 1.7976931348623157e+308) { // +-Infinity + writeFixed32(0, buf, pos); + writeFixed32((sign << 31 | 2146435072) >>> 0, buf, pos + 4); + } else { + var mantissa; + if (value < 2.2250738585072014e-308) { // denormal + mantissa = value / 5e-324; + writeFixed32(mantissa >>> 0, buf, pos); + writeFixed32((sign << 31 | mantissa / 4294967296) >>> 0, buf, pos + 4); + } else { + var exponent = Math.floor(Math.log(value) / Math.LN2); + if (exponent === 1024) + exponent = 1023; + mantissa = value * Math.pow(2, -exponent); + writeFixed32(mantissa * 4503599627370496 >>> 0, buf, pos); + writeFixed32((sign << 31 | exponent + 1023 << 20 | mantissa * 1048576 & 1048575) >>> 0, buf, pos + 4); + } + } + }; + +/** + * Writes a double (64 bit float). + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.double = function write_double(value) { + return this.push(writeDouble, 8, value); +}; + +var writeBytes = util.Array.prototype.set + ? function writeBytes_set(val, buf, pos) { + buf.set(val, pos); // also works for plain array values + } + /* istanbul ignore next */ + : function writeBytes_for(val, buf, pos) { + for (var i = 0; i < val.length; ++i) + buf[pos + i] = val[i]; + }; + +/** + * Writes a sequence of bytes. + * @param {Uint8Array|string} value Buffer or base64 encoded string to write + * @returns {Writer} `this` + */ +Writer.prototype.bytes = function write_bytes(value) { + var len = value.length >>> 0; + if (!len) + return this.push(writeByte, 1, 0); + if (util.isString(value)) { + var buf = Writer.alloc(len = base64.length(value)); + base64.decode(value, buf, 0); + value = buf; + } + return this.uint32(len).push(writeBytes, len, value); +}; + +/** + * Writes a string. + * @param {string} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.string = function write_string(value) { + var len = utf8.length(value); + return len + ? this.uint32(len).push(utf8.write, len, value) + : this.push(writeByte, 1, 0); +}; + +/** + * Forks this writer's state by pushing it to a stack. + * Calling {@link Writer#reset|reset} or {@link Writer#ldelim|ldelim} resets the writer to the previous state. + * @returns {Writer} `this` + */ +Writer.prototype.fork = function fork() { + this.states = new State(this); + this.head = this.tail = new Op(noop, 0, 0); + this.len = 0; + return this; +}; + +/** + * Resets this instance to the last state. + * @returns {Writer} `this` + */ +Writer.prototype.reset = function reset() { + if (this.states) { + this.head = this.states.head; + this.tail = this.states.tail; + this.len = this.states.len; + this.states = this.states.next; + } else { + this.head = this.tail = new Op(noop, 0, 0); + this.len = 0; + } + return this; +}; + +/** + * Resets to the last state and appends the fork state's current write length as a varint followed by its operations. + * @returns {Writer} `this` + */ +Writer.prototype.ldelim = function ldelim() { + var head = this.head, + tail = this.tail, + len = this.len; + this.reset().uint32(len); + if (len) { + this.tail.next = head.next; // skip noop + this.tail = tail; + this.len += len; + } + return this; +}; + +/** + * Finishes the write operation. + * @returns {Uint8Array} Finished buffer + */ +Writer.prototype.finish = function finish() { + var head = this.head.next, // skip noop + buf = this.constructor.alloc(this.len), + pos = 0; + while (head) { + head.fn(head.val, buf, pos); + pos += head.len; + head = head.next; + } + // this.head = this.tail = null; + return buf; +}; + +Writer._configure = function(BufferWriter_) { + BufferWriter = BufferWriter_; +}; + +},{"13":13}],15:[function(require,module,exports){ +"use strict"; +module.exports = BufferWriter; + +// extends Writer +var Writer = require(14); +(BufferWriter.prototype = Object.create(Writer.prototype)).constructor = BufferWriter; + +var util = require(13); + +var Buffer = util.Buffer; + +/** + * Constructs a new buffer writer instance. + * @classdesc Wire format writer using node buffers. + * @extends Writer + * @constructor + */ +function BufferWriter() { + Writer.call(this); +} + +/** + * Allocates a buffer of the specified size. + * @param {number} size Buffer size + * @returns {Buffer} Buffer + */ +BufferWriter.alloc = function alloc_buffer(size) { + return (BufferWriter.alloc = util._Buffer_allocUnsafe)(size); +}; + +var writeBytesBuffer = Buffer && Buffer.prototype instanceof Uint8Array && Buffer.prototype.set.name === "set" + ? function writeBytesBuffer_set(val, buf, pos) { + buf.set(val, pos); // faster than copy (requires node >= 4 where Buffers extend Uint8Array and set is properly inherited) + // also works for plain array values + } + /* istanbul ignore next */ + : function writeBytesBuffer_copy(val, buf, pos) { + if (val.copy) // Buffer values + val.copy(buf, pos, 0, val.length); + else for (var i = 0; i < val.length;) // plain array values + buf[pos++] = val[i++]; + }; + +/** + * @override + */ +BufferWriter.prototype.bytes = function write_bytes_buffer(value) { + if (util.isString(value)) + value = util._Buffer_from(value, "base64"); + var len = value.length >>> 0; + this.uint32(len); + if (len) + this.push(writeBytesBuffer, len, value); + return this; +}; + +function writeStringBuffer(val, buf, pos) { + if (val.length < 40) // plain js is faster for short strings (probably due to redundant assertions) + util.utf8.write(val, buf, pos); + else + buf.utf8Write(val, pos); +} + +/** + * @override + */ +BufferWriter.prototype.string = function write_string_buffer(value) { + var len = Buffer.byteLength(value); + this.uint32(len); + if (len) + this.push(writeStringBuffer, len, value); + return this; +}; + + +/** + * Finishes the write operation. + * @name BufferWriter#finish + * @function + * @returns {Buffer} Finished buffer + */ + +},{"13":13,"14":14}]},{},[7]) + +})(typeof window==="object"&&window||typeof self==="object"&&self||this); +//# sourceMappingURL=protobuf.js.map diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js new file mode 100644 index 000000000000..5a927c4636be --- /dev/null +++ b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js @@ -0,0 +1,485 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/GoogleEarthEnterpriseImageryProvider', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/defined', + 'Core/loadImage', + 'Core/loadJsonp', + 'Core/loadWithXhr', + 'Core/Math', + 'Core/TerrainProvider', + 'Core/WebMercatorTilingScheme', + 'Scene/BingMapsStyle', + 'Scene/DiscardMissingTileImagePolicy', + 'Scene/Imagery', + 'Scene/ImageryLayer', + 'Scene/ImageryProvider', + 'ThirdParty/when' + ], function( + GoogleEarthEnterpriseImageryProvider, + DefaultProxy, + defaultValue, + defined, + loadImage, + loadJsonp, + loadWithXhr, + CesiumMath, + TerrainProvider, + WebMercatorTilingScheme, + BingMapsStyle, + DiscardMissingTileImagePolicy, + Imagery, + ImageryLayer, + ImageryProvider, + when) { + 'use strict'; + + // afterEach(function() { + // loadJsonp.loadAndExecuteScript = loadJsonp.defaultLoadAndExecuteScript; + // loadImage.createImage = loadImage.defaultCreateImage; + // loadWithXhr.load = loadWithXhr.defaultLoad; + // }); + + it('tileXYToQuadKey', function() { + // http://msdn.microsoft.com/en-us/library/bb259689.aspx + // Levels are off by one compared to the documentation because our levels + // start at 0 while Bing's start at 1. + expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(1, 0, 0)).toEqual('2'); + expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(1, 2, 1)).toEqual('02'); + expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(3, 5, 2)).toEqual('021'); + expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(4, 7, 2)).toEqual('100'); + }); + + it('quadKeyToTileXY', function() { + expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('2')).toEqual({ + x : 1, + y : 0, + level : 0 + }); + expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('02')).toEqual({ + x : 1, + y : 2, + level : 1 + }); + expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('021')).toEqual({ + x : 3, + y : 5, + level : 2 + }); + expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('100')).toEqual({ + x : 4, + y : 7, + level : 2 + }); + }); + + it('decode', function() { + CesiumMath.setRandomNumberSeed(123123); + var data = new Uint8Array(1025); + for (var i = 0; i < 1025; ++i) { + data[i] = Math.floor(CesiumMath.nextRandomNumber() * 256); + } + + var buffer = data.buffer.slice(); + var a = new Uint8Array(buffer); + GoogleEarthEnterpriseImageryProvider._decode(buffer); + expect(a).not.toEqual(data); + + // For the algorithm encode/decode are the same + GoogleEarthEnterpriseImageryProvider._decode(buffer); + expect(a).toEqual(data); + }); + + it('request Image populates the correct metadata', function() { + var quad = '0123'; + var index = 0; + var provider; + spyOn(GoogleEarthEnterpriseImageryProvider.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + quadKey = defaultValue(quadKey, '') + index.toString(); + this._tileInfo[quadKey] = { + bits : 0xFF, + cnodeVersion : 1, + imageryVersion : 1, + terrainVersion : 1 + }; + index = (index + 1) % 4; + + return when(); + }); + + var count = 0; + var requestQuads = [GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(0, 0, 1), quad]; + var requestType = ['blob', 'arraybuffer']; + spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { + expect(url).toEqual('http://test.server/3d/flatfile?f1-0' + requestQuads[count] + '-i.1'); + expect(responseType).toEqual(requestType[count]); + ++count; + deferred.resolve(); + }); + + provider = new GoogleEarthEnterpriseImageryProvider({ + url: 'http://test.server/3d' + }); + + var tileXY = GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY(quad); + return provider.requestImage(tileXY.x, tileXY.y, tileXY.level) + .then(function(image) { + expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket.calls.count()).toEqual(4); + expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith(); + expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('0', 1); + expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('01', 1); + expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('012', 1); + + var tileInfo = provider._tileInfo; + expect(tileInfo['0']).toBeDefined(); + expect(tileInfo['01']).toBeDefined(); + expect(tileInfo['012']).toBeDefined(); + expect(tileInfo['0123']).toBeDefined(); + }); + }); + + it('conforms to ImageryProvider interface', function() { + expect(GoogleEarthEnterpriseImageryProvider).toConformToInterface(ImageryProvider); + }); + + it('conforms to TerrainProvider interface', function() { + expect(GoogleEarthEnterpriseImageryProvider).toConformToInterface(TerrainProvider); + }); + + // it('constructor throws when url is not specified', function() { + // function constructWithoutServer() { + // return new BingMapsImageryProvider({ + // mapStyle : BingMapsStyle.AERIAL + // }); + // } + // expect(constructWithoutServer).toThrowDeveloperError(); + // }); + // + // function createFakeMetadataResponse(mapStyle) { + // var stylePrefix = 'a'; + // switch (mapStyle) { + // case BingMapsStyle.AERIAL_WITH_LABELS: + // stylePrefix = 'h'; + // break; + // case BingMapsStyle.ROAD: + // stylePrefix = 'r'; + // break; + // } + // + // return { + // "authenticationResultCode" : "ValidCredentials", + // "brandLogoUri" : "http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png", + // "copyright" : "Copyright © 2014 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.", + // "resourceSets" : [{ + // "estimatedTotal" : 1, + // "resources" : [{ + // "__type" : "ImageryMetadata:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1", + // "imageHeight" : 256, + // "imageUrl" : "http:\/\/ecn.{subdomain}.tiles.virtualearth.net.fake.invalid\/tiles\/" + stylePrefix + "{quadkey}.jpeg?g=3031&mkt={culture}", + // "imageUrlSubdomains" : ["t0", "t1", "t2", "t3"], + // "imageWidth" : 256, + // "imageryProviders" : [{ + // "attribution" : "© 2014 DigitalGlobe", + // "coverageAreas" : [{ + // "bbox" : [-67, -179.99, 27, 0], + // "zoomMax" : 21, + // "zoomMin" : 14 + // }, { + // "bbox" : [27, -179.99, 87, -126.5], + // "zoomMax" : 21, + // "zoomMin" : 14 + // }, { + // "bbox" : [48.4, -126.5, 87, -5.75], + // "zoomMax" : 21, + // "zoomMin" : 14 + // }] + // }, { + // "attribution" : "Image courtesy of NASA", + // "coverageAreas" : [{ + // "bbox" : [-90, -180, 90, 180], + // "zoomMax" : 8, + // "zoomMin" : 1 + // }] + // }], + // "vintageEnd" : null, + // "vintageStart" : null, + // "zoomMax" : 21, + // "zoomMin" : 1 + // }] + // }], + // "statusCode" : 200, + // "statusDescription" : "OK", + // "traceId" : "ea754a48ccdb4dd297c8f35350e0f0d9|BN20130533|02.00.106.1600|" + // }; + // } + // + // function installFakeMetadataRequest(url, mapStyle, proxy) { + // var expectedUrl = url + '/REST/v1/Imagery/Metadata/' + mapStyle + '?incl=ImageryProviders&key='; + // if (defined(proxy)) { + // expectedUrl = proxy.getURL(expectedUrl); + // } + // + // loadJsonp.loadAndExecuteScript = function(url, functionName) { + // expect(url).toStartWith(expectedUrl); + // + // setTimeout(function() { + // window[functionName](createFakeMetadataResponse(mapStyle)); + // }, 1); + // }; + // } + // + // function installFakeImageRequest(expectedUrl) { + // loadImage.createImage = function(url, crossOrigin, deferred) { + // if (/^blob:/.test(url)) { + // // load blob url normally + // loadImage.defaultCreateImage(url, crossOrigin, deferred); + // } else { + // if (defined(expectedUrl)) { + // expect(url).toEqual(expectedUrl); + // } + // // Just return any old image. + // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + // } + // }; + // + // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + // if (defined(expectedUrl)) { + // expect(url).toEqual(expectedUrl); + // } + // + // // Just return any old image. + // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); + // }; + // } + // + // it('resolves readyPromise', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // return provider.readyPromise.then(function(result) { + // expect(result).toBe(true); + // expect(provider.ready).toBe(true); + // }); + // }); + // + // it('rejects readyPromise on error', function() { + // var url = 'host.invalid'; + // var provider = new BingMapsImageryProvider({ + // url : url + // }); + // + // return provider.readyPromise.then(function () { + // fail('should not resolve'); + // }).otherwise(function (e) { + // expect(provider.ready).toBe(false); + // expect(e.message).toContain(url); + // }); + // }); + // + // it('returns valid value for hasAlphaChannel', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.AERIAL; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // expect(typeof provider.hasAlphaChannel).toBe('boolean'); + // }); + // }); + // + // it('can provide a root tile', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // expect(provider.url).toEqual(url); + // expect(provider.key).toBeDefined(); + // expect(provider.mapStyle).toEqual(mapStyle); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // expect(provider.tileWidth).toEqual(256); + // expect(provider.tileHeight).toEqual(256); + // expect(provider.maximumLevel).toEqual(20); + // expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); + // expect(provider.tileDiscardPolicy).toBeInstanceOf(DiscardMissingTileImagePolicy); + // expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle); + // expect(provider.credit).toBeInstanceOf(Object); + // + // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt='); + // + // return provider.requestImage(0, 0, 0).then(function(image) { + // expect(image).toBeInstanceOf(Image); + // }); + // }); + // }); + // + // it('sets correct culture in tile requests', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.AERIAL_WITH_LABELS; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var culture = 'ja-jp'; + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle, + // culture : culture + // }); + // + // expect(provider.culture).toEqual(culture); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/h0.jpeg?g=3031&mkt=ja-jp'); + // + // return provider.requestImage(0, 0, 0).then(function(image) { + // expect(image).toBeInstanceOf(Image); + // }); + // }); + // }); + // + // it('routes requests through a proxy if one is specified', function() { + // var url = 'http://foo.bar.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // var proxy = new DefaultProxy('/proxy/'); + // + // installFakeMetadataRequest(url, mapStyle, proxy); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle, + // proxy : proxy + // }); + // + // expect(provider.url).toEqual(url); + // expect(provider.proxy).toEqual(proxy); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // installFakeImageRequest(proxy.getURL('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt=')); + // + // return provider.requestImage(0, 0, 0).then(function(image) { + // expect(image).toBeInstanceOf(Image); + // }); + // }); + // }); + // + // it('raises error on invalid url', function() { + // var url = 'host.invalid'; + // var provider = new BingMapsImageryProvider({ + // url : url + // }); + // + // var errorEventRaised = false; + // provider.errorEvent.addEventListener(function(error) { + // expect(error.message).toContain(url); + // errorEventRaised = true; + // }); + // + // return pollToPromise(function() { + // return provider.ready || errorEventRaised; + // }).then(function() { + // expect(provider.ready).toEqual(false); + // expect(errorEventRaised).toEqual(true); + // }); + // }); + // + // it('raises error event when image cannot be loaded', function() { + // var url = 'http://foo.bar.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // var layer = new ImageryLayer(provider); + // + // var tries = 0; + // provider.errorEvent.addEventListener(function(error) { + // expect(error.timesRetried).toEqual(tries); + // ++tries; + // if (tries < 3) { + // error.retry = true; + // } + // }); + // + // loadImage.createImage = function(url, crossOrigin, deferred) { + // if (/^blob:/.test(url)) { + // // load blob url normally + // loadImage.defaultCreateImage(url, crossOrigin, deferred); + // } else if (tries === 2) { + // // Succeed after 2 tries + // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + // } else { + // // fail + // setTimeout(function() { + // deferred.reject(); + // }, 1); + // } + // }; + // + // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + // if (tries === 2) { + // // Succeed after 2 tries + // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); + // } else { + // // fail + // setTimeout(function() { + // deferred.reject(); + // }, 1); + // } + // }; + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // var imagery = new Imagery(layer, 0, 0, 0); + // imagery.addReference(); + // layer._requestImagery(imagery); + // + // return pollToPromise(function() { + // return imagery.state === ImageryState.RECEIVED; + // }).then(function() { + // expect(imagery.image).toBeInstanceOf(Image); + // expect(tries).toEqual(2); + // imagery.releaseReference(); + // }); + // }); + // }); +}); From 99839b7169198ab643a5f110960df730eac375f9 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 27 Mar 2017 16:18:12 -0400 Subject: [PATCH 02/60] Google Earth Enterprise. Initial submit of terrain. --- .../GoogleEarthEnterpriseProvider.js} | 455 +++++++++++------- .../Core/GoogleEarthEnterpriseTerrainData.js | 420 ++++++++++++++++ Source/Scene/TileTerrain.js | 4 + ...VerticesFromGoogleEarthEnterpriseBuffer.js | 236 +++++++++ .../GoogleEarthEnterpriseProviderSpec.js} | 57 +-- 5 files changed, 954 insertions(+), 218 deletions(-) rename Source/{Scene/GoogleEarthEnterpriseImageryProvider.js => Core/GoogleEarthEnterpriseProvider.js} (61%) create mode 100644 Source/Core/GoogleEarthEnterpriseTerrainData.js create mode 100644 Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js rename Specs/{Scene/GoogleEarthEnterpriseImageryProviderSpec.js => Core/GoogleEarthEnterpriseProviderSpec.js} (87%) diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Core/GoogleEarthEnterpriseProvider.js similarity index 61% rename from Source/Scene/GoogleEarthEnterpriseImageryProvider.js rename to Source/Core/GoogleEarthEnterpriseProvider.js index c77e9db78304..1811e4f9abd6 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Core/GoogleEarthEnterpriseProvider.js @@ -1,34 +1,46 @@ /*global define*/ define([ - '../Core/appendForwardSlash', - '../Core/Cartesian2', - '../Core/Credit', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/loadArrayBuffer', - '../Core/loadImageFromTypedArray', - '../Core/Math', - '../Core/Rectangle', - '../Core/RuntimeError', - '../Core/TerrainProvider', - '../Core/TileProviderError', - '../Core/GeographicTilingScheme', + './appendForwardSlash', + './Cartesian2', + './Cartesian3', + './Cartographic', + './Credit', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './Ellipsoid', + './Event', + './GeographicTilingScheme', + './GoogleEarthEnterpriseTerrainData', + './HeightmapTerrainData', + './loadArrayBuffer', + './loadImageFromTypedArray', + './Math', + './Rectangle', + './RuntimeError', + './TerrainProvider', + './TileProviderError', + './throttleRequestByServer', + '../Scene/DiscardMissingTileImagePolicy', '../ThirdParty/pako_inflate', '../ThirdParty/protobuf-minimal', - '../ThirdParty/when', - './DiscardMissingTileImagePolicy' + '../ThirdParty/when' ], function( appendForwardSlash, Cartesian2, + Cartesian3, + Cartographic, Credit, defaultValue, defined, defineProperties, DeveloperError, + Ellipsoid, Event, + GeographicTilingScheme, + GoogleEarthEnterpriseTerrainData, + HeightmapTerrainData, loadArrayBuffer, loadImageFromTypedArray, CesiumMath, @@ -36,24 +48,30 @@ define([ RuntimeError, TerrainProvider, TileProviderError, - GeographicTilingScheme, + throttleRequestByServer, + DiscardMissingTileImagePolicy, pako, protobuf, - when, - DiscardMissingTileImagePolicy) { + when) { 'use strict'; // Bitmask for checking tile properties var childrenBitmasks = [0x01, 0x02, 0x04, 0x08]; + var anyChildBitmask = 0x0F; var cacheFlagBitmask = 0x10; // True if there is a child subtree // var vectorDataBitmask = 0x20; var imageBitmask = 0x40; var terrainBitmask = 0x80; + // Datatype sizes + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + /** * Provides tiled imagery using the Google Earth Enterprise Imagery REST API. * - * @alias GoogleEarthEnterpriseImageryProvider + * @alias GoogleEarthEnterpriseProvider * @constructor * * @param {Object} options Object with the following properties: @@ -85,13 +103,13 @@ define([ * * * @example - * var gee = new Cesium.GoogleEarthEnterpriseImageryProvider({ + * var gee = new Cesium.GoogleEarthEnterpriseProvider({ * url : 'https://dev.virtualearth.net' * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} */ - function GoogleEarthEnterpriseImageryProvider(options) { + function GoogleEarthEnterpriseProvider(options) { options = defaultValue(options, {}); //>>includeStart('debug', pragmas.debug); @@ -104,7 +122,7 @@ define([ this._tileProtocol = options.tileProtocol; this._tileDiscardPolicy = options.tileDiscardPolicy; this._proxy = options.proxy; - this._credit = new Credit('Google Imagery', GoogleEarthEnterpriseImageryProvider._logoData, 'http://www.google.com'); + this._credit = new Credit('Google Imagery', GoogleEarthEnterpriseProvider._logoData, 'http://www.google.com'); this._tilingScheme = new GeographicTilingScheme({ numberOfLevelZeroTilesX : 2, @@ -122,13 +140,14 @@ define([ if (!defined(this._tileDiscardPolicy)) { this._tileDiscardPolicy = new DiscardMissingTileImagePolicy({ // TODO - missing image url - missingImageUrl : buildImageUrl(this, GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(0, 0, 1)), + missingImageUrl : buildImageUrl(this, GoogleEarthEnterpriseProvider.tileXYToQuadKey(0, 0, 1)), pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(120, 140), new Cartesian2(130, 160), new Cartesian2(200, 50), new Cartesian2(200, 200)], disableCheckIfAllPixelsAreTransparent : true }); } this._tileInfo = {}; + this._terrainCache = {}; this._subtreePromises = {}; this._errorEvent = new Event(); @@ -141,10 +160,10 @@ define([ }); } - defineProperties(GoogleEarthEnterpriseImageryProvider.prototype, { + defineProperties(GoogleEarthEnterpriseProvider.prototype, { /** * Gets the name of the Google Earth Enterprise server url hosting the imagery. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {String} * @readonly */ @@ -156,7 +175,7 @@ define([ /** * Gets the proxy used by this provider. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Proxy} * @readonly */ @@ -168,8 +187,8 @@ define([ /** * Gets the width of each tile, in pixels. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Number} * @readonly */ @@ -187,8 +206,8 @@ define([ /** * Gets the height of each tile, in pixels. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Number} * @readonly */ @@ -207,8 +226,8 @@ define([ /** * Gets the maximum level-of-detail that can be requested. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Number} * @readonly */ @@ -226,8 +245,8 @@ define([ /** * Gets the minimum level-of-detail that can be requested. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Number} * @readonly */ @@ -245,8 +264,8 @@ define([ /** * Gets the tiling scheme used by this provider. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {TilingScheme} * @readonly */ @@ -264,8 +283,8 @@ define([ /** * Gets the rectangle, in radians, of the imagery provided by this instance. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Rectangle} * @readonly */ @@ -285,8 +304,8 @@ define([ * Gets the tile discard policy. If not undefined, the discard policy is responsible * for filtering out "missing" tiles via its shouldDiscardImage function. If this function * returns undefined, no tiles are filtered. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {TileDiscardPolicy} * @readonly */ @@ -306,7 +325,7 @@ define([ * Gets an event that is raised when the imagery 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 GoogleEarthEnterpriseImageryProvider.prototype + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Event} * @readonly */ @@ -318,7 +337,7 @@ define([ /** * Gets a value indicating whether or not the provider is ready for use. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Boolean} * @readonly */ @@ -330,7 +349,7 @@ define([ /** * Gets a promise that resolves to true when the provider is ready for use. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Promise.} * @readonly */ @@ -342,8 +361,8 @@ define([ /** * Gets the credit to display when this imagery provider is active. Typically this is used to credit - * the source of the imagery. This function should not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * the source of the imagery. This function should not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Credit} * @readonly */ @@ -359,7 +378,7 @@ define([ * be ignored. If this property is true, any images without an alpha channel will be treated * as if their alpha is 1.0 everywhere. Setting this property to false reduces memory usage * and texture upload time. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Boolean} * @readonly */ @@ -373,8 +392,8 @@ define([ * 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 GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Boolean} */ hasWaterMask : { @@ -385,8 +404,8 @@ define([ /** * Gets a value indicating whether or not the requested tiles include vertex normals. - * This function should not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * This function should not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Boolean} */ hasVertexNormals : { @@ -398,9 +417,9 @@ define([ /** * 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 GoogleEarthEnterpriseImageryProvider#ready} returns true. This property may be undefined if availability + * {@link GoogleEarthEnterpriseProvider#ready} returns true. This property may be undefined if availability * information is not available. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @memberof GoogleEarthEnterpriseProvider.prototype * @type {TileAvailability} */ availability : { @@ -420,7 +439,7 @@ define([ * * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. */ - GoogleEarthEnterpriseImageryProvider.prototype.getTileCredits = function(x, y, level) { + GoogleEarthEnterpriseProvider.prototype.getTileCredits = function(x, y, level) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.'); @@ -432,7 +451,7 @@ define([ /** * Requests the image for a given tile. This function should - * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. * * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. @@ -444,7 +463,7 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - GoogleEarthEnterpriseImageryProvider.prototype.requestImage = function(x, y, level) { + GoogleEarthEnterpriseProvider.prototype.requestImage = function(x, y, level) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); @@ -453,7 +472,7 @@ define([ var that = this; var tileInfo = this._tileInfo; - var quadKey = GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(x, y, level); + var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); return populateSubtree(this, quadKey) .then(function(exists){ if (exists) { @@ -463,7 +482,7 @@ define([ return loadArrayBuffer(url) .then(function(image) { if (defined(image)) { - GoogleEarthEnterpriseImageryProvider._decode(image); + GoogleEarthEnterpriseProvider._decode(image); var a = new Uint8Array(image); var type = getImageType(a); if (!defined(type)) { @@ -501,11 +520,11 @@ define([ * instances. The array may be empty if no features are found at the given location. * It may also be undefined if picking is not supported. */ - GoogleEarthEnterpriseImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { + GoogleEarthEnterpriseProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { return undefined; }; - GoogleEarthEnterpriseImageryProvider._logoData = 'data:image/png;base64,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'; + GoogleEarthEnterpriseProvider._logoData = 'data:image/png;base64,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'; /** * Converts a tiles (x, y, level) position into a quadkey used to request an image @@ -515,9 +534,9 @@ define([ * @param {Number} y The tile's y coordinate. * @param {Number} level The tile's zoom level. * - * @see GoogleEarthEnterpriseImageryProvider#quadKeyToTileXY + * @see GoogleEarthEnterpriseProvider#quadKeyToTileXY */ - GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey = function(x, y, level) { + GoogleEarthEnterpriseProvider.tileXYToQuadKey = function(x, y, level) { var quadkey = ''; for ( var i = level; i >= 0; --i) { var bitmask = 1 << i; @@ -554,9 +573,9 @@ define([ * * @param {String} quadkey The tile's quad key * - * @see GoogleEarthEnterpriseImageryProvider#tileXYToQuadKey + * @see GoogleEarthEnterpriseProvider#tileXYToQuadKey */ - GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY = function(quadkey) { + GoogleEarthEnterpriseProvider.quadKeyToTileXY = function(quadkey) { var x = 0; var y = 0; var level = quadkey.length - 1; @@ -584,7 +603,7 @@ define([ /** * Requests the geometry for a given tile. This function should not be called before - * {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. The result must include terrain data and + * {@link GoogleEarthEnterpriseProvider#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. @@ -597,72 +616,121 @@ define([ * 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 GoogleEarthEnterpriseImageryProvider#ready} + * @exception {DeveloperError} This function must not be called before {@link GoogleEarthEnterpriseProvider#ready} * returns true. */ - GoogleEarthEnterpriseImageryProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { + GoogleEarthEnterpriseProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { //>>includeStart('debug', pragmas.debug) if (!this._ready) { throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.'); } //>>includeEnd('debug'); - // var urlTemplates = this._tileUrlTemplates; - // if (urlTemplates.length === 0) { - // return undefined; - // } - // - // var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level); - // - // var tmsY = (yTiles - y - 1); - // - // var url = urlTemplates[(x + tmsY + level) % urlTemplates.length].replace('{z}', level).replace('{x}', x).replace('{y}', tmsY); - // - // var proxy = this._proxy; - // if (defined(proxy)) { - // url = proxy.getURL(url); - // } - // - // var promise; - // - // var extensionList = []; - // if (this._requestVertexNormals && this._hasVertexNormals) { - // extensionList.push(this._littleEndianExtensionSize ? 'octvertexnormals' : 'vertexnormals'); - // } - // if (this._requestWaterMask && this._hasWaterMask) { - // extensionList.push('watermask'); - // } - // - // function tileLoader(tileUrl) { - // return loadArrayBuffer(tileUrl, getRequestHeader(extensionList)); - // } - // throttleRequests = defaultValue(throttleRequests, true); - // if (throttleRequests) { - // promise = throttleRequestByServer(url, tileLoader); - // if (!defined(promise)) { - // return undefined; - // } - // } else { - // promise = tileLoader(url); - // } - // - // var that = this; - // return when(promise, function(buffer) { - // if (defined(that._heightmapStructure)) { - // return createHeightmapTerrainData(that, buffer, level, x, y, tmsY); - // } else { - // return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY); - // } - // }); + var that = this; + var tileInfo = this._tileInfo; + var terrainCache = this._terrainCache; + var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); + return populateSubtree(this, quadKey) + .then(function(exists){ + if (exists) { + var info = tileInfo[quadKey]; + if (defined(terrainCache[quadKey])) { + var buffer = terrainCache[quadKey]; + delete terrainCache[quadKey]; + return new GoogleEarthEnterpriseTerrainData({ + buffer: buffer, + childTileMask: info.bits & anyChildBitmask + }); + } + if ((info.bits & terrainBitmask) !== 0 || info.terrainInParent) { + var q = quadKey; + if (info.terrainInParent) { + // If terrain is in parent tile, process that instead + q = q.substring(0, q.length-1); + } + var url = buildTerrainUrl(that, q, info.terrainVersion); + var promise; + throttleRequests = defaultValue(throttleRequests, true); + if (throttleRequests) { + promise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(promise)) { + return undefined; + } + } else { + promise = loadArrayBuffer(url); + } + + return promise + .then(function(terrain) { + if (defined(terrain)) { + GoogleEarthEnterpriseProvider._decode(terrain); + var uncompressedTerrain = uncompressPacket(terrain); + parseTerrainPacket(that, uncompressedTerrain, q); + + var buffer = terrainCache[quadKey]; + delete terrainCache[quadKey]; + return new GoogleEarthEnterpriseTerrainData({ + buffer: buffer, + childTileMask: info.bits & anyChildBitmask + }); + } + }) + .otherwise(function(error) { + // Just ignore failures and return undefined + }); + } else if(!info.ancestorHasTerrain) { + // We haven't reached a level with terrain, so return the ellipsoid + return new HeightmapTerrainData({ + buffer : new Uint8Array(16 * 16), + width : 16, + height : 16 + }); + } + } + }); }; + function parseTerrainPacket(that, terrainData, quadKey) { + var tileInfo = that._tileInfo; + var info = tileInfo[quadKey]; + var terrainCache = that._terrainCache; + var buffer = terrainData.buffer; + + var dv = new DataView(buffer); + var totalSize = terrainData.length; + + var offset = 0; + var terrainTiles = []; + while(offset < totalSize) { + // Each tile is split into 4 parts + var tileStart = offset; + for (var quad = 0; quad < 4; ++quad) { + var size = dv.getUint32(offset, true); + offset += sizeOfUint32; + offset += size; + } + terrainTiles.push(buffer.slice(tileStart, offset)); + } + + // If we were sent child tiles, store them till they are needed + terrainCache[quadKey] = terrainTiles[0]; + var count = terrainTiles.length-1; + for (var j = 0;j>> 24) & 0x000000ff) | + ((size >>> 8) & 0x0000ff00) | + ((size << 8) & 0x00ff0000) | + ((size << 24) & 0xff000000); + size = v; + } + + var compressedPacket = new Uint8Array(data, offset); + var uncompressedPacket = pako.inflate(compressedPacket); + + if (uncompressedPacket.length !== size) { + throw new RuntimeError('Size of packet doesn\'t match header'); + } + + return uncompressedPacket; + } // Requests quadtree packet and populates _tileInfo with results - GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket = function(quadKey, version) { + GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket = function(quadKey) { quadKey = defaultValue(quadKey, ''); - version = Math.max(1, defaultValue(version, 1)); - var url = this._url + 'flatfile?q2-0' + quadKey + '-q.' + version.toString(); + var url = this._url + 'flatfile?q2-0' + quadKey + '-q.2'; var proxy = this._proxy; if (defined(proxy)) { url = proxy.getURL(url); @@ -764,77 +863,46 @@ define([ var that = this; return loadArrayBuffer(url) .then(function(metadata) { - GoogleEarthEnterpriseImageryProvider._decode(metadata); - - // The layout of this decoded data is - // Magic Uint32 - // Size Uint32 - // [GZipped chunk of Size bytes] + GoogleEarthEnterpriseProvider._decode(metadata); - // Pullout magic and verify we have the correct data - var dv = new DataView(metadata); + var uncompressedPacket = uncompressPacket(metadata); + var dv = new DataView(uncompressedPacket.buffer); var offset = 0; var magic = dv.getUint32(offset, true); - offset += Uint32Size; - if (magic !== compressedMagic && magic !== compressedMagicSwap) { - throw new RuntimeError('Invalid magic'); - } - - // Get the size of the compressed buffer - var size = dv.getUint32(offset, true); - offset += Uint32Size; - if (magic === compressedMagicSwap) { - var v = ((size >>> 24) & 0x000000ff) | - ((size >>> 8) & 0x0000ff00) | - ((size << 8) & 0x00ff0000) | - ((size << 24) & 0xff000000); - size = v; - } - - var compressedPacket = new Uint8Array(metadata, offset); - var uncompressedPacket = pako.inflate(compressedPacket); - - if (uncompressedPacket.length !== size) { - throw new RuntimeError('Size of packet doesn\'t match header'); - } - - dv = new DataView(uncompressedPacket.buffer); - offset = 0; - magic = dv.getUint32(offset, true); - offset += Uint32Size; + offset += sizeOfUint32; if (magic !== qtMagic) { throw new RuntimeError('Invalid magic'); } var dataTypeId = dv.getUint32(offset, true); - offset += Uint32Size; + offset += sizeOfUint32; if (dataTypeId !== 1) { throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); } var version = dv.getUint32(offset, true); - offset += Uint32Size; + offset += sizeOfUint32; if (version !== 2) { throw new RuntimeError('Invalid version. Only QuadTreePacket version 2 supported.'); } var numInstances = dv.getInt32(offset, true); - offset += Int32Size; + offset += sizeOfInt32; var dataInstanceSize = dv.getInt32(offset, true); - offset += Int32Size; + offset += sizeOfInt32; if (dataInstanceSize !== 32) { throw new RuntimeError('Invalid instance size.'); } var dataBufferOffset = dv.getInt32(offset, true); - offset += Int32Size; + offset += sizeOfInt32; var dataBufferSize = dv.getInt32(offset, true); - offset += Int32Size; + offset += sizeOfInt32; var metaBufferSize = dv.getInt32(offset, true); - offset += Int32Size; + offset += sizeOfInt32; // Offset from beginning of packet (instances + current offset) if (dataBufferOffset !== (numInstances * dataInstanceSize + offset)) { @@ -842,7 +910,7 @@ define([ } // Verify the packets is all there header + instances + dataBuffer + metaBuffer - if (dataBufferOffset + dataBufferSize + metaBufferSize !== size) { + if (dataBufferOffset + dataBufferSize + metaBufferSize !== uncompressedPacket.length) { throw new RuntimeError('Invalid packet offsets'); } @@ -854,41 +922,43 @@ define([ ++offset; // 2 byte align - var cnodeVersion = dv.getUint16(offset, true); - offset += Uint16Size; + // We only support version 2 which we verified above this tile already is + //var cnodeVersion = dv.getUint16(offset, true); + offset += sizeOfUint16; var imageVersion = dv.getUint16(offset, true); - offset += Uint16Size; + offset += sizeOfUint16; var terrainVersion = dv.getUint16(offset, true); - offset += Uint16Size; + offset += sizeOfUint16; // Number of channels stored in the dataBuffer //var numChannels = dv.getUint16(offset, true); - offset += Uint16Size; + offset += sizeOfUint16; - offset += Uint16Size; // 4 byte align + offset += sizeOfUint16; // 4 byte align // Channel type offset into dataBuffer //var typeOffset = dv.getInt32(offset, true); - offset += Int32Size; + offset += sizeOfInt32; // Channel version offset into dataBuffer //var versionOffset = dv.getInt32(offset, true); - offset += Int32Size; + offset += sizeOfInt32; offset += 8; // Ignore image neighbors for now // Data providers aren't used ++offset; // Image provider ++offset; // Terrain provider - offset += Uint16Size; // 4 byte align + offset += sizeOfUint16; // 4 byte align instances.push({ bits : bitfield, - cnodeVersion : cnodeVersion, imageryVersion : imageVersion, - terrainVersion : terrainVersion + terrainVersion : terrainVersion, + ancestorHasTerrain : false, // Set it later once we find its parent + terrainInParent : false }); } @@ -922,6 +992,7 @@ define([ } var instance = instances[index++]; + instance.ancestorHasTerrain = parent.ancestorHasTerrain || ((instance.bits & terrainBitmask) !== 0); tileInfo[childKey] = instance; populateTiles(childKey, instance, level + 1); } @@ -952,7 +1023,8 @@ define([ var tileInfo = that._tileInfo; var q = quadKey; var t = tileInfo[q]; - if (defined(t)) { + // If we have tileInfo make sure sure it is not a node with a subtree that's not loaded + if (defined(t) && ((t.bits & cacheFlagBitmask) === 0 || (t.bits & anyChildBitmask) !== 0)) { return when(true); } @@ -976,7 +1048,7 @@ define([ // We need to split up the promise here because when will execute syncronously if _getQuadTreePacket // is already resolved (like in the tests), so subtreePromises will never get cleared out. - promise = that._getQuadTreePacket(q, t.cnodeVersion); + promise = that._getQuadTreePacket(q); subtreePromises[q] = promise .then(function() { @@ -1070,13 +1142,13 @@ define([ message.imageType = 'image/png'; break; default: - throw new RuntimeError('GoogleEarthEnterpriseImageryProvider: Unsupported image type.'); + throw new RuntimeError('GoogleEarthEnterpriseProvider: Unsupported image type.'); } } var alphaType = message.alphaType; if (defined(alphaType) && alphaType !== 0) { - console.log('GoogleEarthEnterpriseImageryProvider: External alpha not supported.'); + console.log('GoogleEarthEnterpriseProvider: External alpha not supported.'); delete message.alphaType; delete message.imageAlpha; } @@ -1084,5 +1156,20 @@ define([ return message; } - return GoogleEarthEnterpriseImageryProvider; + // + // Functions to handle imagery packets + // + function buildTerrainUrl(terrainProvider, quadKey, version) { + version = (defined(version) && version > 0) ? version : 1; + var terrainUrl = terrainProvider._url + 'flatfile?f1c-0' + quadKey + '-t.' + version.toString(); + + var proxy = terrainProvider._proxy; + if (defined(proxy)) { + terrainUrl = proxy.getURL(terrainUrl); + } + + return terrainUrl; + } + + return GoogleEarthEnterpriseProvider; }); diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js new file mode 100644 index 000000000000..74590d1dfb74 --- /dev/null +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -0,0 +1,420 @@ +/*global define*/ +define([ + '../ThirdParty/when', + './Cartesian2', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './GeographicTilingScheme', + './HeightmapTessellator', + './IndexDatatype', + './Intersections2D', + './Math', + './Rectangle', + './TaskProcessor', + './TerrainEncoding', + './TerrainMesh', + './TerrainProvider' +], function( + when, + Cartesian2, + defaultValue, + defined, + defineProperties, + DeveloperError, + GeographicTilingScheme, + HeightmapTessellator, + IndexDatatype, + Intersections2D, + CesiumMath, + Rectangle, + TaskProcessor, + TerrainEncoding, + TerrainMesh, + TerrainProvider) { + 'use strict'; + + /** + * Terrain data for a single tile from a Google Earth Enterprise server. + * + * @alias GoogleEarthEnterpriseTerrainData + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {TypedArray} options.buffer The buffer containing height data. + * @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist. + * If a child's bit is set, geometry will be requested for that tile as well when it + * is needed. If the bit is cleared, the child tile is not requested and geometry is + * instead upsampled from the parent. The bit values are as follows: + * + * + * + * + * + * + *
Bit PositionBit ValueChild Tile
01Southwest
12Southeast
24Northeast
38Northwest
+ * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance; + * otherwise, false. + * + * + * @example + * var buffer = ... + * var childTileMask = ... + * var terrainData = new Cesium.GoogleEarthEnterpriseTerrainData({ + * buffer : heightBuffer, + * childTileMask : childTileMask + * }); + * + * @see TerrainData + * @see HeightTerrainData + * @see QuantizedMeshTerrainData + */ + function GoogleEarthEnterpriseTerrainData(options) { + //>>includeStart('debug', pragmas.debug); + if (!defined(options) || !defined(options.buffer)) { + throw new DeveloperError('options.buffer is required.'); + } + //>>includeEnd('debug'); + + this._buffer = options.buffer; + this._childTileMask = defaultValue(options.childTileMask, 15); + + this._createdByUpsampling = defaultValue(options.createdByUpsampling, false); + + this._skirtHeight = undefined; + this._bufferType = this._buffer.constructor; + this._mesh = undefined; + } + + defineProperties(GoogleEarthEnterpriseTerrainData.prototype, { + /** + * The water mask included in this terrain data, if any. A water mask is a rectangular + * Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land. + * Values in between 0 and 255 are allowed as well to smoothly blend between land and water. + * @memberof GoogleEarthEnterpriseTerrainData.prototype + * @type {Uint8Array|Image|Canvas} + */ + waterMask : { + get : function() { + return undefined; + } + } + }); + + + var taskProcessor = new TaskProcessor('createVerticesFromGoogleEarthEnterpriseBuffer'); + + /** + * Creates a {@link TerrainMesh} from this terrain data. + * + * @private + * + * @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs. + * @param {Number} x The X coordinate of the tile for which to create the terrain data. + * @param {Number} y The Y coordinate of the tile for which to create the terrain data. + * @param {Number} level The level of the tile for which to create the terrain data. + * @param {Number} [exaggeration=1.0] The scale used to exaggerate the terrain. + * @returns {Promise.|undefined} A promise for the terrain mesh, or undefined if too many + * asynchronous mesh creations are already in progress and the operation should + * be retried later. + */ + GoogleEarthEnterpriseTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) { + //>>includeStart('debug', pragmas.debug); + if (!defined(tilingScheme)) { + throw new DeveloperError('tilingScheme is required.'); + } + if (!defined(x)) { + throw new DeveloperError('x is required.'); + } + if (!defined(y)) { + throw new DeveloperError('y is required.'); + } + if (!defined(level)) { + throw new DeveloperError('level is required.'); + } + //>>includeEnd('debug'); + + var ellipsoid = tilingScheme.ellipsoid; + var nativeRectangle = tilingScheme.tileXYToNativeRectangle(x, y, level); + var rectangle = tilingScheme.tileXYToRectangle(x, y, level); + exaggeration = defaultValue(exaggeration, 1.0); + + // Compute the center of the tile for RTC rendering. + var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle)); + + // TODO + this._skirtHeight = 0; + // var levelZeroMaxError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, this._width, tilingScheme.getNumberOfXTilesAtLevel(0)); + // var thisLevelMaxError = levelZeroMaxError / (1 << level); + // this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0); + + var verticesPromise = taskProcessor.scheduleTask({ + buffer : this._buffer, + nativeRectangle : nativeRectangle, + rectangle : rectangle, + relativeToCenter : center, + ellipsoid : ellipsoid, + skirtHeight : this._skirtHeight, + exaggeration : exaggeration + }); + + if (!defined(verticesPromise)) { + // Postponed + return undefined; + } + + var that = this; + return when(verticesPromise, function(result) { + that._mesh = new TerrainMesh( + center, + new Float32Array(result.vertices), + new Uint16Array(result.indices), + result.minimumHeight, + result.maximumHeight, + result.boundingSphere3D, + result.occludeePointInScaledSpace, + result.numberOfAttributes, + result.orientedBoundingBox, + TerrainEncoding.clone(result.encoding), + exaggeration); + + // Free memory received from server after mesh is created. + that._buffer = undefined; + return that._mesh; + }); + }; + + var maxShort = 32767; + + /** + * Computes the terrain height at a specified longitude and latitude. + * + * @param {Rectangle} rectangle The rectangle covered by this terrain data. + * @param {Number} longitude The longitude in radians. + * @param {Number} latitude The latitude in radians. + * @returns {Number} The terrain height at the specified position. If the position + * is outside the rectangle, this method will extrapolate the height, which is likely to be wildly + * incorrect for positions far outside the rectangle. + */ + GoogleEarthEnterpriseTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) { + var u = CesiumMath.clamp((longitude - rectangle.west) / rectangle.width, 0.0, 1.0); + u *= maxShort; + var v = CesiumMath.clamp((latitude - rectangle.south) / rectangle.height, 0.0, 1.0); + v *= maxShort; + + var heightSample; + if (defined(this._mesh)) { + heightSample = interpolateMeshHeight(this, u, v); + } + + return heightSample; + }; + + //var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh'); + + /** + * Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the + * height samples in this instance, interpolated if necessary. + * + * @param {TilingScheme} tilingScheme The tiling scheme of this terrain data. + * @param {Number} thisX The X coordinate of this tile in the tiling scheme. + * @param {Number} thisY The Y coordinate of this tile in the tiling scheme. + * @param {Number} thisLevel The level of this tile in the tiling scheme. + * @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling. + * @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling. + * @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling. + * @returns {Promise.|undefined} A promise for upsampled heightmap terrain data for the descendant tile, + * or undefined if too many asynchronous upsample operations are in progress and the request has been + * deferred. + */ + GoogleEarthEnterpriseTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) { + //>>includeStart('debug', pragmas.debug); + if (!defined(tilingScheme)) { + throw new DeveloperError('tilingScheme is required.'); + } + if (!defined(thisX)) { + throw new DeveloperError('thisX is required.'); + } + if (!defined(thisY)) { + throw new DeveloperError('thisY is required.'); + } + if (!defined(thisLevel)) { + throw new DeveloperError('thisLevel is required.'); + } + if (!defined(descendantX)) { + throw new DeveloperError('descendantX is required.'); + } + if (!defined(descendantY)) { + throw new DeveloperError('descendantY is required.'); + } + if (!defined(descendantLevel)) { + throw new DeveloperError('descendantLevel is required.'); + } + var levelDifference = descendantLevel - thisLevel; + if (levelDifference > 1) { + throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.'); + } + //>>includeEnd('debug'); + + return undefined; + + // var mesh = this._mesh; + // if (!defined(this._mesh)) { + // return undefined; + // } + // + // var isEastChild = thisX * 2 !== descendantX; + // var isNorthChild = thisY * 2 === descendantY; + // + // var ellipsoid = tilingScheme.ellipsoid; + // var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel); + // + // var upsamplePromise = upsampleTaskProcessor.scheduleTask({ + // vertices : mesh.vertices, + // vertexCountWithoutSkirts : mesh.vertices.length / 3, + // indices : mesh.indices, + // skirtIndex : undefined, + // encoding : mesh.encoding, + // minimumHeight : this._minimumHeight, + // maximumHeight : this._maximumHeight, + // isEastChild : isEastChild, + // isNorthChild : isNorthChild, + // childRectangle : childRectangle, + // ellipsoid : ellipsoid, + // exaggeration : mesh.exaggeration + // }); + // + // if (!defined(upsamplePromise)) { + // // Postponed + // return undefined; + // } + // + // // TODO: Skirt + // // var shortestSkirt = Math.min(this._westSkirtHeight, this._eastSkirtHeight); + // // shortestSkirt = Math.min(shortestSkirt, this._southSkirtHeight); + // // shortestSkirt = Math.min(shortestSkirt, this._northSkirtHeight); + // // + // // var westSkirtHeight = isEastChild ? (shortestSkirt * 0.5) : this._westSkirtHeight; + // // var southSkirtHeight = isNorthChild ? (shortestSkirt * 0.5) : this._southSkirtHeight; + // // var eastSkirtHeight = isEastChild ? this._eastSkirtHeight : (shortestSkirt * 0.5); + // // var northSkirtHeight = isNorthChild ? this._northSkirtHeight : (shortestSkirt * 0.5); + // + // return when(upsamplePromise, function(result) { + // var quantizedVertices = new Uint16Array(result.vertices); + // var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices); + // + // return new GoogleEarthEnterpriseTerrainData({ + // quantizedVertices : quantizedVertices, + // indices : indicesTypedArray, + // minimumHeight : result.minimumHeight, + // maximumHeight : result.maximumHeight, + // boundingSphere : BoundingSphere.clone(result.boundingSphere), + // orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox), + // horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), + // westIndices : result.westIndices, + // southIndices : result.southIndices, + // eastIndices : result.eastIndices, + // northIndices : result.northIndices, + // westSkirtHeight : 0, + // southSkirtHeight : 0, + // eastSkirtHeight : 0, + // northSkirtHeight : 0, + // childTileMask : 0, + // createdByUpsampling : true + // }); + // }); + }; + + /** + * Determines if a given child tile is available, based on the + * {@link HeightmapTerrainData.childTileMask}. The given child tile coordinates are assumed + * to be one of the four children of this tile. If non-child tile coordinates are + * given, the availability of the southeast child tile is returned. + * + * @param {Number} thisX The tile X coordinate of this (the parent) tile. + * @param {Number} thisY The tile Y coordinate of this (the parent) tile. + * @param {Number} childX The tile X coordinate of the child tile to check for availability. + * @param {Number} childY The tile Y coordinate of the child tile to check for availability. + * @returns {Boolean} True if the child tile is available; otherwise, false. + */ + GoogleEarthEnterpriseTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) { + //>>includeStart('debug', pragmas.debug); + if (!defined(thisX)) { + throw new DeveloperError('thisX is required.'); + } + if (!defined(thisY)) { + throw new DeveloperError('thisY is required.'); + } + if (!defined(childX)) { + throw new DeveloperError('childX is required.'); + } + if (!defined(childY)) { + throw new DeveloperError('childY is required.'); + } + //>>includeEnd('debug'); + + // Layout: + // 3 2 + // 0 1 + var bitNumber = 0; // southwest child + if (childY === thisY * 2) { // north child + bitNumber += 2; + if (childX === thisX * 2) { + ++bitNumber; // west child + } + } else { // south child + if (childX !== thisX * 2) { + ++bitNumber; // east child + } + } + + return (this._childTileMask & (1 << bitNumber)) !== 0; + }; + + /** + * Gets a value indicating whether or not this terrain data was created by upsampling lower resolution + * terrain data. If this value is false, the data was obtained from some other source, such + * as by downloading it from a remote server. This method should return true for instances + * returned from a call to {@link HeightmapTerrainData#upsample}. + * + * @returns {Boolean} True if this instance was created by upsampling; otherwise, false. + */ + GoogleEarthEnterpriseTerrainData.prototype.wasCreatedByUpsampling = function() { + return this._createdByUpsampling; + }; + + var texCoordScratch0 = new Cartesian2(); + var texCoordScratch1 = new Cartesian2(); + var texCoordScratch2 = new Cartesian2(); + + function interpolateMeshHeight(terrainData, u, v) { + var mesh = terrainData._mesh; + var vertices = mesh.vertices; + var encoding = mesh.encoding; + var indices = mesh.indices; + + for (var i = 0, len = indices.length; i < len; i += 3) { + var i0 = indices[i]; + var i1 = indices[i + 1]; + var i2 = indices[i + 2]; + + var uv0 = encoding.decodeTextureCoordinates(vertices, i0, texCoordScratch0); + var uv1 = encoding.decodeTextureCoordinates(vertices, i1, texCoordScratch1); + var uv2 = encoding.decodeTextureCoordinates(vertices, i2, texCoordScratch2); + + var barycentric = Intersections2D.computeBarycentricCoordinates(u, v, uv0.x, uv0.y, uv1.x, uv1.y, uv2.x, uv2.y, barycentricCoordinateScratch); + if (barycentric.x >= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) { + var h0 = encoding.decodeHeight(vertices, i0); + var h1 = encoding.decodeHeight(vertices, i1); + var h2 = encoding.decodeHeight(vertices, i2); + return barycentric.x * h0 + barycentric.y * h1 + barycentric.z * h2; + } + } + + // Position does not lie in any triangle in this mesh. + return undefined; + } + + return GoogleEarthEnterpriseTerrainData; +}); diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index f152e32cdbb7..0cb082e415dd 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -109,6 +109,10 @@ define([ function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) { function success(terrainData) { + if (!defined(terrainData)) { + return failure(); + } + tileTerrain.data = terrainData; tileTerrain.state = TerrainState.RECEIVED; } diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js new file mode 100644 index 000000000000..7bf98b3f5636 --- /dev/null +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -0,0 +1,236 @@ +/*global define*/ +define([ + '../Core/AxisAlignedBoundingBox', + '../Core/BoundingSphere', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/defined', + '../Core/Ellipsoid', + '../Core/EllipsoidalOccluder', + '../Core/Math', + '../Core/Matrix4', + '../Core/OrientedBoundingBox', + '../Core/Rectangle', + '../Core/TerrainEncoding', + '../Core/Transforms', + './createTaskProcessorWorker' +], function( + AxisAlignedBoundingBox, + BoundingSphere, + Cartesian2, + Cartesian3, + Cartographic, + defined, + Ellipsoid, + EllipsoidalOccluder, + CesiumMath, + Matrix4, + OrientedBoundingBox, + Rectangle, + TerrainEncoding, + Transforms, + createTaskProcessorWorker) { + 'use strict'; + + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; + var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; + + function createVerticesFromGoogleEarthEnterpriseBuffer(parameters, transferableObjects) { + parameters.ellipsoid = Ellipsoid.clone(parameters.ellipsoid); + parameters.rectangle = Rectangle.clone(parameters.rectangle); + + var statistics = processBuffer(parameters.buffer, parameters.relativeToCenter, parameters.ellipsoid, + parameters.rectangle, parameters.nativeRectangle); + var vertices = statistics.vertices; + transferableObjects.push(vertices.buffer); + var indices = statistics.indices; + transferableObjects.push(indices.buffer); + + return { + vertices : vertices.buffer, + indices : indices.buffer, + numberOfAttributes : statistics.encoding.getStride(), + minimumHeight : statistics.minimumHeight, + maximumHeight : statistics.maximumHeight, + boundingSphere3D : statistics.boundingSphere3D, + orientedBoundingBox : statistics.orientedBoundingBox, + occludeePointInScaledSpace : statistics.occludeePointInScaledSpace, + encoding : statistics.encoding + }; + } + + var scratchCartographic = new Cartographic(); + var minimumScratch = new Cartesian3(); + var maximumScratch = new Cartesian3(); + var matrix4Scratch = new Matrix4(); + function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle) { + var geographicWest; + var geographicSouth; + var geographicEast; + var geographicNorth; + + if (!defined(rectangle)) { + geographicWest = CesiumMath.toRadians(nativeRectangle.west); + geographicSouth = CesiumMath.toRadians(nativeRectangle.south); + geographicEast = CesiumMath.toRadians(nativeRectangle.east); + geographicNorth = CesiumMath.toRadians(nativeRectangle.north); + } else { + geographicWest = rectangle.west; + geographicSouth = rectangle.south; + geographicEast = rectangle.east; + geographicNorth = rectangle.north; + } + + var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid); + var toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch); + + var dv = new DataView(buffer); + + var minHeight = Number.POSITIVE_INFINITY; + var maxHeight = Number.NEGATIVE_INFINITY; + + var minimum = minimumScratch; + minimum.x = Number.POSITIVE_INFINITY; + minimum.y = Number.POSITIVE_INFINITY; + minimum.z = Number.POSITIVE_INFINITY; + + var maximum = maximumScratch; + maximum.x = Number.NEGATIVE_INFINITY; + maximum.y = Number.NEGATIVE_INFINITY; + maximum.z = Number.NEGATIVE_INFINITY; + + // Compute sizes + var offset = 0; + var size = 0; + var indicesSize = 0; + for (var quad = 0; quad < 4; ++quad) { + var quadSize = dv.getUint32(offset, true); + + var o = offset; + o += sizeOfUint32 + 4*sizeOfDouble; // size + originX + originY + stepX + stepY + + var c = dv.getInt32(o, true); // Read point count + o += sizeOfInt32; + size += c; + + c = dv.getInt32(o, true); // Read index count + indicesSize += c; + + offset += quadSize + sizeOfUint32; // Jump to next quad + } + + // Create arrays + var positions = new Array(size); + var uvs = new Array(size); + var heights = new Array(size); + var indices = new Array(indicesSize); + + // Each tile is split into 4 parts + var pointOffset = 0; + var indicesOffset = 0; + offset = 0; + for (quad = 0; quad < 4; ++quad) { + //var quadSize = dv.getUint32(offset, true); + offset += sizeOfUint32; + + var originX = CesiumMath.toRadians(dv.getFloat64(offset, true)); + offset += sizeOfDouble; + + var originY = CesiumMath.toRadians(dv.getFloat64(offset, true)); + offset += sizeOfDouble; + + var stepX = CesiumMath.toRadians(dv.getFloat64(offset, true)); + offset += sizeOfDouble; + + var stepY = CesiumMath.toRadians(dv.getFloat64(offset, true)); + offset += sizeOfDouble; + + var numPoints = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var numFaces = dv.getInt32(offset, true); + offset += sizeOfInt32; + + //var level = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var index = pointOffset; + for (var i = 0; i < numPoints; ++i, ++index) { + var longitude = originX + dv.getUint8(offset++) * stepX; + scratchCartographic.longitude = longitude; + var latitude = originY + dv.getUint8(offset++) * stepY; + scratchCartographic.latitude = latitude; + // Height is stored in units of (1/EarthRadius) or (1/6371010.0) + var height = dv.getFloat32(offset, true) * 6371010.0; + scratchCartographic.height = height; + offset += sizeOfFloat; + + minHeight = Math.min(height, minHeight); + maxHeight = Math.max(height, maxHeight); + heights[index] = height; + + var pos = ellipsoid.cartographicToCartesian(scratchCartographic); + Matrix4.multiplyByPoint(toENU, pos, pos); + + Cartesian3.minimumByComponent(pos, minimum, minimum); + Cartesian3.maximumByComponent(pos, maximum, maximum); + + positions[index] = pos; + + var u = (longitude - geographicWest) / (geographicEast - geographicWest); + u = CesiumMath.clamp(u, 0.0, 1.0); + var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth); + v = CesiumMath.clamp(v, 0.0, 1.0); + + uvs[index] = new Cartesian2(u, v); + } + + index = indicesOffset; + var facesElementCount = numFaces * 3; + for (i = 0; i < facesElementCount; ++i, ++index) { + indices[index] = pointOffset + dv.getUint16(offset, true); + offset += sizeOfUint16; + } + + pointOffset += numPoints; + indicesOffset += facesElementCount; + } + + var boundingSphere3D = BoundingSphere.fromPoints(positions); + var orientedBoundingBox; + if (defined(rectangle) && 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) + orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minHeight, maxHeight, ellipsoid); + } + + var occluder = new EllipsoidalOccluder(ellipsoid); + var occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions); + + var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter); + var encoding = new TerrainEncoding(aaBox, minHeight, maxHeight, fromENU, false, false); + var vertices = new Float32Array(size * encoding.getStride()); + + var bufferIndex = 0; + for (var j = 0; j < size; ++j) { + bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j]); + } + + return { + vertices : vertices, + indices : Uint16Array.from(indices), + maximumHeight : maxHeight, + minimumHeight : minHeight, + encoding : encoding, + boundingSphere3D : boundingSphere3D, + orientedBoundingBox : orientedBoundingBox, + occludeePointInScaledSpace : occludeePointInScaledSpace + }; + } + + return createTaskProcessorWorker(createVerticesFromGoogleEarthEnterpriseBuffer); +}); diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseProviderSpec.js similarity index 87% rename from Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js rename to Specs/Core/GoogleEarthEnterpriseProviderSpec.js index 5a927c4636be..da8019c2e4eb 100644 --- a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseProviderSpec.js @@ -1,33 +1,29 @@ /*global defineSuite*/ defineSuite([ - 'Scene/GoogleEarthEnterpriseImageryProvider', + 'Core/GoogleEarthEnterpriseProvider', 'Core/DefaultProxy', 'Core/defaultValue', 'Core/defined', 'Core/loadImage', - 'Core/loadJsonp', 'Core/loadWithXhr', 'Core/Math', 'Core/TerrainProvider', 'Core/WebMercatorTilingScheme', - 'Scene/BingMapsStyle', 'Scene/DiscardMissingTileImagePolicy', 'Scene/Imagery', 'Scene/ImageryLayer', 'Scene/ImageryProvider', 'ThirdParty/when' ], function( - GoogleEarthEnterpriseImageryProvider, + GoogleEarthEnterpriseProvider, DefaultProxy, defaultValue, defined, loadImage, - loadJsonp, loadWithXhr, CesiumMath, TerrainProvider, WebMercatorTilingScheme, - BingMapsStyle, DiscardMissingTileImagePolicy, Imagery, ImageryLayer, @@ -35,39 +31,33 @@ defineSuite([ when) { 'use strict'; - // afterEach(function() { - // loadJsonp.loadAndExecuteScript = loadJsonp.defaultLoadAndExecuteScript; - // loadImage.createImage = loadImage.defaultCreateImage; - // loadWithXhr.load = loadWithXhr.defaultLoad; - // }); - it('tileXYToQuadKey', function() { // http://msdn.microsoft.com/en-us/library/bb259689.aspx // Levels are off by one compared to the documentation because our levels // start at 0 while Bing's start at 1. - expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(1, 0, 0)).toEqual('2'); - expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(1, 2, 1)).toEqual('02'); - expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(3, 5, 2)).toEqual('021'); - expect(GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(4, 7, 2)).toEqual('100'); + expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(1, 0, 0)).toEqual('2'); + expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(1, 2, 1)).toEqual('02'); + expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(3, 5, 2)).toEqual('021'); + expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(4, 7, 2)).toEqual('100'); }); it('quadKeyToTileXY', function() { - expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('2')).toEqual({ + expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('2')).toEqual({ x : 1, y : 0, level : 0 }); - expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('02')).toEqual({ + expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('02')).toEqual({ x : 1, y : 2, level : 1 }); - expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('021')).toEqual({ + expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('021')).toEqual({ x : 3, y : 5, level : 2 }); - expect(GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY('100')).toEqual({ + expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('100')).toEqual({ x : 4, y : 7, level : 2 @@ -83,11 +73,11 @@ defineSuite([ var buffer = data.buffer.slice(); var a = new Uint8Array(buffer); - GoogleEarthEnterpriseImageryProvider._decode(buffer); + GoogleEarthEnterpriseProvider._decode(buffer); expect(a).not.toEqual(data); // For the algorithm encode/decode are the same - GoogleEarthEnterpriseImageryProvider._decode(buffer); + GoogleEarthEnterpriseProvider._decode(buffer); expect(a).toEqual(data); }); @@ -95,11 +85,10 @@ defineSuite([ var quad = '0123'; var index = 0; var provider; - spyOn(GoogleEarthEnterpriseImageryProvider.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + spyOn(GoogleEarthEnterpriseProvider.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { quadKey = defaultValue(quadKey, '') + index.toString(); this._tileInfo[quadKey] = { bits : 0xFF, - cnodeVersion : 1, imageryVersion : 1, terrainVersion : 1 }; @@ -109,7 +98,7 @@ defineSuite([ }); var count = 0; - var requestQuads = [GoogleEarthEnterpriseImageryProvider.tileXYToQuadKey(0, 0, 1), quad]; + var requestQuads = [GoogleEarthEnterpriseProvider.tileXYToQuadKey(0, 0, 1), quad]; var requestType = ['blob', 'arraybuffer']; spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { expect(url).toEqual('http://test.server/3d/flatfile?f1-0' + requestQuads[count] + '-i.1'); @@ -118,18 +107,18 @@ defineSuite([ deferred.resolve(); }); - provider = new GoogleEarthEnterpriseImageryProvider({ + provider = new GoogleEarthEnterpriseProvider({ url: 'http://test.server/3d' }); - var tileXY = GoogleEarthEnterpriseImageryProvider.quadKeyToTileXY(quad); + var tileXY = GoogleEarthEnterpriseProvider.quadKeyToTileXY(quad); return provider.requestImage(tileXY.x, tileXY.y, tileXY.level) .then(function(image) { - expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket.calls.count()).toEqual(4); - expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith(); - expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('0', 1); - expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('01', 1); - expect(GoogleEarthEnterpriseImageryProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('012', 1); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket.calls.count()).toEqual(4); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith(); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('0'); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('01'); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('012'); var tileInfo = provider._tileInfo; expect(tileInfo['0']).toBeDefined(); @@ -140,11 +129,11 @@ defineSuite([ }); it('conforms to ImageryProvider interface', function() { - expect(GoogleEarthEnterpriseImageryProvider).toConformToInterface(ImageryProvider); + expect(GoogleEarthEnterpriseProvider).toConformToInterface(ImageryProvider); }); it('conforms to TerrainProvider interface', function() { - expect(GoogleEarthEnterpriseImageryProvider).toConformToInterface(TerrainProvider); + expect(GoogleEarthEnterpriseProvider).toConformToInterface(TerrainProvider); }); // it('constructor throws when url is not specified', function() { From dd7e0564f4d1f29a4b5ea391678ffa3835f13761 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 27 Mar 2017 17:22:20 -0400 Subject: [PATCH 03/60] More work on terrain. Heights are off but lat/lon positions seem at least close. --- .../createVerticesFromGoogleEarthEnterpriseBuffer.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 7bf98b3f5636..7d497a87e100 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -68,6 +68,7 @@ define([ var maximumScratch = new Cartesian3(); var matrix4Scratch = new Matrix4(); function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle) { + debugger; var geographicWest; var geographicSouth; var geographicEast; @@ -137,16 +138,16 @@ define([ //var quadSize = dv.getUint32(offset, true); offset += sizeOfUint32; - var originX = CesiumMath.toRadians(dv.getFloat64(offset, true)); + var originX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); offset += sizeOfDouble; - var originY = CesiumMath.toRadians(dv.getFloat64(offset, true)); + var originY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); offset += sizeOfDouble; - var stepX = CesiumMath.toRadians(dv.getFloat64(offset, true)); + var stepX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); offset += sizeOfDouble; - var stepY = CesiumMath.toRadians(dv.getFloat64(offset, true)); + var stepY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); offset += sizeOfDouble; var numPoints = dv.getInt32(offset, true); From 8787f66a8afaec8c4230824b977369e10202db0d Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 28 Mar 2017 11:36:37 -0400 Subject: [PATCH 04/60] Terrain and Imagery requests now return undefined if we know there isn't a tile as opposed to a promise everytime. Also handles negative altitudes in terrain. --- Source/Core/GoogleEarthEnterpriseProvider.js | 78 +++++++++++++++++-- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 10 +++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseProvider.js b/Source/Core/GoogleEarthEnterpriseProvider.js index 1811e4f9abd6..2fac795ddb85 100644 --- a/Source/Core/GoogleEarthEnterpriseProvider.js +++ b/Source/Core/GoogleEarthEnterpriseProvider.js @@ -469,15 +469,47 @@ define([ throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); } //>>includeEnd('debug'); + var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); + var tileInfo = this._tileInfo; + var info = tileInfo[quadKey]; + if (defined(info)) { + if (info.bits & imageBitmask === 0) { + // Already have info and there isn't any imagery here + return undefined; + } + } else { + if (info === null) { + // Parent was retrieved and said child doesn't exist + return undefined; + } + + var q = quadKey; + var last; + while(q.length > 1) { + last = q.substring(q.length-1); + q = q.substring(0, q.length-1); + info = tileInfo[q]; + if (defined(info)) { + if ((info.bits & cacheFlagBitmask === 0) && + (info.bits & childrenBitmasks[parseInt(last)] === 0)){ + // We have no subtree or child available at some point in this node's ancestry + return undefined; + } + + break; + } else if (info === null) { + // Some node in the ancestry was loaded and said there wasn't a subtree + return undefined; + } + } + } var that = this; - var tileInfo = this._tileInfo; - var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); return populateSubtree(this, quadKey) .then(function(exists){ if (exists) { - var info = tileInfo[quadKey]; - if (info.bits & imageBitmask) { + info = tileInfo[quadKey]; + if (info.bits & imageBitmask !== 0) { var url = buildImageUrl(that, quadKey, info.imageryVersion); return loadArrayBuffer(url) .then(function(image) { @@ -626,10 +658,44 @@ define([ } //>>includeEnd('debug'); - var that = this; + var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); var tileInfo = this._tileInfo; + var info = tileInfo[quadKey]; + if (defined(info)) { + if ((info.bits & terrainBitmask === 0) && !info.terrainInParent){ + // Already have info and there isn't any imagery here + return undefined; + } + } else { + if (info === null) { + // Parent was retrieved and said child doesn't exist + return undefined; + } + + var q = quadKey; + var last; + while(q.length > 1) { + last = q.substring(q.length-1); + q = q.substring(0, q.length-1); + info = tileInfo[q]; + if (defined(info)) { + if ((info.bits & cacheFlagBitmask === 0) && + (info.bits & childrenBitmasks[parseInt(last)] === 0)){ + // We have no subtree or child available at some point in this node's ancestry + return undefined; + } + + break; + } else if (info === null) { + // Some node in the ancestry was loaded and said there wasn't a subtree + return undefined; + } + } + } + + + var that = this; var terrainCache = this._terrainCache; - var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); return populateSubtree(this, quadKey) .then(function(exists){ if (exists) { diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 7d497a87e100..22d0186f4c54 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -63,6 +63,8 @@ define([ }; } + var negativeElevationFactor = -Math.pow(2, 32); + var negativeElevationThreshold = CesiumMath.EPSILON12; var scratchCartographic = new Cartographic(); var minimumScratch = new Cartesian3(); var maximumScratch = new Cartesian3(); @@ -167,6 +169,14 @@ define([ scratchCartographic.latitude = latitude; // Height is stored in units of (1/EarthRadius) or (1/6371010.0) var height = dv.getFloat32(offset, true) * 6371010.0; + + // In order to support old clients, negative altitude values are stored as + // height/-2^32. Old clients see the value as really close to 0 but new clients multiply + // by -2^32 to get the real negative altitude value. + if (height < negativeElevationThreshold) { + height *= negativeElevationFactor; + } + scratchCartographic.height = height; offset += sizeOfFloat; From c36a00445b7386f0c04c23a86a101ec27f5e43ed Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 29 Mar 2017 17:18:55 -0400 Subject: [PATCH 05/60] Fixed terrain issue with incorrect height and added some terrain data tests. --- .../Core/GoogleEarthEnterpriseTerrainData.js | 6 +- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 18 +- .../GoogleEarthEnterpriseTerrainDataSpec.js | 766 ++++++++++++++++++ 3 files changed, 780 insertions(+), 10 deletions(-) create mode 100644 Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 74590d1dfb74..b5223e3e876d 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -2,6 +2,7 @@ define([ '../ThirdParty/when', './Cartesian2', + './Cartesian3', './defaultValue', './defined', './defineProperties', @@ -19,6 +20,7 @@ define([ ], function( when, Cartesian2, + Cartesian3, defaultValue, defined, defineProperties, @@ -42,7 +44,7 @@ define([ * @constructor * * @param {Object} options Object with the following properties: - * @param {TypedArray} options.buffer The buffer containing height data. + * @param {ArrayBuffer} options.buffer The buffer containing terrain data. * @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist. * If a child's bit is set, geometry will be requested for that tile as well when it * is needed. If the bit is cleared, the child tile is not requested and geometry is @@ -387,7 +389,7 @@ define([ var texCoordScratch0 = new Cartesian2(); var texCoordScratch1 = new Cartesian2(); var texCoordScratch2 = new Cartesian2(); - + var barycentricCoordinateScratch = new Cartesian3(); function interpolateMeshHeight(terrainData, u, v) { var mesh = terrainData._mesh; var vertices = mesh.vertices; diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 22d0186f4c54..4c9571306a08 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -44,7 +44,7 @@ define([ parameters.rectangle = Rectangle.clone(parameters.rectangle); var statistics = processBuffer(parameters.buffer, parameters.relativeToCenter, parameters.ellipsoid, - parameters.rectangle, parameters.nativeRectangle); + parameters.rectangle, parameters.nativeRectangle, parameters.exaggeration); var vertices = statistics.vertices; transferableObjects.push(vertices.buffer); var indices = statistics.indices; @@ -66,10 +66,11 @@ define([ var negativeElevationFactor = -Math.pow(2, 32); var negativeElevationThreshold = CesiumMath.EPSILON12; var scratchCartographic = new Cartographic(); + var scratchCartesian = new Cartesian3(); var minimumScratch = new Cartesian3(); var maximumScratch = new Cartesian3(); var matrix4Scratch = new Matrix4(); - function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle) { + function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration) { debugger; var geographicWest; var geographicSouth; @@ -121,7 +122,7 @@ define([ size += c; c = dv.getInt32(o, true); // Read index count - indicesSize += c; + indicesSize += c*3; offset += quadSize + sizeOfUint32; // Jump to next quad } @@ -169,6 +170,7 @@ define([ scratchCartographic.latitude = latitude; // Height is stored in units of (1/EarthRadius) or (1/6371010.0) var height = dv.getFloat32(offset, true) * 6371010.0; + offset += sizeOfFloat; // In order to support old clients, negative altitude values are stored as // height/-2^32. Old clients see the value as really close to 0 but new clients multiply @@ -176,21 +178,21 @@ define([ if (height < negativeElevationThreshold) { height *= negativeElevationFactor; } + height *= exaggeration; scratchCartographic.height = height; - offset += sizeOfFloat; minHeight = Math.min(height, minHeight); maxHeight = Math.max(height, maxHeight); heights[index] = height; var pos = ellipsoid.cartographicToCartesian(scratchCartographic); - Matrix4.multiplyByPoint(toENU, pos, pos); + positions[index] = pos; - Cartesian3.minimumByComponent(pos, minimum, minimum); - Cartesian3.maximumByComponent(pos, maximum, maximum); + Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); - positions[index] = pos; + Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); + Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum); var u = (longitude - geographicWest) / (geographicEast - geographicWest); u = CesiumMath.clamp(u, 0.0, 1.0); diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js new file mode 100644 index 000000000000..5bf08019a5b1 --- /dev/null +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -0,0 +1,766 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/GoogleEarthEnterpriseTerrainData', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Ellipsoid', + 'Core/GeographicTilingScheme', + 'Core/Math', + 'Core/Rectangle', + 'Core/TerrainData', + 'Core/TerrainMesh', + 'ThirdParty/when' +], function( + GoogleEarthEnterpriseTerrainData, + BoundingSphere, + Cartesian3, + Ellipsoid, + GeographicTilingScheme, + CesiumMath, + Rectangle, + TerrainData, + TerrainMesh, + when) { + 'use strict'; + + var sizeOfUint8 = Uint8Array.BYTES_PER_ELEMENT; + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; + var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; + var toEarthRadii = 1.0/6371010.0; + + function getBuffer(tilingScheme, x, y, level) { + var rectangle = tilingScheme.tileXYToRectangle(x, y, level); + var center = Rectangle.center(rectangle); + var southwest = Rectangle.southwest(rectangle); + var stepX = CesiumMath.toDegrees(rectangle.width / 2) / 180.0; + var stepY = CesiumMath.toDegrees(rectangle.height / 2) / 180.0; + + // 2 Uint8s: x and y values in units of step + var pointSize = 2 * sizeOfUint8 + sizeOfFloat; + + // 3 shorts + var faceSize = 3 * sizeOfUint16; + + // Doubles: OriginX, OriginY, SizeX, SizeY + // Int32s: numPoints, numFaces, level + // 4 corner points + // 2 face (3 shorts) + var quadSize = 4 * sizeOfDouble + 3 * sizeOfInt32 + 4 * pointSize + 2 * faceSize; + + // QuadSize + size of each quad + var totalSize = 4 * (quadSize + sizeOfUint32); + var buf = new ArrayBuffer(totalSize); + var dv = new DataView(buf); + + var offset = 0; + for(var i=0;i<4;++i) { + dv.setUint32(offset, quadSize, true); + offset += sizeOfUint32; + + // Origin + var xOrigin = southwest.longitude; + var yOrigin = southwest.latitude; + if (i & 1) { + xOrigin = center.longitude; + } + if (i & 2) { + yOrigin = center.latitude; + } + + dv.setFloat64(offset, CesiumMath.toDegrees(xOrigin)/180.0, true); + offset += sizeOfDouble; + dv.setFloat64(offset, CesiumMath.toDegrees(yOrigin)/180.0, true); + offset += sizeOfDouble; + + // Step - Each step is a degree + dv.setFloat64(offset, stepX, true); + offset += sizeOfDouble; + dv.setFloat64(offset, stepY, true); + offset += sizeOfDouble; + + // NumPoints + dv.setInt32(offset, 4, true); + offset += sizeOfInt32; + + // NumFaces + dv.setInt32(offset, 2, true); + offset += sizeOfInt32; + + // Level + dv.setInt32(offset, 0, true); + offset += sizeOfInt32; + + // Points + for (var j = 0; j < 4; ++j) { + var xPos = 0; + var yPos = 0; + if (j & 1) { + ++xPos; + } + if (j & 2) { + ++yPos; + } + + dv.setUint8(offset++, xPos); + dv.setUint8(offset++, yPos); + dv.setFloat32(offset, (i * 4 + j) * toEarthRadii, true); + offset += sizeOfFloat; + } + + // Faces + var indices = [0,1,2,1,3,2]; + for (j = 0; j < indices.length; ++j) { + dv.setUint16(offset, indices[j], true); + offset += sizeOfUint16; + } + } + + return buf; + } + + it('conforms to TerrainData interface', function() { + expect(GoogleEarthEnterpriseTerrainData).toConformToInterface(TerrainData); + }); +/* + describe('upsample', function() { + function findVertexWithCoordinates(uBuffer, vBuffer, u, v) { + u *= 32767; + u |= 0; + v *= 32767; + v |= 0; + for (var i = 0; i < uBuffer.length; ++i) { + if (Math.abs(uBuffer[i] - u) <= 1 && Math.abs(vBuffer[i] - v) <= 1) { + return i; + } + } + return -1; + } + + function hasTriangle(ib, i0, i1, i2) { + for (var i = 0; i < ib.length; i += 3) { + if (ib[i] === i0 && ib[i + 1] === i1 && ib[i + 2] === i2 || + ib[i] === i1 && ib[i + 1] === i2 && ib[i + 2] === i0 || + ib[i] === i2 && ib[i + 1] === i0 && ib[i + 2] === i1) { + + return true; + } + } + + return false; + } + + function intercept(interceptCoordinate1, interceptCoordinate2, otherCoordinate1, otherCoordinate2) { + return CesiumMath.lerp(otherCoordinate1, otherCoordinate2, (0.5 - interceptCoordinate1) / (interceptCoordinate2 - interceptCoordinate1)); + } + + function horizontalIntercept(u1, v1, u2, v2) { + return intercept(v1, v2, u1, u2); + } + + function verticalIntercept(u1, v1, u2, v2) { + return intercept(u1, u2, v1, v2); + } + + it('works for all four children of a simple quad', function() { + var data = new QuantizedMeshTerrainData({ + minimumHeight : 0.0, + maximumHeight : 4.0, + quantizedVertices : new Uint16Array([ // order is sw nw se ne + // u + 0, 0, 32767, 32767, + // v + 0, 32767, 0, 32767, + // heights + 32767 / 4.0, 2.0 * 32767 / 4.0, 3.0 * 32767 / 4.0, 32767 + ]), + indices : new Uint16Array([ + 0, 3, 1, + 0, 2, 3 + ]), + boundingSphere : new BoundingSphere(), + horizonOcclusionPoint : new Cartesian3(), + westIndices : [], + southIndices : [], + eastIndices : [], + northIndices : [], + westSkirtHeight : 1.0, + southSkirtHeight : 1.0, + eastSkirtHeight : 1.0, + northSkirtHeight : 1.0, + childTileMask : 15 + }); + + var tilingScheme = new GeographicTilingScheme(); + + return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { + var swPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); + var sePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 0, 1); + var nwPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 1, 1); + var nePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 1, 1); + return when.join(swPromise, sePromise, nwPromise, nePromise); + }).then(function(upsampleResults) { + expect(upsampleResults.length).toBe(4); + + for (var i = 0; i < upsampleResults.length; ++i) { + var upsampled = upsampleResults[i]; + expect(upsampled).toBeDefined(); + + var uBuffer = upsampled._uValues; + var vBuffer = upsampled._vValues; + var ib = upsampled._indices; + + expect(uBuffer.length).toBe(4); + expect(vBuffer.length).toBe(4); + expect(upsampled._heightValues.length).toBe(4); + expect(ib.length).toBe(6); + + var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); + expect(sw).not.toBe(-1); + var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); + expect(nw).not.toBe(-1); + var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); + expect(se).not.toBe(-1); + var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); + expect(ne).not.toBe(-1); + + var nwToSe = hasTriangle(ib, sw, se, nw) && hasTriangle(ib, nw, se, ne); + var swToNe = hasTriangle(ib, sw, ne, nw) && hasTriangle(ib, sw, se, ne); + expect(nwToSe || swToNe).toBe(true); + } + }); + }); + + it('oct-encoded normals works for all four children of a simple quad', function() { + var data = new QuantizedMeshTerrainData({ + minimumHeight : 0.0, + maximumHeight : 4.0, + quantizedVertices : new Uint16Array([ // order is sw nw se ne + // u + 0, 0, 32767, 32767, + // v + 0, 32767, 0, 32767, + // heights + 32767 / 4.0, 2.0 * 32767 / 4.0, 3.0 * 32767 / 4.0, 32767 + ]), + encodedNormals : new Uint8Array([ + // fun property of oct-encoded normals: the octrahedron is projected onto a plane + // and unfolded into a unit square. The 4 corners of this unit square are encoded values + // of the same Cartesian normal, vec3(0.0, 0.0, 1.0). + // Therefore, all 4 normals below are actually oct-encoded representations of vec3(0.0, 0.0, 1.0) + 255, 0, // sw + 255, 255, // nw + 255, 0, // se + 255, 255 // ne + ]), + indices : new Uint16Array([ + 0, 3, 1, + 0, 2, 3 + ]), + boundingSphere : new BoundingSphere(), + horizonOcclusionPoint : new Cartesian3(), + westIndices : [], + southIndices : [], + eastIndices : [], + northIndices : [], + westSkirtHeight : 1.0, + southSkirtHeight : 1.0, + eastSkirtHeight : 1.0, + northSkirtHeight : 1.0, + childTileMask : 15 + }); + + var tilingScheme = new GeographicTilingScheme(); + + return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { + var swPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); + var sePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 0, 1); + var nwPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 1, 1); + var nePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 1, 1); + return when.join(swPromise, sePromise, nwPromise, nePromise); + }).then(function(upsampleResults) { + expect(upsampleResults.length).toBe(4); + + for (var i = 0; i < upsampleResults.length; ++i) { + var upsampled = upsampleResults[i]; + expect(upsampled).toBeDefined(); + + var encodedNormals = upsampled._encodedNormals; + + expect(encodedNormals.length).toBe(8); + + // All 4 normals should remain oct-encoded representations of vec3(0.0, 0.0, -1.0) + for (var n = 0; n < encodedNormals.length; ++n) { + expect(encodedNormals[i]).toBe(255); + } + } + }); + }); + + it('works for a quad with an extra vertex in the northwest child', function() { + var data = new QuantizedMeshTerrainData({ + minimumHeight : 0.0, + maximumHeight : 6.0, + quantizedVertices : new Uint16Array([ // order is sw, nw, se, ne, extra vertex in nw quadrant + // u + 0, 0, 32767, 32767, 0.125 * 32767, + // v + 0, 32767, 0, 32767, 0.75 * 32767, + // heights + 32767 / 6.0, 2.0 * 32767 / 6.0, 3.0 * 32767 / 6.0, 4.0 * 32767 / 6.0, 32767 + ]), + indices : new Uint16Array([ + 0, 4, 1, + 0, 2, 4, + 1, 4, 3, + 3, 4, 2 + ]), + boundingSphere : new BoundingSphere(), + horizonOcclusionPoint : new Cartesian3(), + westIndices : [], + southIndices : [], + eastIndices : [], + northIndices : [], + westSkirtHeight : 1.0, + southSkirtHeight : 1.0, + eastSkirtHeight : 1.0, + northSkirtHeight : 1.0, + childTileMask : 15 + }); + + var tilingScheme = new GeographicTilingScheme(); + return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { + return data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); + }).then(function(upsampled) { + var uBuffer = upsampled._uValues; + var vBuffer = upsampled._vValues; + var ib = upsampled._indices; + + expect(uBuffer.length).toBe(9); + expect(vBuffer.length).toBe(9); + expect(upsampled._heightValues.length).toBe(9); + expect(ib.length).toBe(8 * 3); + + var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); + expect(sw).not.toBe(-1); + var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); + expect(nw).not.toBe(-1); + var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); + expect(se).not.toBe(-1); + var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); + expect(ne).not.toBe(-1); + var extra = findVertexWithCoordinates(uBuffer, vBuffer, 0.25, 0.5); + expect(extra).not.toBe(-1); + var v40 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.0, 0.0, 0.125, 0.75) * 2.0, 0.0); + expect(v40).not.toBe(-1); + var v42 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.5, verticalIntercept(1.0, 0.0, 0.125, 0.75), 0.125, 0.75) * 2.0, 0.0); + expect(v42).not.toBe(-1); + var v402 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.5, 0.0, 0.125, 0.75) * 2.0, 0.0); + expect(v402).not.toBe(-1); + var v43 = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, verticalIntercept(1.0, 1.0, 0.125, 0.75) * 2.0 - 1.0); + expect(v43).not.toBe(-1); + + expect(hasTriangle(ib, sw, extra, nw)).toBe(true); + expect(hasTriangle(ib, sw, v40, extra)).toBe(true); + expect(hasTriangle(ib, v40, v402, extra)).toBe(true); + expect(hasTriangle(ib, v402, v42, extra)).toBe(true); + expect(hasTriangle(ib, extra, v42, v43)).toBe(true); + expect(hasTriangle(ib, v42, se, v43)).toBe(true); + expect(hasTriangle(ib, nw, v43, ne)).toBe(true); + expect(hasTriangle(ib, nw, extra, v43)).toBe(true); + }); + }); + + it('works for a quad with an extra vertex on the splitting plane', function() { + var data = new QuantizedMeshTerrainData({ + minimumHeight : 0.0, + maximumHeight : 6.0, + quantizedVertices : new Uint16Array([ // order is sw, nw, se, ne, extra vertex in nw quadrant + // u + 0, 0, 32767, 32767, 0.5 * 32767, + // v + 0, 32767, 0, 32767, 0.75 * 32767, + // heights + 32767 / 6.0, 2.0 * 32767 / 6.0, 3.0 * 32767 / 6.0, 4.0 * 32767 / 6.0, 32767 + ]), + indices : new Uint16Array([ + 0, 4, 1, + 1, 4, 3, + 0, 2, 4, + 3, 4, 2 + ]), + boundingSphere : new BoundingSphere(), + horizonOcclusionPoint : new Cartesian3(), + westIndices : [], + southIndices : [], + eastIndices : [], + northIndices : [], + westSkirtHeight : 1.0, + southSkirtHeight : 1.0, + eastSkirtHeight : 1.0, + northSkirtHeight : 1.0, + childTileMask : 15 + }); + + var tilingScheme = new GeographicTilingScheme(); + return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { + var nwPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); + var nePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 0, 1); + return when.join(nwPromise, nePromise); + }).then(function(upsampleResults){ + expect(upsampleResults.length).toBe(2); + var uBuffer, vBuffer; + for (var i = 0; i < upsampleResults.length; i++) { + var upsampled = upsampleResults[i]; + expect(upsampled).toBeDefined(); + + uBuffer = upsampled._uValues; + vBuffer = upsampled._vValues; + var ib = upsampled._indices; + + expect(uBuffer.length).toBe(6); + expect(vBuffer.length).toBe(6); + expect(upsampled._heightValues.length).toBe(6); + expect(ib.length).toBe(4 * 3); + + var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); + expect(sw).not.toBe(-1); + var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); + expect(nw).not.toBe(-1); + var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); + expect(se).not.toBe(-1); + var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); + expect(ne).not.toBe(-1); + } + + // northwest + uBuffer = upsampleResults[0]._uValues; + vBuffer = upsampleResults[0]._vValues; + var extra = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.5); + expect(extra).not.toBe(-1); + var v40 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.0, 0.0, 0.5, 0.75) * 2.0, 0.0); + expect(v40).not.toBe(-1); + expect(upsampleResults[0]._westIndices.length).toBe(2); + expect(upsampleResults[0]._eastIndices.length).toBe(3); + expect(upsampleResults[0]._northIndices.length).toBe(2); + expect(upsampleResults[0]._southIndices.length).toBe(3); + + // northeast + uBuffer = upsampleResults[1]._uValues; + vBuffer = upsampleResults[1]._vValues; + extra = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.5); + expect(extra).not.toBe(-1); + var v42 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(1.0, 0.0, 0.5, 0.75) * 0.5, 0.0); + expect(v42).not.toBe(-1); + expect(upsampleResults[1]._westIndices.length).toBe(3); + expect(upsampleResults[1]._eastIndices.length).toBe(2); + expect(upsampleResults[1]._northIndices.length).toBe(2); + expect(upsampleResults[1]._southIndices.length).toBe(3); + }); + }); + }); +*/ + describe('createMesh', function() { + var data; + var tilingScheme; + var buffer + + beforeEach(function() { + tilingScheme = new GeographicTilingScheme(); + buffer = getBuffer(tilingScheme, 0, 0, 0); + data = new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : 15 + }); + }); + + it('requires tilingScheme', function() { + expect(function() { + data.createMesh(undefined, 0, 0, 0); + }).toThrowDeveloperError(); + }); + + it('requires x', function() { + expect(function() { + data.createMesh(tilingScheme, undefined, 0, 0); + }).toThrowDeveloperError(); + }); + + it('requires y', function() { + expect(function() { + data.createMesh(tilingScheme, 0, undefined, 0); + }).toThrowDeveloperError(); + }); + + it('requires level', function() { + expect(function() { + data.createMesh(tilingScheme, 0, 0, undefined); + }).toThrowDeveloperError(); + }); + + it('creates specified vertices plus skirt vertices', function() { + var rectangle = tilingScheme.tileXYToRectangle(0, 0, 0); + var bs = BoundingSphere.fromRectangle3D(rectangle, Ellipsoid.WGS84, 15); + return data.createMesh(tilingScheme, 0, 0, 0).then(function(mesh) { + expect(mesh).toBeInstanceOf(TerrainMesh); + expect(mesh.vertices.length).toBe(16 * mesh.encoding.getStride()); // 16 regular vertices + expect(mesh.indices.length).toBe(4 * 2 * 3); // 2 regular triangles per quad + expect(mesh.minimumHeight).toBe(0); + expect(mesh.maximumHeight).toBeCloseTo(15, 5); + + // Bounding spheres of points and whole rectangle should be close + expect(Cartesian3.distance(bs.center, mesh.boundingSphere3D.center)).toBeLessThan(3); + expect(bs.radius - mesh.boundingSphere3D.radius).toBeLessThan(4); + }); + }); + + it('exaggerates mesh', function() { + var rectangle = tilingScheme.tileXYToRectangle(0, 0, 0); + var bs = BoundingSphere.fromRectangle3D(rectangle, Ellipsoid.WGS84, 30); + return data.createMesh(tilingScheme, 0, 0, 0, 2).then(function(mesh) { + expect(mesh).toBeInstanceOf(TerrainMesh); + expect(mesh.vertices.length).toBe(16 * mesh.encoding.getStride()); // 16 regular vertices + expect(mesh.indices.length).toBe(4 * 2 * 3); // 2 regular triangles + expect(mesh.minimumHeight).toBe(0); + expect(mesh.maximumHeight).toBeCloseTo(30, 5); + + // Bounding spheres of points and whole rectangle should be close + expect(Cartesian3.distance(bs.center, mesh.boundingSphere3D.center)).toBeLessThan(6); + expect(bs.radius - mesh.boundingSphere3D.radius).toBeLessThan(8); + }); + }); + + // it('requires 32bit indices for large meshes', function() { + // var tilingScheme = new GeographicTilingScheme(); + // var quantizedVertices = []; + // var i; + // for (i = 0; i < 65 * 1024; i++) { + // quantizedVertices.push(i % 32767); // u + // } + // for (i = 0; i < 65 * 1024; i++) { + // quantizedVertices.push(Math.floor(i / 32767)); // v + // } + // for (i = 0; i < 65 * 1024; i++) { + // quantizedVertices.push(0.0); // height + // } + // var data = new QuantizedMeshTerrainData({ + // minimumHeight : 0.0, + // maximumHeight : 4.0, + // quantizedVertices : new Uint16Array(quantizedVertices), + // indices : new Uint32Array([ 0, 3, 1, + // 0, 2, 3, + // 65000, 65002, 65003]), + // boundingSphere : new BoundingSphere(), + // horizonOcclusionPoint : new Cartesian3(), + // westIndices : [0, 1], + // southIndices : [0, 1], + // eastIndices : [2, 3], + // northIndices : [1, 3], + // westSkirtHeight : 1.0, + // southSkirtHeight : 1.0, + // eastSkirtHeight : 1.0, + // northSkirtHeight : 1.0, + // childTileMask : 15 + // }); + // + // return data.createMesh(tilingScheme, 0, 0, 0).then(function(mesh) { + // expect(mesh).toBeInstanceOf(TerrainMesh); + // expect(mesh.indices.BYTES_PER_ELEMENT).toBe(4); + // }); + // }); + }); + + /* + describe('interpolateHeight', function() { + var tilingScheme; + var rectangle; + + beforeEach(function() { + tilingScheme = new GeographicTilingScheme(); + rectangle = tilingScheme.tileXYToRectangle(7, 6, 5); + }); + + it('clamps coordinates if given a position outside the mesh', function() { + var mesh = new QuantizedMeshTerrainData({ + minimumHeight : 0.0, + maximumHeight : 4.0, + quantizedVertices : new Uint16Array([ // order is sw nw se ne + // u + 0, 0, 32767, 32767, + // v + 0, 32767, 0, 32767, + // heights + 32767 / 4.0, 2.0 * 32767 / 4.0, 3.0 * 32767 / 4.0, 32767 + ]), + indices : new Uint16Array([ + 0, 3, 1, + 0, 2, 3 + ]), + boundingSphere : new BoundingSphere(), + horizonOcclusionPoint : new Cartesian3(), + westIndices : [0, 1], + southIndices : [0, 1], + eastIndices : [2, 3], + northIndices : [1, 3], + westSkirtHeight : 1.0, + southSkirtHeight : 1.0, + eastSkirtHeight : 1.0, + northSkirtHeight : 1.0, + childTileMask : 15 + }); + + expect(mesh.interpolateHeight(rectangle, 0.0, 0.0)).toBe(mesh.interpolateHeight(rectangle, rectangle.east, rectangle.south)); + }); + + it('returns a height interpolated from the correct triangle', function() { + // zero height along line between southwest and northeast corners. + // Negative height in the northwest corner, positive height in the southeast. + var mesh = new QuantizedMeshTerrainData({ + minimumHeight : -16384, + maximumHeight : 16383, + quantizedVertices : new Uint16Array([ // order is sw nw se ne + // u + 0, 0, 32767, 32767, + // v + 0, 32767, 0, 32767, + // heights + 16384, 0, 32767, 16384 + ]), + indices : new Uint16Array([ + 0, 3, 1, + 0, 2, 3 + ]), + boundingSphere : new BoundingSphere(), + horizonOcclusionPoint : new Cartesian3(), + westIndices : [0, 1], + southIndices : [0, 1], + eastIndices : [2, 3], + northIndices : [1, 3], + westSkirtHeight : 1.0, + southSkirtHeight : 1.0, + eastSkirtHeight : 1.0, + northSkirtHeight : 1.0, + childTileMask : 15 + }); + + + // position in the northwest quadrant of the tile. + var longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.25; + var latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.75; + + var result = mesh.interpolateHeight(rectangle, longitude, latitude); + expect(result).toBeLessThan(0.0); + + // position in the southeast quadrant of the tile. + longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.75; + latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.25; + + result = mesh.interpolateHeight(rectangle, longitude, latitude); + expect(result).toBeGreaterThan(0.0); + + // position on the line between the southwest and northeast corners. + longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.5; + latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.5; + + result = mesh.interpolateHeight(rectangle, longitude, latitude); + expect(result).toEqualEpsilon(0.0, 1e-10); + }); + }); + */ + + describe('isChildAvailable', function() { + var data; + + beforeEach(function() { + data = new GoogleEarthEnterpriseTerrainData({ + buffer: new ArrayBuffer(1), + childTileMask : 15 + }); + }); + + it('requires thisX', function() { + expect(function() { + data.isChildAvailable(undefined, 0, 0, 0); + }).toThrowDeveloperError(); + }); + + it('requires thisY', function() { + expect(function() { + data.isChildAvailable(0, undefined, 0, 0); + }).toThrowDeveloperError(); + }); + + it('requires childX', function() { + expect(function() { + data.isChildAvailable(0, 0, undefined, 0); + }).toThrowDeveloperError(); + }); + + it('requires childY', function() { + expect(function() { + data.isChildAvailable(0, 0, 0, undefined); + }).toThrowDeveloperError(); + }); + + it('returns true for all children when child mask is not explicitly specified', function() { + data = new GoogleEarthEnterpriseTerrainData({ + buffer: new ArrayBuffer(1) + }); + + expect(data.isChildAvailable(10, 20, 20, 40)).toBe(true); + expect(data.isChildAvailable(10, 20, 21, 40)).toBe(true); + expect(data.isChildAvailable(10, 20, 20, 41)).toBe(true); + expect(data.isChildAvailable(10, 20, 21, 41)).toBe(true); + }); + + it('works when only southwest child is available', function() { + data = new GoogleEarthEnterpriseTerrainData({ + buffer: new ArrayBuffer(1), + childTileMask : 1 + }); + + expect(data.isChildAvailable(10, 20, 20, 40)).toBe(false); + expect(data.isChildAvailable(10, 20, 21, 40)).toBe(false); + expect(data.isChildAvailable(10, 20, 20, 41)).toBe(true); + expect(data.isChildAvailable(10, 20, 21, 41)).toBe(false); + }); + + it('works when only southeast child is available', function() { + data = new GoogleEarthEnterpriseTerrainData({ + buffer: new ArrayBuffer(1), + childTileMask : 2 + }); + + expect(data.isChildAvailable(10, 20, 20, 40)).toBe(false); + expect(data.isChildAvailable(10, 20, 21, 40)).toBe(false); + expect(data.isChildAvailable(10, 20, 20, 41)).toBe(false); + expect(data.isChildAvailable(10, 20, 21, 41)).toBe(true); + }); + + it('works when only northeast child is available', function() { + data = new GoogleEarthEnterpriseTerrainData({ + buffer: new ArrayBuffer(1), + childTileMask : 4 + }); + + expect(data.isChildAvailable(10, 20, 20, 40)).toBe(false); + expect(data.isChildAvailable(10, 20, 21, 40)).toBe(true); + expect(data.isChildAvailable(10, 20, 20, 41)).toBe(false); + expect(data.isChildAvailable(10, 20, 21, 41)).toBe(false); + }); + + it('works when only northwest child is available', function() { + data = new GoogleEarthEnterpriseTerrainData({ + buffer: new ArrayBuffer(1), + childTileMask : 8 + }); + + expect(data.isChildAvailable(10, 20, 20, 40)).toBe(true); + expect(data.isChildAvailable(10, 20, 21, 40)).toBe(false); + expect(data.isChildAvailable(10, 20, 20, 41)).toBe(false); + expect(data.isChildAvailable(10, 20, 21, 41)).toBe(false); + }); + }); +}); From d5606807cdafa5fea68457d1ea8c6e592607252e Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 30 Mar 2017 13:55:11 -0400 Subject: [PATCH 06/60] Got terrain mostly loading. --- Source/Core/GoogleEarthEnterpriseProvider.js | 86 +++++++++++-------- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 1 - 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseProvider.js b/Source/Core/GoogleEarthEnterpriseProvider.js index 2fac795ddb85..9429ddf61f25 100644 --- a/Source/Core/GoogleEarthEnterpriseProvider.js +++ b/Source/Core/GoogleEarthEnterpriseProvider.js @@ -68,6 +68,28 @@ define([ var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + function GoogleEarthEnterpriseDiscardPolicy() { + this._image = new Image(); + } + + /** + * Determines if the discard policy is ready to process images. + * @returns {Boolean} True if the discard policy is ready to process images; otherwise, false. + */ + GoogleEarthEnterpriseDiscardPolicy.prototype.isReady = function() { + return true; + }; + + /** + * Given a tile image, decide whether to discard that image. + * + * @param {Image} image An image to test. + * @returns {Boolean} True if the image should be discarded; otherwise, false. + */ + GoogleEarthEnterpriseDiscardPolicy.prototype.shouldDiscardImage = function(image) { + return (image === this._image); + }; + /** * Provides tiled imagery using the Google Earth Enterprise Imagery REST API. * @@ -80,15 +102,8 @@ define([ * By default, tiles are loaded using the same protocol as the page. * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile - * is invalid and should be discarded. If this value is not specified, a default - * {@link DiscardMissingTileImagePolicy} is used which requests - * tile 0,0 at the maximum tile level and checks pixels (0,0), (120,140), (130,160), - * (200,50), and (200,200). If all of these pixels are transparent, the discard check is - * disabled and no tiles are discarded. If any of them have a non-transparent color, any - * tile that has the same values in these pixel locations is discarded. The end result of - * these defaults should be correct tile discarding for a standard Google Earth Enterprise server. To ensure - * that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this - * parameter. + * is invalid and should be discarded. If this value is not specified, a default + * is to discard tiles that fail to download. * @param {Proxy} [options.proxy] A proxy to use for requests. This object is * expected to have a getURL function which returns the proxied URL, if needed. * @@ -138,12 +153,7 @@ define([ // Install the default tile discard policy if none has been supplied. if (!defined(this._tileDiscardPolicy)) { - this._tileDiscardPolicy = new DiscardMissingTileImagePolicy({ - // TODO - missing image url - missingImageUrl : buildImageUrl(this, GoogleEarthEnterpriseProvider.tileXYToQuadKey(0, 0, 1)), - pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(120, 140), new Cartesian2(130, 160), new Cartesian2(200, 50), new Cartesian2(200, 200)], - disableCheckIfAllPixelsAreTransparent : true - }); + this._tileDiscardPolicy = new GoogleEarthEnterpriseDiscardPolicy(); } this._tileInfo = {}; @@ -469,18 +479,19 @@ define([ throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); } //>>includeEnd('debug'); + var invalidImage = this._tileDiscardPolicy._image; // Empty image or undefined depending on discard policy var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); var tileInfo = this._tileInfo; var info = tileInfo[quadKey]; if (defined(info)) { if (info.bits & imageBitmask === 0) { // Already have info and there isn't any imagery here - return undefined; + return invalidImage; } } else { if (info === null) { // Parent was retrieved and said child doesn't exist - return undefined; + return invalidImage; } var q = quadKey; @@ -493,22 +504,21 @@ define([ if ((info.bits & cacheFlagBitmask === 0) && (info.bits & childrenBitmasks[parseInt(last)] === 0)){ // We have no subtree or child available at some point in this node's ancestry - return undefined; + return invalidImage; } break; } else if (info === null) { // Some node in the ancestry was loaded and said there wasn't a subtree - return undefined; + return invalidImage; } } } var that = this; return populateSubtree(this, quadKey) - .then(function(exists){ - if (exists) { - info = tileInfo[quadKey]; + .then(function(info){ + if (defined(info)) { if (info.bits & imageBitmask !== 0) { var url = buildImageUrl(that, quadKey, info.imageryVersion); return loadArrayBuffer(url) @@ -529,12 +539,17 @@ define([ return loadImageFromTypedArray(a, type); } + + return invalidImage; }) .otherwise(function(error) { - // Just ignore failures and return undefined + // Just ignore failures and return invalidImage + return invalidImage; }); } } + + return invalidImage; }); }; @@ -697,9 +712,8 @@ define([ var that = this; var terrainCache = this._terrainCache; return populateSubtree(this, quadKey) - .then(function(exists){ - if (exists) { - var info = tileInfo[quadKey]; + .then(function(info){ + if (defined(info)) { if (defined(terrainCache[quadKey])) { var buffer = terrainCache[quadKey]; delete terrainCache[quadKey]; @@ -1091,7 +1105,7 @@ define([ var t = tileInfo[q]; // If we have tileInfo make sure sure it is not a node with a subtree that's not loaded if (defined(t) && ((t.bits & cacheFlagBitmask) === 0 || (t.bits & anyChildBitmask) !== 0)) { - return when(true); + return when(t); } while((t === undefined) && q.length > 1) { @@ -1103,28 +1117,32 @@ define([ // null so one of its parents was a leaf node, so this tile doesn't exist // undefined so no parent exists - this shouldn't ever happen once the provider is ready if (!defined(t)) { - return when(false); + return when(undefined); } var subtreePromises = that._subtreePromises; var promise = subtreePromises[q]; if (defined(promise)) { - return promise; + return promise + .then(function() { + return tileInfo[quadKey]; + }); } // We need to split up the promise here because when will execute syncronously if _getQuadTreePacket // is already resolved (like in the tests), so subtreePromises will never get cleared out. - promise = that._getQuadTreePacket(q); + // The promise will always resolve with a bool, but the initial request will also remove + // the promise from subtreePromises. + promise = subtreePromises[q] = that._getQuadTreePacket(q); - subtreePromises[q] = promise - .then(function() { - return true; - }); return promise .then(function() { delete subtreePromises[q]; // Recursively call this incase we need multiple subtree requests return populateSubtree(that, quadKey); + }) + .then(function() { + return tileInfo[quadKey]; }); } diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 4c9571306a08..eda66cde09e0 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -71,7 +71,6 @@ define([ var maximumScratch = new Cartesian3(); var matrix4Scratch = new Matrix4(); function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration) { - debugger; var geographicWest; var geographicSouth; var geographicEast; From 6d35cedc2c6233a5f0a633ae70e71a14e28069f6 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 30 Mar 2017 19:02:34 -0400 Subject: [PATCH 07/60] Added skirts. --- .../Core/GoogleEarthEnterpriseTerrainData.js | 9 +- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 92 ++++++++++++++++++- .../GoogleEarthEnterpriseTerrainDataSpec.js | 49 ++++++++-- 3 files changed, 132 insertions(+), 18 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index b5223e3e876d..7050fb58b6ac 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -145,11 +145,10 @@ define([ // Compute the center of the tile for RTC rendering. var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle)); - // TODO - this._skirtHeight = 0; - // var levelZeroMaxError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, this._width, tilingScheme.getNumberOfXTilesAtLevel(0)); - // var thisLevelMaxError = levelZeroMaxError / (1 << level); - // this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0); + // 1024 was the initial size of the heightmap before decimation in GEE + var levelZeroMaxError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, 1024, tilingScheme.getNumberOfXTilesAtLevel(0)); + var thisLevelMaxError = levelZeroMaxError / (1 << level); + this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0); var verticesPromise = taskProcessor.scheduleTask({ buffer : this._buffer, diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index eda66cde09e0..bbc491c574e3 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -44,7 +44,7 @@ define([ parameters.rectangle = Rectangle.clone(parameters.rectangle); var statistics = processBuffer(parameters.buffer, parameters.relativeToCenter, parameters.ellipsoid, - parameters.rectangle, parameters.nativeRectangle, parameters.exaggeration); + parameters.rectangle, parameters.nativeRectangle, parameters.exaggeration, parameters.skirtHeight); var vertices = statistics.vertices; transferableObjects.push(vertices.buffer); var indices = statistics.indices; @@ -70,22 +70,27 @@ define([ var minimumScratch = new Cartesian3(); var maximumScratch = new Cartesian3(); var matrix4Scratch = new Matrix4(); - function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration) { + function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration, skirtHeight) { var geographicWest; var geographicSouth; var geographicEast; var geographicNorth; + var rectangleWidth, rectangleHeight; if (!defined(rectangle)) { geographicWest = CesiumMath.toRadians(nativeRectangle.west); geographicSouth = CesiumMath.toRadians(nativeRectangle.south); geographicEast = CesiumMath.toRadians(nativeRectangle.east); geographicNorth = CesiumMath.toRadians(nativeRectangle.north); + rectangleWidth = CesiumMath.toRadians(rectangle.width); + rectangleHeight = CesiumMath.toRadians(rectangle.height); } else { geographicWest = rectangle.west; geographicSouth = rectangle.south; geographicEast = rectangle.east; geographicNorth = rectangle.north; + rectangleWidth = rectangle.width; + rectangleHeight = rectangle.height; } var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid); @@ -132,6 +137,13 @@ define([ var heights = new Array(size); var indices = new Array(indicesSize); + // Points are laid out in rows starting at SW, so storing border points as we + // come across them all points will be adjacent. + var westBorder = []; + var southBorder = []; + var eastBorder = []; + var northBorder = []; + // Each tile is split into 4 parts var pointOffset = 0; var indicesOffset = 0; @@ -147,9 +159,11 @@ define([ offset += sizeOfDouble; var stepX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); + var halfStepX = stepX * 0.5; offset += sizeOfDouble; var stepY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); + var halfStepY = stepY * 0.5; offset += sizeOfDouble; var numPoints = dv.getInt32(offset, true); @@ -181,6 +195,30 @@ define([ scratchCartographic.height = height; + if (Math.abs(longitude - geographicWest) < halfStepX) { + westBorder.push({ + index: index, + cartographic: Cartographic.clone(scratchCartographic) + }); + } else if(Math.abs(longitude - geographicEast) < halfStepX) { + eastBorder.push({ + index: index, + cartographic: Cartographic.clone(scratchCartographic) + }); + } + + if (Math.abs(latitude - geographicSouth) < halfStepY) { + southBorder.push({ + index: index, + cartographic: Cartographic.clone(scratchCartographic) + }); + } else if(Math.abs(latitude - geographicNorth) < halfStepY) { + northBorder.push({ + index: index, + cartographic: Cartographic.clone(scratchCartographic) + }); + } + minHeight = Math.min(height, minHeight); maxHeight = Math.max(height, maxHeight); heights[index] = height; @@ -212,6 +250,54 @@ define([ indicesOffset += facesElementCount; } + // Add skirt points + var hMin = minHeight; + function addSkirt(borderPoints, longitudeFudge, latitudeFudge) { + var count = borderPoints.length; + var lastBorderPoint; + for (var j = 0; j < count; ++j) { + var borderPoint = borderPoints[j]; + var borderCartographic = borderPoint.cartographic; + if (!defined(lastBorderPoint) || + !Cartographic.equalsEpsilon(borderCartographic, lastBorderPoint.cartographic, CesiumMath.EPSILON7)) { + var borderIndex = borderPoint.index; + var currentIndex = positions.length; + + var longitude = borderCartographic.longitude + longitudeFudge; + var latitude = borderCartographic.latitude + latitudeFudge; + latitude = CesiumMath.clamp(latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); // Don't go over the poles + var height = borderCartographic.height - skirtHeight; + hMin = Math.min(hMin, height); + + Cartographic.fromRadians(longitude, latitude, height, scratchCartographic); + var pos = ellipsoid.cartographicToCartesian(scratchCartographic); + positions.push(pos); + heights.push(height); + uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point + + Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); + + Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); + Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum); + + if (defined(lastBorderPoint)) { + var lastBorderIndex = lastBorderPoint.index; + indices.push(lastBorderIndex, currentIndex - 1, currentIndex, currentIndex, borderIndex, lastBorderIndex); + } + + lastBorderPoint = borderPoint; + } + } + } + + var percentage = 0.00001; + addSkirt(westBorder, -percentage*rectangleWidth, 0); + addSkirt(southBorder, 0, -percentage*rectangleWidth); + addSkirt(eastBorder, percentage*rectangleWidth, 0); + addSkirt(northBorder, 0, percentage*rectangleWidth); + + size = positions.length; // Get new size with skirt vertices + var boundingSphere3D = BoundingSphere.fromPoints(positions); var orientedBoundingBox; if (defined(rectangle) && rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) { @@ -224,7 +310,7 @@ define([ var occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions); var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter); - var encoding = new TerrainEncoding(aaBox, minHeight, maxHeight, fromENU, false, false); + var encoding = new TerrainEncoding(aaBox, hMin, maxHeight, fromENU, false, false); var vertices = new Float32Array(size * encoding.getStride()); var bufferIndex = 0; diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 5bf08019a5b1..ebcc21646e13 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -3,23 +3,29 @@ defineSuite([ 'Core/GoogleEarthEnterpriseTerrainData', 'Core/BoundingSphere', 'Core/Cartesian3', + 'Core/Cartographic', 'Core/Ellipsoid', 'Core/GeographicTilingScheme', 'Core/Math', + 'Core/Matrix4', 'Core/Rectangle', 'Core/TerrainData', 'Core/TerrainMesh', + 'Core/Transforms', 'ThirdParty/when' ], function( GoogleEarthEnterpriseTerrainData, BoundingSphere, Cartesian3, + Cartographic, Ellipsoid, GeographicTilingScheme, CesiumMath, + Matrix4, Rectangle, TerrainData, TerrainMesh, + Transforms, when) { 'use strict'; @@ -55,8 +61,10 @@ defineSuite([ var buf = new ArrayBuffer(totalSize); var dv = new DataView(buf); + var altitudeStart = 0; var offset = 0; for(var i=0;i<4;++i) { + altitudeStart = 0; dv.setUint32(offset, quadSize, true); offset += sizeOfUint32; @@ -65,6 +73,7 @@ defineSuite([ var yOrigin = southwest.latitude; if (i & 1) { xOrigin = center.longitude; + altitudeStart = 10; } if (i & 2) { yOrigin = center.latitude; @@ -97,8 +106,10 @@ defineSuite([ for (var j = 0; j < 4; ++j) { var xPos = 0; var yPos = 0; + var altitude = altitudeStart; if (j & 1) { ++xPos; + altitude += 10; } if (j & 2) { ++yPos; @@ -106,7 +117,7 @@ defineSuite([ dv.setUint8(offset++, xPos); dv.setUint8(offset++, yPos); - dv.setFloat32(offset, (i * 4 + j) * toEarthRadii, true); + dv.setFloat32(offset, altitude * toEarthRadii, true); offset += sizeOfFloat; } @@ -502,17 +513,35 @@ defineSuite([ it('creates specified vertices plus skirt vertices', function() { var rectangle = tilingScheme.tileXYToRectangle(0, 0, 0); - var bs = BoundingSphere.fromRectangle3D(rectangle, Ellipsoid.WGS84, 15); + + var wgs84 = Ellipsoid.WGS84; return data.createMesh(tilingScheme, 0, 0, 0).then(function(mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); - expect(mesh.vertices.length).toBe(16 * mesh.encoding.getStride()); // 16 regular vertices - expect(mesh.indices.length).toBe(4 * 2 * 3); // 2 regular triangles per quad + expect(mesh.vertices.length).toBe(28 * mesh.encoding.getStride()); // 16 regular + 12 skirt vertices + expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); - expect(mesh.maximumHeight).toBeCloseTo(15, 5); - - // Bounding spheres of points and whole rectangle should be close - expect(Cartesian3.distance(bs.center, mesh.boundingSphere3D.center)).toBeLessThan(3); - expect(bs.radius - mesh.boundingSphere3D.radius).toBeLessThan(4); + expect(mesh.maximumHeight).toBeCloseTo(20, 5); + + var encoding = mesh.encoding; + var cartesian = new Cartesian3(); + var cartographic = new Cartographic(); + var count = mesh.vertices.length / mesh.encoding.getStride(); + for (var i = 0; i < count; ++i) { + encoding.decodePosition(mesh.vertices, i, cartesian); + wgs84.cartesianToCartographic(cartesian, cartographic); + + + var height = encoding.decodeHeight(mesh.vertices, i); + if (i < 16) { // Original vertices + expect(height).toBeBetween(0, 20); + + // Only test on original positions as the skirts angle outward + cartographic.longitude = CesiumMath.convertLongitudeRange(cartographic.longitude); + expect(Rectangle.contains(rectangle,cartographic)).toBe(true); + } else { // Skirts + expect(height).toBeBetween(-1000, -980); + } + } }); }); @@ -524,7 +553,7 @@ defineSuite([ expect(mesh.vertices.length).toBe(16 * mesh.encoding.getStride()); // 16 regular vertices expect(mesh.indices.length).toBe(4 * 2 * 3); // 2 regular triangles expect(mesh.minimumHeight).toBe(0); - expect(mesh.maximumHeight).toBeCloseTo(30, 5); + expect(mesh.maximumHeight).toBeCloseTo(40, 5); // Bounding spheres of points and whole rectangle should be close expect(Cartesian3.distance(bs.center, mesh.boundingSphere3D.center)).toBeLessThan(6); From 3eb039e654ba5f865bdda95e4244e4f8a815be8a Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 30 Mar 2017 23:02:03 -0400 Subject: [PATCH 08/60] Got upsampling mostly working. --- .../Core/GoogleEarthEnterpriseTerrainData.js | 172 +++++------ ...VerticesFromGoogleEarthEnterpriseBuffer.js | 11 +- .../GoogleEarthEnterpriseTerrainDataSpec.js | 273 ++++++++---------- 3 files changed, 213 insertions(+), 243 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 7050fb58b6ac..ebfe431c20a6 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -1,6 +1,7 @@ /*global define*/ define([ '../ThirdParty/when', + './BoundingSphere', './Cartesian2', './Cartesian3', './defaultValue', @@ -12,6 +13,8 @@ define([ './IndexDatatype', './Intersections2D', './Math', + './OrientedBoundingBox', + './QuantizedMeshTerrainData', './Rectangle', './TaskProcessor', './TerrainEncoding', @@ -19,6 +22,7 @@ define([ './TerrainProvider' ], function( when, + BoundingSphere, Cartesian2, Cartesian3, defaultValue, @@ -30,6 +34,8 @@ define([ IndexDatatype, Intersections2D, CesiumMath, + OrientedBoundingBox, + QuantizedMeshTerrainData, Rectangle, TaskProcessor, TerrainEncoding, @@ -80,13 +86,27 @@ define([ //>>includeEnd('debug'); this._buffer = options.buffer; - this._childTileMask = defaultValue(options.childTileMask, 15); + + + // Convert from google layout to layout of other providers + // 3 2 -> 2 3 + // 0 1 -> 0 1 + var googleChildTileMask = defaultValue(options.childTileMask, 15); + var childTileMask = googleChildTileMask & 3; // Bottom row is identical + childTileMask |= (googleChildTileMask & 4) ? 8 : 0; // NE + childTileMask |= (googleChildTileMask & 8) ? 4 : 0; // NW + + this._childTileMask = childTileMask; this._createdByUpsampling = defaultValue(options.createdByUpsampling, false); this._skirtHeight = undefined; this._bufferType = this._buffer.constructor; this._mesh = undefined; + this._minimumHeight = undefined; + this._maximumHeight = undefined; + this._vertexCountWithoutSkirts = undefined; + this._skirtIndex = undefined; } defineProperties(GoogleEarthEnterpriseTerrainData.prototype, { @@ -180,6 +200,11 @@ define([ TerrainEncoding.clone(result.encoding), exaggeration); + that._vertexCountWithoutSkirts = result.vertexCountWithoutSkirts; + that._skirtIndex = result.skirtIndex; + that._minimumHeight = result.minimumHeight; + that._maximumHeight = result.maximumHeight; + // Free memory received from server after mesh is created. that._buffer = undefined; return that._mesh; @@ -212,7 +237,7 @@ define([ return heightSample; }; - //var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh'); + var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh'); /** * Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the @@ -258,73 +283,65 @@ define([ } //>>includeEnd('debug'); - return undefined; + var mesh = this._mesh; + if (!defined(this._mesh)) { + return undefined; + } + + var isEastChild = thisX * 2 !== descendantX; + var isNorthChild = thisY * 2 === descendantY; - // var mesh = this._mesh; - // if (!defined(this._mesh)) { - // return undefined; - // } - // - // var isEastChild = thisX * 2 !== descendantX; - // var isNorthChild = thisY * 2 === descendantY; - // - // var ellipsoid = tilingScheme.ellipsoid; - // var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel); - // - // var upsamplePromise = upsampleTaskProcessor.scheduleTask({ - // vertices : mesh.vertices, - // vertexCountWithoutSkirts : mesh.vertices.length / 3, - // indices : mesh.indices, - // skirtIndex : undefined, - // encoding : mesh.encoding, - // minimumHeight : this._minimumHeight, - // maximumHeight : this._maximumHeight, - // isEastChild : isEastChild, - // isNorthChild : isNorthChild, - // childRectangle : childRectangle, - // ellipsoid : ellipsoid, - // exaggeration : mesh.exaggeration - // }); - // - // if (!defined(upsamplePromise)) { - // // Postponed - // return undefined; - // } - // - // // TODO: Skirt - // // var shortestSkirt = Math.min(this._westSkirtHeight, this._eastSkirtHeight); - // // shortestSkirt = Math.min(shortestSkirt, this._southSkirtHeight); - // // shortestSkirt = Math.min(shortestSkirt, this._northSkirtHeight); - // // - // // var westSkirtHeight = isEastChild ? (shortestSkirt * 0.5) : this._westSkirtHeight; - // // var southSkirtHeight = isNorthChild ? (shortestSkirt * 0.5) : this._southSkirtHeight; - // // var eastSkirtHeight = isEastChild ? this._eastSkirtHeight : (shortestSkirt * 0.5); - // // var northSkirtHeight = isNorthChild ? this._northSkirtHeight : (shortestSkirt * 0.5); - // - // return when(upsamplePromise, function(result) { - // var quantizedVertices = new Uint16Array(result.vertices); - // var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices); - // - // return new GoogleEarthEnterpriseTerrainData({ - // quantizedVertices : quantizedVertices, - // indices : indicesTypedArray, - // minimumHeight : result.minimumHeight, - // maximumHeight : result.maximumHeight, - // boundingSphere : BoundingSphere.clone(result.boundingSphere), - // orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox), - // horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), - // westIndices : result.westIndices, - // southIndices : result.southIndices, - // eastIndices : result.eastIndices, - // northIndices : result.northIndices, - // westSkirtHeight : 0, - // southSkirtHeight : 0, - // eastSkirtHeight : 0, - // northSkirtHeight : 0, - // childTileMask : 0, - // createdByUpsampling : true - // }); - // }); + var ellipsoid = tilingScheme.ellipsoid; + var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel); + + var upsamplePromise = upsampleTaskProcessor.scheduleTask({ + vertices : mesh.vertices, + vertexCountWithoutSkirts : this._vertexCountWithoutSkirts, + indices : mesh.indices, + skirtIndex : this._skirtIndex, + encoding : mesh.encoding, + minimumHeight : this._minimumHeight, + maximumHeight : this._maximumHeight, + isEastChild : isEastChild, + isNorthChild : isNorthChild, + childRectangle : childRectangle, + ellipsoid : ellipsoid, + exaggeration : mesh.exaggeration + }); + + if (!defined(upsamplePromise)) { + // Postponed + return undefined; + } + + var that = this; + return when(upsamplePromise, function(result) { + var quantizedVertices = new Uint16Array(result.vertices); + var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices); + + var skirtHeight = that._skirtHeight; + + // Use QuantizedMeshTerrainData since we have what we need already parsed + return new QuantizedMeshTerrainData({ + quantizedVertices : quantizedVertices, + indices : indicesTypedArray, + minimumHeight : result.minimumHeight, + maximumHeight : result.maximumHeight, + boundingSphere : BoundingSphere.clone(result.boundingSphere), + orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox), + horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), + westIndices : result.westIndices, + southIndices : result.southIndices, + eastIndices : result.eastIndices, + northIndices : result.northIndices, + westSkirtHeight : skirtHeight, + southSkirtHeight : skirtHeight, + eastSkirtHeight : skirtHeight, + northSkirtHeight : skirtHeight, + childTileMask : 0, + createdByUpsampling : true + }); + }); }; /** @@ -355,19 +372,12 @@ define([ } //>>includeEnd('debug'); - // Layout: - // 3 2 - // 0 1 - var bitNumber = 0; // southwest child - if (childY === thisY * 2) { // north child - bitNumber += 2; - if (childX === thisX * 2) { - ++bitNumber; // west child - } - } else { // south child - if (childX !== thisX * 2) { - ++bitNumber; // east child - } + var bitNumber = 2; // northwest child + if (childX !== thisX * 2) { + ++bitNumber; // east child + } + if (childY !== thisY * 2) { + bitNumber -= 2; // south child } return (this._childTileMask & (1 << bitNumber)) !== 0; diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index bbc491c574e3..604c0d37686f 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -59,7 +59,9 @@ define([ boundingSphere3D : statistics.boundingSphere3D, orientedBoundingBox : statistics.orientedBoundingBox, occludeePointInScaledSpace : statistics.occludeePointInScaledSpace, - encoding : statistics.encoding + encoding : statistics.encoding, + vertexCountWithoutSkirts : statistics.vertexCountWithoutSkirts, + skirtIndex : statistics.skirtIndex }; } @@ -250,6 +252,9 @@ define([ indicesOffset += facesElementCount; } + var vertexCountWithoutSkirts = pointOffset; + var skirtIndex = indicesOffset; + // Add skirt points var hMin = minHeight; function addSkirt(borderPoints, longitudeFudge, latitudeFudge) { @@ -326,7 +331,9 @@ define([ encoding : encoding, boundingSphere3D : boundingSphere3D, orientedBoundingBox : orientedBoundingBox, - occludeePointInScaledSpace : occludeePointInScaledSpace + occludeePointInScaledSpace : occludeePointInScaledSpace, + vertexCountWithoutSkirts : vertexCountWithoutSkirts, + skirtIndex : skirtIndex }; } diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index ebcc21646e13..13c1d418b414 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -135,7 +135,7 @@ defineSuite([ it('conforms to TerrainData interface', function() { expect(GoogleEarthEnterpriseTerrainData).toConformToInterface(TerrainData); }); -/* + describe('upsample', function() { function findVertexWithCoordinates(uBuffer, vBuffer, u, v) { u *= 32767; @@ -176,35 +176,21 @@ defineSuite([ } it('works for all four children of a simple quad', function() { - var data = new QuantizedMeshTerrainData({ - minimumHeight : 0.0, - maximumHeight : 4.0, - quantizedVertices : new Uint16Array([ // order is sw nw se ne - // u - 0, 0, 32767, 32767, - // v - 0, 32767, 0, 32767, - // heights - 32767 / 4.0, 2.0 * 32767 / 4.0, 3.0 * 32767 / 4.0, 32767 - ]), - indices : new Uint16Array([ - 0, 3, 1, - 0, 2, 3 - ]), - boundingSphere : new BoundingSphere(), - horizonOcclusionPoint : new Cartesian3(), - westIndices : [], - southIndices : [], - eastIndices : [], - northIndices : [], - westSkirtHeight : 1.0, - southSkirtHeight : 1.0, - eastSkirtHeight : 1.0, - northSkirtHeight : 1.0, + var maxShort = 32767; + tilingScheme = new GeographicTilingScheme(); + var buffer = getBuffer(tilingScheme, 0, 0, 0); + var data = new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, childTileMask : 15 }); var tilingScheme = new GeographicTilingScheme(); + var childRectangles = [ + tilingScheme.tileXYToRectangle(0, 0, 1), + tilingScheme.tileXYToRectangle(1, 0, 1), + tilingScheme.tileXYToRectangle(0, 1, 1), + tilingScheme.tileXYToRectangle(1, 1, 1) + ]; return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { var swPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); @@ -222,96 +208,98 @@ defineSuite([ var uBuffer = upsampled._uValues; var vBuffer = upsampled._vValues; var ib = upsampled._indices; + var heights = upsampled._heightValues; - expect(uBuffer.length).toBe(4); - expect(vBuffer.length).toBe(4); - expect(upsampled._heightValues.length).toBe(4); + expect(uBuffer.length).toBe(9); + expect(vBuffer.length).toBe(9); + expect(heights.length).toBe(9); expect(ib.length).toBe(6); - var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); - expect(sw).not.toBe(-1); - var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); - expect(nw).not.toBe(-1); - var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); - expect(se).not.toBe(-1); - var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); - expect(ne).not.toBe(-1); - - var nwToSe = hasTriangle(ib, sw, se, nw) && hasTriangle(ib, nw, se, ne); - var swToNe = hasTriangle(ib, sw, ne, nw) && hasTriangle(ib, sw, se, ne); - expect(nwToSe || swToNe).toBe(true); - } - }); - }); - - it('oct-encoded normals works for all four children of a simple quad', function() { - var data = new QuantizedMeshTerrainData({ - minimumHeight : 0.0, - maximumHeight : 4.0, - quantizedVertices : new Uint16Array([ // order is sw nw se ne - // u - 0, 0, 32767, 32767, - // v - 0, 32767, 0, 32767, - // heights - 32767 / 4.0, 2.0 * 32767 / 4.0, 3.0 * 32767 / 4.0, 32767 - ]), - encodedNormals : new Uint8Array([ - // fun property of oct-encoded normals: the octrahedron is projected onto a plane - // and unfolded into a unit square. The 4 corners of this unit square are encoded values - // of the same Cartesian normal, vec3(0.0, 0.0, 1.0). - // Therefore, all 4 normals below are actually oct-encoded representations of vec3(0.0, 0.0, 1.0) - 255, 0, // sw - 255, 255, // nw - 255, 0, // se - 255, 255 // ne - ]), - indices : new Uint16Array([ - 0, 3, 1, - 0, 2, 3 - ]), - boundingSphere : new BoundingSphere(), - horizonOcclusionPoint : new Cartesian3(), - westIndices : [], - southIndices : [], - eastIndices : [], - northIndices : [], - westSkirtHeight : 1.0, - southSkirtHeight : 1.0, - eastSkirtHeight : 1.0, - northSkirtHeight : 1.0, - childTileMask : 15 - }); - - var tilingScheme = new GeographicTilingScheme(); - - return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { - var swPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); - var sePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 0, 1); - var nwPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 1, 1); - var nePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 1, 1); - return when.join(swPromise, sePromise, nwPromise, nePromise); - }).then(function(upsampleResults) { - expect(upsampleResults.length).toBe(4); - - for (var i = 0; i < upsampleResults.length; ++i) { - var upsampled = upsampleResults[i]; - expect(upsampled).toBeDefined(); - - var encodedNormals = upsampled._encodedNormals; - - expect(encodedNormals.length).toBe(8); + var rectangle = childRectangles[i]; + var north = 0; + var south = 0; + var east = 0; + var west = 0; + var index, u, v, h; + for (var j = 0; j < ib.length; ++j) { + index = ib[j]; + u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; + v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; + if (CesiumMath.equalsEpsilon(u, rectangle.west, CesiumMath.EPSILON7)) { + ++west; + } else if (CesiumMath.equalsEpsilon(u, rectangle.east, CesiumMath.EPSILON7)) { + ++east; + } + + if (CesiumMath.equalsEpsilon(v, rectangle.south, CesiumMath.EPSILON7)) { + ++south; + } else if (CesiumMath.equalsEpsilon(v, rectangle.north, CesiumMath.EPSILON7)) { + ++north; + } + } - // All 4 normals should remain oct-encoded representations of vec3(0.0, 0.0, -1.0) - for (var n = 0; n < encodedNormals.length; ++n) { - expect(encodedNormals[i]).toBe(255); + expect(north).toEqual(3); + expect(south).toEqual(3); + expect(east).toEqual(3); + expect(west).toEqual(3); + + var westIndices = upsampled._westIndices; + for (j = 0; j < westIndices.length; ++j) { + index = westIndices[j]; + u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; + v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; + h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); + console.log(u+'/'+v+'/'+h); + } + console.log(); + + var southIndices = upsampled._southIndices; + for (j = 0; j < southIndices.length; ++j) { + index = southIndices[j]; + u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; + v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; + h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); + console.log(u+'/'+v+'/'+h); + } + console.log(); + + var eastIndices = upsampled._eastIndices; + for (j = 0; j < eastIndices.length; ++j) { + index = eastIndices[j]; + u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; + v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; + h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); + console.log(u+'/'+v+'/'+h); } + console.log(); + + var northIndices = upsampled._northIndices; + for (j = 0; j < northIndices.length; ++j) { + index = northIndices[j]; + u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; + v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; + h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); + console.log(u+'/'+v+'/'+h); + } + + // var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); + // expect(sw).not.toBe(-1); + // var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); + // expect(nw).not.toBe(-1); + // var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); + // expect(se).not.toBe(-1); + // var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); + // expect(ne).not.toBe(-1); + // + // var nwToSe = hasTriangle(ib, sw, se, nw) && hasTriangle(ib, nw, se, ne); + // var swToNe = hasTriangle(ib, sw, ne, nw) && hasTriangle(ib, sw, se, ne); + // expect(nwToSe || swToNe).toBe(true); } }); }); - +/* it('works for a quad with an extra vertex in the northwest child', function() { - var data = new QuantizedMeshTerrainData({ + var data = new GoogleEarthEnterpriseTerrainData({ minimumHeight : 0.0, maximumHeight : 6.0, quantizedVertices : new Uint16Array([ // order is sw, nw, se, ne, extra vertex in nw quadrant @@ -385,7 +373,7 @@ defineSuite([ }); it('works for a quad with an extra vertex on the splitting plane', function() { - var data = new QuantizedMeshTerrainData({ + var data = new GoogleEarthEnterpriseTerrainData({ minimumHeight : 0.0, maximumHeight : 6.0, quantizedVertices : new Uint16Array([ // order is sw, nw, se, ne, extra vertex in nw quadrant @@ -471,8 +459,9 @@ defineSuite([ expect(upsampleResults[1]._southIndices.length).toBe(3); }); }); + */ }); -*/ + describe('createMesh', function() { var data; var tilingScheme; @@ -513,7 +502,7 @@ defineSuite([ it('creates specified vertices plus skirt vertices', function() { var rectangle = tilingScheme.tileXYToRectangle(0, 0, 0); - + var wgs84 = Ellipsoid.WGS84; return data.createMesh(tilingScheme, 0, 0, 0).then(function(mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); @@ -527,15 +516,13 @@ defineSuite([ var cartographic = new Cartographic(); var count = mesh.vertices.length / mesh.encoding.getStride(); for (var i = 0; i < count; ++i) { - encoding.decodePosition(mesh.vertices, i, cartesian); - wgs84.cartesianToCartographic(cartesian, cartographic); - - var height = encoding.decodeHeight(mesh.vertices, i); if (i < 16) { // Original vertices expect(height).toBeBetween(0, 20); // Only test on original positions as the skirts angle outward + encoding.decodePosition(mesh.vertices, i, cartesian); + wgs84.cartesianToCartographic(cartesian, cartographic); cartographic.longitude = CesiumMath.convertLongitudeRange(cartographic.longitude); expect(Rectangle.contains(rectangle,cartographic)).toBe(true); } else { // Skirts @@ -546,59 +533,25 @@ defineSuite([ }); it('exaggerates mesh', function() { - var rectangle = tilingScheme.tileXYToRectangle(0, 0, 0); - var bs = BoundingSphere.fromRectangle3D(rectangle, Ellipsoid.WGS84, 30); return data.createMesh(tilingScheme, 0, 0, 0, 2).then(function(mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); - expect(mesh.vertices.length).toBe(16 * mesh.encoding.getStride()); // 16 regular vertices - expect(mesh.indices.length).toBe(4 * 2 * 3); // 2 regular triangles + expect(mesh.vertices.length).toBe(28 * mesh.encoding.getStride()); // 16 regular + 12 skirt vertices + expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); expect(mesh.maximumHeight).toBeCloseTo(40, 5); - // Bounding spheres of points and whole rectangle should be close - expect(Cartesian3.distance(bs.center, mesh.boundingSphere3D.center)).toBeLessThan(6); - expect(bs.radius - mesh.boundingSphere3D.radius).toBeLessThan(8); + var encoding = mesh.encoding; + var count = mesh.vertices.length / mesh.encoding.getStride(); + for (var i = 0; i < count; ++i) { + var height = encoding.decodeHeight(mesh.vertices, i); + if (i < 16) { // Original vertices + expect(height).toBeBetween(0, 40); + } else { // Skirts + expect(height).toBeBetween(-1000, -960); + } + } }); }); - - // it('requires 32bit indices for large meshes', function() { - // var tilingScheme = new GeographicTilingScheme(); - // var quantizedVertices = []; - // var i; - // for (i = 0; i < 65 * 1024; i++) { - // quantizedVertices.push(i % 32767); // u - // } - // for (i = 0; i < 65 * 1024; i++) { - // quantizedVertices.push(Math.floor(i / 32767)); // v - // } - // for (i = 0; i < 65 * 1024; i++) { - // quantizedVertices.push(0.0); // height - // } - // var data = new QuantizedMeshTerrainData({ - // minimumHeight : 0.0, - // maximumHeight : 4.0, - // quantizedVertices : new Uint16Array(quantizedVertices), - // indices : new Uint32Array([ 0, 3, 1, - // 0, 2, 3, - // 65000, 65002, 65003]), - // boundingSphere : new BoundingSphere(), - // horizonOcclusionPoint : new Cartesian3(), - // westIndices : [0, 1], - // southIndices : [0, 1], - // eastIndices : [2, 3], - // northIndices : [1, 3], - // westSkirtHeight : 1.0, - // southSkirtHeight : 1.0, - // eastSkirtHeight : 1.0, - // northSkirtHeight : 1.0, - // childTileMask : 15 - // }); - // - // return data.createMesh(tilingScheme, 0, 0, 0).then(function(mesh) { - // expect(mesh).toBeInstanceOf(TerrainMesh); - // expect(mesh.indices.BYTES_PER_ELEMENT).toBe(4); - // }); - // }); }); /* From ef659d17cc656d6e5d6807e4b98ca9deadcc8426 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 30 Mar 2017 23:11:01 -0400 Subject: [PATCH 09/60] Tweak. --- .../Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js | 4 ++-- Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 604c0d37686f..4bae9a260114 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -297,9 +297,9 @@ define([ var percentage = 0.00001; addSkirt(westBorder, -percentage*rectangleWidth, 0); - addSkirt(southBorder, 0, -percentage*rectangleWidth); + addSkirt(southBorder, 0, -percentage*rectangleHeight); addSkirt(eastBorder, percentage*rectangleWidth, 0); - addSkirt(northBorder, 0, percentage*rectangleWidth); + addSkirt(northBorder, 0, percentage*rectangleHeight); size = positions.length; // Get new size with skirt vertices diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 13c1d418b414..321fe21cd482 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -238,6 +238,7 @@ defineSuite([ } } + // Each one is made up of 2 triangles expect(north).toEqual(3); expect(south).toEqual(3); expect(east).toEqual(3); From 99f81a30ec02ea72cc8137758128fa18bec7e29d Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 31 Mar 2017 10:55:31 -0400 Subject: [PATCH 10/60] Fixed bit checks. --- Source/Core/GoogleEarthEnterpriseProvider.js | 66 +++++++++++++------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseProvider.js b/Source/Core/GoogleEarthEnterpriseProvider.js index 9429ddf61f25..ef6f0db25ae1 100644 --- a/Source/Core/GoogleEarthEnterpriseProvider.js +++ b/Source/Core/GoogleEarthEnterpriseProvider.js @@ -68,6 +68,10 @@ define([ var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + function isBitSet(bits, mask) { + return ((bits & mask) !== 0); + } + function GoogleEarthEnterpriseDiscardPolicy() { this._image = new Image(); } @@ -484,7 +488,7 @@ define([ var tileInfo = this._tileInfo; var info = tileInfo[quadKey]; if (defined(info)) { - if (info.bits & imageBitmask === 0) { + if (!isBitSet(info.bits, imageBitmask)) { // Already have info and there isn't any imagery here return invalidImage; } @@ -501,8 +505,8 @@ define([ q = q.substring(0, q.length-1); info = tileInfo[q]; if (defined(info)) { - if ((info.bits & cacheFlagBitmask === 0) && - (info.bits & childrenBitmasks[parseInt(last)] === 0)){ + if (!isBitSet(info.bits, cacheFlagBitmask) && + !isBitSet(info.bits, childrenBitmasks[parseInt(last)])){ // We have no subtree or child available at some point in this node's ancestry return invalidImage; } @@ -519,7 +523,7 @@ define([ return populateSubtree(this, quadKey) .then(function(info){ if (defined(info)) { - if (info.bits & imageBitmask !== 0) { + if (isBitSet(info.bits, imageBitmask)) { var url = buildImageUrl(that, quadKey, info.imageryVersion); return loadArrayBuffer(url) .then(function(image) { @@ -598,13 +602,13 @@ define([ //|___|___| // - if ((y & bitmask) === 0) { // Top Row + if (!isBitSet(y, bitmask)) { // Top Row digit |= 2; - if ((x & bitmask) === 0) { // Right to left + if (!isBitSet(x, bitmask)) { // Right to left digit |= 1; } } else { - if ((x & bitmask) !== 0) { // Left to right + if (isBitSet(x, bitmask)) { // Left to right digit |= 1; } } @@ -630,13 +634,13 @@ define([ var bitmask = 1 << i; var digit = +quadkey[level - i]; - if ((digit & 2) !== 0) { // Top Row - if ((digit & 1) === 0) { // // Right to left + if (isBitSet(digit, 2)) { // Top Row + if (!isBitSet(digit, 1)) { // // Right to left x |= bitmask; } } else { y |= bitmask; - if ((digit & 1) !== 0) { // Left to right + if (isBitSet(digit, 1)) { // Left to right x |= bitmask; } } @@ -673,18 +677,19 @@ define([ } //>>includeEnd('debug'); + var bHasTerrain = true; var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); var tileInfo = this._tileInfo; var info = tileInfo[quadKey]; if (defined(info)) { - if ((info.bits & terrainBitmask === 0) && !info.terrainInParent){ - // Already have info and there isn't any imagery here - return undefined; + if (!isBitSet(info.bits, terrainBitmask) && !info.terrainInParent){ + // Already have info and there isn't any terrain here + bHasTerrain = false; } } else { if (info === null) { // Parent was retrieved and said child doesn't exist - return undefined; + bHasTerrain = false; } var q = quadKey; @@ -694,20 +699,33 @@ define([ q = q.substring(0, q.length-1); info = tileInfo[q]; if (defined(info)) { - if ((info.bits & cacheFlagBitmask === 0) && - (info.bits & childrenBitmasks[parseInt(last)] === 0)){ + if (!isBitSet(info.bits, cacheFlagBitmask) && + !isBitSet(info.bits, childrenBitmasks[parseInt(last)])){ // We have no subtree or child available at some point in this node's ancestry - return undefined; + bHasTerrain = false; } break; } else if (info === null) { // Some node in the ancestry was loaded and said there wasn't a subtree - return undefined; + bHasTerrain = false; } } } + if (!bHasTerrain) { + if(defined(info) && !info.ancestorHasTerrain) { + // We haven't reached a level with terrain, so return the ellipsoid + return new HeightmapTerrainData({ + buffer : new Uint8Array(16 * 16), + width : 16, + height : 16 + }); + } + + return undefined; + } + var that = this; var terrainCache = this._terrainCache; @@ -722,7 +740,7 @@ define([ childTileMask: info.bits & anyChildBitmask }); } - if ((info.bits & terrainBitmask) !== 0 || info.terrainInParent) { + if (isBitSet(info.bits, terrainBitmask) || info.terrainInParent) { var q = quadKey; if (info.terrainInParent) { // If terrain is in parent tile, process that instead @@ -796,7 +814,7 @@ define([ terrainCache[quadKey] = terrainTiles[0]; var count = terrainTiles.length-1; for (var j = 0;j Date: Fri, 31 Mar 2017 14:14:55 -0400 Subject: [PATCH 11/60] More cleanup for terrain. Should even work if a child is loaded before a parent. --- Source/Core/GoogleEarthEnterpriseProvider.js | 137 ++++++++++++------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseProvider.js b/Source/Core/GoogleEarthEnterpriseProvider.js index ef6f0db25ae1..631a5fe66b80 100644 --- a/Source/Core/GoogleEarthEnterpriseProvider.js +++ b/Source/Core/GoogleEarthEnterpriseProvider.js @@ -76,6 +76,13 @@ define([ this._image = new Image(); } + var TerrainState = { + UNKNOWN : 0, + NONE : 1, + SELF : 2, + PARENT : 3 + }; + /** * Determines if the discard policy is ready to process images. * @returns {Boolean} True if the discard policy is ready to process images; otherwise, false. @@ -677,43 +684,48 @@ define([ } //>>includeEnd('debug'); - var bHasTerrain = true; + var hasTerrain = true; var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); + var terrainCache = this._terrainCache; var tileInfo = this._tileInfo; var info = tileInfo[quadKey]; - if (defined(info)) { - if (!isBitSet(info.bits, terrainBitmask) && !info.terrainInParent){ - // Already have info and there isn't any terrain here - bHasTerrain = false; - } - } else { - if (info === null) { - // Parent was retrieved and said child doesn't exist - bHasTerrain = false; - } - var q = quadKey; - var last; - while(q.length > 1) { - last = q.substring(q.length-1); - q = q.substring(0, q.length-1); - info = tileInfo[q]; - if (defined(info)) { - if (!isBitSet(info.bits, cacheFlagBitmask) && - !isBitSet(info.bits, childrenBitmasks[parseInt(last)])){ - // We have no subtree or child available at some point in this node's ancestry - bHasTerrain = false; - } + if (!defined(terrainCache[quadKey])) { // If its in the cache we know we have it so just skip all the checks + if (defined(info)) { + if (info.terrainState === TerrainState.NONE) { + // Already have info and there isn't any terrain here + hasTerrain = false; + } + } else { + if (info === null) { + // Parent was retrieved and said child doesn't exist + hasTerrain = false; + } - break; - } else if (info === null) { - // Some node in the ancestry was loaded and said there wasn't a subtree - bHasTerrain = false; + var q = quadKey; + var last; + while (q.length > 1) { + last = q.substring(q.length - 1); + q = q.substring(0, q.length - 1); + info = tileInfo[q]; + if (defined(info)) { + if (!isBitSet(info.bits, cacheFlagBitmask) && + !isBitSet(info.bits, childrenBitmasks[parseInt(last)])) { + // We have no subtree or child available at some point in this node's ancestry + hasTerrain = false; + } + + break; + } else if (info === null) { + // Some node in the ancestry was loaded and said there wasn't a subtree + hasTerrain = false; + break; + } } } } - if (!bHasTerrain) { + if (!hasTerrain) { if(defined(info) && !info.ancestorHasTerrain) { // We haven't reached a level with terrain, so return the ellipsoid return new HeightmapTerrainData({ @@ -728,11 +740,14 @@ define([ var that = this; - var terrainCache = this._terrainCache; return populateSubtree(this, quadKey) .then(function(info){ if (defined(info)) { if (defined(terrainCache[quadKey])) { + if (info.terrainState === TerrainState.UNKNOWN) { + // If its already in the cache then a parent request must've loaded it + info.terrainState = TerrainState.PARENT; + } var buffer = terrainCache[quadKey]; delete terrainCache[quadKey]; return new GoogleEarthEnterpriseTerrainData({ @@ -740,13 +755,35 @@ define([ childTileMask: info.bits & anyChildBitmask }); } - if (isBitSet(info.bits, terrainBitmask) || info.terrainInParent) { - var q = quadKey; - if (info.terrainInParent) { - // If terrain is in parent tile, process that instead - q = q.substring(0, q.length-1); + + var q = quadKey; + var terrainVersion = -1; + var terrainState = info.terrainState; + if (terrainState !== TerrainState.NONE) { + switch(terrainState) { + case TerrainState.SELF: // We have terrain and have retrieved it before + terrainVersion = info.terrainVersion; + break; + case TerrainState.PARENT: // We have terrain in our parent + q = q.substring(0, q.length-1); + terrainVersion = tileInfo[q].terrainVersion; + break; + case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet + if (isBitSet(info.bits, terrainBitmask)) { + terrainVersion = info.terrainVersion; // We should have terrain + } else { + q = q.substring(0, q.length-1); + var parentInfo = tileInfo[q]; + if (defined(parentInfo) && isBitSet(parentInfo.bits, terrainBitmask)) { + terrainVersion = parentInfo.terrainVersion; // Try checking in the parent + } + } + break; } - var url = buildTerrainUrl(that, q, info.terrainVersion); + } + + if (terrainVersion > 0) { + var url = buildTerrainUrl(that, q, terrainVersion); var promise; throttleRequests = defaultValue(throttleRequests, true); if (throttleRequests) { @@ -766,15 +803,23 @@ define([ parseTerrainPacket(that, uncompressedTerrain, q); var buffer = terrainCache[quadKey]; - delete terrainCache[quadKey]; - return new GoogleEarthEnterpriseTerrainData({ - buffer: buffer, - childTileMask: info.bits & anyChildBitmask - }); + if (defined(buffer)) { + if (q !== quadKey) { + // If we didn't request this tile directly then it came from a parent + info.terrainState = TerrainState.PARENT; + } + delete terrainCache[quadKey]; + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : info.bits & anyChildBitmask + }); + } else { + info.terrainState = TerrainState.NONE; + } } }) .otherwise(function(error) { - // Just ignore failures and return undefined + info.terrainState = TerrainState.NONE; }); } else if(!info.ancestorHasTerrain) { // We haven't reached a level with terrain, so return the ellipsoid @@ -812,12 +857,12 @@ define([ // If we were sent child tiles, store them till they are needed terrainCache[quadKey] = terrainTiles[0]; + info.terrainState = TerrainState.SELF; var count = terrainTiles.length-1; - for (var j = 0;j Date: Fri, 31 Mar 2017 15:58:43 -0400 Subject: [PATCH 12/60] Added back cnodeVersion. --- Source/Core/GoogleEarthEnterpriseProvider.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseProvider.js b/Source/Core/GoogleEarthEnterpriseProvider.js index 631a5fe66b80..0ec6a10e9b88 100644 --- a/Source/Core/GoogleEarthEnterpriseProvider.js +++ b/Source/Core/GoogleEarthEnterpriseProvider.js @@ -995,9 +995,10 @@ define([ } // Requests quadtree packet and populates _tileInfo with results - GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket = function(quadKey) { + GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket = function(quadKey, version) { + version = defaultValue(version, 1); quadKey = defaultValue(quadKey, ''); - var url = this._url + 'flatfile?q2-0' + quadKey + '-q.2'; + var url = this._url + 'flatfile?q2-0' + quadKey + '-q.' + version.toString(); var proxy = this._proxy; if (defined(proxy)) { url = proxy.getURL(url); @@ -1064,9 +1065,8 @@ define([ ++offset; ++offset; // 2 byte align - - // We only support version 2 which we verified above this tile already is - //var cnodeVersion = dv.getUint16(offset, true); + + var cnodeVersion = dv.getUint16(offset, true); offset += sizeOfUint16; var imageVersion = dv.getUint16(offset, true); @@ -1098,6 +1098,7 @@ define([ instances.push({ bits : bitfield, + cnodeVersion : cnodeVersion, imageryVersion : imageVersion, terrainVersion : terrainVersion, ancestorHasTerrain : false, // Set it later once we find its parent @@ -1196,7 +1197,7 @@ define([ // is already resolved (like in the tests), so subtreePromises will never get cleared out. // The promise will always resolve with a bool, but the initial request will also remove // the promise from subtreePromises. - promise = subtreePromises[q] = that._getQuadTreePacket(q); + promise = subtreePromises[q] = that._getQuadTreePacket(q, t.cnodeVersion); return promise .then(function() { From 842d9c42f47010534d437327866955fef4aa7120 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 31 Mar 2017 21:01:36 -0400 Subject: [PATCH 13/60] Added first try at removing dups along quad borders. Missing triangles are showing up which need to be looked at. --- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 4bae9a260114..94bf50b2b17b 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -5,6 +5,7 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartographic', + '../Core/defaultValue', '../Core/defined', '../Core/Ellipsoid', '../Core/EllipsoidalOccluder', @@ -21,6 +22,7 @@ define([ Cartesian2, Cartesian3, Cartographic, + defaultValue, defined, Ellipsoid, EllipsoidalOccluder, @@ -39,6 +41,18 @@ define([ var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; + function indexOfEpsilon(arr, elem, elemType) { + elemType = defaultValue(elemType, CesiumMath); + var count = arr.length; + for (var i = 0; i < count; ++i) { + if (elemType.equalsEpsilon(arr[i], elem, CesiumMath.EPSILON12)) { + return i; + } + } + + return -1; + } + function createVerticesFromGoogleEarthEnterpriseBuffer(parameters, transferableObjects) { parameters.ellipsoid = Ellipsoid.clone(parameters.ellipsoid); parameters.rectangle = Rectangle.clone(parameters.rectangle); @@ -95,6 +109,10 @@ define([ rectangleHeight = rectangle.height; } + // Keep track of quad borders so we can remove duplicates around the borders + var quadBorderLatitudes = [geographicSouth, geographicNorth]; + var quadBorderLongitudes = [geographicWest, geographicEast]; + var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid); var toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch); @@ -118,10 +136,23 @@ define([ var size = 0; var indicesSize = 0; for (var quad = 0; quad < 4; ++quad) { - var quadSize = dv.getUint32(offset, true); - var o = offset; - o += sizeOfUint32 + 4*sizeOfDouble; // size + originX + originY + stepX + stepY + var quadSize = dv.getUint32(o, true); + o += sizeOfUint32; + + var x = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0); + o += sizeOfDouble; + if (indexOfEpsilon(quadBorderLongitudes, x) === -1) { + quadBorderLongitudes.push(x); + } + + var y = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0); + o += sizeOfDouble; + if (indexOfEpsilon(quadBorderLatitudes, y) === -1) { + quadBorderLatitudes.push(y); + } + + o += 2*sizeOfDouble; // stepX + stepY var c = dv.getInt32(o, true); // Read point count o += sizeOfInt32; @@ -133,6 +164,10 @@ define([ offset += quadSize + sizeOfUint32; // Jump to next quad } + // Quad Border points to remove duplicates + var quadBorderPoints = []; + var quadBorderIndices = []; + // Create arrays var positions = new Array(size); var uvs = new Array(size); @@ -177,8 +212,11 @@ define([ //var level = dv.getInt32(offset, true); offset += sizeOfInt32; - var index = pointOffset; - for (var i = 0; i < numPoints; ++i, ++index) { + var indicesMapping = []; + for (var i = 0; i < numPoints; ++i) { + // Keep track of quad indices to overall tile indices + indicesMapping.length = numPoints; + var longitude = originX + dv.getUint8(offset++) * stepX; scratchCartographic.longitude = longitude; var latitude = originY + dv.getUint8(offset++) * stepY; @@ -197,36 +235,50 @@ define([ scratchCartographic.height = height; + // Is it along a quad border - if so check if already exists and use that index + if (indexOfEpsilon(quadBorderLongitudes, longitude) !== -1 || + indexOfEpsilon(quadBorderLatitudes, latitude) !== -1) { + var index = indexOfEpsilon(quadBorderPoints, scratchCartographic, Cartographic); + if (index === -1) { + quadBorderPoints.push(Cartographic.clone(scratchCartographic)); + quadBorderIndices.push(pointOffset); + } else { + indicesMapping[i] = index; + continue; + } + } + indicesMapping[i] = pointOffset; + if (Math.abs(longitude - geographicWest) < halfStepX) { westBorder.push({ - index: index, + index: pointOffset, cartographic: Cartographic.clone(scratchCartographic) }); } else if(Math.abs(longitude - geographicEast) < halfStepX) { eastBorder.push({ - index: index, + index: pointOffset, cartographic: Cartographic.clone(scratchCartographic) }); } if (Math.abs(latitude - geographicSouth) < halfStepY) { southBorder.push({ - index: index, + index: pointOffset, cartographic: Cartographic.clone(scratchCartographic) }); } else if(Math.abs(latitude - geographicNorth) < halfStepY) { northBorder.push({ - index: index, + index: pointOffset, cartographic: Cartographic.clone(scratchCartographic) }); } minHeight = Math.min(height, minHeight); maxHeight = Math.max(height, maxHeight); - heights[index] = height; + heights[pointOffset] = height; var pos = ellipsoid.cartographicToCartesian(scratchCartographic); - positions[index] = pos; + positions[pointOffset] = pos; Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); @@ -238,20 +290,21 @@ define([ var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth); v = CesiumMath.clamp(v, 0.0, 1.0); - uvs[index] = new Cartesian2(u, v); + uvs[pointOffset] = new Cartesian2(u, v); + ++pointOffset; } - index = indicesOffset; var facesElementCount = numFaces * 3; - for (i = 0; i < facesElementCount; ++i, ++index) { - indices[index] = pointOffset + dv.getUint16(offset, true); + for (i = 0; i < facesElementCount; ++i, ++indicesOffset) { + indices[indicesOffset] = indicesMapping[dv.getUint16(offset, true)]; offset += sizeOfUint16; } - - pointOffset += numPoints; - indicesOffset += facesElementCount; } + positions.length = pointOffset; + uvs.length = pointOffset; + heights.length = pointOffset; + var vertexCountWithoutSkirts = pointOffset; var skirtIndex = indicesOffset; From e4648da3e282a26877d862659179ebf389300a4c Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 31 Mar 2017 21:59:49 -0400 Subject: [PATCH 14/60] Fixed holes in mesh and unit tests. Still need to remove duplicate corner skirt vertices. --- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 8 +-- .../GoogleEarthEnterpriseTerrainDataSpec.js | 72 ++++--------------- 2 files changed, 16 insertions(+), 64 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 94bf50b2b17b..6eb2e05845c2 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -212,11 +212,9 @@ define([ //var level = dv.getInt32(offset, true); offset += sizeOfInt32; - var indicesMapping = []; + // Keep track of quad indices to overall tile indices + var indicesMapping = new Array(numPoints); for (var i = 0; i < numPoints; ++i) { - // Keep track of quad indices to overall tile indices - indicesMapping.length = numPoints; - var longitude = originX + dv.getUint8(offset++) * stepX; scratchCartographic.longitude = longitude; var latitude = originY + dv.getUint8(offset++) * stepY; @@ -243,7 +241,7 @@ define([ quadBorderPoints.push(Cartographic.clone(scratchCartographic)); quadBorderIndices.push(pointOffset); } else { - indicesMapping[i] = index; + indicesMapping[i] = quadBorderIndices[index]; continue; } } diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 321fe21cd482..8cef4a6c6724 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -210,9 +210,9 @@ defineSuite([ var ib = upsampled._indices; var heights = upsampled._heightValues; - expect(uBuffer.length).toBe(9); - expect(vBuffer.length).toBe(9); - expect(heights.length).toBe(9); + expect(uBuffer.length).toBe(4); + expect(vBuffer.length).toBe(4); + expect(heights.length).toBe(4); expect(ib.length).toBe(6); var rectangle = childRectangles[i]; @@ -244,57 +244,11 @@ defineSuite([ expect(east).toEqual(3); expect(west).toEqual(3); - var westIndices = upsampled._westIndices; - for (j = 0; j < westIndices.length; ++j) { - index = westIndices[j]; - u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; - v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; - h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); - console.log(u+'/'+v+'/'+h); - } - console.log(); - - var southIndices = upsampled._southIndices; - for (j = 0; j < southIndices.length; ++j) { - index = southIndices[j]; - u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; - v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; - h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); - console.log(u+'/'+v+'/'+h); - } - console.log(); - - var eastIndices = upsampled._eastIndices; - for (j = 0; j < eastIndices.length; ++j) { - index = eastIndices[j]; - u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; - v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; - h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); - console.log(u+'/'+v+'/'+h); - } - console.log(); - - var northIndices = upsampled._northIndices; - for (j = 0; j < northIndices.length; ++j) { - index = northIndices[j]; - u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; - v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; - h = CesiumMath.lerp(upsampled._minimumHeight, upsampled._maximumHeight, heights[index] / maxShort); - console.log(u+'/'+v+'/'+h); - } - - // var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); - // expect(sw).not.toBe(-1); - // var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); - // expect(nw).not.toBe(-1); - // var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); - // expect(se).not.toBe(-1); - // var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); - // expect(ne).not.toBe(-1); - // - // var nwToSe = hasTriangle(ib, sw, se, nw) && hasTriangle(ib, nw, se, ne); - // var swToNe = hasTriangle(ib, sw, ne, nw) && hasTriangle(ib, sw, se, ne); - // expect(nwToSe || swToNe).toBe(true); + // Each side of quad has 2 edge points + expect(upsampled._westIndices.length).toEqual(2); + expect(upsampled._southIndices.length).toEqual(2); + expect(upsampled._eastIndices.length).toEqual(2); + expect(upsampled._northIndices.length).toEqual(2); } }); }); @@ -466,7 +420,7 @@ defineSuite([ describe('createMesh', function() { var data; var tilingScheme; - var buffer + var buffer; beforeEach(function() { tilingScheme = new GeographicTilingScheme(); @@ -507,7 +461,7 @@ defineSuite([ var wgs84 = Ellipsoid.WGS84; return data.createMesh(tilingScheme, 0, 0, 0).then(function(mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); - expect(mesh.vertices.length).toBe(28 * mesh.encoding.getStride()); // 16 regular + 12 skirt vertices + expect(mesh.vertices.length).toBe(21 * mesh.encoding.getStride()); // 9 regular + 12 skirt vertices expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); expect(mesh.maximumHeight).toBeCloseTo(20, 5); @@ -518,7 +472,7 @@ defineSuite([ var count = mesh.vertices.length / mesh.encoding.getStride(); for (var i = 0; i < count; ++i) { var height = encoding.decodeHeight(mesh.vertices, i); - if (i < 16) { // Original vertices + if (i < 9) { // Original vertices expect(height).toBeBetween(0, 20); // Only test on original positions as the skirts angle outward @@ -536,7 +490,7 @@ defineSuite([ it('exaggerates mesh', function() { return data.createMesh(tilingScheme, 0, 0, 0, 2).then(function(mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); - expect(mesh.vertices.length).toBe(28 * mesh.encoding.getStride()); // 16 regular + 12 skirt vertices + expect(mesh.vertices.length).toBe(21 * mesh.encoding.getStride()); // 9 regular + 12 skirt vertices expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); expect(mesh.maximumHeight).toBeCloseTo(40, 5); @@ -545,7 +499,7 @@ defineSuite([ var count = mesh.vertices.length / mesh.encoding.getStride(); for (var i = 0; i < count; ++i) { var height = encoding.decodeHeight(mesh.vertices, i); - if (i < 16) { // Original vertices + if (i < 9) { // Original vertices expect(height).toBeBetween(0, 40); } else { // Skirts expect(height).toBeBetween(-1000, -960); From 89bd3df107b77751dbdf3a1ff600647b80ba97f7 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 31 Mar 2017 22:37:50 -0400 Subject: [PATCH 15/60] Removed duplicate vertices from skirts. --- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 58 +++++++++++++------ .../GoogleEarthEnterpriseTerrainDataSpec.js | 4 +- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 6eb2e05845c2..2fb419da94f5 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -308,24 +308,42 @@ define([ // Add skirt points var hMin = minHeight; + quadBorderPoints = []; + quadBorderIndices = []; function addSkirt(borderPoints, longitudeFudge, latitudeFudge) { var count = borderPoints.length; var lastBorderPoint; for (var j = 0; j < count; ++j) { var borderPoint = borderPoints[j]; var borderCartographic = borderPoint.cartographic; - if (!defined(lastBorderPoint) || - !Cartographic.equalsEpsilon(borderCartographic, lastBorderPoint.cartographic, CesiumMath.EPSILON7)) { - var borderIndex = borderPoint.index; - var currentIndex = positions.length; - - var longitude = borderCartographic.longitude + longitudeFudge; - var latitude = borderCartographic.latitude + latitudeFudge; - latitude = CesiumMath.clamp(latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); // Don't go over the poles - var height = borderCartographic.height - skirtHeight; - hMin = Math.min(hMin, height); - - Cartographic.fromRadians(longitude, latitude, height, scratchCartographic); + var borderIndex = borderPoint.index; + var currentIndex = positions.length; + + var longitude = borderCartographic.longitude; + var latitude = borderCartographic.latitude; + latitude = CesiumMath.clamp(latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); // Don't go over the poles + var height = borderCartographic.height - skirtHeight; + hMin = Math.min(hMin, height); + + Cartographic.fromRadians(longitude, latitude, height, scratchCartographic); + + // Detect duplicates at the corners and only had the first one + // We detect based on not fudged points so a corner point is fudged in both directions + var add = true; + var index = indexOfEpsilon(quadBorderPoints, scratchCartographic, Cartographic); + if (index === -1) { + quadBorderPoints.push(Cartographic.clone(scratchCartographic)); + quadBorderIndices.push(currentIndex); + } else { + currentIndex = quadBorderIndices[index]; + ellipsoid.cartesianToCartographic(positions[currentIndex], scratchCartographic); + add = false; + } + + scratchCartographic.longitude += longitudeFudge; + scratchCartographic.latitude += latitudeFudge; + + if (add) { var pos = ellipsoid.cartographicToCartesian(scratchCartographic); positions.push(pos); heights.push(height); @@ -335,17 +353,21 @@ define([ Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum); + } else { + // We found a corner point - it was adjusted for this side so write it back + ellipsoid.cartographicToCartesian(scratchCartographic, positions[currentIndex]); + } - if (defined(lastBorderPoint)) { - var lastBorderIndex = lastBorderPoint.index; - indices.push(lastBorderIndex, currentIndex - 1, currentIndex, currentIndex, borderIndex, lastBorderIndex); - } - - lastBorderPoint = borderPoint; + if (defined(lastBorderPoint)) { + var lastBorderIndex = lastBorderPoint.index; + indices.push(lastBorderIndex, currentIndex - 1, currentIndex, currentIndex, borderIndex, lastBorderIndex); } + + lastBorderPoint = borderPoint; } } + debugger; var percentage = 0.00001; addSkirt(westBorder, -percentage*rectangleWidth, 0); addSkirt(southBorder, 0, -percentage*rectangleHeight); diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 8cef4a6c6724..77a348f20893 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -461,7 +461,7 @@ defineSuite([ var wgs84 = Ellipsoid.WGS84; return data.createMesh(tilingScheme, 0, 0, 0).then(function(mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); - expect(mesh.vertices.length).toBe(21 * mesh.encoding.getStride()); // 9 regular + 12 skirt vertices + expect(mesh.vertices.length).toBe(17 * mesh.encoding.getStride()); // 9 regular + 8 skirt vertices expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); expect(mesh.maximumHeight).toBeCloseTo(20, 5); @@ -490,7 +490,7 @@ defineSuite([ it('exaggerates mesh', function() { return data.createMesh(tilingScheme, 0, 0, 0, 2).then(function(mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); - expect(mesh.vertices.length).toBe(21 * mesh.encoding.getStride()); // 9 regular + 12 skirt vertices + expect(mesh.vertices.length).toBe(17 * mesh.encoding.getStride()); // 9 regular + 8 skirt vertices expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); expect(mesh.maximumHeight).toBeCloseTo(40, 5); From a5119c414dc5ac4ffeacce6956647e6dd097cda2 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 31 Mar 2017 22:41:09 -0400 Subject: [PATCH 16/60] Fixed jshint. --- .../Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js | 1 - Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 2fb419da94f5..b40065779af7 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -367,7 +367,6 @@ define([ } } - debugger; var percentage = 0.00001; addSkirt(westBorder, -percentage*rectangleWidth, 0); addSkirt(southBorder, 0, -percentage*rectangleHeight); diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 77a348f20893..054985c337c0 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -137,6 +137,7 @@ defineSuite([ }); describe('upsample', function() { +/* function findVertexWithCoordinates(uBuffer, vBuffer, u, v) { u *= 32767; u |= 0; @@ -174,6 +175,7 @@ defineSuite([ function verticalIntercept(u1, v1, u2, v2) { return intercept(u1, u2, v1, v2); } +*/ it('works for all four children of a simple quad', function() { var maxShort = 32767; @@ -220,7 +222,7 @@ defineSuite([ var south = 0; var east = 0; var west = 0; - var index, u, v, h; + var index, u, v; for (var j = 0; j < ib.length; ++j) { index = ib[j]; u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; From 917f0874137470048c80a9756f46ae1b047ba671 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 31 Mar 2017 23:38:27 -0400 Subject: [PATCH 17/60] Fixed tests. --- Specs/Core/GoogleEarthEnterpriseProviderSpec.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Specs/Core/GoogleEarthEnterpriseProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseProviderSpec.js index da8019c2e4eb..247f06bea83e 100644 --- a/Specs/Core/GoogleEarthEnterpriseProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseProviderSpec.js @@ -89,6 +89,7 @@ defineSuite([ quadKey = defaultValue(quadKey, '') + index.toString(); this._tileInfo[quadKey] = { bits : 0xFF, + cnodeVersion : 1, imageryVersion : 1, terrainVersion : 1 }; @@ -97,13 +98,9 @@ defineSuite([ return when(); }); - var count = 0; - var requestQuads = [GoogleEarthEnterpriseProvider.tileXYToQuadKey(0, 0, 1), quad]; - var requestType = ['blob', 'arraybuffer']; spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { - expect(url).toEqual('http://test.server/3d/flatfile?f1-0' + requestQuads[count] + '-i.1'); - expect(responseType).toEqual(requestType[count]); - ++count; + expect(url).toEqual('http://test.server/3d/flatfile?f1-0' + quad + '-i.1'); + expect(responseType).toEqual('arraybuffer'); deferred.resolve(); }); @@ -116,9 +113,9 @@ defineSuite([ .then(function(image) { expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket.calls.count()).toEqual(4); expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith(); - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('0'); - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('01'); - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('012'); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('0', 1); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('01', 1); + expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('012', 1); var tileInfo = provider._tileInfo; expect(tileInfo['0']).toBeDefined(); From a8b312a97d8a91859f7cf87d37a049c7b187fc51 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Sat, 1 Apr 2017 09:58:19 -0400 Subject: [PATCH 18/60] Removed credit and added sandcastle example. --- .../gallery/Google Earth Enterprise.html | 54 ++++++++++++++++++ .../gallery/Google Earth Enterprise.jpg | Bin 0 -> 83688 bytes Source/Core/GoogleEarthEnterpriseProvider.js | 9 +-- 3 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 Apps/Sandcastle/gallery/Google Earth Enterprise.html create mode 100644 Apps/Sandcastle/gallery/Google Earth Enterprise.jpg diff --git a/Apps/Sandcastle/gallery/Google Earth Enterprise.html b/Apps/Sandcastle/gallery/Google Earth Enterprise.html new file mode 100644 index 000000000000..96e204f8f0a4 --- /dev/null +++ b/Apps/Sandcastle/gallery/Google Earth Enterprise.html @@ -0,0 +1,54 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Google Earth Enterprise.jpg b/Apps/Sandcastle/gallery/Google Earth Enterprise.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d635cf88088c43a0d52b0e7da136cbae5edff97 GIT binary patch literal 83688 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 z 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 z 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�QoJ1#xL!nUOn4|2&Ai8HZWEH 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 Date: Mon, 3 Apr 2017 16:13:24 -0400 Subject: [PATCH 19/60] Split apart Terrain and Imagery providers. --- .../gallery/Google Earth Enterprise.html | 18 +- Source/Core/GoogleEarthEnterpriseMetadata.js | 609 ++++++++ Source/Core/GoogleEarthEnterpriseProvider.js | 1318 ----------------- .../GoogleEarthEnterpriseTerrainProvider.js | 488 ++++++ .../GoogleEarthEnterpriseImageryProvider.js | 599 ++++++++ 5 files changed, 1706 insertions(+), 1326 deletions(-) create mode 100644 Source/Core/GoogleEarthEnterpriseMetadata.js delete mode 100644 Source/Core/GoogleEarthEnterpriseProvider.js create mode 100644 Source/Core/GoogleEarthEnterpriseTerrainProvider.js create mode 100644 Source/Scene/GoogleEarthEnterpriseImageryProvider.js diff --git a/Apps/Sandcastle/gallery/Google Earth Enterprise.html b/Apps/Sandcastle/gallery/Google Earth Enterprise.html index 96e204f8f0a4..7a95c37834bc 100644 --- a/Apps/Sandcastle/gallery/Google Earth Enterprise.html +++ b/Apps/Sandcastle/gallery/Google Earth Enterprise.html @@ -27,21 +27,23 @@ function startup(Cesium) { 'use strict'; //Sandcastle_Begin -var provider = new Cesium.GoogleEarthEnterpriseProvider({ - url : 'http://www.earthenterprise.org/3d', - proxy : new Cesium.DefaultProxy('/proxy/') -}); - var viewer = new Cesium.Viewer('cesiumContainer', { - imageryProvider : provider, - terrainProvider : provider, + imageryProvider : new Cesium.GoogleEarthEnterpriseImageryProvider({ + url : 'http://www.earthenterprise.org/3d', + proxy : new Cesium.DefaultProxy('/proxy/') + }), + terrainProvider : new Cesium.GoogleEarthEnterpriseTerrainProvider({ + url : 'http://www.earthenterprise.org/3d', + proxy : new Cesium.DefaultProxy('/proxy/') + }), baseLayerPicker : false }); // Start off looking at San Francisco. viewer.camera.setView({ destination: Cesium.Rectangle.fromDegrees(-123.0, 36.0, -121.7, 39.0) -});//Sandcastle_End +}); +//Sandcastle_End Sandcastle.finishedLoading(); } if (typeof Cesium !== "undefined") { diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js new file mode 100644 index 000000000000..1c9805bd4fd6 --- /dev/null +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -0,0 +1,609 @@ +/*global define*/ +define([ + './appendForwardSlash', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './loadArrayBuffer', + './RuntimeError', + '../ThirdParty/pako_inflate', + '../ThirdParty/when' +], function( + appendForwardSlash, + defaultValue, + defined, + defineProperties, + DeveloperError, + loadArrayBuffer, + RuntimeError, + pako, + when) { + 'use strict'; + + // Bitmask for checking tile properties + var childrenBitmasks = [0x01, 0x02, 0x04, 0x08]; + var anyChildBitmask = 0x0F; + var cacheFlagBitmask = 0x10; // True if there is a child subtree + //var vectorDataBitmask = 0x20; + var imageBitmask = 0x40; + var terrainBitmask = 0x80; + + // Datatype sizes + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + + function isBitSet(bits, mask) { + return ((bits & mask) !== 0); + } + + function GoogleEarthEnterpriseTileInformation(bits, cnodeVersion, imageryVersion, terrainVersion) { + this._bits = bits; + this.cnodeVersion = cnodeVersion; + this.imageryVersion = imageryVersion; + this.terrainVersion = terrainVersion; + this.ancestorHasTerrain = false; // Set it later once we find its parent + this.terrainState = 0; // UNKNOWN + } + + GoogleEarthEnterpriseTileInformation.prototype.setParent = function(parent) { + this.ancestorHasTerrain = parent.ancestorHasTerrain || this.hasTerrain(); + }; + + GoogleEarthEnterpriseTileInformation.prototype.hasSubtree = function() { + return isBitSet(this._bits, cacheFlagBitmask); + }; + + GoogleEarthEnterpriseTileInformation.prototype.hasImagery = function() { + return isBitSet(this._bits, imageBitmask); + }; + + GoogleEarthEnterpriseTileInformation.prototype.hasTerrain = function() { + return isBitSet(this._bits, terrainBitmask); + }; + + GoogleEarthEnterpriseTileInformation.prototype.hasChildren = function() { + return isBitSet(this._bits, anyChildBitmask); + }; + + GoogleEarthEnterpriseTileInformation.prototype.hasChild = function(index) { + return isBitSet(this._bits, childrenBitmasks[index]); + }; + + GoogleEarthEnterpriseTileInformation.prototype.getChildBitmask = function(index) { + return this._bits && anyChildBitmask; + }; + + var metadata = {}; + + GoogleEarthEnterpriseMetadata.getMetadata = function(url, proxy) { + //>>includeStart('debug', pragmas.debug); + if (!defined(url)) { + throw new DeveloperError('url is required.'); + } + //>>includeEnd('debug'); + + url = appendForwardSlash(url); + + var result = metadata[url]; + if (defined(metadata[url])) { + ++result.refCount; + return result; + } + + result = new GoogleEarthEnterpriseMetadata(url, proxy); + metadata[url] = result; + + return result; + }; + + GoogleEarthEnterpriseMetadata.releaseMetadata = function(metadataObj) { + //>>includeStart('debug', pragmas.debug); + if (!defined(metadataObj)) { + throw new DeveloperError('metadataObj is required.'); + } + //>>includeEnd('debug'); + + --metadataObj.refCount; + if (metadataObj.refCount === 0) { + delete metadata[metadataObj.url]; + } + }; + + /** + * Provides metadata using the Google Earth Enterprise REST API. This is used by the + * + * @alias GoogleEarthEnterpriseMetadata + * @constructor + * + * @param {String} url The url of the Google Earth Enterprise server hosting the imagery. + * @param {Proxy} [proxy] A proxy to use for requests. This object is + * expected to have a getURL function which returns the proxied URL, if needed. + * + * @see GoogleEarthEnterpriseImageryProvider + * @see GoogleEarthEnterpriseTerrainProvider + * + * @private + */ + function GoogleEarthEnterpriseMetadata(url, proxy) { + //>>includeStart('debug', pragmas.debug); + if (!defined(url)) { + throw new DeveloperError('url is required.'); + } + //>>includeEnd('debug'); + + this._url = url; + this._proxy = proxy; + + this._tileInfo = {}; + this._subtreePromises = {}; + + this.refCount = 1; + + this._readyPromise = this._getQuadTreePacket(); + } + + defineProperties(GoogleEarthEnterpriseMetadata.prototype, { + /** + * Gets the name of the Google Earth Enterprise server url hosting the imagery. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._url; + } + }, + + /** + * Gets a promise that resolves to true when the provider is ready for use. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise; + } + } + }); + + /** + * Converts a tiles (x, y, level) position into a quadkey used to request an image + * from a Google Earth Enterprise server. + * + * @param {Number} x The tile's x coordinate. + * @param {Number} y The tile's y coordinate. + * @param {Number} level The tile's zoom level. + * + * @see GoogleEarthEnterpriseMetadata#quadKeyToTileXY + */ + GoogleEarthEnterpriseMetadata.tileXYToQuadKey = function(x, y, level) { + var quadkey = ''; + for ( var i = level; i >= 0; --i) { + var bitmask = 1 << i; + var digit = 0; + + // Tile Layout + // ___ ___ + //| | | + //| 3 | 2 | + //|-------| + //| 0 | 1 | + //|___|___| + // + + if (!isBitSet(y, bitmask)) { // Top Row + digit |= 2; + if (!isBitSet(x, bitmask)) { // Right to left + digit |= 1; + } + } else { + if (isBitSet(x, bitmask)) { // Left to right + digit |= 1; + } + } + + quadkey += digit; + } + return quadkey; + }; + + /** + * Converts a tile's quadkey used to request an image from a Google Earth Enterprise server into the + * (x, y, level) position. + * + * @param {String} quadkey The tile's quad key + * + * @see GoogleEarthEnterpriseMetadata#tileXYToQuadKey + */ + GoogleEarthEnterpriseMetadata.quadKeyToTileXY = function(quadkey) { + var x = 0; + var y = 0; + var level = quadkey.length - 1; + for ( var i = level; i >= 0; --i) { + var bitmask = 1 << i; + var digit = +quadkey[level - i]; + + if (isBitSet(digit, 2)) { // Top Row + if (!isBitSet(digit, 1)) { // // Right to left + x |= bitmask; + } + } else { + y |= bitmask; + if (isBitSet(digit, 1)) { // Left to right + x |= bitmask; + } + } + } + return { + x : x, + y : y, + level : level + }; + }; + + // Decodes packet with a key that has been around since the beginning of Google Earth Enterprise + var key = "\x45\xf4\xbd\x0b\x79\xe2\x6a\x45\x22\x05\x92\x2c\x17\xcd\x06\x71\xf8\x49\x10\x46\x67\x51\x00\x42\x25\xc6\xe8\x61\x2c\x66\x29\x08\xc6\x34\xdc\x6a\x62\x25\x79\x0a\x77\x1d\x6d\x69\xd6\xf0\x9c\x6b\x93\xa1\xbd\x4e\x75\xe0\x41\x04\x5b\xdf\x40\x56\x0c\xd9\xbb\x72\x9b\x81\x7c\x10\x33\x53\xee\x4f\x6c\xd4\x71\x05\xb0\x7b\xc0\x7f\x45\x03\x56\x5a\xad\x77\x55\x65\x0b\x33\x92\x2a\xac\x19\x6c\x35\x14\xc5\x1d\x30\x73\xf8\x33\x3e\x6d\x46\x38\x4a\xb4\xdd\xf0\x2e\xdd\x17\x75\x16\xda\x8c\x44\x74\x22\x06\xfa\x61\x22\x0c\x33\x22\x53\x6f\xaf\x39\x44\x0b\x8c\x0e\x39\xd9\x39\x13\x4c\xb9\xbf\x7f\xab\x5c\x8c\x50\x5f\x9f\x22\x75\x78\x1f\xe9\x07\x71\x91\x68\x3b\xc1\xc4\x9b\x7f\xf0\x3c\x56\x71\x48\x82\x05\x27\x55\x66\x59\x4e\x65\x1d\x98\x75\xa3\x61\x46\x7d\x61\x3f\x15\x41\x00\x9f\x14\x06\xd7\xb4\x34\x4d\xce\x13\x87\x46\xb0\x1a\xd5\x05\x1c\xb8\x8a\x27\x7b\x8b\xdc\x2b\xbb\x4d\x67\x30\xc8\xd1\xf6\x5c\x8f\x50\xfa\x5b\x2f\x46\x9b\x6e\x35\x18\x2f\x27\x43\x2e\xeb\x0a\x0c\x5e\x10\x05\x10\xa5\x73\x1b\x65\x34\xe5\x6c\x2e\x6a\x43\x27\x63\x14\x23\x55\xa9\x3f\x71\x7b\x67\x43\x7d\x3a\xaf\xcd\xe2\x54\x55\x9c\xfd\x4b\xc6\xe2\x9f\x2f\x28\xed\xcb\x5c\xc6\x2d\x66\x07\x88\xa7\x3b\x2f\x18\x2a\x22\x4e\x0e\xb0\x6b\x2e\xdd\x0d\x95\x7d\x7d\x47\xba\x43\xb2\x11\xb2\x2b\x3e\x4d\xaa\x3e\x7d\xe6\xce\x49\x89\xc6\xe6\x78\x0c\x61\x31\x05\x2d\x01\xa4\x4f\xa5\x7e\x71\x20\x88\xec\x0d\x31\xe8\x4e\x0b\x00\x6e\x50\x68\x7d\x17\x3d\x08\x0d\x17\x95\xa6\x6e\xa3\x68\x97\x24\x5b\x6b\xf3\x17\x23\xf3\xb6\x73\xb3\x0d\x0b\x40\xc0\x9f\xd8\x04\x51\x5d\xfa\x1a\x17\x22\x2e\x15\x6a\xdf\x49\x00\xb9\xa0\x77\x55\xc6\xef\x10\x6a\xbf\x7b\x47\x4c\x7f\x83\x17\x05\xee\xdc\xdc\x46\x85\xa9\xad\x53\x07\x2b\x53\x34\x06\x07\xff\x14\x94\x59\x19\x02\xe4\x38\xe8\x31\x83\x4e\xb9\x58\x46\x6b\xcb\x2d\x23\x86\x92\x70\x00\x35\x88\x22\xcf\x31\xb2\x26\x2f\xe7\xc3\x75\x2d\x36\x2c\x72\x74\xb0\x23\x47\xb7\xd3\xd1\x26\x16\x85\x37\x72\xe2\x00\x8c\x44\xcf\x10\xda\x33\x2d\x1a\xde\x60\x86\x69\x23\x69\x2a\x7c\xcd\x4b\x51\x0d\x95\x54\x39\x77\x2e\x29\xea\x1b\xa6\x50\xa2\x6a\x8f\x6f\x50\x99\x5c\x3e\x54\xfb\xef\x50\x5b\x0b\x07\x45\x17\x89\x6d\x28\x13\x77\x37\x1d\xdb\x8e\x1e\x4a\x05\x66\x4a\x6f\x99\x20\xe5\x70\xe2\xb9\x71\x7e\x0c\x6d\x49\x04\x2d\x7a\xfe\x72\xc7\xf2\x59\x30\x8f\xbb\x02\x5d\x73\xe5\xc9\x20\xea\x78\xec\x20\x90\xf0\x8a\x7f\x42\x17\x7c\x47\x19\x60\xb0\x16\xbd\x26\xb7\x71\xb6\xc7\x9f\x0e\xd1\x33\x82\x3d\xd3\xab\xee\x63\x99\xc8\x2b\x53\xa0\x44\x5c\x71\x01\xc6\xcc\x44\x1f\x32\x4f\x3c\xca\xc0\x29\x3d\x52\xd3\x61\x19\x58\xa9\x7d\x65\xb4\xdc\xcf\x0d\xf4\x3d\xf1\x08\xa9\x42\xda\x23\x09\xd8\xbf\x5e\x50\x49\xf8\x4d\xc0\xcb\x47\x4c\x1c\x4f\xf7\x7b\x2b\xd8\x16\x18\xc5\x31\x92\x3b\xb5\x6f\xdc\x6c\x0d\x92\x88\x16\xd1\x9e\xdb\x3f\xe2\xe9\xda\x5f\xd4\x84\xe2\x46\x61\x5a\xde\x1c\x55\xcf\xa4\x00\xbe\xfd\xce\x67\xf1\x4a\x69\x1c\x97\xe6\x20\x48\xd8\x5d\x7f\x7e\xae\x71\x20\x0e\x4e\xae\xc0\x56\xa9\x91\x01\x3c\x82\x1d\x0f\x72\xe7\x76\xec\x29\x49\xd6\x5d\x2d\x83\xe3\xdb\x36\x06\xa9\x3b\x66\x13\x97\x87\x6a\xd5\xb6\x3d\x50\x5e\x52\xb9\x4b\xc7\x73\x57\x78\xc9\xf4\x2e\x59\x07\x95\x93\x6f\xd0\x4b\x17\x57\x19\x3e\x27\x27\xc7\x60\xdb\x3b\xed\x9a\x0e\x53\x44\x16\x3e\x3f\x8d\x92\x6d\x77\xa2\x0a\xeb\x3f\x52\xa8\xc6\x55\x5e\x31\x49\x37\x85\xf4\xc5\x1f\x26\x2d\xa9\x1c\xbf\x8b\x27\x54\xda\xc3\x6a\x20\xe5\x2a\x78\x04\xb0\xd6\x90\x70\x72\xaa\x8b\x68\xbd\x88\xf7\x02\x5f\x48\xb1\x7e\xc0\x58\x4c\x3f\x66\x1a\xf9\x3e\xe1\x65\xc0\x70\xa7\xcf\x38\x69\xaf\xf0\x56\x6c\x64\x49\x9c\x27\xad\x78\x74\x4f\xc2\x87\xde\x56\x39\x00\xda\x77\x0b\xcb\x2d\x1b\x89\xfb\x35\x4f\x02\xf5\x08\x51\x13\x60\xc1\x0a\x5a\x47\x4d\x26\x1c\x33\x30\x78\xda\xc0\x9c\x46\x47\xe2\x5b\x79\x60\x49\x6e\x37\x67\x53\x0a\x3e\xe9\xec\x46\x39\xb2\xf1\x34\x0d\xc6\x84\x53\x75\x6e\xe1\x0c\x59\xd9\x1e\xde\x29\x85\x10\x7b\x49\x49\xa5\x77\x79\xbe\x49\x56\x2e\x36\xe7\x0b\x3a\xbb\x4f\x03\x62\x7b\xd2\x4d\x31\x95\x2f\xbd\x38\x7b\xa8\x4f\x21\xe1\xec\x46\x70\x76\x95\x7d\x29\x22\x78\x88\x0a\x90\xdd\x9d\x5c\xda\xde\x19\x51\xcf\xf0\xfc\x59\x52\x65\x7c\x33\x13\xdf\xf3\x48\xda\xbb\x2a\x75\xdb\x60\xb2\x02\x15\xd4\xfc\x19\xed\x1b\xec\x7f\x35\xa8\xff\x28\x31\x07\x2d\x12\xc8\xdc\x88\x46\x7c\x8a\x5b\x22"; + var keyBuffer; + + /** + * Decodes data that is received from the Google Earth Enterprise server. + * + * @param {ArrayBuffer} data The data to be decoded. + */ + GoogleEarthEnterpriseMetadata.decode = function(data) { + if (!defined(data)) { + throw new DeveloperError('data is required.'); + } + + var keylen = key.length; + if (!defined(keyBuffer)) { + keyBuffer = new ArrayBuffer(keylen); + var ui8 = new Uint8Array(keyBuffer); + for (var i=0; i < keylen; ++i) { + ui8[i] = key.charCodeAt(i); + } + } + + var dataView = new DataView(data); + var keyView = new DataView(keyBuffer); + + var dp = 0; + var dpend = data.byteLength; + var dpend64 = dpend - (dpend % 8); + var kpend = keylen; + var kp; + var off = 8; + + // This algorithm is intentionally asymmetric to make it more difficult to + // guess. Security through obscurity. :-( + + // while we have a full uint64 (8 bytes) left to do + // assumes buffer is 64bit aligned (or processor doesn't care) + while (dp < dpend64) { + // rotate the key each time through by using the offets 16,0,8,16,0,8,... + off = (off + 8) % 24; + kp = off; + + // run through one key length xor'ing one uint64 at a time + // then drop out to rotate the key for the next bit + while ((dp < dpend64) && (kp < kpend)) { + dataView.setUint32(dp, dataView.getUint32(dp, true) ^ keyView.getUint32(kp, true), true); + dataView.setUint32(dp+4, dataView.getUint32(dp+4, true) ^ keyView.getUint32(kp+4, true), true); + dp += 8; + kp += 24; + } + } + + // now the remaining 1 to 7 bytes + if (dp < dpend) { + if (kp >= kpend) { + // rotate the key one last time (if necessary) + off = (off + 8) % 24; + kp = off; + } + + while (dp < dpend) { + dataView.setUint8(dp, dataView.getUint8(dp) ^ keyView.getUint8(kp)); + dp++; + kp++; + } + } + }; + + + var qtMagic = 32301; + var compressedMagic = 0x7468dead; + var compressedMagicSwap = 0xadde6874; + /** + * Uncompresses a Google Earth Enterprise packet. + * + * @param {ArrayBuffer} data The data to be uncompressed. + */ + GoogleEarthEnterpriseMetadata.uncompressPacket = function (data) { + // The layout of this decoded data is + // Magic Uint32 + // Size Uint32 + // [GZipped chunk of Size bytes] + + // Pullout magic and verify we have the correct data + var dv = new DataView(data); + var offset = 0; + var magic = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (magic !== compressedMagic && magic !== compressedMagicSwap) { + throw new RuntimeError('Invalid magic'); + } + + // Get the size of the compressed buffer + var size = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (magic === compressedMagicSwap) { + size = ((size >>> 24) & 0x000000ff) | + ((size >>> 8) & 0x0000ff00) | + ((size << 8) & 0x00ff0000) | + ((size << 24) & 0xff000000); + } + + var compressedPacket = new Uint8Array(data, offset); + var uncompressedPacket = pako.inflate(compressedPacket); + + if (uncompressedPacket.length !== size) { + throw new RuntimeError('Size of packet doesn\'t match header'); + } + + return uncompressedPacket; + }; + + // Requests quadtree packet and populates _tileInfo with results + GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket = function(quadKey, version) { + version = defaultValue(version, 1); + quadKey = defaultValue(quadKey, ''); + var url = this._url + 'flatfile?q2-0' + quadKey + '-q.' + version.toString(); + var proxy = this._proxy; + if (defined(proxy)) { + url = proxy.getURL(url); + } + + var that = this; + return loadArrayBuffer(url) + .then(function(metadata) { + GoogleEarthEnterpriseMetadata.decode(metadata); + + var uncompressedPacket = GoogleEarthEnterpriseMetadata.uncompressPacket(metadata); + var dv = new DataView(uncompressedPacket.buffer); + var offset = 0; + var magic = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (magic !== qtMagic) { + throw new RuntimeError('Invalid magic'); + } + + var dataTypeId = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (dataTypeId !== 1) { + throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); + } + + var version = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (version !== 2) { + throw new RuntimeError('Invalid version. Only QuadTreePacket version 2 supported.'); + } + + var numInstances = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var dataInstanceSize = dv.getInt32(offset, true); + offset += sizeOfInt32; + if (dataInstanceSize !== 32) { + throw new RuntimeError('Invalid instance size.'); + } + + var dataBufferOffset = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var dataBufferSize = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var metaBufferSize = dv.getInt32(offset, true); + offset += sizeOfInt32; + + // Offset from beginning of packet (instances + current offset) + if (dataBufferOffset !== (numInstances * dataInstanceSize + offset)) { + throw new RuntimeError('Invalid dataBufferOffset'); + } + + // Verify the packets is all there header + instances + dataBuffer + metaBuffer + if (dataBufferOffset + dataBufferSize + metaBufferSize !== uncompressedPacket.length) { + throw new RuntimeError('Invalid packet offsets'); + } + + // Read all the instances + var instances = []; + for (var i = 0; i < numInstances; ++i) { + var bitfield = dv.getUint8(offset); + ++offset; + + ++offset; // 2 byte align + + var cnodeVersion = dv.getUint16(offset, true); + offset += sizeOfUint16; + + var imageVersion = dv.getUint16(offset, true); + offset += sizeOfUint16; + + var terrainVersion = dv.getUint16(offset, true); + offset += sizeOfUint16; + + // Number of channels stored in the dataBuffer + //var numChannels = dv.getUint16(offset, true); + offset += sizeOfUint16; + + offset += sizeOfUint16; // 4 byte align + + // Channel type offset into dataBuffer + //var typeOffset = dv.getInt32(offset, true); + offset += sizeOfInt32; + + // Channel version offset into dataBuffer + //var versionOffset = dv.getInt32(offset, true); + offset += sizeOfInt32; + + offset += 8; // Ignore image neighbors for now + + // Data providers aren't used + ++offset; // Image provider + ++offset; // Terrain provider + offset += sizeOfUint16; // 4 byte align + + instances.push(new GoogleEarthEnterpriseTileInformation(bitfield, cnodeVersion, + imageVersion, terrainVersion)); + } + + var tileInfo = that._tileInfo; + var index = 0; + + function populateTiles(parentKey, parent, level) { + var isLeaf = false; + if (level === 4) { + if (parent.hasSubtree()) { + return; // We have a subtree, so just return + } + + isLeaf = true; // No subtree, so set all children to null + } + for (var i = 0; i < 4; ++i) { + var childKey = parentKey + i.toString(); + if (isLeaf) { + // No subtree so set all children to null + tileInfo[childKey] = null; + } else if (level < 4) { + // We are still in the middle of the subtree, so add child + // only if their bits are set, otherwise set child to null. + if (!parent.hasChild(i)) { + tileInfo[childKey] = null; + } else { + if (index === numInstances) { + console.log('Incorrect number of instances'); + return; + } + + var instance = instances[index++]; + instance.setParent(parent); + tileInfo[childKey] = instance; + populateTiles(childKey, instance, level + 1); + } + } + } + } + + var level = 0; + var root; + if (quadKey === '') { + // Root tile has data at its root, all others don't + root = instances[index++]; + ++level; + } else { + // Root tile has no data except children bits, so put them into the tile info + var top = instances[index++]; + root = tileInfo[quadKey]; + root._bits |= top._bits; + } + + populateTiles(quadKey, root, level); + }); + }; + + // Verifies there is tileInfo for a quadKey. If not it requests the subtrees required to get it. + // Returns promise that resolves to true if the tile info is available, false otherwise. + + /** + * Populates the metadata subtree down to the specified tile. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * + * @returns {Promise} A promise that resolves to the tile info for the requested quad key + */ + GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level) { + var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + return populateSubtree(this, quadkey); + }; + + function populateSubtree(that, quadKey) { + var tileInfo = that._tileInfo; + var q = quadKey; + var t = tileInfo[q]; + // If we have tileInfo make sure sure it is not a node with a subtree that's not loaded + if (defined(t) && (!t.hasSubtree() || t.hasChildren())) { + return when(t); + } + + while((t === undefined) && q.length > 1) { + q = q.substring(0, q.length-1); + t = tileInfo[q]; + } + + // t is either + // null so one of its parents was a leaf node, so this tile doesn't exist + // undefined so no parent exists - this shouldn't ever happen once the provider is ready + if (!defined(t)) { + return when(undefined); + } + + var subtreePromises = that._subtreePromises; + var promise = subtreePromises[q]; + if (defined(promise)) { + return promise + .then(function() { + return tileInfo[quadKey]; + }); + } + + // We need to split up the promise here because when will execute syncronously if _getQuadTreePacket + // is already resolved (like in the tests), so subtreePromises will never get cleared out. + // The promise will always resolve with a bool, but the initial request will also remove + // the promise from subtreePromises. + promise = subtreePromises[q] = that._getQuadTreePacket(q, t.cnodeVersion); + + return promise + .then(function() { + delete subtreePromises[q]; + // Recursively call this incase we need multiple subtree requests + return that.populateSubtree(quadKey); + }) + .then(function() { + return tileInfo[quadKey]; + }); + } + + /** + * Gets information about a tile + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. + */ + GoogleEarthEnterpriseMetadata.prototype.getTileInformation = function(x, y, level) { + var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + return this._tileInfo[quadkey]; + }; + + /** + * Gets information about a tile from a quadKey + * + * @param {String} quadkey The quadkey for the tile + * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. + */ + GoogleEarthEnterpriseMetadata.prototype.getTileInformationFromQuadKey = function(quadkey) { + return this._tileInfo[quadkey]; + }; + + return GoogleEarthEnterpriseMetadata; +}); diff --git a/Source/Core/GoogleEarthEnterpriseProvider.js b/Source/Core/GoogleEarthEnterpriseProvider.js deleted file mode 100644 index 36829fc1d0f2..000000000000 --- a/Source/Core/GoogleEarthEnterpriseProvider.js +++ /dev/null @@ -1,1318 +0,0 @@ -/*global define*/ -define([ - './appendForwardSlash', - './Cartesian2', - './Cartesian3', - './Cartographic', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './Ellipsoid', - './Event', - './GeographicTilingScheme', - './GoogleEarthEnterpriseTerrainData', - './HeightmapTerrainData', - './loadArrayBuffer', - './loadImageFromTypedArray', - './Math', - './Rectangle', - './RuntimeError', - './TerrainProvider', - './TileProviderError', - './throttleRequestByServer', - '../Scene/DiscardMissingTileImagePolicy', - '../ThirdParty/pako_inflate', - '../ThirdParty/protobuf-minimal', - '../ThirdParty/when' - ], function( - appendForwardSlash, - Cartesian2, - Cartesian3, - Cartographic, - defaultValue, - defined, - defineProperties, - DeveloperError, - Ellipsoid, - Event, - GeographicTilingScheme, - GoogleEarthEnterpriseTerrainData, - HeightmapTerrainData, - loadArrayBuffer, - loadImageFromTypedArray, - CesiumMath, - Rectangle, - RuntimeError, - TerrainProvider, - TileProviderError, - throttleRequestByServer, - DiscardMissingTileImagePolicy, - pako, - protobuf, - when) { - 'use strict'; - - // Bitmask for checking tile properties - var childrenBitmasks = [0x01, 0x02, 0x04, 0x08]; - var anyChildBitmask = 0x0F; - var cacheFlagBitmask = 0x10; // True if there is a child subtree - // var vectorDataBitmask = 0x20; - var imageBitmask = 0x40; - var terrainBitmask = 0x80; - - // Datatype sizes - var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; - var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; - var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - - function isBitSet(bits, mask) { - return ((bits & mask) !== 0); - } - - function GoogleEarthEnterpriseDiscardPolicy() { - this._image = new Image(); - } - - var TerrainState = { - UNKNOWN : 0, - NONE : 1, - SELF : 2, - PARENT : 3 - }; - - /** - * Determines if the discard policy is ready to process images. - * @returns {Boolean} True if the discard policy is ready to process images; otherwise, false. - */ - GoogleEarthEnterpriseDiscardPolicy.prototype.isReady = function() { - return true; - }; - - /** - * Given a tile image, decide whether to discard that image. - * - * @param {Image} image An image to test. - * @returns {Boolean} True if the image should be discarded; otherwise, false. - */ - GoogleEarthEnterpriseDiscardPolicy.prototype.shouldDiscardImage = function(image) { - return (image === this._image); - }; - - /** - * Provides tiled imagery using the Google Earth Enterprise Imagery REST API. - * - * @alias GoogleEarthEnterpriseProvider - * @constructor - * - * @param {Object} options Object with the following properties: - * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. - * @param {String} [options.tileProtocol] The protocol to use when loading tiles, e.g. 'http:' or 'https:'. - * By default, tiles are loaded using the same protocol as the page. - * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. - * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile - * is invalid and should be discarded. If this value is not specified, a default - * is to discard tiles that fail to download. - * @param {Proxy} [options.proxy] A proxy to use for requests. This object is - * expected to have a getURL function which returns the proxied URL, if needed. - * - * @see ArcGisMapServerImageryProvider - * @see GoogleEarthImageryProvider - * @see createOpenStreetMapImageryProvider - * @see SingleTileImageryProvider - * @see createTileMapServiceImageryProvider - * @see WebMapServiceImageryProvider - * @see WebMapTileServiceImageryProvider - * @see UrlTemplateImageryProvider - * - * - * @example - * var gee = new Cesium.GoogleEarthEnterpriseProvider({ - * url : 'https://dev.virtualearth.net' - * }); - * - * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} - */ - function GoogleEarthEnterpriseProvider(options) { - options = defaultValue(options, {}); - - //>>includeStart('debug', pragmas.debug); - if (!defined(options.url)) { - throw new DeveloperError('options.url is required.'); - } - //>>includeEnd('debug'); - - this._url = appendForwardSlash(options.url); - this._tileProtocol = options.tileProtocol; - this._tileDiscardPolicy = options.tileDiscardPolicy; - this._proxy = options.proxy; - - this._tilingScheme = new GeographicTilingScheme({ - numberOfLevelZeroTilesX : 2, - numberOfLevelZeroTilesY : 2, - rectangle : new Rectangle(-CesiumMath.PI, -CesiumMath.PI, CesiumMath.PI, CesiumMath.PI), - ellipsoid : options.ellipsoid - }); - - this._tileWidth = 256; - this._tileHeight = 256; - this._maximumLevel = 23; - this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._tileWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0)); - - // Install the default tile discard policy if none has been supplied. - if (!defined(this._tileDiscardPolicy)) { - this._tileDiscardPolicy = new GoogleEarthEnterpriseDiscardPolicy(); - } - - this._tileInfo = {}; - this._terrainCache = {}; - this._subtreePromises = {}; - - this._errorEvent = new Event(); - - this._ready = false; - var that = this; - this._readyPromise = this._getQuadTreePacket() - .then(function() { - that._ready = true; - }); - } - - defineProperties(GoogleEarthEnterpriseProvider.prototype, { - /** - * Gets the name of the Google Earth Enterprise server url hosting the imagery. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {String} - * @readonly - */ - url : { - get : function() { - return this._url; - } - }, - - /** - * Gets the proxy used by this provider. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Proxy} - * @readonly - */ - proxy : { - get : function() { - return this._proxy; - } - }, - - /** - * Gets the width of each tile, in pixels. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Number} - * @readonly - */ - tileWidth : { - get : function() { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return this._tileWidth; - } - }, - - /** - * Gets the height of each tile, in pixels. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Number} - * @readonly - */ - tileHeight: { - get : function() { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return this._tileHeight; - } - }, - - - /** - * Gets the maximum level-of-detail that can be requested. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Number} - * @readonly - */ - maximumLevel : { - get : function() { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return this._maximumLevel; - } - }, - - /** - * Gets the minimum level-of-detail that can be requested. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Number} - * @readonly - */ - minimumLevel : { - get : function() { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return 0; - } - }, - - /** - * Gets the tiling scheme used by this provider. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {TilingScheme} - * @readonly - */ - tilingScheme : { - get : function() { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return this._tilingScheme; - } - }, - - /** - * Gets the rectangle, in radians, of the imagery provided by this instance. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Rectangle} - * @readonly - */ - rectangle : { - get : function() { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('rectangle must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return this._tilingScheme.rectangle; - } - }, - - /** - * Gets the tile discard policy. If not undefined, the discard policy is responsible - * for filtering out "missing" tiles via its shouldDiscardImage function. If this function - * returns undefined, no tiles are filtered. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {TileDiscardPolicy} - * @readonly - */ - tileDiscardPolicy : { - get : function() { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return this._tileDiscardPolicy; - } - }, - - /** - * Gets an event that is raised when the imagery 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 GoogleEarthEnterpriseProvider.prototype - * @type {Event} - * @readonly - */ - errorEvent : { - get : function() { - return this._errorEvent; - } - }, - - /** - * Gets a value indicating whether or not the provider is ready for use. - * @memberof GoogleEarthEnterpriseProvider.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 GoogleEarthEnterpriseProvider.prototype - * @type {Promise.} - * @readonly - */ - readyPromise : { - get : function() { - return this._readyPromise.promise; - } - }, - - /** - * Gets the credit to display when this imagery provider is active. Typically this is used to credit - * the source of the imagery. This function should not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Credit} - * @readonly - */ - credit : { - get : function() { - return undefined; - } - }, - - /** - * Gets a value indicating whether or not the images provided by this imagery provider - * include an alpha channel. If this property is false, an alpha channel, if present, will - * be ignored. If this property is true, any images without an alpha channel will be treated - * as if their alpha is 1.0 everywhere. Setting this property to false reduces memory usage - * and texture upload time. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Boolean} - * @readonly - */ - hasAlphaChannel : { - get : function() { - return false; - } - }, - - /** - * 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 GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Boolean} - */ - hasWaterMask : { - get : function() { - return false; - } - }, - - /** - * Gets a value indicating whether or not the requested tiles include vertex normals. - * This function should not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Boolean} - */ - hasVertexNormals : { - get : function() { - return false; - } - }, - - /** - * 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 GoogleEarthEnterpriseProvider#ready} returns true. This property may be undefined if availability - * information is not available. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {TileAvailability} - */ - availability : { - get : function() { - return undefined; - } - } - }); - - /** - * Gets the credits to be displayed when a given tile is displayed. - * - * @param {Number} x The tile X coordinate. - * @param {Number} y The tile Y coordinate. - * @param {Number} level The tile level; - * @returns {Credit[]} The credits to be displayed when the tile is displayed. - * - * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. - */ - GoogleEarthEnterpriseProvider.prototype.getTileCredits = function(x, y, level) { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - - return undefined; - }; - - /** - * Requests the image for a given tile. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * - * @param {Number} x The tile X coordinate. - * @param {Number} y The tile Y coordinate. - * @param {Number} level The tile level. - * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or - * undefined if there are too many active requests to the server, and the request - * should be retried later. The resolved image may be either an - * Image or a Canvas DOM object. - * - * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. - */ - GoogleEarthEnterpriseProvider.prototype.requestImage = function(x, y, level) { - //>>includeStart('debug', pragmas.debug); - if (!this._ready) { - throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); - } - //>>includeEnd('debug'); - var invalidImage = this._tileDiscardPolicy._image; // Empty image or undefined depending on discard policy - var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); - var tileInfo = this._tileInfo; - var info = tileInfo[quadKey]; - if (defined(info)) { - if (!isBitSet(info.bits, imageBitmask)) { - // Already have info and there isn't any imagery here - return invalidImage; - } - } else { - if (info === null) { - // Parent was retrieved and said child doesn't exist - return invalidImage; - } - - var q = quadKey; - var last; - while(q.length > 1) { - last = q.substring(q.length-1); - q = q.substring(0, q.length-1); - info = tileInfo[q]; - if (defined(info)) { - if (!isBitSet(info.bits, cacheFlagBitmask) && - !isBitSet(info.bits, childrenBitmasks[parseInt(last)])){ - // We have no subtree or child available at some point in this node's ancestry - return invalidImage; - } - - break; - } else if (info === null) { - // Some node in the ancestry was loaded and said there wasn't a subtree - return invalidImage; - } - } - } - - var that = this; - return populateSubtree(this, quadKey) - .then(function(info){ - if (defined(info)) { - if (isBitSet(info.bits, imageBitmask)) { - var url = buildImageUrl(that, quadKey, info.imageryVersion); - return loadArrayBuffer(url) - .then(function(image) { - if (defined(image)) { - GoogleEarthEnterpriseProvider._decode(image); - var a = new Uint8Array(image); - var type = getImageType(a); - if (!defined(type)) { - var message = decodeEarthImageryPacket(a); - type = message.imageType; - a = message.imageData; - } - - if (!defined(type) || !defined(a)) { - throw new RuntimeError('Invalid image'); - } - - return loadImageFromTypedArray(a, type); - } - - return invalidImage; - }) - .otherwise(function(error) { - // Just ignore failures and return invalidImage - return invalidImage; - }); - } - } - - return invalidImage; - }); - }; - - /** - * Picking features is not currently supported by this imagery provider, so this function simply returns - * undefined. - * - * @param {Number} x The tile X coordinate. - * @param {Number} y The tile Y coordinate. - * @param {Number} level The tile level. - * @param {Number} longitude The longitude at which to pick features. - * @param {Number} latitude The latitude at which to pick features. - * @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous - * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo} - * instances. The array may be empty if no features are found at the given location. - * It may also be undefined if picking is not supported. - */ - GoogleEarthEnterpriseProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { - return undefined; - }; - - /** - * Converts a tiles (x, y, level) position into a quadkey used to request an image - * from a Google Earth Enterprise server. - * - * @param {Number} x The tile's x coordinate. - * @param {Number} y The tile's y coordinate. - * @param {Number} level The tile's zoom level. - * - * @see GoogleEarthEnterpriseProvider#quadKeyToTileXY - */ - GoogleEarthEnterpriseProvider.tileXYToQuadKey = function(x, y, level) { - var quadkey = ''; - for ( var i = level; i >= 0; --i) { - var bitmask = 1 << i; - var digit = 0; - - // Tile Layout - // ___ ___ - //| | | - //| 3 | 2 | - //|-------| - //| 0 | 1 | - //|___|___| - // - - if (!isBitSet(y, bitmask)) { // Top Row - digit |= 2; - if (!isBitSet(x, bitmask)) { // Right to left - digit |= 1; - } - } else { - if (isBitSet(x, bitmask)) { // Left to right - digit |= 1; - } - } - - quadkey += digit; - } - return quadkey; - }; - - /** - * Converts a tile's quadkey used to request an image from a Google Earth Enterprise server into the - * (x, y, level) position. - * - * @param {String} quadkey The tile's quad key - * - * @see GoogleEarthEnterpriseProvider#tileXYToQuadKey - */ - GoogleEarthEnterpriseProvider.quadKeyToTileXY = function(quadkey) { - var x = 0; - var y = 0; - var level = quadkey.length - 1; - for ( var i = level; i >= 0; --i) { - var bitmask = 1 << i; - var digit = +quadkey[level - i]; - - if (isBitSet(digit, 2)) { // Top Row - if (!isBitSet(digit, 1)) { // // Right to left - x |= bitmask; - } - } else { - y |= bitmask; - if (isBitSet(digit, 1)) { // Left to right - x |= bitmask; - } - } - } - return { - x : x, - y : y, - level : level - }; - }; - - /** - * Requests the geometry for a given tile. This function should not be called before - * {@link GoogleEarthEnterpriseProvider#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 {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited, - * or false if the request should be initiated regardless of the number of requests - * already in progress. - * @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 GoogleEarthEnterpriseProvider#ready} - * returns true. - */ - GoogleEarthEnterpriseProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { - //>>includeStart('debug', pragmas.debug) - if (!this._ready) { - throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.'); - } - //>>includeEnd('debug'); - - var hasTerrain = true; - var quadKey = GoogleEarthEnterpriseProvider.tileXYToQuadKey(x, y, level); - var terrainCache = this._terrainCache; - var tileInfo = this._tileInfo; - var info = tileInfo[quadKey]; - - if (!defined(terrainCache[quadKey])) { // If its in the cache we know we have it so just skip all the checks - if (defined(info)) { - if (info.terrainState === TerrainState.NONE) { - // Already have info and there isn't any terrain here - hasTerrain = false; - } - } else { - if (info === null) { - // Parent was retrieved and said child doesn't exist - hasTerrain = false; - } - - var q = quadKey; - var last; - while (q.length > 1) { - last = q.substring(q.length - 1); - q = q.substring(0, q.length - 1); - info = tileInfo[q]; - if (defined(info)) { - if (!isBitSet(info.bits, cacheFlagBitmask) && - !isBitSet(info.bits, childrenBitmasks[parseInt(last)])) { - // We have no subtree or child available at some point in this node's ancestry - hasTerrain = false; - } - - break; - } else if (info === null) { - // Some node in the ancestry was loaded and said there wasn't a subtree - hasTerrain = false; - break; - } - } - } - } - - if (!hasTerrain) { - if(defined(info) && !info.ancestorHasTerrain) { - // We haven't reached a level with terrain, so return the ellipsoid - return new HeightmapTerrainData({ - buffer : new Uint8Array(16 * 16), - width : 16, - height : 16 - }); - } - - return undefined; - } - - - var that = this; - return populateSubtree(this, quadKey) - .then(function(info){ - if (defined(info)) { - if (defined(terrainCache[quadKey])) { - if (info.terrainState === TerrainState.UNKNOWN) { - // If its already in the cache then a parent request must've loaded it - info.terrainState = TerrainState.PARENT; - } - var buffer = terrainCache[quadKey]; - delete terrainCache[quadKey]; - return new GoogleEarthEnterpriseTerrainData({ - buffer: buffer, - childTileMask: info.bits & anyChildBitmask - }); - } - - var q = quadKey; - var terrainVersion = -1; - var terrainState = info.terrainState; - if (terrainState !== TerrainState.NONE) { - switch(terrainState) { - case TerrainState.SELF: // We have terrain and have retrieved it before - terrainVersion = info.terrainVersion; - break; - case TerrainState.PARENT: // We have terrain in our parent - q = q.substring(0, q.length-1); - terrainVersion = tileInfo[q].terrainVersion; - break; - case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet - if (isBitSet(info.bits, terrainBitmask)) { - terrainVersion = info.terrainVersion; // We should have terrain - } else { - q = q.substring(0, q.length-1); - var parentInfo = tileInfo[q]; - if (defined(parentInfo) && isBitSet(parentInfo.bits, terrainBitmask)) { - terrainVersion = parentInfo.terrainVersion; // Try checking in the parent - } - } - break; - } - } - - if (terrainVersion > 0) { - var url = buildTerrainUrl(that, q, terrainVersion); - var promise; - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - promise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(promise)) { - return undefined; - } - } else { - promise = loadArrayBuffer(url); - } - - return promise - .then(function(terrain) { - if (defined(terrain)) { - GoogleEarthEnterpriseProvider._decode(terrain); - var uncompressedTerrain = uncompressPacket(terrain); - parseTerrainPacket(that, uncompressedTerrain, q); - - var buffer = terrainCache[quadKey]; - if (defined(buffer)) { - if (q !== quadKey) { - // If we didn't request this tile directly then it came from a parent - info.terrainState = TerrainState.PARENT; - } - delete terrainCache[quadKey]; - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : info.bits & anyChildBitmask - }); - } else { - info.terrainState = TerrainState.NONE; - } - } - }) - .otherwise(function(error) { - info.terrainState = TerrainState.NONE; - }); - } else if(!info.ancestorHasTerrain) { - // We haven't reached a level with terrain, so return the ellipsoid - return new HeightmapTerrainData({ - buffer : new Uint8Array(16 * 16), - width : 16, - height : 16 - }); - } - } - }); - }; - - function parseTerrainPacket(that, terrainData, quadKey) { - var tileInfo = that._tileInfo; - var info = tileInfo[quadKey]; - var terrainCache = that._terrainCache; - var buffer = terrainData.buffer; - - var dv = new DataView(buffer); - var totalSize = terrainData.length; - - var offset = 0; - var terrainTiles = []; - while(offset < totalSize) { - // Each tile is split into 4 parts - var tileStart = offset; - for (var quad = 0; quad < 4; ++quad) { - var size = dv.getUint32(offset, true); - offset += sizeOfUint32; - offset += size; - } - terrainTiles.push(buffer.slice(tileStart, offset)); - } - - // If we were sent child tiles, store them till they are needed - terrainCache[quadKey] = terrainTiles[0]; - info.terrainState = TerrainState.SELF; - var count = terrainTiles.length-1; - for (var j = 0; j < count; ++j) { - var childKey = quadKey + j.toString(); - if (isBitSet(info.bits, childrenBitmasks[j])) { - terrainCache[childKey] = terrainTiles[j+1]; - } - } - } - - /** - * 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. - */ - GoogleEarthEnterpriseProvider.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, otherwise true or false. - */ - GoogleEarthEnterpriseProvider.prototype.getTileDataAvailable = function(x, y, level) { - return undefined; - }; - - // Decodes packet with a key that has been around since the beginning of Google Earth Enterprise - var key = "\x45\xf4\xbd\x0b\x79\xe2\x6a\x45\x22\x05\x92\x2c\x17\xcd\x06\x71\xf8\x49\x10\x46\x67\x51\x00\x42\x25\xc6\xe8\x61\x2c\x66\x29\x08\xc6\x34\xdc\x6a\x62\x25\x79\x0a\x77\x1d\x6d\x69\xd6\xf0\x9c\x6b\x93\xa1\xbd\x4e\x75\xe0\x41\x04\x5b\xdf\x40\x56\x0c\xd9\xbb\x72\x9b\x81\x7c\x10\x33\x53\xee\x4f\x6c\xd4\x71\x05\xb0\x7b\xc0\x7f\x45\x03\x56\x5a\xad\x77\x55\x65\x0b\x33\x92\x2a\xac\x19\x6c\x35\x14\xc5\x1d\x30\x73\xf8\x33\x3e\x6d\x46\x38\x4a\xb4\xdd\xf0\x2e\xdd\x17\x75\x16\xda\x8c\x44\x74\x22\x06\xfa\x61\x22\x0c\x33\x22\x53\x6f\xaf\x39\x44\x0b\x8c\x0e\x39\xd9\x39\x13\x4c\xb9\xbf\x7f\xab\x5c\x8c\x50\x5f\x9f\x22\x75\x78\x1f\xe9\x07\x71\x91\x68\x3b\xc1\xc4\x9b\x7f\xf0\x3c\x56\x71\x48\x82\x05\x27\x55\x66\x59\x4e\x65\x1d\x98\x75\xa3\x61\x46\x7d\x61\x3f\x15\x41\x00\x9f\x14\x06\xd7\xb4\x34\x4d\xce\x13\x87\x46\xb0\x1a\xd5\x05\x1c\xb8\x8a\x27\x7b\x8b\xdc\x2b\xbb\x4d\x67\x30\xc8\xd1\xf6\x5c\x8f\x50\xfa\x5b\x2f\x46\x9b\x6e\x35\x18\x2f\x27\x43\x2e\xeb\x0a\x0c\x5e\x10\x05\x10\xa5\x73\x1b\x65\x34\xe5\x6c\x2e\x6a\x43\x27\x63\x14\x23\x55\xa9\x3f\x71\x7b\x67\x43\x7d\x3a\xaf\xcd\xe2\x54\x55\x9c\xfd\x4b\xc6\xe2\x9f\x2f\x28\xed\xcb\x5c\xc6\x2d\x66\x07\x88\xa7\x3b\x2f\x18\x2a\x22\x4e\x0e\xb0\x6b\x2e\xdd\x0d\x95\x7d\x7d\x47\xba\x43\xb2\x11\xb2\x2b\x3e\x4d\xaa\x3e\x7d\xe6\xce\x49\x89\xc6\xe6\x78\x0c\x61\x31\x05\x2d\x01\xa4\x4f\xa5\x7e\x71\x20\x88\xec\x0d\x31\xe8\x4e\x0b\x00\x6e\x50\x68\x7d\x17\x3d\x08\x0d\x17\x95\xa6\x6e\xa3\x68\x97\x24\x5b\x6b\xf3\x17\x23\xf3\xb6\x73\xb3\x0d\x0b\x40\xc0\x9f\xd8\x04\x51\x5d\xfa\x1a\x17\x22\x2e\x15\x6a\xdf\x49\x00\xb9\xa0\x77\x55\xc6\xef\x10\x6a\xbf\x7b\x47\x4c\x7f\x83\x17\x05\xee\xdc\xdc\x46\x85\xa9\xad\x53\x07\x2b\x53\x34\x06\x07\xff\x14\x94\x59\x19\x02\xe4\x38\xe8\x31\x83\x4e\xb9\x58\x46\x6b\xcb\x2d\x23\x86\x92\x70\x00\x35\x88\x22\xcf\x31\xb2\x26\x2f\xe7\xc3\x75\x2d\x36\x2c\x72\x74\xb0\x23\x47\xb7\xd3\xd1\x26\x16\x85\x37\x72\xe2\x00\x8c\x44\xcf\x10\xda\x33\x2d\x1a\xde\x60\x86\x69\x23\x69\x2a\x7c\xcd\x4b\x51\x0d\x95\x54\x39\x77\x2e\x29\xea\x1b\xa6\x50\xa2\x6a\x8f\x6f\x50\x99\x5c\x3e\x54\xfb\xef\x50\x5b\x0b\x07\x45\x17\x89\x6d\x28\x13\x77\x37\x1d\xdb\x8e\x1e\x4a\x05\x66\x4a\x6f\x99\x20\xe5\x70\xe2\xb9\x71\x7e\x0c\x6d\x49\x04\x2d\x7a\xfe\x72\xc7\xf2\x59\x30\x8f\xbb\x02\x5d\x73\xe5\xc9\x20\xea\x78\xec\x20\x90\xf0\x8a\x7f\x42\x17\x7c\x47\x19\x60\xb0\x16\xbd\x26\xb7\x71\xb6\xc7\x9f\x0e\xd1\x33\x82\x3d\xd3\xab\xee\x63\x99\xc8\x2b\x53\xa0\x44\x5c\x71\x01\xc6\xcc\x44\x1f\x32\x4f\x3c\xca\xc0\x29\x3d\x52\xd3\x61\x19\x58\xa9\x7d\x65\xb4\xdc\xcf\x0d\xf4\x3d\xf1\x08\xa9\x42\xda\x23\x09\xd8\xbf\x5e\x50\x49\xf8\x4d\xc0\xcb\x47\x4c\x1c\x4f\xf7\x7b\x2b\xd8\x16\x18\xc5\x31\x92\x3b\xb5\x6f\xdc\x6c\x0d\x92\x88\x16\xd1\x9e\xdb\x3f\xe2\xe9\xda\x5f\xd4\x84\xe2\x46\x61\x5a\xde\x1c\x55\xcf\xa4\x00\xbe\xfd\xce\x67\xf1\x4a\x69\x1c\x97\xe6\x20\x48\xd8\x5d\x7f\x7e\xae\x71\x20\x0e\x4e\xae\xc0\x56\xa9\x91\x01\x3c\x82\x1d\x0f\x72\xe7\x76\xec\x29\x49\xd6\x5d\x2d\x83\xe3\xdb\x36\x06\xa9\x3b\x66\x13\x97\x87\x6a\xd5\xb6\x3d\x50\x5e\x52\xb9\x4b\xc7\x73\x57\x78\xc9\xf4\x2e\x59\x07\x95\x93\x6f\xd0\x4b\x17\x57\x19\x3e\x27\x27\xc7\x60\xdb\x3b\xed\x9a\x0e\x53\x44\x16\x3e\x3f\x8d\x92\x6d\x77\xa2\x0a\xeb\x3f\x52\xa8\xc6\x55\x5e\x31\x49\x37\x85\xf4\xc5\x1f\x26\x2d\xa9\x1c\xbf\x8b\x27\x54\xda\xc3\x6a\x20\xe5\x2a\x78\x04\xb0\xd6\x90\x70\x72\xaa\x8b\x68\xbd\x88\xf7\x02\x5f\x48\xb1\x7e\xc0\x58\x4c\x3f\x66\x1a\xf9\x3e\xe1\x65\xc0\x70\xa7\xcf\x38\x69\xaf\xf0\x56\x6c\x64\x49\x9c\x27\xad\x78\x74\x4f\xc2\x87\xde\x56\x39\x00\xda\x77\x0b\xcb\x2d\x1b\x89\xfb\x35\x4f\x02\xf5\x08\x51\x13\x60\xc1\x0a\x5a\x47\x4d\x26\x1c\x33\x30\x78\xda\xc0\x9c\x46\x47\xe2\x5b\x79\x60\x49\x6e\x37\x67\x53\x0a\x3e\xe9\xec\x46\x39\xb2\xf1\x34\x0d\xc6\x84\x53\x75\x6e\xe1\x0c\x59\xd9\x1e\xde\x29\x85\x10\x7b\x49\x49\xa5\x77\x79\xbe\x49\x56\x2e\x36\xe7\x0b\x3a\xbb\x4f\x03\x62\x7b\xd2\x4d\x31\x95\x2f\xbd\x38\x7b\xa8\x4f\x21\xe1\xec\x46\x70\x76\x95\x7d\x29\x22\x78\x88\x0a\x90\xdd\x9d\x5c\xda\xde\x19\x51\xcf\xf0\xfc\x59\x52\x65\x7c\x33\x13\xdf\xf3\x48\xda\xbb\x2a\x75\xdb\x60\xb2\x02\x15\xd4\xfc\x19\xed\x1b\xec\x7f\x35\xa8\xff\x28\x31\x07\x2d\x12\xc8\xdc\x88\x46\x7c\x8a\x5b\x22"; - var keyBuffer; - GoogleEarthEnterpriseProvider._decode = function(data) { - if (!defined(data)) { - throw new DeveloperError('data is required.'); - } - - var keylen = key.length; - if (!defined(keyBuffer)) { - keyBuffer = new ArrayBuffer(keylen); - var ui8 = new Uint8Array(keyBuffer); - for (var i=0; i < keylen; ++i) { - ui8[i] = key.charCodeAt(i); - } - } - - var dataView = new DataView(data); - var keyView = new DataView(keyBuffer); - - var dp = 0; - var dpend = data.byteLength; - var dpend64 = dpend - (dpend % 8); - var kpend = keylen; - var kp; - var off = 8; - - // This algorithm is intentionally asymmetric to make it more difficult to - // guess. Security through obscurity. :-( - - // while we have a full uint64 (8 bytes) left to do - // assumes buffer is 64bit aligned (or processor doesn't care) - while (dp < dpend64) { - // rotate the key each time through by using the offets 16,0,8,16,0,8,... - off = (off + 8) % 24; - kp = off; - - // run through one key length xor'ing one uint64 at a time - // then drop out to rotate the key for the next bit - while ((dp < dpend64) && (kp < kpend)) { - dataView.setUint32(dp, dataView.getUint32(dp, true) ^ keyView.getUint32(kp, true), true); - dataView.setUint32(dp+4, dataView.getUint32(dp+4, true) ^ keyView.getUint32(kp+4, true), true); - dp += 8; - kp += 24; - } - } - - // now the remaining 1 to 7 bytes - if (dp < dpend) { - if (kp >= kpend) { - // rotate the key one last time (if necessary) - off = (off + 8) % 24; - kp = off; - } - - while (dp < dpend) { - dataView.setUint8(dp, dataView.getUint8(dp) ^ keyView.getUint8(kp)); - dp++; - kp++; - } - } - }; - - // - // Functions to handle quadtree packets - // - var qtMagic = 32301; - var compressedMagic = 0x7468dead; - var compressedMagicSwap = 0xadde6874; - function uncompressPacket(data) { - // The layout of this decoded data is - // Magic Uint32 - // Size Uint32 - // [GZipped chunk of Size bytes] - - // Pullout magic and verify we have the correct data - var dv = new DataView(data); - var offset = 0; - var magic = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (magic !== compressedMagic && magic !== compressedMagicSwap) { - throw new RuntimeError('Invalid magic'); - } - - // Get the size of the compressed buffer - var size = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (magic === compressedMagicSwap) { - var v = ((size >>> 24) & 0x000000ff) | - ((size >>> 8) & 0x0000ff00) | - ((size << 8) & 0x00ff0000) | - ((size << 24) & 0xff000000); - size = v; - } - - var compressedPacket = new Uint8Array(data, offset); - var uncompressedPacket = pako.inflate(compressedPacket); - - if (uncompressedPacket.length !== size) { - throw new RuntimeError('Size of packet doesn\'t match header'); - } - - return uncompressedPacket; - } - - // Requests quadtree packet and populates _tileInfo with results - GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket = function(quadKey, version) { - version = defaultValue(version, 1); - quadKey = defaultValue(quadKey, ''); - var url = this._url + 'flatfile?q2-0' + quadKey + '-q.' + version.toString(); - var proxy = this._proxy; - if (defined(proxy)) { - url = proxy.getURL(url); - } - - var that = this; - return loadArrayBuffer(url) - .then(function(metadata) { - GoogleEarthEnterpriseProvider._decode(metadata); - - var uncompressedPacket = uncompressPacket(metadata); - var dv = new DataView(uncompressedPacket.buffer); - var offset = 0; - var magic = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (magic !== qtMagic) { - throw new RuntimeError('Invalid magic'); - } - - var dataTypeId = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (dataTypeId !== 1) { - throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); - } - - var version = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (version !== 2) { - throw new RuntimeError('Invalid version. Only QuadTreePacket version 2 supported.'); - } - - var numInstances = dv.getInt32(offset, true); - offset += sizeOfInt32; - - var dataInstanceSize = dv.getInt32(offset, true); - offset += sizeOfInt32; - if (dataInstanceSize !== 32) { - throw new RuntimeError('Invalid instance size.'); - } - - var dataBufferOffset = dv.getInt32(offset, true); - offset += sizeOfInt32; - - var dataBufferSize = dv.getInt32(offset, true); - offset += sizeOfInt32; - - var metaBufferSize = dv.getInt32(offset, true); - offset += sizeOfInt32; - - // Offset from beginning of packet (instances + current offset) - if (dataBufferOffset !== (numInstances * dataInstanceSize + offset)) { - throw new RuntimeError('Invalid dataBufferOffset'); - } - - // Verify the packets is all there header + instances + dataBuffer + metaBuffer - if (dataBufferOffset + dataBufferSize + metaBufferSize !== uncompressedPacket.length) { - throw new RuntimeError('Invalid packet offsets'); - } - - // Read all the instances - var instances = []; - for (var i = 0; i < numInstances; ++i) { - var bitfield = dv.getUint8(offset); - ++offset; - - ++offset; // 2 byte align - - var cnodeVersion = dv.getUint16(offset, true); - offset += sizeOfUint16; - - var imageVersion = dv.getUint16(offset, true); - offset += sizeOfUint16; - - var terrainVersion = dv.getUint16(offset, true); - offset += sizeOfUint16; - - // Number of channels stored in the dataBuffer - //var numChannels = dv.getUint16(offset, true); - offset += sizeOfUint16; - - offset += sizeOfUint16; // 4 byte align - - // Channel type offset into dataBuffer - //var typeOffset = dv.getInt32(offset, true); - offset += sizeOfInt32; - - // Channel version offset into dataBuffer - //var versionOffset = dv.getInt32(offset, true); - offset += sizeOfInt32; - - offset += 8; // Ignore image neighbors for now - - // Data providers aren't used - ++offset; // Image provider - ++offset; // Terrain provider - offset += sizeOfUint16; // 4 byte align - - instances.push({ - bits : bitfield, - cnodeVersion : cnodeVersion, - imageryVersion : imageVersion, - terrainVersion : terrainVersion, - ancestorHasTerrain : false, // Set it later once we find its parent - terrainState : TerrainState.UNKNOWN - }); - } - - var tileInfo = that._tileInfo; - var index = 0; - - function populateTiles(parentKey, parent, level) { - var bits = parent.bits; - var isLeaf = false; - if (level === 4) { - if (isBitSet(bits, cacheFlagBitmask)) { - return; // We have a subtree, so just return - } - - isLeaf = true; // No subtree, so set all children to null - } - for (var i = 0; i < 4; ++i) { - var childKey = parentKey + i.toString(); - if (isLeaf) { - // No subtree so set all children to null - tileInfo[childKey] = null; - } else if (level < 4) { - // We are still in the middle of the subtree, so add child - // only if their bits are set, otherwise set child to null. - if (!isBitSet(bits, childrenBitmasks[i])) { - tileInfo[childKey] = null; - } else { - if (index === numInstances) { - console.log('Incorrect number of instances'); - return; - } - - var instance = instances[index++]; - instance.ancestorHasTerrain = parent.ancestorHasTerrain || isBitSet(instance.bits, terrainBitmask); - tileInfo[childKey] = instance; - populateTiles(childKey, instance, level + 1); - } - } - } - } - - var level = 0; - var root; - if (quadKey === '') { - // Root tile has data at its root, all others don't - root = instances[index++]; - ++level; - } else { - // Root tile has no data except children bits, so put them into the tile info - var top = instances[index++]; - root = tileInfo[quadKey]; - root.bits |= top.bits; - } - - populateTiles(quadKey, root, level); - }); - }; - - // Verifies there is tileInfo for a quadKey. If not it requests the subtrees required to get it. - // Returns promise that resolves to true if the tile info is available, false otherwise. - function populateSubtree(that, quadKey) { - var tileInfo = that._tileInfo; - var q = quadKey; - var t = tileInfo[q]; - // If we have tileInfo make sure sure it is not a node with a subtree that's not loaded - if (defined(t) && (!isBitSet(t.bits, cacheFlagBitmask) || isBitSet(t.bits, anyChildBitmask))) { - return when(t); - } - - while((t === undefined) && q.length > 1) { - q = q.substring(0, q.length-1); - t = tileInfo[q]; - } - - // t is either - // null so one of its parents was a leaf node, so this tile doesn't exist - // undefined so no parent exists - this shouldn't ever happen once the provider is ready - if (!defined(t)) { - return when(undefined); - } - - var subtreePromises = that._subtreePromises; - var promise = subtreePromises[q]; - if (defined(promise)) { - return promise - .then(function() { - return tileInfo[quadKey]; - }); - } - - // We need to split up the promise here because when will execute syncronously if _getQuadTreePacket - // is already resolved (like in the tests), so subtreePromises will never get cleared out. - // The promise will always resolve with a bool, but the initial request will also remove - // the promise from subtreePromises. - promise = subtreePromises[q] = that._getQuadTreePacket(q, t.cnodeVersion); - - return promise - .then(function() { - delete subtreePromises[q]; - // Recursively call this incase we need multiple subtree requests - return populateSubtree(that, quadKey); - }) - .then(function() { - return tileInfo[quadKey]; - }); - } - - // - // Functions to handle imagery packets - // - function buildImageUrl(imageryProvider, quadKey, version) { - version = (defined(version) && version > 0) ? version : 1; - var imageUrl = imageryProvider._url + 'flatfile?f1-0' + quadKey + '-i.' + version.toString(); - - var proxy = imageryProvider._proxy; - if (defined(proxy)) { - imageUrl = proxy.getURL(imageUrl); - } - - return imageUrl; - } - - // Detects if a Uint8Array is a JPEG or PNG - function getImageType(image) { - var jpeg = 'JFIF'; - if (image[6] === jpeg.charCodeAt(0) && image[7] === jpeg.charCodeAt(1) && - image[8] === jpeg.charCodeAt(2) && image[9] === jpeg.charCodeAt(3)) { - return 'image/jpeg'; - } - - var png = 'PNG'; - if (image[1] === png.charCodeAt(0) && image[2] === png.charCodeAt(1) && image[3] === png.charCodeAt(2)) { - return 'image/png'; - } - } - - // Decodes an Imagery protobuf into the message - // Partially generated with the help of protobuf.js static generator - function decodeEarthImageryPacket(data) { - var reader = protobuf.Reader.create(data); - var end = reader.len; - var message = {}; - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.imageType = reader.uint32(); - break; - case 2: - message.imageData = reader.bytes(); - break; - case 3: - message.alphaType = reader.uint32(); - break; - case 4: - message.imageAlpha = reader.bytes(); - break; - case 5: - var copyrightIds = message.copyrightIds; - if (!defined(copyrightIds)) { - copyrightIds = message.copyrightIds = []; - } - if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; - while (reader.pos < end2) { - copyrightIds.push(reader.uint32()); - } - } else { - copyrightIds.push(reader.uint32()); - } - break; - default: - reader.skipType(tag & 7); - break; - } - } - - var imageType = message.imageType; - if (defined(imageType)) { - switch(imageType) { - case 0: - message.imageType = 'image/jpeg'; - break; - case 4: - message.imageType = 'image/png'; - break; - default: - throw new RuntimeError('GoogleEarthEnterpriseProvider: Unsupported image type.'); - } - } - - var alphaType = message.alphaType; - if (defined(alphaType) && alphaType !== 0) { - console.log('GoogleEarthEnterpriseProvider: External alpha not supported.'); - delete message.alphaType; - delete message.imageAlpha; - } - - return message; - } - - // - // Functions to handle imagery packets - // - function buildTerrainUrl(terrainProvider, quadKey, version) { - version = (defined(version) && version > 0) ? version : 1; - var terrainUrl = terrainProvider._url + 'flatfile?f1c-0' + quadKey + '-t.' + version.toString(); - - var proxy = terrainProvider._proxy; - if (defined(proxy)) { - terrainUrl = proxy.getURL(terrainUrl); - } - - return terrainUrl; - } - - return GoogleEarthEnterpriseProvider; -}); diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js new file mode 100644 index 000000000000..ab57c9a473b6 --- /dev/null +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -0,0 +1,488 @@ +/*global define*/ +define([ + './Cartesian2', + './Cartesian3', + './Cartographic', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './Ellipsoid', + './Event', + './GeographicTilingScheme', + './GoogleEarthEnterpriseMetadata', + './GoogleEarthEnterpriseTerrainData', + './HeightmapTerrainData', + './loadArrayBuffer', + './Math', + './Rectangle', + './TerrainProvider', + './throttleRequestByServer' + ], function( + Cartesian2, + Cartesian3, + Cartographic, + defaultValue, + defined, + defineProperties, + DeveloperError, + Ellipsoid, + Event, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + GoogleEarthEnterpriseTerrainData, + HeightmapTerrainData, + loadArrayBuffer, + CesiumMath, + Rectangle, + TerrainProvider, + throttleRequestByServer) { + 'use strict'; + + var TerrainState = { + UNKNOWN : 0, + NONE : 1, + SELF : 2, + PARENT : 3 + }; + + /** + * Provides tiled terrain using the Google Earth Enterprise REST API. + * + * @alias GoogleEarthEnterpriseTerrainProvider + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. + * @param {Proxy} [options.proxy] A proxy to use for requests. This object is + * expected to have a getURL function which returns the proxied URL, if needed. + * + * @see CesiumTerrainProvider + * @see GoogleEarthEnterpriseImageryProvider + * + * @example + * var gee = new Cesium.GoogleEarthEnterpriseTerrainProvider({ + * url : 'http://www.earthenterprise.org/3d' + * }); + * + * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} + */ + function GoogleEarthEnterpriseTerrainProvider(options) { + options = defaultValue(options, {}); + + //>>includeStart('debug', pragmas.debug); + if (!defined(options.url)) { + throw new DeveloperError('options.url is required.'); + } + //>>includeEnd('debug'); + + this._metadata = GoogleEarthEnterpriseMetadata.getMetadata(options.url, options.proxy); + this._proxy = options.proxy; + + this._tilingScheme = new GeographicTilingScheme({ + numberOfLevelZeroTilesX : 2, + numberOfLevelZeroTilesY : 2, + rectangle : new Rectangle(-CesiumMath.PI, -CesiumMath.PI, CesiumMath.PI, CesiumMath.PI), + ellipsoid : options.ellipsoid + }); + + // 1024 was the initial size of the heightmap before decimation in GEE + this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, 1024, this._tilingScheme.getNumberOfXTilesAtLevel(0)); + + this._terrainCache = {}; + + this._errorEvent = new Event(); + + this._ready = false; + var that = this; + this._readyPromise = this._metadata.readyPromise + .then(function() { + that._ready = true; + }); + } + + defineProperties(GoogleEarthEnterpriseTerrainProvider.prototype, { + /** + * Gets the name of the Google Earth Enterprise server url hosting the imagery. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._metadata.url; + } + }, + + /** + * Gets the proxy used by this provider. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : function() { + return this._proxy; + } + }, + + /** + * Gets the tiling scheme used by this provider. This function should + * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {TilingScheme} + * @readonly + */ + tilingScheme : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tilingScheme; + } + }, + + /** + * Gets an event that is raised when the imagery 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 GoogleEarthEnterpriseProvider.prototype + * @type {Event} + * @readonly + */ + errorEvent : { + get : function() { + return this._errorEvent; + } + }, + + /** + * Gets a value indicating whether or not the provider is ready for use. + * @memberof GoogleEarthEnterpriseProvider.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 GoogleEarthEnterpriseProvider.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + }, + + /** + * Gets the credit to display when this imagery provider is active. Typically this is used to credit + * the source of the imagery. This function should not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {Credit} + * @readonly + */ + credit : { + get : function() { + return undefined; + } + }, + + /** + * 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 GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {Boolean} + */ + hasWaterMask : { + get : function() { + return false; + } + }, + + /** + * Gets a value indicating whether or not the requested tiles include vertex normals. + * This function should not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {Boolean} + */ + hasVertexNormals : { + get : function() { + return false; + } + }, + + /** + * 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 GoogleEarthEnterpriseProvider#ready} returns true. This property may be undefined if availability + * information is not available. + * @memberof GoogleEarthEnterpriseProvider.prototype + * @type {TileAvailability} + */ + availability : { + get : function() { + return undefined; + } + } + }); + + /** + * Requests the geometry for a given tile. This function should not be called before + * {@link GoogleEarthEnterpriseProvider#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 {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited, + * or false if the request should be initiated regardless of the number of requests + * already in progress. + * @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 GoogleEarthEnterpriseProvider#ready} + * returns true. + */ + GoogleEarthEnterpriseTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { + //>>includeStart('debug', pragmas.debug) + if (!this._ready) { + throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.'); + } + //>>includeEnd('debug'); + + var hasTerrain = true; + var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + var terrainCache = this._terrainCache; + var metadata = this._metadata; + var info = metadata.getTileInformationFromQuadKey(quadKey); + + if (!defined(terrainCache[quadKey])) { // If its in the cache we know we have it so just skip all the checks + if (defined(info)) { + if (info.terrainState === TerrainState.NONE) { + // Already have info and there isn't any terrain here + hasTerrain = false; + } + } else { + if (info === null) { + // Parent was retrieved and said child doesn't exist + hasTerrain = false; + } + + var q = quadKey; + var last; + while (q.length > 1) { + last = q.substring(q.length - 1); + q = q.substring(0, q.length - 1); + info = metadata.getTileInformationFromQuadKey(q); + if (defined(info)) { + if (!info.hasSubtree() && + !info.hasChild(parseInt(last))) { + // We have no subtree or child available at some point in this node's ancestry + hasTerrain = false; + } + + break; + } else if (info === null) { + // Some node in the ancestry was loaded and said there wasn't a subtree + hasTerrain = false; + break; + } + } + } + } + + if (!hasTerrain) { + if(defined(info) && !info.ancestorHasTerrain) { + // We haven't reached a level with terrain, so return the ellipsoid + return new HeightmapTerrainData({ + buffer : new Uint8Array(16 * 16), + width : 16, + height : 16 + }); + } + + return undefined; + } + + + var that = this; + return metadata.populateSubtree(x, y, level) + .then(function(info){ + if (defined(info)) { + if (defined(terrainCache[quadKey])) { + if (info.terrainState === TerrainState.UNKNOWN) { + // If its already in the cache then a parent request must've loaded it + info.terrainState = TerrainState.PARENT; + } + var buffer = terrainCache[quadKey]; + delete terrainCache[quadKey]; + return new GoogleEarthEnterpriseTerrainData({ + buffer: buffer, + childTileMask: info.getChildBitmask() + }); + } + + var parentInfo; + var q = quadKey; + var terrainVersion = -1; + var terrainState = info.terrainState; + if (terrainState !== TerrainState.NONE) { + switch(terrainState) { + case TerrainState.SELF: // We have terrain and have retrieved it before + terrainVersion = info.terrainVersion; + break; + case TerrainState.PARENT: // We have terrain in our parent + q = q.substring(0, q.length-1); + parentInfo = metadata.getTileInformationFromQuadKey(q); + terrainVersion = parentInfo.terrainVersion; + break; + case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet + if (info.hasTerrain()) { + terrainVersion = info.terrainVersion; // We should have terrain + } else { + q = q.substring(0, q.length-1); + parentInfo = metadata.getTileInformationFromQuadKey(q); + if (defined(parentInfo) && parentInfo.hasTerrain()) { + terrainVersion = parentInfo.terrainVersion; // Try checking in the parent + } + } + break; + } + } + + if (terrainVersion > 0) { + var url = buildTerrainUrl(that, q, terrainVersion); + var promise; + throttleRequests = defaultValue(throttleRequests, true); + if (throttleRequests) { + promise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(promise)) { + return undefined; + } + } else { + promise = loadArrayBuffer(url); + } + return promise + .then(function(terrain) { + if (defined(terrain)) { + GoogleEarthEnterpriseMetadata.decode(terrain); + var uncompressedTerrain = GoogleEarthEnterpriseMetadata.uncompressPacket(terrain); + parseTerrainPacket(that, uncompressedTerrain, q); + + var buffer = terrainCache[quadKey]; + if (defined(buffer)) { + if (q !== quadKey) { + // If we didn't request this tile directly then it came from a parent + info.terrainState = TerrainState.PARENT; + } + delete terrainCache[quadKey]; + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : info.getChildBitmask() + }); + } else { + info.terrainState = TerrainState.NONE; + } + } + }) + .otherwise(function(error) { + info.terrainState = TerrainState.NONE; + }); + } else if(!info.ancestorHasTerrain) { + // We haven't reached a level with terrain, so return the ellipsoid + return new HeightmapTerrainData({ + buffer : new Uint8Array(16 * 16), + width : 16, + height : 16 + }); + } + } + }); + }; + + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + function parseTerrainPacket(that, terrainData, quadKey) { + var info = that._metadata.getTileInformationFromQuadKey(quadKey); + var terrainCache = that._terrainCache; + var buffer = terrainData.buffer; + + var dv = new DataView(buffer); + var totalSize = terrainData.length; + + var offset = 0; + var terrainTiles = []; + while(offset < totalSize) { + // Each tile is split into 4 parts + var tileStart = offset; + for (var quad = 0; quad < 4; ++quad) { + var size = dv.getUint32(offset, true); + offset += sizeOfUint32; + offset += size; + } + terrainTiles.push(buffer.slice(tileStart, offset)); + } + + // If we were sent child tiles, store them till they are needed + terrainCache[quadKey] = terrainTiles[0]; + info.terrainState = TerrainState.SELF; + var count = terrainTiles.length-1; + for (var j = 0; j < count; ++j) { + var childKey = quadKey + j.toString(); + if (info.hasChild(j)) { + terrainCache[childKey] = terrainTiles[j+1]; + } + } + } + + /** + * 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. + */ + GoogleEarthEnterpriseTerrainProvider.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, otherwise true or false. + */ + GoogleEarthEnterpriseTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { + return undefined; + }; + + // + // Functions to handle imagery packets + // + function buildTerrainUrl(terrainProvider, quadKey, version) { + version = (defined(version) && version > 0) ? version : 1; + var terrainUrl = terrainProvider.url + 'flatfile?f1c-0' + quadKey + '-t.' + version.toString(); + + var proxy = terrainProvider._proxy; + if (defined(proxy)) { + terrainUrl = proxy.getURL(terrainUrl); + } + + return terrainUrl; + } + + return GoogleEarthEnterpriseTerrainProvider; +}); diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js new file mode 100644 index 000000000000..36fea936cebf --- /dev/null +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -0,0 +1,599 @@ +/*global define*/ +define([ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/GeographicTilingScheme', + '../Core/GoogleEarthEnterpriseMetadata', + '../Core/loadArrayBuffer', + '../Core/loadImageFromTypedArray', + '../Core/Math', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/throttleRequestByServer', + '../ThirdParty/protobuf-minimal', + '../ThirdParty/when' +], function( + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + loadArrayBuffer, + loadImageFromTypedArray, + CesiumMath, + Rectangle, + RuntimeError, + throttleRequestByServer, + protobuf, + when) { + 'use strict'; + + function GoogleEarthEnterpriseDiscardPolicy() { + this._image = new Image(); + } + + /** + * Determines if the discard policy is ready to process images. + * @returns {Boolean} True if the discard policy is ready to process images; otherwise, false. + */ + GoogleEarthEnterpriseDiscardPolicy.prototype.isReady = function() { + return true; + }; + + /** + * Given a tile image, decide whether to discard that image. + * + * @param {Image} image An image to test. + * @returns {Boolean} True if the image should be discarded; otherwise, false. + */ + GoogleEarthEnterpriseDiscardPolicy.prototype.shouldDiscardImage = function(image) { + return (image === this._image); + }; + + /** + * Provides tiled imagery using the Google Earth Enterprise REST API. + * + * @alias GoogleEarthEnterpriseImageryProvider + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. + * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile + * is invalid and should be discarded. If this value is not specified, a default + * is to discard tiles that fail to download. + * @param {Proxy} [options.proxy] A proxy to use for requests. This object is + * expected to have a getURL function which returns the proxied URL, if needed. + * + * @see ArcGisMapServerImageryProvider + * @see GoogleEarthImageryProvider + * @see createOpenStreetMapImageryProvider + * @see SingleTileImageryProvider + * @see createTileMapServiceImageryProvider + * @see WebMapServiceImageryProvider + * @see WebMapTileServiceImageryProvider + * @see UrlTemplateImageryProvider + * + * + * @example + * var gee = new Cesium.GoogleEarthEnterpriseImageryProvider({ + * url : 'http://www.earthenterprise.org/3d' + * }); + * + * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} + */ + function GoogleEarthEnterpriseImageryProvider(options) { + options = defaultValue(options, {}); + + //>>includeStart('debug', pragmas.debug); + if (!defined(options.url)) { + throw new DeveloperError('options.url is required.'); + } + //>>includeEnd('debug'); + + this._metadata = GoogleEarthEnterpriseMetadata.getMetadata(options.url, options.proxy); + this._tileDiscardPolicy = options.tileDiscardPolicy; + this._proxy = options.proxy; + + this._tilingScheme = new GeographicTilingScheme({ + numberOfLevelZeroTilesX : 2, + numberOfLevelZeroTilesY : 2, + rectangle : new Rectangle(-CesiumMath.PI, -CesiumMath.PI, CesiumMath.PI, CesiumMath.PI), + ellipsoid : options.ellipsoid + }); + + this._tileWidth = 256; + this._tileHeight = 256; + this._maximumLevel = 23; + + // Install the default tile discard policy if none has been supplied. + if (!defined(this._tileDiscardPolicy)) { + this._tileDiscardPolicy = new GoogleEarthEnterpriseDiscardPolicy(); + } + + this._errorEvent = new Event(); + + this._ready = false; + var that = this; + this._readyPromise = this._metadata.readyPromise + .then(function() { + that._ready = true; + }); + } + + defineProperties(GoogleEarthEnterpriseImageryProvider.prototype, { + /** + * Gets the name of the Google Earth Enterprise server url hosting the imagery. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._metadata.url; + } + }, + + /** + * Gets the proxy used by this provider. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : function() { + return this._proxy; + } + }, + + /** + * Gets the width of each tile, in pixels. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + tileWidth : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tileWidth; + } + }, + + /** + * Gets the height of each tile, in pixels. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + tileHeight: { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tileHeight; + } + }, + + + /** + * Gets the maximum level-of-detail that can be requested. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + maximumLevel : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._maximumLevel; + } + }, + + /** + * Gets the minimum level-of-detail that can be requested. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Number} + * @readonly + */ + minimumLevel : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return 0; + } + }, + + /** + * Gets the tiling scheme used by this provider. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {TilingScheme} + * @readonly + */ + tilingScheme : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tilingScheme; + } + }, + + /** + * Gets the rectangle, in radians, of the imagery provided by this instance. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Rectangle} + * @readonly + */ + rectangle : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('rectangle must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tilingScheme.rectangle; + } + }, + + /** + * Gets the tile discard policy. If not undefined, the discard policy is responsible + * for filtering out "missing" tiles via its shouldDiscardImage function. If this function + * returns undefined, no tiles are filtered. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {TileDiscardPolicy} + * @readonly + */ + tileDiscardPolicy : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return this._tileDiscardPolicy; + } + }, + + /** + * Gets an event that is raised when the imagery 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 GoogleEarthEnterpriseImageryProvider.prototype + * @type {Event} + * @readonly + */ + errorEvent : { + get : function() { + return this._errorEvent; + } + }, + + /** + * Gets a value indicating whether or not the provider is ready for use. + * @memberof GoogleEarthEnterpriseImageryProvider.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 GoogleEarthEnterpriseImageryProvider.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + }, + + /** + * Gets the credit to display when this imagery provider is active. Typically this is used to credit + * the source of the imagery. This function should not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Credit} + * @readonly + */ + credit : { + get : function() { + return undefined; + } + }, + + /** + * Gets a value indicating whether or not the images provided by this imagery provider + * include an alpha channel. If this property is false, an alpha channel, if present, will + * be ignored. If this property is true, any images without an alpha channel will be treated + * as if their alpha is 1.0 everywhere. Setting this property to false reduces memory usage + * and texture upload time. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Boolean} + * @readonly + */ + hasAlphaChannel : { + get : function() { + return false; + } + } + }); + + /** + * Gets the credits to be displayed when a given tile is displayed. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level; + * @returns {Credit[]} The credits to be displayed when the tile is displayed. + * + * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. + */ + GoogleEarthEnterpriseImageryProvider.prototype.getTileCredits = function(x, y, level) { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + + return undefined; + }; + + /** + * Requests the image for a given tile. This function should + * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or + * undefined if there are too many active requests to the server, and the request + * should be retried later. The resolved image may be either an + * Image or a Canvas DOM object. + * + * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. + */ + GoogleEarthEnterpriseImageryProvider.prototype.requestImage = function(x, y, level) { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + var that = this; + + var invalidImage = this._tileDiscardPolicy._image; // Empty image or undefined depending on discard policy + var metadata = this._metadata; + var info = metadata.getTileInformation(x, y, level); + var promise; + if (defined(info)) { + if (!info.hasImagery()) { + // Already have info and there isn't any imagery here + return invalidImage; + } + var url = buildImageUrl(this, info, x, y, level); + promise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(promise)) { + return; // Returning undefined will allow for a retry later + } + } else { + if (info === null) { + // Parent was retrieved and said child doesn't exist + return invalidImage; + } + + var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + var last; + while(q.length > 1) { + last = q.substring(q.length-1); + q = q.substring(0, q.length-1); + info = metadata.getTileInformationFromQuadKey(q); + if (defined(info)) { + if (!info.hasSubtree() && + !info.hasChild(parseInt(last))){ + // We have no subtree or child available at some point in this node's ancestry + return invalidImage; + } + + break; + } else if (info === null) { + // Some node in the ancestry was loaded and said there wasn't a subtree + return invalidImage; + } + } + + // There is nothing in the heirarchy that leads us to think this tile isn't available + // so populate the tree so the info is available for this tile. + promise = metadata.populateSubtree(x, y, level) + .then(function(info) { + if (defined(info)) { + if (info.hasImagery()) { + var url = buildImageUrl(that, info, x, y, level); + return loadArrayBuffer(url); + } + } + + return when(invalidImage); + }); + } + + return promise + .then(function(image) { + if (image === invalidImage) { + return invalidImage; + } + + GoogleEarthEnterpriseMetadata.decode(image); + var a = new Uint8Array(image); + var type = getImageType(a); + if (!defined(type)) { + var message = decodeEarthImageryPacket(a); + type = message.imageType; + a = message.imageData; + } + + if (!defined(type) || !defined(a)) { + throw new RuntimeError('Invalid image'); + } + + return loadImageFromTypedArray(a, type); + }) + .otherwise(function(error) { + // Just ignore failures and return invalidImage + return invalidImage; + }); + }; + + /** + * Picking features is not currently supported by this imagery provider, so this function simply returns + * undefined. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @param {Number} longitude The longitude at which to pick features. + * @param {Number} latitude The latitude at which to pick features. + * @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous + * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo} + * instances. The array may be empty if no features are found at the given location. + * It may also be undefined if picking is not supported. + */ + GoogleEarthEnterpriseImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { + return undefined; + }; + + + // + // Functions to handle imagery packets + // + function buildImageUrl(imageryProvider, info, x, y, level) { + var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + var version = info.imageryVersion; + version = (defined(version) && version > 0) ? version : 1; + var imageUrl = imageryProvider.url + 'flatfile?f1-0' + quadKey + '-i.' + version.toString(); + + var proxy = imageryProvider._proxy; + if (defined(proxy)) { + imageUrl = proxy.getURL(imageUrl); + } + + return imageUrl; + } + + // Detects if a Uint8Array is a JPEG or PNG + function getImageType(image) { + var jpeg = 'JFIF'; + if (image[6] === jpeg.charCodeAt(0) && image[7] === jpeg.charCodeAt(1) && + image[8] === jpeg.charCodeAt(2) && image[9] === jpeg.charCodeAt(3)) { + return 'image/jpeg'; + } + + var png = 'PNG'; + if (image[1] === png.charCodeAt(0) && image[2] === png.charCodeAt(1) && image[3] === png.charCodeAt(2)) { + return 'image/png'; + } + } + + // Decodes an Imagery protobuf into the message + // Partially generated with the help of protobuf.js static generator + function decodeEarthImageryPacket(data) { + var reader = protobuf.Reader.create(data); + var end = reader.len; + var message = {}; + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.imageType = reader.uint32(); + break; + case 2: + message.imageData = reader.bytes(); + break; + case 3: + message.alphaType = reader.uint32(); + break; + case 4: + message.imageAlpha = reader.bytes(); + break; + case 5: + var copyrightIds = message.copyrightIds; + if (!defined(copyrightIds)) { + copyrightIds = message.copyrightIds = []; + } + if ((tag & 7) === 2) { + var end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + copyrightIds.push(reader.uint32()); + } + } else { + copyrightIds.push(reader.uint32()); + } + break; + default: + reader.skipType(tag & 7); + break; + } + } + + var imageType = message.imageType; + if (defined(imageType)) { + switch(imageType) { + case 0: + message.imageType = 'image/jpeg'; + break; + case 4: + message.imageType = 'image/png'; + break; + default: + throw new RuntimeError('GoogleEarthEnterpriseImageryProvider: Unsupported image type.'); + } + } + + var alphaType = message.alphaType; + if (defined(alphaType) && alphaType !== 0) { + console.log('GoogleEarthEnterpriseImageryProvider: External alpha not supported.'); + delete message.alphaType; + delete message.imageAlpha; + } + + return message; + } + + + return GoogleEarthEnterpriseImageryProvider; +}); From c72d3200b0dee591d7a2680c14897ef5e57f2de4 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 3 Apr 2017 17:20:43 -0400 Subject: [PATCH 20/60] Added tests for GoogleEarthEnterpriseMetadata and fixed an issue or 2 along the way. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 28 +- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 450 ++++++++++++++++++ ...ogleEarthEnterpriseTerrainProviderSpec.js} | 72 +-- 3 files changed, 470 insertions(+), 80 deletions(-) create mode 100644 Specs/Core/GoogleEarthEnterpriseMetadataSpec.js rename Specs/Core/{GoogleEarthEnterpriseProviderSpec.js => GoogleEarthEnterpriseTerrainProviderSpec.js} (86%) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 1c9805bd4fd6..700ff0b0a49f 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -38,7 +38,7 @@ define([ return ((bits & mask) !== 0); } - function GoogleEarthEnterpriseTileInformation(bits, cnodeVersion, imageryVersion, terrainVersion) { + function TileInformation(bits, cnodeVersion, imageryVersion, terrainVersion) { this._bits = bits; this.cnodeVersion = cnodeVersion; this.imageryVersion = imageryVersion; @@ -47,34 +47,36 @@ define([ this.terrainState = 0; // UNKNOWN } - GoogleEarthEnterpriseTileInformation.prototype.setParent = function(parent) { + TileInformation.prototype.setParent = function(parent) { this.ancestorHasTerrain = parent.ancestorHasTerrain || this.hasTerrain(); }; - GoogleEarthEnterpriseTileInformation.prototype.hasSubtree = function() { + TileInformation.prototype.hasSubtree = function() { return isBitSet(this._bits, cacheFlagBitmask); }; - GoogleEarthEnterpriseTileInformation.prototype.hasImagery = function() { + TileInformation.prototype.hasImagery = function() { return isBitSet(this._bits, imageBitmask); }; - GoogleEarthEnterpriseTileInformation.prototype.hasTerrain = function() { + TileInformation.prototype.hasTerrain = function() { return isBitSet(this._bits, terrainBitmask); }; - GoogleEarthEnterpriseTileInformation.prototype.hasChildren = function() { + TileInformation.prototype.hasChildren = function() { return isBitSet(this._bits, anyChildBitmask); }; - GoogleEarthEnterpriseTileInformation.prototype.hasChild = function(index) { + TileInformation.prototype.hasChild = function(index) { return isBitSet(this._bits, childrenBitmasks[index]); }; - GoogleEarthEnterpriseTileInformation.prototype.getChildBitmask = function(index) { + TileInformation.prototype.getChildBitmask = function(index) { return this._bits && anyChildBitmask; }; + GoogleEarthEnterpriseMetadata.TileInformation = TileInformation; + var metadata = {}; GoogleEarthEnterpriseMetadata.getMetadata = function(url, proxy) { @@ -460,7 +462,7 @@ define([ ++offset; // Terrain provider offset += sizeOfUint16; // 4 byte align - instances.push(new GoogleEarthEnterpriseTileInformation(bitfield, cnodeVersion, + instances.push(new TileInformation(bitfield, cnodeVersion, imageVersion, terrainVersion)); } @@ -528,7 +530,7 @@ define([ * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. * - * @returns {Promise} A promise that resolves to the tile info for the requested quad key + * @returns {Promise} A promise that resolves to the tile info for the requested quad key */ GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level) { var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); @@ -575,7 +577,7 @@ define([ .then(function() { delete subtreePromises[q]; // Recursively call this incase we need multiple subtree requests - return that.populateSubtree(quadKey); + return populateSubtree(that, quadKey); }) .then(function() { return tileInfo[quadKey]; @@ -588,7 +590,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. - * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. + * @returns {GoogleEarthEnterpriseMetadata.TileInformation|undefined} Information about the tile or undefined if it isn't loaded. */ GoogleEarthEnterpriseMetadata.prototype.getTileInformation = function(x, y, level) { var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); @@ -599,7 +601,7 @@ define([ * Gets information about a tile from a quadKey * * @param {String} quadkey The quadkey for the tile - * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. + * @returns {GoogleEarthEnterpriseMetadata.TileInformation|undefined} Information about the tile or undefined if it isn't loaded. */ GoogleEarthEnterpriseMetadata.prototype.getTileInformationFromQuadKey = function(quadkey) { return this._tileInfo[quadkey]; diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js new file mode 100644 index 000000000000..faa07e220e48 --- /dev/null +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -0,0 +1,450 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/loadWithXhr', + 'Core/Math', + 'ThirdParty/when' +], function( + GoogleEarthEnterpriseMetadata, + DefaultProxy, + defaultValue, + loadWithXhr, + CesiumMath, + when) { + 'use strict'; + + it('tileXYToQuadKey', function() { + // http://msdn.microsoft.com/en-us/library/bb259689.aspx + // Levels are off by one compared to the documentation because our levels + // start at 0 while Bing's start at 1. + expect(GoogleEarthEnterpriseMetadata.tileXYToQuadKey(1, 0, 0)).toEqual('2'); + expect(GoogleEarthEnterpriseMetadata.tileXYToQuadKey(1, 2, 1)).toEqual('02'); + expect(GoogleEarthEnterpriseMetadata.tileXYToQuadKey(3, 5, 2)).toEqual('021'); + expect(GoogleEarthEnterpriseMetadata.tileXYToQuadKey(4, 7, 2)).toEqual('100'); + }); + + it('quadKeyToTileXY', function() { + expect(GoogleEarthEnterpriseMetadata.quadKeyToTileXY('2')).toEqual({ + x : 1, + y : 0, + level : 0 + }); + expect(GoogleEarthEnterpriseMetadata.quadKeyToTileXY('02')).toEqual({ + x : 1, + y : 2, + level : 1 + }); + expect(GoogleEarthEnterpriseMetadata.quadKeyToTileXY('021')).toEqual({ + x : 3, + y : 5, + level : 2 + }); + expect(GoogleEarthEnterpriseMetadata.quadKeyToTileXY('100')).toEqual({ + x : 4, + y : 7, + level : 2 + }); + }); + + it('decode', function() { + CesiumMath.setRandomNumberSeed(123123); + var data = new Uint8Array(1025); + for (var i = 0; i < 1025; ++i) { + data[i] = Math.floor(CesiumMath.nextRandomNumber() * 256); + } + + var buffer = data.buffer.slice(); + var a = new Uint8Array(buffer); + GoogleEarthEnterpriseMetadata.decode(buffer); + expect(a).not.toEqual(data); + + // For the algorithm encode/decode are the same + GoogleEarthEnterpriseMetadata.decode(buffer); + expect(a).toEqual(data); + }); + + it('populateSubtree', function() { + var quad = '0123'; + var index = 0; + spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + quadKey = defaultValue(quadKey, '') + index.toString(); + this._tileInfo[quadKey] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + index = (index + 1) % 4; + + return when(); + }); + + var metadata = new GoogleEarthEnterpriseMetadata('http://test.server'); + return metadata.readyPromise + .then(function() { + var tileXY = GoogleEarthEnterpriseMetadata.quadKeyToTileXY(quad); + return metadata.populateSubtree(tileXY.x, tileXY.y, tileXY.level); + }) + .then(function() { + expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket.calls.count()).toEqual(4); + expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith(); + expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith('0', 1); + expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith('01', 1); + expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith('012', 1); + + var tileInfo = metadata._tileInfo; + expect(tileInfo['0']).toBeDefined(); + expect(tileInfo['01']).toBeDefined(); + expect(tileInfo['012']).toBeDefined(); + expect(tileInfo['0123']).toBeDefined(); + }); + }); + + it('getMetadata/releaseMetadata', function() { + var server1 = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); + var server1a = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); + var server2 = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server2'); + + expect(server1a).toBe(server1); + expect(server1).not.toBe(server2); + + GoogleEarthEnterpriseMetadata.releaseMetadata(server1); + GoogleEarthEnterpriseMetadata.releaseMetadata(server1a); + + var server1b = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); + expect(server1b).not.toBe(server1); + }); + + // it('constructor throws when url is not specified', function() { + // function constructWithoutServer() { + // return new BingMapsImageryProvider({ + // mapStyle : BingMapsStyle.AERIAL + // }); + // } + // expect(constructWithoutServer).toThrowDeveloperError(); + // }); + // + // function createFakeMetadataResponse(mapStyle) { + // var stylePrefix = 'a'; + // switch (mapStyle) { + // case BingMapsStyle.AERIAL_WITH_LABELS: + // stylePrefix = 'h'; + // break; + // case BingMapsStyle.ROAD: + // stylePrefix = 'r'; + // break; + // } + // + // return { + // "authenticationResultCode" : "ValidCredentials", + // "brandLogoUri" : "http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png", + // "copyright" : "Copyright © 2014 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.", + // "resourceSets" : [{ + // "estimatedTotal" : 1, + // "resources" : [{ + // "__type" : "ImageryMetadata:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1", + // "imageHeight" : 256, + // "imageUrl" : "http:\/\/ecn.{subdomain}.tiles.virtualearth.net.fake.invalid\/tiles\/" + stylePrefix + "{quadkey}.jpeg?g=3031&mkt={culture}", + // "imageUrlSubdomains" : ["t0", "t1", "t2", "t3"], + // "imageWidth" : 256, + // "imageryProviders" : [{ + // "attribution" : "© 2014 DigitalGlobe", + // "coverageAreas" : [{ + // "bbox" : [-67, -179.99, 27, 0], + // "zoomMax" : 21, + // "zoomMin" : 14 + // }, { + // "bbox" : [27, -179.99, 87, -126.5], + // "zoomMax" : 21, + // "zoomMin" : 14 + // }, { + // "bbox" : [48.4, -126.5, 87, -5.75], + // "zoomMax" : 21, + // "zoomMin" : 14 + // }] + // }, { + // "attribution" : "Image courtesy of NASA", + // "coverageAreas" : [{ + // "bbox" : [-90, -180, 90, 180], + // "zoomMax" : 8, + // "zoomMin" : 1 + // }] + // }], + // "vintageEnd" : null, + // "vintageStart" : null, + // "zoomMax" : 21, + // "zoomMin" : 1 + // }] + // }], + // "statusCode" : 200, + // "statusDescription" : "OK", + // "traceId" : "ea754a48ccdb4dd297c8f35350e0f0d9|BN20130533|02.00.106.1600|" + // }; + // } + // + // function installFakeMetadataRequest(url, mapStyle, proxy) { + // var expectedUrl = url + '/REST/v1/Imagery/Metadata/' + mapStyle + '?incl=ImageryProviders&key='; + // if (defined(proxy)) { + // expectedUrl = proxy.getURL(expectedUrl); + // } + // + // loadJsonp.loadAndExecuteScript = function(url, functionName) { + // expect(url).toStartWith(expectedUrl); + // + // setTimeout(function() { + // window[functionName](createFakeMetadataResponse(mapStyle)); + // }, 1); + // }; + // } + // + // function installFakeImageRequest(expectedUrl) { + // loadImage.createImage = function(url, crossOrigin, deferred) { + // if (/^blob:/.test(url)) { + // // load blob url normally + // loadImage.defaultCreateImage(url, crossOrigin, deferred); + // } else { + // if (defined(expectedUrl)) { + // expect(url).toEqual(expectedUrl); + // } + // // Just return any old image. + // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + // } + // }; + // + // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + // if (defined(expectedUrl)) { + // expect(url).toEqual(expectedUrl); + // } + // + // // Just return any old image. + // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); + // }; + // } + // + // it('resolves readyPromise', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // return provider.readyPromise.then(function(result) { + // expect(result).toBe(true); + // expect(provider.ready).toBe(true); + // }); + // }); + // + // it('rejects readyPromise on error', function() { + // var url = 'host.invalid'; + // var provider = new BingMapsImageryProvider({ + // url : url + // }); + // + // return provider.readyPromise.then(function () { + // fail('should not resolve'); + // }).otherwise(function (e) { + // expect(provider.ready).toBe(false); + // expect(e.message).toContain(url); + // }); + // }); + // + // it('returns valid value for hasAlphaChannel', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.AERIAL; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // expect(typeof provider.hasAlphaChannel).toBe('boolean'); + // }); + // }); + // + // it('can provide a root tile', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // expect(provider.url).toEqual(url); + // expect(provider.key).toBeDefined(); + // expect(provider.mapStyle).toEqual(mapStyle); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // expect(provider.tileWidth).toEqual(256); + // expect(provider.tileHeight).toEqual(256); + // expect(provider.maximumLevel).toEqual(20); + // expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); + // expect(provider.tileDiscardPolicy).toBeInstanceOf(DiscardMissingTileImagePolicy); + // expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle); + // expect(provider.credit).toBeInstanceOf(Object); + // + // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt='); + // + // return provider.requestImage(0, 0, 0).then(function(image) { + // expect(image).toBeInstanceOf(Image); + // }); + // }); + // }); + // + // it('sets correct culture in tile requests', function() { + // var url = 'http://fake.fake.invalid'; + // var mapStyle = BingMapsStyle.AERIAL_WITH_LABELS; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var culture = 'ja-jp'; + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle, + // culture : culture + // }); + // + // expect(provider.culture).toEqual(culture); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/h0.jpeg?g=3031&mkt=ja-jp'); + // + // return provider.requestImage(0, 0, 0).then(function(image) { + // expect(image).toBeInstanceOf(Image); + // }); + // }); + // }); + // + // it('routes requests through a proxy if one is specified', function() { + // var url = 'http://foo.bar.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // var proxy = new DefaultProxy('/proxy/'); + // + // installFakeMetadataRequest(url, mapStyle, proxy); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle, + // proxy : proxy + // }); + // + // expect(provider.url).toEqual(url); + // expect(provider.proxy).toEqual(proxy); + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // installFakeImageRequest(proxy.getURL('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt=')); + // + // return provider.requestImage(0, 0, 0).then(function(image) { + // expect(image).toBeInstanceOf(Image); + // }); + // }); + // }); + // + // it('raises error on invalid url', function() { + // var url = 'host.invalid'; + // var provider = new BingMapsImageryProvider({ + // url : url + // }); + // + // var errorEventRaised = false; + // provider.errorEvent.addEventListener(function(error) { + // expect(error.message).toContain(url); + // errorEventRaised = true; + // }); + // + // return pollToPromise(function() { + // return provider.ready || errorEventRaised; + // }).then(function() { + // expect(provider.ready).toEqual(false); + // expect(errorEventRaised).toEqual(true); + // }); + // }); + // + // it('raises error event when image cannot be loaded', function() { + // var url = 'http://foo.bar.invalid'; + // var mapStyle = BingMapsStyle.ROAD; + // + // installFakeMetadataRequest(url, mapStyle); + // installFakeImageRequest(); + // + // var provider = new BingMapsImageryProvider({ + // url : url, + // mapStyle : mapStyle + // }); + // + // var layer = new ImageryLayer(provider); + // + // var tries = 0; + // provider.errorEvent.addEventListener(function(error) { + // expect(error.timesRetried).toEqual(tries); + // ++tries; + // if (tries < 3) { + // error.retry = true; + // } + // }); + // + // loadImage.createImage = function(url, crossOrigin, deferred) { + // if (/^blob:/.test(url)) { + // // load blob url normally + // loadImage.defaultCreateImage(url, crossOrigin, deferred); + // } else if (tries === 2) { + // // Succeed after 2 tries + // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + // } else { + // // fail + // setTimeout(function() { + // deferred.reject(); + // }, 1); + // } + // }; + // + // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + // if (tries === 2) { + // // Succeed after 2 tries + // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); + // } else { + // // fail + // setTimeout(function() { + // deferred.reject(); + // }, 1); + // } + // }; + // + // return pollToPromise(function() { + // return provider.ready; + // }).then(function() { + // var imagery = new Imagery(layer, 0, 0, 0); + // imagery.addReference(); + // layer._requestImagery(imagery); + // + // return pollToPromise(function() { + // return imagery.state === ImageryState.RECEIVED; + // }).then(function() { + // expect(imagery.image).toBeInstanceOf(Image); + // expect(tries).toEqual(2); + // imagery.releaseReference(); + // }); + // }); + // }); +}); diff --git a/Specs/Core/GoogleEarthEnterpriseProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js similarity index 86% rename from Specs/Core/GoogleEarthEnterpriseProviderSpec.js rename to Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index 247f06bea83e..63649a14e4fa 100644 --- a/Specs/Core/GoogleEarthEnterpriseProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -1,6 +1,6 @@ /*global defineSuite*/ defineSuite([ - 'Core/GoogleEarthEnterpriseProvider', + 'Core/GoogleEarthEnterpriseTerrainProvider', 'Core/DefaultProxy', 'Core/defaultValue', 'Core/defined', @@ -8,14 +8,9 @@ defineSuite([ 'Core/loadWithXhr', 'Core/Math', 'Core/TerrainProvider', - 'Core/WebMercatorTilingScheme', - 'Scene/DiscardMissingTileImagePolicy', - 'Scene/Imagery', - 'Scene/ImageryLayer', - 'Scene/ImageryProvider', 'ThirdParty/when' ], function( - GoogleEarthEnterpriseProvider, + GoogleEarthEnterpriseTerrainProvider, DefaultProxy, defaultValue, defined, @@ -23,64 +18,10 @@ defineSuite([ loadWithXhr, CesiumMath, TerrainProvider, - WebMercatorTilingScheme, - DiscardMissingTileImagePolicy, - Imagery, - ImageryLayer, - ImageryProvider, when) { 'use strict'; - it('tileXYToQuadKey', function() { - // http://msdn.microsoft.com/en-us/library/bb259689.aspx - // Levels are off by one compared to the documentation because our levels - // start at 0 while Bing's start at 1. - expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(1, 0, 0)).toEqual('2'); - expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(1, 2, 1)).toEqual('02'); - expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(3, 5, 2)).toEqual('021'); - expect(GoogleEarthEnterpriseProvider.tileXYToQuadKey(4, 7, 2)).toEqual('100'); - }); - - it('quadKeyToTileXY', function() { - expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('2')).toEqual({ - x : 1, - y : 0, - level : 0 - }); - expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('02')).toEqual({ - x : 1, - y : 2, - level : 1 - }); - expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('021')).toEqual({ - x : 3, - y : 5, - level : 2 - }); - expect(GoogleEarthEnterpriseProvider.quadKeyToTileXY('100')).toEqual({ - x : 4, - y : 7, - level : 2 - }); - }); - - it('decode', function() { - CesiumMath.setRandomNumberSeed(123123); - var data = new Uint8Array(1025); - for (var i = 0; i < 1025; ++i) { - data[i] = Math.floor(CesiumMath.nextRandomNumber() * 256); - } - - var buffer = data.buffer.slice(); - var a = new Uint8Array(buffer); - GoogleEarthEnterpriseProvider._decode(buffer); - expect(a).not.toEqual(data); - - // For the algorithm encode/decode are the same - GoogleEarthEnterpriseProvider._decode(buffer); - expect(a).toEqual(data); - }); - + /* it('request Image populates the correct metadata', function() { var quad = '0123'; var index = 0; @@ -124,13 +65,10 @@ defineSuite([ expect(tileInfo['0123']).toBeDefined(); }); }); - - it('conforms to ImageryProvider interface', function() { - expect(GoogleEarthEnterpriseProvider).toConformToInterface(ImageryProvider); - }); + */ it('conforms to TerrainProvider interface', function() { - expect(GoogleEarthEnterpriseProvider).toConformToInterface(TerrainProvider); + expect(GoogleEarthEnterpriseTerrainProvider).toConformToInterface(TerrainProvider); }); // it('constructor throws when url is not specified', function() { From 06b5ab4ee5e93dc94dfc82db85e995fbbc7ec6f9 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 3 Apr 2017 22:12:49 -0400 Subject: [PATCH 21/60] Added tests for GoogleEarthEnterpriseMetadata. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 22 +- .../GoogleEarthEnterpriseTerrainProvider.js | 5 +- .../GoogleEarthEnterpriseImageryProvider.js | 5 +- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 524 +++++++----------- 4 files changed, 212 insertions(+), 344 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 700ff0b0a49f..8ba830988e6c 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -143,7 +143,10 @@ define([ this.refCount = 1; - this._readyPromise = this._getQuadTreePacket(); + this._readyPromise = this._getQuadTreePacket() + .then(function() { + return true; + }); } defineProperties(GoogleEarthEnterpriseMetadata.prototype, { @@ -159,6 +162,18 @@ define([ } }, + /** + * Gets the proxy used by this provider. + * @memberof GoogleEarthEnterpriseImageryProvider.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : function() { + return this._proxy; + } + }, + /** * Gets a promise that resolves to true when the provider is ready for use. * @memberof GoogleEarthEnterpriseProvider.prototype @@ -390,11 +405,8 @@ define([ throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); } - var version = dv.getUint32(offset, true); + //var version = dv.getUint32(offset, true); offset += sizeOfUint32; - if (version !== 2) { - throw new RuntimeError('Invalid version. Only QuadTreePacket version 2 supported.'); - } var numInstances = dv.getInt32(offset, true); offset += sizeOfInt32; diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index ab57c9a473b6..fb663e78f00a 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -97,8 +97,9 @@ define([ this._ready = false; var that = this; this._readyPromise = this._metadata.readyPromise - .then(function() { - that._ready = true; + .then(function(result) { + that._ready = result; + return result; }); } diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 36fea936cebf..07494578d68c 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -121,8 +121,9 @@ define([ this._ready = false; var that = this; this._readyPromise = this._metadata.readyPromise - .then(function() { - that._ready = true; + .then(function(result) { + that._ready = result; + return result; }); } diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index faa07e220e48..2690eaa1ac1a 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -98,6 +98,10 @@ defineSuite([ }); it('getMetadata/releaseMetadata', function() { + spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + return when(); + }); + var server1 = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); var server1a = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); var server2 = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server2'); @@ -112,339 +116,189 @@ defineSuite([ expect(server1b).not.toBe(server1); }); - // it('constructor throws when url is not specified', function() { - // function constructWithoutServer() { - // return new BingMapsImageryProvider({ - // mapStyle : BingMapsStyle.AERIAL - // }); - // } - // expect(constructWithoutServer).toThrowDeveloperError(); - // }); - // - // function createFakeMetadataResponse(mapStyle) { - // var stylePrefix = 'a'; - // switch (mapStyle) { - // case BingMapsStyle.AERIAL_WITH_LABELS: - // stylePrefix = 'h'; - // break; - // case BingMapsStyle.ROAD: - // stylePrefix = 'r'; - // break; - // } - // - // return { - // "authenticationResultCode" : "ValidCredentials", - // "brandLogoUri" : "http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png", - // "copyright" : "Copyright © 2014 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.", - // "resourceSets" : [{ - // "estimatedTotal" : 1, - // "resources" : [{ - // "__type" : "ImageryMetadata:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1", - // "imageHeight" : 256, - // "imageUrl" : "http:\/\/ecn.{subdomain}.tiles.virtualearth.net.fake.invalid\/tiles\/" + stylePrefix + "{quadkey}.jpeg?g=3031&mkt={culture}", - // "imageUrlSubdomains" : ["t0", "t1", "t2", "t3"], - // "imageWidth" : 256, - // "imageryProviders" : [{ - // "attribution" : "© 2014 DigitalGlobe", - // "coverageAreas" : [{ - // "bbox" : [-67, -179.99, 27, 0], - // "zoomMax" : 21, - // "zoomMin" : 14 - // }, { - // "bbox" : [27, -179.99, 87, -126.5], - // "zoomMax" : 21, - // "zoomMin" : 14 - // }, { - // "bbox" : [48.4, -126.5, 87, -5.75], - // "zoomMax" : 21, - // "zoomMin" : 14 - // }] - // }, { - // "attribution" : "Image courtesy of NASA", - // "coverageAreas" : [{ - // "bbox" : [-90, -180, 90, 180], - // "zoomMax" : 8, - // "zoomMin" : 1 - // }] - // }], - // "vintageEnd" : null, - // "vintageStart" : null, - // "zoomMax" : 21, - // "zoomMin" : 1 - // }] - // }], - // "statusCode" : 200, - // "statusDescription" : "OK", - // "traceId" : "ea754a48ccdb4dd297c8f35350e0f0d9|BN20130533|02.00.106.1600|" - // }; - // } - // - // function installFakeMetadataRequest(url, mapStyle, proxy) { - // var expectedUrl = url + '/REST/v1/Imagery/Metadata/' + mapStyle + '?incl=ImageryProviders&key='; - // if (defined(proxy)) { - // expectedUrl = proxy.getURL(expectedUrl); - // } - // - // loadJsonp.loadAndExecuteScript = function(url, functionName) { - // expect(url).toStartWith(expectedUrl); - // - // setTimeout(function() { - // window[functionName](createFakeMetadataResponse(mapStyle)); - // }, 1); - // }; - // } - // - // function installFakeImageRequest(expectedUrl) { - // loadImage.createImage = function(url, crossOrigin, deferred) { - // if (/^blob:/.test(url)) { - // // load blob url normally - // loadImage.defaultCreateImage(url, crossOrigin, deferred); - // } else { - // if (defined(expectedUrl)) { - // expect(url).toEqual(expectedUrl); - // } - // // Just return any old image. - // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); - // } - // }; - // - // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { - // if (defined(expectedUrl)) { - // expect(url).toEqual(expectedUrl); - // } - // - // // Just return any old image. - // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); - // }; - // } - // - // it('resolves readyPromise', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // return provider.readyPromise.then(function(result) { - // expect(result).toBe(true); - // expect(provider.ready).toBe(true); - // }); - // }); - // - // it('rejects readyPromise on error', function() { - // var url = 'host.invalid'; - // var provider = new BingMapsImageryProvider({ - // url : url - // }); - // - // return provider.readyPromise.then(function () { - // fail('should not resolve'); - // }).otherwise(function (e) { - // expect(provider.ready).toBe(false); - // expect(e.message).toContain(url); - // }); - // }); - // - // it('returns valid value for hasAlphaChannel', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.AERIAL; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // expect(typeof provider.hasAlphaChannel).toBe('boolean'); - // }); - // }); - // - // it('can provide a root tile', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // expect(provider.url).toEqual(url); - // expect(provider.key).toBeDefined(); - // expect(provider.mapStyle).toEqual(mapStyle); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // expect(provider.tileWidth).toEqual(256); - // expect(provider.tileHeight).toEqual(256); - // expect(provider.maximumLevel).toEqual(20); - // expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); - // expect(provider.tileDiscardPolicy).toBeInstanceOf(DiscardMissingTileImagePolicy); - // expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle); - // expect(provider.credit).toBeInstanceOf(Object); - // - // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt='); - // - // return provider.requestImage(0, 0, 0).then(function(image) { - // expect(image).toBeInstanceOf(Image); - // }); - // }); - // }); - // - // it('sets correct culture in tile requests', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.AERIAL_WITH_LABELS; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var culture = 'ja-jp'; - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle, - // culture : culture - // }); - // - // expect(provider.culture).toEqual(culture); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/h0.jpeg?g=3031&mkt=ja-jp'); - // - // return provider.requestImage(0, 0, 0).then(function(image) { - // expect(image).toBeInstanceOf(Image); - // }); - // }); - // }); - // - // it('routes requests through a proxy if one is specified', function() { - // var url = 'http://foo.bar.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // var proxy = new DefaultProxy('/proxy/'); - // - // installFakeMetadataRequest(url, mapStyle, proxy); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle, - // proxy : proxy - // }); - // - // expect(provider.url).toEqual(url); - // expect(provider.proxy).toEqual(proxy); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // installFakeImageRequest(proxy.getURL('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt=')); - // - // return provider.requestImage(0, 0, 0).then(function(image) { - // expect(image).toBeInstanceOf(Image); - // }); - // }); - // }); - // - // it('raises error on invalid url', function() { - // var url = 'host.invalid'; - // var provider = new BingMapsImageryProvider({ - // url : url - // }); - // - // var errorEventRaised = false; - // provider.errorEvent.addEventListener(function(error) { - // expect(error.message).toContain(url); - // errorEventRaised = true; - // }); - // - // return pollToPromise(function() { - // return provider.ready || errorEventRaised; - // }).then(function() { - // expect(provider.ready).toEqual(false); - // expect(errorEventRaised).toEqual(true); - // }); - // }); - // - // it('raises error event when image cannot be loaded', function() { - // var url = 'http://foo.bar.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // var layer = new ImageryLayer(provider); - // - // var tries = 0; - // provider.errorEvent.addEventListener(function(error) { - // expect(error.timesRetried).toEqual(tries); - // ++tries; - // if (tries < 3) { - // error.retry = true; - // } - // }); - // - // loadImage.createImage = function(url, crossOrigin, deferred) { - // if (/^blob:/.test(url)) { - // // load blob url normally - // loadImage.defaultCreateImage(url, crossOrigin, deferred); - // } else if (tries === 2) { - // // Succeed after 2 tries - // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); - // } else { - // // fail - // setTimeout(function() { - // deferred.reject(); - // }, 1); - // } - // }; - // - // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { - // if (tries === 2) { - // // Succeed after 2 tries - // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); - // } else { - // // fail - // setTimeout(function() { - // deferred.reject(); - // }, 1); - // } - // }; - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // var imagery = new Imagery(layer, 0, 0, 0); - // imagery.addReference(); - // layer._requestImagery(imagery); - // - // return pollToPromise(function() { - // return imagery.state === ImageryState.RECEIVED; - // }).then(function() { - // expect(imagery.image).toBeInstanceOf(Image); - // expect(tries).toEqual(2); - // imagery.releaseReference(); - // }); - // }); - // }); + it('constructor throws when url is not specified', function() { + function constructWithoutServer() { + return new GoogleEarthEnterpriseMetadata(); + } + expect(constructWithoutServer).toThrowDeveloperError(); + }); + + it('getMetadata throws when url is not specified', function() { + function constructWithoutServer() { + return GoogleEarthEnterpriseMetadata.getMetadata(); + } + expect(constructWithoutServer).toThrowDeveloperError(); + }); + + it('releaseMetadata throws when object is not specified', function() { + function constructWithoutServer() { + return GoogleEarthEnterpriseMetadata.releaseMetadata(); + } + expect(constructWithoutServer).toThrowDeveloperError(); + }); + + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + function createFakeMetadataResponse() { + var numInstances = 2; + var buffer = new ArrayBuffer(32+numInstances*32); + var dv = new DataView(buffer); + + var offset = 0; + + dv.setUint32(offset, 32301, true); + offset += sizeOfUint32; + + dv.setUint32(offset, 1, true); + offset += sizeOfUint32; + + dv.setUint32(offset, 2, true); + offset += sizeOfUint32; + + dv.setInt32(offset, numInstances, true); + offset += sizeOfInt32; + + dv.setInt32(offset, 32, true); + offset += sizeOfInt32; + + dv.setInt32(offset, 32 + 32*numInstances, true); + offset += sizeOfInt32; + + dv.setInt32(offset, 0, true); + offset += sizeOfInt32; + + dv.setInt32(offset, 0, true); + offset += sizeOfInt32; + + for (var i = 0; i < numInstances; ++i) { + if (i === (numInstances-1)) { + dv.setUint8(offset, 0x40); + } else { + dv.setUint8(offset, 0x41); + } + ++offset; + + ++offset; // 2 byte align + + dv.setUint16(offset, 2, true); + offset += sizeOfUint16; + + dv.setUint16(offset, 1, true); + offset += sizeOfUint16; + + dv.setUint16(offset, 1, true); + offset += sizeOfUint16; + + // Number of channels stored in the dataBuffer + //var numChannels = dv.getUint16(offset, true); + offset += sizeOfUint16; + + offset += sizeOfUint16; // 4 byte align + + // Channel type offset into dataBuffer + //var typeOffset = dv.getInt32(offset, true); + offset += sizeOfInt32; + + // Channel version offset into dataBuffer + //var versionOffset = dv.getInt32(offset, true); + offset += sizeOfInt32; + + offset += 8; // Ignore image neighbors for now + + // Data providers aren't used + ++offset; // Image provider + ++offset; // Terrain provider + offset += sizeOfUint16; // 4 byte align + } + + return buffer; + } + + it('resolves readyPromise', function() { + var baseurl = 'http://fake.fake.invalid/'; + + var response = createFakeMetadataResponse(); + spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { + expect(url).toEqual(baseurl + 'flatfile?q2-0-q.1'); + expect(responseType).toEqual('arraybuffer'); + deferred.resolve(response); + }); + + spyOn(GoogleEarthEnterpriseMetadata, 'decode').and.callFake(function(data) { + expect(data).toEqual(response); + return data; + }); + + spyOn(GoogleEarthEnterpriseMetadata, 'uncompressPacket').and.callFake(function(data) { + expect(data).toEqual(response); + return new Uint8Array(data); + }); + + var provider = new GoogleEarthEnterpriseMetadata(baseurl); + + return provider.readyPromise.then(function(result) { + expect(result).toBe(true); + + var tileInfo = provider._tileInfo['0']; + expect(tileInfo).toBeDefined(); + expect(tileInfo._bits).toEqual(0x40); + expect(tileInfo.cnodeVersion).toEqual(2); + expect(tileInfo.imageryVersion).toEqual(1); + expect(tileInfo.terrainVersion).toEqual(1); + expect(tileInfo.ancestorHasTerrain).toEqual(false); + expect(tileInfo.terrainState).toEqual(0); + }); + }); + + it('rejects readyPromise on error', function() { + var url = 'host.invalid/'; + var provider = new GoogleEarthEnterpriseMetadata(url); + + return provider.readyPromise.then(function () { + fail('should not resolve'); + }).otherwise(function (e) { + expect(e.statusCode).toEqual(404); + }); + }); + + it('routes requests through a proxy if one is specified', function() { + var proxy = new DefaultProxy('/proxy/'); + var baseurl = 'http://fake.fake.invalid/'; + + var response = createFakeMetadataResponse(); + spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { + expect(url).toEqual(proxy.getURL(baseurl + 'flatfile?q2-0-q.1')); + expect(responseType).toEqual('arraybuffer'); + deferred.resolve(response); + }); + + spyOn(GoogleEarthEnterpriseMetadata, 'decode').and.callFake(function(data) { + expect(data).toEqual(response); + return data; + }); + + spyOn(GoogleEarthEnterpriseMetadata, 'uncompressPacket').and.callFake(function(data) { + expect(data).toEqual(response); + return new Uint8Array(data); + }); + + var provider = new GoogleEarthEnterpriseMetadata(baseurl, proxy); + + expect(provider.url).toEqual(baseurl); + expect(provider.proxy).toEqual(proxy); + + return provider.readyPromise.then(function(result) { + expect(result).toBe(true); + + var tileInfo = provider._tileInfo['0']; + expect(tileInfo).toBeDefined(); + expect(tileInfo._bits).toEqual(0x40); + expect(tileInfo.cnodeVersion).toEqual(2); + expect(tileInfo.imageryVersion).toEqual(1); + expect(tileInfo.terrainVersion).toEqual(1); + expect(tileInfo.ancestorHasTerrain).toEqual(false); + expect(tileInfo.terrainState).toEqual(0); + }); + }); }); From 999155cbb2e344202d3b7fd21bdf0574c0f981cb Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 4 Apr 2017 16:36:08 -0400 Subject: [PATCH 22/60] Added GEE terrain tests. --- .../GoogleEarthEnterpriseTerrainProvider.js | 35 +- Source/Scene/Globe.js | 7 +- .../GoogleEarthEnterpriseImageryProvider.js | 30 +- Source/Scene/ImageryLayer.js | 5 + ...oogleEarthEnterpriseTerrainProviderSpec.js | 637 ++++++++---------- Specs/Data/GoogleEarthEnterprise/gee.terrain | Bin 0 -> 4213 bytes 6 files changed, 336 insertions(+), 378 deletions(-) create mode 100644 Specs/Data/GoogleEarthEnterprise/gee.terrain diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index fb663e78f00a..f2a17589557e 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -3,9 +3,11 @@ define([ './Cartesian2', './Cartesian3', './Cartographic', + './Credit', './defaultValue', './defined', './defineProperties', + './destroyObject', './DeveloperError', './Ellipsoid', './Event', @@ -22,9 +24,11 @@ define([ Cartesian2, Cartesian3, Cartographic, + Credit, defaultValue, defined, defineProperties, + destroyObject, DeveloperError, Ellipsoid, Event, @@ -54,9 +58,10 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. - * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. * @param {Proxy} [options.proxy] A proxy to use for requests. This object is * expected to have a getURL function which returns the proxied URL, if needed. + * @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. * * @see CesiumTerrainProvider * @see GoogleEarthEnterpriseImageryProvider @@ -87,6 +92,12 @@ define([ ellipsoid : options.ellipsoid }); + var credit = options.credit; + if (typeof credit === 'string') { + credit = new Credit(credit); + } + this._credit = credit; + // 1024 was the initial size of the heightmap before decimation in GEE this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, 1024, this._tilingScheme.getNumberOfXTilesAtLevel(0)); @@ -181,7 +192,7 @@ define([ */ readyPromise : { get : function() { - return this._readyPromise.promise; + return this._readyPromise; } }, @@ -194,7 +205,7 @@ define([ */ credit : { get : function() { - return undefined; + return this._credit; } }, @@ -467,9 +478,27 @@ define([ * @returns {Boolean} Undefined if not supported, otherwise true or false. */ GoogleEarthEnterpriseTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { + var metadata = this._metadata; + var info = metadata.getTileInformation(x, y, level); + if (defined(info)) { + return info.hasTerrain(); + } return undefined; }; + /** + * Releases resources used by this provider + */ + GoogleEarthEnterpriseTerrainProvider.prototype.destroy = function() { + var metadata = this._metadata; + if (defined(metadata)) { + this._metadata = undefined; + GoogleEarthEnterpriseMetadata.releaseMetadata(metadata); + } + + return destroyObject(this); + }; + // // Functions to handle imagery packets // diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index f947b21caaf5..238f3bf6dc7d 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -248,7 +248,12 @@ define([ return this._terrainProvider; }, set : function(value) { - if (value !== this._terrainProvider) { + var oldTerrainProvider = this._terrainProvider; + if (value !== oldTerrainProvider) { + if (defined(oldTerrainProvider) && defined(oldTerrainProvider.destroy)) { + oldTerrainProvider.destroy(); + } + this._terrainProvider = value; this._terrainProviderChanged.raiseEvent(value); } diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 07494578d68c..1e45469a776a 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -1,8 +1,10 @@ /*global define*/ define([ + '../Core/Credit', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', '../Core/Event', '../Core/GeographicTilingScheme', @@ -16,9 +18,11 @@ define([ '../ThirdParty/protobuf-minimal', '../ThirdParty/when' ], function( + Credit, defaultValue, defined, defineProperties, + destroyObject, DeveloperError, Event, GeographicTilingScheme, @@ -63,12 +67,13 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {Proxy} [options.proxy] A proxy to use for requests. This object is + * expected to have a getURL function which returns the proxied URL, if needed. * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile * is invalid and should be discarded. If this value is not specified, a default * is to discard tiles that fail to download. - * @param {Proxy} [options.proxy] A proxy to use for requests. This object is - * expected to have a getURL function which returns the proxied URL, if needed. + * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas. * * @see ArcGisMapServerImageryProvider * @see GoogleEarthImageryProvider @@ -107,6 +112,12 @@ define([ ellipsoid : options.ellipsoid }); + var credit = options.credit; + if (typeof credit === 'string') { + credit = new Credit(credit); + } + this._credit = credit; + this._tileWidth = 256; this._tileHeight = 256; this._maximumLevel = 23; @@ -335,7 +346,7 @@ define([ */ credit : { get : function() { - return undefined; + return this._credit; } }, @@ -498,6 +509,19 @@ define([ return undefined; }; + /** + * Releases resources used by this provider + */ + GoogleEarthEnterpriseImageryProvider.prototype.destroy = function() { + var metadata = this._metadata; + if (defined(metadata)) { + this._metadata = undefined; + GoogleEarthEnterpriseMetadata.releaseMetadata(metadata); + } + + return destroyObject(this); + }; + // // Functions to handle imagery packets diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 9fef8eeac772..7fa12ba481c6 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -357,6 +357,11 @@ define([ * @see ImageryLayer#isDestroyed */ ImageryLayer.prototype.destroy = function() { + var imageryProvider = this._imageryProvider; + if (defined(imageryProvider.destroy)) { + imageryProvider.destroy(); + } + return destroyObject(this); }; diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index 63649a14e4fa..d32478981c66 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -4,406 +4,301 @@ defineSuite([ 'Core/DefaultProxy', 'Core/defaultValue', 'Core/defined', + 'Core/Ellipsoid', + 'Core/GeographicTilingScheme', + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/GoogleEarthEnterpriseTerrainData', 'Core/loadImage', 'Core/loadWithXhr', 'Core/Math', 'Core/TerrainProvider', + 'Specs/pollToPromise', 'ThirdParty/when' ], function( GoogleEarthEnterpriseTerrainProvider, DefaultProxy, defaultValue, defined, + Ellipsoid, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + GoogleEarthEnterpriseTerrainData, loadImage, loadWithXhr, CesiumMath, TerrainProvider, + pollToPromise, when) { 'use strict'; - /* - it('request Image populates the correct metadata', function() { - var quad = '0123'; - var index = 0; - var provider; - spyOn(GoogleEarthEnterpriseProvider.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { - quadKey = defaultValue(quadKey, '') + index.toString(); - this._tileInfo[quadKey] = { - bits : 0xFF, - cnodeVersion : 1, - imageryVersion : 1, - terrainVersion : 1 - }; - index = (index + 1) % 4; + function installMockGetQuadTreePacket() { + spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + quadKey = defaultValue(quadKey, ''); + this._tileInfo[quadKey + '0'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + this._tileInfo[quadKey + '1'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + this._tileInfo[quadKey + '2'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + this._tileInfo[quadKey + '3'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); return when(); }); + } - spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { - expect(url).toEqual('http://test.server/3d/flatfile?f1-0' + quad + '-i.1'); - expect(responseType).toEqual('arraybuffer'); - deferred.resolve(); + var terrainProvider; + function waitForTile(level, x, y, f) { + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' }); - provider = new GoogleEarthEnterpriseProvider({ - url: 'http://test.server/3d' - }); + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + var promise = terrainProvider.requestTileGeometry(level, x, y); - var tileXY = GoogleEarthEnterpriseProvider.quadKeyToTileXY(quad); - return provider.requestImage(tileXY.x, tileXY.y, tileXY.level) - .then(function(image) { - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket.calls.count()).toEqual(4); - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith(); - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('0', 1); - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('01', 1); - expect(GoogleEarthEnterpriseProvider.prototype._getQuadTreePacket).toHaveBeenCalledWith('012', 1); - - var tileInfo = provider._tileInfo; - expect(tileInfo['0']).toBeDefined(); - expect(tileInfo['01']).toBeDefined(); - expect(tileInfo['012']).toBeDefined(); - expect(tileInfo['0123']).toBeDefined(); + return when(promise, f, function(error) { + expect('requestTileGeometry').toBe('returning a tile.'); // test failure }); + }); + } + + afterEach(function() { + loadWithXhr.load = loadWithXhr.defaultLoad; + if (defined(terrainProvider)) { + terrainProvider.destroy(); + terrainProvider = undefined; + } }); - */ it('conforms to TerrainProvider interface', function() { expect(GoogleEarthEnterpriseTerrainProvider).toConformToInterface(TerrainProvider); }); - // it('constructor throws when url is not specified', function() { - // function constructWithoutServer() { - // return new BingMapsImageryProvider({ - // mapStyle : BingMapsStyle.AERIAL - // }); - // } - // expect(constructWithoutServer).toThrowDeveloperError(); - // }); - // - // function createFakeMetadataResponse(mapStyle) { - // var stylePrefix = 'a'; - // switch (mapStyle) { - // case BingMapsStyle.AERIAL_WITH_LABELS: - // stylePrefix = 'h'; - // break; - // case BingMapsStyle.ROAD: - // stylePrefix = 'r'; - // break; - // } - // - // return { - // "authenticationResultCode" : "ValidCredentials", - // "brandLogoUri" : "http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png", - // "copyright" : "Copyright © 2014 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.", - // "resourceSets" : [{ - // "estimatedTotal" : 1, - // "resources" : [{ - // "__type" : "ImageryMetadata:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1", - // "imageHeight" : 256, - // "imageUrl" : "http:\/\/ecn.{subdomain}.tiles.virtualearth.net.fake.invalid\/tiles\/" + stylePrefix + "{quadkey}.jpeg?g=3031&mkt={culture}", - // "imageUrlSubdomains" : ["t0", "t1", "t2", "t3"], - // "imageWidth" : 256, - // "imageryProviders" : [{ - // "attribution" : "© 2014 DigitalGlobe", - // "coverageAreas" : [{ - // "bbox" : [-67, -179.99, 27, 0], - // "zoomMax" : 21, - // "zoomMin" : 14 - // }, { - // "bbox" : [27, -179.99, 87, -126.5], - // "zoomMax" : 21, - // "zoomMin" : 14 - // }, { - // "bbox" : [48.4, -126.5, 87, -5.75], - // "zoomMax" : 21, - // "zoomMin" : 14 - // }] - // }, { - // "attribution" : "Image courtesy of NASA", - // "coverageAreas" : [{ - // "bbox" : [-90, -180, 90, 180], - // "zoomMax" : 8, - // "zoomMin" : 1 - // }] - // }], - // "vintageEnd" : null, - // "vintageStart" : null, - // "zoomMax" : 21, - // "zoomMin" : 1 - // }] - // }], - // "statusCode" : 200, - // "statusDescription" : "OK", - // "traceId" : "ea754a48ccdb4dd297c8f35350e0f0d9|BN20130533|02.00.106.1600|" - // }; - // } - // - // function installFakeMetadataRequest(url, mapStyle, proxy) { - // var expectedUrl = url + '/REST/v1/Imagery/Metadata/' + mapStyle + '?incl=ImageryProviders&key='; - // if (defined(proxy)) { - // expectedUrl = proxy.getURL(expectedUrl); - // } - // - // loadJsonp.loadAndExecuteScript = function(url, functionName) { - // expect(url).toStartWith(expectedUrl); - // - // setTimeout(function() { - // window[functionName](createFakeMetadataResponse(mapStyle)); - // }, 1); - // }; - // } - // - // function installFakeImageRequest(expectedUrl) { - // loadImage.createImage = function(url, crossOrigin, deferred) { - // if (/^blob:/.test(url)) { - // // load blob url normally - // loadImage.defaultCreateImage(url, crossOrigin, deferred); - // } else { - // if (defined(expectedUrl)) { - // expect(url).toEqual(expectedUrl); - // } - // // Just return any old image. - // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); - // } - // }; - // - // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { - // if (defined(expectedUrl)) { - // expect(url).toEqual(expectedUrl); - // } - // - // // Just return any old image. - // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); - // }; - // } - // - // it('resolves readyPromise', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // return provider.readyPromise.then(function(result) { - // expect(result).toBe(true); - // expect(provider.ready).toBe(true); - // }); - // }); - // - // it('rejects readyPromise on error', function() { - // var url = 'host.invalid'; - // var provider = new BingMapsImageryProvider({ - // url : url - // }); - // - // return provider.readyPromise.then(function () { - // fail('should not resolve'); - // }).otherwise(function (e) { - // expect(provider.ready).toBe(false); - // expect(e.message).toContain(url); - // }); - // }); - // - // it('returns valid value for hasAlphaChannel', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.AERIAL; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // expect(typeof provider.hasAlphaChannel).toBe('boolean'); - // }); - // }); - // - // it('can provide a root tile', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // expect(provider.url).toEqual(url); - // expect(provider.key).toBeDefined(); - // expect(provider.mapStyle).toEqual(mapStyle); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // expect(provider.tileWidth).toEqual(256); - // expect(provider.tileHeight).toEqual(256); - // expect(provider.maximumLevel).toEqual(20); - // expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); - // expect(provider.tileDiscardPolicy).toBeInstanceOf(DiscardMissingTileImagePolicy); - // expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle); - // expect(provider.credit).toBeInstanceOf(Object); - // - // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt='); - // - // return provider.requestImage(0, 0, 0).then(function(image) { - // expect(image).toBeInstanceOf(Image); - // }); - // }); - // }); - // - // it('sets correct culture in tile requests', function() { - // var url = 'http://fake.fake.invalid'; - // var mapStyle = BingMapsStyle.AERIAL_WITH_LABELS; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var culture = 'ja-jp'; - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle, - // culture : culture - // }); - // - // expect(provider.culture).toEqual(culture); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // installFakeImageRequest('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/h0.jpeg?g=3031&mkt=ja-jp'); - // - // return provider.requestImage(0, 0, 0).then(function(image) { - // expect(image).toBeInstanceOf(Image); - // }); - // }); - // }); - // - // it('routes requests through a proxy if one is specified', function() { - // var url = 'http://foo.bar.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // var proxy = new DefaultProxy('/proxy/'); - // - // installFakeMetadataRequest(url, mapStyle, proxy); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle, - // proxy : proxy - // }); - // - // expect(provider.url).toEqual(url); - // expect(provider.proxy).toEqual(proxy); - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // installFakeImageRequest(proxy.getURL('http://ecn.t0.tiles.virtualearth.net.fake.invalid/tiles/r0.jpeg?g=3031&mkt=')); - // - // return provider.requestImage(0, 0, 0).then(function(image) { - // expect(image).toBeInstanceOf(Image); - // }); - // }); - // }); - // - // it('raises error on invalid url', function() { - // var url = 'host.invalid'; - // var provider = new BingMapsImageryProvider({ - // url : url - // }); - // - // var errorEventRaised = false; - // provider.errorEvent.addEventListener(function(error) { - // expect(error.message).toContain(url); - // errorEventRaised = true; - // }); - // - // return pollToPromise(function() { - // return provider.ready || errorEventRaised; - // }).then(function() { - // expect(provider.ready).toEqual(false); - // expect(errorEventRaised).toEqual(true); - // }); - // }); - // - // it('raises error event when image cannot be loaded', function() { - // var url = 'http://foo.bar.invalid'; - // var mapStyle = BingMapsStyle.ROAD; - // - // installFakeMetadataRequest(url, mapStyle); - // installFakeImageRequest(); - // - // var provider = new BingMapsImageryProvider({ - // url : url, - // mapStyle : mapStyle - // }); - // - // var layer = new ImageryLayer(provider); - // - // var tries = 0; - // provider.errorEvent.addEventListener(function(error) { - // expect(error.timesRetried).toEqual(tries); - // ++tries; - // if (tries < 3) { - // error.retry = true; - // } - // }); - // - // loadImage.createImage = function(url, crossOrigin, deferred) { - // if (/^blob:/.test(url)) { - // // load blob url normally - // loadImage.defaultCreateImage(url, crossOrigin, deferred); - // } else if (tries === 2) { - // // Succeed after 2 tries - // loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); - // } else { - // // fail - // setTimeout(function() { - // deferred.reject(); - // }, 1); - // } - // }; - // - // loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { - // if (tries === 2) { - // // Succeed after 2 tries - // loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); - // } else { - // // fail - // setTimeout(function() { - // deferred.reject(); - // }, 1); - // } - // }; - // - // return pollToPromise(function() { - // return provider.ready; - // }).then(function() { - // var imagery = new Imagery(layer, 0, 0, 0); - // imagery.addReference(); - // layer._requestImagery(imagery); - // - // return pollToPromise(function() { - // return imagery.state === ImageryState.RECEIVED; - // }).then(function() { - // expect(imagery.image).toBeInstanceOf(Image); - // expect(tries).toEqual(2); - // imagery.releaseReference(); - // }); - // }); - // }); + it('constructor throws if url is not provided', function() { + expect(function() { + return new GoogleEarthEnterpriseTerrainProvider(); + }).toThrowDeveloperError(); + + expect(function() { + return new GoogleEarthEnterpriseTerrainProvider({ + }); + }).toThrowDeveloperError(); + }); + + it('resolves readyPromise', function() { + installMockGetQuadTreePacket(); + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' + }); + + return terrainProvider.readyPromise.then(function (result) { + expect(result).toBe(true); + expect(terrainProvider.ready).toBe(true); + }); + }); + + it('uses geographic tiling scheme by default', function() { + installMockGetQuadTreePacket(); + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + var tilingScheme = terrainProvider.tilingScheme; + expect(tilingScheme instanceof GeographicTilingScheme).toBe(true); + }); + }); + + it('can use a custom ellipsoid', function() { + installMockGetQuadTreePacket(); + + var ellipsoid = new Ellipsoid(1, 2, 3); + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url', + ellipsoid : ellipsoid + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + expect(terrainProvider.tilingScheme.ellipsoid).toEqual(ellipsoid); + }); + }); + + it('has error event', function() { + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' + }); + expect(terrainProvider.errorEvent).toBeDefined(); + expect(terrainProvider.errorEvent).toBe(terrainProvider.errorEvent); + }); + + it('returns reasonable geometric error for various levels', function() { + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' + }); + + expect(terrainProvider.getLevelMaximumGeometricError(0)).toBeGreaterThan(0.0); + expect(terrainProvider.getLevelMaximumGeometricError(0)).toEqualEpsilon(terrainProvider.getLevelMaximumGeometricError(1) * 2.0, CesiumMath.EPSILON10); + expect(terrainProvider.getLevelMaximumGeometricError(1)).toEqualEpsilon(terrainProvider.getLevelMaximumGeometricError(2) * 2.0, CesiumMath.EPSILON10); + }); + + it('logo is undefined if credit is not provided', function() { + installMockGetQuadTreePacket(); + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + expect(terrainProvider.credit).toBeUndefined(); + }); + }); + + it('logo is defined if credit is provided', function() { + installMockGetQuadTreePacket(); + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url', + credit : 'thanks to our awesome made up contributors!' + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + expect(terrainProvider.credit).toBeDefined(); + }); + }); + + it('has a water mask is false', function() { + installMockGetQuadTreePacket(); + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + expect(terrainProvider.hasWaterMask).toBe(false); + }); + }); + + it('has vertex normals is false', function() { + installMockGetQuadTreePacket(); + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : 'made/up/url' + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + expect(terrainProvider.hasVertexNormals).toBe(false); + }); + }); + + describe('requestTileGeometry', function() { + it('uses the proxy if one is supplied', function() { + installMockGetQuadTreePacket(); + var baseUrl = 'made/up/url'; + + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + expect(url.indexOf('/proxy/?')).toBe(0); + + // Just return any old file, as long as its big enough + loadWithXhr.defaultLoad('Data/EarthOrientationParameters/IcrfToFixedStkComponentsRotationData.json', responseType, method, data, headers, deferred); + }; + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : baseUrl, + proxy : new DefaultProxy('/proxy/') + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + return terrainProvider.requestTileGeometry(0, 0, 0); + }); + }); + + it('provides GoogleEarthEnterpriseTerrainData', function() { + installMockGetQuadTreePacket(); + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + loadWithXhr.defaultLoad('Data/GoogleEarthEnterprise/gee.terrain', responseType, method, data, headers, deferred); + }; + + return waitForTile(0, 0, 0, function(loadedData) { + expect(loadedData).toBeInstanceOf(GoogleEarthEnterpriseTerrainData); + }); + }); + + it('returns undefined if too many requests are already in progress', function() { + installMockGetQuadTreePacket(); + var baseUrl = 'made/up/url'; + + var deferreds = []; + + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + // Do nothing, so requests never complete + deferreds.push(deferred); + }; + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : baseUrl + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + var promise = terrainProvider.requestTileGeometry(0, 0, 0); + expect(promise).toBeDefined(); + + var i; + for (i = 0; i < 10; ++i) { + promise = terrainProvider.requestTileGeometry(0, 0, 0); + } + + return terrainProvider.requestTileGeometry(0, 0, 0) + .then(function(terrainData) { + expect(terrainData).toBeUndefined(); + + for (i = 0; i < deferreds.length; ++i) { + deferreds[i].resolve(); + } + }); + }); + }); + + it('supports getTileDataAvailable()', function() { + installMockGetQuadTreePacket(); + var baseUrl = 'made/up/url'; + + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + loadWithXhr.defaultLoad('Data/CesiumTerrainTileJson/tile.terrain', responseType, method, data, headers, deferred); + }; + + terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ + url : baseUrl + }); + + return pollToPromise(function() { + return terrainProvider.ready; + }).then(function() { + var tileInfo = terrainProvider._metadata._tileInfo; + var info = tileInfo[GoogleEarthEnterpriseMetadata.tileXYToQuadKey(0, 1, 0)]; + info._bits = 0x7F; // Remove terrain bit from 0,1,0 tile + + expect(terrainProvider.getTileDataAvailable(0, 0, 0)).toBe(true); + expect(terrainProvider.getTileDataAvailable(0, 1, 0)).toBe(false); + expect(terrainProvider.getTileDataAvailable(1, 0, 0)).toBe(true); + expect(terrainProvider.getTileDataAvailable(1, 1, 0)).toBe(true); + expect(terrainProvider.getTileDataAvailable(0, 0, 2)).toBeUndefined(); + }); + }); + }); }); diff --git a/Specs/Data/GoogleEarthEnterprise/gee.terrain b/Specs/Data/GoogleEarthEnterprise/gee.terrain new file mode 100644 index 0000000000000000000000000000000000000000..38773d668e4be2854ce2c0831a8731de48383e0e GIT binary patch literal 4213 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 Date: Tue, 4 Apr 2017 17:27:15 -0400 Subject: [PATCH 23/60] More GEE test fixes. Almost done imagery provider tests. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 11 +- .../GoogleEarthEnterpriseImageryProvider.js | 2 +- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 2 +- ...oogleEarthEnterpriseImageryProviderSpec.js | 274 ++++++++++++++++++ 4 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 8ba830988e6c..b8c6b1c6bd77 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -143,9 +143,14 @@ define([ this.refCount = 1; + var that = this; this._readyPromise = this._getQuadTreePacket() .then(function() { return true; + }) + .otherwise(function(e) { + var message = 'An error occurred while accessing ' + getMetadataUrl(that, '', 1) + '.'; + throw new RuntimeError(message); }); } @@ -379,7 +384,7 @@ define([ GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket = function(quadKey, version) { version = defaultValue(version, 1); quadKey = defaultValue(quadKey, ''); - var url = this._url + 'flatfile?q2-0' + quadKey + '-q.' + version.toString(); + var url = getMetadataUrl(this, quadKey, version); var proxy = this._proxy; if (defined(proxy)) { url = proxy.getURL(url); @@ -619,5 +624,9 @@ define([ return this._tileInfo[quadkey]; }; + function getMetadataUrl(that, quadKey, version) { + return that._url + 'flatfile?q2-0' + quadKey + '-q.' + version.toString(); + } + return GoogleEarthEnterpriseMetadata; }); diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 1e45469a776a..1d3f5e00e06f 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -333,7 +333,7 @@ define([ */ readyPromise : { get : function() { - return this._readyPromise.promise; + return this._readyPromise; } }, diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index 2690eaa1ac1a..f9d683aea617 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -258,7 +258,7 @@ defineSuite([ return provider.readyPromise.then(function () { fail('should not resolve'); }).otherwise(function (e) { - expect(e.statusCode).toEqual(404); + expect(e.message).toContain(url); }); }); diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js new file mode 100644 index 000000000000..084330701128 --- /dev/null +++ b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js @@ -0,0 +1,274 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/GoogleEarthEnterpriseImageryProvider', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/defined', + 'Core/GeographicTilingScheme', + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/loadImage', + 'Core/loadWithXhr', + 'Core/Rectangle', + 'Scene/DiscardMissingTileImagePolicy', + 'Scene/Imagery', + 'Scene/ImageryLayer', + 'Scene/ImageryProvider', + 'Scene/ImageryState', + 'Specs/pollToPromise', + 'ThirdParty/when' +], function( + GoogleEarthEnterpriseImageryProvider, + DefaultProxy, + defaultValue, + defined, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + loadImage, + loadWithXhr, + Rectangle, + DiscardMissingTileImagePolicy, + Imagery, + ImageryLayer, + ImageryProvider, + ImageryState, + pollToPromise, + when) { + 'use strict'; + + afterEach(function() { + loadImage.createImage = loadImage.defaultCreateImage; + loadWithXhr.load = loadWithXhr.defaultLoad; + }); + + it('conforms to ImageryProvider interface', function() { + expect(GoogleEarthEnterpriseImageryProvider).toConformToInterface(ImageryProvider); + }); + + it('constructor throws when url is not specified', function() { + function constructWithoutServer() { + return new GoogleEarthEnterpriseImageryProvider({ + }); + } + expect(constructWithoutServer).toThrowDeveloperError(); + }); + + function installMockGetQuadTreePacket() { + spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + quadKey = defaultValue(quadKey, ''); + this._tileInfo[quadKey + '0'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + this._tileInfo[quadKey + '1'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + this._tileInfo[quadKey + '2'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + this._tileInfo[quadKey + '3'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + + return when(); + }); + } + + function installFakeImageRequest(expectedUrl) { + loadImage.createImage = function(url, crossOrigin, deferred) { + if (/^blob:/.test(url)) { + // load blob url normally + loadImage.defaultCreateImage(url, crossOrigin, deferred); + } else { + if (defined(expectedUrl)) { + expect(url).toEqual(expectedUrl); + } + // Just return any old image. + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + } + }; + + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + if (defined(expectedUrl)) { + expect(url).toEqual(expectedUrl); + } + + // Just return any old image. + loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); + }; + } + + it('resolves readyPromise', function() { + installMockGetQuadTreePacket(); + var url = 'http://fake.fake.invalid'; + + var provider = new GoogleEarthEnterpriseImageryProvider({ + url : url + }); + + return provider.readyPromise.then(function(result) { + expect(result).toBe(true); + expect(provider.ready).toBe(true); + }); + }); + + it('rejects readyPromise on error', function() { + var url = 'host.invalid'; + var provider = new GoogleEarthEnterpriseImageryProvider({ + url : url + }); + + return provider.readyPromise.then(function () { + fail('should not resolve'); + }).otherwise(function (e) { + expect(provider.ready).toBe(false); + expect(e.message).toContain(url); + }); + }); + + it('returns false for hasAlphaChannel', function() { + installMockGetQuadTreePacket(); + var url = 'http://fake.fake.invalid'; + + var provider = new GoogleEarthEnterpriseImageryProvider({ + url : url + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(typeof provider.hasAlphaChannel).toBe('boolean'); + expect(provider.hasAlphaChannel).toBe(false); + }); + }); + + it('can provide a root tile', function() { + installMockGetQuadTreePacket(); + var url = 'http://fake.fake.invalid/'; + + var provider = new GoogleEarthEnterpriseImageryProvider({ + url : url + }); + + expect(provider.url).toEqual(url); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.tileWidth).toEqual(256); + expect(provider.tileHeight).toEqual(256); + expect(provider.maximumLevel).toEqual(23); + expect(provider.tilingScheme).toBeInstanceOf(GeographicTilingScheme); + // Defaults to custom tile policy + expect(provider.tileDiscardPolicy).not.toBeInstanceOf(DiscardMissingTileImagePolicy); + expect(provider.rectangle).toEqual(new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI)); + expect(provider.credit).toBeUndefined(); + + installFakeImageRequest('http://fake.fake.invalid/flatfile?f1-03-i.1'); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + it('routes requests through a proxy if one is specified', function() { + installMockGetQuadTreePacket(); + var url = 'http://foo.bar.invalid/'; + + var proxy = new DefaultProxy('/proxy/'); + + var provider = new GoogleEarthEnterpriseImageryProvider({ + url : url, + proxy : proxy + }); + + expect(provider.url).toEqual(url); + expect(provider.proxy).toEqual(proxy); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + installFakeImageRequest(proxy.getURL('http://foo.bar.invalid/flatfile?f1-03-i.1')); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + it('raises error on invalid url', function() { + installMockGetQuadTreePacket(); + var url = 'host.invalid'; + var provider = new GoogleEarthEnterpriseImageryProvider({ + url : url + }); + + var errorEventRaised = false; + provider.errorEvent.addEventListener(function(error) { + expect(error.message).toContain(url); + errorEventRaised = true; + }); + + return pollToPromise(function() { + return provider.ready || errorEventRaised; + }).then(function() { + expect(provider.ready).toEqual(false); + expect(errorEventRaised).toEqual(true); + }); + }); + + it('raises error event when image cannot be loaded', function() { + installMockGetQuadTreePacket(); + //installFakeImageRequest(); + var url = 'http://foo.bar.invalid'; + + var provider = new GoogleEarthEnterpriseImageryProvider({ + url : url + }); + + var layer = new ImageryLayer(provider); + + var tries = 0; + provider.errorEvent.addEventListener(function(error) { + expect(error.timesRetried).toEqual(tries); + ++tries; + if (tries < 3) { + error.retry = true; + } + }); + + loadImage.createImage = function(url, crossOrigin, deferred) { + if (/^blob:/.test(url)) { + // load blob url normally + loadImage.defaultCreateImage(url, crossOrigin, deferred); + } else if (tries === 2) { + // Succeed after 2 tries + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + } else { + // fail + setTimeout(function() { + deferred.reject(); + }, 1); + } + }; + + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + if (tries === 2) { + // Succeed after 2 tries + loadWithXhr.defaultLoad('Data/Images/Red16x16.png', responseType, method, data, headers, deferred); + } else { + // fail + setTimeout(function() { + deferred.reject(); + }, 1); + } + }; + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + var imagery = new Imagery(layer, 0, 0, 0); + imagery.addReference(); + layer._requestImagery(imagery); + + return pollToPromise(function() { + return imagery.state === ImageryState.RECEIVED; + }).then(function() { + expect(imagery.image).toBeInstanceOf(Image); + expect(tries).toEqual(2); + imagery.releaseReference(); + }); + }); + }); +}); From 0b5e4fb7a8453c582198919556ec97c2ee5f7f3a Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 5 Apr 2017 12:17:15 -0400 Subject: [PATCH 24/60] Added imagery tests and fixed a few small issues. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 2 +- .../GoogleEarthEnterpriseTerrainProvider.js | 14 ++- .../GoogleEarthEnterpriseImageryProvider.js | 12 ++- .../GoogleEarthEnterpriseTerrainDataSpec.js | 73 ++----------- ...oogleEarthEnterpriseImageryProviderSpec.js | 102 +++++++++--------- 5 files changed, 81 insertions(+), 122 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index b8c6b1c6bd77..494e08a9d433 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -150,7 +150,7 @@ define([ }) .otherwise(function(e) { var message = 'An error occurred while accessing ' + getMetadataUrl(that, '', 1) + '.'; - throw new RuntimeError(message); + return when.reject(new RuntimeError(message)); }); } diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index f2a17589557e..caa5a3acea20 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -19,7 +19,9 @@ define([ './Math', './Rectangle', './TerrainProvider', - './throttleRequestByServer' + './throttleRequestByServer', + './TileProviderError', + '../ThirdParty/when' ], function( Cartesian2, Cartesian3, @@ -40,7 +42,9 @@ define([ CesiumMath, Rectangle, TerrainProvider, - throttleRequestByServer) { + throttleRequestByServer, + TileProviderError, + when) { 'use strict'; var TerrainState = { @@ -107,10 +111,16 @@ define([ this._ready = false; var that = this; + var metadataError; this._readyPromise = this._metadata.readyPromise .then(function(result) { + TileProviderError.handleSuccess(metadataError); that._ready = result; return result; + }) + .otherwise(function(e) { + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, e.message, undefined, undefined, undefined, e); + return when.reject(e); }); } diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 1d3f5e00e06f..340b2f9122be 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -15,6 +15,7 @@ define([ '../Core/Rectangle', '../Core/RuntimeError', '../Core/throttleRequestByServer', + '../Core/TileProviderError', '../ThirdParty/protobuf-minimal', '../ThirdParty/when' ], function( @@ -33,6 +34,7 @@ define([ Rectangle, RuntimeError, throttleRequestByServer, + TileProviderError, protobuf, when) { 'use strict'; @@ -131,10 +133,16 @@ define([ this._ready = false; var that = this; + var metadataError; this._readyPromise = this._metadata.readyPromise .then(function(result) { + TileProviderError.handleSuccess(metadataError); that._ready = result; return result; + }) + .otherwise(function(e) { + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, e.message, undefined, undefined, undefined, e); + return when.reject(e); }); } @@ -484,10 +492,6 @@ define([ } return loadImageFromTypedArray(a, type); - }) - .otherwise(function(error) { - // Just ignore failures and return invalidImage - return invalidImage; }); }; diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 054985c337c0..687126e8e6f6 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -511,103 +511,48 @@ defineSuite([ }); }); - /* describe('interpolateHeight', function() { var tilingScheme; var rectangle; + var mesh; beforeEach(function() { tilingScheme = new GeographicTilingScheme(); rectangle = tilingScheme.tileXYToRectangle(7, 6, 5); - }); - - it('clamps coordinates if given a position outside the mesh', function() { - var mesh = new QuantizedMeshTerrainData({ - minimumHeight : 0.0, - maximumHeight : 4.0, - quantizedVertices : new Uint16Array([ // order is sw nw se ne - // u - 0, 0, 32767, 32767, - // v - 0, 32767, 0, 32767, - // heights - 32767 / 4.0, 2.0 * 32767 / 4.0, 3.0 * 32767 / 4.0, 32767 - ]), - indices : new Uint16Array([ - 0, 3, 1, - 0, 2, 3 - ]), - boundingSphere : new BoundingSphere(), - horizonOcclusionPoint : new Cartesian3(), - westIndices : [0, 1], - southIndices : [0, 1], - eastIndices : [2, 3], - northIndices : [1, 3], - westSkirtHeight : 1.0, - southSkirtHeight : 1.0, - eastSkirtHeight : 1.0, - northSkirtHeight : 1.0, + var buffer = getBuffer(tilingScheme, 7, 6, 5); + mesh = new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, childTileMask : 15 }); + }); + it('clamps coordinates if given a position outside the mesh', function() { expect(mesh.interpolateHeight(rectangle, 0.0, 0.0)).toBe(mesh.interpolateHeight(rectangle, rectangle.east, rectangle.south)); }); it('returns a height interpolated from the correct triangle', function() { - // zero height along line between southwest and northeast corners. - // Negative height in the northwest corner, positive height in the southeast. - var mesh = new QuantizedMeshTerrainData({ - minimumHeight : -16384, - maximumHeight : 16383, - quantizedVertices : new Uint16Array([ // order is sw nw se ne - // u - 0, 0, 32767, 32767, - // v - 0, 32767, 0, 32767, - // heights - 16384, 0, 32767, 16384 - ]), - indices : new Uint16Array([ - 0, 3, 1, - 0, 2, 3 - ]), - boundingSphere : new BoundingSphere(), - horizonOcclusionPoint : new Cartesian3(), - westIndices : [0, 1], - southIndices : [0, 1], - eastIndices : [2, 3], - northIndices : [1, 3], - westSkirtHeight : 1.0, - southSkirtHeight : 1.0, - eastSkirtHeight : 1.0, - northSkirtHeight : 1.0, - childTileMask : 15 - }); - - // position in the northwest quadrant of the tile. var longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.25; var latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.75; var result = mesh.interpolateHeight(rectangle, longitude, latitude); - expect(result).toBeLessThan(0.0); + expect(result).toBeBetween(0.0, 10.0); // position in the southeast quadrant of the tile. longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.75; latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.25; result = mesh.interpolateHeight(rectangle, longitude, latitude); - expect(result).toBeGreaterThan(0.0); + expect(result).toBeBetween(10.0, 20.0); // position on the line between the southwest and northeast corners. longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.5; latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.5; result = mesh.interpolateHeight(rectangle, longitude, latitude); - expect(result).toEqualEpsilon(0.0, 1e-10); + expect(result).toEqualEpsilon(10.0, 1e-10); }); }); - */ describe('isChildAvailable', function() { var data; diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js index 084330701128..f7e306c94c58 100644 --- a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js +++ b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js @@ -35,9 +35,26 @@ defineSuite([ when) { 'use strict'; + var oldDecode; + beforeAll(function() { + oldDecode = GoogleEarthEnterpriseMetadata.decode; + GoogleEarthEnterpriseMetadata.decode = function(data) { + return data; + }; + }); + + afterAll(function() { + GoogleEarthEnterpriseMetadata.decode = oldDecode; + }); + + var imageryProvider; afterEach(function() { loadImage.createImage = loadImage.defaultCreateImage; loadWithXhr.load = loadWithXhr.defaultLoad; + if (defined(imageryProvider)) { + imageryProvider.destroy(); + imageryProvider = undefined; + } }); it('conforms to ImageryProvider interface', function() { @@ -92,26 +109,26 @@ defineSuite([ installMockGetQuadTreePacket(); var url = 'http://fake.fake.invalid'; - var provider = new GoogleEarthEnterpriseImageryProvider({ + imageryProvider = new GoogleEarthEnterpriseImageryProvider({ url : url }); - return provider.readyPromise.then(function(result) { + return imageryProvider.readyPromise.then(function(result) { expect(result).toBe(true); - expect(provider.ready).toBe(true); + expect(imageryProvider.ready).toBe(true); }); }); it('rejects readyPromise on error', function() { var url = 'host.invalid'; - var provider = new GoogleEarthEnterpriseImageryProvider({ + imageryProvider = new GoogleEarthEnterpriseImageryProvider({ url : url }); - return provider.readyPromise.then(function () { + return imageryProvider.readyPromise.then(function () { fail('should not resolve'); }).otherwise(function (e) { - expect(provider.ready).toBe(false); + expect(imageryProvider.ready).toBe(false); expect(e.message).toContain(url); }); }); @@ -120,15 +137,15 @@ defineSuite([ installMockGetQuadTreePacket(); var url = 'http://fake.fake.invalid'; - var provider = new GoogleEarthEnterpriseImageryProvider({ + imageryProvider = new GoogleEarthEnterpriseImageryProvider({ url : url }); return pollToPromise(function() { - return provider.ready; + return imageryProvider.ready; }).then(function() { - expect(typeof provider.hasAlphaChannel).toBe('boolean'); - expect(provider.hasAlphaChannel).toBe(false); + expect(typeof imageryProvider.hasAlphaChannel).toBe('boolean'); + expect(imageryProvider.hasAlphaChannel).toBe(false); }); }); @@ -136,27 +153,27 @@ defineSuite([ installMockGetQuadTreePacket(); var url = 'http://fake.fake.invalid/'; - var provider = new GoogleEarthEnterpriseImageryProvider({ + imageryProvider = new GoogleEarthEnterpriseImageryProvider({ url : url }); - expect(provider.url).toEqual(url); + expect(imageryProvider.url).toEqual(url); return pollToPromise(function() { - return provider.ready; + return imageryProvider.ready; }).then(function() { - expect(provider.tileWidth).toEqual(256); - expect(provider.tileHeight).toEqual(256); - expect(provider.maximumLevel).toEqual(23); - expect(provider.tilingScheme).toBeInstanceOf(GeographicTilingScheme); + expect(imageryProvider.tileWidth).toEqual(256); + expect(imageryProvider.tileHeight).toEqual(256); + expect(imageryProvider.maximumLevel).toEqual(23); + expect(imageryProvider.tilingScheme).toBeInstanceOf(GeographicTilingScheme); // Defaults to custom tile policy - expect(provider.tileDiscardPolicy).not.toBeInstanceOf(DiscardMissingTileImagePolicy); - expect(provider.rectangle).toEqual(new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI)); - expect(provider.credit).toBeUndefined(); + expect(imageryProvider.tileDiscardPolicy).not.toBeInstanceOf(DiscardMissingTileImagePolicy); + expect(imageryProvider.rectangle).toEqual(new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI)); + expect(imageryProvider.credit).toBeUndefined(); installFakeImageRequest('http://fake.fake.invalid/flatfile?f1-03-i.1'); - return provider.requestImage(0, 0, 0).then(function(image) { + return imageryProvider.requestImage(0, 0, 0).then(function(image) { expect(image).toBeInstanceOf(Image); }); }); @@ -168,59 +185,57 @@ defineSuite([ var proxy = new DefaultProxy('/proxy/'); - var provider = new GoogleEarthEnterpriseImageryProvider({ + imageryProvider = new GoogleEarthEnterpriseImageryProvider({ url : url, proxy : proxy }); - expect(provider.url).toEqual(url); - expect(provider.proxy).toEqual(proxy); + expect(imageryProvider.url).toEqual(url); + expect(imageryProvider.proxy).toEqual(proxy); return pollToPromise(function() { - return provider.ready; + return imageryProvider.ready; }).then(function() { installFakeImageRequest(proxy.getURL('http://foo.bar.invalid/flatfile?f1-03-i.1')); - return provider.requestImage(0, 0, 0).then(function(image) { + return imageryProvider.requestImage(0, 0, 0).then(function(image) { expect(image).toBeInstanceOf(Image); }); }); }); it('raises error on invalid url', function() { - installMockGetQuadTreePacket(); var url = 'host.invalid'; - var provider = new GoogleEarthEnterpriseImageryProvider({ + imageryProvider = new GoogleEarthEnterpriseImageryProvider({ url : url }); var errorEventRaised = false; - provider.errorEvent.addEventListener(function(error) { + imageryProvider.errorEvent.addEventListener(function(error) { expect(error.message).toContain(url); errorEventRaised = true; }); return pollToPromise(function() { - return provider.ready || errorEventRaised; + return imageryProvider.ready || errorEventRaised; }).then(function() { - expect(provider.ready).toEqual(false); + expect(imageryProvider.ready).toEqual(false); expect(errorEventRaised).toEqual(true); }); }); it('raises error event when image cannot be loaded', function() { installMockGetQuadTreePacket(); - //installFakeImageRequest(); var url = 'http://foo.bar.invalid'; - var provider = new GoogleEarthEnterpriseImageryProvider({ + imageryProvider = new GoogleEarthEnterpriseImageryProvider({ url : url }); - var layer = new ImageryLayer(provider); + var layer = new ImageryLayer(imageryProvider); var tries = 0; - provider.errorEvent.addEventListener(function(error) { + imageryProvider.errorEvent.addEventListener(function(error) { expect(error.timesRetried).toEqual(tries); ++tries; if (tries < 3) { @@ -228,21 +243,6 @@ defineSuite([ } }); - loadImage.createImage = function(url, crossOrigin, deferred) { - if (/^blob:/.test(url)) { - // load blob url normally - loadImage.defaultCreateImage(url, crossOrigin, deferred); - } else if (tries === 2) { - // Succeed after 2 tries - loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); - } else { - // fail - setTimeout(function() { - deferred.reject(); - }, 1); - } - }; - loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { if (tries === 2) { // Succeed after 2 tries @@ -256,7 +256,7 @@ defineSuite([ }; return pollToPromise(function() { - return provider.ready; + return imageryProvider.ready; }).then(function() { var imagery = new Imagery(layer, 0, 0, 0); imagery.addReference(); From 7bdbaa768ce7fe97fbe7a93186dfc49dc49264ca Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 5 Apr 2017 14:30:46 -0400 Subject: [PATCH 25/60] Added tests for interpolateHeight. --- .../Core/GoogleEarthEnterpriseTerrainData.js | 104 +++++++++++++++++- Source/Core/QuantizedMeshTerrainData.js | 2 +- .../GoogleEarthEnterpriseTerrainDataSpec.js | 2 +- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index ebfe431c20a6..86a40a9e0707 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -225,16 +225,13 @@ define([ */ GoogleEarthEnterpriseTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) { var u = CesiumMath.clamp((longitude - rectangle.west) / rectangle.width, 0.0, 1.0); - u *= maxShort; var v = CesiumMath.clamp((latitude - rectangle.south) / rectangle.height, 0.0, 1.0); - v *= maxShort; - var heightSample; - if (defined(this._mesh)) { - heightSample = interpolateMeshHeight(this, u, v); + if (!defined(this._mesh)) { + return interpolateHeight(this, u, v, rectangle); } - return heightSample; + return interpolateMeshHeight(this, u, v); }; var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh'); @@ -427,5 +424,100 @@ define([ return undefined; } + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; + var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; + function interpolateHeight(terrainData, u, v, rectangle) { + var buffer = terrainData._buffer; + var quad = 0; // SW + var uStart = 0.0; + var vStart = 0.0; + if (v > 0.5) { // Upper row + if (u > 0.5) { // NE + quad = 2; + uStart = 0.5; + } else { // NW + quad = 3; + } + vStart = 0.5; + } else if (u > 0.5) { // SE + quad = 1; + uStart = 0.5; + } + + var dv = new DataView(buffer); + var offset = 0; + for (var q = 0; q < quad; ++q) { + offset += dv.getUint32(offset, true); + offset += sizeOfUint32; + } + offset += sizeOfUint32; // Skip length of quad + offset += 2*sizeOfDouble; // Skip origin + + // Read sizes + var xSize = CesiumMath.toRadians(dv.getFloat64(offset, true)*180.0); + offset += sizeOfDouble; + var ySize = CesiumMath.toRadians(dv.getFloat64(offset, true)*180.0); + offset += sizeOfDouble; + + // Samples per quad + var xScale = rectangle.width / xSize / 2; + var yScale = rectangle.height / ySize / 2; + + // Number of points + var numPoints = dv.getInt32(offset, true); + offset += sizeOfInt32; + + // Number of faces + var numIndices = dv.getInt32(offset, true)*3; + offset += sizeOfInt32; + + offset += sizeOfInt32; // Skip Level + + var uBuffer = new Array(numPoints); + var vBuffer = new Array(numPoints); + var heights = new Array(numPoints); + for (var i=0;i= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) { + return barycentric.x * heights[i0] + + barycentric.y * heights[i1] + + barycentric.z * heights[i2]; + } + } + + // Position does not lie in any triangle in this mesh. + return undefined; + } + return GoogleEarthEnterpriseTerrainData; }); diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index d1b8323d96e6..03ebf3bb4bac 100644 --- a/Source/Core/QuantizedMeshTerrainData.js +++ b/Source/Core/QuantizedMeshTerrainData.js @@ -483,7 +483,7 @@ define([ return interpolateHeight(this, u, v); } - interpolateMeshHeight(this, u, v); + return interpolateMeshHeight(this, u, v); }; var texCoordScratch0 = new Cartesian2(); diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 687126e8e6f6..296bd42714dd 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -550,7 +550,7 @@ defineSuite([ latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.5; result = mesh.interpolateHeight(rectangle, longitude, latitude); - expect(result).toEqualEpsilon(10.0, 1e-10); + expect(result).toEqualEpsilon(10.0, 1e-6); }); }); From 470b028da006ed28496b0548b12f61cdc010894f Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 6 Apr 2017 09:54:15 -0400 Subject: [PATCH 26/60] Fixed interpolation tests. --- Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 296bd42714dd..7fcaa26e21cc 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -71,13 +71,17 @@ defineSuite([ // Origin var xOrigin = southwest.longitude; var yOrigin = southwest.latitude; - if (i & 1) { + + if ((i & 2) !== 0) { // Top row + if ((i & 1) === 0) { // NE + xOrigin = center.longitude; + altitudeStart = 10; + } + yOrigin = center.latitude; + } else if ((i & 1) !== 0) { // SE xOrigin = center.longitude; altitudeStart = 10; } - if (i & 2) { - yOrigin = center.latitude; - } dv.setFloat64(offset, CesiumMath.toDegrees(xOrigin)/180.0, true); offset += sizeOfDouble; From ce8c212d472ff54d61c6e7b9ce61520c9c5d7850 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 6 Apr 2017 10:48:14 -0400 Subject: [PATCH 27/60] Cleanup. --- .../Core/GoogleEarthEnterpriseTerrainData.js | 2 - .../GoogleEarthEnterpriseTerrainDataSpec.js | 203 ------------------ 2 files changed, 205 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 86a40a9e0707..fb62c7ffea73 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -211,8 +211,6 @@ define([ }); }; - var maxShort = 32767; - /** * Computes the terrain height at a specified longitude and latitude. * diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index 7fcaa26e21cc..be4da1a37e1c 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -141,46 +141,6 @@ defineSuite([ }); describe('upsample', function() { -/* - function findVertexWithCoordinates(uBuffer, vBuffer, u, v) { - u *= 32767; - u |= 0; - v *= 32767; - v |= 0; - for (var i = 0; i < uBuffer.length; ++i) { - if (Math.abs(uBuffer[i] - u) <= 1 && Math.abs(vBuffer[i] - v) <= 1) { - return i; - } - } - return -1; - } - - function hasTriangle(ib, i0, i1, i2) { - for (var i = 0; i < ib.length; i += 3) { - if (ib[i] === i0 && ib[i + 1] === i1 && ib[i + 2] === i2 || - ib[i] === i1 && ib[i + 1] === i2 && ib[i + 2] === i0 || - ib[i] === i2 && ib[i + 1] === i0 && ib[i + 2] === i1) { - - return true; - } - } - - return false; - } - - function intercept(interceptCoordinate1, interceptCoordinate2, otherCoordinate1, otherCoordinate2) { - return CesiumMath.lerp(otherCoordinate1, otherCoordinate2, (0.5 - interceptCoordinate1) / (interceptCoordinate2 - interceptCoordinate1)); - } - - function horizontalIntercept(u1, v1, u2, v2) { - return intercept(v1, v2, u1, u2); - } - - function verticalIntercept(u1, v1, u2, v2) { - return intercept(u1, u2, v1, v2); - } -*/ - it('works for all four children of a simple quad', function() { var maxShort = 32767; tilingScheme = new GeographicTilingScheme(); @@ -258,169 +218,6 @@ defineSuite([ } }); }); -/* - it('works for a quad with an extra vertex in the northwest child', function() { - var data = new GoogleEarthEnterpriseTerrainData({ - minimumHeight : 0.0, - maximumHeight : 6.0, - quantizedVertices : new Uint16Array([ // order is sw, nw, se, ne, extra vertex in nw quadrant - // u - 0, 0, 32767, 32767, 0.125 * 32767, - // v - 0, 32767, 0, 32767, 0.75 * 32767, - // heights - 32767 / 6.0, 2.0 * 32767 / 6.0, 3.0 * 32767 / 6.0, 4.0 * 32767 / 6.0, 32767 - ]), - indices : new Uint16Array([ - 0, 4, 1, - 0, 2, 4, - 1, 4, 3, - 3, 4, 2 - ]), - boundingSphere : new BoundingSphere(), - horizonOcclusionPoint : new Cartesian3(), - westIndices : [], - southIndices : [], - eastIndices : [], - northIndices : [], - westSkirtHeight : 1.0, - southSkirtHeight : 1.0, - eastSkirtHeight : 1.0, - northSkirtHeight : 1.0, - childTileMask : 15 - }); - - var tilingScheme = new GeographicTilingScheme(); - return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { - return data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); - }).then(function(upsampled) { - var uBuffer = upsampled._uValues; - var vBuffer = upsampled._vValues; - var ib = upsampled._indices; - - expect(uBuffer.length).toBe(9); - expect(vBuffer.length).toBe(9); - expect(upsampled._heightValues.length).toBe(9); - expect(ib.length).toBe(8 * 3); - - var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); - expect(sw).not.toBe(-1); - var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); - expect(nw).not.toBe(-1); - var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); - expect(se).not.toBe(-1); - var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); - expect(ne).not.toBe(-1); - var extra = findVertexWithCoordinates(uBuffer, vBuffer, 0.25, 0.5); - expect(extra).not.toBe(-1); - var v40 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.0, 0.0, 0.125, 0.75) * 2.0, 0.0); - expect(v40).not.toBe(-1); - var v42 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.5, verticalIntercept(1.0, 0.0, 0.125, 0.75), 0.125, 0.75) * 2.0, 0.0); - expect(v42).not.toBe(-1); - var v402 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.5, 0.0, 0.125, 0.75) * 2.0, 0.0); - expect(v402).not.toBe(-1); - var v43 = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, verticalIntercept(1.0, 1.0, 0.125, 0.75) * 2.0 - 1.0); - expect(v43).not.toBe(-1); - - expect(hasTriangle(ib, sw, extra, nw)).toBe(true); - expect(hasTriangle(ib, sw, v40, extra)).toBe(true); - expect(hasTriangle(ib, v40, v402, extra)).toBe(true); - expect(hasTriangle(ib, v402, v42, extra)).toBe(true); - expect(hasTriangle(ib, extra, v42, v43)).toBe(true); - expect(hasTriangle(ib, v42, se, v43)).toBe(true); - expect(hasTriangle(ib, nw, v43, ne)).toBe(true); - expect(hasTriangle(ib, nw, extra, v43)).toBe(true); - }); - }); - - it('works for a quad with an extra vertex on the splitting plane', function() { - var data = new GoogleEarthEnterpriseTerrainData({ - minimumHeight : 0.0, - maximumHeight : 6.0, - quantizedVertices : new Uint16Array([ // order is sw, nw, se, ne, extra vertex in nw quadrant - // u - 0, 0, 32767, 32767, 0.5 * 32767, - // v - 0, 32767, 0, 32767, 0.75 * 32767, - // heights - 32767 / 6.0, 2.0 * 32767 / 6.0, 3.0 * 32767 / 6.0, 4.0 * 32767 / 6.0, 32767 - ]), - indices : new Uint16Array([ - 0, 4, 1, - 1, 4, 3, - 0, 2, 4, - 3, 4, 2 - ]), - boundingSphere : new BoundingSphere(), - horizonOcclusionPoint : new Cartesian3(), - westIndices : [], - southIndices : [], - eastIndices : [], - northIndices : [], - westSkirtHeight : 1.0, - southSkirtHeight : 1.0, - eastSkirtHeight : 1.0, - northSkirtHeight : 1.0, - childTileMask : 15 - }); - - var tilingScheme = new GeographicTilingScheme(); - return when(data.createMesh(tilingScheme, 0, 0, 0, 1)).then(function() { - var nwPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); - var nePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 0, 1); - return when.join(nwPromise, nePromise); - }).then(function(upsampleResults){ - expect(upsampleResults.length).toBe(2); - var uBuffer, vBuffer; - for (var i = 0; i < upsampleResults.length; i++) { - var upsampled = upsampleResults[i]; - expect(upsampled).toBeDefined(); - - uBuffer = upsampled._uValues; - vBuffer = upsampled._vValues; - var ib = upsampled._indices; - - expect(uBuffer.length).toBe(6); - expect(vBuffer.length).toBe(6); - expect(upsampled._heightValues.length).toBe(6); - expect(ib.length).toBe(4 * 3); - - var sw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.0); - expect(sw).not.toBe(-1); - var nw = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 1.0); - expect(nw).not.toBe(-1); - var se = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.0); - expect(se).not.toBe(-1); - var ne = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 1.0); - expect(ne).not.toBe(-1); - } - - // northwest - uBuffer = upsampleResults[0]._uValues; - vBuffer = upsampleResults[0]._vValues; - var extra = findVertexWithCoordinates(uBuffer, vBuffer, 1.0, 0.5); - expect(extra).not.toBe(-1); - var v40 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(0.0, 0.0, 0.5, 0.75) * 2.0, 0.0); - expect(v40).not.toBe(-1); - expect(upsampleResults[0]._westIndices.length).toBe(2); - expect(upsampleResults[0]._eastIndices.length).toBe(3); - expect(upsampleResults[0]._northIndices.length).toBe(2); - expect(upsampleResults[0]._southIndices.length).toBe(3); - - // northeast - uBuffer = upsampleResults[1]._uValues; - vBuffer = upsampleResults[1]._vValues; - extra = findVertexWithCoordinates(uBuffer, vBuffer, 0.0, 0.5); - expect(extra).not.toBe(-1); - var v42 = findVertexWithCoordinates(uBuffer, vBuffer, horizontalIntercept(1.0, 0.0, 0.5, 0.75) * 0.5, 0.0); - expect(v42).not.toBe(-1); - expect(upsampleResults[1]._westIndices.length).toBe(3); - expect(upsampleResults[1]._eastIndices.length).toBe(2); - expect(upsampleResults[1]._northIndices.length).toBe(2); - expect(upsampleResults[1]._southIndices.length).toBe(3); - }); - }); - */ }); describe('createMesh', function() { From f60b1af1f8a3ca469702bcebf89560c4962c2aab Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 6 Apr 2017 19:14:13 -0400 Subject: [PATCH 28/60] Got terrain mostly working well. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 10 ++++- .../Core/GoogleEarthEnterpriseTerrainData.js | 5 +-- .../GoogleEarthEnterpriseTerrainProvider.js | 37 ++++++++++++++----- .../GoogleEarthEnterpriseImageryProvider.js | 11 ++++++ 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 494e08a9d433..6b3428861ec5 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -291,6 +291,12 @@ define([ } var dataView = new DataView(data); + var magic = dataView.getUint32(0, true); + if (magic === compressedMagic || magic === compressedMagicSwap) { + // Occasionally packets don't come back encoded, so just return + return data; + } + var keyView = new DataView(keyBuffer); var dp = 0; @@ -592,10 +598,12 @@ define([ return promise .then(function() { - delete subtreePromises[q]; // Recursively call this incase we need multiple subtree requests return populateSubtree(that, quadKey); }) + .always(function() { + delete subtreePromises[q]; + }) .then(function() { return tileInfo[quadKey]; }); diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index fb62c7ffea73..4feb98e612f1 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -164,9 +164,8 @@ define([ // Compute the center of the tile for RTC rendering. var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle)); - - // 1024 was the initial size of the heightmap before decimation in GEE - var levelZeroMaxError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, 1024, tilingScheme.getNumberOfXTilesAtLevel(0)); + + var levelZeroMaxError = 40075.16; // From Google's Doc var thisLevelMaxError = levelZeroMaxError / (1 << level); this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0); diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index caa5a3acea20..3ae1685b7df9 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -102,10 +102,11 @@ define([ } this._credit = credit; - // 1024 was the initial size of the heightmap before decimation in GEE - this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, 1024, this._tilingScheme.getNumberOfXTilesAtLevel(0)); + // Pulled from Google's documentation + this._levelZeroMaximumGeometricError = 40075.16; this._terrainCache = {}; + this._terrainPromises = {}; this._errorEvent = new Event(); @@ -341,6 +342,7 @@ define([ var that = this; + var terrainPromises = this._terrainPromises; return metadata.populateSubtree(x, y, level) .then(function(info){ if (defined(info)) { @@ -388,15 +390,29 @@ define([ if (terrainVersion > 0) { var url = buildTerrainUrl(that, q, terrainVersion); var promise; - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - promise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(promise)) { - return undefined; - } + if (defined(terrainPromises[q])) { + promise = terrainPromises[q]; } else { - promise = loadArrayBuffer(url); + var requestPromise; + throttleRequests = defaultValue(throttleRequests, true); + if (throttleRequests) { + requestPromise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(requestPromise)) { + return undefined; + } + } else { + requestPromise = loadArrayBuffer(url); + } + + terrainPromises[q] = requestPromise; + + promise = requestPromise + .always(function(terrain) { + delete terrainPromises[q]; + return terrain; + }); } + return promise .then(function(terrain) { if (defined(terrain)) { @@ -491,7 +507,8 @@ define([ var metadata = this._metadata; var info = metadata.getTileInformation(x, y, level); if (defined(info)) { - return info.hasTerrain(); + // We may have terrain or no ancestors have had it so we'll return the ellipsoid + return info.terrainState !== TerrainState.NONE || !info.ancestorHasTerrain; } return undefined; }; diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 340b2f9122be..404ef9a10140 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -395,6 +395,8 @@ define([ return undefined; }; + var loadedImages = {}; + /** * Requests the image for a given tile. This function should * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. @@ -472,6 +474,15 @@ define([ }); } + var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + if (!defined(loadedImages[quadkey])) { + loadedImages[quadkey] = 0; + } + if (++loadedImages[quadkey] > 10) { + debugger; + console.error(x + ', ' + y + ', ' + level + ' loaded ' + loadedImages[quadkey] + ' times.'); + } + return promise .then(function(image) { if (image === invalidImage) { From 5f563bb90e577a0607cbdf2dab1c0112baab03dc Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 7 Apr 2017 11:18:19 -0400 Subject: [PATCH 29/60] Added handling for throttling requests even if promise was returned. --- .../Core/GoogleEarthEnterpriseTerrainProvider.js | 7 +++++++ .../Scene/GoogleEarthEnterpriseImageryProvider.js | 14 ++++---------- Source/Scene/ImageryLayer.js | 4 +++- Source/Scene/TileTerrain.js | 4 +++- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index 3ae1685b7df9..e1bd304f7733 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -18,6 +18,7 @@ define([ './loadArrayBuffer', './Math', './Rectangle', + './RuntimeError', './TerrainProvider', './throttleRequestByServer', './TileProviderError', @@ -41,6 +42,7 @@ define([ loadArrayBuffer, CesiumMath, Rectangle, + RuntimeError, TerrainProvider, throttleRequestByServer, TileProviderError, @@ -435,9 +437,12 @@ define([ info.terrainState = TerrainState.NONE; } } + + return when.reject(new RuntimeError('Failed to load terrain.')); }) .otherwise(function(error) { info.terrainState = TerrainState.NONE; + return when.reject(error); }); } else if(!info.ancestorHasTerrain) { // We haven't reached a level with terrain, so return the ellipsoid @@ -448,6 +453,8 @@ define([ }); } } + + return when.reject(new RuntimeError('Failed to load terrain.')); }); }; diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 404ef9a10140..b4ecd679874f 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -466,7 +466,7 @@ define([ if (defined(info)) { if (info.hasImagery()) { var url = buildImageUrl(that, info, x, y, level); - return loadArrayBuffer(url); + return throttleRequestByServer(url, loadArrayBuffer); } } @@ -474,17 +474,11 @@ define([ }); } - var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - if (!defined(loadedImages[quadkey])) { - loadedImages[quadkey] = 0; - } - if (++loadedImages[quadkey] > 10) { - debugger; - console.error(x + ', ' + y + ', ' + level + ' loaded ' + loadedImages[quadkey] + ' times.'); - } - return promise .then(function(image) { + if (!defined(image)) { + return; // Throttled + } if (image === invalidImage) { return invalidImage; } diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 7fa12ba481c6..1236bda15cc6 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -669,7 +669,9 @@ define([ function success(image) { if (!defined(image)) { - return failure(); + // Too many parallel requests, so postpone loading tile. + imagery.state = ImageryState.UNLOADED; + return; } imagery.image = image; diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index 0cb082e415dd..c999cd952fc6 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -110,7 +110,9 @@ define([ function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) { function success(terrainData) { if (!defined(terrainData)) { - return failure(); + // Deferred - try again later. + tileTerrain.state = TerrainState.UNLOADED; + return; } tileTerrain.data = terrainData; From 1b3da6a6dfb1b41fb79bf2778e5a0d87d9a6e8c4 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 7 Apr 2017 13:39:14 -0400 Subject: [PATCH 30/60] Cleanup. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 87 +++++++------------ .../GoogleEarthEnterpriseTerrainProvider.js | 34 ++++---- Source/Scene/Globe.js | 7 +- .../GoogleEarthEnterpriseImageryProvider.js | 38 ++++---- Source/Scene/ImageryLayer.js | 5 -- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 69 ++++----------- ...oogleEarthEnterpriseTerrainProviderSpec.js | 66 ++++++++------ ...oogleEarthEnterpriseImageryProviderSpec.js | 6 +- 8 files changed, 124 insertions(+), 188 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 6b3428861ec5..335a401ed562 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -77,74 +77,36 @@ define([ GoogleEarthEnterpriseMetadata.TileInformation = TileInformation; - var metadata = {}; - - GoogleEarthEnterpriseMetadata.getMetadata = function(url, proxy) { - //>>includeStart('debug', pragmas.debug); - if (!defined(url)) { - throw new DeveloperError('url is required.'); - } - //>>includeEnd('debug'); - - url = appendForwardSlash(url); - - var result = metadata[url]; - if (defined(metadata[url])) { - ++result.refCount; - return result; - } - - result = new GoogleEarthEnterpriseMetadata(url, proxy); - metadata[url] = result; - - return result; - }; - - GoogleEarthEnterpriseMetadata.releaseMetadata = function(metadataObj) { - //>>includeStart('debug', pragmas.debug); - if (!defined(metadataObj)) { - throw new DeveloperError('metadataObj is required.'); - } - //>>includeEnd('debug'); - - --metadataObj.refCount; - if (metadataObj.refCount === 0) { - delete metadata[metadataObj.url]; - } - }; - /** * Provides metadata using the Google Earth Enterprise REST API. This is used by the * * @alias GoogleEarthEnterpriseMetadata * @constructor * - * @param {String} url The url of the Google Earth Enterprise server hosting the imagery. - * @param {Proxy} [proxy] A proxy to use for requests. This object is + * @param {Object} options Object with the following properties: + * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {Proxy} [options.proxy] A proxy to use for requests. This object is * expected to have a getURL function which returns the proxied URL, if needed. * * @see GoogleEarthEnterpriseImageryProvider * @see GoogleEarthEnterpriseTerrainProvider * - * @private */ - function GoogleEarthEnterpriseMetadata(url, proxy) { + function GoogleEarthEnterpriseMetadata(options) { //>>includeStart('debug', pragmas.debug); - if (!defined(url)) { + if (!defined(options.url)) { throw new DeveloperError('url is required.'); } //>>includeEnd('debug'); - this._url = url; - this._proxy = proxy; + this._url = options.url; + this._proxy = options.proxy; this._tileInfo = {}; this._subtreePromises = {}; - this.refCount = 1; - var that = this; - this._readyPromise = this._getQuadTreePacket() + this._readyPromise = this.getQuadTreePacket() .then(function() { return true; }) @@ -156,8 +118,8 @@ define([ defineProperties(GoogleEarthEnterpriseMetadata.prototype, { /** - * Gets the name of the Google Earth Enterprise server url hosting the imagery. - * @memberof GoogleEarthEnterpriseProvider.prototype + * Gets the name of the Google Earth Enterprise server. + * @memberof GoogleEarthEnterpriseMetadata.prototype * @type {String} * @readonly */ @@ -168,7 +130,7 @@ define([ }, /** - * Gets the proxy used by this provider. + * Gets the proxy used for metadata requests. * @memberof GoogleEarthEnterpriseImageryProvider.prototype * @type {Proxy} * @readonly @@ -180,7 +142,7 @@ define([ }, /** - * Gets a promise that resolves to true when the provider is ready for use. + * Gets a promise that resolves to true when the metadata is ready for use. * @memberof GoogleEarthEnterpriseProvider.prototype * @type {Promise.} * @readonly @@ -275,6 +237,8 @@ define([ * Decodes data that is received from the Google Earth Enterprise server. * * @param {ArrayBuffer} data The data to be decoded. + * + * @private */ GoogleEarthEnterpriseMetadata.decode = function(data) { if (!defined(data)) { @@ -350,6 +314,8 @@ define([ * Uncompresses a Google Earth Enterprise packet. * * @param {ArrayBuffer} data The data to be uncompressed. + * + * @private */ GoogleEarthEnterpriseMetadata.uncompressPacket = function (data) { // The layout of this decoded data is @@ -386,8 +352,15 @@ define([ return uncompressedPacket; }; - // Requests quadtree packet and populates _tileInfo with results - GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket = function(quadKey, version) { + /** + * Retrieves a Google Earth Enterprise quadtree packet. + * + * @param {String} [quadKey=''] The quadkey to retrieve the packet for. + * @param {Number} [version=1] The cnode version to be used in the request. + * + * @private + */ + GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket = function(quadKey, version) { version = defaultValue(version, 1); quadKey = defaultValue(quadKey, ''); var url = getMetadataUrl(this, quadKey, version); @@ -554,6 +527,8 @@ define([ * @param {Number} level The tile level. * * @returns {Promise} A promise that resolves to the tile info for the requested quad key + * + * @private */ GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level) { var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); @@ -590,11 +565,11 @@ define([ }); } - // We need to split up the promise here because when will execute syncronously if _getQuadTreePacket + // We need to split up the promise here because when will execute syncronously if getQuadTreePacket // is already resolved (like in the tests), so subtreePromises will never get cleared out. // The promise will always resolve with a bool, but the initial request will also remove // the promise from subtreePromises. - promise = subtreePromises[q] = that._getQuadTreePacket(q, t.cnodeVersion); + promise = subtreePromises[q] = that.getQuadTreePacket(q, t.cnodeVersion); return promise .then(function() { @@ -616,6 +591,8 @@ define([ * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. * @returns {GoogleEarthEnterpriseMetadata.TileInformation|undefined} Information about the tile or undefined if it isn't loaded. + * + * @private */ GoogleEarthEnterpriseMetadata.prototype.getTileInformation = function(x, y, level) { var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); @@ -627,6 +604,8 @@ define([ * * @param {String} quadkey The quadkey for the tile * @returns {GoogleEarthEnterpriseMetadata.TileInformation|undefined} Information about the tile or undefined if it isn't loaded. + * + * @private */ GoogleEarthEnterpriseMetadata.prototype.getTileInformationFromQuadKey = function(quadkey) { return this._tileInfo[quadkey]; diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index e1bd304f7733..5413ea217bcf 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -64,17 +64,19 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {GoogleEarthEnterpriseMetadata} options.metadata A metadata object that can be used to share metadata requests with a GoogleEarthEnterpriseImageryProvider. * @param {Proxy} [options.proxy] A proxy to use for requests. This object is * expected to have a getURL function which returns the proxied URL, if needed. * @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. * - * @see CesiumTerrainProvider * @see GoogleEarthEnterpriseImageryProvider + * @see CesiumTerrainProvider * * @example + * var geeMetadata = new GoogleEarthEnterpriseMetadata('http://www.earthenterprise.org/3d'); * var gee = new Cesium.GoogleEarthEnterpriseTerrainProvider({ - * url : 'http://www.earthenterprise.org/3d' + * metadata : geeMetadata * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} @@ -83,13 +85,20 @@ define([ options = defaultValue(options, {}); //>>includeStart('debug', pragmas.debug); - if (!defined(options.url)) { - throw new DeveloperError('options.url is required.'); + if (!(defined(options.url) || defined(options.metadata))) { + throw new DeveloperError('options.url or options.metadata is required.'); } //>>includeEnd('debug'); - this._metadata = GoogleEarthEnterpriseMetadata.getMetadata(options.url, options.proxy); - this._proxy = options.proxy; + if (defined(options.metadata)) { + this._metadata = options.metadata; + } else { + this._metadata = new GoogleEarthEnterpriseMetadata({ + url : options.url, + proxy : options.proxy + }); + } + this._proxy = defaultValue(options.proxy, this._metadata.proxy); this._tilingScheme = new GeographicTilingScheme({ numberOfLevelZeroTilesX : 2, @@ -520,19 +529,6 @@ define([ return undefined; }; - /** - * Releases resources used by this provider - */ - GoogleEarthEnterpriseTerrainProvider.prototype.destroy = function() { - var metadata = this._metadata; - if (defined(metadata)) { - this._metadata = undefined; - GoogleEarthEnterpriseMetadata.releaseMetadata(metadata); - } - - return destroyObject(this); - }; - // // Functions to handle imagery packets // diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 238f3bf6dc7d..f947b21caaf5 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -248,12 +248,7 @@ define([ return this._terrainProvider; }, set : function(value) { - var oldTerrainProvider = this._terrainProvider; - if (value !== oldTerrainProvider) { - if (defined(oldTerrainProvider) && defined(oldTerrainProvider.destroy)) { - oldTerrainProvider.destroy(); - } - + if (value !== this._terrainProvider) { this._terrainProvider = value; this._terrainProviderChanged.raiseEvent(value); } diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index b4ecd679874f..a0ef18a5c2de 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -4,7 +4,6 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/destroyObject', '../Core/DeveloperError', '../Core/Event', '../Core/GeographicTilingScheme', @@ -23,7 +22,6 @@ define([ defaultValue, defined, defineProperties, - destroyObject, DeveloperError, Event, GeographicTilingScheme, @@ -69,6 +67,7 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {GoogleEarthEnterpriseMetadata} options.metadata A metadata object that can be used to share metadata requests with a GoogleEarthEnterpriseTerrainProvider. * @param {Proxy} [options.proxy] A proxy to use for requests. This object is * expected to have a getURL function which returns the proxied URL, if needed. * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. @@ -77,6 +76,7 @@ define([ * is to discard tiles that fail to download. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas. * + * @see GoogleEarthEnterpriseTerrainProvider * @see ArcGisMapServerImageryProvider * @see GoogleEarthImageryProvider * @see createOpenStreetMapImageryProvider @@ -88,8 +88,9 @@ define([ * * * @example + * var geeMetadata = new GoogleEarthEnterpriseMetadata('http://www.earthenterprise.org/3d'); * var gee = new Cesium.GoogleEarthEnterpriseImageryProvider({ - * url : 'http://www.earthenterprise.org/3d' + * metadata : geeMetadata * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} @@ -98,14 +99,21 @@ define([ options = defaultValue(options, {}); //>>includeStart('debug', pragmas.debug); - if (!defined(options.url)) { - throw new DeveloperError('options.url is required.'); + if (!(defined(options.url) || defined(options.metadata))) { + throw new DeveloperError('options.url or options.metadata is required.'); } //>>includeEnd('debug'); - this._metadata = GoogleEarthEnterpriseMetadata.getMetadata(options.url, options.proxy); + if (defined(options.metadata)) { + this._metadata = options.metadata; + } else { + this._metadata = new GoogleEarthEnterpriseMetadata({ + url : options.url, + proxy : options.proxy + }); + } this._tileDiscardPolicy = options.tileDiscardPolicy; - this._proxy = options.proxy; + this._proxy = defaultValue(options.proxy, this._metadata.proxy); this._tilingScheme = new GeographicTilingScheme({ numberOfLevelZeroTilesX : 2, @@ -395,8 +403,6 @@ define([ return undefined; }; - var loadedImages = {}; - /** * Requests the image for a given tile. This function should * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. @@ -518,20 +524,6 @@ define([ return undefined; }; - /** - * Releases resources used by this provider - */ - GoogleEarthEnterpriseImageryProvider.prototype.destroy = function() { - var metadata = this._metadata; - if (defined(metadata)) { - this._metadata = undefined; - GoogleEarthEnterpriseMetadata.releaseMetadata(metadata); - } - - return destroyObject(this); - }; - - // // Functions to handle imagery packets // diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 1236bda15cc6..82c6cebaf9aa 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -357,11 +357,6 @@ define([ * @see ImageryLayer#isDestroyed */ ImageryLayer.prototype.destroy = function() { - var imageryProvider = this._imageryProvider; - if (defined(imageryProvider.destroy)) { - imageryProvider.destroy(); - } - return destroyObject(this); }; diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index f9d683aea617..0c43c4380a26 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -68,7 +68,7 @@ defineSuite([ it('populateSubtree', function() { var quad = '0123'; var index = 0; - spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + spyOn(GoogleEarthEnterpriseMetadata.prototype, 'getQuadTreePacket').and.callFake(function(quadKey, version) { quadKey = defaultValue(quadKey, '') + index.toString(); this._tileInfo[quadKey] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); index = (index + 1) % 4; @@ -76,18 +76,20 @@ defineSuite([ return when(); }); - var metadata = new GoogleEarthEnterpriseMetadata('http://test.server'); + var metadata = new GoogleEarthEnterpriseMetadata({ + url: 'http://test.server' + }); return metadata.readyPromise .then(function() { var tileXY = GoogleEarthEnterpriseMetadata.quadKeyToTileXY(quad); return metadata.populateSubtree(tileXY.x, tileXY.y, tileXY.level); }) .then(function() { - expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket.calls.count()).toEqual(4); - expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith(); - expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith('0', 1); - expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith('01', 1); - expect(GoogleEarthEnterpriseMetadata.prototype._getQuadTreePacket).toHaveBeenCalledWith('012', 1); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket.calls.count()).toEqual(4); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith(); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('0', 1); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('01', 1); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('012', 1); var tileInfo = metadata._tileInfo; expect(tileInfo['0']).toBeDefined(); @@ -97,46 +99,6 @@ defineSuite([ }); }); - it('getMetadata/releaseMetadata', function() { - spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { - return when(); - }); - - var server1 = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); - var server1a = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); - var server2 = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server2'); - - expect(server1a).toBe(server1); - expect(server1).not.toBe(server2); - - GoogleEarthEnterpriseMetadata.releaseMetadata(server1); - GoogleEarthEnterpriseMetadata.releaseMetadata(server1a); - - var server1b = GoogleEarthEnterpriseMetadata.getMetadata('http://test.server1'); - expect(server1b).not.toBe(server1); - }); - - it('constructor throws when url is not specified', function() { - function constructWithoutServer() { - return new GoogleEarthEnterpriseMetadata(); - } - expect(constructWithoutServer).toThrowDeveloperError(); - }); - - it('getMetadata throws when url is not specified', function() { - function constructWithoutServer() { - return GoogleEarthEnterpriseMetadata.getMetadata(); - } - expect(constructWithoutServer).toThrowDeveloperError(); - }); - - it('releaseMetadata throws when object is not specified', function() { - function constructWithoutServer() { - return GoogleEarthEnterpriseMetadata.releaseMetadata(); - } - expect(constructWithoutServer).toThrowDeveloperError(); - }); - var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; @@ -235,7 +197,9 @@ defineSuite([ return new Uint8Array(data); }); - var provider = new GoogleEarthEnterpriseMetadata(baseurl); + var provider = new GoogleEarthEnterpriseMetadata({ + url: baseurl + }); return provider.readyPromise.then(function(result) { expect(result).toBe(true); @@ -253,7 +217,9 @@ defineSuite([ it('rejects readyPromise on error', function() { var url = 'host.invalid/'; - var provider = new GoogleEarthEnterpriseMetadata(url); + var provider = new GoogleEarthEnterpriseMetadata({ + url: url + }); return provider.readyPromise.then(function () { fail('should not resolve'); @@ -283,7 +249,10 @@ defineSuite([ return new Uint8Array(data); }); - var provider = new GoogleEarthEnterpriseMetadata(baseurl, proxy); + var provider = new GoogleEarthEnterpriseMetadata({ + url : baseurl, + proxy : proxy + }); expect(provider.url).toEqual(baseurl); expect(provider.proxy).toEqual(proxy); diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index d32478981c66..3ea1dc60f69a 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -32,7 +32,7 @@ defineSuite([ 'use strict'; function installMockGetQuadTreePacket() { - spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + spyOn(GoogleEarthEnterpriseMetadata.prototype, 'getQuadTreePacket').and.callFake(function(quadKey, version) { quadKey = defaultValue(quadKey, ''); this._tileInfo[quadKey + '0'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); this._tileInfo[quadKey + '1'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); @@ -62,10 +62,6 @@ defineSuite([ afterEach(function() { loadWithXhr.load = loadWithXhr.defaultLoad; - if (defined(terrainProvider)) { - terrainProvider.destroy(); - terrainProvider = undefined; - } }); it('conforms to TerrainProvider interface', function() { @@ -210,8 +206,7 @@ defineSuite([ loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { expect(url.indexOf('/proxy/?')).toBe(0); - // Just return any old file, as long as its big enough - loadWithXhr.defaultLoad('Data/EarthOrientationParameters/IcrfToFixedStkComponentsRotationData.json', responseType, method, data, headers, deferred); + loadWithXhr.defaultLoad('Data/GoogleEarthEnterprise/gee.terrain', responseType, method, data, headers, deferred); }; terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ @@ -242,8 +237,12 @@ defineSuite([ var baseUrl = 'made/up/url'; var deferreds = []; - + var loadRealTile = true; loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + if (loadRealTile) { + loadRealTile = false; + return loadWithXhr.defaultLoad('Data/GoogleEarthEnterprise/gee.terrain', responseType, method, data, headers, deferred); + } // Do nothing, so requests never complete deferreds.push(deferred); }; @@ -252,26 +251,39 @@ defineSuite([ url : baseUrl }); + var promises = []; return pollToPromise(function() { return terrainProvider.ready; - }).then(function() { - var promise = terrainProvider.requestTileGeometry(0, 0, 0); - expect(promise).toBeDefined(); - - var i; - for (i = 0; i < 10; ++i) { - promise = terrainProvider.requestTileGeometry(0, 0, 0); - } - - return terrainProvider.requestTileGeometry(0, 0, 0) - .then(function(terrainData) { - expect(terrainData).toBeUndefined(); - - for (i = 0; i < deferreds.length; ++i) { - deferreds[i].resolve(); - } - }); - }); + }) + .then(function() { + var promise = terrainProvider.requestTileGeometry(1, 2, 3); + expect(promise).toBeDefined(); + return promise; + }) + .then(function(terrainData) { + expect(terrainData).toBeDefined(); + for (var i = 0; i < 10; ++i) { + promises.push(terrainProvider.requestTileGeometry(i, i, i)); + } + + return terrainProvider.requestTileGeometry(1, 2, 3); + }) + .then(function(terrainData) { + expect(terrainData).toBeUndefined(); + for (var i = 0; i < deferreds.length; ++i) { + deferreds[i].resolve(); + } + + // Parsing terrain will fail, so just eat the errors and request the tile again + return when.all(promises) + .otherwise(function() { + loadRealTile = true; + return terrainProvider.requestTileGeometry(1, 2, 3); + }); + }) + .then(function(terrainData) { + expect(terrainData).toBeDefined(); + }); }); it('supports getTileDataAvailable()', function() { @@ -292,6 +304,8 @@ defineSuite([ var tileInfo = terrainProvider._metadata._tileInfo; var info = tileInfo[GoogleEarthEnterpriseMetadata.tileXYToQuadKey(0, 1, 0)]; info._bits = 0x7F; // Remove terrain bit from 0,1,0 tile + info.terrainState = 1; // NONE + info.ancestorHasTerrain = true; expect(terrainProvider.getTileDataAvailable(0, 0, 0)).toBe(true); expect(terrainProvider.getTileDataAvailable(0, 1, 0)).toBe(false); diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js index f7e306c94c58..bd697a9d7b58 100644 --- a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js +++ b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js @@ -51,10 +51,6 @@ defineSuite([ afterEach(function() { loadImage.createImage = loadImage.defaultCreateImage; loadWithXhr.load = loadWithXhr.defaultLoad; - if (defined(imageryProvider)) { - imageryProvider.destroy(); - imageryProvider = undefined; - } }); it('conforms to ImageryProvider interface', function() { @@ -70,7 +66,7 @@ defineSuite([ }); function installMockGetQuadTreePacket() { - spyOn(GoogleEarthEnterpriseMetadata.prototype, '_getQuadTreePacket').and.callFake(function(quadKey, version) { + spyOn(GoogleEarthEnterpriseMetadata.prototype, 'getQuadTreePacket').and.callFake(function(quadKey, version) { quadKey = defaultValue(quadKey, ''); this._tileInfo[quadKey + '0'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); this._tileInfo[quadKey + '1'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); From 63bda07f0a2157aa87bc312d873ad69eafca8030 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 7 Apr 2017 14:39:59 -0400 Subject: [PATCH 31/60] Updated sandcastle example and fixed GEE terrain with Mercator imagery. --- .../gallery/Google Earth Enterprise.html | 11 ++++--- Source/Core/GoogleEarthEnterpriseMetadata.js | 2 +- .../Core/GoogleEarthEnterpriseTerrainData.js | 5 ++-- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 29 ++++++++++++++++--- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/Apps/Sandcastle/gallery/Google Earth Enterprise.html b/Apps/Sandcastle/gallery/Google Earth Enterprise.html index 7a95c37834bc..0e9bda72954c 100644 --- a/Apps/Sandcastle/gallery/Google Earth Enterprise.html +++ b/Apps/Sandcastle/gallery/Google Earth Enterprise.html @@ -27,14 +27,17 @@ function startup(Cesium) { 'use strict'; //Sandcastle_Begin +var geeMetadata = new Cesium.GoogleEarthEnterpriseMetadata({ + url : 'http://www.earthenterprise.org/3d', + proxy : new Cesium.DefaultProxy('/proxy/') +}); + var viewer = new Cesium.Viewer('cesiumContainer', { imageryProvider : new Cesium.GoogleEarthEnterpriseImageryProvider({ - url : 'http://www.earthenterprise.org/3d', - proxy : new Cesium.DefaultProxy('/proxy/') + metadata : geeMetadata }), terrainProvider : new Cesium.GoogleEarthEnterpriseTerrainProvider({ - url : 'http://www.earthenterprise.org/3d', - proxy : new Cesium.DefaultProxy('/proxy/') + metadata : geeMetadata }), baseLayerPicker : false }); diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 335a401ed562..85d3978c7a72 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -99,7 +99,7 @@ define([ } //>>includeEnd('debug'); - this._url = options.url; + this._url = appendForwardSlash(options.url); this._proxy = options.proxy; this._tileInfo = {}; diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 4feb98e612f1..816165e738d9 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -164,7 +164,7 @@ define([ // Compute the center of the tile for RTC rendering. var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle)); - + var levelZeroMaxError = 40075.16; // From Google's Doc var thisLevelMaxError = levelZeroMaxError / (1 << level); this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0); @@ -176,7 +176,8 @@ define([ relativeToCenter : center, ellipsoid : ellipsoid, skirtHeight : this._skirtHeight, - exaggeration : exaggeration + exaggeration : exaggeration, + includeWebMercatorT : true }); if (!defined(verticesPromise)) { diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index b40065779af7..b9c8a95c5ebf 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -15,6 +15,7 @@ define([ '../Core/Rectangle', '../Core/TerrainEncoding', '../Core/Transforms', + '../Core/WebMercatorProjection', './createTaskProcessorWorker' ], function( AxisAlignedBoundingBox, @@ -32,6 +33,7 @@ define([ Rectangle, TerrainEncoding, Transforms, + WebMercatorProjection, createTaskProcessorWorker) { 'use strict'; @@ -58,7 +60,8 @@ define([ parameters.rectangle = Rectangle.clone(parameters.rectangle); var statistics = processBuffer(parameters.buffer, parameters.relativeToCenter, parameters.ellipsoid, - parameters.rectangle, parameters.nativeRectangle, parameters.exaggeration, parameters.skirtHeight); + parameters.rectangle, parameters.nativeRectangle, parameters.exaggeration, parameters.skirtHeight, + parameters.includeWebMercatorT); var vertices = statistics.vertices; transferableObjects.push(vertices.buffer); var indices = statistics.indices; @@ -86,7 +89,7 @@ define([ var minimumScratch = new Cartesian3(); var maximumScratch = new Cartesian3(); var matrix4Scratch = new Matrix4(); - function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration, skirtHeight) { + function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration, skirtHeight, includeWebMercatorT) { var geographicWest; var geographicSouth; var geographicEast; @@ -116,6 +119,13 @@ define([ var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid); var toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch); + var southMercatorY; + var oneOverMercatorHeight; + if (includeWebMercatorT) { + southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicSouth); + oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) - southMercatorY); + } + var dv = new DataView(buffer); var minHeight = Number.POSITIVE_INFINITY; @@ -172,6 +182,7 @@ define([ var positions = new Array(size); var uvs = new Array(size); var heights = new Array(size); + var webMercatorTs = includeWebMercatorT ? new Array(size) : []; var indices = new Array(indicesSize); // Points are laid out in rows starting at SW, so storing border points as we @@ -278,6 +289,10 @@ define([ var pos = ellipsoid.cartographicToCartesian(scratchCartographic); positions[pointOffset] = pos; + if (includeWebMercatorT) { + webMercatorTs[pointOffset] = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight; + } + Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); @@ -302,6 +317,9 @@ define([ positions.length = pointOffset; uvs.length = pointOffset; heights.length = pointOffset; + if (includeWebMercatorT) { + webMercatorTs.length = pointOffset; + } var vertexCountWithoutSkirts = pointOffset; var skirtIndex = indicesOffset; @@ -348,6 +366,9 @@ define([ positions.push(pos); heights.push(height); uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point + if (includeWebMercatorT) { + webMercatorTs.push(webMercatorTs[borderIndex]); + } Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); @@ -387,12 +408,12 @@ define([ var occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions); var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter); - var encoding = new TerrainEncoding(aaBox, hMin, maxHeight, fromENU, false, false); + var encoding = new TerrainEncoding(aaBox, hMin, maxHeight, fromENU, false, includeWebMercatorT); var vertices = new Float32Array(size * encoding.getStride()); var bufferIndex = 0; for (var j = 0; j < size; ++j) { - bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j]); + bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j], undefined, webMercatorTs[j]); } return { From 309eb116b938ff4be99137bab22548f6dcdb044f Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 7 Apr 2017 15:51:59 -0400 Subject: [PATCH 32/60] Fixed skirts. --- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index b9c8a95c5ebf..86d97fe30bf6 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -268,9 +268,7 @@ define([ index: pointOffset, cartographic: Cartographic.clone(scratchCartographic) }); - } - - if (Math.abs(latitude - geographicSouth) < halfStepY) { + } else if (Math.abs(latitude - geographicSouth) < halfStepY) { southBorder.push({ index: pointOffset, cartographic: Cartographic.clone(scratchCartographic) @@ -326,11 +324,9 @@ define([ // Add skirt points var hMin = minHeight; - quadBorderPoints = []; - quadBorderIndices = []; - function addSkirt(borderPoints, longitudeFudge, latitudeFudge) { + var lastBorderPoint; + function addSkirt(borderPoints, fudgeFactor, eastOrWest, cornerFudge) { var count = borderPoints.length; - var lastBorderPoint; for (var j = 0; j < count; ++j) { var borderPoint = borderPoints[j]; var borderCartographic = borderPoint.cartographic; @@ -345,39 +341,33 @@ define([ Cartographic.fromRadians(longitude, latitude, height, scratchCartographic); - // Detect duplicates at the corners and only had the first one - // We detect based on not fudged points so a corner point is fudged in both directions - var add = true; - var index = indexOfEpsilon(quadBorderPoints, scratchCartographic, Cartographic); - if (index === -1) { - quadBorderPoints.push(Cartographic.clone(scratchCartographic)); - quadBorderIndices.push(currentIndex); - } else { - currentIndex = quadBorderIndices[index]; - ellipsoid.cartesianToCartographic(positions[currentIndex], scratchCartographic); - add = false; + // Adjust sides to angle out + if (eastOrWest) { + scratchCartographic.longitude += fudgeFactor; } - scratchCartographic.longitude += longitudeFudge; - scratchCartographic.latitude += latitudeFudge; + // Adjust top or bottom to angle out + // Since corners are in the east/west arrays angle the first and last points as well + if (!eastOrWest) { + scratchCartographic.latitude += fudgeFactor; + } else if (j === (count-1)) { + scratchCartographic.latitude += cornerFudge; + } else if (j === 0) { + scratchCartographic.latitude -= cornerFudge; + } - if (add) { - var pos = ellipsoid.cartographicToCartesian(scratchCartographic); - positions.push(pos); - heights.push(height); - uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point - if (includeWebMercatorT) { - webMercatorTs.push(webMercatorTs[borderIndex]); - } + var pos = ellipsoid.cartographicToCartesian(scratchCartographic); + positions.push(pos); + heights.push(height); + uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point + if (includeWebMercatorT) { + webMercatorTs.push(webMercatorTs[borderIndex]); + } - Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); + Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); - Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); - Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum); - } else { - // We found a corner point - it was adjusted for this side so write it back - ellipsoid.cartographicToCartesian(scratchCartographic, positions[currentIndex]); - } + Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); + Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum); if (defined(lastBorderPoint)) { var lastBorderIndex = lastBorderPoint.index; @@ -388,11 +378,26 @@ define([ } } + // Sort counter clockwise from NW corner + // Corner points are in the east/west arrays + westBorder.sort(function(a, b) { + return b.cartographic.latitude - a.cartographic.latitude; + }); + southBorder.sort(function(a, b) { + return a.cartographic.longitude - b.cartographic.longitude; + }); + eastBorder.sort(function(a, b) { + return a.cartographic.latitude - b.cartographic.latitude; + }); + northBorder.sort(function(a, b) { + return b.cartographic.longitude - a.cartographic.longitude; + }); + var percentage = 0.00001; - addSkirt(westBorder, -percentage*rectangleWidth, 0); - addSkirt(southBorder, 0, -percentage*rectangleHeight); - addSkirt(eastBorder, percentage*rectangleWidth, 0); - addSkirt(northBorder, 0, percentage*rectangleHeight); + addSkirt(westBorder, -percentage*rectangleWidth, true, -percentage*rectangleHeight); + addSkirt(southBorder, -percentage*rectangleHeight, false); + addSkirt(eastBorder, percentage*rectangleWidth, true, percentage*rectangleHeight); + addSkirt(northBorder, percentage*rectangleHeight, false); size = positions.length; // Get new size with skirt vertices From 09f7543c80e7305a1a497cad1226880a64e0c47b Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 7 Apr 2017 18:03:48 -0400 Subject: [PATCH 33/60] Fixed issue where 2 skirt triangles weren't being drawn. --- Source/Core/GoogleEarthEnterpriseTerrainData.js | 2 +- .../createVerticesFromGoogleEarthEnterpriseBuffer.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 816165e738d9..18300cf5fd10 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -167,7 +167,7 @@ define([ var levelZeroMaxError = 40075.16; // From Google's Doc var thisLevelMaxError = levelZeroMaxError / (1 << level); - this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0); + this._skirtHeight = Math.min(thisLevelMaxError * 8.0, 1000.0); var verticesPromise = taskProcessor.scheduleTask({ buffer : this._buffer, diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 86d97fe30bf6..50018768ff2b 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -399,6 +399,17 @@ define([ addSkirt(eastBorder, percentage*rectangleWidth, true, percentage*rectangleHeight); addSkirt(northBorder, percentage*rectangleHeight, false); + // Since the corner between the north and west sides is in the west array, generate the last + // two triangles between the last north vertex and the first west vertex + if (westBorder.length > 0 && northBorder.length > 0) { + var firstBorderIndex = westBorder[0].index; + var firstSkirtIndex = vertexCountWithoutSkirts; + var lastBorderIndex = northBorder[northBorder.length-1].index; + var lastSkirtIndex = positions.length - 1; + + indices.push(lastBorderIndex, lastSkirtIndex, firstSkirtIndex, firstSkirtIndex, firstBorderIndex, lastBorderIndex); + } + size = positions.length; // Get new size with skirt vertices var boundingSphere3D = BoundingSphere.fromPoints(positions); From 22e8fdaccd9eb02dc7483f3c479a38a1788278f4 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 7 Apr 2017 18:06:15 -0400 Subject: [PATCH 34/60] Tweak. --- Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 50018768ff2b..41a450d781b8 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -409,7 +409,7 @@ define([ indices.push(lastBorderIndex, lastSkirtIndex, firstSkirtIndex, firstSkirtIndex, firstBorderIndex, lastBorderIndex); } - + size = positions.length; // Get new size with skirt vertices var boundingSphere3D = BoundingSphere.fromPoints(positions); From 541e2db7da94ce69ee52665b52d97a5609e6dc44 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 7 Apr 2017 18:12:20 -0400 Subject: [PATCH 35/60] Updated CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 145981237412..1ccb76a52b1c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Change Log ### 1.33 - 2017-05-01 * Added `disableDepthTestDistance` to billboards, points and labels. This sets the distance to the camera where the depth test will be disabled. Setting it to zero (the default) will alwasy enable the depth test. Setting it to `Number.POSITVE_INFINITY` will never enabled the depth test. Also added `scene.minimumDisableDepthTestDistance` to change the default value from zero. [#5166](https://github.com/AnalyticalGraphicsInc/cesium/pull/5166) +* Added `GoogleEarthEnterpriseTerrainProvider` and `GoogleEarthEnterpriseImageryProvider` to read data from Google Earth Enterprise servers. `GoogleEarthEnterpriseMeta` was also added so providers can share metadata requests. ### 1.32 - 2017-04-03 From 26722e73dc7dce6d0eea0120e3a3ffc0ea13f95b Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 10 Apr 2017 11:43:41 -0400 Subject: [PATCH 36/60] Tweaks from PR comments. --- LICENSE.md | 57 +++++++++++++++++++ Source/Core/GoogleEarthEnterpriseMetadata.js | 51 +++++++++++++++-- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 9 ++- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 3 - 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index e95d8ff924ac..e47c4a54b493 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -367,6 +367,63 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +### pako + +https://github.com/nodeca/pako + +>(The MIT License) +> +>Copyright (C) 2014-2017 by Vitaly Puzrin and Andrei Tuputcyn +> +>Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +> +>The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +> +>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +### protobuf + +https://github.com/dcodeIO/ProtoBuf.js + +>Copyright (c) 2016, Daniel Wirtz All rights reserved. +> +>Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +>* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +>* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +>* Neither the name of its author, nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Tests ===== diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 85d3978c7a72..f09eb7960539 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -25,7 +25,6 @@ define([ var childrenBitmasks = [0x01, 0x02, 0x04, 0x08]; var anyChildBitmask = 0x0F; var cacheFlagBitmask = 0x10; // True if there is a child subtree - //var vectorDataBitmask = 0x20; var imageBitmask = 0x40; var terrainBitmask = 0x80; @@ -38,6 +37,16 @@ define([ return ((bits & mask) !== 0); } + /** + * Contains information about each tile from a Google Earth Enterprise server + * + * @param {Number} bits Bitmask that contains the type of data and available children for each tile. + * @param {Number} cnodeVersion Version of the request for subtree metadata. + * @param {Number} imageryVersion Version of the request for imagery tile. + * @param {Number} terrainVersion Version of the request for terrain tile. + * + * @private + */ function TileInformation(bits, cnodeVersion, imageryVersion, terrainVersion) { this._bits = bits; this.cnodeVersion = cnodeVersion; @@ -47,30 +56,67 @@ define([ this.terrainState = 0; // UNKNOWN } + /** + * Sets the parent for the tile + * + * @param {TileInformation} parent Parent tile + */ TileInformation.prototype.setParent = function(parent) { this.ancestorHasTerrain = parent.ancestorHasTerrain || this.hasTerrain(); }; + /** + * Gets whether a subtree is available + * + * @returns {Boolean} true if subtree is available, false otherwise. + */ TileInformation.prototype.hasSubtree = function() { return isBitSet(this._bits, cacheFlagBitmask); }; + /** + * Gets whether imagery is available + * + * @returns {Boolean} true if imagery is available, false otherwise. + */ TileInformation.prototype.hasImagery = function() { return isBitSet(this._bits, imageBitmask); }; + /** + * Gets whether terrain is available + * + * @returns {Boolean} true if terrain is available, false otherwise. + */ TileInformation.prototype.hasTerrain = function() { return isBitSet(this._bits, terrainBitmask); }; + /** + * Gets whether any children are present + * + * @returns {Boolean} true if any children are available, false otherwise. + */ TileInformation.prototype.hasChildren = function() { return isBitSet(this._bits, anyChildBitmask); }; + /** + * Gets whether a specified child is available + * + * @param {Number} index Index of child tile + * + * @returns {Boolean} true if child is available, false otherwise + */ TileInformation.prototype.hasChild = function(index) { return isBitSet(this._bits, childrenBitmasks[index]); }; + /** + * Gets bitmask containing children + * + * @returns {Number} Children bitmask + */ TileInformation.prototype.getChildBitmask = function(index) { return this._bits && anyChildBitmask; }; @@ -438,17 +484,14 @@ define([ offset += sizeOfUint16; // Number of channels stored in the dataBuffer - //var numChannels = dv.getUint16(offset, true); offset += sizeOfUint16; offset += sizeOfUint16; // 4 byte align // Channel type offset into dataBuffer - //var typeOffset = dv.getInt32(offset, true); offset += sizeOfInt32; // Channel version offset into dataBuffer - //var versionOffset = dv.getInt32(offset, true); offset += sizeOfInt32; offset += 8; // Ignore image neighbors for now diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 41a450d781b8..9190e1bf2e3e 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -13,6 +13,7 @@ define([ '../Core/Matrix4', '../Core/OrientedBoundingBox', '../Core/Rectangle', + '../Core/RuntimeError', '../Core/TerrainEncoding', '../Core/Transforms', '../Core/WebMercatorProjection', @@ -31,6 +32,7 @@ define([ Matrix4, OrientedBoundingBox, Rectangle, + RuntimeError, TerrainEncoding, Transforms, WebMercatorProjection, @@ -197,8 +199,9 @@ define([ var indicesOffset = 0; offset = 0; for (quad = 0; quad < 4; ++quad) { - //var quadSize = dv.getUint32(offset, true); + var quadSize = dv.getUint32(offset, true); offset += sizeOfUint32; + var startQuad = offset; var originX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); offset += sizeOfDouble; @@ -310,6 +313,10 @@ define([ indices[indicesOffset] = indicesMapping[dv.getUint16(offset, true)]; offset += sizeOfUint16; } + + if (quadSize !== (offset-startQuad)) { + throw new RuntimeError('Invalid terrain tile.'); + } } positions.length = pointOffset; diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index 0c43c4380a26..3c0c6ffa2694 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -16,9 +16,6 @@ defineSuite([ 'use strict'; it('tileXYToQuadKey', function() { - // http://msdn.microsoft.com/en-us/library/bb259689.aspx - // Levels are off by one compared to the documentation because our levels - // start at 0 while Bing's start at 1. expect(GoogleEarthEnterpriseMetadata.tileXYToQuadKey(1, 0, 0)).toEqual('2'); expect(GoogleEarthEnterpriseMetadata.tileXYToQuadKey(1, 2, 1)).toEqual('02'); expect(GoogleEarthEnterpriseMetadata.tileXYToQuadKey(3, 5, 2)).toEqual('021'); From 195ced5026ddf6c7831f95fb063d6ad02999b70f Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 10 Apr 2017 12:03:22 -0400 Subject: [PATCH 37/60] jshint fix. --- .../Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 9190e1bf2e3e..fd9ab29b851a 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -147,9 +147,10 @@ define([ var offset = 0; var size = 0; var indicesSize = 0; + var quadSize; for (var quad = 0; quad < 4; ++quad) { var o = offset; - var quadSize = dv.getUint32(o, true); + quadSize = dv.getUint32(o, true); o += sizeOfUint32; var x = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0); @@ -199,7 +200,7 @@ define([ var indicesOffset = 0; offset = 0; for (quad = 0; quad < 4; ++quad) { - var quadSize = dv.getUint32(offset, true); + quadSize = dv.getUint32(offset, true); offset += sizeOfUint32; var startQuad = offset; From 679c43588985361d2c7422532db96892b9d4a14b Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 10 Apr 2017 13:53:11 -0400 Subject: [PATCH 38/60] More tweaks from PR comments. --- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 134 ++++++++++-------- 1 file changed, 75 insertions(+), 59 deletions(-) diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index fd9ab29b851a..421e6653292e 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -331,60 +331,15 @@ define([ var skirtIndex = indicesOffset; // Add skirt points - var hMin = minHeight; - var lastBorderPoint; - function addSkirt(borderPoints, fudgeFactor, eastOrWest, cornerFudge) { - var count = borderPoints.length; - for (var j = 0; j < count; ++j) { - var borderPoint = borderPoints[j]; - var borderCartographic = borderPoint.cartographic; - var borderIndex = borderPoint.index; - var currentIndex = positions.length; - - var longitude = borderCartographic.longitude; - var latitude = borderCartographic.latitude; - latitude = CesiumMath.clamp(latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); // Don't go over the poles - var height = borderCartographic.height - skirtHeight; - hMin = Math.min(hMin, height); - - Cartographic.fromRadians(longitude, latitude, height, scratchCartographic); - - // Adjust sides to angle out - if (eastOrWest) { - scratchCartographic.longitude += fudgeFactor; - } - - // Adjust top or bottom to angle out - // Since corners are in the east/west arrays angle the first and last points as well - if (!eastOrWest) { - scratchCartographic.latitude += fudgeFactor; - } else if (j === (count-1)) { - scratchCartographic.latitude += cornerFudge; - } else if (j === 0) { - scratchCartographic.latitude -= cornerFudge; - } - - var pos = ellipsoid.cartographicToCartesian(scratchCartographic); - positions.push(pos); - heights.push(height); - uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point - if (includeWebMercatorT) { - webMercatorTs.push(webMercatorTs[borderIndex]); - } - - Matrix4.multiplyByPoint(toENU, pos, scratchCartesian); - - Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); - Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum); - - if (defined(lastBorderPoint)) { - var lastBorderIndex = lastBorderPoint.index; - indices.push(lastBorderIndex, currentIndex - 1, currentIndex, currentIndex, borderIndex, lastBorderIndex); - } - - lastBorderPoint = borderPoint; - } - } + var skirtOptions = { + hMin: minHeight, + lastBorderPoint : undefined, + skirtHeight: skirtHeight, + toENU: toENU, + ellipsoid: ellipsoid, + minimum: minimum, + maximum: maximum + }; // Sort counter clockwise from NW corner // Corner points are in the east/west arrays @@ -402,10 +357,14 @@ define([ }); var percentage = 0.00001; - addSkirt(westBorder, -percentage*rectangleWidth, true, -percentage*rectangleHeight); - addSkirt(southBorder, -percentage*rectangleHeight, false); - addSkirt(eastBorder, percentage*rectangleWidth, true, percentage*rectangleHeight); - addSkirt(northBorder, percentage*rectangleHeight, false); + addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, + westBorder, -percentage*rectangleWidth, true, -percentage*rectangleHeight); + addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, + southBorder, -percentage*rectangleHeight, false); + addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, + eastBorder, percentage*rectangleWidth, true, percentage*rectangleHeight); + addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, + northBorder, percentage*rectangleHeight, false); // Since the corner between the north and west sides is in the west array, generate the last // two triangles between the last north vertex and the first west vertex @@ -432,7 +391,7 @@ define([ var occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions); var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter); - var encoding = new TerrainEncoding(aaBox, hMin, maxHeight, fromENU, false, includeWebMercatorT); + var encoding = new TerrainEncoding(aaBox, skirtOptions.hMin, maxHeight, fromENU, false, includeWebMercatorT); var vertices = new Float32Array(size * encoding.getStride()); var bufferIndex = 0; @@ -454,5 +413,62 @@ define([ }; } + function addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, + borderPoints, fudgeFactor, eastOrWest, cornerFudge) { + var count = borderPoints.length; + for (var j = 0; j < count; ++j) { + var borderPoint = borderPoints[j]; + var borderCartographic = borderPoint.cartographic; + var borderIndex = borderPoint.index; + var currentIndex = positions.length; + + var longitude = borderCartographic.longitude; + var latitude = borderCartographic.latitude; + latitude = CesiumMath.clamp(latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); // Don't go over the poles + var height = borderCartographic.height - skirtOptions.skirtHeight; + skirtOptions.hMin = Math.min(skirtOptions.hMin, height); + + Cartographic.fromRadians(longitude, latitude, height, scratchCartographic); + + // Adjust sides to angle out + if (eastOrWest) { + scratchCartographic.longitude += fudgeFactor; + } + + // Adjust top or bottom to angle out + // Since corners are in the east/west arrays angle the first and last points as well + if (!eastOrWest) { + scratchCartographic.latitude += fudgeFactor; + } else if (j === (count-1)) { + scratchCartographic.latitude += cornerFudge; + } else if (j === 0) { + scratchCartographic.latitude -= cornerFudge; + } + + var pos = skirtOptions.ellipsoid.cartographicToCartesian(scratchCartographic); + positions.push(pos); + heights.push(height); + uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point + if (webMercatorTs.length > 0) { + webMercatorTs.push(webMercatorTs[borderIndex]); + } + + Matrix4.multiplyByPoint(skirtOptions.toENU, pos, scratchCartesian); + + var minimum = skirtOptions.minimum; + var maximum = skirtOptions.maximum; + Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum); + Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum); + + var lastBorderPoint = skirtOptions.lastBorderPoint; + if (defined(lastBorderPoint)) { + var lastBorderIndex = lastBorderPoint.index; + indices.push(lastBorderIndex, currentIndex - 1, currentIndex, currentIndex, borderIndex, lastBorderIndex); + } + + skirtOptions.lastBorderPoint = borderPoint; + } + } + return createTaskProcessorWorker(createVerticesFromGoogleEarthEnterpriseBuffer); }); From 812bb87564e1fa8db4e4ca830e0cdb4fe6802e76 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 10 Apr 2017 14:46:05 -0400 Subject: [PATCH 39/60] Updated test data. --- Specs/Data/GoogleEarthEnterprise/gee.terrain | Bin 4213 -> 87 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Specs/Data/GoogleEarthEnterprise/gee.terrain b/Specs/Data/GoogleEarthEnterprise/gee.terrain index 38773d668e4be2854ce2c0831a8731de48383e0e..d2880fec7816e6804c9a7a284588438da4226984 100644 GIT binary patch literal 87 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 Date: Mon, 10 Apr 2017 16:41:30 -0400 Subject: [PATCH 40/60] Added code to cleanup terrain cache for unloaded tiles. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 61 ++++++------ .../Core/GoogleEarthEnterpriseTerrainData.js | 98 +++++++++---------- .../GoogleEarthEnterpriseTerrainProvider.js | 92 ++++++++++++----- .../GoogleEarthEnterpriseImageryProvider.js | 84 ++++++++-------- Source/Scene/GoogleEarthImageryProvider.js | 73 +++++++------- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 74 +++++++------- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 43 ++++---- .../GoogleEarthEnterpriseTerrainDataSpec.js | 78 +++++++-------- ...oogleEarthEnterpriseTerrainProviderSpec.js | 6 +- ...oogleEarthEnterpriseImageryProviderSpec.js | 74 +++++++------- Specs/Scene/GoogleEarthImageryProviderSpec.js | 54 +++++----- 11 files changed, 390 insertions(+), 347 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index f09eb7960539..0e6780e402f0 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -1,24 +1,24 @@ /*global define*/ define([ - './appendForwardSlash', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './loadArrayBuffer', - './RuntimeError', - '../ThirdParty/pako_inflate', - '../ThirdParty/when' -], function( - appendForwardSlash, - defaultValue, - defined, - defineProperties, - DeveloperError, - loadArrayBuffer, - RuntimeError, - pako, - when) { + '../ThirdParty/pako_inflate', + '../ThirdParty/when', + './appendForwardSlash', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './loadArrayBuffer', + './RuntimeError' + ], function( + pako, + when, + appendForwardSlash, + defaultValue, + defined, + defineProperties, + DeveloperError, + loadArrayBuffer, + RuntimeError) { 'use strict'; // Bitmask for checking tile properties @@ -117,7 +117,7 @@ define([ * * @returns {Number} Children bitmask */ - TileInformation.prototype.getChildBitmask = function(index) { + TileInformation.prototype.getChildBitmask = function() { return this._bits && anyChildBitmask; }; @@ -212,7 +212,7 @@ define([ */ GoogleEarthEnterpriseMetadata.tileXYToQuadKey = function(x, y, level) { var quadkey = ''; - for ( var i = level; i >= 0; --i) { + for (var i = level; i >= 0; --i) { var bitmask = 1 << i; var digit = 0; @@ -253,7 +253,7 @@ define([ var x = 0; var y = 0; var level = quadkey.length - 1; - for ( var i = level; i >= 0; --i) { + for (var i = level; i >= 0; --i) { var bitmask = 1 << i; var digit = +quadkey[level - i]; @@ -295,7 +295,7 @@ define([ if (!defined(keyBuffer)) { keyBuffer = new ArrayBuffer(keylen); var ui8 = new Uint8Array(keyBuffer); - for (var i=0; i < keylen; ++i) { + for (var i = 0; i < keylen; ++i) { ui8[i] = key.charCodeAt(i); } } @@ -330,7 +330,7 @@ define([ // then drop out to rotate the key for the next bit while ((dp < dpend64) && (kp < kpend)) { dataView.setUint32(dp, dataView.getUint32(dp, true) ^ keyView.getUint32(kp, true), true); - dataView.setUint32(dp+4, dataView.getUint32(dp+4, true) ^ keyView.getUint32(kp+4, true), true); + dataView.setUint32(dp + 4, dataView.getUint32(dp + 4, true) ^ keyView.getUint32(kp + 4, true), true); dp += 8; kp += 24; } @@ -352,7 +352,6 @@ define([ } }; - var qtMagic = 32301; var compressedMagic = 0x7468dead; var compressedMagicSwap = 0xadde6874; @@ -363,7 +362,7 @@ define([ * * @private */ - GoogleEarthEnterpriseMetadata.uncompressPacket = function (data) { + GoogleEarthEnterpriseMetadata.uncompressPacket = function(data) { // The layout of this decoded data is // Magic Uint32 // Size Uint32 @@ -383,9 +382,9 @@ define([ offset += sizeOfUint32; if (magic === compressedMagicSwap) { size = ((size >>> 24) & 0x000000ff) | - ((size >>> 8) & 0x0000ff00) | - ((size << 8) & 0x00ff0000) | - ((size << 24) & 0xff000000); + ((size >>> 8) & 0x0000ff00) | + ((size << 8) & 0x00ff0000) | + ((size << 24) & 0xff000000); } var compressedPacket = new Uint8Array(data, offset); @@ -587,8 +586,8 @@ define([ return when(t); } - while((t === undefined) && q.length > 1) { - q = q.substring(0, q.length-1); + while ((t === undefined) && q.length > 1) { + q = q.substring(0, q.length - 1); t = tileInfo[q]; } diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 18300cf5fd10..a865e8e7eff8 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -1,46 +1,46 @@ /*global define*/ define([ - '../ThirdParty/when', - './BoundingSphere', - './Cartesian2', - './Cartesian3', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './GeographicTilingScheme', - './HeightmapTessellator', - './IndexDatatype', - './Intersections2D', - './Math', - './OrientedBoundingBox', - './QuantizedMeshTerrainData', - './Rectangle', - './TaskProcessor', - './TerrainEncoding', - './TerrainMesh', - './TerrainProvider' -], function( - when, - BoundingSphere, - Cartesian2, - Cartesian3, - defaultValue, - defined, - defineProperties, - DeveloperError, - GeographicTilingScheme, - HeightmapTessellator, - IndexDatatype, - Intersections2D, - CesiumMath, - OrientedBoundingBox, - QuantizedMeshTerrainData, - Rectangle, - TaskProcessor, - TerrainEncoding, - TerrainMesh, - TerrainProvider) { + '../ThirdParty/when', + './BoundingSphere', + './Cartesian2', + './Cartesian3', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './GeographicTilingScheme', + './HeightmapTessellator', + './IndexDatatype', + './Intersections2D', + './Math', + './OrientedBoundingBox', + './QuantizedMeshTerrainData', + './Rectangle', + './TaskProcessor', + './TerrainEncoding', + './TerrainMesh', + './TerrainProvider' + ], function( + when, + BoundingSphere, + Cartesian2, + Cartesian3, + defaultValue, + defined, + defineProperties, + DeveloperError, + GeographicTilingScheme, + HeightmapTessellator, + IndexDatatype, + Intersections2D, + CesiumMath, + OrientedBoundingBox, + QuantizedMeshTerrainData, + Rectangle, + TaskProcessor, + TerrainEncoding, + TerrainMesh, + TerrainProvider) { 'use strict'; /** @@ -87,7 +87,6 @@ define([ this._buffer = options.buffer; - // Convert from google layout to layout of other providers // 3 2 -> 2 3 // 0 1 -> 0 1 @@ -124,7 +123,6 @@ define([ } }); - var taskProcessor = new TaskProcessor('createVerticesFromGoogleEarthEnterpriseBuffer'); /** @@ -394,6 +392,7 @@ define([ var texCoordScratch1 = new Cartesian2(); var texCoordScratch2 = new Cartesian2(); var barycentricCoordinateScratch = new Cartesian3(); + function interpolateMeshHeight(terrainData, u, v) { var mesh = terrainData._mesh; var vertices = mesh.vertices; @@ -427,6 +426,7 @@ define([ var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; + function interpolateHeight(terrainData, u, v, rectangle) { var buffer = terrainData._buffer; var quad = 0; // SW @@ -452,12 +452,12 @@ define([ offset += sizeOfUint32; } offset += sizeOfUint32; // Skip length of quad - offset += 2*sizeOfDouble; // Skip origin + offset += 2 * sizeOfDouble; // Skip origin // Read sizes - var xSize = CesiumMath.toRadians(dv.getFloat64(offset, true)*180.0); + var xSize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); offset += sizeOfDouble; - var ySize = CesiumMath.toRadians(dv.getFloat64(offset, true)*180.0); + var ySize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); offset += sizeOfDouble; // Samples per quad @@ -469,7 +469,7 @@ define([ offset += sizeOfInt32; // Number of faces - var numIndices = dv.getInt32(offset, true)*3; + var numIndices = dv.getInt32(offset, true) * 3; offset += sizeOfInt32; offset += sizeOfInt32; // Skip Level @@ -477,7 +477,7 @@ define([ var uBuffer = new Array(numPoints); var vBuffer = new Array(numPoints); var heights = new Array(numPoints); - for (var i=0;i 10) { + var terrainCache = this._terrainCache; + var keys = Object.keys(terrainCache); + var count = keys.length; + for (var i = 0; i < count; ++i) { + var k = keys[i]; + var e = terrainCache[k]; + if (JulianDate.secondsDifference(julianDateScratch, e.timestamp) > 10) { + delete terrainCache[k]; + } + } + + JulianDate.clone(julianDateScratch, this._lastTidy); + } + }; + /** * Provides tiled terrain using the Google Earth Enterprise REST API. * @@ -116,7 +159,7 @@ define([ // Pulled from Google's documentation this._levelZeroMaximumGeometricError = 40075.16; - this._terrainCache = {}; + this._terrainCache = new TerrainCache(); this._terrainPromises = {}; this._errorEvent = new Event(); @@ -303,7 +346,8 @@ define([ var metadata = this._metadata; var info = metadata.getTileInformationFromQuadKey(quadKey); - if (!defined(terrainCache[quadKey])) { // If its in the cache we know we have it so just skip all the checks + if (!defined(terrainCache.get(quadKey))) { // If its in the cache we know we have it so just skip all the checks + terrainCache.tidy(); // Cleanup the cache since we know we don't have it if (defined(info)) { if (info.terrainState === TerrainState.NONE) { // Already have info and there isn't any terrain here @@ -339,7 +383,7 @@ define([ } if (!hasTerrain) { - if(defined(info) && !info.ancestorHasTerrain) { + if (defined(info) && !info.ancestorHasTerrain) { // We haven't reached a level with terrain, so return the ellipsoid return new HeightmapTerrainData({ buffer : new Uint8Array(16 * 16), @@ -351,22 +395,21 @@ define([ return undefined; } - var that = this; var terrainPromises = this._terrainPromises; return metadata.populateSubtree(x, y, level) - .then(function(info){ + .then(function(info) { if (defined(info)) { - if (defined(terrainCache[quadKey])) { + var buffer = terrainCache.get(quadKey); + if (defined(buffer)) { if (info.terrainState === TerrainState.UNKNOWN) { // If its already in the cache then a parent request must've loaded it info.terrainState = TerrainState.PARENT; } - var buffer = terrainCache[quadKey]; - delete terrainCache[quadKey]; + return new GoogleEarthEnterpriseTerrainData({ - buffer: buffer, - childTileMask: info.getChildBitmask() + buffer : buffer, + childTileMask : info.getChildBitmask() }); } @@ -375,12 +418,12 @@ define([ var terrainVersion = -1; var terrainState = info.terrainState; if (terrainState !== TerrainState.NONE) { - switch(terrainState) { + switch (terrainState) { case TerrainState.SELF: // We have terrain and have retrieved it before terrainVersion = info.terrainVersion; break; case TerrainState.PARENT: // We have terrain in our parent - q = q.substring(0, q.length-1); + q = q.substring(0, q.length - 1); parentInfo = metadata.getTileInformationFromQuadKey(q); terrainVersion = parentInfo.terrainVersion; break; @@ -388,7 +431,7 @@ define([ if (info.hasTerrain()) { terrainVersion = info.terrainVersion; // We should have terrain } else { - q = q.substring(0, q.length-1); + q = q.substring(0, q.length - 1); parentInfo = metadata.getTileInformationFromQuadKey(q); if (defined(parentInfo) && parentInfo.hasTerrain()) { terrainVersion = parentInfo.terrainVersion; // Try checking in the parent @@ -431,13 +474,13 @@ define([ var uncompressedTerrain = GoogleEarthEnterpriseMetadata.uncompressPacket(terrain); parseTerrainPacket(that, uncompressedTerrain, q); - var buffer = terrainCache[quadKey]; + var buffer = terrainCache.get(quadKey); if (defined(buffer)) { if (q !== quadKey) { // If we didn't request this tile directly then it came from a parent info.terrainState = TerrainState.PARENT; } - delete terrainCache[quadKey]; + return new GoogleEarthEnterpriseTerrainData({ buffer : buffer, childTileMask : info.getChildBitmask() @@ -453,7 +496,7 @@ define([ info.terrainState = TerrainState.NONE; return when.reject(error); }); - } else if(!info.ancestorHasTerrain) { + } else if (!info.ancestorHasTerrain) { // We haven't reached a level with terrain, so return the ellipsoid return new HeightmapTerrainData({ buffer : new Uint8Array(16 * 16), @@ -468,6 +511,7 @@ define([ }; var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + function parseTerrainPacket(that, terrainData, quadKey) { var info = that._metadata.getTileInformationFromQuadKey(quadKey); var terrainCache = that._terrainCache; @@ -478,7 +522,7 @@ define([ var offset = 0; var terrainTiles = []; - while(offset < totalSize) { + while (offset < totalSize) { // Each tile is split into 4 parts var tileStart = offset; for (var quad = 0; quad < 4; ++quad) { @@ -490,13 +534,13 @@ define([ } // If we were sent child tiles, store them till they are needed - terrainCache[quadKey] = terrainTiles[0]; + terrainCache.add(quadKey, terrainTiles[0]); info.terrainState = TerrainState.SELF; - var count = terrainTiles.length-1; + var count = terrainTiles.length - 1; for (var j = 0; j < count; ++j) { var childKey = quadKey + j.toString(); if (info.hasChild(j)) { - terrainCache[childKey] = terrainTiles[j+1]; + terrainCache.add(childKey, terrainTiles[j + 1]); } } } diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index a0ef18a5c2de..60fddec9e6d5 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -1,40 +1,40 @@ /*global define*/ define([ - '../Core/Credit', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/GeographicTilingScheme', - '../Core/GoogleEarthEnterpriseMetadata', - '../Core/loadArrayBuffer', - '../Core/loadImageFromTypedArray', - '../Core/Math', - '../Core/Rectangle', - '../Core/RuntimeError', - '../Core/throttleRequestByServer', - '../Core/TileProviderError', - '../ThirdParty/protobuf-minimal', - '../ThirdParty/when' -], function( - Credit, - defaultValue, - defined, - defineProperties, - DeveloperError, - Event, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - loadArrayBuffer, - loadImageFromTypedArray, - CesiumMath, - Rectangle, - RuntimeError, - throttleRequestByServer, - TileProviderError, - protobuf, - when) { + '../Core/Credit', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/GeographicTilingScheme', + '../Core/GoogleEarthEnterpriseMetadata', + '../Core/loadArrayBuffer', + '../Core/loadImageFromTypedArray', + '../Core/Math', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/throttleRequestByServer', + '../Core/TileProviderError', + '../ThirdParty/protobuf-minimal', + '../ThirdParty/when' + ], function( + Credit, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + loadArrayBuffer, + loadImageFromTypedArray, + CesiumMath, + Rectangle, + RuntimeError, + throttleRequestByServer, + TileProviderError, + protobuf, + when) { 'use strict'; function GoogleEarthEnterpriseDiscardPolicy() { @@ -205,7 +205,7 @@ define([ * @type {Number} * @readonly */ - tileHeight: { + tileHeight : { get : function() { //>>includeStart('debug', pragmas.debug); if (!this._ready) { @@ -217,7 +217,6 @@ define([ } }, - /** * Gets the maximum level-of-detail that can be requested. This function should * not be called before {@link GoogleEarthEnterpriseImageryProvider#ready} returns true. @@ -447,13 +446,13 @@ define([ var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); var last; - while(q.length > 1) { - last = q.substring(q.length-1); - q = q.substring(0, q.length-1); + while (q.length > 1) { + last = q.substring(q.length - 1); + q = q.substring(0, q.length - 1); info = metadata.getTileInformationFromQuadKey(q); if (defined(info)) { if (!info.hasSubtree() && - !info.hasChild(parseInt(last))){ + !info.hasChild(parseInt(last))) { // We have no subtree or child available at some point in this node's ancestry return invalidImage; } @@ -598,7 +597,7 @@ define([ var imageType = message.imageType; if (defined(imageType)) { - switch(imageType) { + switch (imageType) { case 0: message.imageType = 'image/jpeg'; break; @@ -620,6 +619,5 @@ define([ return message; } - return GoogleEarthEnterpriseImageryProvider; }); diff --git a/Source/Scene/GoogleEarthImageryProvider.js b/Source/Scene/GoogleEarthImageryProvider.js index 8dc4d9e283e2..ec0e617edeb7 100644 --- a/Source/Scene/GoogleEarthImageryProvider.js +++ b/Source/Scene/GoogleEarthImageryProvider.js @@ -132,7 +132,6 @@ define([ this._version = undefined; - this._tileWidth = 256; this._tileHeight = 256; this._maximumLevel = options.maximumLevel; @@ -154,56 +153,56 @@ define([ try { // First, try parsing it like normal in case a future version sends correctly formatted JSON data = JSON.parse(text); - } catch(e) { + } catch (e) { // Quote object strings manually, then try parsing again data = JSON.parse(text.replace(/([\[\{,])[\n\r ]*([A-Za-z0-9]+)[\n\r ]*:/g, '$1"$2":')); } var layer; - for(var i = 0; i < data.layers.length; i++) { - if(data.layers[i].id === that._channel) { - layer = data.layers[i]; - break; - } + for (var i = 0; i < data.layers.length; i++) { + if (data.layers[i].id === that._channel) { + layer = data.layers[i]; + break; + } } var message; - if(!defined(layer)) { - message = 'Could not find layer with channel (id) of ' + that._channel + '.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); - throw new RuntimeError(message); + if (!defined(layer)) { + message = 'Could not find layer with channel (id) of ' + that._channel + '.'; + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + throw new RuntimeError(message); } - if(!defined(layer.version)) { - message = 'Could not find a version in channel (id) ' + that._channel + '.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); - throw new RuntimeError(message); + if (!defined(layer.version)) { + message = 'Could not find a version in channel (id) ' + that._channel + '.'; + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + throw new RuntimeError(message); } that._version = layer.version; - if(defined(data.projection) && data.projection === 'flat') { - that._tilingScheme = new GeographicTilingScheme({ - numberOfLevelZeroTilesX : 2, - numberOfLevelZeroTilesY : 2, - rectangle: new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI), - ellipsoid : options.ellipsoid - }); - // Default to mercator projection when projection is undefined - } else if(!defined(data.projection) || data.projection === 'mercator') { - that._tilingScheme = new WebMercatorTilingScheme({ - numberOfLevelZeroTilesX : 2, - numberOfLevelZeroTilesY : 2, - ellipsoid : options.ellipsoid - }); + if (defined(data.projection) && data.projection === 'flat') { + that._tilingScheme = new GeographicTilingScheme({ + numberOfLevelZeroTilesX : 2, + numberOfLevelZeroTilesY : 2, + rectangle : new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI), + ellipsoid : options.ellipsoid + }); + // Default to mercator projection when projection is undefined + } else if (!defined(data.projection) || data.projection === 'mercator') { + that._tilingScheme = new WebMercatorTilingScheme({ + numberOfLevelZeroTilesX : 2, + numberOfLevelZeroTilesY : 2, + ellipsoid : options.ellipsoid + }); } else { - message = 'Unsupported projection ' + data.projection + '.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); - throw new RuntimeError(message); + message = 'Unsupported projection ' + data.projection + '.'; + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + throw new RuntimeError(message); } that._imageUrlTemplate = that._imageUrlTemplate.replace('{request}', that._requestType) - .replace('{channel}', that._channel).replace('{version}', that._version); + .replace('{channel}', that._channel).replace('{version}', that._version); that._ready = true; that._readyPromise.resolve(true); @@ -217,10 +216,10 @@ define([ } function requestMetadata() { - var url = (!defined(that._proxy)) ? metadataUrl : that._proxy.getURL(metadataUrl); + var url = (!defined(that._proxy)) ? metadataUrl : that._proxy.getURL(metadataUrl); - var metadata = loadText(url); - when(metadata, metadataSuccess, metadataFailure); + var metadata = loadText(url); + when(metadata, metadataSuccess, metadataFailure); } requestMetadata(); @@ -301,7 +300,7 @@ define([ * @type {Number} * @readonly */ - tileHeight: { + tileHeight : { get : function() { //>>includeStart('debug', pragmas.debug); if (!this._ready) { diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index 421e6653292e..a612e37e208f 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -1,42 +1,42 @@ /*global define*/ define([ - '../Core/AxisAlignedBoundingBox', - '../Core/BoundingSphere', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/defaultValue', - '../Core/defined', - '../Core/Ellipsoid', - '../Core/EllipsoidalOccluder', - '../Core/Math', - '../Core/Matrix4', - '../Core/OrientedBoundingBox', - '../Core/Rectangle', - '../Core/RuntimeError', - '../Core/TerrainEncoding', - '../Core/Transforms', - '../Core/WebMercatorProjection', - './createTaskProcessorWorker' -], function( - AxisAlignedBoundingBox, - BoundingSphere, - Cartesian2, - Cartesian3, - Cartographic, - defaultValue, - defined, - Ellipsoid, - EllipsoidalOccluder, - CesiumMath, - Matrix4, - OrientedBoundingBox, - Rectangle, - RuntimeError, - TerrainEncoding, - Transforms, - WebMercatorProjection, - createTaskProcessorWorker) { + '../Core/AxisAlignedBoundingBox', + '../Core/BoundingSphere', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/defaultValue', + '../Core/defined', + '../Core/Ellipsoid', + '../Core/EllipsoidalOccluder', + '../Core/Math', + '../Core/Matrix4', + '../Core/OrientedBoundingBox', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/TerrainEncoding', + '../Core/Transforms', + '../Core/WebMercatorProjection', + './createTaskProcessorWorker' + ], function( + AxisAlignedBoundingBox, + BoundingSphere, + Cartesian2, + Cartesian3, + Cartographic, + defaultValue, + defined, + Ellipsoid, + EllipsoidalOccluder, + CesiumMath, + Matrix4, + OrientedBoundingBox, + Rectangle, + RuntimeError, + TerrainEncoding, + Transforms, + WebMercatorProjection, + createTaskProcessorWorker) { 'use strict'; var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index 3c0c6ffa2694..25cbef7554f6 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -1,18 +1,18 @@ /*global defineSuite*/ defineSuite([ - 'Core/GoogleEarthEnterpriseMetadata', - 'Core/DefaultProxy', - 'Core/defaultValue', - 'Core/loadWithXhr', - 'Core/Math', - 'ThirdParty/when' -], function( - GoogleEarthEnterpriseMetadata, - DefaultProxy, - defaultValue, - loadWithXhr, - CesiumMath, - when) { + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/loadWithXhr', + 'Core/Math', + 'ThirdParty/when' + ], function( + GoogleEarthEnterpriseMetadata, + DefaultProxy, + defaultValue, + loadWithXhr, + CesiumMath, + when) { 'use strict'; it('tileXYToQuadKey', function() { @@ -74,7 +74,7 @@ defineSuite([ }); var metadata = new GoogleEarthEnterpriseMetadata({ - url: 'http://test.server' + url : 'http://test.server' }); return metadata.readyPromise .then(function() { @@ -99,9 +99,10 @@ defineSuite([ var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + function createFakeMetadataResponse() { var numInstances = 2; - var buffer = new ArrayBuffer(32+numInstances*32); + var buffer = new ArrayBuffer(32 + numInstances * 32); var dv = new DataView(buffer); var offset = 0; @@ -121,7 +122,7 @@ defineSuite([ dv.setInt32(offset, 32, true); offset += sizeOfInt32; - dv.setInt32(offset, 32 + 32*numInstances, true); + dv.setInt32(offset, 32 + 32 * numInstances, true); offset += sizeOfInt32; dv.setInt32(offset, 0, true); @@ -131,7 +132,7 @@ defineSuite([ offset += sizeOfInt32; for (var i = 0; i < numInstances; ++i) { - if (i === (numInstances-1)) { + if (i === (numInstances - 1)) { dv.setUint8(offset, 0x40); } else { dv.setUint8(offset, 0x41); @@ -195,7 +196,7 @@ defineSuite([ }); var provider = new GoogleEarthEnterpriseMetadata({ - url: baseurl + url : baseurl }); return provider.readyPromise.then(function(result) { @@ -215,12 +216,12 @@ defineSuite([ it('rejects readyPromise on error', function() { var url = 'host.invalid/'; var provider = new GoogleEarthEnterpriseMetadata({ - url: url + url : url }); - return provider.readyPromise.then(function () { + return provider.readyPromise.then(function() { fail('should not resolve'); - }).otherwise(function (e) { + }).otherwise(function(e) { expect(e.message).toContain(url); }); }); diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index be4da1a37e1c..a6736fc35b06 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -1,32 +1,32 @@ /*global defineSuite*/ defineSuite([ - 'Core/GoogleEarthEnterpriseTerrainData', - 'Core/BoundingSphere', - 'Core/Cartesian3', - 'Core/Cartographic', - 'Core/Ellipsoid', - 'Core/GeographicTilingScheme', - 'Core/Math', - 'Core/Matrix4', - 'Core/Rectangle', - 'Core/TerrainData', - 'Core/TerrainMesh', - 'Core/Transforms', - 'ThirdParty/when' -], function( - GoogleEarthEnterpriseTerrainData, - BoundingSphere, - Cartesian3, - Cartographic, - Ellipsoid, - GeographicTilingScheme, - CesiumMath, - Matrix4, - Rectangle, - TerrainData, - TerrainMesh, - Transforms, - when) { + 'Core/GoogleEarthEnterpriseTerrainData', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/GeographicTilingScheme', + 'Core/Math', + 'Core/Matrix4', + 'Core/Rectangle', + 'Core/TerrainData', + 'Core/TerrainMesh', + 'Core/Transforms', + 'ThirdParty/when' + ], function( + GoogleEarthEnterpriseTerrainData, + BoundingSphere, + Cartesian3, + Cartographic, + Ellipsoid, + GeographicTilingScheme, + CesiumMath, + Matrix4, + Rectangle, + TerrainData, + TerrainMesh, + Transforms, + when) { 'use strict'; var sizeOfUint8 = Uint8Array.BYTES_PER_ELEMENT; @@ -35,7 +35,7 @@ defineSuite([ var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; - var toEarthRadii = 1.0/6371010.0; + var toEarthRadii = 1.0 / 6371010.0; function getBuffer(tilingScheme, x, y, level) { var rectangle = tilingScheme.tileXYToRectangle(x, y, level); @@ -63,7 +63,7 @@ defineSuite([ var altitudeStart = 0; var offset = 0; - for(var i=0;i<4;++i) { + for (var i = 0; i < 4; ++i) { altitudeStart = 0; dv.setUint32(offset, quadSize, true); offset += sizeOfUint32; @@ -83,9 +83,9 @@ defineSuite([ altitudeStart = 10; } - dv.setFloat64(offset, CesiumMath.toDegrees(xOrigin)/180.0, true); + dv.setFloat64(offset, CesiumMath.toDegrees(xOrigin) / 180.0, true); offset += sizeOfDouble; - dv.setFloat64(offset, CesiumMath.toDegrees(yOrigin)/180.0, true); + dv.setFloat64(offset, CesiumMath.toDegrees(yOrigin) / 180.0, true); offset += sizeOfDouble; // Step - Each step is a degree @@ -126,7 +126,7 @@ defineSuite([ } // Faces - var indices = [0,1,2,1,3,2]; + var indices = [0, 1, 2, 1, 3, 2]; for (j = 0; j < indices.length; ++j) { dv.setUint16(offset, indices[j], true); offset += sizeOfUint16; @@ -282,7 +282,7 @@ defineSuite([ encoding.decodePosition(mesh.vertices, i, cartesian); wgs84.cartesianToCartographic(cartesian, cartographic); cartographic.longitude = CesiumMath.convertLongitudeRange(cartographic.longitude); - expect(Rectangle.contains(rectangle,cartographic)).toBe(true); + expect(Rectangle.contains(rectangle, cartographic)).toBe(true); } else { // Skirts expect(height).toBeBetween(-1000, -980); } @@ -360,7 +360,7 @@ defineSuite([ beforeEach(function() { data = new GoogleEarthEnterpriseTerrainData({ - buffer: new ArrayBuffer(1), + buffer : new ArrayBuffer(1), childTileMask : 15 }); }); @@ -391,7 +391,7 @@ defineSuite([ it('returns true for all children when child mask is not explicitly specified', function() { data = new GoogleEarthEnterpriseTerrainData({ - buffer: new ArrayBuffer(1) + buffer : new ArrayBuffer(1) }); expect(data.isChildAvailable(10, 20, 20, 40)).toBe(true); @@ -402,7 +402,7 @@ defineSuite([ it('works when only southwest child is available', function() { data = new GoogleEarthEnterpriseTerrainData({ - buffer: new ArrayBuffer(1), + buffer : new ArrayBuffer(1), childTileMask : 1 }); @@ -414,7 +414,7 @@ defineSuite([ it('works when only southeast child is available', function() { data = new GoogleEarthEnterpriseTerrainData({ - buffer: new ArrayBuffer(1), + buffer : new ArrayBuffer(1), childTileMask : 2 }); @@ -426,7 +426,7 @@ defineSuite([ it('works when only northeast child is available', function() { data = new GoogleEarthEnterpriseTerrainData({ - buffer: new ArrayBuffer(1), + buffer : new ArrayBuffer(1), childTileMask : 4 }); @@ -438,7 +438,7 @@ defineSuite([ it('works when only northwest child is available', function() { data = new GoogleEarthEnterpriseTerrainData({ - buffer: new ArrayBuffer(1), + buffer : new ArrayBuffer(1), childTileMask : 8 }); diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index 3ea1dc60f69a..47e52128fe32 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -44,6 +44,7 @@ defineSuite([ } var terrainProvider; + function waitForTile(level, x, y, f) { terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ url : 'made/up/url' @@ -74,8 +75,7 @@ defineSuite([ }).toThrowDeveloperError(); expect(function() { - return new GoogleEarthEnterpriseTerrainProvider({ - }); + return new GoogleEarthEnterpriseTerrainProvider({}); }).toThrowDeveloperError(); }); @@ -86,7 +86,7 @@ defineSuite([ url : 'made/up/url' }); - return terrainProvider.readyPromise.then(function (result) { + return terrainProvider.readyPromise.then(function(result) { expect(result).toBe(true); expect(terrainProvider.ready).toBe(true); }); diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js index bd697a9d7b58..125d47cac4b3 100644 --- a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js +++ b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js @@ -1,38 +1,38 @@ /*global defineSuite*/ defineSuite([ - 'Scene/GoogleEarthEnterpriseImageryProvider', - 'Core/DefaultProxy', - 'Core/defaultValue', - 'Core/defined', - 'Core/GeographicTilingScheme', - 'Core/GoogleEarthEnterpriseMetadata', - 'Core/loadImage', - 'Core/loadWithXhr', - 'Core/Rectangle', - 'Scene/DiscardMissingTileImagePolicy', - 'Scene/Imagery', - 'Scene/ImageryLayer', - 'Scene/ImageryProvider', - 'Scene/ImageryState', - 'Specs/pollToPromise', - 'ThirdParty/when' -], function( - GoogleEarthEnterpriseImageryProvider, - DefaultProxy, - defaultValue, - defined, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - loadImage, - loadWithXhr, - Rectangle, - DiscardMissingTileImagePolicy, - Imagery, - ImageryLayer, - ImageryProvider, - ImageryState, - pollToPromise, - when) { + 'Scene/GoogleEarthEnterpriseImageryProvider', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/defined', + 'Core/GeographicTilingScheme', + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/loadImage', + 'Core/loadWithXhr', + 'Core/Rectangle', + 'Scene/DiscardMissingTileImagePolicy', + 'Scene/Imagery', + 'Scene/ImageryLayer', + 'Scene/ImageryProvider', + 'Scene/ImageryState', + 'Specs/pollToPromise', + 'ThirdParty/when' + ], function( + GoogleEarthEnterpriseImageryProvider, + DefaultProxy, + defaultValue, + defined, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + loadImage, + loadWithXhr, + Rectangle, + DiscardMissingTileImagePolicy, + Imagery, + ImageryLayer, + ImageryProvider, + ImageryState, + pollToPromise, + when) { 'use strict'; var oldDecode; @@ -59,9 +59,9 @@ defineSuite([ it('constructor throws when url is not specified', function() { function constructWithoutServer() { - return new GoogleEarthEnterpriseImageryProvider({ - }); + return new GoogleEarthEnterpriseImageryProvider({}); } + expect(constructWithoutServer).toThrowDeveloperError(); }); @@ -121,9 +121,9 @@ defineSuite([ url : url }); - return imageryProvider.readyPromise.then(function () { + return imageryProvider.readyPromise.then(function() { fail('should not resolve'); - }).otherwise(function (e) { + }).otherwise(function(e) { expect(imageryProvider.ready).toBe(false); expect(e.message).toContain(url); }); diff --git a/Specs/Scene/GoogleEarthImageryProviderSpec.js b/Specs/Scene/GoogleEarthImageryProviderSpec.js index 7df3ce2e74ea..cef6ee3fbc5a 100644 --- a/Specs/Scene/GoogleEarthImageryProviderSpec.js +++ b/Specs/Scene/GoogleEarthImageryProviderSpec.js @@ -42,6 +42,7 @@ defineSuite([ channel : 1234 }); } + expect(constructWithoutServer).toThrowDeveloperError(); }); @@ -51,6 +52,7 @@ defineSuite([ url : 'http://invalid.localhost' }); } + expect(constructWithoutChannel).toThrowDeveloperError(); }); @@ -69,7 +71,7 @@ defineSuite([ path : path }); - return provider.readyPromise.then(function (result) { + return provider.readyPromise.then(function(result) { expect(result).toBe(true); expect(provider.ready).toBe(true); }); @@ -82,9 +84,9 @@ defineSuite([ channel : 1234 }); - return provider.readyPromise.then(function () { + return provider.readyPromise.then(function() { fail('should not resolve'); - }).otherwise(function (e) { + }).otherwise(function(e) { expect(provider.ready).toBe(false); expect(e.message).toContain(url); }); @@ -178,29 +180,29 @@ defineSuite([ loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { return deferred.resolve('{\n' + - 'isAuthenticated: true,\n' + - 'layers: [\n' + - ' {\n' + - ' icon: "icons/773_l.png",\n' + - ' id: 1234,\n' + - ' initialState: true,\n' + - ' label: "Imagery",\n' + - ' lookAt: "none",\n' + - ' requestType: "ImageryMaps",\n' + - ' version: 1\n' + - ' },{\n' + - ' icon: "icons/773_l.png",\n' + - ' id: 1007,\n' + - ' initialState: true,\n' + - ' label: "Labels",\n' + - ' lookAt: "none",\n' + - ' requestType: "VectorMapsRaster",\n' + - ' version: 8\n' + - ' }\n' + - '],\n' + - 'serverUrl: "https://example.invalid",\n' + - 'useGoogleLayers: false\n' + - '}'); + 'isAuthenticated: true,\n' + + 'layers: [\n' + + ' {\n' + + ' icon: "icons/773_l.png",\n' + + ' id: 1234,\n' + + ' initialState: true,\n' + + ' label: "Imagery",\n' + + ' lookAt: "none",\n' + + ' requestType: "ImageryMaps",\n' + + ' version: 1\n' + + ' },{\n' + + ' icon: "icons/773_l.png",\n' + + ' id: 1007,\n' + + ' initialState: true,\n' + + ' label: "Labels",\n' + + ' lookAt: "none",\n' + + ' requestType: "VectorMapsRaster",\n' + + ' version: 8\n' + + ' }\n' + + '],\n' + + 'serverUrl: "https://example.invalid",\n' + + 'useGoogleLayers: false\n' + + '}'); }; var provider = new GoogleEarthImageryProvider({ From 2d304a007fd03125dee1176456b30402ed48c96c Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 12:05:17 -0400 Subject: [PATCH 41/60] Sped up GEE by throttling metadata requests and offloading terrain decoding/uncompressing to a web worker. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 42 ++- .../GoogleEarthEnterpriseTerrainProvider.js | 250 +++++++++--------- .../GoogleEarthEnterpriseImageryProvider.js | 29 +- ...ecodeGoogleEarthEnterpriseTerrainPacket.js | 73 +++++ 4 files changed, 249 insertions(+), 145 deletions(-) create mode 100644 Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 0e6780e402f0..74820bdcb594 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -8,7 +8,8 @@ define([ './defineProperties', './DeveloperError', './loadArrayBuffer', - './RuntimeError' + './RuntimeError', + './throttleRequestByServer' ], function( pako, when, @@ -18,7 +19,8 @@ define([ defineProperties, DeveloperError, loadArrayBuffer, - RuntimeError) { + RuntimeError, + throttleRequestByServer) { 'use strict'; // Bitmask for checking tile properties @@ -152,7 +154,7 @@ define([ this._subtreePromises = {}; var that = this; - this._readyPromise = this.getQuadTreePacket() + this._readyPromise = this.getQuadTreePacket('', 1, false) .then(function() { return true; }) @@ -405,17 +407,23 @@ define([ * * @private */ - GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket = function(quadKey, version) { + GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket = function(quadKey, version, throttle) { version = defaultValue(version, 1); quadKey = defaultValue(quadKey, ''); + throttle = defaultValue(throttle, true); var url = getMetadataUrl(this, quadKey, version); var proxy = this._proxy; if (defined(proxy)) { url = proxy.getURL(url); } + var promise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(promise)) { + return undefined; + } + var that = this; - return loadArrayBuffer(url) + return promise .then(function(metadata) { GoogleEarthEnterpriseMetadata.decode(metadata); @@ -434,8 +442,12 @@ define([ throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); } - //var version = dv.getUint32(offset, true); + // Tile format version + var quadVersion = dv.getUint32(offset, true); offset += sizeOfUint32; + if (quadVersion !== 2) { + throw new RuntimeError('Invalid quadpacket version. Only version 2 is supported.'); + } var numInstances = dv.getInt32(offset, true); offset += sizeOfInt32; @@ -572,12 +584,12 @@ define([ * * @private */ - GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level) { + GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level, throttle) { var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); return populateSubtree(this, quadkey); }; - function populateSubtree(that, quadKey) { + function populateSubtree(that, quadKey, throttle) { var tileInfo = that._tileInfo; var q = quadKey; var t = tileInfo[q]; @@ -595,7 +607,7 @@ define([ // null so one of its parents was a leaf node, so this tile doesn't exist // undefined so no parent exists - this shouldn't ever happen once the provider is ready if (!defined(t)) { - return when(undefined); + return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); } var subtreePromises = that._subtreePromises; @@ -603,6 +615,9 @@ define([ if (defined(promise)) { return promise .then(function() { + if (!defined(tileInfo[quadKey])) { + return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); + } return tileInfo[quadKey]; }); } @@ -611,7 +626,11 @@ define([ // is already resolved (like in the tests), so subtreePromises will never get cleared out. // The promise will always resolve with a bool, but the initial request will also remove // the promise from subtreePromises. - promise = subtreePromises[q] = that.getQuadTreePacket(q, t.cnodeVersion); + promise = that.getQuadTreePacket(q, t.cnodeVersion, throttle); + if (!defined(promise)) { + return undefined; + } + subtreePromises[q] = promise; return promise .then(function() { @@ -622,6 +641,9 @@ define([ delete subtreePromises[q]; }) .then(function() { + if (!defined(tileInfo[quadKey])) { + return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); + } return tileInfo[quadKey]; }); } diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index 7055118b8b4a..f6e09b2bd06c 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -21,6 +21,7 @@ define([ './Math', './Rectangle', './RuntimeError', + './TaskProcessor', './TerrainProvider', './throttleRequestByServer', './TileProviderError' @@ -46,6 +47,7 @@ define([ CesiumMath, Rectangle, RuntimeError, + TaskProcessor, TerrainProvider, throttleRequestByServer, TileProviderError) { @@ -315,6 +317,8 @@ define([ } }); + var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterpriseTerrainPacket'); + /** * Requests the geometry for a given tile. This function should not be called before * {@link GoogleEarthEnterpriseProvider#ready} returns true. The result must include terrain data and @@ -392,159 +396,153 @@ define([ }); } - return undefined; + return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } var that = this; var terrainPromises = this._terrainPromises; - return metadata.populateSubtree(x, y, level) + var metadataPromise = metadata.populateSubtree(x, y, level, throttleRequests); + if (!defined(metadataPromise)) { + return undefined; // Metadata request throttled + } + + return metadataPromise .then(function(info) { - if (defined(info)) { - var buffer = terrainCache.get(quadKey); - if (defined(buffer)) { - if (info.terrainState === TerrainState.UNKNOWN) { - // If its already in the cache then a parent request must've loaded it - info.terrainState = TerrainState.PARENT; - } + if (!defined(info)) { + return undefined; //Metadata request throttled + } - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : info.getChildBitmask() - }); + var buffer = terrainCache.get(quadKey); + if (defined(buffer)) { + if (info.terrainState === TerrainState.UNKNOWN) { + // If its already in the cache then a parent request must've loaded it + info.terrainState = TerrainState.PARENT; } - var parentInfo; - var q = quadKey; - var terrainVersion = -1; - var terrainState = info.terrainState; - if (terrainState !== TerrainState.NONE) { - switch (terrainState) { - case TerrainState.SELF: // We have terrain and have retrieved it before - terrainVersion = info.terrainVersion; - break; - case TerrainState.PARENT: // We have terrain in our parent + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : info.getChildBitmask() + }); + } + + var parentInfo; + var q = quadKey; + var terrainVersion = -1; + var terrainState = info.terrainState; + if (terrainState !== TerrainState.NONE) { + switch (terrainState) { + case TerrainState.SELF: // We have terrain and have retrieved it before + terrainVersion = info.terrainVersion; + break; + case TerrainState.PARENT: // We have terrain in our parent + q = q.substring(0, q.length - 1); + parentInfo = metadata.getTileInformationFromQuadKey(q); + terrainVersion = parentInfo.terrainVersion; + break; + case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet + if (info.hasTerrain()) { + terrainVersion = info.terrainVersion; // We should have terrain + } else { q = q.substring(0, q.length - 1); parentInfo = metadata.getTileInformationFromQuadKey(q); - terrainVersion = parentInfo.terrainVersion; - break; - case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet - if (info.hasTerrain()) { - terrainVersion = info.terrainVersion; // We should have terrain - } else { - q = q.substring(0, q.length - 1); - parentInfo = metadata.getTileInformationFromQuadKey(q); - if (defined(parentInfo) && parentInfo.hasTerrain()) { - terrainVersion = parentInfo.terrainVersion; // Try checking in the parent - } + if (defined(parentInfo) && parentInfo.hasTerrain()) { + terrainVersion = parentInfo.terrainVersion; // Try checking in the parent } - break; - } + } + break; } + } - if (terrainVersion > 0) { - var url = buildTerrainUrl(that, q, terrainVersion); - var promise; - if (defined(terrainPromises[q])) { - promise = terrainPromises[q]; - } else { - var requestPromise; - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - requestPromise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(requestPromise)) { - return undefined; - } - } else { - requestPromise = loadArrayBuffer(url); + if (terrainVersion > 0) { + var url = buildTerrainUrl(that, q, terrainVersion); + var promise; + if (defined(terrainPromises[q])) { + promise = terrainPromises[q]; + } else { + var requestPromise; + throttleRequests = defaultValue(throttleRequests, true); + if (throttleRequests) { + requestPromise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(requestPromise)) { + return undefined; } + } else { + requestPromise = loadArrayBuffer(url); + } - terrainPromises[q] = requestPromise; + terrainPromises[q] = requestPromise; - promise = requestPromise - .always(function(terrain) { - delete terrainPromises[q]; - return terrain; - }); - } + promise = requestPromise + .always(function(terrain) { + delete terrainPromises[q]; + return terrain; + }); + } - return promise - .then(function(terrain) { - if (defined(terrain)) { - GoogleEarthEnterpriseMetadata.decode(terrain); - var uncompressedTerrain = GoogleEarthEnterpriseMetadata.uncompressPacket(terrain); - parseTerrainPacket(that, uncompressedTerrain, q); + return promise + .then(function(terrain) { + if (defined(terrain)) { + var decodePromise = taskProcessor.scheduleTask({ + buffer : terrain, + type : 'Terrain' + }); - var buffer = terrainCache.get(quadKey); - if (defined(buffer)) { + if (!defined(decodePromise)) { + // Postponed + return undefined; + } + return decodePromise + .then(function(terrainTiles) { + // If we were sent child tiles, store them till they are needed + var buffer; if (q !== quadKey) { - // If we didn't request this tile directly then it came from a parent - info.terrainState = TerrainState.PARENT; + terrainCache.add(q, terrainTiles[0]); + var parentInfo = metadata.getTileInformationFromQuadKey(q); + parentInfo.terrainState = TerrainState.SELF; + } else { + buffer = terrainTiles[0]; + info.terrainState = TerrainState.SELF; + } + var count = terrainTiles.length - 1; + for (var j = 0; j < count; ++j) { + var childKey = q + j.toString(); + if (childKey === quadKey) { + buffer = terrainTiles[j + 1]; + // If we didn't request this tile directly then it came from a parent + info.terrainState = TerrainState.PARENT; + } else if (info.hasChild(j)) { + terrainCache.add(childKey, terrainTiles[j + 1]); + } } - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : info.getChildBitmask() - }); - } else { - info.terrainState = TerrainState.NONE; - } - } + if (defined(buffer)) { + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : info.getChildBitmask() + }); + } else { + info.terrainState = TerrainState.NONE; + } + }); + } - return when.reject(new RuntimeError('Failed to load terrain.')); - }) - .otherwise(function(error) { - info.terrainState = TerrainState.NONE; - return when.reject(error); - }); - } else if (!info.ancestorHasTerrain) { - // We haven't reached a level with terrain, so return the ellipsoid - return new HeightmapTerrainData({ - buffer : new Uint8Array(16 * 16), - width : 16, - height : 16 + return when.reject(new RuntimeError('Failed to load terrain.')); + }) + .otherwise(function(error) { + info.terrainState = TerrainState.NONE; + return when.reject(error); }); - } + } else if (!info.ancestorHasTerrain) { + // We haven't reached a level with terrain, so return the ellipsoid + return new HeightmapTerrainData({ + buffer : new Uint8Array(16 * 16), + width : 16, + height : 16 + }); } - - return when.reject(new RuntimeError('Failed to load terrain.')); }); }; - var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - - function parseTerrainPacket(that, terrainData, quadKey) { - var info = that._metadata.getTileInformationFromQuadKey(quadKey); - var terrainCache = that._terrainCache; - var buffer = terrainData.buffer; - - var dv = new DataView(buffer); - var totalSize = terrainData.length; - - var offset = 0; - var terrainTiles = []; - while (offset < totalSize) { - // Each tile is split into 4 parts - var tileStart = offset; - for (var quad = 0; quad < 4; ++quad) { - var size = dv.getUint32(offset, true); - offset += sizeOfUint32; - offset += size; - } - terrainTiles.push(buffer.slice(tileStart, offset)); - } - - // If we were sent child tiles, store them till they are needed - terrainCache.add(quadKey, terrainTiles[0]); - info.terrainState = TerrainState.SELF; - var count = terrainTiles.length - 1; - for (var j = 0; j < count; ++j) { - var childKey = quadKey + j.toString(); - if (info.hasChild(j)) { - terrainCache.add(childKey, terrainTiles[j + 1]); - } - } - } - /** * Gets the maximum geometric error allowed in a tile at a given level. * diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 60fddec9e6d5..b7010a0bbc28 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -435,9 +435,6 @@ define([ } var url = buildImageUrl(this, info, x, y, level); promise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(promise)) { - return; // Returning undefined will allow for a retry later - } } else { if (info === null) { // Parent was retrieved and said child doesn't exist @@ -466,17 +463,31 @@ define([ // There is nothing in the heirarchy that leads us to think this tile isn't available // so populate the tree so the info is available for this tile. - promise = metadata.populateSubtree(x, y, level) - .then(function(info) { - if (defined(info)) { + var metadataPromise = metadata.populateSubtree(x, y, level, true); + + if (defined(metadataPromise)) { + promise = metadataPromise + .then(function(info) { + if (!defined(info)) { + return undefined; //Metadata throttled + } + if (info.hasImagery()) { var url = buildImageUrl(that, info, x, y, level); return throttleRequestByServer(url, loadArrayBuffer); } - } - return when(invalidImage); - }); + return when(invalidImage); + }) + .otherwise(function() { + // Metadata couldn't be loaded, so imagery isn't available + return invalidImage; + }); + } + } + + if (!defined(promise)) { + return undefined; // Metadata or Image request throttled } return promise diff --git a/Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js b/Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js new file mode 100644 index 000000000000..43ed8828d06f --- /dev/null +++ b/Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js @@ -0,0 +1,73 @@ +/*global define*/ +define([ + '../Core/GoogleEarthEnterpriseMetadata', + './createTaskProcessorWorker' +], function( + GoogleEarthEnterpriseMetadata, + createTaskProcessorWorker) { + 'use strict'; + + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + + var Types = { + METADATA : 0, + IMAGERY : 1, + TERRAIN : 2 + }; + + Types.fromString = function(s) { + if (s === 'Metadata') { + return Types.METADATA; + } else if (s === 'Imagery') { + return Types.IMAGERY; + } else if (s === 'Terrain') { + return Types.TERRAIN; + } + }; + + function decodeGoogleEarthEnterpriseTerrainPacket(parameters, transferableObjects) { + var type = Types.fromString(parameters.type); + var buffer = parameters.buffer; + GoogleEarthEnterpriseMetadata.decode(buffer); + + var length = buffer.byteLength; + if (type !== Types.IMAGERY) { + var uncompressedTerrain = GoogleEarthEnterpriseMetadata.uncompressPacket(buffer); + buffer = uncompressedTerrain.buffer; + length = uncompressedTerrain.length; + } + + switch(type) { + case Types.METADATA: + break; + case Types.IMAGERY: + break; + case Types.TERRAIN: + return processTerrain(buffer, length, transferableObjects); + } + + } + + function processTerrain(buffer, totalSize, transferableObjects) { + var dv = new DataView(buffer); + + var offset = 0; + var terrainTiles = []; + while (offset < totalSize) { + // Each tile is split into 4 parts + var tileStart = offset; + for (var quad = 0; quad < 4; ++quad) { + var size = dv.getUint32(offset, true); + offset += sizeOfUint32; + offset += size; + } + var tile = buffer.slice(tileStart, offset); + transferableObjects.push(tile); + terrainTiles.push(tile); + } + + return terrainTiles; + } + + return createTaskProcessorWorker(decodeGoogleEarthEnterpriseTerrainPacket); +}); From d212c9b745248c69f2de8836e8b7fcd6cdaa5202 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 12:14:27 -0400 Subject: [PATCH 42/60] Fixed tests. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 13 ++++++++++--- Specs/Core/GoogleEarthEnterpriseMetadataSpec.js | 8 ++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 74820bdcb594..022db597d02c 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -404,6 +404,9 @@ define([ * * @param {String} [quadKey=''] The quadkey to retrieve the packet for. * @param {Number} [version=1] The cnode version to be used in the request. + * @param {Boolean} [throttle=true] True if the number of simultaneous requests should be limited, + * or false if the request should be initiated regardless of the number of requests + * already in progress. * * @private */ @@ -446,7 +449,7 @@ define([ var quadVersion = dv.getUint32(offset, true); offset += sizeOfUint32; if (quadVersion !== 2) { - throw new RuntimeError('Invalid quadpacket version. Only version 2 is supported.'); + throw new RuntimeError('Invalid QuadTreePacket version. Only version 2 is supported.'); } var numInstances = dv.getInt32(offset, true); @@ -579,14 +582,18 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Boolean} [throttle=true] True if the number of simultaneous requests should be limited, + * or false if the request should be initiated regardless of the number of requests + * already in progress. * * @returns {Promise} A promise that resolves to the tile info for the requested quad key * * @private */ GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level, throttle) { + throttle = defaultValue(throttle, true); var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - return populateSubtree(this, quadkey); + return populateSubtree(this, quadkey, throttle); }; function populateSubtree(that, quadKey, throttle) { @@ -635,7 +642,7 @@ define([ return promise .then(function() { // Recursively call this incase we need multiple subtree requests - return populateSubtree(that, quadKey); + return populateSubtree(that, quadKey, throttle); }) .always(function() { delete subtreePromises[q]; diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index 25cbef7554f6..1ad3005ff753 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -83,10 +83,10 @@ defineSuite([ }) .then(function() { expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket.calls.count()).toEqual(4); - expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith(); - expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('0', 1); - expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('01', 1); - expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('012', 1); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('', 1, false); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('0', 1, true); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('01', 1, true); + expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('012', 1, true); var tileInfo = metadata._tileInfo; expect(tileInfo['0']).toBeDefined(); From 209951af78afeb926932bbc324d3a8ad0e25ceb5 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 16:58:42 -0400 Subject: [PATCH 43/60] Moved metadata decoding to WebWorker. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 245 ++++------------- .../GoogleEarthEnterpriseTerrainProvider.js | 6 +- .../decodeGoogleEarthEnterprisePacket.js | 250 ++++++++++++++++++ ...ecodeGoogleEarthEnterpriseTerrainPacket.js | 73 ----- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 105 +------- Specs/Data/GoogleEarthEnterprise/gee.metadata | Bin 0 -> 43 bytes 6 files changed, 310 insertions(+), 369 deletions(-) create mode 100644 Source/Workers/decodeGoogleEarthEnterprisePacket.js delete mode 100644 Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js create mode 100644 Specs/Data/GoogleEarthEnterprise/gee.metadata diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 022db597d02c..67c335a952e8 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -9,6 +9,7 @@ define([ './DeveloperError', './loadArrayBuffer', './RuntimeError', + './TaskProcessor', './throttleRequestByServer' ], function( pako, @@ -20,6 +21,7 @@ define([ DeveloperError, loadArrayBuffer, RuntimeError, + TaskProcessor, throttleRequestByServer) { 'use strict'; @@ -30,11 +32,6 @@ define([ var imageBitmask = 0x40; var terrainBitmask = 0x80; - // Datatype sizes - var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; - var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; - var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - function isBitSet(bits, mask) { return ((bits & mask) !== 0); } @@ -58,6 +55,19 @@ define([ this.terrainState = 0; // UNKNOWN } + /** + * Creates TileInformation from an object + * + * @param {Object} obj Object with same properties as TileInformation + */ + TileInformation.fromObject = function(obj) { + var newInfo = new TileInformation(obj._bits, obj.cnodeVersion, obj.imageryVersion, obj.terrainVersion); + newInfo.ancestorHasTerrain = obj.ancestorHasTerrain; + newInfo.terrainState = obj.terrainState; + + return newInfo; + }; + /** * Sets the parent for the tile * @@ -277,6 +287,9 @@ define([ }; }; + var compressedMagic = 0x7468dead; + var compressedMagicSwap = 0xadde6874; + // Decodes packet with a key that has been around since the beginning of Google Earth Enterprise var key = "\x45\xf4\xbd\x0b\x79\xe2\x6a\x45\x22\x05\x92\x2c\x17\xcd\x06\x71\xf8\x49\x10\x46\x67\x51\x00\x42\x25\xc6\xe8\x61\x2c\x66\x29\x08\xc6\x34\xdc\x6a\x62\x25\x79\x0a\x77\x1d\x6d\x69\xd6\xf0\x9c\x6b\x93\xa1\xbd\x4e\x75\xe0\x41\x04\x5b\xdf\x40\x56\x0c\xd9\xbb\x72\x9b\x81\x7c\x10\x33\x53\xee\x4f\x6c\xd4\x71\x05\xb0\x7b\xc0\x7f\x45\x03\x56\x5a\xad\x77\x55\x65\x0b\x33\x92\x2a\xac\x19\x6c\x35\x14\xc5\x1d\x30\x73\xf8\x33\x3e\x6d\x46\x38\x4a\xb4\xdd\xf0\x2e\xdd\x17\x75\x16\xda\x8c\x44\x74\x22\x06\xfa\x61\x22\x0c\x33\x22\x53\x6f\xaf\x39\x44\x0b\x8c\x0e\x39\xd9\x39\x13\x4c\xb9\xbf\x7f\xab\x5c\x8c\x50\x5f\x9f\x22\x75\x78\x1f\xe9\x07\x71\x91\x68\x3b\xc1\xc4\x9b\x7f\xf0\x3c\x56\x71\x48\x82\x05\x27\x55\x66\x59\x4e\x65\x1d\x98\x75\xa3\x61\x46\x7d\x61\x3f\x15\x41\x00\x9f\x14\x06\xd7\xb4\x34\x4d\xce\x13\x87\x46\xb0\x1a\xd5\x05\x1c\xb8\x8a\x27\x7b\x8b\xdc\x2b\xbb\x4d\x67\x30\xc8\xd1\xf6\x5c\x8f\x50\xfa\x5b\x2f\x46\x9b\x6e\x35\x18\x2f\x27\x43\x2e\xeb\x0a\x0c\x5e\x10\x05\x10\xa5\x73\x1b\x65\x34\xe5\x6c\x2e\x6a\x43\x27\x63\x14\x23\x55\xa9\x3f\x71\x7b\x67\x43\x7d\x3a\xaf\xcd\xe2\x54\x55\x9c\xfd\x4b\xc6\xe2\x9f\x2f\x28\xed\xcb\x5c\xc6\x2d\x66\x07\x88\xa7\x3b\x2f\x18\x2a\x22\x4e\x0e\xb0\x6b\x2e\xdd\x0d\x95\x7d\x7d\x47\xba\x43\xb2\x11\xb2\x2b\x3e\x4d\xaa\x3e\x7d\xe6\xce\x49\x89\xc6\xe6\x78\x0c\x61\x31\x05\x2d\x01\xa4\x4f\xa5\x7e\x71\x20\x88\xec\x0d\x31\xe8\x4e\x0b\x00\x6e\x50\x68\x7d\x17\x3d\x08\x0d\x17\x95\xa6\x6e\xa3\x68\x97\x24\x5b\x6b\xf3\x17\x23\xf3\xb6\x73\xb3\x0d\x0b\x40\xc0\x9f\xd8\x04\x51\x5d\xfa\x1a\x17\x22\x2e\x15\x6a\xdf\x49\x00\xb9\xa0\x77\x55\xc6\xef\x10\x6a\xbf\x7b\x47\x4c\x7f\x83\x17\x05\xee\xdc\xdc\x46\x85\xa9\xad\x53\x07\x2b\x53\x34\x06\x07\xff\x14\x94\x59\x19\x02\xe4\x38\xe8\x31\x83\x4e\xb9\x58\x46\x6b\xcb\x2d\x23\x86\x92\x70\x00\x35\x88\x22\xcf\x31\xb2\x26\x2f\xe7\xc3\x75\x2d\x36\x2c\x72\x74\xb0\x23\x47\xb7\xd3\xd1\x26\x16\x85\x37\x72\xe2\x00\x8c\x44\xcf\x10\xda\x33\x2d\x1a\xde\x60\x86\x69\x23\x69\x2a\x7c\xcd\x4b\x51\x0d\x95\x54\x39\x77\x2e\x29\xea\x1b\xa6\x50\xa2\x6a\x8f\x6f\x50\x99\x5c\x3e\x54\xfb\xef\x50\x5b\x0b\x07\x45\x17\x89\x6d\x28\x13\x77\x37\x1d\xdb\x8e\x1e\x4a\x05\x66\x4a\x6f\x99\x20\xe5\x70\xe2\xb9\x71\x7e\x0c\x6d\x49\x04\x2d\x7a\xfe\x72\xc7\xf2\x59\x30\x8f\xbb\x02\x5d\x73\xe5\xc9\x20\xea\x78\xec\x20\x90\xf0\x8a\x7f\x42\x17\x7c\x47\x19\x60\xb0\x16\xbd\x26\xb7\x71\xb6\xc7\x9f\x0e\xd1\x33\x82\x3d\xd3\xab\xee\x63\x99\xc8\x2b\x53\xa0\x44\x5c\x71\x01\xc6\xcc\x44\x1f\x32\x4f\x3c\xca\xc0\x29\x3d\x52\xd3\x61\x19\x58\xa9\x7d\x65\xb4\xdc\xcf\x0d\xf4\x3d\xf1\x08\xa9\x42\xda\x23\x09\xd8\xbf\x5e\x50\x49\xf8\x4d\xc0\xcb\x47\x4c\x1c\x4f\xf7\x7b\x2b\xd8\x16\x18\xc5\x31\x92\x3b\xb5\x6f\xdc\x6c\x0d\x92\x88\x16\xd1\x9e\xdb\x3f\xe2\xe9\xda\x5f\xd4\x84\xe2\x46\x61\x5a\xde\x1c\x55\xcf\xa4\x00\xbe\xfd\xce\x67\xf1\x4a\x69\x1c\x97\xe6\x20\x48\xd8\x5d\x7f\x7e\xae\x71\x20\x0e\x4e\xae\xc0\x56\xa9\x91\x01\x3c\x82\x1d\x0f\x72\xe7\x76\xec\x29\x49\xd6\x5d\x2d\x83\xe3\xdb\x36\x06\xa9\x3b\x66\x13\x97\x87\x6a\xd5\xb6\x3d\x50\x5e\x52\xb9\x4b\xc7\x73\x57\x78\xc9\xf4\x2e\x59\x07\x95\x93\x6f\xd0\x4b\x17\x57\x19\x3e\x27\x27\xc7\x60\xdb\x3b\xed\x9a\x0e\x53\x44\x16\x3e\x3f\x8d\x92\x6d\x77\xa2\x0a\xeb\x3f\x52\xa8\xc6\x55\x5e\x31\x49\x37\x85\xf4\xc5\x1f\x26\x2d\xa9\x1c\xbf\x8b\x27\x54\xda\xc3\x6a\x20\xe5\x2a\x78\x04\xb0\xd6\x90\x70\x72\xaa\x8b\x68\xbd\x88\xf7\x02\x5f\x48\xb1\x7e\xc0\x58\x4c\x3f\x66\x1a\xf9\x3e\xe1\x65\xc0\x70\xa7\xcf\x38\x69\xaf\xf0\x56\x6c\x64\x49\x9c\x27\xad\x78\x74\x4f\xc2\x87\xde\x56\x39\x00\xda\x77\x0b\xcb\x2d\x1b\x89\xfb\x35\x4f\x02\xf5\x08\x51\x13\x60\xc1\x0a\x5a\x47\x4d\x26\x1c\x33\x30\x78\xda\xc0\x9c\x46\x47\xe2\x5b\x79\x60\x49\x6e\x37\x67\x53\x0a\x3e\xe9\xec\x46\x39\xb2\xf1\x34\x0d\xc6\x84\x53\x75\x6e\xe1\x0c\x59\xd9\x1e\xde\x29\x85\x10\x7b\x49\x49\xa5\x77\x79\xbe\x49\x56\x2e\x36\xe7\x0b\x3a\xbb\x4f\x03\x62\x7b\xd2\x4d\x31\x95\x2f\xbd\x38\x7b\xa8\x4f\x21\xe1\xec\x46\x70\x76\x95\x7d\x29\x22\x78\x88\x0a\x90\xdd\x9d\x5c\xda\xde\x19\x51\xcf\xf0\xfc\x59\x52\x65\x7c\x33\x13\xdf\xf3\x48\xda\xbb\x2a\x75\xdb\x60\xb2\x02\x15\xd4\xfc\x19\xed\x1b\xec\x7f\x35\xa8\xff\x28\x31\x07\x2d\x12\xc8\xdc\x88\x46\x7c\x8a\x5b\x22"; var keyBuffer; @@ -354,50 +367,7 @@ define([ } }; - var qtMagic = 32301; - var compressedMagic = 0x7468dead; - var compressedMagicSwap = 0xadde6874; - /** - * Uncompresses a Google Earth Enterprise packet. - * - * @param {ArrayBuffer} data The data to be uncompressed. - * - * @private - */ - GoogleEarthEnterpriseMetadata.uncompressPacket = function(data) { - // The layout of this decoded data is - // Magic Uint32 - // Size Uint32 - // [GZipped chunk of Size bytes] - - // Pullout magic and verify we have the correct data - var dv = new DataView(data); - var offset = 0; - var magic = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (magic !== compressedMagic && magic !== compressedMagicSwap) { - throw new RuntimeError('Invalid magic'); - } - - // Get the size of the compressed buffer - var size = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (magic === compressedMagicSwap) { - size = ((size >>> 24) & 0x000000ff) | - ((size >>> 8) & 0x0000ff00) | - ((size << 8) & 0x00ff0000) | - ((size << 24) & 0xff000000); - } - - var compressedPacket = new Uint8Array(data, offset); - var uncompressedPacket = pako.inflate(compressedPacket); - - if (uncompressedPacket.length !== size) { - throw new RuntimeError('Size of packet doesn\'t match header'); - } - - return uncompressedPacket; - }; + var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterprisePacket', Number.POSITIVE_INFINITY); /** * Retrieves a Google Earth Enterprise quadtree packet. @@ -420,156 +390,55 @@ define([ url = proxy.getURL(url); } - var promise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(promise)) { - return undefined; + var promise; + if (throttle) { + promise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(promise)) { + return undefined; + } + } else { + promise = loadArrayBuffer(url); } - var that = this; + var tileInfo = this._tileInfo; return promise .then(function(metadata) { - GoogleEarthEnterpriseMetadata.decode(metadata); - - var uncompressedPacket = GoogleEarthEnterpriseMetadata.uncompressPacket(metadata); - var dv = new DataView(uncompressedPacket.buffer); - var offset = 0; - var magic = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (magic !== qtMagic) { - throw new RuntimeError('Invalid magic'); - } - - var dataTypeId = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (dataTypeId !== 1) { - throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); - } - - // Tile format version - var quadVersion = dv.getUint32(offset, true); - offset += sizeOfUint32; - if (quadVersion !== 2) { - throw new RuntimeError('Invalid QuadTreePacket version. Only version 2 is supported.'); - } - - var numInstances = dv.getInt32(offset, true); - offset += sizeOfInt32; - - var dataInstanceSize = dv.getInt32(offset, true); - offset += sizeOfInt32; - if (dataInstanceSize !== 32) { - throw new RuntimeError('Invalid instance size.'); - } - - var dataBufferOffset = dv.getInt32(offset, true); - offset += sizeOfInt32; - - var dataBufferSize = dv.getInt32(offset, true); - offset += sizeOfInt32; - - var metaBufferSize = dv.getInt32(offset, true); - offset += sizeOfInt32; - - // Offset from beginning of packet (instances + current offset) - if (dataBufferOffset !== (numInstances * dataInstanceSize + offset)) { - throw new RuntimeError('Invalid dataBufferOffset'); - } - - // Verify the packets is all there header + instances + dataBuffer + metaBuffer - if (dataBufferOffset + dataBufferSize + metaBufferSize !== uncompressedPacket.length) { - throw new RuntimeError('Invalid packet offsets'); - } - - // Read all the instances - var instances = []; - for (var i = 0; i < numInstances; ++i) { - var bitfield = dv.getUint8(offset); - ++offset; - - ++offset; // 2 byte align - - var cnodeVersion = dv.getUint16(offset, true); - offset += sizeOfUint16; - - var imageVersion = dv.getUint16(offset, true); - offset += sizeOfUint16; - - var terrainVersion = dv.getUint16(offset, true); - offset += sizeOfUint16; - - // Number of channels stored in the dataBuffer - offset += sizeOfUint16; - - offset += sizeOfUint16; // 4 byte align - - // Channel type offset into dataBuffer - offset += sizeOfInt32; - - // Channel version offset into dataBuffer - offset += sizeOfInt32; - - offset += 8; // Ignore image neighbors for now - - // Data providers aren't used - ++offset; // Image provider - ++offset; // Terrain provider - offset += sizeOfUint16; // 4 byte align - - instances.push(new TileInformation(bitfield, cnodeVersion, - imageVersion, terrainVersion)); - } - - var tileInfo = that._tileInfo; - var index = 0; + var decodePromise = taskProcessor.scheduleTask({ + buffer : metadata, + quadKey : quadKey, + type : 'Metadata' + }); - function populateTiles(parentKey, parent, level) { - var isLeaf = false; - if (level === 4) { - if (parent.hasSubtree()) { - return; // We have a subtree, so just return + return decodePromise + .then(function(result) { + var parent; + var topLevelKeyLength = quadKey.length + 1; + if (quadKey !== '') { + // Root tile has no data except children bits, so put them into the tile info + var top = result[quadKey]; + parent = tileInfo[quadKey]; + parent._bits |= top._bits; + + delete result[quadKey]; } - isLeaf = true; // No subtree, so set all children to null - } - for (var i = 0; i < 4; ++i) { - var childKey = parentKey + i.toString(); - if (isLeaf) { - // No subtree so set all children to null - tileInfo[childKey] = null; - } else if (level < 4) { - // We are still in the middle of the subtree, so add child - // only if their bits are set, otherwise set child to null. - if (!parent.hasChild(i)) { - tileInfo[childKey] = null; - } else { - if (index === numInstances) { - console.log('Incorrect number of instances'); - return; + // Copy the resulting objects into tileInfo + for(var key in result) { + if(result.hasOwnProperty(key)) { + var r = result[key]; + if (r !== null) { + var info = TileInformation.fromObject(result[key]); + tileInfo[key] = info; + if (defined(parent) && key.length === topLevelKeyLength) { + info.setParent(parent); + } + } else { + tileInfo[key] = null; } - var instance = instances[index++]; - instance.setParent(parent); - tileInfo[childKey] = instance; - populateTiles(childKey, instance, level + 1); } } - } - } - - var level = 0; - var root; - if (quadKey === '') { - // Root tile has data at its root, all others don't - root = instances[index++]; - ++level; - } else { - // Root tile has no data except children bits, so put them into the tile info - var top = instances[index++]; - root = tileInfo[quadKey]; - root._bits |= top._bits; - } - - populateTiles(quadKey, root, level); + }); }); }; diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index f6e09b2bd06c..ced80f23dd59 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -317,7 +317,7 @@ define([ } }); - var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterpriseTerrainPacket'); + var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterprisePacket', Number.POSITIVE_INFINITY); /** * Requests the geometry for a given tile. This function should not be called before @@ -487,10 +487,6 @@ define([ type : 'Terrain' }); - if (!defined(decodePromise)) { - // Postponed - return undefined; - } return decodePromise .then(function(terrainTiles) { // If we were sent child tiles, store them till they are needed diff --git a/Source/Workers/decodeGoogleEarthEnterprisePacket.js b/Source/Workers/decodeGoogleEarthEnterprisePacket.js new file mode 100644 index 000000000000..aa6b3c885506 --- /dev/null +++ b/Source/Workers/decodeGoogleEarthEnterprisePacket.js @@ -0,0 +1,250 @@ +/*global define*/ +define([ + '../Core/RuntimeError', + '../Core/GoogleEarthEnterpriseMetadata', + './createTaskProcessorWorker', + '../ThirdParty/pako_inflate' +], function( + RuntimeError, + GoogleEarthEnterpriseMetadata, + createTaskProcessorWorker, + pako) { + 'use strict'; + + // Datatype sizes + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + + var Types = { + METADATA : 0, + TERRAIN : 1 + }; + + Types.fromString = function(s) { + if (s === 'Metadata') { + return Types.METADATA; + } else if (s === 'Terrain') { + return Types.TERRAIN; + } + }; + + function decodeGoogleEarthEnterpriseTerrainPacket(parameters, transferableObjects) { + var type = Types.fromString(parameters.type); + var buffer = parameters.buffer; + GoogleEarthEnterpriseMetadata.decode(buffer); + + var uncompressedTerrain = uncompressPacket(buffer); + buffer = uncompressedTerrain.buffer; + var length = uncompressedTerrain.length; + + switch(type) { + case Types.METADATA: + return processMetadata(buffer, length, parameters.quadKey); + case Types.TERRAIN: + return processTerrain(buffer, length, transferableObjects); + } + + } + + var qtMagic = 32301; + function processMetadata(buffer, totalSize, quadKey) { + var dv = new DataView(buffer); + var offset = 0; + var magic = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (magic !== qtMagic) { + throw new RuntimeError('Invalid magic'); + } + + var dataTypeId = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (dataTypeId !== 1) { + throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket'); + } + + // Tile format version + var quadVersion = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (quadVersion !== 2) { + throw new RuntimeError('Invalid QuadTreePacket version. Only version 2 is supported.'); + } + + var numInstances = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var dataInstanceSize = dv.getInt32(offset, true); + offset += sizeOfInt32; + if (dataInstanceSize !== 32) { + throw new RuntimeError('Invalid instance size.'); + } + + var dataBufferOffset = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var dataBufferSize = dv.getInt32(offset, true); + offset += sizeOfInt32; + + var metaBufferSize = dv.getInt32(offset, true); + offset += sizeOfInt32; + + // Offset from beginning of packet (instances + current offset) + if (dataBufferOffset !== (numInstances * dataInstanceSize + offset)) { + throw new RuntimeError('Invalid dataBufferOffset'); + } + + // Verify the packets is all there header + instances + dataBuffer + metaBuffer + if (dataBufferOffset + dataBufferSize + metaBufferSize !== totalSize) { + throw new RuntimeError('Invalid packet offsets'); + } + + // Read all the instances + var instances = []; + for (var i = 0; i < numInstances; ++i) { + var bitfield = dv.getUint8(offset); + ++offset; + + ++offset; // 2 byte align + + var cnodeVersion = dv.getUint16(offset, true); + offset += sizeOfUint16; + + var imageVersion = dv.getUint16(offset, true); + offset += sizeOfUint16; + + var terrainVersion = dv.getUint16(offset, true); + offset += sizeOfUint16; + + // Number of channels stored in the dataBuffer + offset += sizeOfUint16; + + offset += sizeOfUint16; // 4 byte align + + // Channel type offset into dataBuffer + offset += sizeOfInt32; + + // Channel version offset into dataBuffer + offset += sizeOfInt32; + + offset += 8; // Ignore image neighbors for now + + // Data providers aren't used + ++offset; // Image provider + ++offset; // Terrain provider + offset += sizeOfUint16; // 4 byte align + + instances.push(new GoogleEarthEnterpriseMetadata.TileInformation(bitfield, cnodeVersion, + imageVersion, terrainVersion)); + } + + var tileInfo = []; + var index = 0; + + function populateTiles(parentKey, parent, level) { + var isLeaf = false; + if (level === 4) { + if (parent.hasSubtree()) { + return; // We have a subtree, so just return + } + + isLeaf = true; // No subtree, so set all children to null + } + for (var i = 0; i < 4; ++i) { + var childKey = parentKey + i.toString(); + if (isLeaf) { + // No subtree so set all children to null + tileInfo[childKey] = null; + } else if (level < 4) { + // We are still in the middle of the subtree, so add child + // only if their bits are set, otherwise set child to null. + if (!parent.hasChild(i)) { + tileInfo[childKey] = null; + } else { + if (index === numInstances) { + console.log('Incorrect number of instances'); + return; + } + + var instance = instances[index++]; + instance.setParent(parent); + tileInfo[childKey] = instance; + populateTiles(childKey, instance, level + 1); + } + } + } + } + + var level = 0; + var root = instances[index++]; + if (quadKey === '') { + // Root tile has data at its root and one less level + ++level; + } else { + tileInfo[quadKey] = root; // This will only contain the child bitmask + } + + populateTiles(quadKey, root, level); + + return tileInfo; + } + + function processTerrain(buffer, totalSize, transferableObjects) { + var dv = new DataView(buffer); + + var offset = 0; + var terrainTiles = []; + while (offset < totalSize) { + // Each tile is split into 4 parts + var tileStart = offset; + for (var quad = 0; quad < 4; ++quad) { + var size = dv.getUint32(offset, true); + offset += sizeOfUint32; + offset += size; + } + var tile = buffer.slice(tileStart, offset); + transferableObjects.push(tile); + terrainTiles.push(tile); + } + + return terrainTiles; + } + + var compressedMagic = 0x7468dead; + var compressedMagicSwap = 0xadde6874; + function uncompressPacket(data) { + // The layout of this decoded data is + // Magic Uint32 + // Size Uint32 + // [GZipped chunk of Size bytes] + + // Pullout magic and verify we have the correct data + var dv = new DataView(data); + var offset = 0; + var magic = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (magic !== compressedMagic && magic !== compressedMagicSwap) { + throw new RuntimeError('Invalid magic'); + } + + // Get the size of the compressed buffer + var size = dv.getUint32(offset, true); + offset += sizeOfUint32; + if (magic === compressedMagicSwap) { + size = ((size >>> 24) & 0x000000ff) | + ((size >>> 8) & 0x0000ff00) | + ((size << 8) & 0x00ff0000) | + ((size << 24) & 0xff000000); + } + + var compressedPacket = new Uint8Array(data, offset); + var uncompressedPacket = pako.inflate(compressedPacket); + + if (uncompressedPacket.length !== size) { + throw new RuntimeError('Size of packet doesn\'t match header'); + } + + return uncompressedPacket; + } + + return createTaskProcessorWorker(decodeGoogleEarthEnterpriseTerrainPacket); +}); diff --git a/Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js b/Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js deleted file mode 100644 index 43ed8828d06f..000000000000 --- a/Source/Workers/decodeGoogleEarthEnterpriseTerrainPacket.js +++ /dev/null @@ -1,73 +0,0 @@ -/*global define*/ -define([ - '../Core/GoogleEarthEnterpriseMetadata', - './createTaskProcessorWorker' -], function( - GoogleEarthEnterpriseMetadata, - createTaskProcessorWorker) { - 'use strict'; - - var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - - var Types = { - METADATA : 0, - IMAGERY : 1, - TERRAIN : 2 - }; - - Types.fromString = function(s) { - if (s === 'Metadata') { - return Types.METADATA; - } else if (s === 'Imagery') { - return Types.IMAGERY; - } else if (s === 'Terrain') { - return Types.TERRAIN; - } - }; - - function decodeGoogleEarthEnterpriseTerrainPacket(parameters, transferableObjects) { - var type = Types.fromString(parameters.type); - var buffer = parameters.buffer; - GoogleEarthEnterpriseMetadata.decode(buffer); - - var length = buffer.byteLength; - if (type !== Types.IMAGERY) { - var uncompressedTerrain = GoogleEarthEnterpriseMetadata.uncompressPacket(buffer); - buffer = uncompressedTerrain.buffer; - length = uncompressedTerrain.length; - } - - switch(type) { - case Types.METADATA: - break; - case Types.IMAGERY: - break; - case Types.TERRAIN: - return processTerrain(buffer, length, transferableObjects); - } - - } - - function processTerrain(buffer, totalSize, transferableObjects) { - var dv = new DataView(buffer); - - var offset = 0; - var terrainTiles = []; - while (offset < totalSize) { - // Each tile is split into 4 parts - var tileStart = offset; - for (var quad = 0; quad < 4; ++quad) { - var size = dv.getUint32(offset, true); - offset += sizeOfUint32; - offset += size; - } - var tile = buffer.slice(tileStart, offset); - transferableObjects.push(tile); - terrainTiles.push(tile); - } - - return terrainTiles; - } - - return createTaskProcessorWorker(decodeGoogleEarthEnterpriseTerrainPacket); -}); diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index 1ad3005ff753..33271152bfda 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -96,103 +96,13 @@ defineSuite([ }); }); - var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; - var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; - var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - - function createFakeMetadataResponse() { - var numInstances = 2; - var buffer = new ArrayBuffer(32 + numInstances * 32); - var dv = new DataView(buffer); - - var offset = 0; - - dv.setUint32(offset, 32301, true); - offset += sizeOfUint32; - - dv.setUint32(offset, 1, true); - offset += sizeOfUint32; - - dv.setUint32(offset, 2, true); - offset += sizeOfUint32; - - dv.setInt32(offset, numInstances, true); - offset += sizeOfInt32; - - dv.setInt32(offset, 32, true); - offset += sizeOfInt32; - - dv.setInt32(offset, 32 + 32 * numInstances, true); - offset += sizeOfInt32; - - dv.setInt32(offset, 0, true); - offset += sizeOfInt32; - - dv.setInt32(offset, 0, true); - offset += sizeOfInt32; - - for (var i = 0; i < numInstances; ++i) { - if (i === (numInstances - 1)) { - dv.setUint8(offset, 0x40); - } else { - dv.setUint8(offset, 0x41); - } - ++offset; - - ++offset; // 2 byte align - - dv.setUint16(offset, 2, true); - offset += sizeOfUint16; - - dv.setUint16(offset, 1, true); - offset += sizeOfUint16; - - dv.setUint16(offset, 1, true); - offset += sizeOfUint16; - - // Number of channels stored in the dataBuffer - //var numChannels = dv.getUint16(offset, true); - offset += sizeOfUint16; - - offset += sizeOfUint16; // 4 byte align - - // Channel type offset into dataBuffer - //var typeOffset = dv.getInt32(offset, true); - offset += sizeOfInt32; - - // Channel version offset into dataBuffer - //var versionOffset = dv.getInt32(offset, true); - offset += sizeOfInt32; - - offset += 8; // Ignore image neighbors for now - - // Data providers aren't used - ++offset; // Image provider - ++offset; // Terrain provider - offset += sizeOfUint16; // 4 byte align - } - - return buffer; - } - it('resolves readyPromise', function() { var baseurl = 'http://fake.fake.invalid/'; - var response = createFakeMetadataResponse(); spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { expect(url).toEqual(baseurl + 'flatfile?q2-0-q.1'); expect(responseType).toEqual('arraybuffer'); - deferred.resolve(response); - }); - - spyOn(GoogleEarthEnterpriseMetadata, 'decode').and.callFake(function(data) { - expect(data).toEqual(response); - return data; - }); - - spyOn(GoogleEarthEnterpriseMetadata, 'uncompressPacket').and.callFake(function(data) { - expect(data).toEqual(response); - return new Uint8Array(data); + loadWithXhr.defaultLoad('Data/GoogleEarthEnterprise/gee.metadata', responseType, method, data, headers, deferred); }); var provider = new GoogleEarthEnterpriseMetadata({ @@ -230,21 +140,10 @@ defineSuite([ var proxy = new DefaultProxy('/proxy/'); var baseurl = 'http://fake.fake.invalid/'; - var response = createFakeMetadataResponse(); spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { expect(url).toEqual(proxy.getURL(baseurl + 'flatfile?q2-0-q.1')); expect(responseType).toEqual('arraybuffer'); - deferred.resolve(response); - }); - - spyOn(GoogleEarthEnterpriseMetadata, 'decode').and.callFake(function(data) { - expect(data).toEqual(response); - return data; - }); - - spyOn(GoogleEarthEnterpriseMetadata, 'uncompressPacket').and.callFake(function(data) { - expect(data).toEqual(response); - return new Uint8Array(data); + loadWithXhr.defaultLoad('Data/GoogleEarthEnterprise/gee.metadata', responseType, method, data, headers, deferred); }); var provider = new GoogleEarthEnterpriseMetadata({ diff --git a/Specs/Data/GoogleEarthEnterprise/gee.metadata b/Specs/Data/GoogleEarthEnterprise/gee.metadata new file mode 100644 index 0000000000000000000000000000000000000000..c224cecaa7b0128bbd2ed8120607918429fd2b3c GIT binary patch literal 43 zcmV+`0M!3gmv}Mzl0^06*n^ho B6fgh) literal 0 HcmV?d00001 From ba528679d04fee4f0f9d4e118df5dca10ff73b12 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 21:51:09 -0400 Subject: [PATCH 44/60] Rearchitected terrain to be a little cleaner. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 33 +- .../GoogleEarthEnterpriseTerrainProvider.js | 318 +++++++++--------- Source/Scene/TileTerrain.js | 6 - 3 files changed, 184 insertions(+), 173 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 67c335a952e8..164d813d4ee3 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -130,7 +130,7 @@ define([ * @returns {Number} Children bitmask */ TileInformation.prototype.getChildBitmask = function() { - return this._bits && anyChildBitmask; + return this._bits & anyChildBitmask; }; GoogleEarthEnterpriseMetadata.TileInformation = TileInformation; @@ -287,6 +287,37 @@ define([ }; }; + GoogleEarthEnterpriseMetadata.prototype.isValid = function(quadKey) { + var info = this.getTileInformationFromQuadKey(quadKey); + if (defined(info)) { + return info !== null; + } + + var valid = true; + var q = quadKey; + var last; + while (q.length > 1) { + last = q.substring(q.length - 1); + q = q.substring(0, q.length - 1); + info = this.getTileInformationFromQuadKey(q); + if (defined(info)) { + if (!info.hasSubtree() && + !info.hasChild(parseInt(last))) { + // We have no subtree or child available at some point in this node's ancestry + valid = false; + } + + break; + } else if (info === null) { + // Some node in the ancestry was loaded and said there wasn't a subtree + valid = false; + break; + } + } + + return valid; + }; + var compressedMagic = 0x7468dead; var compressedMagicSwap = 0xadde6874; diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index ced80f23dd59..fb39458657b9 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -344,198 +344,155 @@ define([ } //>>includeEnd('debug'); - var hasTerrain = true; var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); var terrainCache = this._terrainCache; var metadata = this._metadata; var info = metadata.getTileInformationFromQuadKey(quadKey); - if (!defined(terrainCache.get(quadKey))) { // If its in the cache we know we have it so just skip all the checks - terrainCache.tidy(); // Cleanup the cache since we know we don't have it - if (defined(info)) { - if (info.terrainState === TerrainState.NONE) { - // Already have info and there isn't any terrain here - hasTerrain = false; - } - } else { - if (info === null) { - // Parent was retrieved and said child doesn't exist - hasTerrain = false; - } + // Check if this tile is even possibly available + if (!defined(info)) { + return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); + } - var q = quadKey; - var last; - while (q.length > 1) { - last = q.substring(q.length - 1); + // If its in the cache, return it + var buffer = terrainCache.get(quadKey); + if (defined(buffer)) { + if (info.terrainState === TerrainState.UNKNOWN) { + // If its already in the cache then a parent request must've loaded it + info.terrainState = TerrainState.PARENT; + } + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : info.getChildBitmask() + }); + } + + // Clean up the cache + terrainCache.tidy(); + + // We have a tile, check to see if no ancestors have terrain or that we know for sure it doesn't + if (!info.ancestorHasTerrain) { + // We haven't reached a level with terrain, so return the ellipsoid + return new HeightmapTerrainData({ + buffer : new Uint8Array(16 * 16), + width : 16, + height : 16 + }); + } else if (info.terrainState === TerrainState.NONE) { + // Already have info and there isn't any terrain here + return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); + } + + // Figure out where we are getting the terrain and what version + var parentInfo; + var q = quadKey; + var terrainVersion = -1; + var terrainState = info.terrainState; + if (terrainState !== TerrainState.NONE) { + switch (terrainState) { + case TerrainState.SELF: // We have terrain and have retrieved it before + terrainVersion = info.terrainVersion; + break; + case TerrainState.PARENT: // We have terrain in our parent q = q.substring(0, q.length - 1); - info = metadata.getTileInformationFromQuadKey(q); - if (defined(info)) { - if (!info.hasSubtree() && - !info.hasChild(parseInt(last))) { - // We have no subtree or child available at some point in this node's ancestry - hasTerrain = false; + parentInfo = metadata.getTileInformationFromQuadKey(q); + terrainVersion = parentInfo.terrainVersion; + break; + case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet + if (info.hasTerrain()) { + terrainVersion = info.terrainVersion; // We should have terrain + } else { + q = q.substring(0, q.length - 1); + parentInfo = metadata.getTileInformationFromQuadKey(q); + if (defined(parentInfo) && parentInfo.hasTerrain()) { + terrainVersion = parentInfo.terrainVersion; // Try checking in the parent } - - break; - } else if (info === null) { - // Some node in the ancestry was loaded and said there wasn't a subtree - hasTerrain = false; - break; } - } + break; } } - if (!hasTerrain) { - if (defined(info) && !info.ancestorHasTerrain) { - // We haven't reached a level with terrain, so return the ellipsoid - return new HeightmapTerrainData({ - buffer : new Uint8Array(16 * 16), - width : 16, - height : 16 - }); - } - + // We can't figure out where to get the terrain + if (terrainVersion < 0) { return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } - var that = this; + // Load that terrain var terrainPromises = this._terrainPromises; - var metadataPromise = metadata.populateSubtree(x, y, level, throttleRequests); - if (!defined(metadataPromise)) { - return undefined; // Metadata request throttled - } - - return metadataPromise - .then(function(info) { - if (!defined(info)) { - return undefined; //Metadata request throttled + var url = buildTerrainUrl(this, q, terrainVersion); + var promise; + if (defined(terrainPromises[q])) { // Already being loaded possibly from another child, so return existing promise + promise = terrainPromises[q]; + } else { // Create new request for terrain + var requestPromise; + throttleRequests = defaultValue(throttleRequests, true); + if (throttleRequests) { + requestPromise = throttleRequestByServer(url, loadArrayBuffer); + if (!defined(requestPromise)) { + return undefined; // Throttled } + } else { + requestPromise = loadArrayBuffer(url); + } - var buffer = terrainCache.get(quadKey); - if (defined(buffer)) { - if (info.terrainState === TerrainState.UNKNOWN) { - // If its already in the cache then a parent request must've loaded it - info.terrainState = TerrainState.PARENT; - } + terrainPromises[q] = requestPromise; - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : info.getChildBitmask() + promise = requestPromise + .always(function(terrain) { + delete terrainPromises[q]; + return terrain; + }); + } + + return promise + .then(function(terrain) { + if (defined(terrain)) { + var decodePromise = taskProcessor.scheduleTask({ + buffer : terrain, + type : 'Terrain' }); - } - var parentInfo; - var q = quadKey; - var terrainVersion = -1; - var terrainState = info.terrainState; - if (terrainState !== TerrainState.NONE) { - switch (terrainState) { - case TerrainState.SELF: // We have terrain and have retrieved it before - terrainVersion = info.terrainVersion; - break; - case TerrainState.PARENT: // We have terrain in our parent - q = q.substring(0, q.length - 1); - parentInfo = metadata.getTileInformationFromQuadKey(q); - terrainVersion = parentInfo.terrainVersion; - break; - case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet - if (info.hasTerrain()) { - terrainVersion = info.terrainVersion; // We should have terrain + return decodePromise + .then(function(terrainTiles) { + // If we were sent child tiles, store them till they are needed + var buffer; + if (q !== quadKey) { + terrainCache.add(q, terrainTiles[0]); + var parentInfo = metadata.getTileInformationFromQuadKey(q); + parentInfo.terrainState = TerrainState.SELF; } else { - q = q.substring(0, q.length - 1); - parentInfo = metadata.getTileInformationFromQuadKey(q); - if (defined(parentInfo) && parentInfo.hasTerrain()) { - terrainVersion = parentInfo.terrainVersion; // Try checking in the parent - } + buffer = terrainTiles[0]; + info.terrainState = TerrainState.SELF; } - break; - } - } - - if (terrainVersion > 0) { - var url = buildTerrainUrl(that, q, terrainVersion); - var promise; - if (defined(terrainPromises[q])) { - promise = terrainPromises[q]; - } else { - var requestPromise; - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - requestPromise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(requestPromise)) { - return undefined; + var count = terrainTiles.length - 1; + for (var j = 0; j < count; ++j) { + var childKey = q + j.toString(); + if (childKey === quadKey) { + buffer = terrainTiles[j + 1]; + // If we didn't request this tile directly then it came from a parent + info.terrainState = TerrainState.PARENT; + } else if (info.hasChild(j)) { + terrainCache.add(childKey, terrainTiles[j + 1]); + } } - } else { - requestPromise = loadArrayBuffer(url); - } - - terrainPromises[q] = requestPromise; - promise = requestPromise - .always(function(terrain) { - delete terrainPromises[q]; - return terrain; - }); - } - - return promise - .then(function(terrain) { - if (defined(terrain)) { - var decodePromise = taskProcessor.scheduleTask({ - buffer : terrain, - type : 'Terrain' + if (defined(buffer)) { + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : info.getChildBitmask() }); - - return decodePromise - .then(function(terrainTiles) { - // If we were sent child tiles, store them till they are needed - var buffer; - if (q !== quadKey) { - terrainCache.add(q, terrainTiles[0]); - var parentInfo = metadata.getTileInformationFromQuadKey(q); - parentInfo.terrainState = TerrainState.SELF; - } else { - buffer = terrainTiles[0]; - info.terrainState = TerrainState.SELF; - } - var count = terrainTiles.length - 1; - for (var j = 0; j < count; ++j) { - var childKey = q + j.toString(); - if (childKey === quadKey) { - buffer = terrainTiles[j + 1]; - // If we didn't request this tile directly then it came from a parent - info.terrainState = TerrainState.PARENT; - } else if (info.hasChild(j)) { - terrainCache.add(childKey, terrainTiles[j + 1]); - } - } - - if (defined(buffer)) { - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : info.getChildBitmask() - }); - } else { - info.terrainState = TerrainState.NONE; - } - }); + } else { + info.terrainState = TerrainState.NONE; + return when.reject(new RuntimeError('Failed to load terrain.')); } - - return when.reject(new RuntimeError('Failed to load terrain.')); - }) - .otherwise(function(error) { - info.terrainState = TerrainState.NONE; - return when.reject(error); }); - } else if (!info.ancestorHasTerrain) { - // We haven't reached a level with terrain, so return the ellipsoid - return new HeightmapTerrainData({ - buffer : new Uint8Array(16 * 16), - width : 16, - height : 16 - }); } + + return when.reject(new RuntimeError('Failed to load terrain.')); + }) + .otherwise(function(error) { + info.terrainState = TerrainState.NONE; + return when.reject(error); }); }; @@ -559,12 +516,41 @@ define([ */ GoogleEarthEnterpriseTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { var metadata = this._metadata; + var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + var info = metadata.getTileInformation(x, y, level); + if (info === null) { + return false; + } + if (defined(info)) { - // We may have terrain or no ancestors have had it so we'll return the ellipsoid - return info.terrainState !== TerrainState.NONE || !info.ancestorHasTerrain; + if (!info.ancestorHasTerrain) { + return true; // We'll just return the ellipsoid + } + + var terrainState = info.terrainState; + if (terrainState === TerrainState.NONE) { + return false; // Terrain is not available + } + + if (terrainState === TerrainState.UNKNOWN) { + if (!info.hasTerrain()) { + quadKey = quadKey.substring(0, quadKey.length - 1); + var parentInfo = metadata.getTileInformationFromQuadKey(quadKey); + if (!defined(parentInfo) || !parentInfo.hasTerrain()) { + return false; + } + } + } + + return true; + } + + if (metadata.isValid(quadKey)) { + // We will need this tile, so request metadata and return false for now + metadata.populateSubtree(x, y, level); } - return undefined; + return false; }; // diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index c999cd952fc6..f152e32cdbb7 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -109,12 +109,6 @@ define([ function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) { function success(terrainData) { - if (!defined(terrainData)) { - // Deferred - try again later. - tileTerrain.state = TerrainState.UNLOADED; - return; - } - tileTerrain.data = terrainData; tileTerrain.state = TerrainState.RECEIVED; } From 698964e4c6a1c1cbda6b64e5106e06c5bb51fc91 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 22:03:29 -0400 Subject: [PATCH 45/60] Imagery cleanup. --- .../GoogleEarthEnterpriseImageryProvider.js | 81 ++++--------------- Source/Scene/ImageryLayer.js | 4 +- 2 files changed, 17 insertions(+), 68 deletions(-) diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index b7010a0bbc28..3abcba7ac671 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -426,79 +426,30 @@ define([ var invalidImage = this._tileDiscardPolicy._image; // Empty image or undefined depending on discard policy var metadata = this._metadata; + var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y , level); var info = metadata.getTileInformation(x, y, level); - var promise; - if (defined(info)) { - if (!info.hasImagery()) { - // Already have info and there isn't any imagery here - return invalidImage; - } - var url = buildImageUrl(this, info, x, y, level); - promise = throttleRequestByServer(url, loadArrayBuffer); - } else { - if (info === null) { - // Parent was retrieved and said child doesn't exist - return invalidImage; - } - - var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - var last; - while (q.length > 1) { - last = q.substring(q.length - 1); - q = q.substring(0, q.length - 1); - info = metadata.getTileInformationFromQuadKey(q); - if (defined(info)) { - if (!info.hasSubtree() && - !info.hasChild(parseInt(last))) { - // We have no subtree or child available at some point in this node's ancestry - return invalidImage; - } - - break; - } else if (info === null) { - // Some node in the ancestry was loaded and said there wasn't a subtree - return invalidImage; - } - } - - // There is nothing in the heirarchy that leads us to think this tile isn't available - // so populate the tree so the info is available for this tile. - var metadataPromise = metadata.populateSubtree(x, y, level, true); - - if (defined(metadataPromise)) { - promise = metadataPromise - .then(function(info) { - if (!defined(info)) { - return undefined; //Metadata throttled - } - - if (info.hasImagery()) { - var url = buildImageUrl(that, info, x, y, level); - return throttleRequestByServer(url, loadArrayBuffer); - } - - return when(invalidImage); - }) - .otherwise(function() { - // Metadata couldn't be loaded, so imagery isn't available - return invalidImage; - }); + if (!defined(info)) { + if (metadata.isValid(quadKey)) { + metadata.populateSubtree(x, y, level); + return undefined; // No metadata so return undefined so we can be loaded later + } else { + return invalidImage; // Image doesn't exist } } + if (!info.hasImagery()) { + // Already have info and there isn't any imagery here + return invalidImage; + } + // Load the + var url = buildImageUrl(this, info, x, y, level); + var promise = throttleRequestByServer(url, loadArrayBuffer); if (!defined(promise)) { - return undefined; // Metadata or Image request throttled + return undefined; //Throttled } return promise .then(function(image) { - if (!defined(image)) { - return; // Throttled - } - if (image === invalidImage) { - return invalidImage; - } - GoogleEarthEnterpriseMetadata.decode(image); var a = new Uint8Array(image); var type = getImageType(a); @@ -509,7 +460,7 @@ define([ } if (!defined(type) || !defined(a)) { - throw new RuntimeError('Invalid image'); + return invalidImage; } return loadImageFromTypedArray(a, type); diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 82c6cebaf9aa..9fef8eeac772 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -664,9 +664,7 @@ define([ function success(image) { if (!defined(image)) { - // Too many parallel requests, so postpone loading tile. - imagery.state = ImageryState.UNLOADED; - return; + return failure(); } imagery.image = image; From c274947386cbbb8b9ca667bef0532754562b939d Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 22:15:00 -0400 Subject: [PATCH 46/60] Fixed tests. --- ...GoogleEarthEnterpriseTerrainProviderSpec.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index 47e52128fe32..c754012a8912 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -49,6 +49,11 @@ defineSuite([ terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ url : 'made/up/url' }); + // Fake tile info + var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + var t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 0, 1, 1); + t.ancestorHasTerrain = true; + terrainProvider._metadata._tileInfo[q] = t; return pollToPromise(function() { return terrainProvider.ready; @@ -256,6 +261,12 @@ defineSuite([ return terrainProvider.ready; }) .then(function() { + // Fake tile info + var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(1, 2, 3); + var t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 0, 1, 1); + t.ancestorHasTerrain = true; + terrainProvider._metadata._tileInfo[q] = t; + var promise = terrainProvider.requestTileGeometry(1, 2, 3); expect(promise).toBeDefined(); return promise; @@ -263,6 +274,11 @@ defineSuite([ .then(function(terrainData) { expect(terrainData).toBeDefined(); for (var i = 0; i < 10; ++i) { + // Fake tile info + var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(i, i, i); + var t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 0, 1, 1); + t.ancestorHasTerrain = true; + terrainProvider._metadata._tileInfo[q] = t; promises.push(terrainProvider.requestTileGeometry(i, i, i)); } @@ -311,7 +327,7 @@ defineSuite([ expect(terrainProvider.getTileDataAvailable(0, 1, 0)).toBe(false); expect(terrainProvider.getTileDataAvailable(1, 0, 0)).toBe(true); expect(terrainProvider.getTileDataAvailable(1, 1, 0)).toBe(true); - expect(terrainProvider.getTileDataAvailable(0, 0, 2)).toBeUndefined(); + expect(terrainProvider.getTileDataAvailable(0, 0, 2)).toBe(false); }); }); }); From 057dff56acc5851175ff5344fe518ee1c343d0c3 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 22:29:23 -0400 Subject: [PATCH 47/60] Fixed more tests. --- ...oogleEarthEnterpriseTerrainProviderSpec.js | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index c754012a8912..8f1e9dbfdaba 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -34,10 +34,21 @@ defineSuite([ function installMockGetQuadTreePacket() { spyOn(GoogleEarthEnterpriseMetadata.prototype, 'getQuadTreePacket').and.callFake(function(quadKey, version) { quadKey = defaultValue(quadKey, ''); - this._tileInfo[quadKey + '0'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); - this._tileInfo[quadKey + '1'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); - this._tileInfo[quadKey + '2'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); - this._tileInfo[quadKey + '3'] = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + var t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + t.ancestorHasTerrain = true; + this._tileInfo[quadKey + '0'] = t; + + t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + t.ancestorHasTerrain = true; + this._tileInfo[quadKey + '1'] = t; + + t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + t.ancestorHasTerrain = true; + this._tileInfo[quadKey + '2'] = t; + + t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 1, 1, 1); + t.ancestorHasTerrain = true; + this._tileInfo[quadKey + '3'] = t; return when(); }); @@ -49,14 +60,9 @@ defineSuite([ terrainProvider = new GoogleEarthEnterpriseTerrainProvider({ url : 'made/up/url' }); - // Fake tile info - var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - var t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 0, 1, 1); - t.ancestorHasTerrain = true; - terrainProvider._metadata._tileInfo[q] = t; return pollToPromise(function() { - return terrainProvider.ready; + return terrainProvider.ready && terrainProvider.getTileDataAvailable(x, y, level); }).then(function() { var promise = terrainProvider.requestTileGeometry(level, x, y); @@ -260,13 +266,16 @@ defineSuite([ return pollToPromise(function() { return terrainProvider.ready; }) + .then(function(){ + return pollToPromise(function() { + var b = true; + for (var i = 0; i < 10; ++i) { + b = b && terrainProvider.getTileDataAvailable(i, i, i); + } + return b && terrainProvider.getTileDataAvailable(1, 2, 3); + }) + }) .then(function() { - // Fake tile info - var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(1, 2, 3); - var t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 0, 1, 1); - t.ancestorHasTerrain = true; - terrainProvider._metadata._tileInfo[q] = t; - var promise = terrainProvider.requestTileGeometry(1, 2, 3); expect(promise).toBeDefined(); return promise; @@ -274,11 +283,6 @@ defineSuite([ .then(function(terrainData) { expect(terrainData).toBeDefined(); for (var i = 0; i < 10; ++i) { - // Fake tile info - var q = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(i, i, i); - var t = new GoogleEarthEnterpriseMetadata.TileInformation(0xFF, 0, 1, 1); - t.ancestorHasTerrain = true; - terrainProvider._metadata._tileInfo[q] = t; promises.push(terrainProvider.requestTileGeometry(i, i, i)); } From 14ba59d5380cdf6f75ef81c28b91bb10b4755d4e Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 22:30:54 -0400 Subject: [PATCH 48/60] Tweak. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 164d813d4ee3..cf35a1d51c53 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -459,10 +459,10 @@ define([ var r = result[key]; if (r !== null) { var info = TileInformation.fromObject(result[key]); - tileInfo[key] = info; if (defined(parent) && key.length === topLevelKeyLength) { info.setParent(parent); } + tileInfo[key] = info; } else { tileInfo[key] = null; } From dc737af16b0bd2c825eba1d3cda35962d9beb6df Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 11 Apr 2017 22:42:49 -0400 Subject: [PATCH 49/60] Auto format and jshint fix. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 50 +++---- .../Core/GoogleEarthEnterpriseTerrainData.js | 82 ++++++------ .../GoogleEarthEnterpriseTerrainProvider.js | 102 +++++++------- .../GoogleEarthEnterpriseImageryProvider.js | 73 +++++----- ...VerticesFromGoogleEarthEnterpriseBuffer.js | 125 +++++++++--------- .../decodeGoogleEarthEnterprisePacket.js | 4 +- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 26 ++-- .../GoogleEarthEnterpriseTerrainDataSpec.js | 54 ++++---- ...oogleEarthEnterpriseTerrainProviderSpec.js | 62 ++++----- ...oogleEarthEnterpriseImageryProviderSpec.js | 66 ++++----- 10 files changed, 323 insertions(+), 321 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index cf35a1d51c53..ca153fa56a87 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -1,28 +1,28 @@ /*global define*/ define([ - '../ThirdParty/pako_inflate', - '../ThirdParty/when', - './appendForwardSlash', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './loadArrayBuffer', - './RuntimeError', - './TaskProcessor', - './throttleRequestByServer' - ], function( - pako, - when, - appendForwardSlash, - defaultValue, - defined, - defineProperties, - DeveloperError, - loadArrayBuffer, - RuntimeError, - TaskProcessor, - throttleRequestByServer) { + '../ThirdParty/pako_inflate', + '../ThirdParty/when', + './appendForwardSlash', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './loadArrayBuffer', + './RuntimeError', + './TaskProcessor', + './throttleRequestByServer' +], function( + pako, + when, + appendForwardSlash, + defaultValue, + defined, + defineProperties, + DeveloperError, + loadArrayBuffer, + RuntimeError, + TaskProcessor, + throttleRequestByServer) { 'use strict'; // Bitmask for checking tile properties @@ -454,8 +454,8 @@ define([ } // Copy the resulting objects into tileInfo - for(var key in result) { - if(result.hasOwnProperty(key)) { + for (var key in result) { + if (result.hasOwnProperty(key)) { var r = result[key]; if (r !== null) { var info = TileInformation.fromObject(result[key]); diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index a865e8e7eff8..6944ac46ba40 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -1,46 +1,46 @@ /*global define*/ define([ - '../ThirdParty/when', - './BoundingSphere', - './Cartesian2', - './Cartesian3', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './GeographicTilingScheme', - './HeightmapTessellator', - './IndexDatatype', - './Intersections2D', - './Math', - './OrientedBoundingBox', - './QuantizedMeshTerrainData', - './Rectangle', - './TaskProcessor', - './TerrainEncoding', - './TerrainMesh', - './TerrainProvider' - ], function( - when, - BoundingSphere, - Cartesian2, - Cartesian3, - defaultValue, - defined, - defineProperties, - DeveloperError, - GeographicTilingScheme, - HeightmapTessellator, - IndexDatatype, - Intersections2D, - CesiumMath, - OrientedBoundingBox, - QuantizedMeshTerrainData, - Rectangle, - TaskProcessor, - TerrainEncoding, - TerrainMesh, - TerrainProvider) { + '../ThirdParty/when', + './BoundingSphere', + './Cartesian2', + './Cartesian3', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './GeographicTilingScheme', + './HeightmapTessellator', + './IndexDatatype', + './Intersections2D', + './Math', + './OrientedBoundingBox', + './QuantizedMeshTerrainData', + './Rectangle', + './TaskProcessor', + './TerrainEncoding', + './TerrainMesh', + './TerrainProvider' +], function( + when, + BoundingSphere, + Cartesian2, + Cartesian3, + defaultValue, + defined, + defineProperties, + DeveloperError, + GeographicTilingScheme, + HeightmapTessellator, + IndexDatatype, + Intersections2D, + CesiumMath, + OrientedBoundingBox, + QuantizedMeshTerrainData, + Rectangle, + TaskProcessor, + TerrainEncoding, + TerrainMesh, + TerrainProvider) { 'use strict'; /** diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index fb39458657b9..198a8cb6033c 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -1,56 +1,56 @@ /*global define*/ define([ - '../ThirdParty/when', - './Cartesian2', - './Cartesian3', - './Cartographic', - './Credit', - './defaultValue', - './defined', - './defineProperties', - './destroyObject', - './DeveloperError', - './Ellipsoid', - './Event', - './GeographicTilingScheme', - './GoogleEarthEnterpriseMetadata', - './GoogleEarthEnterpriseTerrainData', - './HeightmapTerrainData', - './JulianDate', - './loadArrayBuffer', - './Math', - './Rectangle', - './RuntimeError', - './TaskProcessor', - './TerrainProvider', - './throttleRequestByServer', - './TileProviderError' - ], function( - when, - Cartesian2, - Cartesian3, - Cartographic, - Credit, - defaultValue, - defined, - defineProperties, - destroyObject, - DeveloperError, - Ellipsoid, - Event, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - GoogleEarthEnterpriseTerrainData, - HeightmapTerrainData, - JulianDate, - loadArrayBuffer, - CesiumMath, - Rectangle, - RuntimeError, - TaskProcessor, - TerrainProvider, - throttleRequestByServer, - TileProviderError) { + '../ThirdParty/when', + './Cartesian2', + './Cartesian3', + './Cartographic', + './Credit', + './defaultValue', + './defined', + './defineProperties', + './destroyObject', + './DeveloperError', + './Ellipsoid', + './Event', + './GeographicTilingScheme', + './GoogleEarthEnterpriseMetadata', + './GoogleEarthEnterpriseTerrainData', + './HeightmapTerrainData', + './JulianDate', + './loadArrayBuffer', + './Math', + './Rectangle', + './RuntimeError', + './TaskProcessor', + './TerrainProvider', + './throttleRequestByServer', + './TileProviderError' +], function( + when, + Cartesian2, + Cartesian3, + Cartographic, + Credit, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + Ellipsoid, + Event, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + GoogleEarthEnterpriseTerrainData, + HeightmapTerrainData, + JulianDate, + loadArrayBuffer, + CesiumMath, + Rectangle, + RuntimeError, + TaskProcessor, + TerrainProvider, + throttleRequestByServer, + TileProviderError) { 'use strict'; var TerrainState = { diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index 3abcba7ac671..a747840ef403 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -1,40 +1,40 @@ /*global define*/ define([ - '../Core/Credit', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/GeographicTilingScheme', - '../Core/GoogleEarthEnterpriseMetadata', - '../Core/loadArrayBuffer', - '../Core/loadImageFromTypedArray', - '../Core/Math', - '../Core/Rectangle', - '../Core/RuntimeError', - '../Core/throttleRequestByServer', - '../Core/TileProviderError', - '../ThirdParty/protobuf-minimal', - '../ThirdParty/when' - ], function( - Credit, - defaultValue, - defined, - defineProperties, - DeveloperError, - Event, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - loadArrayBuffer, - loadImageFromTypedArray, - CesiumMath, - Rectangle, - RuntimeError, - throttleRequestByServer, - TileProviderError, - protobuf, - when) { + '../Core/Credit', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/GeographicTilingScheme', + '../Core/GoogleEarthEnterpriseMetadata', + '../Core/loadArrayBuffer', + '../Core/loadImageFromTypedArray', + '../Core/Math', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/throttleRequestByServer', + '../Core/TileProviderError', + '../ThirdParty/protobuf-minimal', + '../ThirdParty/when' +], function( + Credit, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + loadArrayBuffer, + loadImageFromTypedArray, + CesiumMath, + Rectangle, + RuntimeError, + throttleRequestByServer, + TileProviderError, + protobuf, + when) { 'use strict'; function GoogleEarthEnterpriseDiscardPolicy() { @@ -422,11 +422,10 @@ define([ throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); } //>>includeEnd('debug'); - var that = this; var invalidImage = this._tileDiscardPolicy._image; // Empty image or undefined depending on discard policy var metadata = this._metadata; - var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y , level); + var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); var info = metadata.getTileInformation(x, y, level); if (!defined(info)) { if (metadata.isValid(quadKey)) { diff --git a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js index a612e37e208f..6d83e7b18888 100644 --- a/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -1,42 +1,42 @@ /*global define*/ define([ - '../Core/AxisAlignedBoundingBox', - '../Core/BoundingSphere', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/defaultValue', - '../Core/defined', - '../Core/Ellipsoid', - '../Core/EllipsoidalOccluder', - '../Core/Math', - '../Core/Matrix4', - '../Core/OrientedBoundingBox', - '../Core/Rectangle', - '../Core/RuntimeError', - '../Core/TerrainEncoding', - '../Core/Transforms', - '../Core/WebMercatorProjection', - './createTaskProcessorWorker' - ], function( - AxisAlignedBoundingBox, - BoundingSphere, - Cartesian2, - Cartesian3, - Cartographic, - defaultValue, - defined, - Ellipsoid, - EllipsoidalOccluder, - CesiumMath, - Matrix4, - OrientedBoundingBox, - Rectangle, - RuntimeError, - TerrainEncoding, - Transforms, - WebMercatorProjection, - createTaskProcessorWorker) { + '../Core/AxisAlignedBoundingBox', + '../Core/BoundingSphere', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/defaultValue', + '../Core/defined', + '../Core/Ellipsoid', + '../Core/EllipsoidalOccluder', + '../Core/Math', + '../Core/Matrix4', + '../Core/OrientedBoundingBox', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/TerrainEncoding', + '../Core/Transforms', + '../Core/WebMercatorProjection', + './createTaskProcessorWorker' +], function( + AxisAlignedBoundingBox, + BoundingSphere, + Cartesian2, + Cartesian3, + Cartographic, + defaultValue, + defined, + Ellipsoid, + EllipsoidalOccluder, + CesiumMath, + Matrix4, + OrientedBoundingBox, + Rectangle, + RuntimeError, + TerrainEncoding, + Transforms, + WebMercatorProjection, + createTaskProcessorWorker) { 'use strict'; var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; @@ -91,6 +91,7 @@ define([ var minimumScratch = new Cartesian3(); var maximumScratch = new Cartesian3(); var matrix4Scratch = new Matrix4(); + function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration, skirtHeight, includeWebMercatorT) { var geographicWest; var geographicSouth; @@ -165,14 +166,14 @@ define([ quadBorderLatitudes.push(y); } - o += 2*sizeOfDouble; // stepX + stepY + o += 2 * sizeOfDouble; // stepX + stepY var c = dv.getInt32(o, true); // Read point count o += sizeOfInt32; size += c; c = dv.getInt32(o, true); // Read index count - indicesSize += c*3; + indicesSize += c * 3; offset += quadSize + sizeOfUint32; // Jump to next quad } @@ -264,23 +265,23 @@ define([ if (Math.abs(longitude - geographicWest) < halfStepX) { westBorder.push({ - index: pointOffset, - cartographic: Cartographic.clone(scratchCartographic) + index : pointOffset, + cartographic : Cartographic.clone(scratchCartographic) }); - } else if(Math.abs(longitude - geographicEast) < halfStepX) { + } else if (Math.abs(longitude - geographicEast) < halfStepX) { eastBorder.push({ - index: pointOffset, - cartographic: Cartographic.clone(scratchCartographic) + index : pointOffset, + cartographic : Cartographic.clone(scratchCartographic) }); } else if (Math.abs(latitude - geographicSouth) < halfStepY) { southBorder.push({ - index: pointOffset, - cartographic: Cartographic.clone(scratchCartographic) + index : pointOffset, + cartographic : Cartographic.clone(scratchCartographic) }); - } else if(Math.abs(latitude - geographicNorth) < halfStepY) { + } else if (Math.abs(latitude - geographicNorth) < halfStepY) { northBorder.push({ - index: pointOffset, - cartographic: Cartographic.clone(scratchCartographic) + index : pointOffset, + cartographic : Cartographic.clone(scratchCartographic) }); } @@ -315,7 +316,7 @@ define([ offset += sizeOfUint16; } - if (quadSize !== (offset-startQuad)) { + if (quadSize !== (offset - startQuad)) { throw new RuntimeError('Invalid terrain tile.'); } } @@ -332,13 +333,13 @@ define([ // Add skirt points var skirtOptions = { - hMin: minHeight, + hMin : minHeight, lastBorderPoint : undefined, - skirtHeight: skirtHeight, - toENU: toENU, - ellipsoid: ellipsoid, - minimum: minimum, - maximum: maximum + skirtHeight : skirtHeight, + toENU : toENU, + ellipsoid : ellipsoid, + minimum : minimum, + maximum : maximum }; // Sort counter clockwise from NW corner @@ -358,20 +359,20 @@ define([ var percentage = 0.00001; addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, - westBorder, -percentage*rectangleWidth, true, -percentage*rectangleHeight); + westBorder, -percentage * rectangleWidth, true, -percentage * rectangleHeight); addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, - southBorder, -percentage*rectangleHeight, false); + southBorder, -percentage * rectangleHeight, false); addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, - eastBorder, percentage*rectangleWidth, true, percentage*rectangleHeight); + eastBorder, percentage * rectangleWidth, true, percentage * rectangleHeight); addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions, - northBorder, percentage*rectangleHeight, false); + northBorder, percentage * rectangleHeight, false); // Since the corner between the north and west sides is in the west array, generate the last // two triangles between the last north vertex and the first west vertex if (westBorder.length > 0 && northBorder.length > 0) { var firstBorderIndex = westBorder[0].index; var firstSkirtIndex = vertexCountWithoutSkirts; - var lastBorderIndex = northBorder[northBorder.length-1].index; + var lastBorderIndex = northBorder[northBorder.length - 1].index; var lastSkirtIndex = positions.length - 1; indices.push(lastBorderIndex, lastSkirtIndex, firstSkirtIndex, firstSkirtIndex, firstBorderIndex, lastBorderIndex); @@ -439,7 +440,7 @@ define([ // Since corners are in the east/west arrays angle the first and last points as well if (!eastOrWest) { scratchCartographic.latitude += fudgeFactor; - } else if (j === (count-1)) { + } else if (j === (count - 1)) { scratchCartographic.latitude += cornerFudge; } else if (j === 0) { scratchCartographic.latitude -= cornerFudge; diff --git a/Source/Workers/decodeGoogleEarthEnterprisePacket.js b/Source/Workers/decodeGoogleEarthEnterprisePacket.js index aa6b3c885506..33fcf13c7d54 100644 --- a/Source/Workers/decodeGoogleEarthEnterprisePacket.js +++ b/Source/Workers/decodeGoogleEarthEnterprisePacket.js @@ -38,7 +38,7 @@ define([ buffer = uncompressedTerrain.buffer; var length = uncompressedTerrain.length; - switch(type) { + switch (type) { case Types.METADATA: return processMetadata(buffer, length, parameters.quadKey); case Types.TERRAIN: @@ -48,6 +48,7 @@ define([ } var qtMagic = 32301; + function processMetadata(buffer, totalSize, quadKey) { var dv = new DataView(buffer); var offset = 0; @@ -211,6 +212,7 @@ define([ var compressedMagic = 0x7468dead; var compressedMagicSwap = 0xadde6874; + function uncompressPacket(data) { // The layout of this decoded data is // Magic Uint32 diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index 33271152bfda..a71c2e2ec7a9 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -1,18 +1,18 @@ /*global defineSuite*/ defineSuite([ - 'Core/GoogleEarthEnterpriseMetadata', - 'Core/DefaultProxy', - 'Core/defaultValue', - 'Core/loadWithXhr', - 'Core/Math', - 'ThirdParty/when' - ], function( - GoogleEarthEnterpriseMetadata, - DefaultProxy, - defaultValue, - loadWithXhr, - CesiumMath, - when) { + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/loadWithXhr', + 'Core/Math', + 'ThirdParty/when' +], function( + GoogleEarthEnterpriseMetadata, + DefaultProxy, + defaultValue, + loadWithXhr, + CesiumMath, + when) { 'use strict'; it('tileXYToQuadKey', function() { diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js index a6736fc35b06..af0203c72944 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainDataSpec.js @@ -1,32 +1,32 @@ /*global defineSuite*/ defineSuite([ - 'Core/GoogleEarthEnterpriseTerrainData', - 'Core/BoundingSphere', - 'Core/Cartesian3', - 'Core/Cartographic', - 'Core/Ellipsoid', - 'Core/GeographicTilingScheme', - 'Core/Math', - 'Core/Matrix4', - 'Core/Rectangle', - 'Core/TerrainData', - 'Core/TerrainMesh', - 'Core/Transforms', - 'ThirdParty/when' - ], function( - GoogleEarthEnterpriseTerrainData, - BoundingSphere, - Cartesian3, - Cartographic, - Ellipsoid, - GeographicTilingScheme, - CesiumMath, - Matrix4, - Rectangle, - TerrainData, - TerrainMesh, - Transforms, - when) { + 'Core/GoogleEarthEnterpriseTerrainData', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/GeographicTilingScheme', + 'Core/Math', + 'Core/Matrix4', + 'Core/Rectangle', + 'Core/TerrainData', + 'Core/TerrainMesh', + 'Core/Transforms', + 'ThirdParty/when' +], function( + GoogleEarthEnterpriseTerrainData, + BoundingSphere, + Cartesian3, + Cartographic, + Ellipsoid, + GeographicTilingScheme, + CesiumMath, + Matrix4, + Rectangle, + TerrainData, + TerrainMesh, + Transforms, + when) { 'use strict'; var sizeOfUint8 = Uint8Array.BYTES_PER_ELEMENT; diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js index 8f1e9dbfdaba..fd7ec9a376bc 100644 --- a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js @@ -1,34 +1,34 @@ /*global defineSuite*/ defineSuite([ - 'Core/GoogleEarthEnterpriseTerrainProvider', - 'Core/DefaultProxy', - 'Core/defaultValue', - 'Core/defined', - 'Core/Ellipsoid', - 'Core/GeographicTilingScheme', - 'Core/GoogleEarthEnterpriseMetadata', - 'Core/GoogleEarthEnterpriseTerrainData', - 'Core/loadImage', - 'Core/loadWithXhr', - 'Core/Math', - 'Core/TerrainProvider', - 'Specs/pollToPromise', - 'ThirdParty/when' - ], function( - GoogleEarthEnterpriseTerrainProvider, - DefaultProxy, - defaultValue, - defined, - Ellipsoid, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - GoogleEarthEnterpriseTerrainData, - loadImage, - loadWithXhr, - CesiumMath, - TerrainProvider, - pollToPromise, - when) { + 'Core/GoogleEarthEnterpriseTerrainProvider', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/defined', + 'Core/Ellipsoid', + 'Core/GeographicTilingScheme', + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/GoogleEarthEnterpriseTerrainData', + 'Core/loadImage', + 'Core/loadWithXhr', + 'Core/Math', + 'Core/TerrainProvider', + 'Specs/pollToPromise', + 'ThirdParty/when' +], function( + GoogleEarthEnterpriseTerrainProvider, + DefaultProxy, + defaultValue, + defined, + Ellipsoid, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + GoogleEarthEnterpriseTerrainData, + loadImage, + loadWithXhr, + CesiumMath, + TerrainProvider, + pollToPromise, + when) { 'use strict'; function installMockGetQuadTreePacket() { @@ -266,14 +266,14 @@ defineSuite([ return pollToPromise(function() { return terrainProvider.ready; }) - .then(function(){ + .then(function() { return pollToPromise(function() { var b = true; for (var i = 0; i < 10; ++i) { b = b && terrainProvider.getTileDataAvailable(i, i, i); } return b && terrainProvider.getTileDataAvailable(1, 2, 3); - }) + }); }) .then(function() { var promise = terrainProvider.requestTileGeometry(1, 2, 3); diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js index 125d47cac4b3..f5467b37f034 100644 --- a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js +++ b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js @@ -1,38 +1,38 @@ /*global defineSuite*/ defineSuite([ - 'Scene/GoogleEarthEnterpriseImageryProvider', - 'Core/DefaultProxy', - 'Core/defaultValue', - 'Core/defined', - 'Core/GeographicTilingScheme', - 'Core/GoogleEarthEnterpriseMetadata', - 'Core/loadImage', - 'Core/loadWithXhr', - 'Core/Rectangle', - 'Scene/DiscardMissingTileImagePolicy', - 'Scene/Imagery', - 'Scene/ImageryLayer', - 'Scene/ImageryProvider', - 'Scene/ImageryState', - 'Specs/pollToPromise', - 'ThirdParty/when' - ], function( - GoogleEarthEnterpriseImageryProvider, - DefaultProxy, - defaultValue, - defined, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - loadImage, - loadWithXhr, - Rectangle, - DiscardMissingTileImagePolicy, - Imagery, - ImageryLayer, - ImageryProvider, - ImageryState, - pollToPromise, - when) { + 'Scene/GoogleEarthEnterpriseImageryProvider', + 'Core/DefaultProxy', + 'Core/defaultValue', + 'Core/defined', + 'Core/GeographicTilingScheme', + 'Core/GoogleEarthEnterpriseMetadata', + 'Core/loadImage', + 'Core/loadWithXhr', + 'Core/Rectangle', + 'Scene/DiscardMissingTileImagePolicy', + 'Scene/Imagery', + 'Scene/ImageryLayer', + 'Scene/ImageryProvider', + 'Scene/ImageryState', + 'Specs/pollToPromise', + 'ThirdParty/when' +], function( + GoogleEarthEnterpriseImageryProvider, + DefaultProxy, + defaultValue, + defined, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + loadImage, + loadWithXhr, + Rectangle, + DiscardMissingTileImagePolicy, + Imagery, + ImageryLayer, + ImageryProvider, + ImageryState, + pollToPromise, + when) { 'use strict'; var oldDecode; From 82f3ed0cfabb9650579482fc771dc974aae73c80 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 12 Apr 2017 09:55:19 -0400 Subject: [PATCH 50/60] Removed unneeded promise return value. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index ca153fa56a87..f5b03472b925 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -520,13 +520,7 @@ define([ var subtreePromises = that._subtreePromises; var promise = subtreePromises[q]; if (defined(promise)) { - return promise - .then(function() { - if (!defined(tileInfo[quadKey])) { - return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); - } - return tileInfo[quadKey]; - }); + return promise; } // We need to split up the promise here because when will execute syncronously if getQuadTreePacket @@ -545,13 +539,10 @@ define([ return populateSubtree(that, quadKey, throttle); }) .always(function() { - delete subtreePromises[q]; - }) - .then(function() { if (!defined(tileInfo[quadKey])) { - return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); + console.log('GoogleEarthEnterpriseMetadata: Failed to retrieve metadata for tile ' + quadKey); } - return tileInfo[quadKey]; + delete subtreePromises[q]; }); } From 3675c149c0f6c34f2f8025afc586cf21a980dc6c Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 12 Apr 2017 10:09:28 -0400 Subject: [PATCH 51/60] Cleanup. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index f5b03472b925..a6b2dd71e475 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -520,13 +520,16 @@ define([ var subtreePromises = that._subtreePromises; var promise = subtreePromises[q]; if (defined(promise)) { - return promise; + return promise + .then(function() { + // Recursively call this incase we need multiple subtree requests + return populateSubtree(that, quadKey, throttle); + }); } // We need to split up the promise here because when will execute syncronously if getQuadTreePacket // is already resolved (like in the tests), so subtreePromises will never get cleared out. - // The promise will always resolve with a bool, but the initial request will also remove - // the promise from subtreePromises. + // Only the initial request will also remove the promise from subtreePromises. promise = that.getQuadTreePacket(q, t.cnodeVersion, throttle); if (!defined(promise)) { return undefined; @@ -539,9 +542,6 @@ define([ return populateSubtree(that, quadKey, throttle); }) .always(function() { - if (!defined(tileInfo[quadKey])) { - console.log('GoogleEarthEnterpriseMetadata: Failed to retrieve metadata for tile ' + quadKey); - } delete subtreePromises[q]; }); } From b10c0d1e0c1586a04ad5eabbbea237ec5971e5b3 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 12 Apr 2017 10:10:09 -0400 Subject: [PATCH 52/60] Cleanup. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index a6b2dd71e475..df32f16c55bc 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -473,9 +473,6 @@ define([ }); }; - // Verifies there is tileInfo for a quadKey. If not it requests the subtrees required to get it. - // Returns promise that resolves to true if the tile info is available, false otherwise. - /** * Populates the metadata subtree down to the specified tile. * From 6b75e4d81edab2af7be431d84374aab00bcd15ac Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Wed, 12 Apr 2017 10:26:25 -0400 Subject: [PATCH 53/60] Cleanup. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index df32f16c55bc..1e3a237c6d30 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -507,13 +507,6 @@ define([ t = tileInfo[q]; } - // t is either - // null so one of its parents was a leaf node, so this tile doesn't exist - // undefined so no parent exists - this shouldn't ever happen once the provider is ready - if (!defined(t)) { - return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); - } - var subtreePromises = that._subtreePromises; var promise = subtreePromises[q]; if (defined(promise)) { @@ -524,6 +517,14 @@ define([ }); } + // t is either + // null so one of its parents was a leaf node, so this tile doesn't exist + // exists but doesn't have a subtree to request + // undefined so no parent exists - this shouldn't ever happen once the provider is ready + if (!defined(t) || !t.hasSubtree()) { + return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); + } + // We need to split up the promise here because when will execute syncronously if getQuadTreePacket // is already resolved (like in the tests), so subtreePromises will never get cleared out. // Only the initial request will also remove the promise from subtreePromises. From 498a0449adda31cd5c095a2e2e88028d7a38ac74 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 20 Apr 2017 11:00:21 -0400 Subject: [PATCH 54/60] Cleanup --- Source/Workers/decodeGoogleEarthEnterprisePacket.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Source/Workers/decodeGoogleEarthEnterprisePacket.js b/Source/Workers/decodeGoogleEarthEnterprisePacket.js index 33fcf13c7d54..affc711c7527 100644 --- a/Source/Workers/decodeGoogleEarthEnterprisePacket.js +++ b/Source/Workers/decodeGoogleEarthEnterprisePacket.js @@ -228,15 +228,9 @@ define([ throw new RuntimeError('Invalid magic'); } - // Get the size of the compressed buffer - var size = dv.getUint32(offset, true); + // Get the size of the compressed buffer - the endianness depends on which magic was used + var size = dv.getUint32(offset, (magic === compressedMagic)); offset += sizeOfUint32; - if (magic === compressedMagicSwap) { - size = ((size >>> 24) & 0x000000ff) | - ((size >>> 8) & 0x0000ff00) | - ((size << 8) & 0x00ff0000) | - ((size << 24) & 0xff000000); - } var compressedPacket = new Uint8Array(data, offset); var uncompressedPacket = pako.inflate(compressedPacket); From f40a74c36f64755fa6f28738bd476ffb3a7c013c Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 20 Apr 2017 17:30:11 -0400 Subject: [PATCH 55/60] Fixed issue where we were returning the ellipsoid after a terrain tile had been returned in the parent. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 45 ++++++++++++------- .../decodeGoogleEarthEnterprisePacket.js | 1 - 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 1e3a237c6d30..a0187c6c44e7 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -442,31 +442,44 @@ define([ return decodePromise .then(function(result) { - var parent; - var topLevelKeyLength = quadKey.length + 1; + var root; + var topLevelKeyLength = -1; if (quadKey !== '') { // Root tile has no data except children bits, so put them into the tile info + topLevelKeyLength = quadKey.length + 1; var top = result[quadKey]; - parent = tileInfo[quadKey]; - parent._bits |= top._bits; + root = tileInfo[quadKey]; + root._bits |= top._bits; delete result[quadKey]; } // Copy the resulting objects into tileInfo - for (var key in result) { - if (result.hasOwnProperty(key)) { - var r = result[key]; - if (r !== null) { - var info = TileInformation.fromObject(result[key]); - if (defined(parent) && key.length === topLevelKeyLength) { - info.setParent(parent); - } - tileInfo[key] = info; - } else { - tileInfo[key] = null; + // Make sure we start with shorter quadkeys first, so we know the parents have + // already been processed. Otherwise we can lose ancestorHasTerrain along the way. + var keys = Object.keys(result); + keys.sort(function(a, b) { + return a.length - b.length; + }); + var keysLength = keys.length; + for (var i = 0; i < keysLength; ++i) { + var key = keys[i]; + if (key === "3013203200203") { + debugger; + } + var r = result[key]; + if (r !== null) { + var info = TileInformation.fromObject(result[key]); + var keyLength = key.length; + if (keyLength === topLevelKeyLength) { + info.setParent(root); + } else if(keyLength > 1){ + var parent = tileInfo[key.substring(0, key.length - 1)]; + info.setParent(parent); } - + tileInfo[key] = info; + } else { + tileInfo[key] = null; } } }); diff --git a/Source/Workers/decodeGoogleEarthEnterprisePacket.js b/Source/Workers/decodeGoogleEarthEnterprisePacket.js index affc711c7527..37b1ed3e1ad5 100644 --- a/Source/Workers/decodeGoogleEarthEnterprisePacket.js +++ b/Source/Workers/decodeGoogleEarthEnterprisePacket.js @@ -167,7 +167,6 @@ define([ } var instance = instances[index++]; - instance.setParent(parent); tileInfo[childKey] = instance; populateTiles(childKey, instance, level + 1); } From afd66e8bf8d93b17751a752c21bf1dadc6cc3994 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Sun, 23 Apr 2017 21:52:51 -0400 Subject: [PATCH 56/60] Fixed jshint and changed error checking to use Check class. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 10 ++++------ Source/Core/GoogleEarthEnterpriseTerrainData.js | 7 ++++--- Source/Scene/GoogleEarthEnterpriseImageryProvider.js | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index a0187c6c44e7..be849f21c0ec 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -3,6 +3,7 @@ define([ '../ThirdParty/pako_inflate', '../ThirdParty/when', './appendForwardSlash', + './Check', './defaultValue', './defined', './defineProperties', @@ -15,6 +16,7 @@ define([ pako, when, appendForwardSlash, + Check, defaultValue, defined, defineProperties, @@ -151,10 +153,9 @@ define([ * */ function GoogleEarthEnterpriseMetadata(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); - if (!defined(options.url)) { - throw new DeveloperError('url is required.'); - } + Check.typeOf.string('options.url', options.url); //>>includeEnd('debug'); this._url = appendForwardSlash(options.url); @@ -464,9 +465,6 @@ define([ var keysLength = keys.length; for (var i = 0; i < keysLength; ++i) { var key = keys[i]; - if (key === "3013203200203") { - debugger; - } var r = result[key]; if (r !== null) { var info = TileInformation.fromObject(result[key]); diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 6944ac46ba40..011c05ab18b7 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -4,6 +4,7 @@ define([ './BoundingSphere', './Cartesian2', './Cartesian3', + './Check', './defaultValue', './defined', './defineProperties', @@ -25,6 +26,7 @@ define([ BoundingSphere, Cartesian2, Cartesian3, + Check, defaultValue, defined, defineProperties, @@ -79,10 +81,9 @@ define([ * @see QuantizedMeshTerrainData */ function GoogleEarthEnterpriseTerrainData(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); - if (!defined(options) || !defined(options.buffer)) { - throw new DeveloperError('options.buffer is required.'); - } + Check.typeOf.object('options.buffer', options.buffer); //>>includeEnd('debug'); this._buffer = options.buffer; diff --git a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js index a747840ef403..c9fc42f49d81 100644 --- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js +++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js @@ -96,7 +96,7 @@ define([ * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} */ function GoogleEarthEnterpriseImageryProvider(options) { - options = defaultValue(options, {}); + options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); if (!(defined(options.url) || defined(options.metadata))) { From 08c6b42fe9010de70dfa28901e620beb4626d4e7 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 24 Apr 2017 13:56:41 -0400 Subject: [PATCH 57/60] Cleanup from PR comments. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 27 ++- .../Core/GoogleEarthEnterpriseTerrainData.js | 179 ++++++++---------- .../GoogleEarthEnterpriseTerrainProvider.js | 6 +- 3 files changed, 96 insertions(+), 116 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index be849f21c0ec..03f6204f5d3d 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -60,14 +60,23 @@ define([ /** * Creates TileInformation from an object * - * @param {Object} obj Object with same properties as TileInformation + * @param {Object} info Object to be cloned + * @param {TileInformation} [result] The object onto which to store the result. + * @returns {TileInformation} The modified result parameter or a new TileInformation instance if none was provided. */ - TileInformation.fromObject = function(obj) { - var newInfo = new TileInformation(obj._bits, obj.cnodeVersion, obj.imageryVersion, obj.terrainVersion); - newInfo.ancestorHasTerrain = obj.ancestorHasTerrain; - newInfo.terrainState = obj.terrainState; + TileInformation.clone = function(info, result) { + if (!defined(result)) { + result = new TileInformation(info._bits, info.cnodeVersion, info.imageryVersion, info.terrainVersion); + } else { + result._bits = info._bits; + result.cnodeVersion = info.cnodeVersion; + result.imageryVersion = info.imageryVersion; + result.terrainVersion = info.terrainVersion; + } + result.ancestorHasTerrain = info.ancestorHasTerrain; + result.terrainState = info.terrainState; - return newInfo; + return result; }; /** @@ -439,7 +448,7 @@ define([ buffer : metadata, quadKey : quadKey, type : 'Metadata' - }); + }, [metadata]); return decodePromise .then(function(result) { @@ -467,7 +476,7 @@ define([ var key = keys[i]; var r = result[key]; if (r !== null) { - var info = TileInformation.fromObject(result[key]); + var info = TileInformation.clone(result[key]); var keyLength = key.length; if (keyLength === topLevelKeyLength) { info.setParent(root); @@ -510,7 +519,7 @@ define([ var t = tileInfo[q]; // If we have tileInfo make sure sure it is not a node with a subtree that's not loaded if (defined(t) && (!t.hasSubtree() || t.hasChildren())) { - return when(t); + return t; } while ((t === undefined) && q.length > 1) { diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index 011c05ab18b7..0f434fdaa4b0 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -19,8 +19,7 @@ define([ './Rectangle', './TaskProcessor', './TerrainEncoding', - './TerrainMesh', - './TerrainProvider' + './TerrainMesh' ], function( when, BoundingSphere, @@ -41,8 +40,7 @@ define([ Rectangle, TaskProcessor, TerrainEncoding, - TerrainMesh, - TerrainProvider) { + TerrainMesh) { 'use strict'; /** @@ -125,6 +123,9 @@ define([ }); var taskProcessor = new TaskProcessor('createVerticesFromGoogleEarthEnterpriseBuffer'); + + var nativeRectangleScratch = new Rectangle(); + var rectangleScratch = new Rectangle(); /** * Creates a {@link TerrainMesh} from this terrain data. @@ -142,27 +143,19 @@ define([ */ GoogleEarthEnterpriseTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) { //>>includeStart('debug', pragmas.debug); - if (!defined(tilingScheme)) { - throw new DeveloperError('tilingScheme is required.'); - } - if (!defined(x)) { - throw new DeveloperError('x is required.'); - } - if (!defined(y)) { - throw new DeveloperError('y is required.'); - } - if (!defined(level)) { - throw new DeveloperError('level is required.'); - } + Check.typeOf.object('tilingScheme', tilingScheme); + Check.typeOf.number('x', x); + Check.typeOf.number('y', y); + Check.typeOf.number('level', level); //>>includeEnd('debug'); var ellipsoid = tilingScheme.ellipsoid; - var nativeRectangle = tilingScheme.tileXYToNativeRectangle(x, y, level); - var rectangle = tilingScheme.tileXYToRectangle(x, y, level); + tilingScheme.tileXYToNativeRectangle(x, y, level, nativeRectangleScratch); + tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch); exaggeration = defaultValue(exaggeration, 1.0); // Compute the center of the tile for RTC rendering. - var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle)); + var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangleScratch)); var levelZeroMaxError = 40075.16; // From Google's Doc var thisLevelMaxError = levelZeroMaxError / (1 << level); @@ -170,8 +163,8 @@ define([ var verticesPromise = taskProcessor.scheduleTask({ buffer : this._buffer, - nativeRectangle : nativeRectangle, - rectangle : rectangle, + nativeRectangle : nativeRectangleScratch, + rectangle : rectangleScratch, relativeToCenter : center, ellipsoid : ellipsoid, skirtHeight : this._skirtHeight, @@ -185,29 +178,30 @@ define([ } var that = this; - return when(verticesPromise, function(result) { - that._mesh = new TerrainMesh( - center, - new Float32Array(result.vertices), - new Uint16Array(result.indices), - result.minimumHeight, - result.maximumHeight, - result.boundingSphere3D, - result.occludeePointInScaledSpace, - result.numberOfAttributes, - result.orientedBoundingBox, - TerrainEncoding.clone(result.encoding), - exaggeration); - - that._vertexCountWithoutSkirts = result.vertexCountWithoutSkirts; - that._skirtIndex = result.skirtIndex; - that._minimumHeight = result.minimumHeight; - that._maximumHeight = result.maximumHeight; - - // Free memory received from server after mesh is created. - that._buffer = undefined; - return that._mesh; - }); + return verticesPromise + .then(function(result) { + that._mesh = new TerrainMesh( + center, + new Float32Array(result.vertices), + new Uint16Array(result.indices), + result.minimumHeight, + result.maximumHeight, + result.boundingSphere3D, + result.occludeePointInScaledSpace, + result.numberOfAttributes, + result.orientedBoundingBox, + TerrainEncoding.clone(result.encoding), + exaggeration); + + that._vertexCountWithoutSkirts = result.vertexCountWithoutSkirts; + that._skirtIndex = result.skirtIndex; + that._minimumHeight = result.minimumHeight; + that._maximumHeight = result.maximumHeight; + + // Free memory received from server after mesh is created. + that._buffer = undefined; + return that._mesh; + }); }; /** @@ -250,27 +244,13 @@ define([ */ GoogleEarthEnterpriseTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) { //>>includeStart('debug', pragmas.debug); - if (!defined(tilingScheme)) { - throw new DeveloperError('tilingScheme is required.'); - } - if (!defined(thisX)) { - throw new DeveloperError('thisX is required.'); - } - if (!defined(thisY)) { - throw new DeveloperError('thisY is required.'); - } - if (!defined(thisLevel)) { - throw new DeveloperError('thisLevel is required.'); - } - if (!defined(descendantX)) { - throw new DeveloperError('descendantX is required.'); - } - if (!defined(descendantY)) { - throw new DeveloperError('descendantY is required.'); - } - if (!defined(descendantLevel)) { - throw new DeveloperError('descendantLevel is required.'); - } + Check.typeOf.object('tilingScheme', tilingScheme); + Check.typeOf.number('thisX', thisX); + Check.typeOf.number('thisY', thisY); + Check.typeOf.number('thisLevel', thisLevel); + Check.typeOf.number('descendantX', descendantX); + Check.typeOf.number('descendantY', descendantY); + Check.typeOf.number('descendantLevel', descendantLevel); var levelDifference = descendantLevel - thisLevel; if (levelDifference > 1) { throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.'); @@ -309,33 +289,34 @@ define([ } var that = this; - return when(upsamplePromise, function(result) { - var quantizedVertices = new Uint16Array(result.vertices); - var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices); - - var skirtHeight = that._skirtHeight; - - // Use QuantizedMeshTerrainData since we have what we need already parsed - return new QuantizedMeshTerrainData({ - quantizedVertices : quantizedVertices, - indices : indicesTypedArray, - minimumHeight : result.minimumHeight, - maximumHeight : result.maximumHeight, - boundingSphere : BoundingSphere.clone(result.boundingSphere), - orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox), - horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), - westIndices : result.westIndices, - southIndices : result.southIndices, - eastIndices : result.eastIndices, - northIndices : result.northIndices, - westSkirtHeight : skirtHeight, - southSkirtHeight : skirtHeight, - eastSkirtHeight : skirtHeight, - northSkirtHeight : skirtHeight, - childTileMask : 0, - createdByUpsampling : true + return upsamplePromise + .then(function(result) { + var quantizedVertices = new Uint16Array(result.vertices); + var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices); + + var skirtHeight = that._skirtHeight; + + // Use QuantizedMeshTerrainData since we have what we need already parsed + return new QuantizedMeshTerrainData({ + quantizedVertices : quantizedVertices, + indices : indicesTypedArray, + minimumHeight : result.minimumHeight, + maximumHeight : result.maximumHeight, + boundingSphere : BoundingSphere.clone(result.boundingSphere), + orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox), + horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), + westIndices : result.westIndices, + southIndices : result.southIndices, + eastIndices : result.eastIndices, + northIndices : result.northIndices, + westSkirtHeight : skirtHeight, + southSkirtHeight : skirtHeight, + eastSkirtHeight : skirtHeight, + northSkirtHeight : skirtHeight, + childTileMask : 0, + createdByUpsampling : true + }); }); - }); }; /** @@ -352,18 +333,10 @@ define([ */ GoogleEarthEnterpriseTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) { //>>includeStart('debug', pragmas.debug); - if (!defined(thisX)) { - throw new DeveloperError('thisX is required.'); - } - if (!defined(thisY)) { - throw new DeveloperError('thisY is required.'); - } - if (!defined(childX)) { - throw new DeveloperError('childX is required.'); - } - if (!defined(childY)) { - throw new DeveloperError('childY is required.'); - } + Check.typeOf.number('thisX', thisX); + Check.typeOf.number('thisY', thisY); + Check.typeOf.number('childX', childX); + Check.typeOf.number('childY', childY); //>>includeEnd('debug'); var bitNumber = 2; // northwest child diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index 198a8cb6033c..ffac179f7b53 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -447,12 +447,10 @@ define([ return promise .then(function(terrain) { if (defined(terrain)) { - var decodePromise = taskProcessor.scheduleTask({ + return taskProcessor.scheduleTask({ buffer : terrain, type : 'Terrain' - }); - - return decodePromise + } ,[terrain]) .then(function(terrainTiles) { // If we were sent child tiles, store them till they are needed var buffer; From 0740d613f0e9bcb94e8fa77ea6d18edfb3c7d76d Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 24 Apr 2017 15:57:20 -0400 Subject: [PATCH 58/60] Fixed requesting some non-existant tiles. --- Source/Core/GoogleEarthEnterpriseMetadata.js | 2 +- .../GoogleEarthEnterpriseTerrainProvider.js | 38 +++++++++++++++---- .../Core/GoogleEarthEnterpriseMetadataSpec.js | 4 +- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js index 03f6204f5d3d..799b9c2137c3 100644 --- a/Source/Core/GoogleEarthEnterpriseMetadata.js +++ b/Source/Core/GoogleEarthEnterpriseMetadata.js @@ -54,7 +54,7 @@ define([ this.imageryVersion = imageryVersion; this.terrainVersion = terrainVersion; this.ancestorHasTerrain = false; // Set it later once we find its parent - this.terrainState = 0; // UNKNOWN + this.terrainState = undefined; } /** diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index ffac179f7b53..98a66fbc9393 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -319,6 +319,23 @@ define([ var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterprisePacket', Number.POSITIVE_INFINITY); + // If the tile has its own terrain, then you can just use its child bitmask. If it was requested using it's parent + // then you need to check all of its children to see if they have terrain. + function computeChildMask(quadKey, info, metadata) { + var childMask = info.getChildBitmask(); + if (info.terrainState === TerrainState.PARENT) { + childMask = 0; + for (var i = 0; i < 4; ++i) { + var child = metadata.getTileInformationFromQuadKey(quadKey + i.toString()); + if (defined(child) && child.hasTerrain()) { + childMask |= (1 << i); + } + } + } + + return childMask; + } + /** * Requests the geometry for a given tile. This function should not be called before * {@link GoogleEarthEnterpriseProvider#ready} returns true. The result must include terrain data and @@ -354,16 +371,23 @@ define([ return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } + var terrainState = info.terrainState; + if (!defined(terrainState)) { + // First time we have tried to load this tile, so set terrain state to UNKNOWN + terrainState = info.terrainState = TerrainState.UNKNOWN; + } + // If its in the cache, return it var buffer = terrainCache.get(quadKey); if (defined(buffer)) { - if (info.terrainState === TerrainState.UNKNOWN) { + if (terrainState === TerrainState.UNKNOWN) { // If its already in the cache then a parent request must've loaded it info.terrainState = TerrainState.PARENT; } + return new GoogleEarthEnterpriseTerrainData({ buffer : buffer, - childTileMask : info.getChildBitmask() + childTileMask : computeChildMask(quadKey, info, metadata) }); } @@ -378,7 +402,7 @@ define([ width : 16, height : 16 }); - } else if (info.terrainState === TerrainState.NONE) { + } else if (terrainState === TerrainState.NONE) { // Already have info and there isn't any terrain here return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } @@ -387,7 +411,6 @@ define([ var parentInfo; var q = quadKey; var terrainVersion = -1; - var terrainState = info.terrainState; if (terrainState !== TerrainState.NONE) { switch (terrainState) { case TerrainState.SELF: // We have terrain and have retrieved it before @@ -450,7 +473,7 @@ define([ return taskProcessor.scheduleTask({ buffer : terrain, type : 'Terrain' - } ,[terrain]) + } /*,[terrain]*/) .then(function(terrainTiles) { // If we were sent child tiles, store them till they are needed var buffer; @@ -477,7 +500,7 @@ define([ if (defined(buffer)) { return new GoogleEarthEnterpriseTerrainData({ buffer : buffer, - childTileMask : info.getChildBitmask() + childTileMask : computeChildMask(quadKey, info, metadata) }); } else { info.terrainState = TerrainState.NONE; @@ -531,7 +554,8 @@ define([ return false; // Terrain is not available } - if (terrainState === TerrainState.UNKNOWN) { + if (!defined(terrainState) || (terrainState === TerrainState.UNKNOWN)) { + info.terrainState = TerrainState.UNKNOWN; if (!info.hasTerrain()) { quadKey = quadKey.substring(0, quadKey.length - 1); var parentInfo = metadata.getTileInformationFromQuadKey(quadKey); diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js index a71c2e2ec7a9..fc00e8f97884 100644 --- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js +++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js @@ -119,7 +119,7 @@ defineSuite([ expect(tileInfo.imageryVersion).toEqual(1); expect(tileInfo.terrainVersion).toEqual(1); expect(tileInfo.ancestorHasTerrain).toEqual(false); - expect(tileInfo.terrainState).toEqual(0); + expect(tileInfo.terrainState).toBeUndefined(); }); }); @@ -164,7 +164,7 @@ defineSuite([ expect(tileInfo.imageryVersion).toEqual(1); expect(tileInfo.terrainVersion).toEqual(1); expect(tileInfo.ancestorHasTerrain).toEqual(false); - expect(tileInfo.terrainState).toEqual(0); + expect(tileInfo.terrainState).toBeUndefined(); }); }); }); From 79347a38c6dc6b6a18ec80b01db6f98360f8a276 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Mon, 24 Apr 2017 16:18:34 -0400 Subject: [PATCH 59/60] Fixed double parsing of terrain buffer. --- .../GoogleEarthEnterpriseTerrainProvider.js | 101 ++++++++---------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index 98a66fbc9393..a89cd02af05a 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -380,11 +380,6 @@ define([ // If its in the cache, return it var buffer = terrainCache.get(quadKey); if (defined(buffer)) { - if (terrainState === TerrainState.UNKNOWN) { - // If its already in the cache then a parent request must've loaded it - info.terrainState = TerrainState.PARENT; - } - return new GoogleEarthEnterpriseTerrainData({ buffer : buffer, childTileMask : computeChildMask(quadKey, info, metadata) @@ -458,62 +453,60 @@ define([ requestPromise = loadArrayBuffer(url); } - terrainPromises[q] = requestPromise; - promise = requestPromise - .always(function(terrain) { + .then(function(terrain) { + if (defined(terrain)) { + return taskProcessor.scheduleTask({ + buffer : terrain, + type : 'Terrain' + }, [terrain]) + .then(function(terrainTiles) { + // Add requested tile and mark it as SELF + var requestedInfo = metadata.getTileInformationFromQuadKey(q); + requestedInfo.terrainState = TerrainState.SELF; + terrainCache.add(q, terrainTiles[0]); + + // Add children to cache + var count = terrainTiles.length - 1; + for (var j = 0; j < count; ++j) { + var childKey = q + j.toString(); + var child = metadata.getTileInformationFromQuadKey(childKey); + if (defined(child)) { + terrainCache.add(childKey, terrainTiles[j + 1]); + child.terrainState = TerrainState.PARENT; + } + } + }); + } + + return when.reject(new RuntimeError('Failed to load terrain.')); + }) + .otherwise(function(error) { + info.terrainState = TerrainState.NONE; + return when.reject(error); + }); + + terrainPromises[q] = promise; // Store promise without delete from terrainPromises + + // Set promise so we remove from terrainPromises just one time + promise = promise + .always(function() { delete terrainPromises[q]; - return terrain; }); } return promise - .then(function(terrain) { - if (defined(terrain)) { - return taskProcessor.scheduleTask({ - buffer : terrain, - type : 'Terrain' - } /*,[terrain]*/) - .then(function(terrainTiles) { - // If we were sent child tiles, store them till they are needed - var buffer; - if (q !== quadKey) { - terrainCache.add(q, terrainTiles[0]); - var parentInfo = metadata.getTileInformationFromQuadKey(q); - parentInfo.terrainState = TerrainState.SELF; - } else { - buffer = terrainTiles[0]; - info.terrainState = TerrainState.SELF; - } - var count = terrainTiles.length - 1; - for (var j = 0; j < count; ++j) { - var childKey = q + j.toString(); - if (childKey === quadKey) { - buffer = terrainTiles[j + 1]; - // If we didn't request this tile directly then it came from a parent - info.terrainState = TerrainState.PARENT; - } else if (info.hasChild(j)) { - terrainCache.add(childKey, terrainTiles[j + 1]); - } - } - - if (defined(buffer)) { - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : computeChildMask(quadKey, info, metadata) - }); - } else { - info.terrainState = TerrainState.NONE; - return when.reject(new RuntimeError('Failed to load terrain.')); - } - }); + .then(function() { + var buffer = terrainCache.get(quadKey); + if (defined(buffer)) { + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : computeChildMask(quadKey, info, metadata) + }); + } else { + info.terrainState = TerrainState.NONE; + return when.reject(new RuntimeError('Failed to load terrain.')); } - - return when.reject(new RuntimeError('Failed to load terrain.')); - }) - .otherwise(function(error) { - info.terrainState = TerrainState.NONE; - return when.reject(error); }); }; From 30afaff5a906fa24e94496ad710b81f509628f22 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Fri, 28 Apr 2017 16:31:50 -0400 Subject: [PATCH 60/60] Remove unnecessary if check. --- .../GoogleEarthEnterpriseTerrainProvider.js | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js index a89cd02af05a..8cdb8277bf53 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js @@ -406,28 +406,26 @@ define([ var parentInfo; var q = quadKey; var terrainVersion = -1; - if (terrainState !== TerrainState.NONE) { - switch (terrainState) { - case TerrainState.SELF: // We have terrain and have retrieved it before - terrainVersion = info.terrainVersion; - break; - case TerrainState.PARENT: // We have terrain in our parent + switch (terrainState) { + case TerrainState.SELF: // We have terrain and have retrieved it before + terrainVersion = info.terrainVersion; + break; + case TerrainState.PARENT: // We have terrain in our parent + q = q.substring(0, q.length - 1); + parentInfo = metadata.getTileInformationFromQuadKey(q); + terrainVersion = parentInfo.terrainVersion; + break; + case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet + if (info.hasTerrain()) { + terrainVersion = info.terrainVersion; // We should have terrain + } else { q = q.substring(0, q.length - 1); parentInfo = metadata.getTileInformationFromQuadKey(q); - terrainVersion = parentInfo.terrainVersion; - break; - case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet - if (info.hasTerrain()) { - terrainVersion = info.terrainVersion; // We should have terrain - } else { - q = q.substring(0, q.length - 1); - parentInfo = metadata.getTileInformationFromQuadKey(q); - if (defined(parentInfo) && parentInfo.hasTerrain()) { - terrainVersion = parentInfo.terrainVersion; // Try checking in the parent - } + if (defined(parentInfo) && parentInfo.hasTerrain()) { + terrainVersion = parentInfo.terrainVersion; // Try checking in the parent } - break; - } + } + break; } // We can't figure out where to get the terrain