智慧水务管理系统 - 精河县供水工程综合管理平台

PntsParser.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import Check from "../Core/Check.js";
  3. import Color from "../Core/Color.js";
  4. import combine from "../Core/combine.js";
  5. import ComponentDatatype from "../Core/ComponentDatatype.js";
  6. import defined from "../Core/defined.js";
  7. import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
  8. import oneTimeWarning from "../Core/oneTimeWarning.js";
  9. import RuntimeError from "../Core/RuntimeError.js";
  10. import AttributeType from "./AttributeType.js";
  11. import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js";
  12. import VertexAttributeSemantic from "./VertexAttributeSemantic.js";
  13. /**
  14. * Handles parsing of a Point Cloud
  15. *
  16. * @namespace PntsParser
  17. * @private
  18. */
  19. const PntsParser = {};
  20. const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  21. /**
  22. * Parses the contents of a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/PointCloud|Point Cloud}.
  23. *
  24. * @private
  25. *
  26. * @param {*} arrayBuffer The array buffer containing the pnts
  27. * @param {*} [byteOffset=0] The byte offset of the beginning of the pnts in the array buffer
  28. * @returns {object} An object containing a parsed representation of the point cloud
  29. */
  30. PntsParser.parse = function (arrayBuffer, byteOffset) {
  31. byteOffset = byteOffset ?? 0;
  32. //>>includeStart('debug', pragmas.debug);
  33. Check.defined("arrayBuffer", arrayBuffer);
  34. //>>includeEnd('debug');
  35. const uint8Array = new Uint8Array(arrayBuffer);
  36. const view = new DataView(arrayBuffer);
  37. byteOffset += sizeOfUint32; // Skip magic
  38. const version = view.getUint32(byteOffset, true);
  39. if (version !== 1) {
  40. throw new RuntimeError(
  41. `Only Point Cloud tile version 1 is supported. Version ${version} is not.`,
  42. );
  43. }
  44. byteOffset += sizeOfUint32;
  45. // Skip byteLength
  46. byteOffset += sizeOfUint32;
  47. const featureTableJsonByteLength = view.getUint32(byteOffset, true);
  48. if (featureTableJsonByteLength === 0) {
  49. throw new RuntimeError(
  50. "Feature table must have a byte length greater than zero",
  51. );
  52. }
  53. byteOffset += sizeOfUint32;
  54. const featureTableBinaryByteLength = view.getUint32(byteOffset, true);
  55. byteOffset += sizeOfUint32;
  56. const batchTableJsonByteLength = view.getUint32(byteOffset, true);
  57. byteOffset += sizeOfUint32;
  58. const batchTableBinaryByteLength = view.getUint32(byteOffset, true);
  59. byteOffset += sizeOfUint32;
  60. const featureTableJson = getJsonFromTypedArray(
  61. uint8Array,
  62. byteOffset,
  63. featureTableJsonByteLength,
  64. );
  65. byteOffset += featureTableJsonByteLength;
  66. const featureTableBinary = new Uint8Array(
  67. arrayBuffer,
  68. byteOffset,
  69. featureTableBinaryByteLength,
  70. );
  71. byteOffset += featureTableBinaryByteLength;
  72. // Get the batch table JSON and binary
  73. let batchTableJson;
  74. let batchTableBinary;
  75. if (batchTableJsonByteLength > 0) {
  76. // Has a batch table JSON
  77. batchTableJson = getJsonFromTypedArray(
  78. uint8Array,
  79. byteOffset,
  80. batchTableJsonByteLength,
  81. );
  82. if (Object.keys(batchTableJson).length === 0) {
  83. batchTableJson = undefined;
  84. }
  85. byteOffset += batchTableJsonByteLength;
  86. if (batchTableBinaryByteLength > 0) {
  87. // Has a batch table binary
  88. batchTableBinary = new Uint8Array(
  89. arrayBuffer,
  90. byteOffset,
  91. batchTableBinaryByteLength,
  92. );
  93. // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
  94. batchTableBinary = new Uint8Array(batchTableBinary);
  95. byteOffset += batchTableBinaryByteLength;
  96. }
  97. }
  98. const featureTable = new Cesium3DTileFeatureTable(
  99. featureTableJson,
  100. featureTableBinary,
  101. );
  102. const pointsLength = featureTable.getGlobalProperty("POINTS_LENGTH");
  103. featureTable.featuresLength = pointsLength;
  104. if (!defined(pointsLength)) {
  105. throw new RuntimeError(
  106. "Feature table global property: POINTS_LENGTH must be defined",
  107. );
  108. }
  109. let rtcCenter = featureTable.getGlobalProperty(
  110. "RTC_CENTER",
  111. ComponentDatatype.FLOAT,
  112. 3,
  113. );
  114. if (defined(rtcCenter)) {
  115. rtcCenter = Cartesian3.unpack(rtcCenter);
  116. }
  117. // Start with the draco compressed properties and add in uncompressed
  118. // properties.
  119. const parsedContent = parseDracoProperties(featureTable, batchTableJson);
  120. parsedContent.rtcCenter = rtcCenter;
  121. parsedContent.pointsLength = pointsLength;
  122. if (!parsedContent.hasPositions) {
  123. const positions = parsePositions(featureTable);
  124. parsedContent.positions = positions;
  125. parsedContent.hasPositions =
  126. parsedContent.hasPositions || defined(positions);
  127. }
  128. if (!parsedContent.hasPositions) {
  129. throw new RuntimeError(
  130. "Either POSITION or POSITION_QUANTIZED must be defined.",
  131. );
  132. }
  133. if (!parsedContent.hasNormals) {
  134. const normals = parseNormals(featureTable);
  135. parsedContent.normals = normals;
  136. parsedContent.hasNormals = parsedContent.hasNormals || defined(normals);
  137. }
  138. if (!parsedContent.hasColors) {
  139. const colors = parseColors(featureTable);
  140. parsedContent.colors = colors;
  141. parsedContent.hasColors = parsedContent.hasColors || defined(colors);
  142. parsedContent.hasConstantColor = defined(parsedContent.constantColor);
  143. parsedContent.isTranslucent = defined(colors) && colors.isTranslucent;
  144. }
  145. if (!parsedContent.hasBatchIds) {
  146. const batchIds = parseBatchIds(featureTable);
  147. parsedContent.batchIds = batchIds;
  148. parsedContent.hasBatchIds = parsedContent.hasBatchIds || defined(batchIds);
  149. }
  150. if (parsedContent.hasBatchIds) {
  151. const batchLength = featureTable.getGlobalProperty("BATCH_LENGTH");
  152. if (!defined(batchLength)) {
  153. throw new RuntimeError(
  154. "Global property: BATCH_LENGTH must be defined when BATCH_ID is defined.",
  155. );
  156. }
  157. parsedContent.batchLength = batchLength;
  158. }
  159. if (defined(batchTableJson) || defined(batchTableBinary)) {
  160. parsedContent.batchTableJson = batchTableJson;
  161. parsedContent.batchTableBinary = batchTableBinary;
  162. }
  163. // Handle the case that binary body references are contained in the
  164. // batch table without a batch table binary being present
  165. removeInvalidBinaryBodyReferences(parsedContent);
  166. return parsedContent;
  167. };
  168. /**
  169. * Remove all invalid binary body references from the batch table
  170. * JSON of the given parsed content.
  171. *
  172. * This is a workaround for gracefully handling the invalid PNTS
  173. * files that may have been created by the point cloud tiler.
  174. * See https://github.com/CesiumGS/cesium/issues/12872
  175. *
  176. * When the batch table JSON is undefined, nothing will be done.
  177. * When the batch table binary is defined, nothing will be done
  178. * (assuming that any binary body references are valid - this is
  179. * not checked here).
  180. *
  181. * Otherwise, this will remove all binary body references from the
  182. * batch table JSON that are not resolved from draco via the
  183. * `parsedContent.draco.batchTableProperties`.
  184. *
  185. * If any (invalid) binary body reference is found (and removed),
  186. * a one-time warning will be printed.
  187. *
  188. * @param {object} parsedContent The parsed content
  189. */
  190. function removeInvalidBinaryBodyReferences(parsedContent) {
  191. const batchTableJson = parsedContent.batchTableJson;
  192. if (!defined(batchTableJson)) {
  193. return;
  194. }
  195. const batchTableBinary = parsedContent.batchTableBinary;
  196. if (defined(batchTableBinary)) {
  197. return;
  198. }
  199. const dracoBatchTablePropertyNames = Object.keys(
  200. parsedContent.draco?.batchTableProperties ?? {},
  201. );
  202. // Collect the names of all binary body references (identified
  203. // by the property having a `byteOffset`) that have not been
  204. // resolved via the parsedContent.draco.batchTableProperties
  205. const invalidBinaryBodyReferenceNames = [];
  206. for (const name of Object.keys(batchTableJson)) {
  207. const property = batchTableJson[name];
  208. const byteOffset = property.byteOffset;
  209. if (defined(byteOffset)) {
  210. if (!dracoBatchTablePropertyNames.includes(name)) {
  211. invalidBinaryBodyReferenceNames.push(name);
  212. }
  213. }
  214. }
  215. // If there have been invalid binary body references, print
  216. // a one-time warning for each of them, and delete them.
  217. for (const name of invalidBinaryBodyReferenceNames) {
  218. oneTimeWarning(
  219. `PntsParser-invalidBinaryBodyReference`,
  220. `The point cloud data contained a binary property ${name} that could not be resolved - skipping`,
  221. );
  222. delete batchTableJson[name];
  223. }
  224. }
  225. function parseDracoProperties(featureTable, batchTableJson) {
  226. const featureTableJson = featureTable.json;
  227. let dracoBuffer;
  228. let dracoFeatureTableProperties;
  229. let dracoBatchTableProperties;
  230. const featureTableDraco = defined(featureTableJson.extensions)
  231. ? featureTableJson.extensions["3DTILES_draco_point_compression"]
  232. : undefined;
  233. const batchTableDraco =
  234. defined(batchTableJson) && defined(batchTableJson.extensions)
  235. ? batchTableJson.extensions["3DTILES_draco_point_compression"]
  236. : undefined;
  237. if (defined(batchTableDraco)) {
  238. dracoBatchTableProperties = batchTableDraco.properties;
  239. }
  240. let hasPositions;
  241. let hasColors;
  242. let hasNormals;
  243. let hasBatchIds;
  244. let isTranslucent;
  245. if (defined(featureTableDraco)) {
  246. dracoFeatureTableProperties = featureTableDraco.properties;
  247. const dracoByteOffset = featureTableDraco.byteOffset;
  248. const dracoByteLength = featureTableDraco.byteLength;
  249. if (
  250. !defined(dracoFeatureTableProperties) ||
  251. !defined(dracoByteOffset) ||
  252. !defined(dracoByteLength)
  253. ) {
  254. throw new RuntimeError(
  255. "Draco properties, byteOffset, and byteLength must be defined",
  256. );
  257. }
  258. dracoBuffer = featureTable.buffer.slice(
  259. dracoByteOffset,
  260. dracoByteOffset + dracoByteLength,
  261. );
  262. hasPositions = defined(dracoFeatureTableProperties.POSITION);
  263. hasColors =
  264. defined(dracoFeatureTableProperties.RGB) ||
  265. defined(dracoFeatureTableProperties.RGBA);
  266. hasNormals = defined(dracoFeatureTableProperties.NORMAL);
  267. hasBatchIds = defined(dracoFeatureTableProperties.BATCH_ID);
  268. isTranslucent = defined(dracoFeatureTableProperties.RGBA);
  269. }
  270. let draco;
  271. if (defined(dracoBuffer)) {
  272. draco = {
  273. buffer: dracoBuffer,
  274. featureTableProperties: dracoFeatureTableProperties,
  275. batchTableProperties: dracoBatchTableProperties,
  276. properties: combine(
  277. dracoFeatureTableProperties,
  278. dracoBatchTableProperties,
  279. ),
  280. dequantizeInShader: true,
  281. };
  282. }
  283. return {
  284. draco: draco,
  285. hasPositions: hasPositions,
  286. hasColors: hasColors,
  287. isTranslucent: isTranslucent,
  288. hasNormals: hasNormals,
  289. hasBatchIds: hasBatchIds,
  290. };
  291. }
  292. function parsePositions(featureTable) {
  293. const featureTableJson = featureTable.json;
  294. let positions;
  295. if (defined(featureTableJson.POSITION)) {
  296. positions = featureTable.getPropertyArray(
  297. "POSITION",
  298. ComponentDatatype.FLOAT,
  299. 3,
  300. );
  301. return {
  302. name: VertexAttributeSemantic.POSITION,
  303. semantic: VertexAttributeSemantic.POSITION,
  304. typedArray: positions,
  305. isQuantized: false,
  306. componentDatatype: ComponentDatatype.FLOAT,
  307. type: AttributeType.VEC3,
  308. };
  309. } else if (defined(featureTableJson.POSITION_QUANTIZED)) {
  310. positions = featureTable.getPropertyArray(
  311. "POSITION_QUANTIZED",
  312. ComponentDatatype.UNSIGNED_SHORT,
  313. 3,
  314. );
  315. const quantizedVolumeScale = featureTable.getGlobalProperty(
  316. "QUANTIZED_VOLUME_SCALE",
  317. ComponentDatatype.FLOAT,
  318. 3,
  319. );
  320. if (!defined(quantizedVolumeScale)) {
  321. throw new RuntimeError(
  322. "Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions.",
  323. );
  324. }
  325. const quantizedRange = (1 << 16) - 1;
  326. const quantizedVolumeOffset = featureTable.getGlobalProperty(
  327. "QUANTIZED_VOLUME_OFFSET",
  328. ComponentDatatype.FLOAT,
  329. 3,
  330. );
  331. if (!defined(quantizedVolumeOffset)) {
  332. throw new RuntimeError(
  333. "Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions.",
  334. );
  335. }
  336. return {
  337. name: VertexAttributeSemantic.POSITION,
  338. semantic: VertexAttributeSemantic.POSITION,
  339. typedArray: positions,
  340. isQuantized: true,
  341. componentDatatype: ComponentDatatype.FLOAT,
  342. type: AttributeType.VEC3,
  343. quantizedRange: quantizedRange,
  344. quantizedVolumeOffset: Cartesian3.unpack(quantizedVolumeOffset),
  345. quantizedVolumeScale: Cartesian3.unpack(quantizedVolumeScale),
  346. quantizedComponentDatatype: ComponentDatatype.UNSIGNED_SHORT,
  347. quantizedType: AttributeType.VEC3,
  348. };
  349. }
  350. }
  351. function parseColors(featureTable) {
  352. const featureTableJson = featureTable.json;
  353. let colors;
  354. if (defined(featureTableJson.RGBA)) {
  355. colors = featureTable.getPropertyArray(
  356. "RGBA",
  357. ComponentDatatype.UNSIGNED_BYTE,
  358. 4,
  359. );
  360. return {
  361. name: VertexAttributeSemantic.COLOR,
  362. semantic: VertexAttributeSemantic.COLOR,
  363. setIndex: 0,
  364. typedArray: colors,
  365. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  366. type: AttributeType.VEC4,
  367. normalized: true,
  368. isRGB565: false,
  369. isTranslucent: true,
  370. };
  371. } else if (defined(featureTableJson.RGB)) {
  372. colors = featureTable.getPropertyArray(
  373. "RGB",
  374. ComponentDatatype.UNSIGNED_BYTE,
  375. 3,
  376. );
  377. return {
  378. name: "COLOR",
  379. semantic: VertexAttributeSemantic.COLOR,
  380. setIndex: 0,
  381. typedArray: colors,
  382. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  383. type: AttributeType.VEC3,
  384. normalized: true,
  385. isRGB565: false,
  386. isTranslucent: false,
  387. };
  388. } else if (defined(featureTableJson.RGB565)) {
  389. colors = featureTable.getPropertyArray(
  390. "RGB565",
  391. ComponentDatatype.UNSIGNED_SHORT,
  392. 1,
  393. );
  394. return {
  395. name: "COLOR",
  396. semantic: VertexAttributeSemantic.COLOR,
  397. setIndex: 0,
  398. typedArray: colors,
  399. // These settings are for the Model implementation
  400. // which decodes on the CPU and uploads a VEC3 of float colors.
  401. // PointCloud does the decoding on the GPU so uploads a
  402. // UNSIGNED_SHORT instead.
  403. componentDatatype: ComponentDatatype.FLOAT,
  404. type: AttributeType.VEC3,
  405. normalized: false,
  406. isRGB565: true,
  407. isTranslucent: false,
  408. };
  409. } else if (defined(featureTableJson.CONSTANT_RGBA)) {
  410. const constantRGBA = featureTable.getGlobalProperty(
  411. "CONSTANT_RGBA",
  412. ComponentDatatype.UNSIGNED_BYTE,
  413. 4,
  414. );
  415. const alpha = constantRGBA[3];
  416. const constantColor = Color.fromBytes(
  417. constantRGBA[0],
  418. constantRGBA[1],
  419. constantRGBA[2],
  420. alpha,
  421. );
  422. const isTranslucent = alpha < 255;
  423. return {
  424. name: VertexAttributeSemantic.COLOR,
  425. semantic: VertexAttributeSemantic.COLOR,
  426. setIndex: 0,
  427. constantColor: constantColor,
  428. componentDatatype: ComponentDatatype.FLOAT,
  429. type: AttributeType.VEC4,
  430. isQuantized: false,
  431. isTranslucent: isTranslucent,
  432. };
  433. }
  434. return undefined;
  435. }
  436. function parseNormals(featureTable) {
  437. const featureTableJson = featureTable.json;
  438. let normals;
  439. if (defined(featureTableJson.NORMAL)) {
  440. normals = featureTable.getPropertyArray(
  441. "NORMAL",
  442. ComponentDatatype.FLOAT,
  443. 3,
  444. );
  445. return {
  446. name: VertexAttributeSemantic.NORMAL,
  447. semantic: VertexAttributeSemantic.NORMAL,
  448. typedArray: normals,
  449. octEncoded: false,
  450. octEncodedZXY: false,
  451. componentDatatype: ComponentDatatype.FLOAT,
  452. type: AttributeType.VEC3,
  453. };
  454. } else if (defined(featureTableJson.NORMAL_OCT16P)) {
  455. normals = featureTable.getPropertyArray(
  456. "NORMAL_OCT16P",
  457. ComponentDatatype.UNSIGNED_BYTE,
  458. 2,
  459. );
  460. const quantizationBits = 8;
  461. return {
  462. name: VertexAttributeSemantic.NORMAL,
  463. semantic: VertexAttributeSemantic.NORMAL,
  464. typedArray: normals,
  465. octEncoded: true,
  466. octEncodedZXY: false,
  467. quantizedRange: (1 << quantizationBits) - 1,
  468. quantizedType: AttributeType.VEC2,
  469. quantizedComponentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  470. componentDatatype: ComponentDatatype.FLOAT,
  471. type: AttributeType.VEC3,
  472. };
  473. }
  474. return undefined;
  475. }
  476. function parseBatchIds(featureTable) {
  477. const featureTableJson = featureTable.json;
  478. if (defined(featureTableJson.BATCH_ID)) {
  479. const batchIds = featureTable.getPropertyArray(
  480. "BATCH_ID",
  481. ComponentDatatype.UNSIGNED_SHORT,
  482. 1,
  483. );
  484. return {
  485. name: VertexAttributeSemantic.FEATURE_ID,
  486. semantic: VertexAttributeSemantic.FEATURE_ID,
  487. setIndex: 0,
  488. typedArray: batchIds,
  489. componentDatatype: ComponentDatatype.fromTypedArray(batchIds),
  490. type: AttributeType.SCALAR,
  491. };
  492. }
  493. return undefined;
  494. }
  495. export default PntsParser;