Add existing to tracked
This commit is contained in:
+268
@@ -0,0 +1,268 @@
|
||||
import decodeGoogleEarthEnterpriseData from "../Core/decodeGoogleEarthEnterpriseData.js";
|
||||
import GoogleEarthEnterpriseTileInformation from "../Core/GoogleEarthEnterpriseTileInformation.js";
|
||||
import RuntimeError from "../Core/RuntimeError.js";
|
||||
import { inflate } from "pako/browser/inflate";
|
||||
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
|
||||
|
||||
// Datatype sizes
|
||||
const sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
|
||||
const sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
|
||||
const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
|
||||
|
||||
const 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) {
|
||||
const type = Types.fromString(parameters.type);
|
||||
let buffer = parameters.buffer;
|
||||
decodeGoogleEarthEnterpriseData(parameters.key, buffer);
|
||||
|
||||
const uncompressedTerrain = uncompressPacket(buffer);
|
||||
buffer = uncompressedTerrain.buffer;
|
||||
const 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const qtMagic = 32301;
|
||||
|
||||
function processMetadata(buffer, totalSize, quadKey) {
|
||||
const dv = new DataView(buffer);
|
||||
let offset = 0;
|
||||
const magic = dv.getUint32(offset, true);
|
||||
offset += sizeOfUint32;
|
||||
if (magic !== qtMagic) {
|
||||
throw new RuntimeError("Invalid magic");
|
||||
}
|
||||
|
||||
const dataTypeId = dv.getUint32(offset, true);
|
||||
offset += sizeOfUint32;
|
||||
if (dataTypeId !== 1) {
|
||||
throw new RuntimeError("Invalid data type. Must be 1 for QuadTreePacket");
|
||||
}
|
||||
|
||||
// Tile format version
|
||||
const quadVersion = dv.getUint32(offset, true);
|
||||
offset += sizeOfUint32;
|
||||
if (quadVersion !== 2) {
|
||||
throw new RuntimeError(
|
||||
"Invalid QuadTreePacket version. Only version 2 is supported.",
|
||||
);
|
||||
}
|
||||
|
||||
const numInstances = dv.getInt32(offset, true);
|
||||
offset += sizeOfInt32;
|
||||
|
||||
const dataInstanceSize = dv.getInt32(offset, true);
|
||||
offset += sizeOfInt32;
|
||||
if (dataInstanceSize !== 32) {
|
||||
throw new RuntimeError("Invalid instance size.");
|
||||
}
|
||||
|
||||
const dataBufferOffset = dv.getInt32(offset, true);
|
||||
offset += sizeOfInt32;
|
||||
|
||||
const dataBufferSize = dv.getInt32(offset, true);
|
||||
offset += sizeOfInt32;
|
||||
|
||||
const 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
|
||||
const instances = [];
|
||||
for (let i = 0; i < numInstances; ++i) {
|
||||
const bitfield = dv.getUint8(offset);
|
||||
++offset;
|
||||
|
||||
++offset; // 2 byte align
|
||||
|
||||
const cnodeVersion = dv.getUint16(offset, true);
|
||||
offset += sizeOfUint16;
|
||||
|
||||
const imageVersion = dv.getUint16(offset, true);
|
||||
offset += sizeOfUint16;
|
||||
|
||||
const 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
|
||||
const imageProvider = dv.getUint8(offset++);
|
||||
const terrainProvider = dv.getUint8(offset++);
|
||||
offset += sizeOfUint16; // 4 byte align
|
||||
|
||||
instances.push(
|
||||
new GoogleEarthEnterpriseTileInformation(
|
||||
bitfield,
|
||||
cnodeVersion,
|
||||
imageVersion,
|
||||
terrainVersion,
|
||||
imageProvider,
|
||||
terrainProvider,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const tileInfo = [];
|
||||
let index = 0;
|
||||
|
||||
function populateTiles(parentKey, parent, level) {
|
||||
let 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 (let i = 0; i < 4; ++i) {
|
||||
const 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;
|
||||
}
|
||||
|
||||
const instance = instances[index++];
|
||||
tileInfo[childKey] = instance;
|
||||
populateTiles(childKey, instance, level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let level = 0;
|
||||
const 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;
|
||||
}
|
||||
|
||||
const numMeshesPerPacket = 5;
|
||||
const numSubMeshesPerMesh = 4;
|
||||
|
||||
// Each terrain packet will have 5 meshes - each contain 4 sub-meshes:
|
||||
// 1 even level mesh and its 4 odd level children.
|
||||
// Any remaining bytes after the 20 sub-meshes contains water surface meshes,
|
||||
// which are ignored.
|
||||
function processTerrain(buffer, totalSize, transferableObjects) {
|
||||
const dv = new DataView(buffer);
|
||||
|
||||
// Find the sub-meshes.
|
||||
const advanceMesh = function (pos) {
|
||||
for (let sub = 0; sub < numSubMeshesPerMesh; ++sub) {
|
||||
const size = dv.getUint32(pos, true);
|
||||
pos += sizeOfUint32;
|
||||
pos += size;
|
||||
if (pos > totalSize) {
|
||||
throw new RuntimeError("Malformed terrain packet found.");
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
let offset = 0;
|
||||
const terrainMeshes = [];
|
||||
while (terrainMeshes.length < numMeshesPerPacket) {
|
||||
const start = offset;
|
||||
offset = advanceMesh(offset);
|
||||
const mesh = buffer.slice(start, offset);
|
||||
transferableObjects.push(mesh);
|
||||
terrainMeshes.push(mesh);
|
||||
}
|
||||
|
||||
return terrainMeshes;
|
||||
}
|
||||
|
||||
const compressedMagic = 0x7468dead;
|
||||
const 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
|
||||
const dv = new DataView(data);
|
||||
let offset = 0;
|
||||
const 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
|
||||
const size = dv.getUint32(offset, magic === compressedMagic);
|
||||
offset += sizeOfUint32;
|
||||
|
||||
const compressedPacket = new Uint8Array(data, offset);
|
||||
const uncompressedPacket = inflate(compressedPacket);
|
||||
|
||||
if (uncompressedPacket.length !== size) {
|
||||
throw new RuntimeError("Size of packet doesn't match header");
|
||||
}
|
||||
|
||||
return uncompressedPacket;
|
||||
}
|
||||
export default createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);
|
||||
Reference in New Issue
Block a user