/** * Cesium - https://github.com/CesiumGS/cesium * * Copyright 2011-2020 Cesium Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Columbus View (Pat. Pend.) * * Portions licensed separately. * See https://github.com/CesiumGS/cesium/blob/master/LICENSE.md for full licensing details. */ define(['./when-8d13db60', './Check-70bec281', './RuntimeError-ba10bc3e', './createTaskProcessorWorker', './pako_inflate-8ea163f9'], function (when, Check, RuntimeError, createTaskProcessorWorker, pako_inflate) { 'use strict'; var compressedMagic = 0x7468dead; var compressedMagicSwap = 0xadde6874; /** * Decodes data that is received from the Google Earth Enterprise server. * * @param {ArrayBuffer} key The key used during decoding. * @param {ArrayBuffer} data The data to be decoded. * * @private */ function decodeGoogleEarthEnterpriseData(key, data) { if (decodeGoogleEarthEnterpriseData.passThroughDataForTesting) { return data; } //>>includeStart('debug', pragmas.debug); Check.Check.typeOf.object('key', key); Check.Check.typeOf.object('data', data); //>>includeEnd('debug'); var keyLength = key.byteLength; if (keyLength === 0 || (keyLength % 4) !== 0) { throw new RuntimeError.RuntimeError('The length of key must be greater than 0 and a multiple of 4.'); } 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(key); var dp = 0; var dpend = data.byteLength; var dpend64 = dpend - (dpend % 8); var kpend = keyLength; 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++; } } } decodeGoogleEarthEnterpriseData.passThroughDataForTesting = false; /** * @private */ function isBitSet(bits, mask) { return ((bits & mask) !== 0); } // 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 imageBitmask = 0x40; var terrainBitmask = 0x80; /** * 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. * @param {Number} imageryProvider Id of imagery provider. * @param {Number} terrainProvider Id of terrain provider. * * @private */ function GoogleEarthEnterpriseTileInformation(bits, cnodeVersion, imageryVersion, terrainVersion, imageryProvider, terrainProvider) { this._bits = bits; this.cnodeVersion = cnodeVersion; this.imageryVersion = imageryVersion; this.terrainVersion = terrainVersion; this.imageryProvider = imageryProvider; this.terrainProvider = terrainProvider; this.ancestorHasTerrain = false; // Set it later once we find its parent this.terrainState = undefined; } /** * Creates GoogleEarthEnterpriseTileInformation from an object * * @param {Object} info Object to be cloned * @param {GoogleEarthEnterpriseTileInformation} [result] The object onto which to store the result. * @returns {GoogleEarthEnterpriseTileInformation} The modified result parameter or a new GoogleEarthEnterpriseTileInformation instance if none was provided. */ GoogleEarthEnterpriseTileInformation.clone = function(info, result) { if (!when.defined(result)) { result = new GoogleEarthEnterpriseTileInformation(info._bits, info.cnodeVersion, info.imageryVersion, info.terrainVersion, info.imageryProvider, info.terrainProvider); } else { result._bits = info._bits; result.cnodeVersion = info.cnodeVersion; result.imageryVersion = info.imageryVersion; result.terrainVersion = info.terrainVersion; result.imageryProvider = info.imageryProvider; result.terrainProvider = info.terrainProvider; } result.ancestorHasTerrain = info.ancestorHasTerrain; result.terrainState = info.terrainState; return result; }; /** * Sets the parent for the tile * * @param {GoogleEarthEnterpriseTileInformation} parent Parent tile */ GoogleEarthEnterpriseTileInformation.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. */ GoogleEarthEnterpriseTileInformation.prototype.hasSubtree = function() { return isBitSet(this._bits, cacheFlagBitmask); }; /** * Gets whether imagery is available * * @returns {Boolean} true if imagery is available, false otherwise. */ GoogleEarthEnterpriseTileInformation.prototype.hasImagery = function() { return isBitSet(this._bits, imageBitmask); }; /** * Gets whether terrain is available * * @returns {Boolean} true if terrain is available, false otherwise. */ GoogleEarthEnterpriseTileInformation.prototype.hasTerrain = function() { return isBitSet(this._bits, terrainBitmask); }; /** * Gets whether any children are present * * @returns {Boolean} true if any children are available, false otherwise. */ GoogleEarthEnterpriseTileInformation.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 */ GoogleEarthEnterpriseTileInformation.prototype.hasChild = function(index) { return isBitSet(this._bits, childrenBitmasks[index]); }; /** * Gets bitmask containing children * * @returns {Number} Children bitmask */ GoogleEarthEnterpriseTileInformation.prototype.getChildBitmask = function() { return this._bits & anyChildBitmask; }; // 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, DBROOT : 2 }; Types.fromString = function(s) { if (s === 'Metadata') { return Types.METADATA; } else if (s === 'Terrain') { return Types.TERRAIN; } else if (s === 'DbRoot') { return Types.DBROOT; } }; function decodeGoogleEarthEnterprisePacket(parameters, transferableObjects) { var type = Types.fromString(parameters.type); var buffer = parameters.buffer; decodeGoogleEarthEnterpriseData(parameters.key, 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); case Types.DBROOT: transferableObjects.push(buffer); return { buffer : buffer }; } } 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.RuntimeError('Invalid magic'); } var dataTypeId = dv.getUint32(offset, true); offset += sizeOfUint32; if (dataTypeId !== 1) { throw new RuntimeError.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.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.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.RuntimeError('Invalid dataBufferOffset'); } // Verify the packets is all there header + instances + dataBuffer + metaBuffer if (dataBufferOffset + dataBufferSize + metaBufferSize !== totalSize) { throw new RuntimeError.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 var imageProvider = dv.getUint8(offset++); var terrainProvider = dv.getUint8(offset++); offset += sizeOfUint16; // 4 byte align instances.push(new GoogleEarthEnterpriseTileInformation(bitfield, cnodeVersion, imageVersion, terrainVersion, imageProvider, terrainProvider)); } 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++]; 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$1 = 0x7468dead; var compressedMagicSwap$1 = 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$1 && magic !== compressedMagicSwap$1) { throw new RuntimeError.RuntimeError('Invalid magic'); } // Get the size of the compressed buffer - the endianness depends on which magic was used var size = dv.getUint32(offset, (magic === compressedMagic$1)); offset += sizeOfUint32; var compressedPacket = new Uint8Array(data, offset); var uncompressedPacket = pako_inflate.pako.inflate(compressedPacket); if (uncompressedPacket.length !== size) { throw new RuntimeError.RuntimeError('Size of packet doesn\'t match header'); } return uncompressedPacket; } var decodeGoogleEarthEnterprisePacket$1 = createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket); return decodeGoogleEarthEnterprisePacket$1; });