123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- import decodeGoogleEarthEnterpriseData from "../Core/decodeGoogleEarthEnterpriseData.js";
- import GoogleEarthEnterpriseTileInformation from "../Core/GoogleEarthEnterpriseTileInformation.js";
- import RuntimeError from "../Core/RuntimeError.js";
- import pako from "../ThirdParty/pako.js";
- 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 containg 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 = pako.inflate(compressedPacket);
- if (uncompressedPacket.length !== size) {
- throw new RuntimeError("Size of packet doesn't match header");
- }
- return uncompressedPacket;
- }
- export default createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);
|