import decodeGoogleEarthEnterpriseData from '../Core/decodeGoogleEarthEnterpriseData.js'; import GoogleEarthEnterpriseTileInformation from '../Core/GoogleEarthEnterpriseTileInformation.js'; import RuntimeError from '../Core/RuntimeError.js'; import pako from '../ThirdParty/pako_inflate.js'; import createTaskProcessorWorker from './createTaskProcessorWorker.js'; // 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('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 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 = 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 - the endianness depends on which magic was used var size = dv.getUint32(offset, (magic === compressedMagic)); offset += sizeOfUint32; 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; } export default createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);