decodeGoogleEarthEnterprisePacket.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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_inflate.js";
  5. import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
  6. // Datatype sizes
  7. var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
  8. var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
  9. var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  10. var 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. var type = Types.fromString(parameters.type);
  26. var buffer = parameters.buffer;
  27. decodeGoogleEarthEnterpriseData(parameters.key, buffer);
  28. var uncompressedTerrain = uncompressPacket(buffer);
  29. buffer = uncompressedTerrain.buffer;
  30. var 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. var qtMagic = 32301;
  44. function processMetadata(buffer, totalSize, quadKey) {
  45. var dv = new DataView(buffer);
  46. var offset = 0;
  47. var magic = dv.getUint32(offset, true);
  48. offset += sizeOfUint32;
  49. if (magic !== qtMagic) {
  50. throw new RuntimeError("Invalid magic");
  51. }
  52. var 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. var 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. var numInstances = dv.getInt32(offset, true);
  66. offset += sizeOfInt32;
  67. var dataInstanceSize = dv.getInt32(offset, true);
  68. offset += sizeOfInt32;
  69. if (dataInstanceSize !== 32) {
  70. throw new RuntimeError("Invalid instance size.");
  71. }
  72. var dataBufferOffset = dv.getInt32(offset, true);
  73. offset += sizeOfInt32;
  74. var dataBufferSize = dv.getInt32(offset, true);
  75. offset += sizeOfInt32;
  76. var 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. var instances = [];
  88. for (var i = 0; i < numInstances; ++i) {
  89. var bitfield = dv.getUint8(offset);
  90. ++offset;
  91. ++offset; // 2 byte align
  92. var cnodeVersion = dv.getUint16(offset, true);
  93. offset += sizeOfUint16;
  94. var imageVersion = dv.getUint16(offset, true);
  95. offset += sizeOfUint16;
  96. var 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. var imageProvider = dv.getUint8(offset++);
  108. var 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. var tileInfo = [];
  122. var index = 0;
  123. function populateTiles(parentKey, parent, level) {
  124. var 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 (var i = 0; i < 4; ++i) {
  132. var 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. var instance = instances[index++];
  147. tileInfo[childKey] = instance;
  148. populateTiles(childKey, instance, level + 1);
  149. }
  150. }
  151. }
  152. }
  153. var level = 0;
  154. var 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. function processTerrain(buffer, totalSize, transferableObjects) {
  165. var dv = new DataView(buffer);
  166. var offset = 0;
  167. var terrainTiles = [];
  168. while (offset < totalSize) {
  169. // Each tile is split into 4 parts
  170. var tileStart = offset;
  171. for (var quad = 0; quad < 4; ++quad) {
  172. var size = dv.getUint32(offset, true);
  173. offset += sizeOfUint32;
  174. offset += size;
  175. }
  176. var tile = buffer.slice(tileStart, offset);
  177. transferableObjects.push(tile);
  178. terrainTiles.push(tile);
  179. }
  180. return terrainTiles;
  181. }
  182. var compressedMagic = 0x7468dead;
  183. var compressedMagicSwap = 0xadde6874;
  184. function uncompressPacket(data) {
  185. // The layout of this decoded data is
  186. // Magic Uint32
  187. // Size Uint32
  188. // [GZipped chunk of Size bytes]
  189. // Pullout magic and verify we have the correct data
  190. var dv = new DataView(data);
  191. var offset = 0;
  192. var magic = dv.getUint32(offset, true);
  193. offset += sizeOfUint32;
  194. if (magic !== compressedMagic && magic !== compressedMagicSwap) {
  195. throw new RuntimeError("Invalid magic");
  196. }
  197. // Get the size of the compressed buffer - the endianness depends on which magic was used
  198. var size = dv.getUint32(offset, magic === compressedMagic);
  199. offset += sizeOfUint32;
  200. var compressedPacket = new Uint8Array(data, offset);
  201. var uncompressedPacket = pako.inflate(compressedPacket);
  202. if (uncompressedPacket.length !== size) {
  203. throw new RuntimeError("Size of packet doesn't match header");
  204. }
  205. return uncompressedPacket;
  206. }
  207. export default createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);