255 lines
8.3 KiB
JavaScript
255 lines
8.3 KiB
JavaScript
|
|
define([
|
||
|
|
'../Core/decodeGoogleEarthEnterpriseData',
|
||
|
|
'../Core/GoogleEarthEnterpriseTileInformation',
|
||
|
|
'../Core/RuntimeError',
|
||
|
|
'../ThirdParty/pako_inflate',
|
||
|
|
'./createTaskProcessorWorker'
|
||
|
|
], function(
|
||
|
|
decodeGoogleEarthEnterpriseData,
|
||
|
|
GoogleEarthEnterpriseTileInformation,
|
||
|
|
RuntimeError,
|
||
|
|
pako,
|
||
|
|
createTaskProcessorWorker) {
|
||
|
|
'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,
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
return createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);
|
||
|
|
});
|