| New file |
| | |
| | | /** |
| | | * 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; |
| | | |
| | | }); |