decodeGoogleEarthEnterprisePacket.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import decodeGoogleEarthEnterpriseData from "../Core/decodeGoogleEarthEnterpriseData.js";
  2. import GoogleEarthEnterpriseTileInformation from "../Core/GoogleEarthEnterpriseTileInformation.js";
  3. import RuntimeError from "../Core/RuntimeError.js";
  4. import pako from "../ThirdParty/pako.js";
  5. import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
  6. // Datatype sizes
  7. const sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
  8. const sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
  9. const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  10. const Types = {
  11. METADATA: 0,
  12. TERRAIN: 1,
  13. DBROOT: 2,
  14. };
  15. Types.fromString = function (s) {
  16. if (s === "Metadata") {
  17. return Types.METADATA;
  18. } else if (s === "Terrain") {
  19. return Types.TERRAIN;
  20. } else if (s === "DbRoot") {
  21. return Types.DBROOT;
  22. }
  23. };
  24. function decodeGoogleEarthEnterprisePacket(parameters, transferableObjects) {
  25. const type = Types.fromString(parameters.type);
  26. let buffer = parameters.buffer;
  27. decodeGoogleEarthEnterpriseData(parameters.key, buffer);
  28. const uncompressedTerrain = uncompressPacket(buffer);
  29. buffer = uncompressedTerrain.buffer;
  30. const length = uncompressedTerrain.length;
  31. switch (type) {
  32. case Types.METADATA:
  33. return processMetadata(buffer, length, parameters.quadKey);
  34. case Types.TERRAIN:
  35. return processTerrain(buffer, length, transferableObjects);
  36. case Types.DBROOT:
  37. transferableObjects.push(buffer);
  38. return {
  39. buffer: buffer,
  40. };
  41. }
  42. }
  43. const qtMagic = 32301;
  44. function processMetadata(buffer, totalSize, quadKey) {
  45. const dv = new DataView(buffer);
  46. let offset = 0;
  47. const magic = dv.getUint32(offset, true);
  48. offset += sizeOfUint32;
  49. if (magic !== qtMagic) {
  50. throw new RuntimeError("Invalid magic");
  51. }
  52. const dataTypeId = dv.getUint32(offset, true);
  53. offset += sizeOfUint32;
  54. if (dataTypeId !== 1) {
  55. throw new RuntimeError("Invalid data type. Must be 1 for QuadTreePacket");
  56. }
  57. // Tile format version
  58. const quadVersion = dv.getUint32(offset, true);
  59. offset += sizeOfUint32;
  60. if (quadVersion !== 2) {
  61. throw new RuntimeError(
  62. "Invalid QuadTreePacket version. Only version 2 is supported."
  63. );
  64. }
  65. const numInstances = dv.getInt32(offset, true);
  66. offset += sizeOfInt32;
  67. const dataInstanceSize = dv.getInt32(offset, true);
  68. offset += sizeOfInt32;
  69. if (dataInstanceSize !== 32) {
  70. throw new RuntimeError("Invalid instance size.");
  71. }
  72. const dataBufferOffset = dv.getInt32(offset, true);
  73. offset += sizeOfInt32;
  74. const dataBufferSize = dv.getInt32(offset, true);
  75. offset += sizeOfInt32;
  76. const metaBufferSize = dv.getInt32(offset, true);
  77. offset += sizeOfInt32;
  78. // Offset from beginning of packet (instances + current offset)
  79. if (dataBufferOffset !== numInstances * dataInstanceSize + offset) {
  80. throw new RuntimeError("Invalid dataBufferOffset");
  81. }
  82. // Verify the packets is all there header + instances + dataBuffer + metaBuffer
  83. if (dataBufferOffset + dataBufferSize + metaBufferSize !== totalSize) {
  84. throw new RuntimeError("Invalid packet offsets");
  85. }
  86. // Read all the instances
  87. const instances = [];
  88. for (let i = 0; i < numInstances; ++i) {
  89. const bitfield = dv.getUint8(offset);
  90. ++offset;
  91. ++offset; // 2 byte align
  92. const cnodeVersion = dv.getUint16(offset, true);
  93. offset += sizeOfUint16;
  94. const imageVersion = dv.getUint16(offset, true);
  95. offset += sizeOfUint16;
  96. const terrainVersion = dv.getUint16(offset, true);
  97. offset += sizeOfUint16;
  98. // Number of channels stored in the dataBuffer
  99. offset += sizeOfUint16;
  100. offset += sizeOfUint16; // 4 byte align
  101. // Channel type offset into dataBuffer
  102. offset += sizeOfInt32;
  103. // Channel version offset into dataBuffer
  104. offset += sizeOfInt32;
  105. offset += 8; // Ignore image neighbors for now
  106. // Data providers
  107. const imageProvider = dv.getUint8(offset++);
  108. const terrainProvider = dv.getUint8(offset++);
  109. offset += sizeOfUint16; // 4 byte align
  110. instances.push(
  111. new GoogleEarthEnterpriseTileInformation(
  112. bitfield,
  113. cnodeVersion,
  114. imageVersion,
  115. terrainVersion,
  116. imageProvider,
  117. terrainProvider
  118. )
  119. );
  120. }
  121. const tileInfo = [];
  122. let index = 0;
  123. function populateTiles(parentKey, parent, level) {
  124. let isLeaf = false;
  125. if (level === 4) {
  126. if (parent.hasSubtree()) {
  127. return; // We have a subtree, so just return
  128. }
  129. isLeaf = true; // No subtree, so set all children to null
  130. }
  131. for (let i = 0; i < 4; ++i) {
  132. const childKey = parentKey + i.toString();
  133. if (isLeaf) {
  134. // No subtree so set all children to null
  135. tileInfo[childKey] = null;
  136. } else if (level < 4) {
  137. // We are still in the middle of the subtree, so add child
  138. // only if their bits are set, otherwise set child to null.
  139. if (!parent.hasChild(i)) {
  140. tileInfo[childKey] = null;
  141. } else {
  142. if (index === numInstances) {
  143. console.log("Incorrect number of instances");
  144. return;
  145. }
  146. const instance = instances[index++];
  147. tileInfo[childKey] = instance;
  148. populateTiles(childKey, instance, level + 1);
  149. }
  150. }
  151. }
  152. }
  153. let level = 0;
  154. const root = instances[index++];
  155. if (quadKey === "") {
  156. // Root tile has data at its root and one less level
  157. ++level;
  158. } else {
  159. tileInfo[quadKey] = root; // This will only contain the child bitmask
  160. }
  161. populateTiles(quadKey, root, level);
  162. return tileInfo;
  163. }
  164. const numMeshesPerPacket = 5;
  165. const numSubMeshesPerMesh = 4;
  166. // Each terrain packet will have 5 meshes - each containg 4 sub-meshes:
  167. // 1 even level mesh and its 4 odd level children.
  168. // Any remaining bytes after the 20 sub-meshes contains water surface meshes,
  169. // which are ignored.
  170. function processTerrain(buffer, totalSize, transferableObjects) {
  171. const dv = new DataView(buffer);
  172. // Find the sub-meshes.
  173. const advanceMesh = function (pos) {
  174. for (let sub = 0; sub < numSubMeshesPerMesh; ++sub) {
  175. const size = dv.getUint32(pos, true);
  176. pos += sizeOfUint32;
  177. pos += size;
  178. if (pos > totalSize) {
  179. throw new RuntimeError("Malformed terrain packet found.");
  180. }
  181. }
  182. return pos;
  183. };
  184. let offset = 0;
  185. const terrainMeshes = [];
  186. while (terrainMeshes.length < numMeshesPerPacket) {
  187. const start = offset;
  188. offset = advanceMesh(offset);
  189. const mesh = buffer.slice(start, offset);
  190. transferableObjects.push(mesh);
  191. terrainMeshes.push(mesh);
  192. }
  193. return terrainMeshes;
  194. }
  195. const compressedMagic = 0x7468dead;
  196. const compressedMagicSwap = 0xadde6874;
  197. function uncompressPacket(data) {
  198. // The layout of this decoded data is
  199. // Magic Uint32
  200. // Size Uint32
  201. // [GZipped chunk of Size bytes]
  202. // Pullout magic and verify we have the correct data
  203. const dv = new DataView(data);
  204. let offset = 0;
  205. const magic = dv.getUint32(offset, true);
  206. offset += sizeOfUint32;
  207. if (magic !== compressedMagic && magic !== compressedMagicSwap) {
  208. throw new RuntimeError("Invalid magic");
  209. }
  210. // Get the size of the compressed buffer - the endianness depends on which magic was used
  211. const size = dv.getUint32(offset, magic === compressedMagic);
  212. offset += sizeOfUint32;
  213. const compressedPacket = new Uint8Array(data, offset);
  214. const uncompressedPacket = pako.inflate(compressedPacket);
  215. if (uncompressedPacket.length !== size) {
  216. throw new RuntimeError("Size of packet doesn't match header");
  217. }
  218. return uncompressedPacket;
  219. }
  220. export default createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);