@loaders.gl/pointclouds 0.0.0-stage → 5.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (511) hide show
  1. package/LAS-README.md +12 -0
  2. package/LICENSE +24 -0
  3. package/PCD-README.md +12 -0
  4. package/PLY-README.md +38 -0
  5. package/README.md +11 -2
  6. package/THIRD-PARTY-NOTICES.md +27 -0
  7. package/dist/dist.dev.js +30165 -0
  8. package/dist/dist.min.js +54 -0
  9. package/dist/index.cjs +61 -0
  10. package/dist/index.cjs.map +7 -0
  11. package/dist/index.d.ts +16 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +13 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/las/bundled.cjs +72 -0
  16. package/dist/las/bundled.cjs.map +7 -0
  17. package/dist/las/bundled.d.ts +12 -0
  18. package/dist/las/bundled.d.ts.map +1 -0
  19. package/dist/las/bundled.js +12 -0
  20. package/dist/las/bundled.js.map +1 -0
  21. package/dist/las/index.cjs +78 -0
  22. package/dist/las/index.cjs.map +7 -0
  23. package/dist/las/index.d.ts +17 -0
  24. package/dist/las/index.d.ts.map +1 -0
  25. package/dist/las/index.js +15 -0
  26. package/dist/las/index.js.map +1 -0
  27. package/dist/las/las-copc-loader-types.cjs +49 -0
  28. package/dist/las/las-copc-loader-types.cjs.map +7 -0
  29. package/dist/las/las-copc-loader-types.d.ts +63 -0
  30. package/dist/las/las-copc-loader-types.d.ts.map +1 -0
  31. package/dist/las/las-copc-loader-types.js +14 -0
  32. package/dist/las/las-copc-loader-types.js.map +1 -0
  33. package/dist/las/las-copc-loader.cjs +57 -0
  34. package/dist/las/las-copc-loader.cjs.map +7 -0
  35. package/dist/las/las-copc-loader.d.ts +37 -0
  36. package/dist/las/las-copc-loader.d.ts.map +1 -0
  37. package/dist/las/las-copc-loader.js +40 -0
  38. package/dist/las/las-copc-loader.js.map +1 -0
  39. package/dist/las/las-format.cjs +44 -0
  40. package/dist/las/las-format.cjs.map +7 -0
  41. package/dist/las/las-format.d.ts +16 -0
  42. package/dist/las/las-format.d.ts.map +1 -0
  43. package/dist/las/las-format.js +19 -0
  44. package/dist/las/las-format.js.map +1 -0
  45. package/dist/las/las-loader-shared.cjs +82 -0
  46. package/dist/las/las-loader-shared.cjs.map +7 -0
  47. package/dist/las/las-loader-shared.d.ts +66 -0
  48. package/dist/las/las-loader-shared.d.ts.map +1 -0
  49. package/dist/las/las-loader-shared.js +58 -0
  50. package/dist/las/las-loader-shared.js.map +1 -0
  51. package/dist/las/las-loader-types.cjs +54 -0
  52. package/dist/las/las-loader-types.cjs.map +7 -0
  53. package/dist/las/las-loader-types.d.ts +104 -0
  54. package/dist/las/las-loader-types.d.ts.map +1 -0
  55. package/dist/las/las-loader-types.js +19 -0
  56. package/dist/las/las-loader-types.js.map +1 -0
  57. package/dist/las/las-loader.cjs +80 -0
  58. package/dist/las/las-loader.cjs.map +7 -0
  59. package/dist/las/las-loader.d.ts +45 -0
  60. package/dist/las/las-loader.d.ts.map +1 -0
  61. package/dist/las/las-loader.js +58 -0
  62. package/dist/las/las-loader.js.map +1 -0
  63. package/dist/las/las-writer.cjs +1054 -0
  64. package/dist/las/las-writer.cjs.map +7 -0
  65. package/dist/las/las-writer.d.ts +69 -0
  66. package/dist/las/las-writer.d.ts.map +1 -0
  67. package/dist/las/las-writer.js +994 -0
  68. package/dist/las/las-writer.js.map +1 -0
  69. package/dist/las/laz-rs-loader-types.cjs +49 -0
  70. package/dist/las/laz-rs-loader-types.cjs.map +7 -0
  71. package/dist/las/laz-rs-loader-types.d.ts +65 -0
  72. package/dist/las/laz-rs-loader-types.d.ts.map +1 -0
  73. package/dist/las/laz-rs-loader-types.js +16 -0
  74. package/dist/las/laz-rs-loader-types.js.map +1 -0
  75. package/dist/las/laz-rs-loader.cjs +272 -0
  76. package/dist/las/laz-rs-loader.cjs.map +7 -0
  77. package/dist/las/laz-rs-loader.d.ts +37 -0
  78. package/dist/las/laz-rs-loader.d.ts.map +1 -0
  79. package/dist/las/laz-rs-loader.js +38 -0
  80. package/dist/las/laz-rs-loader.js.map +1 -0
  81. package/dist/las/lazperf-loader-types.cjs +49 -0
  82. package/dist/las/lazperf-loader-types.cjs.map +7 -0
  83. package/dist/las/lazperf-loader-types.d.ts +67 -0
  84. package/dist/las/lazperf-loader-types.d.ts.map +1 -0
  85. package/dist/las/lazperf-loader-types.js +17 -0
  86. package/dist/las/lazperf-loader-types.js.map +1 -0
  87. package/dist/las/lazperf-loader.cjs +60 -0
  88. package/dist/las/lazperf-loader.cjs.map +7 -0
  89. package/dist/las/lazperf-loader.d.ts +39 -0
  90. package/dist/las/lazperf-loader.d.ts.map +1 -0
  91. package/dist/las/lazperf-loader.js +44 -0
  92. package/dist/las/lazperf-loader.js.map +1 -0
  93. package/dist/las/lib/copc/parse-las.cjs +177 -0
  94. package/dist/las/lib/copc/parse-las.cjs.map +7 -0
  95. package/dist/las/lib/copc/parse-las.d.ts +17 -0
  96. package/dist/las/lib/copc/parse-las.d.ts.map +1 -0
  97. package/dist/las/lib/copc/parse-las.js +215 -0
  98. package/dist/las/lib/copc/parse-las.js.map +1 -0
  99. package/dist/las/lib/get-las-schema.cjs +62 -0
  100. package/dist/las/lib/get-las-schema.cjs.map +7 -0
  101. package/dist/las/lib/get-las-schema.d.ts +16 -0
  102. package/dist/las/lib/get-las-schema.d.ts.map +1 -0
  103. package/dist/las/lib/get-las-schema.js +46 -0
  104. package/dist/las/lib/get-las-schema.js.map +1 -0
  105. package/dist/las/lib/las-compatibility.cjs +110 -0
  106. package/dist/las/lib/las-compatibility.cjs.map +7 -0
  107. package/dist/las/lib/las-compatibility.d.ts +40 -0
  108. package/dist/las/lib/las-compatibility.d.ts.map +1 -0
  109. package/dist/las/lib/las-compatibility.js +115 -0
  110. package/dist/las/lib/las-compatibility.js.map +1 -0
  111. package/dist/las/lib/las-extra-bytes.cjs +223 -0
  112. package/dist/las/lib/las-extra-bytes.cjs.map +7 -0
  113. package/dist/las/lib/las-extra-bytes.d.ts +35 -0
  114. package/dist/las/lib/las-extra-bytes.d.ts.map +1 -0
  115. package/dist/las/lib/las-extra-bytes.js +195 -0
  116. package/dist/las/lib/las-extra-bytes.js.map +1 -0
  117. package/dist/las/lib/las-types.cjs +19 -0
  118. package/dist/las/lib/las-types.cjs.map +7 -0
  119. package/dist/las/lib/las-types.d.ts +194 -0
  120. package/dist/las/lib/las-types.d.ts.map +1 -0
  121. package/dist/las/lib/las-types.js +5 -0
  122. package/dist/las/lib/las-types.js.map +1 -0
  123. package/dist/las/lib/las-waveform.cjs +196 -0
  124. package/dist/las/lib/las-waveform.cjs.map +7 -0
  125. package/dist/las/lib/las-waveform.d.ts +89 -0
  126. package/dist/las/lib/las-waveform.d.ts.map +1 -0
  127. package/dist/las/lib/las-waveform.js +193 -0
  128. package/dist/las/lib/las-waveform.js.map +1 -0
  129. package/dist/las/lib/laz-perf/laslaz-decoder.cjs +382 -0
  130. package/dist/las/lib/laz-perf/laslaz-decoder.cjs.map +7 -0
  131. package/dist/las/lib/laz-perf/laslaz-decoder.d.ts +129 -0
  132. package/dist/las/lib/laz-perf/laslaz-decoder.d.ts.map +1 -0
  133. package/dist/las/lib/laz-perf/laslaz-decoder.js +377 -0
  134. package/dist/las/lib/laz-perf/laslaz-decoder.js.map +1 -0
  135. package/dist/las/lib/laz-perf/parse-las.cjs +270 -0
  136. package/dist/las/lib/laz-perf/parse-las.cjs.map +7 -0
  137. package/dist/las/lib/laz-perf/parse-las.d.ts +24 -0
  138. package/dist/las/lib/laz-perf/parse-las.d.ts.map +1 -0
  139. package/dist/las/lib/laz-perf/parse-las.js +307 -0
  140. package/dist/las/lib/laz-perf/parse-las.js.map +1 -0
  141. package/dist/las/lib/laz-rs-wasm/laslaz-decoder.cjs +631 -0
  142. package/dist/las/lib/laz-rs-wasm/laslaz-decoder.cjs.map +7 -0
  143. package/dist/las/lib/laz-rs-wasm/laslaz-decoder.d.ts +129 -0
  144. package/dist/las/lib/laz-rs-wasm/laslaz-decoder.d.ts.map +1 -0
  145. package/dist/las/lib/laz-rs-wasm/laslaz-decoder.js +414 -0
  146. package/dist/las/lib/laz-rs-wasm/laslaz-decoder.js.map +1 -0
  147. package/dist/las/lib/laz-rs-wasm/parse-las.cjs +270 -0
  148. package/dist/las/lib/laz-rs-wasm/parse-las.cjs.map +7 -0
  149. package/dist/las/lib/laz-rs-wasm/parse-las.d.ts +24 -0
  150. package/dist/las/lib/laz-rs-wasm/parse-las.d.ts.map +1 -0
  151. package/dist/las/lib/laz-rs-wasm/parse-las.js +307 -0
  152. package/dist/las/lib/laz-rs-wasm/parse-las.js.map +1 -0
  153. package/dist/las/lib/typescript/parse-las.cjs +2767 -0
  154. package/dist/las/lib/typescript/parse-las.cjs.map +7 -0
  155. package/dist/las/lib/typescript/parse-las.d.ts +39 -0
  156. package/dist/las/lib/typescript/parse-las.d.ts.map +1 -0
  157. package/dist/las/lib/typescript/parse-las.js +2607 -0
  158. package/dist/las/lib/typescript/parse-las.js.map +1 -0
  159. package/dist/las/libs/laz-perf/laz-perf.cjs +18093 -0
  160. package/dist/las/libs/laz-perf/laz-perf.cjs.map +7 -0
  161. package/dist/las/libs/laz-perf/laz-perf.d.ts +2 -0
  162. package/dist/las/libs/laz-perf/laz-perf.d.ts.map +1 -0
  163. package/dist/las/libs/laz-perf/laz-perf.js +19003 -0
  164. package/dist/las/libs/laz-perf/laz-perf.js.map +1 -0
  165. package/dist/las/libs/laz-rs-wasm/laz_rs_wasm.d.ts +95 -0
  166. package/dist/las/libs/laz-rs-wasm/laz_rs_wasm.d.ts.map +1 -0
  167. package/dist/las/libs/laz-rs-wasm/laz_rs_wasm.js +442 -0
  168. package/dist/las/libs/laz-rs-wasm/laz_rs_wasm.js.map +1 -0
  169. package/dist/las/libs/laz-rs-wasm/laz_rs_wasm_bg.wasm +0 -0
  170. package/dist/las/libs/laz-rs-wasm/laz_rs_wasm_bg.wasm.d.ts +38 -0
  171. package/dist/las/unbundled.cjs +71 -0
  172. package/dist/las/unbundled.cjs.map +7 -0
  173. package/dist/las/unbundled.d.ts +11 -0
  174. package/dist/las/unbundled.d.ts.map +1 -0
  175. package/dist/las/unbundled.js +11 -0
  176. package/dist/las/unbundled.js.map +1 -0
  177. package/dist/las/workers/las-worker.cjs +23 -0
  178. package/dist/las/workers/las-worker.cjs.map +7 -0
  179. package/dist/las/workers/las-worker.d.ts +2 -0
  180. package/dist/las/workers/las-worker.d.ts.map +1 -0
  181. package/dist/las/workers/las-worker.js +23 -0
  182. package/dist/las/workers/las-worker.js.map +1 -0
  183. package/dist/las-copc-loader-types.cjs +51 -0
  184. package/dist/las-copc-loader-types.cjs.map +7 -0
  185. package/dist/las-copc-loader-types.d.ts +2 -0
  186. package/dist/las-copc-loader-types.d.ts.map +1 -0
  187. package/dist/las-copc-loader-types.js +2 -0
  188. package/dist/las-copc-loader-types.js.map +1 -0
  189. package/dist/las-copc-loader.cjs +59 -0
  190. package/dist/las-copc-loader.cjs.map +7 -0
  191. package/dist/las-copc-loader.d.ts +2 -0
  192. package/dist/las-copc-loader.d.ts.map +1 -0
  193. package/dist/las-copc-loader.js +2 -0
  194. package/dist/las-copc-loader.js.map +1 -0
  195. package/dist/las-loader-types.cjs +56 -0
  196. package/dist/las-loader-types.cjs.map +7 -0
  197. package/dist/las-loader-types.d.ts +2 -0
  198. package/dist/las-loader-types.d.ts.map +1 -0
  199. package/dist/las-loader-types.js +2 -0
  200. package/dist/las-loader-types.js.map +1 -0
  201. package/dist/las-loader.cjs +82 -0
  202. package/dist/las-loader.cjs.map +7 -0
  203. package/dist/las-loader.d.ts +2 -0
  204. package/dist/las-loader.d.ts.map +1 -0
  205. package/dist/las-loader.js +2 -0
  206. package/dist/las-loader.js.map +1 -0
  207. package/dist/las-worker.js +23169 -0
  208. package/dist/las-writer.cjs +1056 -0
  209. package/dist/las-writer.cjs.map +7 -0
  210. package/dist/las-writer.d.ts +2 -0
  211. package/dist/las-writer.d.ts.map +1 -0
  212. package/dist/las-writer.js +2 -0
  213. package/dist/las-writer.js.map +1 -0
  214. package/dist/laz-rs-loader-types.cjs +51 -0
  215. package/dist/laz-rs-loader-types.cjs.map +7 -0
  216. package/dist/laz-rs-loader-types.d.ts +2 -0
  217. package/dist/laz-rs-loader-types.d.ts.map +1 -0
  218. package/dist/laz-rs-loader-types.js +2 -0
  219. package/dist/laz-rs-loader-types.js.map +1 -0
  220. package/dist/laz-rs-loader.cjs +274 -0
  221. package/dist/laz-rs-loader.cjs.map +7 -0
  222. package/dist/laz-rs-loader.d.ts +2 -0
  223. package/dist/laz-rs-loader.d.ts.map +1 -0
  224. package/dist/laz-rs-loader.js +2 -0
  225. package/dist/laz-rs-loader.js.map +1 -0
  226. package/dist/lazperf-loader-types.cjs +51 -0
  227. package/dist/lazperf-loader-types.cjs.map +7 -0
  228. package/dist/lazperf-loader-types.d.ts +2 -0
  229. package/dist/lazperf-loader-types.d.ts.map +1 -0
  230. package/dist/lazperf-loader-types.js +2 -0
  231. package/dist/lazperf-loader-types.js.map +1 -0
  232. package/dist/lazperf-loader.cjs +62 -0
  233. package/dist/lazperf-loader.cjs.map +7 -0
  234. package/dist/lazperf-loader.d.ts +2 -0
  235. package/dist/lazperf-loader.d.ts.map +1 -0
  236. package/dist/lazperf-loader.js +2 -0
  237. package/dist/lazperf-loader.js.map +1 -0
  238. package/dist/pcd/bundled.cjs +33 -0
  239. package/dist/pcd/bundled.cjs.map +7 -0
  240. package/dist/pcd/bundled.d.ts +3 -0
  241. package/dist/pcd/bundled.d.ts.map +1 -0
  242. package/dist/pcd/bundled.js +5 -0
  243. package/dist/pcd/bundled.js.map +1 -0
  244. package/dist/pcd/index.cjs +40 -0
  245. package/dist/pcd/index.cjs.map +7 -0
  246. package/dist/pcd/index.d.ts +8 -0
  247. package/dist/pcd/index.d.ts.map +1 -0
  248. package/dist/pcd/index.js +10 -0
  249. package/dist/pcd/index.js.map +1 -0
  250. package/dist/pcd/lib/decompress-lzf.cjs +80 -0
  251. package/dist/pcd/lib/decompress-lzf.cjs.map +7 -0
  252. package/dist/pcd/lib/decompress-lzf.d.ts +7 -0
  253. package/dist/pcd/lib/decompress-lzf.d.ts.map +1 -0
  254. package/dist/pcd/lib/decompress-lzf.js +62 -0
  255. package/dist/pcd/lib/decompress-lzf.js.map +1 -0
  256. package/dist/pcd/lib/get-pcd-schema.cjs +70 -0
  257. package/dist/pcd/lib/get-pcd-schema.cjs.map +7 -0
  258. package/dist/pcd/lib/get-pcd-schema.d.ts +10 -0
  259. package/dist/pcd/lib/get-pcd-schema.d.ts.map +1 -0
  260. package/dist/pcd/lib/get-pcd-schema.js +53 -0
  261. package/dist/pcd/lib/get-pcd-schema.js.map +1 -0
  262. package/dist/pcd/lib/parse-pcd.cjs +354 -0
  263. package/dist/pcd/lib/parse-pcd.cjs.map +7 -0
  264. package/dist/pcd/lib/parse-pcd.d.ts +20 -0
  265. package/dist/pcd/lib/parse-pcd.d.ts.map +1 -0
  266. package/dist/pcd/lib/parse-pcd.js +341 -0
  267. package/dist/pcd/lib/parse-pcd.js.map +1 -0
  268. package/dist/pcd/lib/pcd-types.cjs +19 -0
  269. package/dist/pcd/lib/pcd-types.cjs.map +7 -0
  270. package/dist/pcd/lib/pcd-types.d.ts +31 -0
  271. package/dist/pcd/lib/pcd-types.d.ts.map +1 -0
  272. package/dist/pcd/lib/pcd-types.js +5 -0
  273. package/dist/pcd/lib/pcd-types.js.map +1 -0
  274. package/dist/pcd/pcd-format.cjs +42 -0
  275. package/dist/pcd/pcd-format.cjs.map +7 -0
  276. package/dist/pcd/pcd-format.d.ts +16 -0
  277. package/dist/pcd/pcd-format.d.ts.map +1 -0
  278. package/dist/pcd/pcd-format.js +19 -0
  279. package/dist/pcd/pcd-format.js.map +1 -0
  280. package/dist/pcd/pcd-loader-types.cjs +64 -0
  281. package/dist/pcd/pcd-loader-types.cjs.map +7 -0
  282. package/dist/pcd/pcd-loader-types.d.ts +105 -0
  283. package/dist/pcd/pcd-loader-types.d.ts.map +1 -0
  284. package/dist/pcd/pcd-loader-types.js +35 -0
  285. package/dist/pcd/pcd-loader-types.js.map +1 -0
  286. package/dist/pcd/pcd-loader.cjs +144 -0
  287. package/dist/pcd/pcd-loader.cjs.map +7 -0
  288. package/dist/pcd/pcd-loader.d.ts +44 -0
  289. package/dist/pcd/pcd-loader.d.ts.map +1 -0
  290. package/dist/pcd/pcd-loader.js +128 -0
  291. package/dist/pcd/pcd-loader.js.map +1 -0
  292. package/dist/pcd/pcd-writer.cjs +134 -0
  293. package/dist/pcd/pcd-writer.cjs.map +7 -0
  294. package/dist/pcd/pcd-writer.d.ts +37 -0
  295. package/dist/pcd/pcd-writer.d.ts.map +1 -0
  296. package/dist/pcd/pcd-writer.js +115 -0
  297. package/dist/pcd/pcd-writer.js.map +1 -0
  298. package/dist/pcd/unbundled.cjs +33 -0
  299. package/dist/pcd/unbundled.cjs.map +7 -0
  300. package/dist/pcd/unbundled.d.ts +3 -0
  301. package/dist/pcd/unbundled.d.ts.map +1 -0
  302. package/dist/pcd/unbundled.js +5 -0
  303. package/dist/pcd/unbundled.js.map +1 -0
  304. package/dist/pcd/workers/pcd-worker.cjs +23 -0
  305. package/dist/pcd/workers/pcd-worker.cjs.map +7 -0
  306. package/dist/pcd/workers/pcd-worker.d.ts +2 -0
  307. package/dist/pcd/workers/pcd-worker.d.ts.map +1 -0
  308. package/dist/pcd/workers/pcd-worker.js +23 -0
  309. package/dist/pcd/workers/pcd-worker.js.map +1 -0
  310. package/dist/pcd-loader-types.cjs +66 -0
  311. package/dist/pcd-loader-types.cjs.map +7 -0
  312. package/dist/pcd-loader-types.d.ts +2 -0
  313. package/dist/pcd-loader-types.d.ts.map +1 -0
  314. package/dist/pcd-loader-types.js +2 -0
  315. package/dist/pcd-loader-types.js.map +1 -0
  316. package/dist/pcd-loader.cjs +146 -0
  317. package/dist/pcd-loader.cjs.map +7 -0
  318. package/dist/pcd-loader.d.ts +2 -0
  319. package/dist/pcd-loader.d.ts.map +1 -0
  320. package/dist/pcd-loader.js +2 -0
  321. package/dist/pcd-loader.js.map +1 -0
  322. package/dist/pcd-worker.js +17655 -0
  323. package/dist/pcd-writer.cjs +136 -0
  324. package/dist/pcd-writer.cjs.map +7 -0
  325. package/dist/pcd-writer.d.ts +2 -0
  326. package/dist/pcd-writer.d.ts.map +1 -0
  327. package/dist/pcd-writer.js +2 -0
  328. package/dist/pcd-writer.js.map +1 -0
  329. package/dist/ply/bundled.cjs +33 -0
  330. package/dist/ply/bundled.cjs.map +7 -0
  331. package/dist/ply/bundled.d.ts +3 -0
  332. package/dist/ply/bundled.d.ts.map +1 -0
  333. package/dist/ply/bundled.js +5 -0
  334. package/dist/ply/bundled.js.map +1 -0
  335. package/dist/ply/index.cjs +40 -0
  336. package/dist/ply/index.cjs.map +7 -0
  337. package/dist/ply/index.d.ts +8 -0
  338. package/dist/ply/index.d.ts.map +1 -0
  339. package/dist/ply/index.js +11 -0
  340. package/dist/ply/index.js.map +1 -0
  341. package/dist/ply/lib/get-ply-schema.cjs +126 -0
  342. package/dist/ply/lib/get-ply-schema.cjs.map +7 -0
  343. package/dist/ply/lib/get-ply-schema.d.ts +10 -0
  344. package/dist/ply/lib/get-ply-schema.d.ts.map +1 -0
  345. package/dist/ply/lib/get-ply-schema.js +117 -0
  346. package/dist/ply/lib/get-ply-schema.js.map +1 -0
  347. package/dist/ply/lib/normalize-ply.cjs +96 -0
  348. package/dist/ply/lib/normalize-ply.cjs.map +7 -0
  349. package/dist/ply/lib/normalize-ply.d.ts +8 -0
  350. package/dist/ply/lib/normalize-ply.d.ts.map +1 -0
  351. package/dist/ply/lib/normalize-ply.js +89 -0
  352. package/dist/ply/lib/normalize-ply.js.map +1 -0
  353. package/dist/ply/lib/parse-ply-arrow.cjs +552 -0
  354. package/dist/ply/lib/parse-ply-arrow.cjs.map +7 -0
  355. package/dist/ply/lib/parse-ply-arrow.d.ts +25 -0
  356. package/dist/ply/lib/parse-ply-arrow.d.ts.map +1 -0
  357. package/dist/ply/lib/parse-ply-arrow.js +493 -0
  358. package/dist/ply/lib/parse-ply-arrow.js.map +1 -0
  359. package/dist/ply/lib/parse-ply-in-batches.cjs +607 -0
  360. package/dist/ply/lib/parse-ply-in-batches.cjs.map +7 -0
  361. package/dist/ply/lib/parse-ply-in-batches.d.ts +9 -0
  362. package/dist/ply/lib/parse-ply-in-batches.d.ts.map +1 -0
  363. package/dist/ply/lib/parse-ply-in-batches.js +625 -0
  364. package/dist/ply/lib/parse-ply-in-batches.js.map +1 -0
  365. package/dist/ply/lib/parse-ply.cjs +755 -0
  366. package/dist/ply/lib/parse-ply.cjs.map +7 -0
  367. package/dist/ply/lib/parse-ply.d.ts +69 -0
  368. package/dist/ply/lib/parse-ply.d.ts.map +1 -0
  369. package/dist/ply/lib/parse-ply.js +851 -0
  370. package/dist/ply/lib/parse-ply.js.map +1 -0
  371. package/dist/ply/lib/ply-types.cjs +19 -0
  372. package/dist/ply/lib/ply-types.cjs.map +7 -0
  373. package/dist/ply/lib/ply-types.d.ts +37 -0
  374. package/dist/ply/lib/ply-types.d.ts.map +1 -0
  375. package/dist/ply/lib/ply-types.js +2 -0
  376. package/dist/ply/lib/ply-types.js.map +1 -0
  377. package/dist/ply/ply-format.cjs +43 -0
  378. package/dist/ply/ply-format.cjs.map +7 -0
  379. package/dist/ply/ply-format.d.ts +18 -0
  380. package/dist/ply/ply-format.d.ts.map +1 -0
  381. package/dist/ply/ply-format.js +22 -0
  382. package/dist/ply/ply-format.js.map +1 -0
  383. package/dist/ply/ply-loader-types.cjs +65 -0
  384. package/dist/ply/ply-loader-types.cjs.map +7 -0
  385. package/dist/ply/ply-loader-types.d.ts +111 -0
  386. package/dist/ply/ply-loader-types.d.ts.map +1 -0
  387. package/dist/ply/ply-loader-types.js +38 -0
  388. package/dist/ply/ply-loader-types.js.map +1 -0
  389. package/dist/ply/ply-loader.cjs +134 -0
  390. package/dist/ply/ply-loader.cjs.map +7 -0
  391. package/dist/ply/ply-loader.d.ts +44 -0
  392. package/dist/ply/ply-loader.d.ts.map +1 -0
  393. package/dist/ply/ply-loader.js +121 -0
  394. package/dist/ply/ply-loader.js.map +1 -0
  395. package/dist/ply/ply-writer.cjs +175 -0
  396. package/dist/ply/ply-writer.cjs.map +7 -0
  397. package/dist/ply/ply-writer.d.ts +37 -0
  398. package/dist/ply/ply-writer.d.ts.map +1 -0
  399. package/dist/ply/ply-writer.js +140 -0
  400. package/dist/ply/ply-writer.js.map +1 -0
  401. package/dist/ply/unbundled.cjs +33 -0
  402. package/dist/ply/unbundled.cjs.map +7 -0
  403. package/dist/ply/unbundled.d.ts +3 -0
  404. package/dist/ply/unbundled.d.ts.map +1 -0
  405. package/dist/ply/unbundled.js +5 -0
  406. package/dist/ply/unbundled.js.map +1 -0
  407. package/dist/ply/workers/ply-worker.cjs +34 -0
  408. package/dist/ply/workers/ply-worker.cjs.map +7 -0
  409. package/dist/ply/workers/ply-worker.d.ts +2 -0
  410. package/dist/ply/workers/ply-worker.d.ts.map +1 -0
  411. package/dist/ply/workers/ply-worker.js +37 -0
  412. package/dist/ply/workers/ply-worker.js.map +1 -0
  413. package/dist/ply-loader-types.cjs +67 -0
  414. package/dist/ply-loader-types.cjs.map +7 -0
  415. package/dist/ply-loader-types.d.ts +2 -0
  416. package/dist/ply-loader-types.d.ts.map +1 -0
  417. package/dist/ply-loader-types.js +2 -0
  418. package/dist/ply-loader-types.js.map +1 -0
  419. package/dist/ply-loader.cjs +136 -0
  420. package/dist/ply-loader.cjs.map +7 -0
  421. package/dist/ply-loader.d.ts +2 -0
  422. package/dist/ply-loader.d.ts.map +1 -0
  423. package/dist/ply-loader.js +2 -0
  424. package/dist/ply-loader.js.map +1 -0
  425. package/dist/ply-worker.js +19308 -0
  426. package/dist/ply-writer.cjs +177 -0
  427. package/dist/ply-writer.cjs.map +7 -0
  428. package/dist/ply-writer.d.ts +2 -0
  429. package/dist/ply-writer.d.ts.map +1 -0
  430. package/dist/ply-writer.js +2 -0
  431. package/dist/ply-writer.js.map +1 -0
  432. package/package.json +237 -4
  433. package/src/index.ts +19 -0
  434. package/src/las/bundled.ts +39 -0
  435. package/src/las/index.ts +70 -0
  436. package/src/las/las-copc-loader-types.ts +18 -0
  437. package/src/las/las-copc-loader.ts +61 -0
  438. package/src/las/las-format.ts +21 -0
  439. package/src/las/las-loader-shared.ts +124 -0
  440. package/src/las/las-loader-types.ts +27 -0
  441. package/src/las/las-loader.ts +109 -0
  442. package/src/las/las-writer.ts +1473 -0
  443. package/src/las/laz-rs-loader-types.ts +21 -0
  444. package/src/las/laz-rs-loader.ts +53 -0
  445. package/src/las/lazperf-loader-types.ts +22 -0
  446. package/src/las/lazperf-loader.ts +65 -0
  447. package/src/las/lib/LASZIP-LICENSE +202 -0
  448. package/src/las/lib/LASZIP-NOTICE +4 -0
  449. package/src/las/lib/copc/parse-las.ts +265 -0
  450. package/src/las/lib/get-las-schema.ts +50 -0
  451. package/src/las/lib/las-compatibility.ts +178 -0
  452. package/src/las/lib/las-extra-bytes.ts +274 -0
  453. package/src/las/lib/las-types.ts +206 -0
  454. package/src/las/lib/las-waveform.ts +292 -0
  455. package/src/las/lib/laz-perf/laslaz-decoder.ts +443 -0
  456. package/src/las/lib/laz-perf/parse-las.ts +412 -0
  457. package/src/las/lib/laz-rs-wasm/laslaz-decoder.ts +488 -0
  458. package/src/las/lib/laz-rs-wasm/parse-las.ts +412 -0
  459. package/src/las/lib/typescript/parse-las.ts +3730 -0
  460. package/src/las/libs/laz-perf/laz-perf.ts +20615 -0
  461. package/src/las/libs/laz-rs-wasm/README.md +5 -0
  462. package/src/las/libs/laz-rs-wasm/laz_rs_wasm.d.ts +89 -0
  463. package/src/las/libs/laz-rs-wasm/laz_rs_wasm.js +497 -0
  464. package/src/las/libs/laz-rs-wasm/laz_rs_wasm_bg.wasm +0 -0
  465. package/src/las/libs/laz-rs-wasm/laz_rs_wasm_bg.wasm.d.ts +38 -0
  466. package/src/las/libs/laz-rs-wasm/package.json +25 -0
  467. package/src/las/unbundled.ts +38 -0
  468. package/src/las/workers/las-worker.ts +24 -0
  469. package/src/las-copc-loader-types.ts +1 -0
  470. package/src/las-copc-loader.ts +1 -0
  471. package/src/las-loader-types.ts +1 -0
  472. package/src/las-loader.ts +1 -0
  473. package/src/las-writer.ts +1 -0
  474. package/src/laz-rs-loader-types.ts +1 -0
  475. package/src/laz-rs-loader.ts +1 -0
  476. package/src/lazperf-loader-types.ts +1 -0
  477. package/src/lazperf-loader.ts +1 -0
  478. package/src/pcd/bundled.ts +9 -0
  479. package/src/pcd/index.ts +13 -0
  480. package/src/pcd/lib/decompress-lzf.ts +68 -0
  481. package/src/pcd/lib/get-pcd-schema.ts +66 -0
  482. package/src/pcd/lib/parse-pcd.ts +474 -0
  483. package/src/pcd/lib/pcd-types.ts +34 -0
  484. package/src/pcd/pcd-format.ts +21 -0
  485. package/src/pcd/pcd-loader-types.ts +53 -0
  486. package/src/pcd/pcd-loader.ts +179 -0
  487. package/src/pcd/pcd-writer.ts +152 -0
  488. package/src/pcd/unbundled.ts +6 -0
  489. package/src/pcd/workers/pcd-worker.ts +24 -0
  490. package/src/pcd-loader-types.ts +1 -0
  491. package/src/pcd-loader.ts +1 -0
  492. package/src/pcd-writer.ts +1 -0
  493. package/src/ply/bundled.ts +9 -0
  494. package/src/ply/index.ts +15 -0
  495. package/src/ply/lib/get-ply-schema.ts +133 -0
  496. package/src/ply/lib/normalize-ply.ts +110 -0
  497. package/src/ply/lib/parse-ply-arrow.ts +676 -0
  498. package/src/ply/lib/parse-ply-in-batches.ts +827 -0
  499. package/src/ply/lib/parse-ply.ts +1081 -0
  500. package/src/ply/lib/ply-types.ts +47 -0
  501. package/src/ply/ply-format.ts +24 -0
  502. package/src/ply/ply-loader-types.ts +61 -0
  503. package/src/ply/ply-loader.ts +185 -0
  504. package/src/ply/ply-writer.ts +215 -0
  505. package/src/ply/unbundled.ts +6 -0
  506. package/src/ply/workers/ply-worker.ts +40 -0
  507. package/src/ply-loader-types.ts +1 -0
  508. package/src/ply-loader.ts +1 -0
  509. package/src/ply-writer.ts +1 -0
  510. package/third-party-licenses/plasio-LICENSE +21 -0
  511. package/third-party-licenses/three-LICENSE +21 -0
@@ -0,0 +1,3730 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {MeshArrowTable, MeshAttributes} from '@loaders.gl/core/schema';
6
+ import {makeMeshArrowTable, type MeshArrowAttributes} from '@loaders.gl/core/schema-utils';
7
+ import {
8
+ BinaryChunkReader,
9
+ createLAZChunkDecoder,
10
+ createLAZChunkDecoderCursor,
11
+ decodeLAZChunk,
12
+ decodeLAZChunkTable,
13
+ getLAZChunkDeclaredByteLength,
14
+ getLAZChunkHeaderByteLength,
15
+ NeedsMoreData
16
+ } from '@loaders.gl/core/loader-utils';
17
+ import type {
18
+ LAZChunkMetadata,
19
+ LAZChunkTableEntry,
20
+ LAZPointDataTarget
21
+ } from '@loaders.gl/core/loader-utils';
22
+ import type {LASLoaderOptions} from '../../las-loader-types';
23
+ import {getLASSchema} from '../get-las-schema';
24
+ import {
25
+ getLASCompatibilityLayout,
26
+ populateLASCompatibilityPoint,
27
+ type LASCompatibilityLayout
28
+ } from '../las-compatibility';
29
+ import {
30
+ createLASTypedExtraBytesAttributes,
31
+ createLASTypedExtraBytesValue,
32
+ parseLASExtraBytes,
33
+ populateLASTypedExtraBytesComponent,
34
+ type LASTypedExtraBytesAttribute
35
+ } from '../las-extra-bytes';
36
+ import type {
37
+ LASExtendedVariableLengthRecord,
38
+ LASGeoTIFFKey,
39
+ LASHeader,
40
+ LASMetadata,
41
+ LASVariableLengthRecord,
42
+ LASWaveformPacketDescriptor
43
+ } from '../las-types';
44
+
45
+ /** Arrow table returned by the TypeScript LAS parser with LAS loader metadata attached. */
46
+ export type LASArrowTable = MeshArrowTable & {
47
+ /** Loader identifier preserved for mesh compatibility conversion. */
48
+ loader: 'las';
49
+ /** Parsed LAS header and metadata for the batch. */
50
+ loaderData: LASHeader;
51
+ /** Batch parse progress in the range [0, 1]. */
52
+ progress?: number;
53
+ };
54
+
55
+ type LASDecodedChunk = {
56
+ arrayBuffer: ArrayBufferLike;
57
+ header: LASHeader;
58
+ };
59
+
60
+ /** Metadata needed to decode one standalone LAZ chunk into LAS Arrow columns. */
61
+ export type LAZChunkArrowTableMetadata = LAZChunkMetadata & {
62
+ /** LAS coordinate scales applied to encoded XYZ integers. */
63
+ scale: [number, number, number];
64
+ /** LAS coordinate offsets applied to encoded XYZ integers. */
65
+ offset: [number, number, number];
66
+ };
67
+
68
+ const DEFAULT_BATCH_SIZE = 1000 * 100;
69
+ const LASF_SIGNATURE = 0x4653414c;
70
+ const LAS_14_HEADER_LENGTH = 375;
71
+ const COMPRESSED_POINT_FORMAT_MASK = 0x80;
72
+ const POINT_FORMAT_MASK = 0x3f;
73
+ const LASZIP_USER_ID = 'laszip encoded';
74
+ const LASZIP_RECORD_ID = 22204;
75
+ const VARIABLE_CHUNK_SIZE = 0xffffffff;
76
+ const LAZ_CHUNK_TABLE_POINTER_LENGTH = 8;
77
+ /** Read-ahead block that amortizes legacy cursor feeds while keeping retained input bounded. */
78
+ const LEGACY_LAZ_FEED_BLOCK_SIZE = 64 * 1024;
79
+
80
+ /** One compressed field item declared by the LASzip VLR. */
81
+ type LASZipItem = {
82
+ /** LASzip item type identifier. */
83
+ type: number;
84
+ /** Uncompressed item width in bytes. */
85
+ size: number;
86
+ /** LASzip codec version for this item. */
87
+ version: number;
88
+ };
89
+
90
+ type LASZipVLR = {
91
+ compressor: number;
92
+ /** Entropy coder identifier declared by LASzip. */
93
+ coder: number;
94
+ chunkSize: number;
95
+ variableChunks: boolean;
96
+ /** Ordered compressed field descriptors. */
97
+ items: LASZipItem[];
98
+ point14ItemVersion: 2 | 3 | 4 | null;
99
+ rgb14ItemVersion: 2 | 3 | 4 | null;
100
+ wavePacket13ItemVersion: 1 | null;
101
+ wavePacketItemVersion: 3 | 4 | null;
102
+ byte14ItemVersion: 2 | 3 | 4 | null;
103
+ };
104
+
105
+ /** Build chunk metadata with the item versions declared by the LASzip VLR. */
106
+ function createLAZChunkMetadata(
107
+ header: LASHeader,
108
+ laszip: LASZipVLR,
109
+ pointCount: number
110
+ ): LAZChunkMetadata {
111
+ return {
112
+ pointDataRecordFormat: header.pointsFormatId,
113
+ pointDataRecordLength: header.pointsStructSize,
114
+ pointCount,
115
+ point10ItemVersion: laszip.items.find(item => item.type === 6)?.version as 1 | 2 | undefined,
116
+ gpsTime11ItemVersion: laszip.items.find(item => item.type === 7)?.version as 1 | 2 | undefined,
117
+ rgb12ItemVersion: laszip.items.find(item => item.type === 8)?.version as 1 | 2 | undefined,
118
+ byteItemVersion: laszip.items.find(item => item.type === 0)?.version as 1 | 2 | undefined,
119
+ point14ItemVersion: laszip.point14ItemVersion ?? undefined,
120
+ rgb14ItemVersion: laszip.rgb14ItemVersion ?? undefined,
121
+ wavePacketItemVersion: laszip.wavePacketItemVersion ?? undefined,
122
+ byte14ItemVersion: laszip.byte14ItemVersion ?? undefined
123
+ };
124
+ }
125
+
126
+ type RawPointBatchState = {
127
+ rawBatch: Uint8Array;
128
+ batchPointCount: number;
129
+ totalRead: number;
130
+ rawBatchAllocations: number;
131
+ stats?: LAZStreamingDecodeStats;
132
+ };
133
+
134
+ type PointDataBatchState = {
135
+ batchCapacity: number;
136
+ positions: Float32Array | Float64Array;
137
+ colors: Uint8Array | null;
138
+ rawColors: Uint16Array | null;
139
+ intensities: Uint16Array | null;
140
+ classifications: Uint8Array | null;
141
+ syntheticFlags: Uint8Array | null;
142
+ keyPointFlags: Uint8Array | null;
143
+ withheldFlags: Uint8Array | null;
144
+ overlapFlags: Uint8Array | null;
145
+ gpsTimes: Float64Array | null;
146
+ nir: Uint16Array | null;
147
+ scanAngles: Int16Array | null;
148
+ userData: Uint8Array | null;
149
+ pointSourceIds: Uint16Array | null;
150
+ returnNumbers: Uint8Array | null;
151
+ numberOfReturns: Uint8Array | null;
152
+ scannerChannels: Uint8Array | null;
153
+ scanDirectionFlags: Uint8Array | null;
154
+ edgeOfFlightLines: Uint8Array | null;
155
+ waveforms: Uint8Array | null;
156
+ extraBytes: Uint8Array | null;
157
+ typedExtraBytes: LASTypedExtraBytesAttribute[] | null;
158
+ /** Packed raw source used to project selected typed Extra Bytes without raw point records. */
159
+ typedExtraBytesSource: Uint8Array | null;
160
+ /** Validated modern field reconstruction layout when enabled. */
161
+ compatibilityLayout: LASCompatibilityLayout | null;
162
+ /** Internal packed source when compatibility reconstruction alone requires Extra Bytes. */
163
+ compatibilityExtraBytesSource: Uint8Array | null;
164
+ target: LAZPointDataTarget;
165
+ batchPointCount: number;
166
+ totalRead: number;
167
+ };
168
+
169
+ type LAZStreamingDecodeStats = {
170
+ copiedBytes: number;
171
+ chunkConcatenations: number;
172
+ rawBatchAllocations: number;
173
+ decodedChunkAllocations: number;
174
+ };
175
+
176
+ type LAZChunkByteLengthMetadata = {
177
+ pointDataRecordFormat: number;
178
+ pointDataRecordLength: number;
179
+ pointCount: number;
180
+ };
181
+
182
+ /** Parse LAS data with the TypeScript loader variant. */
183
+ export function parseLAS(arrayBuffer: ArrayBuffer, options: LASLoaderOptions = {}): LASArrowTable {
184
+ const header = parseLASHeader(arrayBuffer);
185
+ if (header.isCompressed) {
186
+ const bytes = new Uint8Array(arrayBuffer);
187
+ const laszip = parseLASZipVLR(bytes, header);
188
+ validateTypeScriptLAZSupport(header, laszip);
189
+ if (header.pointsFormatId <= 5 || (header.pointsFormatId >= 6 && header.pointsFormatId <= 10)) {
190
+ return parseCompleteLAZFileToArrowTable(bytes, header, laszip, options);
191
+ }
192
+ const rawPointData = decodeLAZFileToRawPointData(arrayBuffer, header, laszip);
193
+ const totalPointCount = header.pointsCount;
194
+ return parseLASArrowTableBatch(
195
+ rawPointData,
196
+ {
197
+ ...header,
198
+ pointsOffset: 0,
199
+ pointsCount: totalPointCount,
200
+ totalToRead: totalPointCount,
201
+ totalRead: totalPointCount
202
+ },
203
+ options
204
+ );
205
+ }
206
+
207
+ const totalPointCount = header.pointsCount;
208
+ return parseLASArrowTableBatch(
209
+ arrayBuffer,
210
+ {...header, totalToRead: totalPointCount, totalRead: totalPointCount},
211
+ options
212
+ );
213
+ }
214
+
215
+ /** Decode one complete modern LAZ chunk directly into selected Arrow columns. */
216
+ export function decodeLAZChunkToArrowTable(
217
+ compressed: ArrayBuffer | ArrayBufferView,
218
+ metadata: LAZChunkArrowTableMetadata,
219
+ options: LASLoaderOptions
220
+ ): LASArrowTable {
221
+ if (metadata.pointDataRecordFormat < 6 || metadata.pointDataRecordFormat > 8) {
222
+ throw new Error(
223
+ `LASLoader: standalone Arrow chunk decode supports PDRF 6-8; received ${metadata.pointDataRecordFormat}`
224
+ );
225
+ }
226
+ const pointCount = metadata.pointCount;
227
+ const header: LASHeader = {
228
+ pointsOffset: 0,
229
+ pointsFormatId: metadata.pointDataRecordFormat,
230
+ pointsStructSize: metadata.pointDataRecordLength,
231
+ pointsCount: pointCount,
232
+ scale: metadata.scale,
233
+ offset: metadata.offset,
234
+ maxs: [0, 0, 0],
235
+ mins: [0, 0, 0],
236
+ totalToRead: pointCount,
237
+ totalRead: pointCount,
238
+ hasColor: hasPointColor(metadata.pointDataRecordFormat),
239
+ versionAsString: '1.4',
240
+ isCompressed: true,
241
+ headerSize: LAS_14_HEADER_LENGTH,
242
+ vlrCount: 0
243
+ };
244
+ const state = createPointDataBatchState(pointCount, header, options);
245
+ if (state.waveforms || state.extraBytes || state.typedExtraBytes) {
246
+ throw new Error('LASLoader: standalone Arrow chunk decode only supports direct LAZ columns');
247
+ }
248
+ const cursor = createLAZChunkDecoderCursor(compressed, metadata);
249
+ const decodedPointCount = cursor.decodeIntoPointData(state.target, pointCount);
250
+ if (decodedPointCount !== pointCount) {
251
+ throw new Error(
252
+ `LASLoader: standalone LAZ chunk produced ${decodedPointCount} points; expected ${pointCount}`
253
+ );
254
+ }
255
+ return makePointDataStateArrowTable(header, state, pointCount, options, true);
256
+ }
257
+
258
+ /** Decode one complete supported LAZ file directly into its represented Arrow columns. */
259
+ function parseCompleteLAZFileToArrowTable(
260
+ bytes: Uint8Array,
261
+ header: LASHeader,
262
+ laszip: LASZipVLR,
263
+ options: LASLoaderOptions
264
+ ): LASArrowTable {
265
+ const pointCount = header.pointsCount;
266
+ const state = createPointDataBatchState(pointCount, header, options);
267
+ const chunkTable = readLAZFileChunks(bytes, header, laszip, options);
268
+ let decodedPointCount = 0;
269
+ let byteOffset =
270
+ header.pointsOffset + (laszip.compressor === 1 ? 0 : LAZ_CHUNK_TABLE_POINTER_LENGTH);
271
+
272
+ for (const chunk of chunkTable) {
273
+ decodeCompleteLAZChunkToPointData(
274
+ bytes,
275
+ byteOffset,
276
+ chunk.byteLength,
277
+ chunk.pointCount,
278
+ header,
279
+ laszip,
280
+ state,
281
+ decodedPointCount
282
+ );
283
+ byteOffset += chunk.byteLength;
284
+ decodedPointCount += chunk.pointCount;
285
+ }
286
+
287
+ if (decodedPointCount !== pointCount) {
288
+ throw new Error(`LASLoader: decoded ${decodedPointCount} LAZ points; expected ${pointCount}`);
289
+ }
290
+ return makePointDataStateArrowTable(header, state, pointCount, options);
291
+ }
292
+
293
+ /** Convert one fully populated point-data state into its Arrow table. */
294
+ function makePointDataStateArrowTable(
295
+ header: LASHeader,
296
+ state: PointDataBatchState,
297
+ pointCount: number,
298
+ options: LASLoaderOptions,
299
+ preserveRawColors: boolean = false
300
+ ): LASArrowTable {
301
+ const colors =
302
+ preserveRawColors && state.rawColors
303
+ ? state.rawColors
304
+ : state.colors
305
+ ? state.colors
306
+ : state.rawColors
307
+ ? convertRawColorsToUint8(state.rawColors, pointCount, options)
308
+ : null;
309
+ return makeLASArrowTableFromAttributes(
310
+ {...header, pointsOffset: 0, pointsCount: pointCount, totalRead: pointCount},
311
+ state.positions,
312
+ colors,
313
+ state.intensities,
314
+ state.classifications,
315
+ state.syntheticFlags,
316
+ state.keyPointFlags,
317
+ state.withheldFlags,
318
+ state.overlapFlags,
319
+ state.gpsTimes,
320
+ state.nir,
321
+ state.scanAngles,
322
+ state.userData,
323
+ state.pointSourceIds,
324
+ state.returnNumbers,
325
+ state.numberOfReturns,
326
+ state.scannerChannels,
327
+ state.scanDirectionFlags,
328
+ state.edgeOfFlightLines,
329
+ state.waveforms,
330
+ state.extraBytes,
331
+ state.typedExtraBytes
332
+ );
333
+ }
334
+
335
+ /** Decode one complete supported LAZ chunk into preallocated Arrow column buffers. */
336
+ function decodeCompleteLAZChunkToPointData(
337
+ bytes: Uint8Array,
338
+ byteOffset: number,
339
+ byteLength: number,
340
+ pointCount: number,
341
+ header: LASHeader,
342
+ laszip: LASZipVLR,
343
+ state: PointDataBatchState,
344
+ targetPointOffset: number
345
+ ): void {
346
+ const compressed = bytes.subarray(byteOffset, byteOffset + byteLength);
347
+ if (compressed.byteLength !== byteLength) {
348
+ throw new NeedsMoreData('LASLoader: truncated LAZ chunk');
349
+ }
350
+ const cursor = createLAZChunkDecoderCursor(
351
+ compressed,
352
+ createLAZChunkMetadata(header, laszip, pointCount)
353
+ );
354
+ state.target.pointOffset = targetPointOffset;
355
+ const decodedPointCount = cursor.decodeIntoPointData(state.target, pointCount);
356
+ if (decodedPointCount !== pointCount) {
357
+ throw new Error(
358
+ `LASLoader: decoded ${decodedPointCount} points from a ${pointCount}-point LAZ chunk`
359
+ );
360
+ }
361
+ populateDecodedTypedExtraBytes(state, targetPointOffset, pointCount);
362
+ }
363
+
364
+ /** Parse LAS data from an incoming byte iterator into point batches. */
365
+ export async function* parseLASInBatches(
366
+ arrayBufferIterator:
367
+ | AsyncIterable<ArrayBufferLike | ArrayBufferView>
368
+ | Iterable<ArrayBufferLike | ArrayBufferView>,
369
+ options: LASLoaderOptions = {}
370
+ ): AsyncIterable<LASArrowTable> {
371
+ const batchSize = getBatchSize(options);
372
+ const inputIterator = toAsyncIterator(arrayBufferIterator);
373
+ let pending: Uint8Array<ArrayBufferLike> = new Uint8Array(0);
374
+ let absoluteOffset = 0;
375
+ let header: LASHeader | null = null;
376
+ let sourcePointIndex = 0;
377
+ let totalRead = 0;
378
+
379
+ while (true) {
380
+ const next = await inputIterator.next();
381
+ if (next.done) {
382
+ break;
383
+ }
384
+ const chunk = next.value;
385
+ pending = concatenateUint8Arrays(pending, toUint8Array(chunk));
386
+ if (!header) {
387
+ header = tryParseHeader(pending, absoluteOffset);
388
+ if (header) {
389
+ header.totalToRead = header.pointsCount;
390
+ }
391
+ }
392
+ if (!header) {
393
+ continue;
394
+ }
395
+ if (header.isCompressed) {
396
+ yield* parseLAZInBatches(pending, inputIterator, header, options);
397
+ return;
398
+ }
399
+
400
+ for (const batch of readAvailablePointBatches(
401
+ pending,
402
+ absoluteOffset,
403
+ header,
404
+ sourcePointIndex,
405
+ totalRead,
406
+ batchSize
407
+ )) {
408
+ sourcePointIndex = batch.sourcePointIndex;
409
+ totalRead = batch.totalRead;
410
+ pending = batch.pending;
411
+ absoluteOffset = batch.absoluteOffset;
412
+ yield parseLASArrowTableBatch(batch.arrayBuffer, batch.header, options);
413
+ }
414
+ }
415
+
416
+ if (!header) {
417
+ throw new Error('LASLoader: incomplete LAS header');
418
+ }
419
+ if (totalRead < header.totalToRead) {
420
+ throw new Error('LASLoader: truncated LAS point data');
421
+ }
422
+ }
423
+
424
+ /** Decode LAS data into raw point chunks. */
425
+ export function* parseLASChunkedIterator(
426
+ arrayBuffer: ArrayBuffer,
427
+ batchSize: number = DEFAULT_BATCH_SIZE,
428
+ options: LASLoaderOptions = {}
429
+ ): Iterable<LASDecodedChunk> {
430
+ const header = parseLASHeader(arrayBuffer);
431
+ if (header.isCompressed) {
432
+ yield* parseLAZChunkedIterator(arrayBuffer, header, batchSize, undefined, options);
433
+ return;
434
+ }
435
+
436
+ const totalToRead = header.pointsCount;
437
+ let sourcePointIndex = 0;
438
+ let totalRead = 0;
439
+
440
+ while (sourcePointIndex < header.pointsCount && totalRead < totalToRead) {
441
+ const batchPointCount = Math.min(batchSize, header.pointsCount - sourcePointIndex);
442
+ const start = header.pointsOffset + sourcePointIndex * header.pointsStructSize;
443
+ const end = start + batchPointCount * header.pointsStructSize;
444
+ sourcePointIndex += batchPointCount;
445
+ totalRead += batchPointCount;
446
+ const batchHeader = {
447
+ ...header,
448
+ pointsOffset: 0,
449
+ pointsCount: batchPointCount,
450
+ totalToRead,
451
+ totalRead
452
+ };
453
+ yield {
454
+ arrayBuffer: arrayBuffer.slice(start, end),
455
+ header: batchHeader
456
+ };
457
+ }
458
+ }
459
+
460
+ /** Decode compressed LAZ file data from an incoming byte iterator into raw point chunks. */
461
+ export async function* decodeLAZFileInBatches(
462
+ arrayBufferIterator:
463
+ | AsyncIterable<ArrayBufferLike | ArrayBufferView>
464
+ | Iterable<ArrayBufferLike | ArrayBufferView>,
465
+ options: LASLoaderOptions = {}
466
+ ): AsyncIterable<LASDecodedChunk> {
467
+ const inputIterator = toAsyncIterator(arrayBufferIterator);
468
+ let pending: Uint8Array<ArrayBufferLike> = new Uint8Array(0);
469
+ let header: LASHeader | null = null;
470
+
471
+ while (!header) {
472
+ const next = await inputIterator.next();
473
+ if (next.done) {
474
+ throw new Error('LASLoader: incomplete LAS header');
475
+ }
476
+ pending = concatenateUint8Arrays(pending, toUint8Array(next.value));
477
+ header = tryParseHeader(pending, 0);
478
+ }
479
+ if (!header.isCompressed) {
480
+ throw new Error('LASLoader: decodeLAZFileInBatches requires compressed LAZ input');
481
+ }
482
+
483
+ yield* decodePendingLAZFileInBatches(pending, inputIterator, header, options);
484
+ }
485
+
486
+ async function* decodePendingLAZFileInBatches(
487
+ initialPending: Uint8Array<ArrayBufferLike>,
488
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
489
+ header: LASHeader,
490
+ options: LASLoaderOptions = {}
491
+ ): AsyncIterable<LASDecodedChunk> {
492
+ const {pending, laszip} = await readLASZipVLRFromInput(initialPending, inputIterator, header);
493
+ validateTypeScriptLAZSupport(header, laszip);
494
+
495
+ yield* decodeLAZFileWithParsedVLRInBatches(pending, inputIterator, header, laszip, options);
496
+ }
497
+
498
+ async function* decodeLAZFileWithParsedVLRInBatches(
499
+ initialPending: Uint8Array<ArrayBufferLike>,
500
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
501
+ header: LASHeader,
502
+ laszip: LASZipVLR,
503
+ options: LASLoaderOptions
504
+ ): AsyncIterable<LASDecodedChunk> {
505
+ if (laszip.variableChunks) {
506
+ const bytes = await collectRemainingInputBytes(initialPending, inputIterator);
507
+ yield* decodeLAZFileFromCompleteBytes(bytes, header, laszip, options);
508
+ return;
509
+ }
510
+
511
+ if (header.pointsFormatId <= 5) {
512
+ yield* decodePendingFixedLegacyLAZFileInBatches(
513
+ initialPending,
514
+ inputIterator,
515
+ header,
516
+ laszip,
517
+ options
518
+ );
519
+ return;
520
+ }
521
+
522
+ const outputHeader = {...header, totalToRead: header.pointsCount};
523
+ const batchSize = getBatchSize(options);
524
+ const state = createRawPointBatchState(
525
+ batchSize,
526
+ header.pointsStructSize,
527
+ getLAZStreamingDecodeStats(options)
528
+ );
529
+ let sourcePointIndex = 0;
530
+ const chunkByteLengths: number[] = [];
531
+ const reader = new BinaryChunkReader();
532
+ reader.write(initialPending);
533
+
534
+ await readUntilAvailable(
535
+ reader,
536
+ inputIterator,
537
+ header.pointsOffset + LAZ_CHUNK_TABLE_POINTER_LENGTH,
538
+ 'LASLoader: incomplete compressed LAZ point data'
539
+ );
540
+ reader.skip(header.pointsOffset);
541
+ const chunkTableOffset = readStreamedLAZChunkTableOffset(
542
+ reader.getDataView(LAZ_CHUNK_TABLE_POINTER_LENGTH)!
543
+ );
544
+
545
+ while (sourcePointIndex < header.pointsCount) {
546
+ const chunkPointCount = Math.min(laszip.chunkSize, header.pointsCount - sourcePointIndex);
547
+ const metadata = createLAZChunkMetadata(header, laszip, chunkPointCount);
548
+ const chunkByteLength = await readLAZChunkByteLengthFromReader(reader, inputIterator, metadata);
549
+ chunkByteLengths.push(chunkByteLength);
550
+ recordReadBytesStats(reader, chunkByteLength, state.stats);
551
+ const compressedChunk = reader.readBytes(chunkByteLength);
552
+ if (options.las?.recoverMissingChunkTable && chunkTableOffset === header.pointsOffset) {
553
+ validateRecoveredLayeredPointCount(compressedChunk, metadata);
554
+ }
555
+ for (const batch of appendDecodedLAZChunk(compressedChunk, metadata, outputHeader, state)) {
556
+ yield batch;
557
+ }
558
+
559
+ sourcePointIndex += chunkPointCount;
560
+ }
561
+
562
+ await validateStreamedFixedLAZChunkTable(
563
+ reader,
564
+ inputIterator,
565
+ header,
566
+ laszip,
567
+ chunkTableOffset,
568
+ chunkByteLengths,
569
+ options
570
+ );
571
+
572
+ const finalBatch = flushRawPointBatch(outputHeader, state);
573
+ if (finalBatch) {
574
+ yield finalBatch;
575
+ }
576
+ }
577
+
578
+ async function* decodePendingFixedLegacyLAZFileInBatches(
579
+ initialPending: Uint8Array<ArrayBufferLike>,
580
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
581
+ header: LASHeader,
582
+ laszip: LASZipVLR,
583
+ options: LASLoaderOptions = {}
584
+ ): AsyncIterable<LASDecodedChunk> {
585
+ const outputHeader = {...header, totalToRead: header.pointsCount};
586
+ const batchSize = getBatchSize(options);
587
+ const state = createRawPointBatchState(
588
+ batchSize,
589
+ header.pointsStructSize,
590
+ getLAZStreamingDecodeStats(options)
591
+ );
592
+ const chunkByteLengths: number[] = [];
593
+ const reader = new BinaryChunkReader();
594
+ reader.write(initialPending);
595
+
596
+ await readUntilAvailable(
597
+ reader,
598
+ inputIterator,
599
+ header.pointsOffset + (laszip.compressor === 1 ? 0 : LAZ_CHUNK_TABLE_POINTER_LENGTH),
600
+ 'LASLoader: incomplete compressed LAZ point data'
601
+ );
602
+ reader.skip(header.pointsOffset);
603
+ const chunkTableOffset =
604
+ laszip.compressor === 1
605
+ ? 0
606
+ : readStreamedLAZChunkTableOffset(reader.getDataView(LAZ_CHUNK_TABLE_POINTER_LENGTH)!);
607
+
608
+ let sourcePointIndex = 0;
609
+ while (sourcePointIndex < header.pointsCount) {
610
+ const chunkPointCount =
611
+ laszip.compressor === 1
612
+ ? header.pointsCount
613
+ : Math.min(laszip.chunkSize, header.pointsCount - sourcePointIndex);
614
+ const metadata = createLAZChunkMetadata(header, laszip, chunkPointCount);
615
+ const cursor = createLAZChunkDecoderCursor(new Uint8Array(0), metadata);
616
+ let fedByteLength = 0;
617
+ let consumedByteLength = 0;
618
+ let inputDone = false;
619
+
620
+ while (cursor.remainingPointCount > 0) {
621
+ const availableByteLength = reader.getAvailableByteLength();
622
+ const requestedByteLength = getLegacyLAZFeedByteLength(
623
+ availableByteLength,
624
+ fedByteLength,
625
+ cursor.requiredInputByteLength - consumedByteLength
626
+ );
627
+ if (requestedByteLength > fedByteLength) {
628
+ const checkpoint = reader.checkpoint();
629
+ reader.skip(fedByteLength);
630
+ const addedByteLength = requestedByteLength - fedByteLength;
631
+ recordReadBytesStats(reader, addedByteLength, state.stats);
632
+ cursor.feed(reader.readBytes(addedByteLength));
633
+ reader.restore(checkpoint);
634
+ fedByteLength = requestedByteLength;
635
+ }
636
+
637
+ const availableBatchPointCount = batchSize - state.batchPointCount;
638
+ const decodedPointCount = cursor.decodeAvailableInto(
639
+ state.rawBatch,
640
+ state.batchPointCount * header.pointsStructSize,
641
+ availableBatchPointCount,
642
+ inputDone
643
+ );
644
+ if (decodedPointCount > 0) {
645
+ if (laszip.compressor === 1) {
646
+ const decodedByteLength = cursor.compressedByteOffset - consumedByteLength;
647
+ reader.skip(decodedByteLength);
648
+ fedByteLength -= decodedByteLength;
649
+ consumedByteLength = cursor.compressedByteOffset;
650
+ cursor.discardConsumedInput();
651
+ }
652
+ state.batchPointCount += decodedPointCount;
653
+ if (state.batchPointCount === batchSize) {
654
+ const batch = flushRawPointBatch(outputHeader, state);
655
+ if (batch) {
656
+ yield batch;
657
+ }
658
+ }
659
+ continue;
660
+ }
661
+
662
+ if (inputDone) {
663
+ throw new NeedsMoreData(
664
+ `LASLoader: truncated legacy LAZ chunk after ${chunkPointCount - cursor.remainingPointCount} of ${chunkPointCount} points with ${availableByteLength} bytes available`
665
+ );
666
+ }
667
+ const next = await inputIterator.next();
668
+ if (next.done) {
669
+ inputDone = true;
670
+ } else {
671
+ reader.write(next.value);
672
+ }
673
+ }
674
+
675
+ reader.skip(cursor.compressedByteOffset - consumedByteLength);
676
+ chunkByteLengths.push(cursor.compressedByteOffset);
677
+
678
+ sourcePointIndex += chunkPointCount;
679
+ }
680
+
681
+ if (laszip.compressor !== 1) {
682
+ await validateStreamedFixedLAZChunkTable(
683
+ reader,
684
+ inputIterator,
685
+ header,
686
+ laszip,
687
+ chunkTableOffset,
688
+ chunkByteLengths,
689
+ options
690
+ );
691
+ }
692
+
693
+ const finalBatch = flushRawPointBatch(outputHeader, state);
694
+ if (finalBatch) {
695
+ yield finalBatch;
696
+ }
697
+ }
698
+
699
+ /**
700
+ * Return the queued prefix length to expose to a legacy LAZ cursor.
701
+ *
702
+ * Legacy arithmetic decoding can request input one point at a time. Feeding only that minimum
703
+ * makes the streaming path repeat reader checkpoints and tiny copies for every point. Growing
704
+ * the exposed prefix in fixed blocks amortizes that work while limiting speculative read-ahead
705
+ * to one block beyond the cursor requirement.
706
+ */
707
+ function getLegacyLAZFeedByteLength(
708
+ availableByteLength: number,
709
+ fedByteLength: number,
710
+ requiredInputByteLength: number
711
+ ): number {
712
+ return Math.min(
713
+ availableByteLength,
714
+ Math.max(requiredInputByteLength, fedByteLength + LEGACY_LAZ_FEED_BLOCK_SIZE)
715
+ );
716
+ }
717
+
718
+ async function* parseLAZInBatches(
719
+ initialPending: Uint8Array<ArrayBufferLike>,
720
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
721
+ header: LASHeader,
722
+ options: LASLoaderOptions
723
+ ): AsyncIterable<LASArrowTable> {
724
+ const {pending, laszip} = await readLASZipVLRFromInput(initialPending, inputIterator, header);
725
+ validateTypeScriptLAZSupport(header, laszip);
726
+ if (!laszip.variableChunks && header.pointsFormatId <= 5) {
727
+ yield* parsePendingFixedLegacyLAZFileInArrowBatches(
728
+ pending,
729
+ inputIterator,
730
+ header,
731
+ laszip,
732
+ options
733
+ );
734
+ return;
735
+ }
736
+ if (!laszip.variableChunks && header.pointsFormatId >= 6 && header.pointsFormatId <= 10) {
737
+ yield* parsePendingLAZFileInArrowBatches(pending, inputIterator, header, laszip, options);
738
+ return;
739
+ }
740
+
741
+ for await (const batch of decodeLAZFileWithParsedVLRInBatches(
742
+ pending,
743
+ inputIterator,
744
+ header,
745
+ laszip,
746
+ options
747
+ )) {
748
+ yield parseLASArrowTableBatch(batch.arrayBuffer, batch.header, options);
749
+ }
750
+ }
751
+
752
+ /**
753
+ * Decode fixed-size legacy LAZ chunks directly into streaming Arrow batches.
754
+ *
755
+ * This path intentionally parallels the raw legacy stream loop for PDRFs 0-5. Writing into
756
+ * Arrow-owned arrays avoids allocating and reparsing an intermediate raw LAS point-record batch,
757
+ * while the cursor still provides point-atomic suspension when more compressed input is required.
758
+ */
759
+ async function* parsePendingFixedLegacyLAZFileInArrowBatches(
760
+ initialPending: Uint8Array<ArrayBufferLike>,
761
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
762
+ header: LASHeader,
763
+ laszip: LASZipVLR,
764
+ options: LASLoaderOptions
765
+ ): AsyncIterable<LASArrowTable> {
766
+ const outputHeader = {...header, totalToRead: header.pointsCount};
767
+ const state = createPointDataBatchState(getBatchSize(options), header, options);
768
+ const stats = getLAZStreamingDecodeStats(options);
769
+ const chunkByteLengths: number[] = [];
770
+ const reader = new BinaryChunkReader();
771
+ reader.write(initialPending);
772
+
773
+ await readUntilAvailable(
774
+ reader,
775
+ inputIterator,
776
+ header.pointsOffset + (laszip.compressor === 1 ? 0 : LAZ_CHUNK_TABLE_POINTER_LENGTH),
777
+ 'LASLoader: incomplete compressed LAZ point data'
778
+ );
779
+ reader.skip(header.pointsOffset);
780
+ const chunkTableOffset =
781
+ laszip.compressor === 1
782
+ ? 0
783
+ : readStreamedLAZChunkTableOffset(reader.getDataView(LAZ_CHUNK_TABLE_POINTER_LENGTH)!);
784
+
785
+ let sourcePointIndex = 0;
786
+ while (sourcePointIndex < header.pointsCount) {
787
+ const chunkPointCount =
788
+ laszip.compressor === 1
789
+ ? header.pointsCount
790
+ : Math.min(laszip.chunkSize, header.pointsCount - sourcePointIndex);
791
+ const metadata = createLAZChunkMetadata(header, laszip, chunkPointCount);
792
+ const cursor = createLAZChunkDecoderCursor(new Uint8Array(0), metadata);
793
+ let fedByteLength = 0;
794
+ let consumedByteLength = 0;
795
+ let inputDone = false;
796
+
797
+ while (cursor.remainingPointCount > 0) {
798
+ const availableByteLength = reader.getAvailableByteLength();
799
+ const requestedByteLength = getLegacyLAZFeedByteLength(
800
+ availableByteLength,
801
+ fedByteLength,
802
+ cursor.requiredInputByteLength - consumedByteLength
803
+ );
804
+ if (requestedByteLength > fedByteLength) {
805
+ const checkpoint = reader.checkpoint();
806
+ reader.skip(fedByteLength);
807
+ const addedByteLength = requestedByteLength - fedByteLength;
808
+ recordReadBytesStats(reader, addedByteLength, stats);
809
+ cursor.feed(reader.readBytes(addedByteLength));
810
+ reader.restore(checkpoint);
811
+ fedByteLength = requestedByteLength;
812
+ }
813
+
814
+ state.target.pointOffset = state.batchPointCount;
815
+ const availableBatchPointCount = state.batchCapacity - state.batchPointCount;
816
+ const decodedPointCount = cursor.decodeAvailableIntoPointData(
817
+ state.target,
818
+ availableBatchPointCount,
819
+ inputDone
820
+ );
821
+ if (decodedPointCount > 0) {
822
+ if (laszip.compressor === 1) {
823
+ const decodedByteLength = cursor.compressedByteOffset - consumedByteLength;
824
+ reader.skip(decodedByteLength);
825
+ fedByteLength -= decodedByteLength;
826
+ consumedByteLength = cursor.compressedByteOffset;
827
+ cursor.discardConsumedInput();
828
+ }
829
+ populateDecodedTypedExtraBytes(state, state.batchPointCount, decodedPointCount);
830
+ state.batchPointCount += decodedPointCount;
831
+ if (state.batchPointCount === state.batchCapacity) {
832
+ const batch = flushPointDataBatch(outputHeader, state, options);
833
+ if (batch) {
834
+ yield batch;
835
+ }
836
+ }
837
+ continue;
838
+ }
839
+
840
+ if (inputDone) {
841
+ throw new NeedsMoreData(
842
+ `LASLoader: truncated legacy LAZ chunk after ${chunkPointCount - cursor.remainingPointCount} of ${chunkPointCount} points with ${availableByteLength} bytes available`
843
+ );
844
+ }
845
+ const next = await inputIterator.next();
846
+ if (next.done) {
847
+ inputDone = true;
848
+ } else {
849
+ reader.write(next.value);
850
+ }
851
+ }
852
+
853
+ reader.skip(cursor.compressedByteOffset - consumedByteLength);
854
+ chunkByteLengths.push(cursor.compressedByteOffset);
855
+ sourcePointIndex += chunkPointCount;
856
+ }
857
+
858
+ if (laszip.compressor !== 1) {
859
+ await validateStreamedFixedLAZChunkTable(
860
+ reader,
861
+ inputIterator,
862
+ header,
863
+ laszip,
864
+ chunkTableOffset,
865
+ chunkByteLengths,
866
+ options
867
+ );
868
+ }
869
+
870
+ const finalBatch = flushPointDataBatch(outputHeader, state, options);
871
+ if (finalBatch) {
872
+ yield finalBatch;
873
+ }
874
+ }
875
+
876
+ async function* parsePendingLAZFileInArrowBatches(
877
+ initialPending: Uint8Array<ArrayBufferLike>,
878
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
879
+ header: LASHeader,
880
+ laszip: LASZipVLR,
881
+ options: LASLoaderOptions
882
+ ): AsyncIterable<LASArrowTable> {
883
+ const outputHeader = {...header, totalToRead: header.pointsCount};
884
+ const state = createPointDataBatchState(getBatchSize(options), header, options);
885
+ let sourcePointIndex = 0;
886
+ const chunkByteLengths: number[] = [];
887
+ const reader = new BinaryChunkReader();
888
+ reader.write(initialPending);
889
+
890
+ await readUntilAvailable(
891
+ reader,
892
+ inputIterator,
893
+ header.pointsOffset + LAZ_CHUNK_TABLE_POINTER_LENGTH,
894
+ 'LASLoader: incomplete compressed LAZ point data'
895
+ );
896
+ reader.skip(header.pointsOffset);
897
+ const chunkTableOffset = readStreamedLAZChunkTableOffset(
898
+ reader.getDataView(LAZ_CHUNK_TABLE_POINTER_LENGTH)!
899
+ );
900
+
901
+ while (sourcePointIndex < header.pointsCount) {
902
+ const chunkPointCount = Math.min(laszip.chunkSize, header.pointsCount - sourcePointIndex);
903
+ const metadata = createLAZChunkMetadata(header, laszip, chunkPointCount);
904
+ const chunkByteLength = yield* appendProgressiveLAZChunkToPointDataBatches(
905
+ reader,
906
+ inputIterator,
907
+ metadata,
908
+ outputHeader,
909
+ state,
910
+ options,
911
+ Boolean(options.las?.recoverMissingChunkTable && chunkTableOffset === header.pointsOffset)
912
+ );
913
+ chunkByteLengths.push(chunkByteLength);
914
+
915
+ sourcePointIndex += chunkPointCount;
916
+ }
917
+
918
+ await validateStreamedFixedLAZChunkTable(
919
+ reader,
920
+ inputIterator,
921
+ header,
922
+ laszip,
923
+ chunkTableOffset,
924
+ chunkByteLengths,
925
+ options
926
+ );
927
+
928
+ const finalBatch = flushPointDataBatch(outputHeader, state, options);
929
+ if (finalBatch) {
930
+ yield finalBatch;
931
+ }
932
+ }
933
+
934
+ /** Feed one layered LAZ chunk and yield requested Arrow rows before its trailing layers arrive. */
935
+ async function* appendProgressiveLAZChunkToPointDataBatches(
936
+ reader: BinaryChunkReader,
937
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
938
+ metadata: LAZChunkMetadata,
939
+ header: LASHeader,
940
+ state: PointDataBatchState,
941
+ options: LASLoaderOptions,
942
+ validateRecoveryPointCount: boolean
943
+ ): AsyncGenerator<LASArrowTable, number> {
944
+ const headerByteLength = getLAZChunkHeaderByteLength(metadata);
945
+ await readUntilAvailable(
946
+ reader,
947
+ inputIterator,
948
+ headerByteLength,
949
+ 'LASLoader: incomplete layered LAZ chunk header'
950
+ );
951
+ const compressedHeader = reader.readBytes(headerByteLength);
952
+ if (validateRecoveryPointCount) validateRecoveredLayeredPointCount(compressedHeader, metadata);
953
+ const chunkByteLength = getLAZChunkDeclaredByteLength(compressedHeader, metadata);
954
+ const decoder = createLAZChunkDecoder(metadata);
955
+ decoder.feed(compressedHeader);
956
+ let fedByteLength = headerByteLength;
957
+
958
+ while (fedByteLength < chunkByteLength) {
959
+ yield* readAvailableLAZPointDataBatches(decoder, header, state, options);
960
+ if (reader.getAvailableByteLength() === 0) {
961
+ const next = await inputIterator.next();
962
+ if (next.done) {
963
+ throw new NeedsMoreData('LASLoader: incomplete layered LAZ chunk payload');
964
+ }
965
+ reader.write(next.value);
966
+ }
967
+ const byteLength = Math.min(reader.getAvailableByteLength(), chunkByteLength - fedByteLength);
968
+ decoder.feed(reader.readBytes(byteLength));
969
+ fedByteLength += byteLength;
970
+ }
971
+
972
+ decoder.close();
973
+ yield* readAvailableLAZPointDataBatches(decoder, header, state, options);
974
+ if (decoder.remainingPointCount !== 0) {
975
+ throw new NeedsMoreData(
976
+ `LASLoader: layered LAZ chunk produced ${metadata.pointCount - decoder.remainingPointCount} of ${metadata.pointCount} points`
977
+ );
978
+ }
979
+ return chunkByteLength;
980
+ }
981
+
982
+ /** Drain all currently decodable rows from one feedable layered LAZ chunk. */
983
+ function* readAvailableLAZPointDataBatches(
984
+ decoder: ReturnType<typeof createLAZChunkDecoder>,
985
+ header: LASHeader,
986
+ state: PointDataBatchState,
987
+ options: LASLoaderOptions
988
+ ): Iterable<LASArrowTable> {
989
+ while (decoder.remainingPointCount > 0) {
990
+ const batchRemainingPointCount = state.batchCapacity - state.batchPointCount;
991
+ state.target.pointOffset = state.batchPointCount;
992
+ const pointsDecoded = decoder.readPointDataBatch(state.target, batchRemainingPointCount);
993
+ if (!pointsDecoded) {
994
+ return;
995
+ }
996
+ populateDecodedTypedExtraBytes(state, state.batchPointCount, pointsDecoded);
997
+ state.batchPointCount += pointsDecoded;
998
+ if (state.batchPointCount === state.batchCapacity) {
999
+ const batch = flushPointDataBatch(header, state, options);
1000
+ if (batch) {
1001
+ yield batch;
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+
1007
+ function* parseLAZChunkedIterator(
1008
+ arrayBuffer: ArrayBuffer,
1009
+ header: LASHeader,
1010
+ batchSize: number,
1011
+ parsedLASZipVLR?: LASZipVLR,
1012
+ options: LASLoaderOptions = {}
1013
+ ): Iterable<LASDecodedChunk> {
1014
+ const laszip = parsedLASZipVLR || parseLASZipVLR(new Uint8Array(arrayBuffer), header);
1015
+ validateTypeScriptLAZSupport(header, laszip);
1016
+
1017
+ if (header.pointsFormatId <= 5 || laszip.variableChunks) {
1018
+ yield* decodeLAZFileFromCompleteBytes(new Uint8Array(arrayBuffer), header, laszip, {
1019
+ ...options,
1020
+ batchSize
1021
+ });
1022
+ return;
1023
+ }
1024
+
1025
+ const outputHeader = {...header, totalToRead: header.pointsCount};
1026
+ const state = createRawPointBatchState(batchSize, header.pointsStructSize);
1027
+ const bytes = new Uint8Array(arrayBuffer);
1028
+ const chunkTable = readLAZFileChunks(bytes, header, laszip, options);
1029
+ let byteOffset =
1030
+ header.pointsOffset + (laszip.compressor === 1 ? 0 : LAZ_CHUNK_TABLE_POINTER_LENGTH);
1031
+
1032
+ for (const chunk of chunkTable) {
1033
+ const metadata = createLAZChunkMetadata(header, laszip, chunk.pointCount);
1034
+ const compressed = bytes.subarray(byteOffset, byteOffset + chunk.byteLength);
1035
+ for (const batch of appendDecodedLAZChunk(compressed, metadata, outputHeader, state)) {
1036
+ yield batch;
1037
+ }
1038
+
1039
+ byteOffset += chunk.byteLength;
1040
+ }
1041
+
1042
+ const finalBatch = flushRawPointBatch(outputHeader, state);
1043
+ if (finalBatch) {
1044
+ yield finalBatch;
1045
+ }
1046
+ }
1047
+
1048
+ function* decodeLAZFileFromCompleteBytes(
1049
+ bytes: Uint8Array,
1050
+ header: LASHeader,
1051
+ laszip: LASZipVLR,
1052
+ options: LASLoaderOptions = {}
1053
+ ): Iterable<LASDecodedChunk> {
1054
+ const batchSize = getBatchSize(options);
1055
+ const outputHeader = {...header, totalToRead: header.pointsCount};
1056
+ const state = createRawPointBatchState(
1057
+ batchSize,
1058
+ header.pointsStructSize,
1059
+ getLAZStreamingDecodeStats(options)
1060
+ );
1061
+ const chunkTable = readLAZFileChunks(bytes, header, laszip, options);
1062
+ let byteOffset =
1063
+ header.pointsOffset + (laszip.compressor === 1 ? 0 : LAZ_CHUNK_TABLE_POINTER_LENGTH);
1064
+
1065
+ for (const chunk of chunkTable) {
1066
+ const compressedChunk = bytes.subarray(byteOffset, byteOffset + chunk.byteLength);
1067
+ if (compressedChunk.byteLength < chunk.byteLength) {
1068
+ throw new NeedsMoreData('LASLoader: truncated legacy LAZ chunk');
1069
+ }
1070
+ const metadata = createLAZChunkMetadata(header, laszip, chunk.pointCount);
1071
+ if (laszip.compressor === 1) {
1072
+ yield* appendDecodedLAZChunk(compressedChunk, metadata, outputHeader, state);
1073
+ } else {
1074
+ const rawChunk = decodeLAZChunk(compressedChunk, metadata);
1075
+ recordDecodedChunkAllocation(rawChunk.byteLength, state.stats);
1076
+ yield* appendRawPointChunk(rawChunk, outputHeader, state);
1077
+ }
1078
+ byteOffset += chunk.byteLength;
1079
+ }
1080
+
1081
+ const finalBatch = flushRawPointBatch(outputHeader, state);
1082
+ if (finalBatch) {
1083
+ yield finalBatch;
1084
+ }
1085
+ }
1086
+
1087
+ function decodeLAZFileToRawPointData(
1088
+ arrayBuffer: ArrayBuffer,
1089
+ header: LASHeader,
1090
+ laszip: LASZipVLR
1091
+ ): Uint8Array {
1092
+ const totalPointCount = header.pointsCount;
1093
+ const rawPointData = new Uint8Array(totalPointCount * header.pointsStructSize);
1094
+ let byteOffset = 0;
1095
+
1096
+ for (const batch of parseLAZChunkedIterator(arrayBuffer, header, DEFAULT_BATCH_SIZE, laszip)) {
1097
+ const source = new Uint8Array(batch.arrayBuffer);
1098
+ rawPointData.set(source, byteOffset);
1099
+ byteOffset += source.byteLength;
1100
+ }
1101
+
1102
+ return rawPointData;
1103
+ }
1104
+
1105
+ /** Parse the public LAS header block. */
1106
+ export function parseLASHeader(arrayBuffer: ArrayBufferLike): LASHeader {
1107
+ const dataView = new DataView(arrayBuffer);
1108
+ if (arrayBuffer.byteLength < 227 || dataView.getUint32(0, true) !== LASF_SIGNATURE) {
1109
+ throw new Error('LASLoader: invalid LAS header');
1110
+ }
1111
+
1112
+ const versionMajor = dataView.getUint8(24);
1113
+ const versionMinor = dataView.getUint8(25);
1114
+ if (versionMajor !== 1 || versionMinor > 5) {
1115
+ throw new Error(`LASLoader: unsupported LAS version ${versionMajor}.${versionMinor}`);
1116
+ }
1117
+ const headerSize = dataView.getUint16(94, true);
1118
+ const globalEncoding = dataView.getUint16(6, true);
1119
+ const vlrCount = dataView.getUint32(100, true);
1120
+ const pointFormatByte = dataView.getUint8(104);
1121
+ const pointsFormatId = pointFormatByte & POINT_FORMAT_MASK;
1122
+ const isCompressed = Boolean(pointFormatByte & COMPRESSED_POINT_FORMAT_MASK);
1123
+ const legacyPointCount = dataView.getUint32(107, true);
1124
+ const extendedPointCount =
1125
+ versionMajor > 1 || versionMinor >= 4 ? readUint64(dataView, 247) : legacyPointCount;
1126
+ const pointsCount = extendedPointCount || legacyPointCount;
1127
+ const pointsOffset = dataView.getUint32(96, true);
1128
+ const pointsStructSize = dataView.getUint16(105, true);
1129
+ if (versionMinor === 5) {
1130
+ if (headerSize < 393) {
1131
+ throw new Error('LASLoader: LAS 1.5 header must be at least 393 bytes');
1132
+ }
1133
+ if (pointsFormatId < 6 || pointsFormatId > 10) {
1134
+ throw new Error(
1135
+ `LASLoader: LAS 1.5 requires point data record formats 6-10; received ${pointsFormatId}`
1136
+ );
1137
+ }
1138
+ if ((globalEncoding & 0x10) === 0) {
1139
+ throw new Error('LASLoader: LAS 1.5 requires the WKT global encoding flag');
1140
+ }
1141
+ if ((globalEncoding & 0xffa0) !== 0) {
1142
+ throw new Error('LASLoader: LAS 1.5 Global Encoding contains reserved bits');
1143
+ }
1144
+ if ((globalEncoding & 0x40) !== 0 && (globalEncoding & 0x01) === 0) {
1145
+ throw new Error('LASLoader: LAS 1.5 Time Offset Flag requires GPS Time Type');
1146
+ }
1147
+ const maxGpsTime = dataView.getFloat64(375, true);
1148
+ const minGpsTime = dataView.getFloat64(383, true);
1149
+ if (
1150
+ !Number.isFinite(maxGpsTime) ||
1151
+ !Number.isFinite(minGpsTime) ||
1152
+ (maxGpsTime !== 0 && minGpsTime !== 0 && maxGpsTime < minGpsTime)
1153
+ ) {
1154
+ throw new Error('LASLoader: LAS 1.5 GPS time range is invalid');
1155
+ }
1156
+ }
1157
+ const scale: [number, number, number] = [
1158
+ dataView.getFloat64(131, true),
1159
+ dataView.getFloat64(139, true),
1160
+ dataView.getFloat64(147, true)
1161
+ ];
1162
+ const offset: [number, number, number] = [
1163
+ dataView.getFloat64(155, true),
1164
+ dataView.getFloat64(163, true),
1165
+ dataView.getFloat64(171, true)
1166
+ ];
1167
+ const maxs = [
1168
+ dataView.getFloat64(179, true),
1169
+ dataView.getFloat64(195, true),
1170
+ dataView.getFloat64(211, true)
1171
+ ];
1172
+ const mins = [
1173
+ dataView.getFloat64(187, true),
1174
+ dataView.getFloat64(203, true),
1175
+ dataView.getFloat64(219, true)
1176
+ ];
1177
+
1178
+ const header: LASHeader = {
1179
+ pointsOffset,
1180
+ pointsFormatId,
1181
+ pointsStructSize,
1182
+ pointsCount,
1183
+ scale,
1184
+ offset,
1185
+ maxs,
1186
+ mins,
1187
+ totalToRead: pointsCount,
1188
+ totalRead: 0,
1189
+ hasColor: hasPointColor(pointsFormatId),
1190
+ versionAsString: `${versionMajor}.${versionMinor}`,
1191
+ isCompressed,
1192
+ headerSize,
1193
+ vlrCount,
1194
+ userHeaderData:
1195
+ versionMinor === 5 && headerSize > 393
1196
+ ? new Uint8Array(arrayBuffer, 393, headerSize - 393).slice()
1197
+ : undefined
1198
+ };
1199
+ if (hasCompleteLASMetadata(arrayBuffer, header)) {
1200
+ header.metadata = parseLASMetadata(arrayBuffer, header);
1201
+ }
1202
+ return header;
1203
+ }
1204
+
1205
+ function hasCompleteLASMetadata(arrayBuffer: ArrayBufferLike, header: LASHeader): boolean {
1206
+ const vlrEnd = header.headerSize! + header.vlrCount! * 54;
1207
+ return arrayBuffer.byteLength >= Math.max(vlrEnd, header.pointsOffset);
1208
+ }
1209
+
1210
+ function parseLASMetadata(arrayBuffer: ArrayBufferLike, header: LASHeader): LASMetadata {
1211
+ const dataView = new DataView(arrayBuffer);
1212
+ const version = header.versionAsString || '1.0';
1213
+ const versionParts = version.split('.').map(Number);
1214
+ const isAtLeast14 = versionParts[0] > 1 || (versionParts[0] === 1 && versionParts[1] >= 4);
1215
+ const vlrs = parseLASVariableLengthRecords(dataView, header);
1216
+ const evlrOffset = isAtLeast14 ? readUint64(dataView, 235) : 0;
1217
+ const evlrCount = isAtLeast14 ? dataView.getUint32(243, true) : 0;
1218
+ const evlrs = parseLASExtendedVariableLengthRecords(dataView, evlrOffset, evlrCount);
1219
+ const metadata: LASMetadata = {
1220
+ fileSourceId: dataView.getUint16(4, true),
1221
+ globalEncoding: dataView.getUint16(6, true),
1222
+ waveformDataOffset:
1223
+ versionParts[0] > 1 || (versionParts[0] === 1 && versionParts[1] >= 3)
1224
+ ? dataView.getBigUint64(227, true)
1225
+ : undefined,
1226
+ projectId: formatLASProjectId(new Uint8Array(arrayBuffer, 8, 16)),
1227
+ systemIdentifier: readLASString(new Uint8Array(arrayBuffer), 26, 32),
1228
+ generatingSoftware: readLASString(new Uint8Array(arrayBuffer), 58, 32),
1229
+ creationDayOfYear: dataView.getUint16(90, true),
1230
+ creationYear: dataView.getUint16(92, true),
1231
+ headerSize: header.headerSize!,
1232
+ userHeaderData: header.userHeaderData,
1233
+ vlrCount: header.vlrCount!,
1234
+ evlrOffset: evlrOffset || undefined,
1235
+ evlrCount: evlrCount || undefined,
1236
+ maxGpsTime: versionParts[1] === 5 ? dataView.getFloat64(375, true) : undefined,
1237
+ minGpsTime: versionParts[1] === 5 ? dataView.getFloat64(383, true) : undefined,
1238
+ timeOffset: versionParts[1] === 5 ? dataView.getUint16(391, true) : undefined,
1239
+ pointsByReturn: parsePointsByReturn(dataView, isAtLeast14),
1240
+ vlrs,
1241
+ evlrs,
1242
+ extraBytes: [],
1243
+ waveformPacketDescriptors: []
1244
+ };
1245
+
1246
+ for (const record of vlrs.concat(
1247
+ evlrs.map(evlr => ({...evlr, data: evlr.data || new Uint8Array()}))
1248
+ )) {
1249
+ parseTypedLASMetadataRecord(record, metadata);
1250
+ }
1251
+ metadata.compatibility = getLASCompatibilityLayout(header, metadata);
1252
+ resolveLASGeoTIFFKeyDirectory(metadata);
1253
+ return metadata;
1254
+ }
1255
+
1256
+ function parseLASVariableLengthRecords(
1257
+ dataView: DataView,
1258
+ header: LASHeader
1259
+ ): LASVariableLengthRecord[] {
1260
+ const bytes = new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength);
1261
+ const records: LASVariableLengthRecord[] = [];
1262
+ let offset = header.headerSize!;
1263
+ for (let index = 0; index < header.vlrCount!; index++) {
1264
+ if (offset + 54 > bytes.byteLength) {
1265
+ break;
1266
+ }
1267
+ const dataLength = dataView.getUint16(offset + 20, true);
1268
+ const dataOffset = offset + 54;
1269
+ if (dataOffset + dataLength > bytes.byteLength) {
1270
+ break;
1271
+ }
1272
+ records.push({
1273
+ reserved: dataView.getUint16(offset, true),
1274
+ userId: readLASString(bytes, offset + 2, 16),
1275
+ recordId: dataView.getUint16(offset + 18, true),
1276
+ description: readLASString(bytes, offset + 22, 32),
1277
+ offset,
1278
+ data: bytes.slice(dataOffset, dataOffset + dataLength)
1279
+ });
1280
+ offset = dataOffset + dataLength;
1281
+ }
1282
+ return records;
1283
+ }
1284
+
1285
+ function parseLASExtendedVariableLengthRecords(
1286
+ dataView: DataView,
1287
+ evlrOffset: number,
1288
+ evlrCount: number
1289
+ ): LASExtendedVariableLengthRecord[] {
1290
+ if (!evlrOffset || !evlrCount || evlrOffset >= dataView.byteLength) {
1291
+ return [];
1292
+ }
1293
+ const bytes = new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength);
1294
+ const records: LASExtendedVariableLengthRecord[] = [];
1295
+ let offset = evlrOffset;
1296
+ for (let index = 0; index < evlrCount; index++) {
1297
+ if (offset + 60 > bytes.byteLength) {
1298
+ break;
1299
+ }
1300
+ const dataLength = readUint64(dataView, offset + 20);
1301
+ const dataOffset = offset + 60;
1302
+ if (dataLength > bytes.byteLength - dataOffset) {
1303
+ break;
1304
+ }
1305
+ records.push({
1306
+ reserved: dataView.getUint16(offset, true),
1307
+ userId: readLASString(bytes, offset + 2, 16),
1308
+ recordId: dataView.getUint16(offset + 18, true),
1309
+ description: readLASString(bytes, offset + 28, 32),
1310
+ offset,
1311
+ data: bytes.slice(dataOffset, dataOffset + dataLength),
1312
+ dataOffset,
1313
+ dataLength
1314
+ });
1315
+ offset = dataOffset + dataLength;
1316
+ }
1317
+ return records;
1318
+ }
1319
+
1320
+ function parseTypedLASMetadataRecord(
1321
+ record: LASVariableLengthRecord | LASExtendedVariableLengthRecord,
1322
+ metadata: LASMetadata
1323
+ ): void {
1324
+ const data = record.data;
1325
+ if (!data) {
1326
+ return;
1327
+ }
1328
+ if (record.userId === 'LASF_Spec' && record.recordId === 4) {
1329
+ metadata.extraBytes.push(...parseLASExtraBytes(data));
1330
+ } else if (record.userId === 'LASF_Spec' && record.recordId >= 100 && record.recordId <= 354) {
1331
+ metadata.waveformPacketDescriptors.push(parseLASWaveformDescriptor(record.recordId, data));
1332
+ } else if (record.userId === 'LASF_Projection' && record.recordId === 2111) {
1333
+ metadata.wktMathTransform = decodeLASString(data);
1334
+ } else if (record.userId === 'LASF_Projection' && record.recordId === 2112) {
1335
+ metadata.wkt = decodeLASString(data);
1336
+ } else if (record.userId === 'LASF_Projection' && record.recordId === 34735) {
1337
+ metadata.geotiff = {...metadata.geotiff, keys: readUint16Array(data)};
1338
+ } else if (record.userId === 'LASF_Projection' && record.recordId === 34736) {
1339
+ metadata.geotiff = {...metadata.geotiff, doubles: readFloat64Array(data)};
1340
+ } else if (record.userId === 'LASF_Projection' && record.recordId === 34737) {
1341
+ metadata.geotiff = {...metadata.geotiff, ascii: decodeLASString(data)};
1342
+ }
1343
+ }
1344
+
1345
+ /** Resolve GeoKey directory entries against their companion LAS GeoTIFF records. */
1346
+ function resolveLASGeoTIFFKeyDirectory(metadata: LASMetadata): void {
1347
+ const geotiff = metadata.geotiff;
1348
+ const keys = geotiff?.keys;
1349
+ if (!geotiff || !keys || keys.length < 4) {
1350
+ return;
1351
+ }
1352
+ const declaredEntryCount = keys[3];
1353
+ if (keys.length < 4 + declaredEntryCount * 4) {
1354
+ return;
1355
+ }
1356
+ const entries: LASGeoTIFFKey[] = [];
1357
+ for (let entryIndex = 0; entryIndex < declaredEntryCount; entryIndex++) {
1358
+ const entryOffset = 4 + entryIndex * 4;
1359
+ const entry: LASGeoTIFFKey = {
1360
+ keyId: keys[entryOffset],
1361
+ tiffTagLocation: keys[entryOffset + 1],
1362
+ count: keys[entryOffset + 2],
1363
+ valueOffset: keys[entryOffset + 3]
1364
+ };
1365
+ entry.value = resolveLASGeoTIFFKeyValue(entry, geotiff);
1366
+ if (entry.value === undefined) {
1367
+ delete entry.value;
1368
+ }
1369
+ entries.push(entry);
1370
+ }
1371
+ geotiff.keyDirectory = {
1372
+ version: keys[0],
1373
+ keyRevision: keys[1],
1374
+ minorRevision: keys[2],
1375
+ entries
1376
+ };
1377
+ }
1378
+
1379
+ /** Resolve one GeoKey value from its TIFF-tag location. */
1380
+ function resolveLASGeoTIFFKeyValue(
1381
+ entry: LASGeoTIFFKey,
1382
+ geotiff: NonNullable<LASMetadata['geotiff']>
1383
+ ): number | number[] | string | undefined {
1384
+ if (entry.tiffTagLocation === 0) {
1385
+ return entry.valueOffset;
1386
+ }
1387
+ if (entry.tiffTagLocation === 34735 && geotiff.keys) {
1388
+ const values = geotiff.keys.slice(entry.valueOffset, entry.valueOffset + entry.count);
1389
+ return values.length === entry.count ? Array.from(values) : undefined;
1390
+ }
1391
+ if (entry.tiffTagLocation === 34736 && geotiff.doubles) {
1392
+ const values = geotiff.doubles.slice(entry.valueOffset, entry.valueOffset + entry.count);
1393
+ if (values.length !== entry.count) {
1394
+ return undefined;
1395
+ }
1396
+ return entry.count === 1 ? values[0] : Array.from(values);
1397
+ }
1398
+ if (entry.tiffTagLocation === 34737 && geotiff.ascii !== undefined) {
1399
+ const end = entry.valueOffset + entry.count;
1400
+ if (end > geotiff.ascii.length) {
1401
+ return undefined;
1402
+ }
1403
+ return geotiff.ascii.slice(entry.valueOffset, end).replace(/\|$/, '');
1404
+ }
1405
+ return undefined;
1406
+ }
1407
+
1408
+ function parseLASWaveformDescriptor(
1409
+ recordId: number,
1410
+ data: Uint8Array
1411
+ ): LASWaveformPacketDescriptor {
1412
+ if (data.byteLength < 28) {
1413
+ throw new Error(`LASLoader: waveform descriptor VLR ${recordId} is truncated`);
1414
+ }
1415
+ const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
1416
+ return {
1417
+ recordId,
1418
+ bitsPerSample: dataView.getUint8(2),
1419
+ compressionType: dataView.getUint8(3),
1420
+ numberOfSamples: dataView.getUint32(4, true),
1421
+ temporalSampleSpacing: dataView.getUint32(8, true),
1422
+ digitizerGain: dataView.getFloat64(12, true),
1423
+ digitizerOffset: dataView.getFloat64(20, true)
1424
+ };
1425
+ }
1426
+
1427
+ function parsePointsByReturn(dataView: DataView, isAtLeast14: boolean): number[] {
1428
+ const count = isAtLeast14 ? 15 : 5;
1429
+ const offset = isAtLeast14 ? 255 : 111;
1430
+ const values: number[] = [];
1431
+ for (let index = 0; index < count; index++) {
1432
+ values.push(
1433
+ isAtLeast14
1434
+ ? readUint64(dataView, offset + index * 8)
1435
+ : dataView.getUint32(offset + index * 4, true)
1436
+ );
1437
+ }
1438
+ return values;
1439
+ }
1440
+
1441
+ function readLASString(bytes: Uint8Array, offset: number, length: number): string {
1442
+ return decodeLASString(bytes.subarray(offset, offset + length));
1443
+ }
1444
+
1445
+ function decodeLASString(bytes: Uint8Array): string {
1446
+ let end = bytes.length;
1447
+ while (end > 0 && bytes[end - 1] === 0) {
1448
+ end--;
1449
+ }
1450
+ return new TextDecoder().decode(bytes.subarray(0, end)).trim();
1451
+ }
1452
+
1453
+ function formatLASProjectId(bytes: Uint8Array): string {
1454
+ const formatLittleEndian = (start: number, length: number): string =>
1455
+ Array.from(bytes.subarray(start, start + length), byte => byte.toString(16).padStart(2, '0'))
1456
+ .reverse()
1457
+ .join('');
1458
+ const formatBigEndian = (start: number, length: number): string =>
1459
+ Array.from(bytes.subarray(start, start + length), byte =>
1460
+ byte.toString(16).padStart(2, '0')
1461
+ ).join('');
1462
+ return `${formatLittleEndian(0, 4)}-${formatLittleEndian(4, 2)}-${formatLittleEndian(6, 2)}-${formatBigEndian(8, 2)}${formatBigEndian(10, 6)}`;
1463
+ }
1464
+
1465
+ function readUint16Array(bytes: Uint8Array): Uint16Array {
1466
+ const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1467
+ const values = new Uint16Array(Math.floor(bytes.byteLength / 2));
1468
+ for (let index = 0; index < values.length; index++) {
1469
+ values[index] = dataView.getUint16(index * 2, true);
1470
+ }
1471
+ return values;
1472
+ }
1473
+
1474
+ function readFloat64Array(bytes: Uint8Array): Float64Array {
1475
+ const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1476
+ const values = new Float64Array(Math.floor(bytes.byteLength / 8));
1477
+ for (let index = 0; index < values.length; index++) {
1478
+ values[index] = dataView.getFloat64(index * 8, true);
1479
+ }
1480
+ return values;
1481
+ }
1482
+
1483
+ /**
1484
+ * Parse raw LAS point records into a MeshArrowTable batch.
1485
+ * @param arrayBuffer Raw point record data or a complete LAS file buffer.
1486
+ * @param lasHeader LAS header describing the point record layout.
1487
+ * @param options LAS loader options.
1488
+ * @returns Arrow table batch populated directly from LAS point records.
1489
+ */
1490
+ function parseLASArrowTableBatch(
1491
+ arrayBuffer: ArrayBufferLike | ArrayBufferView,
1492
+ lasHeader: LASHeader,
1493
+ options: LASLoaderOptions = {}
1494
+ ): LASArrowTable {
1495
+ const batchSize = lasHeader.pointsCount;
1496
+ const PositionsType = options.las?.fp64 ? Float64Array : Float32Array;
1497
+ const positions = new PositionsType(batchSize * 3);
1498
+ const selection = getLASColumnSelection(options);
1499
+ const colors = lasHeader.hasColor && selection.color ? new Uint8Array(batchSize * 4) : null;
1500
+ const intensities = selection.intensity ? new Uint16Array(batchSize) : null;
1501
+ const classifications = selection.classification ? new Uint8Array(batchSize) : null;
1502
+ const syntheticFlags = selection.synthetic ? new Uint8Array(batchSize) : null;
1503
+ const keyPointFlags = selection.keyPoint ? new Uint8Array(batchSize) : null;
1504
+ const withheldFlags = selection.withheld ? new Uint8Array(batchSize) : null;
1505
+ const overlapFlags = selection.overlap ? new Uint8Array(batchSize) : null;
1506
+ const gpsTimes =
1507
+ selection.gpsTime && getGpsTimeOffset(lasHeader.pointsFormatId) >= 0
1508
+ ? new Float64Array(batchSize)
1509
+ : null;
1510
+ const nir =
1511
+ selection.nir &&
1512
+ (getNirOffset(lasHeader.pointsFormatId) >= 0 ||
1513
+ (options.las?.compatibilityMode !== 'raw' &&
1514
+ lasHeader.metadata?.compatibility?.nirByteOffset !== undefined))
1515
+ ? new Uint16Array(batchSize)
1516
+ : null;
1517
+ const scanAngles = selection.scanAngle ? new Int16Array(batchSize) : null;
1518
+ const userData = selection.userData ? new Uint8Array(batchSize) : null;
1519
+ const pointSourceIds = selection.pointSourceId ? new Uint16Array(batchSize) : null;
1520
+ const returnNumbers = selection.returnNumber ? new Uint8Array(batchSize) : null;
1521
+ const numberOfReturns = selection.numberOfReturns ? new Uint8Array(batchSize) : null;
1522
+ const scannerChannels = selection.scannerChannel ? new Uint8Array(batchSize) : null;
1523
+ const scanDirectionFlags = selection.scanDirectionFlag ? new Uint8Array(batchSize) : null;
1524
+ const edgeOfFlightLines = selection.edgeOfFlightLine ? new Uint8Array(batchSize) : null;
1525
+ const waveforms =
1526
+ selection.waveform && getWaveformOffset(lasHeader.pointsFormatId) >= 0
1527
+ ? new Uint8Array(batchSize * 29)
1528
+ : null;
1529
+ const extraByteCount = Math.max(
1530
+ 0,
1531
+ lasHeader.pointsStructSize - getLAZPointDataRecordBaseLength(lasHeader.pointsFormatId)
1532
+ );
1533
+ const extraBytes =
1534
+ selection.extraBytes && options.las?.extraBytes !== 'typed' && extraByteCount
1535
+ ? new Uint8Array(batchSize * extraByteCount)
1536
+ : null;
1537
+ const typedExtraBytes =
1538
+ selection.extraBytes && options.las?.extraBytes === 'typed'
1539
+ ? createTypedExtraBytesAttributes(batchSize, lasHeader)
1540
+ : null;
1541
+
1542
+ populateLASAttributesFromDataView(makeDataView(arrayBuffer), lasHeader, options, {
1543
+ positions,
1544
+ colors,
1545
+ intensities,
1546
+ classifications,
1547
+ syntheticFlags,
1548
+ keyPointFlags,
1549
+ withheldFlags,
1550
+ overlapFlags,
1551
+ gpsTimes,
1552
+ nir,
1553
+ scanAngles,
1554
+ userData,
1555
+ pointSourceIds,
1556
+ returnNumbers,
1557
+ numberOfReturns,
1558
+ scannerChannels,
1559
+ scanDirectionFlags,
1560
+ edgeOfFlightLines,
1561
+ waveforms,
1562
+ extraBytes,
1563
+ typedExtraBytes,
1564
+ pointOffset: 0,
1565
+ sourcePointIndex: 0,
1566
+ pointCount: batchSize
1567
+ });
1568
+
1569
+ return makeLASArrowTableFromAttributes(
1570
+ lasHeader,
1571
+ positions,
1572
+ colors,
1573
+ intensities,
1574
+ classifications,
1575
+ syntheticFlags,
1576
+ keyPointFlags,
1577
+ withheldFlags,
1578
+ overlapFlags,
1579
+ gpsTimes,
1580
+ nir,
1581
+ scanAngles,
1582
+ userData,
1583
+ pointSourceIds,
1584
+ returnNumbers,
1585
+ numberOfReturns,
1586
+ scannerChannels,
1587
+ scanDirectionFlags,
1588
+ edgeOfFlightLines,
1589
+ waveforms,
1590
+ extraBytes,
1591
+ typedExtraBytes
1592
+ );
1593
+ }
1594
+
1595
+ function makeLASArrowTableFromAttributes(
1596
+ lasHeader: LASHeader,
1597
+ positions: Float32Array | Float64Array,
1598
+ colors: Uint8Array | Uint16Array | null,
1599
+ intensities: Uint16Array | null,
1600
+ classifications: Uint8Array | null,
1601
+ syntheticFlags: Uint8Array | null,
1602
+ keyPointFlags: Uint8Array | null,
1603
+ withheldFlags: Uint8Array | null,
1604
+ overlapFlags: Uint8Array | null,
1605
+ gpsTimes: Float64Array | null,
1606
+ nir: Uint16Array | null,
1607
+ scanAngles: Int16Array | null,
1608
+ userData: Uint8Array | null,
1609
+ pointSourceIds: Uint16Array | null,
1610
+ returnNumbers: Uint8Array | null,
1611
+ numberOfReturns: Uint8Array | null,
1612
+ scannerChannels: Uint8Array | null,
1613
+ scanDirectionFlags: Uint8Array | null,
1614
+ edgeOfFlightLines: Uint8Array | null,
1615
+ waveforms: Uint8Array | null,
1616
+ extraBytes: Uint8Array | null,
1617
+ typedExtraBytes: LASTypedExtraBytesAttribute[] | null
1618
+ ): LASArrowTable {
1619
+ const attributes: MeshArrowAttributes = {
1620
+ POSITION: {value: positions, size: 3}
1621
+ };
1622
+ if (intensities) {
1623
+ attributes.intensity = {value: intensities, size: 1};
1624
+ }
1625
+ if (classifications) {
1626
+ attributes.classification = {value: classifications, size: 1};
1627
+ }
1628
+ if (syntheticFlags) {
1629
+ attributes.synthetic = {value: syntheticFlags, size: 1};
1630
+ }
1631
+ if (keyPointFlags) {
1632
+ attributes.keyPoint = {value: keyPointFlags, size: 1};
1633
+ }
1634
+ if (withheldFlags) {
1635
+ attributes.withheld = {value: withheldFlags, size: 1};
1636
+ }
1637
+ if (overlapFlags) {
1638
+ attributes.overlap = {value: overlapFlags, size: 1};
1639
+ }
1640
+ if (colors) {
1641
+ attributes.COLOR_0 = {value: colors, size: colors instanceof Uint16Array ? 3 : 4};
1642
+ }
1643
+ if (gpsTimes) {
1644
+ attributes.GPS_TIME = {value: gpsTimes, size: 1};
1645
+ }
1646
+ if (nir) {
1647
+ attributes.NIR = {value: nir, size: 1};
1648
+ }
1649
+ if (scanAngles) {
1650
+ attributes.scanAngle = {value: scanAngles, size: 1};
1651
+ }
1652
+ if (userData) {
1653
+ attributes.userData = {value: userData, size: 1};
1654
+ }
1655
+ if (pointSourceIds) {
1656
+ attributes.pointSourceId = {value: pointSourceIds, size: 1};
1657
+ }
1658
+ if (returnNumbers) {
1659
+ attributes.returnNumber = {value: returnNumbers, size: 1};
1660
+ }
1661
+ if (numberOfReturns) {
1662
+ attributes.numberOfReturns = {value: numberOfReturns, size: 1};
1663
+ }
1664
+ if (scannerChannels) {
1665
+ attributes.scannerChannel = {value: scannerChannels, size: 1};
1666
+ }
1667
+ if (scanDirectionFlags) {
1668
+ attributes.scanDirectionFlag = {value: scanDirectionFlags, size: 1};
1669
+ }
1670
+ if (edgeOfFlightLines) {
1671
+ attributes.edgeOfFlightLine = {value: edgeOfFlightLines, size: 1};
1672
+ }
1673
+ if (waveforms) {
1674
+ attributes.WAVEFORM = {value: waveforms, size: 29};
1675
+ }
1676
+ if (extraBytes) {
1677
+ attributes.EXTRA_BYTES = {
1678
+ value: extraBytes,
1679
+ size: extraBytes.length / Math.max(lasHeader.pointsCount, 1)
1680
+ };
1681
+ }
1682
+ if (typedExtraBytes) {
1683
+ for (const attribute of typedExtraBytes) {
1684
+ attributes[attribute.name] = {value: attribute.value, size: attribute.size};
1685
+ }
1686
+ }
1687
+
1688
+ const numericAttributes: MeshAttributes = {};
1689
+ for (const [name, attribute] of Object.entries(attributes)) {
1690
+ if (
1691
+ !(attribute.value instanceof BigInt64Array) &&
1692
+ !(attribute.value instanceof BigUint64Array)
1693
+ ) {
1694
+ numericAttributes[name] = {...attribute, value: attribute.value};
1695
+ }
1696
+ }
1697
+ const schema = getLASSchema(lasHeader, numericAttributes);
1698
+ return {
1699
+ ...makeMeshArrowTable(attributes, {
1700
+ schema,
1701
+ topology: 'point-list',
1702
+ mode: 0,
1703
+ boundingBox: getHeaderBoundingBox(lasHeader)
1704
+ }),
1705
+ loader: 'las',
1706
+ loaderData: lasHeader,
1707
+ progress: lasHeader.totalRead / lasHeader.totalToRead
1708
+ };
1709
+ }
1710
+
1711
+ /** Create a DataView with exact offsets for ArrayBufferView inputs. */
1712
+ function makeDataView(arrayBuffer: ArrayBufferLike | ArrayBufferView): DataView {
1713
+ if (ArrayBuffer.isView(arrayBuffer)) {
1714
+ return new DataView(arrayBuffer.buffer, arrayBuffer.byteOffset, arrayBuffer.byteLength);
1715
+ }
1716
+ return new DataView(arrayBuffer);
1717
+ }
1718
+
1719
+ function populateLASAttributesFromDataView(
1720
+ dataView: DataView,
1721
+ lasHeader: LASHeader,
1722
+ options: LASLoaderOptions,
1723
+ target: {
1724
+ positions: Float32Array | Float64Array;
1725
+ colors: Uint8Array | null;
1726
+ intensities: Uint16Array | null;
1727
+ classifications: Uint8Array | null;
1728
+ syntheticFlags: Uint8Array | null;
1729
+ keyPointFlags: Uint8Array | null;
1730
+ withheldFlags: Uint8Array | null;
1731
+ overlapFlags: Uint8Array | null;
1732
+ gpsTimes: Float64Array | null;
1733
+ nir: Uint16Array | null;
1734
+ scanAngles: Int16Array | null;
1735
+ userData: Uint8Array | null;
1736
+ pointSourceIds: Uint16Array | null;
1737
+ returnNumbers: Uint8Array | null;
1738
+ numberOfReturns: Uint8Array | null;
1739
+ scannerChannels: Uint8Array | null;
1740
+ scanDirectionFlags: Uint8Array | null;
1741
+ edgeOfFlightLines: Uint8Array | null;
1742
+ waveforms: Uint8Array | null;
1743
+ extraBytes: Uint8Array | null;
1744
+ typedExtraBytes: LASTypedExtraBytesAttribute[] | null;
1745
+ pointOffset: number;
1746
+ sourcePointIndex: number;
1747
+ pointCount: number;
1748
+ }
1749
+ ): void {
1750
+ const {
1751
+ scale: [scaleX, scaleY, scaleZ],
1752
+ offset: [offsetX, offsetY, offsetZ]
1753
+ } = lasHeader;
1754
+ const pointsFormatId = lasHeader.pointsFormatId;
1755
+ const pointRecordLength = lasHeader.pointsStructSize;
1756
+ const colorOffset = target.colors ? getColorOffset(pointsFormatId) : -1;
1757
+ const twoByteColor =
1758
+ colorOffset >= 0
1759
+ ? detectTwoByteColors(
1760
+ dataView,
1761
+ lasHeader,
1762
+ target.sourcePointIndex,
1763
+ target.pointCount,
1764
+ options.las?.colorDepth
1765
+ )
1766
+ : false;
1767
+ const intensities = target.intensities;
1768
+ const classifications = target.classifications;
1769
+ const syntheticFlags = target.syntheticFlags;
1770
+ const keyPointFlags = target.keyPointFlags;
1771
+ const withheldFlags = target.withheldFlags;
1772
+ const overlapFlags = target.overlapFlags;
1773
+ const gpsTimes = target.gpsTimes;
1774
+ const nir = target.nir;
1775
+ const scanAngles = target.scanAngles;
1776
+ const userData = target.userData;
1777
+ const pointSourceIds = target.pointSourceIds;
1778
+ const returnNumbers = target.returnNumbers;
1779
+ const numberOfReturns = target.numberOfReturns;
1780
+ const scannerChannels = target.scannerChannels;
1781
+ const scanDirectionFlags = target.scanDirectionFlags;
1782
+ const edgeOfFlightLines = target.edgeOfFlightLines;
1783
+ const waveforms = target.waveforms;
1784
+ const extraBytes = target.extraBytes;
1785
+ const typedExtraBytes = target.typedExtraBytes;
1786
+ const gpsTimeOffset = gpsTimes ? getGpsTimeOffset(pointsFormatId) : -1;
1787
+ const nirOffset = nir ? getNirOffset(pointsFormatId) : -1;
1788
+
1789
+ for (let pointIndex = 0; pointIndex < target.pointCount; pointIndex++) {
1790
+ const sourcePointIndex = target.sourcePointIndex + pointIndex;
1791
+ const pointOffset = lasHeader.pointsOffset + sourcePointIndex * pointRecordLength;
1792
+ const targetPointIndex = target.pointOffset + pointIndex;
1793
+ target.positions[targetPointIndex * 3] =
1794
+ dataView.getInt32(pointOffset, true) * scaleX + offsetX;
1795
+ target.positions[targetPointIndex * 3 + 1] =
1796
+ dataView.getInt32(pointOffset + 4, true) * scaleY + offsetY;
1797
+ target.positions[targetPointIndex * 3 + 2] =
1798
+ dataView.getInt32(pointOffset + 8, true) * scaleZ + offsetZ;
1799
+ if (intensities) {
1800
+ intensities[targetPointIndex] = dataView.getUint16(pointOffset + 12, true);
1801
+ }
1802
+ if (classifications) {
1803
+ classifications[targetPointIndex] = readClassification(dataView, pointOffset, pointsFormatId);
1804
+ }
1805
+ if (gpsTimes && gpsTimeOffset >= 0) {
1806
+ gpsTimes[targetPointIndex] = dataView.getFloat64(pointOffset + gpsTimeOffset, true);
1807
+ }
1808
+ if (nir && nirOffset >= 0) {
1809
+ nir[targetPointIndex] = dataView.getUint16(pointOffset + nirOffset, true);
1810
+ }
1811
+ if (scanAngles) {
1812
+ scanAngles[targetPointIndex] =
1813
+ pointsFormatId <= 5
1814
+ ? dataView.getInt8(pointOffset + 16)
1815
+ : dataView.getInt16(pointOffset + 18, true);
1816
+ }
1817
+ if (userData) {
1818
+ userData[targetPointIndex] = dataView.getUint8(pointOffset + 17);
1819
+ }
1820
+ if (pointSourceIds) {
1821
+ pointSourceIds[targetPointIndex] = dataView.getUint16(
1822
+ pointOffset + (pointsFormatId <= 5 ? 18 : 20),
1823
+ true
1824
+ );
1825
+ }
1826
+ const returnFlags = dataView.getUint8(pointOffset + 14);
1827
+ const scanFlags = dataView.getUint8(pointOffset + 15);
1828
+ const classificationFlags = pointsFormatId <= 5 ? scanFlags >> 5 : scanFlags;
1829
+ if (syntheticFlags) {
1830
+ syntheticFlags[targetPointIndex] = classificationFlags & 1;
1831
+ }
1832
+ if (keyPointFlags) {
1833
+ keyPointFlags[targetPointIndex] = (classificationFlags >> 1) & 1;
1834
+ }
1835
+ if (withheldFlags) {
1836
+ withheldFlags[targetPointIndex] = (classificationFlags >> 2) & 1;
1837
+ }
1838
+ if (overlapFlags) {
1839
+ overlapFlags[targetPointIndex] = pointsFormatId <= 5 ? 0 : (classificationFlags >> 3) & 1;
1840
+ }
1841
+ if (returnNumbers) {
1842
+ returnNumbers[targetPointIndex] =
1843
+ pointsFormatId <= 5 ? returnFlags & 0x07 : returnFlags & 0x0f;
1844
+ }
1845
+ if (numberOfReturns) {
1846
+ numberOfReturns[targetPointIndex] =
1847
+ pointsFormatId <= 5 ? (returnFlags >> 3) & 0x07 : returnFlags >> 4;
1848
+ }
1849
+ if (scannerChannels) {
1850
+ scannerChannels[targetPointIndex] = pointsFormatId <= 5 ? 0 : (scanFlags >> 4) & 0x03;
1851
+ }
1852
+ if (scanDirectionFlags) {
1853
+ scanDirectionFlags[targetPointIndex] =
1854
+ pointsFormatId <= 5 ? (returnFlags >> 6) & 1 : (scanFlags >> 6) & 1;
1855
+ }
1856
+ if (edgeOfFlightLines) {
1857
+ edgeOfFlightLines[targetPointIndex] =
1858
+ pointsFormatId <= 5 ? (returnFlags >> 7) & 1 : (scanFlags >> 7) & 1;
1859
+ }
1860
+ if (waveforms) {
1861
+ const waveformOffset = getWaveformOffset(pointsFormatId);
1862
+ if (waveformOffset >= 0) {
1863
+ const waveformBytes = new Uint8Array(
1864
+ dataView.buffer,
1865
+ dataView.byteOffset + pointOffset + waveformOffset,
1866
+ 29
1867
+ );
1868
+ waveforms.set(waveformBytes, targetPointIndex * 29);
1869
+ }
1870
+ }
1871
+ if (extraBytes) {
1872
+ const extraByteOffset = getLAZPointDataRecordBaseLength(pointsFormatId);
1873
+ const extraByteCount = lasHeader.pointsStructSize - extraByteOffset;
1874
+ if (extraByteCount > 0) {
1875
+ extraBytes.set(
1876
+ new Uint8Array(
1877
+ dataView.buffer,
1878
+ dataView.byteOffset + pointOffset + extraByteOffset,
1879
+ extraByteCount
1880
+ ),
1881
+ targetPointIndex * extraByteCount
1882
+ );
1883
+ }
1884
+ }
1885
+ if (typedExtraBytes) {
1886
+ populateTypedExtraBytesFromDataView(
1887
+ dataView,
1888
+ pointOffset,
1889
+ pointsFormatId,
1890
+ targetPointIndex,
1891
+ typedExtraBytes
1892
+ );
1893
+ }
1894
+
1895
+ if (options.las?.compatibilityMode !== 'raw' && lasHeader.metadata?.compatibility) {
1896
+ populateLASCompatibilityPoint(
1897
+ dataView,
1898
+ pointOffset + getLAZPointDataRecordBaseLength(pointsFormatId),
1899
+ targetPointIndex,
1900
+ lasHeader.metadata.compatibility,
1901
+ target
1902
+ );
1903
+ }
1904
+
1905
+ if (colorOffset >= 0 && target.colors) {
1906
+ const red = dataView.getUint16(pointOffset + colorOffset, true);
1907
+ const green = dataView.getUint16(pointOffset + colorOffset + 2, true);
1908
+ const blue = dataView.getUint16(pointOffset + colorOffset + 4, true);
1909
+ target.colors[targetPointIndex * 4] = twoByteColor ? red / 256 : red;
1910
+ target.colors[targetPointIndex * 4 + 1] = twoByteColor ? green / 256 : green;
1911
+ target.colors[targetPointIndex * 4 + 2] = twoByteColor ? blue / 256 : blue;
1912
+ target.colors[targetPointIndex * 4 + 3] = 255;
1913
+ }
1914
+ }
1915
+ }
1916
+
1917
+ function readClassification(
1918
+ dataView: DataView,
1919
+ pointOffset: number,
1920
+ pointsFormatId: number
1921
+ ): number {
1922
+ return pointsFormatId <= 5
1923
+ ? dataView.getUint8(pointOffset + 15) & 0x1f
1924
+ : dataView.getUint8(pointOffset + 16);
1925
+ }
1926
+
1927
+ function hasPointColor(pointsFormatId: number): boolean {
1928
+ return getColorOffset(pointsFormatId) >= 0;
1929
+ }
1930
+
1931
+ function getColorOffset(pointsFormatId: number): number {
1932
+ switch (pointsFormatId) {
1933
+ case 2:
1934
+ return 20;
1935
+ case 3:
1936
+ return 28;
1937
+ case 5:
1938
+ return 28;
1939
+ case 7:
1940
+ case 8:
1941
+ case 10:
1942
+ return 30;
1943
+ default:
1944
+ return -1;
1945
+ }
1946
+ }
1947
+
1948
+ /** Return the fixed LAS waveform packet reference offset for a point format. */
1949
+ function getWaveformOffset(pointsFormatId: number): number {
1950
+ switch (pointsFormatId) {
1951
+ case 4:
1952
+ return 28;
1953
+ case 5:
1954
+ return 34;
1955
+ case 9:
1956
+ return 30;
1957
+ case 10:
1958
+ return 38;
1959
+ default:
1960
+ return -1;
1961
+ }
1962
+ }
1963
+
1964
+ function getGpsTimeOffset(pointsFormatId: number): number {
1965
+ return pointsFormatId === 1 ||
1966
+ pointsFormatId === 3 ||
1967
+ pointsFormatId === 4 ||
1968
+ pointsFormatId === 5
1969
+ ? 20
1970
+ : pointsFormatId >= 6 && pointsFormatId <= 10
1971
+ ? 22
1972
+ : -1;
1973
+ }
1974
+
1975
+ function getNirOffset(pointsFormatId: number): number {
1976
+ return pointsFormatId === 8 || pointsFormatId === 10 ? 36 : -1;
1977
+ }
1978
+
1979
+ function detectTwoByteColors(
1980
+ dataView: DataView,
1981
+ lasHeader: LASHeader,
1982
+ sourcePointIndex: number,
1983
+ batchSize: number,
1984
+ colorDepth?: number | string
1985
+ ): boolean {
1986
+ if (colorDepth === 8) {
1987
+ return false;
1988
+ }
1989
+ if (colorDepth === 16) {
1990
+ return true;
1991
+ }
1992
+ if (colorDepth !== 'auto') {
1993
+ return false;
1994
+ }
1995
+ const colorOffset = getColorOffset(lasHeader.pointsFormatId);
1996
+ if (colorOffset < 0) {
1997
+ return false;
1998
+ }
1999
+ for (let pointIndex = 0; pointIndex < batchSize; pointIndex++) {
2000
+ const pointOffset =
2001
+ lasHeader.pointsOffset + (sourcePointIndex + pointIndex) * lasHeader.pointsStructSize;
2002
+ if (
2003
+ dataView.getUint16(pointOffset + colorOffset, true) > 255 ||
2004
+ dataView.getUint16(pointOffset + colorOffset + 2, true) > 255 ||
2005
+ dataView.getUint16(pointOffset + colorOffset + 4, true) > 255
2006
+ ) {
2007
+ return true;
2008
+ }
2009
+ }
2010
+ return false;
2011
+ }
2012
+
2013
+ function getBatchSize(options: LASLoaderOptions): number {
2014
+ const batchSize = options.batchSize ?? options.core?.batchSize;
2015
+ return typeof batchSize === 'number' ? batchSize : DEFAULT_BATCH_SIZE;
2016
+ }
2017
+
2018
+ function parseLASZipVLR(bytes: Uint8Array, header: LASHeader): LASZipVLR {
2019
+ const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2020
+ let offset = header.headerSize || LAS_14_HEADER_LENGTH;
2021
+ const vlrCount = header.vlrCount || 0;
2022
+
2023
+ for (let index = 0; index < vlrCount; index++) {
2024
+ if (offset + 54 > bytes.byteLength) {
2025
+ throw new NeedsMoreData('LASLoader: incomplete VLR header');
2026
+ }
2027
+ const userId = readNullTerminatedAscii(bytes, offset + 2, 16);
2028
+ const recordId = dataView.getUint16(offset + 18, true);
2029
+ const recordLength = dataView.getUint16(offset + 20, true);
2030
+ const dataOffset = offset + 54;
2031
+
2032
+ if (dataOffset + recordLength > bytes.byteLength) {
2033
+ throw new NeedsMoreData('LASLoader: incomplete VLR data');
2034
+ }
2035
+ if (userId === LASZIP_USER_ID && recordId === LASZIP_RECORD_ID) {
2036
+ if (recordLength < 34) {
2037
+ throw new Error('LASLoader: malformed LASzip VLR');
2038
+ }
2039
+ const chunkSize = dataView.getUint32(dataOffset + 12, true);
2040
+ const itemCount = dataView.getUint16(dataOffset + 32, true);
2041
+ const itemDataByteLength = 34 + itemCount * 6;
2042
+ if (itemDataByteLength > recordLength) {
2043
+ throw new Error('LASLoader: malformed LASzip VLR item table');
2044
+ }
2045
+ let point14ItemVersion: 2 | 3 | 4 | null = null;
2046
+ let rgb14ItemVersion: 2 | 3 | 4 | null = null;
2047
+ let wavePacket13ItemVersion: 1 | null = null;
2048
+ let wavePacketItemVersion: 3 | 4 | null = null;
2049
+ let byte14ItemVersion: 2 | 3 | 4 | null = null;
2050
+ const items: LASZipItem[] = [];
2051
+ for (let itemIndex = 0; itemIndex < itemCount; itemIndex++) {
2052
+ const itemOffset = dataOffset + 34 + itemIndex * 6;
2053
+ const itemType = dataView.getUint16(itemOffset, true);
2054
+ const itemSize = dataView.getUint16(itemOffset + 2, true);
2055
+ const itemVersion = dataView.getUint16(itemOffset + 4, true);
2056
+ items.push({type: itemType, size: itemSize, version: itemVersion});
2057
+ if ([0, 6, 7, 8].includes(itemType)) {
2058
+ if (itemVersion !== 1 && itemVersion !== 2) {
2059
+ throw new Error(
2060
+ `LASLoader: unsupported legacy LASzip item type ${itemType} version ${itemVersion}`
2061
+ );
2062
+ }
2063
+ } else if (itemType === 9) {
2064
+ if (itemVersion !== 1) {
2065
+ throw new Error(`LASLoader: unsupported WavePacket13 item version ${itemVersion}`);
2066
+ }
2067
+ wavePacket13ItemVersion = itemVersion;
2068
+ } else if ([10, 11, 12, 14].includes(itemType)) {
2069
+ if (itemVersion !== 2 && itemVersion !== 3 && itemVersion !== 4) {
2070
+ throw new Error(
2071
+ `LASLoader: unsupported LAS 1.4 item type ${itemType} version ${itemVersion}`
2072
+ );
2073
+ }
2074
+ if (itemType === 10) {
2075
+ point14ItemVersion = itemVersion;
2076
+ } else if (itemType === 11 || itemType === 12) {
2077
+ rgb14ItemVersion = itemVersion;
2078
+ } else {
2079
+ byte14ItemVersion = itemVersion;
2080
+ }
2081
+ } else if (itemType === 13) {
2082
+ if (itemVersion !== 3 && itemVersion !== 4) {
2083
+ throw new Error(`LASLoader: unsupported WavePacket14 item version ${itemVersion}`);
2084
+ }
2085
+ wavePacketItemVersion = itemVersion;
2086
+ }
2087
+ }
2088
+ return {
2089
+ compressor: dataView.getUint16(dataOffset, true),
2090
+ coder: dataView.getUint16(dataOffset + 2, true),
2091
+ chunkSize,
2092
+ variableChunks:
2093
+ dataView.getUint16(dataOffset, true) !== 1 &&
2094
+ (chunkSize === 0 || chunkSize === VARIABLE_CHUNK_SIZE),
2095
+ items,
2096
+ point14ItemVersion,
2097
+ rgb14ItemVersion,
2098
+ wavePacket13ItemVersion,
2099
+ wavePacketItemVersion,
2100
+ byte14ItemVersion
2101
+ };
2102
+ }
2103
+ offset = dataOffset + recordLength;
2104
+ }
2105
+
2106
+ throw new Error('LASLoader: compressed LAZ file does not contain a LASzip VLR');
2107
+ }
2108
+
2109
+ function validateTypeScriptLAZSupport(header: LASHeader, laszip: LASZipVLR): void {
2110
+ if (![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].includes(header.pointsFormatId)) {
2111
+ throw new Error(
2112
+ `LASLoader: TypeScript LAZ streaming only supports point formats 0-10; received ${header.pointsFormatId}`
2113
+ );
2114
+ }
2115
+ if (header.pointsFormatId <= 5 && laszip.compressor !== 1 && laszip.compressor !== 2) {
2116
+ throw new Error(
2117
+ `LASLoader: legacy TypeScript LAZ decoding requires LASzip compressor 1 or 2; received ${laszip.compressor}`
2118
+ );
2119
+ }
2120
+ if (header.pointsFormatId >= 6 && laszip.compressor !== 3) {
2121
+ throw new Error(
2122
+ `LASLoader: LAS 1.4 TypeScript LAZ decoding requires LASzip compressor 3; received ${laszip.compressor}`
2123
+ );
2124
+ }
2125
+ if (laszip.coder !== 0) {
2126
+ throw new Error(
2127
+ `LASLoader: TypeScript LAZ decoding requires LASzip arithmetic coder 0; received ${laszip.coder}`
2128
+ );
2129
+ }
2130
+ validateLASZipItemLayout(header, laszip.items);
2131
+ if (header.pointsFormatId >= 6 && !laszip.point14ItemVersion) {
2132
+ throw new Error(
2133
+ `LASLoader: point format ${header.pointsFormatId} requires a Point14 LASzip item`
2134
+ );
2135
+ }
2136
+ if (
2137
+ (header.pointsFormatId === 4 || header.pointsFormatId === 5) &&
2138
+ !laszip.wavePacket13ItemVersion
2139
+ ) {
2140
+ throw new Error(
2141
+ `LASLoader: point format ${header.pointsFormatId} requires a WavePacket13 LASzip item`
2142
+ );
2143
+ }
2144
+ if (
2145
+ (header.pointsFormatId === 7 || header.pointsFormatId === 8 || header.pointsFormatId === 10) &&
2146
+ !laszip.rgb14ItemVersion
2147
+ ) {
2148
+ throw new Error(
2149
+ `LASLoader: point format ${header.pointsFormatId} requires an RGB14 or RGBNIR14 LASzip item`
2150
+ );
2151
+ }
2152
+ if (
2153
+ header.pointsFormatId >= 6 &&
2154
+ header.pointsStructSize > getLAZPointDataRecordBaseLength(header.pointsFormatId) &&
2155
+ !laszip.byte14ItemVersion
2156
+ ) {
2157
+ throw new Error(
2158
+ `LASLoader: point format ${header.pointsFormatId} with Extra Bytes requires a Byte14 LASzip item`
2159
+ );
2160
+ }
2161
+ if (
2162
+ (header.pointsFormatId === 9 || header.pointsFormatId === 10) &&
2163
+ !laszip.wavePacketItemVersion
2164
+ ) {
2165
+ throw new Error(
2166
+ `LASLoader: point format ${header.pointsFormatId} requires a WavePacket14 LASzip item`
2167
+ );
2168
+ }
2169
+ }
2170
+
2171
+ /** Validate the LASzip item sequence assumed by the point-format decoder. */
2172
+ function validateLASZipItemLayout(header: LASHeader, actualItems: LASZipItem[]): void {
2173
+ const pointDataRecordFormat = header.pointsFormatId;
2174
+ const baseLength = getLAZPointDataRecordBaseLength(pointDataRecordFormat);
2175
+ const extraByteCount = header.pointsStructSize - baseLength;
2176
+ if (extraByteCount < 0) {
2177
+ throw new Error(
2178
+ `LASLoader: point format ${pointDataRecordFormat} record length ${header.pointsStructSize} is smaller than ${baseLength}`
2179
+ );
2180
+ }
2181
+
2182
+ const expectedItems: Array<{type: number; size: number}> = [];
2183
+ switch (pointDataRecordFormat) {
2184
+ case 0:
2185
+ expectedItems.push({type: 6, size: 20});
2186
+ break;
2187
+ case 1:
2188
+ expectedItems.push({type: 6, size: 20}, {type: 7, size: 8});
2189
+ break;
2190
+ case 2:
2191
+ expectedItems.push({type: 6, size: 20}, {type: 8, size: 6});
2192
+ break;
2193
+ case 3:
2194
+ expectedItems.push({type: 6, size: 20}, {type: 7, size: 8}, {type: 8, size: 6});
2195
+ break;
2196
+ case 4:
2197
+ expectedItems.push({type: 6, size: 20}, {type: 7, size: 8}, {type: 9, size: 29});
2198
+ break;
2199
+ case 5:
2200
+ expectedItems.push(
2201
+ {type: 6, size: 20},
2202
+ {type: 7, size: 8},
2203
+ {type: 8, size: 6},
2204
+ {type: 9, size: 29}
2205
+ );
2206
+ break;
2207
+ case 6:
2208
+ expectedItems.push({type: 10, size: 30});
2209
+ break;
2210
+ case 7:
2211
+ expectedItems.push({type: 10, size: 30}, {type: 11, size: 6});
2212
+ break;
2213
+ case 8:
2214
+ expectedItems.push({type: 10, size: 30}, {type: 12, size: 8});
2215
+ break;
2216
+ case 9:
2217
+ expectedItems.push({type: 10, size: 30}, {type: 13, size: 29});
2218
+ break;
2219
+ case 10:
2220
+ expectedItems.push({type: 10, size: 30}, {type: 12, size: 8}, {type: 13, size: 29});
2221
+ break;
2222
+ default:
2223
+ return;
2224
+ }
2225
+ if (extraByteCount > 0) {
2226
+ expectedItems.push({type: pointDataRecordFormat <= 5 ? 0 : 14, size: extraByteCount});
2227
+ }
2228
+
2229
+ if (actualItems.length !== expectedItems.length) {
2230
+ throw new Error(
2231
+ `LASLoader: point format ${pointDataRecordFormat} has ${actualItems.length} LASzip items; expected ${expectedItems.length}`
2232
+ );
2233
+ }
2234
+ for (let itemIndex = 0; itemIndex < expectedItems.length; itemIndex++) {
2235
+ const actualItem = actualItems[itemIndex];
2236
+ const expectedItem = expectedItems[itemIndex];
2237
+ if (actualItem.type !== expectedItem.type) {
2238
+ throw new Error(
2239
+ `LASLoader: LASzip item ${itemIndex} has type ${actualItem.type}; expected ${expectedItem.type} for point format ${pointDataRecordFormat}`
2240
+ );
2241
+ }
2242
+ if (actualItem.size !== expectedItem.size) {
2243
+ throw new Error(
2244
+ `LASLoader: LASzip item ${itemIndex} has size ${actualItem.size}; expected ${expectedItem.size} for point format ${pointDataRecordFormat}`
2245
+ );
2246
+ }
2247
+ }
2248
+ }
2249
+
2250
+ /** Read enough input to parse the LASzip VLR without assuming VLR alignment. */
2251
+ async function readLASZipVLRFromInput(
2252
+ initialPending: Uint8Array<ArrayBufferLike>,
2253
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
2254
+ header: LASHeader
2255
+ ): Promise<{pending: Uint8Array<ArrayBufferLike>; laszip: LASZipVLR}> {
2256
+ let pending = initialPending;
2257
+ while (true) {
2258
+ try {
2259
+ return {pending, laszip: parseLASZipVLR(pending, header)};
2260
+ } catch (error) {
2261
+ if (!(error instanceof NeedsMoreData)) {
2262
+ throw error;
2263
+ }
2264
+ }
2265
+
2266
+ const next = await inputIterator.next();
2267
+ if (next.done) {
2268
+ throw new NeedsMoreData('LASLoader: incomplete LASzip VLR');
2269
+ }
2270
+ pending = concatenateUint8Arrays(pending, toUint8Array(next.value));
2271
+ }
2272
+ }
2273
+
2274
+ /** Collect a forward-only input once when metadata at EOF is required before decoding. */
2275
+ async function collectRemainingInputBytes(
2276
+ initialPending: Uint8Array<ArrayBufferLike>,
2277
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>
2278
+ ): Promise<Uint8Array<ArrayBufferLike>> {
2279
+ const chunks: Uint8Array<ArrayBufferLike>[] = [initialPending];
2280
+ let totalByteLength = initialPending.byteLength;
2281
+
2282
+ while (true) {
2283
+ const next = await inputIterator.next();
2284
+ if (next.done) {
2285
+ break;
2286
+ }
2287
+ const chunk = toUint8Array(next.value);
2288
+ chunks.push(chunk);
2289
+ totalByteLength += chunk.byteLength;
2290
+ }
2291
+
2292
+ if (chunks.length === 1) {
2293
+ return initialPending;
2294
+ }
2295
+ const bytes = new Uint8Array(totalByteLength);
2296
+ let byteOffset = 0;
2297
+ for (const chunk of chunks) {
2298
+ bytes.set(chunk, byteOffset);
2299
+ byteOffset += chunk.byteLength;
2300
+ }
2301
+ return bytes;
2302
+ }
2303
+
2304
+ /** Validate the trailing chunk table after progressively decoding a fixed-chunk LAZ file. */
2305
+ async function validateStreamedFixedLAZChunkTable(
2306
+ reader: BinaryChunkReader,
2307
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
2308
+ header: LASHeader,
2309
+ laszip: LASZipVLR,
2310
+ chunkTableOffset: number,
2311
+ decodedChunkByteLengths: readonly number[],
2312
+ options: LASLoaderOptions
2313
+ ): Promise<void> {
2314
+ if (options.las?.recoverMissingChunkTable && chunkTableOffset === header.pointsOffset) {
2315
+ if (laszip.variableChunks)
2316
+ throw new Error('LASLoader: cannot recover a missing variable-size LAZ chunk table');
2317
+ while (true) {
2318
+ if (reader.getAvailableByteLength())
2319
+ throw new Error('LASLoader: unexpected bytes after recovered LAZ chunks');
2320
+ const next = await inputIterator.next();
2321
+ if (next.done) return;
2322
+ reader.write(next.value);
2323
+ }
2324
+ }
2325
+ const decodedPointDataByteLength = decodedChunkByteLengths.reduce(
2326
+ (total, byteLength) => total + byteLength,
2327
+ 0
2328
+ );
2329
+ const decodedPointDataEnd =
2330
+ header.pointsOffset + LAZ_CHUNK_TABLE_POINTER_LENGTH + decodedPointDataByteLength;
2331
+ const effectiveChunkTableOffset =
2332
+ chunkTableOffset === -1 ? decodedPointDataEnd : chunkTableOffset;
2333
+ if (
2334
+ !Number.isSafeInteger(effectiveChunkTableOffset) ||
2335
+ effectiveChunkTableOffset < decodedPointDataEnd
2336
+ ) {
2337
+ throw new Error('LASLoader: LAZ chunk table byte lengths overlap the chunk table');
2338
+ }
2339
+
2340
+ const gapByteLength = effectiveChunkTableOffset - decodedPointDataEnd;
2341
+ await readUntilAvailable(
2342
+ reader,
2343
+ inputIterator,
2344
+ gapByteLength,
2345
+ 'LASLoader: incomplete LAZ chunk table'
2346
+ );
2347
+ reader.skip(gapByteLength);
2348
+ const chunks = await readStreamedLAZChunkTable(reader, inputIterator, header, laszip);
2349
+
2350
+ if (chunks.length !== decodedChunkByteLengths.length) {
2351
+ throw new Error(
2352
+ `LASLoader: LAZ chunk table contains ${chunks.length} chunks; decoded ${decodedChunkByteLengths.length}`
2353
+ );
2354
+ }
2355
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
2356
+ if (chunks[chunkIndex].byteLength !== decodedChunkByteLengths[chunkIndex]) {
2357
+ throw new Error(
2358
+ `LASLoader: LAZ chunk ${chunkIndex} has ${chunks[chunkIndex].byteLength} table bytes; decoded ${decodedChunkByteLengths[chunkIndex]}`
2359
+ );
2360
+ }
2361
+ }
2362
+ if (chunkTableOffset === -1) {
2363
+ await validateStreamedLAZChunkTableFooter(reader, inputIterator, effectiveChunkTableOffset);
2364
+ }
2365
+ }
2366
+
2367
+ /** Validate the trailing table pointer emitted by non-seekable LASzip writers. */
2368
+ async function validateStreamedLAZChunkTableFooter(
2369
+ reader: BinaryChunkReader,
2370
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
2371
+ expectedChunkTableOffset: number
2372
+ ): Promise<void> {
2373
+ const footer = new Uint8Array(LAZ_CHUNK_TABLE_POINTER_LENGTH);
2374
+ let byteLength = 0;
2375
+ while (true) {
2376
+ const availableByteLength = reader.getAvailableByteLength();
2377
+ if (availableByteLength > 0) {
2378
+ const bytes = reader.readBytes(Math.min(availableByteLength, 64 * 1024));
2379
+ appendTrailingBytes(footer, bytes);
2380
+ byteLength += bytes.byteLength;
2381
+ continue;
2382
+ }
2383
+ const next = await inputIterator.next();
2384
+ if (next.done) {
2385
+ break;
2386
+ }
2387
+ reader.write(next.value);
2388
+ }
2389
+ if (byteLength < LAZ_CHUNK_TABLE_POINTER_LENGTH) {
2390
+ throw new NeedsMoreData('LASLoader: incomplete non-seekable LAZ chunk-table footer');
2391
+ }
2392
+ const footerOffset = readUint64(new DataView(footer.buffer), 0);
2393
+ if (!Number.isSafeInteger(footerOffset) || footerOffset !== expectedChunkTableOffset) {
2394
+ throw new Error(
2395
+ `LASLoader: non-seekable LAZ footer points to ${footerOffset}; expected ${expectedChunkTableOffset}`
2396
+ );
2397
+ }
2398
+ }
2399
+
2400
+ /** Retain only the final bytes from a forward-only input stream. */
2401
+ function appendTrailingBytes(target: Uint8Array, bytes: Uint8Array): void {
2402
+ if (bytes.byteLength >= target.byteLength) {
2403
+ target.set(bytes.subarray(bytes.byteLength - target.byteLength));
2404
+ return;
2405
+ }
2406
+ target.copyWithin(0, bytes.byteLength);
2407
+ target.set(bytes, target.byteLength - bytes.byteLength);
2408
+ }
2409
+
2410
+ /** Read only as much trailing input as the compressed chunk table requires. */
2411
+ async function readStreamedLAZChunkTable(
2412
+ reader: BinaryChunkReader,
2413
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
2414
+ header: LASHeader,
2415
+ laszip: LASZipVLR
2416
+ ): Promise<LAZChunkTableEntry[]> {
2417
+ let candidateByteLength = 64;
2418
+ while (true) {
2419
+ const availableByteLength = reader.getAvailableByteLength();
2420
+ if (availableByteLength >= 8) {
2421
+ const checkpoint = reader.checkpoint();
2422
+ try {
2423
+ const bytes = reader.readBytes(Math.min(availableByteLength, candidateByteLength));
2424
+ const chunks = decodeAndValidateLAZChunkTable(bytes, header, laszip);
2425
+ reader.restore(checkpoint);
2426
+ return chunks;
2427
+ } catch (error) {
2428
+ reader.restore(checkpoint);
2429
+ if (!(error instanceof NeedsMoreData)) {
2430
+ throw error;
2431
+ }
2432
+ candidateByteLength *= 2;
2433
+ if (candidateByteLength <= availableByteLength) {
2434
+ continue;
2435
+ }
2436
+ }
2437
+ }
2438
+
2439
+ const next = await inputIterator.next();
2440
+ if (next.done) {
2441
+ throw new NeedsMoreData('LASLoader: incomplete LAZ chunk table');
2442
+ }
2443
+ reader.write(next.value);
2444
+ }
2445
+ }
2446
+
2447
+ async function readUntilAvailable(
2448
+ reader: BinaryChunkReader,
2449
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
2450
+ byteLength: number,
2451
+ errorMessage: string
2452
+ ): Promise<void> {
2453
+ while (!reader.hasAvailableBytes(byteLength)) {
2454
+ const next = await inputIterator.next();
2455
+ if (next.done) {
2456
+ throw new NeedsMoreData(errorMessage);
2457
+ }
2458
+ reader.write(next.value);
2459
+ }
2460
+ }
2461
+
2462
+ async function readLAZChunkByteLengthFromReader(
2463
+ reader: BinaryChunkReader,
2464
+ inputIterator: AsyncIterator<ArrayBufferLike | ArrayBufferView>,
2465
+ metadata: LAZChunkByteLengthMetadata
2466
+ ): Promise<number> {
2467
+ while (true) {
2468
+ const checkpoint = reader.checkpoint();
2469
+ let chunkByteLength: number | null;
2470
+ try {
2471
+ chunkByteLength = tryReadLAZChunkByteLengthFromReader(reader, metadata);
2472
+ } finally {
2473
+ reader.restore(checkpoint);
2474
+ }
2475
+
2476
+ if (chunkByteLength !== null) {
2477
+ await readUntilAvailable(
2478
+ reader,
2479
+ inputIterator,
2480
+ chunkByteLength,
2481
+ 'LASLoader: truncated LAZ chunk'
2482
+ );
2483
+ return chunkByteLength;
2484
+ }
2485
+
2486
+ const next = await inputIterator.next();
2487
+ if (next.done) {
2488
+ throw new NeedsMoreData('LASLoader: truncated LAZ chunk');
2489
+ }
2490
+ reader.write(next.value);
2491
+ }
2492
+ }
2493
+
2494
+ function tryReadLAZChunkByteLengthFromReader(
2495
+ reader: BinaryChunkReader,
2496
+ metadata: LAZChunkByteLengthMetadata
2497
+ ): number | null {
2498
+ if (metadata.pointDataRecordFormat < 6 || metadata.pointDataRecordFormat > 10) {
2499
+ return null;
2500
+ }
2501
+
2502
+ const extraByteCount =
2503
+ metadata.pointDataRecordLength -
2504
+ getLAZPointDataRecordBaseLength(metadata.pointDataRecordFormat);
2505
+ if (extraByteCount < 0) {
2506
+ throw new Error(`Invalid point record length ${metadata.pointDataRecordLength}`);
2507
+ }
2508
+ const sizeHeaderCount =
2509
+ getLAZChunkSizeHeaderBaseCount(metadata.pointDataRecordFormat) + extraByteCount;
2510
+ const sizeHeaderOffset = metadata.pointDataRecordLength + 4;
2511
+ const sizeHeaderByteLength = sizeHeaderCount * 4;
2512
+ const minimumByteLength = sizeHeaderOffset + sizeHeaderByteLength;
2513
+ if (!reader.hasAvailableBytes(minimumByteLength)) {
2514
+ return null;
2515
+ }
2516
+
2517
+ reader.skip(sizeHeaderOffset);
2518
+ let chunkByteLength = minimumByteLength;
2519
+ for (let index = 0; index < sizeHeaderCount; index++) {
2520
+ chunkByteLength += reader.readUint32LE();
2521
+ }
2522
+ return chunkByteLength;
2523
+ }
2524
+
2525
+ function getLAZPointDataRecordBaseLength(pointDataRecordFormat: number): number {
2526
+ switch (pointDataRecordFormat) {
2527
+ case 0:
2528
+ return 20;
2529
+ case 1:
2530
+ return 28;
2531
+ case 2:
2532
+ return 26;
2533
+ case 3:
2534
+ return 34;
2535
+ case 4:
2536
+ return 57;
2537
+ case 5:
2538
+ return 63;
2539
+ case 6:
2540
+ return 30;
2541
+ case 7:
2542
+ return 36;
2543
+ case 8:
2544
+ return 38;
2545
+ case 9:
2546
+ return 59;
2547
+ case 10:
2548
+ return 67;
2549
+ default:
2550
+ throw new Error(`Unsupported LAS point format ${pointDataRecordFormat}`);
2551
+ }
2552
+ }
2553
+
2554
+ function getLAZChunkSizeHeaderBaseCount(pointDataRecordFormat: number): number {
2555
+ switch (pointDataRecordFormat) {
2556
+ case 6:
2557
+ return 9;
2558
+ case 7:
2559
+ return 10;
2560
+ case 8:
2561
+ return 11;
2562
+ case 9:
2563
+ return 10;
2564
+ case 10:
2565
+ return 12;
2566
+ default:
2567
+ throw new Error(`Unsupported LAS 1.4 point format ${pointDataRecordFormat}`);
2568
+ }
2569
+ }
2570
+
2571
+ /** Describe a pointwise stream as one chunk, or read the compressed chunk table. */
2572
+ function readLAZFileChunks(
2573
+ bytes: Uint8Array,
2574
+ header: LASHeader,
2575
+ laszip: LASZipVLR,
2576
+ options: LASLoaderOptions
2577
+ ): LAZChunkTableEntry[] {
2578
+ if (laszip.compressor === 1) {
2579
+ return [{pointCount: header.pointsCount, byteLength: bytes.byteLength - header.pointsOffset}];
2580
+ }
2581
+ const chunkTableOffset = readLAZChunkTableOffset(
2582
+ new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength),
2583
+ header.pointsOffset
2584
+ );
2585
+ return readLAZChunkTable(bytes, header, laszip, chunkTableOffset, options);
2586
+ }
2587
+
2588
+ function readLAZChunkTable(
2589
+ bytes: Uint8Array,
2590
+ header: LASHeader,
2591
+ laszip: LASZipVLR,
2592
+ chunkTableOffset: number,
2593
+ options: LASLoaderOptions
2594
+ ): LAZChunkTableEntry[] {
2595
+ if (options.las?.recoverMissingChunkTable && chunkTableOffset === header.pointsOffset) {
2596
+ return recoverFixedLAZChunkTable(bytes, header, laszip);
2597
+ }
2598
+ if (
2599
+ !Number.isSafeInteger(chunkTableOffset) ||
2600
+ chunkTableOffset < 0 ||
2601
+ chunkTableOffset + 8 > bytes.byteLength
2602
+ ) {
2603
+ throw new NeedsMoreData('LASLoader: incomplete LAZ chunk table');
2604
+ }
2605
+ const chunks = decodeAndValidateLAZChunkTable(bytes.subarray(chunkTableOffset), header, laszip);
2606
+ const decodedByteLength = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
2607
+ if (header.pointsOffset + LAZ_CHUNK_TABLE_POINTER_LENGTH + decodedByteLength > chunkTableOffset) {
2608
+ throw new Error('LASLoader: LAZ chunk table byte lengths overlap the chunk table');
2609
+ }
2610
+ return chunks;
2611
+ }
2612
+
2613
+ /** Recover fixed chunks only when LASzip explicitly marks an interrupted writer. */
2614
+ function recoverFixedLAZChunkTable(
2615
+ bytes: Uint8Array,
2616
+ header: LASHeader,
2617
+ laszip: LASZipVLR
2618
+ ): LAZChunkTableEntry[] {
2619
+ if (laszip.variableChunks || !laszip.chunkSize) {
2620
+ throw new Error('LASLoader: cannot recover a missing variable-size LAZ chunk table');
2621
+ }
2622
+ const chunks: LAZChunkTableEntry[] = [];
2623
+ let byteOffset = header.pointsOffset + LAZ_CHUNK_TABLE_POINTER_LENGTH;
2624
+ let pointOffset = 0;
2625
+ while (pointOffset < header.pointsCount) {
2626
+ const pointCount = Math.min(laszip.chunkSize, header.pointsCount - pointOffset);
2627
+ const metadata = createLAZChunkMetadata(header, laszip, pointCount);
2628
+ const compressed = bytes.subarray(byteOffset);
2629
+ let byteLength: number;
2630
+ if (header.pointsFormatId <= 5) {
2631
+ const cursor = createLAZChunkDecoderCursor(compressed, metadata);
2632
+ const scratch = new Uint8Array(Math.min(pointCount, 256) * header.pointsStructSize);
2633
+ while (cursor.remainingPointCount) {
2634
+ cursor.decodeInto(scratch, 0, Math.min(cursor.remainingPointCount, 256));
2635
+ }
2636
+ byteLength = cursor.compressedByteOffset;
2637
+ } else {
2638
+ validateRecoveredLayeredPointCount(compressed, metadata);
2639
+ byteLength = getLAZChunkDeclaredByteLength(compressed, metadata);
2640
+ }
2641
+ if (!byteLength || byteOffset + byteLength > bytes.byteLength) {
2642
+ throw new NeedsMoreData('LASLoader: truncated LAZ chunk during table recovery');
2643
+ }
2644
+ chunks.push({pointCount, byteLength});
2645
+ byteOffset += byteLength;
2646
+ pointOffset += pointCount;
2647
+ }
2648
+ if (byteOffset !== bytes.byteLength) {
2649
+ throw new Error('LASLoader: unexpected bytes after recovered LAZ chunks');
2650
+ }
2651
+ return chunks;
2652
+ }
2653
+
2654
+ /** Require recovered layered chunks to agree with the inferred header point count. */
2655
+ function validateRecoveredLayeredPointCount(
2656
+ compressed: Uint8Array,
2657
+ metadata: LAZChunkMetadata
2658
+ ): void {
2659
+ if (compressed.byteLength < metadata.pointDataRecordLength + 4) {
2660
+ throw new NeedsMoreData('LASLoader: truncated recovered layered LAZ chunk header');
2661
+ }
2662
+ const view = new DataView(compressed.buffer, compressed.byteOffset, compressed.byteLength);
2663
+ const pointCount = view.getUint32(metadata.pointDataRecordLength, true);
2664
+ if (pointCount !== metadata.pointCount) {
2665
+ throw new Error(
2666
+ `LASLoader: recovered layered LAZ chunk contains ${pointCount} points; expected ${metadata.pointCount}`
2667
+ );
2668
+ }
2669
+ }
2670
+
2671
+ /** Decode and validate a LAZ chunk table whose first byte is the table version. */
2672
+ function decodeAndValidateLAZChunkTable(
2673
+ chunkTableBytes: Uint8Array,
2674
+ header: LASHeader,
2675
+ laszip: LASZipVLR
2676
+ ): LAZChunkTableEntry[] {
2677
+ if (chunkTableBytes.byteLength < 8) {
2678
+ throw new NeedsMoreData('LASLoader: incomplete LAZ chunk table');
2679
+ }
2680
+ const dataView = new DataView(
2681
+ chunkTableBytes.buffer,
2682
+ chunkTableBytes.byteOffset,
2683
+ chunkTableBytes.byteLength
2684
+ );
2685
+ const version = dataView.getUint32(0, true);
2686
+ const chunkCount = dataView.getUint32(4, true);
2687
+ if (version !== 0) {
2688
+ throw new Error(`LASLoader: unsupported LAZ chunk table version ${version}`);
2689
+ }
2690
+ if (chunkCount === 0) {
2691
+ if (header.pointsCount !== 0) {
2692
+ throw new Error('LASLoader: missing LAZ chunk table');
2693
+ }
2694
+ return [];
2695
+ }
2696
+ const chunks = decodeLAZChunkTable(chunkTableBytes.subarray(8), {
2697
+ chunkCount,
2698
+ pointCount: header.pointsCount,
2699
+ chunkSize: laszip.chunkSize,
2700
+ variable: laszip.variableChunks
2701
+ });
2702
+ let decodedPointCount = 0;
2703
+ for (const chunk of chunks) {
2704
+ if (chunk.pointCount === 0 || chunk.byteLength === 0) {
2705
+ throw new Error('LASLoader: invalid empty LAZ chunk-table entry');
2706
+ }
2707
+ decodedPointCount += chunk.pointCount;
2708
+ }
2709
+ if (decodedPointCount !== header.pointsCount) {
2710
+ throw new Error(
2711
+ `LASLoader: LAZ chunk table contains ${decodedPointCount} points; expected ${header.pointsCount}`
2712
+ );
2713
+ }
2714
+ return chunks;
2715
+ }
2716
+
2717
+ function createRawPointBatchState(
2718
+ batchSize: number,
2719
+ pointRecordLength: number,
2720
+ stats?: LAZStreamingDecodeStats
2721
+ ): RawPointBatchState {
2722
+ if (stats) {
2723
+ stats.rawBatchAllocations++;
2724
+ }
2725
+ return {
2726
+ rawBatch: new Uint8Array(batchSize * pointRecordLength),
2727
+ batchPointCount: 0,
2728
+ totalRead: 0,
2729
+ rawBatchAllocations: 1,
2730
+ stats
2731
+ };
2732
+ }
2733
+
2734
+ function createPointDataBatchState(
2735
+ batchSize: number,
2736
+ header: LASHeader,
2737
+ options: LASLoaderOptions
2738
+ ): PointDataBatchState {
2739
+ const PositionsType = options.las?.fp64 ? Float64Array : Float32Array;
2740
+ const positions = new PositionsType(batchSize * 3);
2741
+ const selection = getLASColumnSelection(options);
2742
+ const useRawColors =
2743
+ header.hasColor &&
2744
+ selection.color &&
2745
+ (options.las?.colorDepth === 16 || options.las?.colorDepth === 'auto');
2746
+ const colors =
2747
+ header.hasColor && selection.color && !useRawColors ? new Uint8Array(batchSize * 4) : null;
2748
+ const rawColors = useRawColors ? new Uint16Array(batchSize * 3) : null;
2749
+ const intensities = selection.intensity ? new Uint16Array(batchSize) : null;
2750
+ const classifications = selection.classification ? new Uint8Array(batchSize) : null;
2751
+ const syntheticFlags = selection.synthetic ? new Uint8Array(batchSize) : null;
2752
+ const keyPointFlags = selection.keyPoint ? new Uint8Array(batchSize) : null;
2753
+ const withheldFlags = selection.withheld ? new Uint8Array(batchSize) : null;
2754
+ const overlapFlags = selection.overlap ? new Uint8Array(batchSize) : null;
2755
+ const gpsTimes =
2756
+ selection.gpsTime && getGpsTimeOffset(header.pointsFormatId) >= 0
2757
+ ? new Float64Array(batchSize)
2758
+ : null;
2759
+ const nir =
2760
+ selection.nir &&
2761
+ (getNirOffset(header.pointsFormatId) >= 0 ||
2762
+ (options.las?.compatibilityMode !== 'raw' &&
2763
+ header.metadata?.compatibility?.nirByteOffset !== undefined))
2764
+ ? new Uint16Array(batchSize)
2765
+ : null;
2766
+ const scanAngles = selection.scanAngle ? new Int16Array(batchSize) : null;
2767
+ const userData = selection.userData ? new Uint8Array(batchSize) : null;
2768
+ const pointSourceIds = selection.pointSourceId ? new Uint16Array(batchSize) : null;
2769
+ const returnNumbers = selection.returnNumber ? new Uint8Array(batchSize) : null;
2770
+ const numberOfReturns = selection.numberOfReturns ? new Uint8Array(batchSize) : null;
2771
+ const scannerChannels = selection.scannerChannel ? new Uint8Array(batchSize) : null;
2772
+ const scanDirectionFlags = selection.scanDirectionFlag ? new Uint8Array(batchSize) : null;
2773
+ const edgeOfFlightLines = selection.edgeOfFlightLine ? new Uint8Array(batchSize) : null;
2774
+ const waveforms =
2775
+ selection.waveform && getWaveformOffset(header.pointsFormatId) >= 0
2776
+ ? new Uint8Array(batchSize * 29)
2777
+ : null;
2778
+ const extraByteCount = Math.max(
2779
+ 0,
2780
+ header.pointsStructSize - getLAZPointDataRecordBaseLength(header.pointsFormatId)
2781
+ );
2782
+ const extraBytes =
2783
+ selection.extraBytes && options.las?.extraBytes !== 'typed' && extraByteCount
2784
+ ? new Uint8Array(batchSize * extraByteCount)
2785
+ : null;
2786
+ const typedExtraBytes =
2787
+ selection.extraBytes && options.las?.extraBytes === 'typed'
2788
+ ? createTypedExtraBytesAttributes(batchSize, header)
2789
+ : null;
2790
+ const typedExtraBytesSource =
2791
+ typedExtraBytes?.length && extraByteCount ? new Uint8Array(batchSize * extraByteCount) : null;
2792
+ const reconstructColumns =
2793
+ selection.classification ||
2794
+ selection.returnNumber ||
2795
+ selection.numberOfReturns ||
2796
+ selection.scanAngle ||
2797
+ selection.overlap ||
2798
+ selection.scannerChannel ||
2799
+ selection.nir;
2800
+ const compatibilityLayout =
2801
+ options.las?.compatibilityMode === 'raw' || !reconstructColumns
2802
+ ? null
2803
+ : header.metadata?.compatibility || null;
2804
+ const compatibilityExtraBytesSource =
2805
+ compatibilityLayout && !extraBytes && !typedExtraBytesSource
2806
+ ? new Uint8Array(batchSize * extraByteCount)
2807
+ : null;
2808
+ return {
2809
+ batchCapacity: batchSize,
2810
+ positions,
2811
+ colors,
2812
+ rawColors,
2813
+ intensities,
2814
+ classifications,
2815
+ syntheticFlags,
2816
+ keyPointFlags,
2817
+ withheldFlags,
2818
+ overlapFlags,
2819
+ gpsTimes,
2820
+ nir,
2821
+ scanAngles,
2822
+ userData,
2823
+ pointSourceIds,
2824
+ returnNumbers,
2825
+ numberOfReturns,
2826
+ scannerChannels,
2827
+ scanDirectionFlags,
2828
+ edgeOfFlightLines,
2829
+ waveforms,
2830
+ extraBytes,
2831
+ typedExtraBytes,
2832
+ typedExtraBytesSource,
2833
+ compatibilityLayout,
2834
+ compatibilityExtraBytesSource,
2835
+ target: {
2836
+ positions,
2837
+ intensities,
2838
+ classifications,
2839
+ syntheticFlags,
2840
+ keyPointFlags,
2841
+ withheldFlags,
2842
+ overlapFlags,
2843
+ gpsTimes,
2844
+ nir,
2845
+ scanAngles,
2846
+ userData,
2847
+ pointSourceIds,
2848
+ returnNumbers,
2849
+ numberOfReturns,
2850
+ scannerChannels,
2851
+ scanDirectionFlags,
2852
+ edgeOfFlightLines,
2853
+ waveforms,
2854
+ extraBytes: extraBytes || typedExtraBytesSource || compatibilityExtraBytesSource,
2855
+ colors,
2856
+ rawColors,
2857
+ pointOffset: 0,
2858
+ scale: header.scale,
2859
+ offset: header.offset
2860
+ },
2861
+ batchPointCount: 0,
2862
+ totalRead: 0
2863
+ };
2864
+ }
2865
+
2866
+ /** Create typed Extra Bytes output buffers from the descriptors in a LAS header. */
2867
+ function createTypedExtraBytesAttributes(
2868
+ batchSize: number,
2869
+ header: LASHeader
2870
+ ): LASTypedExtraBytesAttribute[] {
2871
+ const extraByteCount =
2872
+ header.pointsStructSize - getLAZPointDataRecordBaseLength(header.pointsFormatId);
2873
+ return createLASTypedExtraBytesAttributes(
2874
+ batchSize,
2875
+ header.metadata?.extraBytes || [],
2876
+ extraByteCount
2877
+ );
2878
+ }
2879
+
2880
+ /** Return the byte width of one scalar Extra Bytes value. */
2881
+ function getExtraBytesScalarByteLength(dataType: number): number {
2882
+ if (dataType <= 2) return 1;
2883
+ if (dataType <= 4) return 2;
2884
+ if (dataType <= 6 || dataType === 9) return 4;
2885
+ if (dataType === 7 || dataType === 8 || dataType === 10) return 8;
2886
+ return 0;
2887
+ }
2888
+
2889
+ /** Decode typed Extra Bytes directly from uncompressed LAS point records. */
2890
+ function populateTypedExtraBytesFromDataView(
2891
+ dataView: DataView,
2892
+ pointOffset: number,
2893
+ pointDataRecordFormat: number,
2894
+ targetPointIndex: number,
2895
+ attributes: LASTypedExtraBytesAttribute[]
2896
+ ): void {
2897
+ const extraByteBaseOffset = pointOffset + getLAZPointDataRecordBaseLength(pointDataRecordFormat);
2898
+ for (const attribute of attributes) {
2899
+ const targetOffset = targetPointIndex * attribute.size;
2900
+ const sourceOffset = extraByteBaseOffset + attribute.byteOffset;
2901
+ for (let componentIndex = 0; componentIndex < attribute.size; componentIndex++) {
2902
+ populateLASTypedExtraBytesComponent(
2903
+ dataView,
2904
+ sourceOffset + componentIndex * getExtraBytesScalarByteLength(attribute.scalarDataType),
2905
+ attribute,
2906
+ targetOffset + componentIndex,
2907
+ componentIndex
2908
+ );
2909
+ }
2910
+ }
2911
+ }
2912
+
2913
+ /** Project packed direct-decoder Extra Bytes into descriptor-defined typed columns. */
2914
+ function populateTypedExtraBytesFromPacked(
2915
+ packedExtraBytes: Uint8Array,
2916
+ extraByteCount: number,
2917
+ pointOffset: number,
2918
+ pointCount: number,
2919
+ attributes: LASTypedExtraBytesAttribute[]
2920
+ ): void {
2921
+ const dataView = new DataView(
2922
+ packedExtraBytes.buffer,
2923
+ packedExtraBytes.byteOffset,
2924
+ packedExtraBytes.byteLength
2925
+ );
2926
+ for (let pointIndex = 0; pointIndex < pointCount; pointIndex++) {
2927
+ const targetPointIndex = pointOffset + pointIndex;
2928
+ const sourceOffset = targetPointIndex * extraByteCount;
2929
+ for (const attribute of attributes) {
2930
+ const targetOffset = targetPointIndex * attribute.size;
2931
+ const scalarByteLength = getExtraBytesScalarByteLength(attribute.scalarDataType);
2932
+ for (let componentIndex = 0; componentIndex < attribute.size; componentIndex++) {
2933
+ populateLASTypedExtraBytesComponent(
2934
+ dataView,
2935
+ sourceOffset + attribute.byteOffset + componentIndex * scalarByteLength,
2936
+ attribute,
2937
+ targetOffset + componentIndex,
2938
+ componentIndex
2939
+ );
2940
+ }
2941
+ }
2942
+ }
2943
+ }
2944
+
2945
+ /** Populate typed Extra Bytes produced by the direct LAZ target for newly decoded points. */
2946
+ function populateDecodedTypedExtraBytes(
2947
+ state: PointDataBatchState,
2948
+ pointOffset: number,
2949
+ pointCount: number
2950
+ ): void {
2951
+ const compatibilityBytes =
2952
+ state.extraBytes || state.typedExtraBytesSource || state.compatibilityExtraBytesSource;
2953
+ if (state.compatibilityLayout && compatibilityBytes && pointCount) {
2954
+ const view = new DataView(
2955
+ compatibilityBytes.buffer,
2956
+ compatibilityBytes.byteOffset,
2957
+ compatibilityBytes.byteLength
2958
+ );
2959
+ const extraByteCount = compatibilityBytes.length / state.batchCapacity;
2960
+ for (let pointIndex = pointOffset; pointIndex < pointOffset + pointCount; pointIndex++) {
2961
+ populateLASCompatibilityPoint(
2962
+ view,
2963
+ pointIndex * extraByteCount,
2964
+ pointIndex,
2965
+ state.compatibilityLayout,
2966
+ state
2967
+ );
2968
+ }
2969
+ }
2970
+ if (!state.typedExtraBytesSource || !state.typedExtraBytes?.length || pointCount === 0) {
2971
+ return;
2972
+ }
2973
+ populateTypedExtraBytesFromPacked(
2974
+ state.typedExtraBytesSource,
2975
+ state.typedExtraBytesSource.length / state.batchCapacity,
2976
+ pointOffset,
2977
+ pointCount,
2978
+ state.typedExtraBytes
2979
+ );
2980
+ }
2981
+
2982
+ function getLAZStreamingDecodeStats(
2983
+ options: LASLoaderOptions
2984
+ ): LAZStreamingDecodeStats | undefined {
2985
+ return (options.las as {lazStreamingStats?: LAZStreamingDecodeStats} | undefined)
2986
+ ?.lazStreamingStats;
2987
+ }
2988
+
2989
+ function recordReadBytesStats(
2990
+ reader: BinaryChunkReader,
2991
+ byteLength: number,
2992
+ stats?: LAZStreamingDecodeStats
2993
+ ): void {
2994
+ if (!stats) {
2995
+ return;
2996
+ }
2997
+ const bufferOffsets = reader.findBufferOffsets(byteLength);
2998
+ if (bufferOffsets && bufferOffsets.length > 1) {
2999
+ stats.copiedBytes += byteLength;
3000
+ stats.chunkConcatenations++;
3001
+ }
3002
+ }
3003
+
3004
+ function recordDecodedChunkAllocation(byteLength: number, stats?: LAZStreamingDecodeStats): void {
3005
+ if (stats) {
3006
+ stats.copiedBytes += byteLength;
3007
+ stats.decodedChunkAllocations++;
3008
+ }
3009
+ }
3010
+
3011
+ function* appendDecodedLAZChunk(
3012
+ compressedChunk: Uint8Array,
3013
+ metadata: LAZChunkByteLengthMetadata,
3014
+ header: LASHeader,
3015
+ state: RawPointBatchState
3016
+ ): Iterable<LASDecodedChunk> {
3017
+ const pointRecordLength = header.pointsStructSize;
3018
+ const decoder = createLAZChunkDecoderCursor(compressedChunk, metadata);
3019
+
3020
+ while (decoder.remainingPointCount > 0) {
3021
+ const batchCapacity = state.rawBatch.byteLength / pointRecordLength;
3022
+ const batchRemainingPointCount = batchCapacity - state.batchPointCount;
3023
+ const pointsDecoded = decoder.decodeInto(
3024
+ state.rawBatch,
3025
+ state.batchPointCount * pointRecordLength,
3026
+ batchRemainingPointCount
3027
+ );
3028
+ state.batchPointCount += pointsDecoded;
3029
+
3030
+ if (state.batchPointCount === batchCapacity) {
3031
+ const batch = flushRawPointBatch(header, state);
3032
+ if (batch) {
3033
+ yield batch;
3034
+ }
3035
+ }
3036
+ }
3037
+ }
3038
+
3039
+ function* appendRawPointChunk(
3040
+ rawChunk: Uint8Array,
3041
+ header: LASHeader,
3042
+ state: RawPointBatchState
3043
+ ): Iterable<LASDecodedChunk> {
3044
+ const pointRecordLength = header.pointsStructSize;
3045
+ const chunkPointCount = rawChunk.byteLength / pointRecordLength;
3046
+
3047
+ for (let chunkPointIndex = 0; chunkPointIndex < chunkPointCount; chunkPointIndex++) {
3048
+ const sourceOffset = chunkPointIndex * pointRecordLength;
3049
+ const targetOffset = state.batchPointCount * pointRecordLength;
3050
+ state.rawBatch.set(
3051
+ rawChunk.subarray(sourceOffset, sourceOffset + pointRecordLength),
3052
+ targetOffset
3053
+ );
3054
+ state.batchPointCount++;
3055
+
3056
+ if (state.batchPointCount === state.rawBatch.byteLength / pointRecordLength) {
3057
+ const batch = flushRawPointBatch(header, state);
3058
+ if (batch) {
3059
+ yield batch;
3060
+ }
3061
+ }
3062
+ }
3063
+ }
3064
+
3065
+ function flushRawPointBatch(header: LASHeader, state: RawPointBatchState): LASDecodedChunk | null {
3066
+ if (state.batchPointCount === 0) {
3067
+ return null;
3068
+ }
3069
+
3070
+ const byteLength = state.batchPointCount * header.pointsStructSize;
3071
+ const fullBatch = byteLength === state.rawBatch.byteLength;
3072
+ const arrayBuffer = fullBatch
3073
+ ? state.rawBatch.buffer
3074
+ : state.rawBatch.buffer.slice(0, byteLength);
3075
+ state.totalRead += state.batchPointCount;
3076
+ const batchHeader = {
3077
+ ...header,
3078
+ pointsOffset: 0,
3079
+ pointsCount: state.batchPointCount,
3080
+ totalRead: state.totalRead
3081
+ };
3082
+ state.batchPointCount = 0;
3083
+ if (fullBatch) {
3084
+ state.rawBatch = new Uint8Array(state.rawBatch.byteLength);
3085
+ state.rawBatchAllocations++;
3086
+ if (state.stats) {
3087
+ state.stats.rawBatchAllocations++;
3088
+ }
3089
+ }
3090
+ return {arrayBuffer, header: batchHeader};
3091
+ }
3092
+
3093
+ function flushPointDataBatch(
3094
+ header: LASHeader,
3095
+ state: PointDataBatchState,
3096
+ options: LASLoaderOptions
3097
+ ): LASArrowTable | null {
3098
+ if (state.batchPointCount === 0) {
3099
+ return null;
3100
+ }
3101
+
3102
+ state.totalRead += state.batchPointCount;
3103
+ const batchHeader = {
3104
+ ...header,
3105
+ pointsOffset: 0,
3106
+ pointsCount: state.batchPointCount,
3107
+ totalRead: state.totalRead
3108
+ };
3109
+ const batchPointCount = state.batchPointCount;
3110
+ const fullBatch = batchPointCount === state.batchCapacity;
3111
+ const positions = fullBatch ? state.positions : state.positions.subarray(0, batchPointCount * 3);
3112
+ const intensities = state.intensities
3113
+ ? fullBatch
3114
+ ? state.intensities
3115
+ : state.intensities.subarray(0, batchPointCount)
3116
+ : null;
3117
+ const classifications = state.classifications
3118
+ ? fullBatch
3119
+ ? state.classifications
3120
+ : state.classifications.subarray(0, batchPointCount)
3121
+ : null;
3122
+ const syntheticFlags = state.syntheticFlags
3123
+ ? fullBatch
3124
+ ? state.syntheticFlags
3125
+ : state.syntheticFlags.subarray(0, batchPointCount)
3126
+ : null;
3127
+ const keyPointFlags = state.keyPointFlags
3128
+ ? fullBatch
3129
+ ? state.keyPointFlags
3130
+ : state.keyPointFlags.subarray(0, batchPointCount)
3131
+ : null;
3132
+ const withheldFlags = state.withheldFlags
3133
+ ? fullBatch
3134
+ ? state.withheldFlags
3135
+ : state.withheldFlags.subarray(0, batchPointCount)
3136
+ : null;
3137
+ const overlapFlags = state.overlapFlags
3138
+ ? fullBatch
3139
+ ? state.overlapFlags
3140
+ : state.overlapFlags.subarray(0, batchPointCount)
3141
+ : null;
3142
+ const colors = state.colors
3143
+ ? fullBatch
3144
+ ? state.colors
3145
+ : state.colors.subarray(0, batchPointCount * 4)
3146
+ : state.rawColors
3147
+ ? convertRawColorsToUint8(state.rawColors, batchPointCount, options)
3148
+ : null;
3149
+ const gpsTimes = state.gpsTimes
3150
+ ? fullBatch
3151
+ ? state.gpsTimes
3152
+ : state.gpsTimes.subarray(0, batchPointCount)
3153
+ : null;
3154
+ const nir = state.nir ? (fullBatch ? state.nir : state.nir.subarray(0, batchPointCount)) : null;
3155
+ const scanAngles = state.scanAngles
3156
+ ? fullBatch
3157
+ ? state.scanAngles
3158
+ : state.scanAngles.subarray(0, batchPointCount)
3159
+ : null;
3160
+ const userData = state.userData
3161
+ ? fullBatch
3162
+ ? state.userData
3163
+ : state.userData.subarray(0, batchPointCount)
3164
+ : null;
3165
+ const pointSourceIds = state.pointSourceIds
3166
+ ? fullBatch
3167
+ ? state.pointSourceIds
3168
+ : state.pointSourceIds.subarray(0, batchPointCount)
3169
+ : null;
3170
+ const returnNumbers = state.returnNumbers
3171
+ ? fullBatch
3172
+ ? state.returnNumbers
3173
+ : state.returnNumbers.subarray(0, batchPointCount)
3174
+ : null;
3175
+ const numberOfReturns = state.numberOfReturns
3176
+ ? fullBatch
3177
+ ? state.numberOfReturns
3178
+ : state.numberOfReturns.subarray(0, batchPointCount)
3179
+ : null;
3180
+ const scannerChannels = state.scannerChannels
3181
+ ? fullBatch
3182
+ ? state.scannerChannels
3183
+ : state.scannerChannels.subarray(0, batchPointCount)
3184
+ : null;
3185
+ const scanDirectionFlags = state.scanDirectionFlags
3186
+ ? fullBatch
3187
+ ? state.scanDirectionFlags
3188
+ : state.scanDirectionFlags.subarray(0, batchPointCount)
3189
+ : null;
3190
+ const edgeOfFlightLines = state.edgeOfFlightLines
3191
+ ? fullBatch
3192
+ ? state.edgeOfFlightLines
3193
+ : state.edgeOfFlightLines.subarray(0, batchPointCount)
3194
+ : null;
3195
+ const waveforms = state.waveforms
3196
+ ? fullBatch
3197
+ ? state.waveforms
3198
+ : state.waveforms.subarray(0, batchPointCount * 29)
3199
+ : null;
3200
+ const extraByteCount = state.extraBytes ? state.extraBytes.length / state.batchCapacity : 0;
3201
+ const extraBytes = state.extraBytes
3202
+ ? fullBatch
3203
+ ? state.extraBytes
3204
+ : state.extraBytes.subarray(0, batchPointCount * extraByteCount)
3205
+ : null;
3206
+ const typedExtraBytes = state.typedExtraBytes
3207
+ ? state.typedExtraBytes.map(attribute => ({
3208
+ ...attribute,
3209
+ value: fullBatch
3210
+ ? attribute.value
3211
+ : attribute.value.subarray(0, batchPointCount * attribute.size)
3212
+ }))
3213
+ : null;
3214
+ const table = makeLASArrowTableFromAttributes(
3215
+ batchHeader,
3216
+ positions,
3217
+ colors,
3218
+ intensities,
3219
+ classifications,
3220
+ syntheticFlags,
3221
+ keyPointFlags,
3222
+ withheldFlags,
3223
+ overlapFlags,
3224
+ gpsTimes,
3225
+ nir,
3226
+ scanAngles,
3227
+ userData,
3228
+ pointSourceIds,
3229
+ returnNumbers,
3230
+ numberOfReturns,
3231
+ scannerChannels,
3232
+ scanDirectionFlags,
3233
+ edgeOfFlightLines,
3234
+ waveforms,
3235
+ extraBytes,
3236
+ typedExtraBytes
3237
+ );
3238
+
3239
+ state.batchPointCount = 0;
3240
+ if (fullBatch) {
3241
+ const PositionsType = options.las?.fp64 ? Float64Array : Float32Array;
3242
+ state.positions = new PositionsType(state.positions.length);
3243
+ state.colors = state.colors ? new Uint8Array(state.colors.length) : null;
3244
+ state.rawColors = state.rawColors ? new Uint16Array(state.rawColors.length) : null;
3245
+ state.intensities = state.intensities ? new Uint16Array(state.intensities.length) : null;
3246
+ state.classifications = state.classifications
3247
+ ? new Uint8Array(state.classifications.length)
3248
+ : null;
3249
+ state.syntheticFlags = state.syntheticFlags
3250
+ ? new Uint8Array(state.syntheticFlags.length)
3251
+ : null;
3252
+ state.keyPointFlags = state.keyPointFlags ? new Uint8Array(state.keyPointFlags.length) : null;
3253
+ state.withheldFlags = state.withheldFlags ? new Uint8Array(state.withheldFlags.length) : null;
3254
+ state.overlapFlags = state.overlapFlags ? new Uint8Array(state.overlapFlags.length) : null;
3255
+ state.gpsTimes = state.gpsTimes ? new Float64Array(state.gpsTimes.length) : null;
3256
+ state.nir = state.nir ? new Uint16Array(state.nir.length) : null;
3257
+ state.scanAngles = state.scanAngles ? new Int16Array(state.scanAngles.length) : null;
3258
+ state.userData = state.userData ? new Uint8Array(state.userData.length) : null;
3259
+ state.pointSourceIds = state.pointSourceIds
3260
+ ? new Uint16Array(state.pointSourceIds.length)
3261
+ : null;
3262
+ state.returnNumbers = state.returnNumbers ? new Uint8Array(state.returnNumbers.length) : null;
3263
+ state.numberOfReturns = state.numberOfReturns
3264
+ ? new Uint8Array(state.numberOfReturns.length)
3265
+ : null;
3266
+ state.scannerChannels = state.scannerChannels
3267
+ ? new Uint8Array(state.scannerChannels.length)
3268
+ : null;
3269
+ state.scanDirectionFlags = state.scanDirectionFlags
3270
+ ? new Uint8Array(state.scanDirectionFlags.length)
3271
+ : null;
3272
+ state.edgeOfFlightLines = state.edgeOfFlightLines
3273
+ ? new Uint8Array(state.edgeOfFlightLines.length)
3274
+ : null;
3275
+ state.waveforms = state.waveforms ? new Uint8Array(state.waveforms.length) : null;
3276
+ state.extraBytes = state.extraBytes ? new Uint8Array(state.extraBytes.length) : null;
3277
+ state.typedExtraBytes = state.typedExtraBytes
3278
+ ? state.typedExtraBytes.map(attribute => ({
3279
+ ...attribute,
3280
+ value: createLASTypedExtraBytesValue(
3281
+ attribute.scalarDataType,
3282
+ attribute.value.length,
3283
+ attribute.outputFloat64
3284
+ )
3285
+ }))
3286
+ : null;
3287
+ state.typedExtraBytesSource = state.typedExtraBytesSource
3288
+ ? new Uint8Array(state.typedExtraBytesSource.length)
3289
+ : null;
3290
+ state.target.positions = state.positions;
3291
+ state.target.colors = state.colors;
3292
+ state.target.rawColors = state.rawColors;
3293
+ state.target.intensities = state.intensities;
3294
+ state.target.classifications = state.classifications;
3295
+ state.target.syntheticFlags = state.syntheticFlags;
3296
+ state.target.keyPointFlags = state.keyPointFlags;
3297
+ state.target.withheldFlags = state.withheldFlags;
3298
+ state.target.overlapFlags = state.overlapFlags;
3299
+ state.target.gpsTimes = state.gpsTimes;
3300
+ state.target.nir = state.nir;
3301
+ state.target.scanAngles = state.scanAngles;
3302
+ state.target.userData = state.userData;
3303
+ state.target.pointSourceIds = state.pointSourceIds;
3304
+ state.target.returnNumbers = state.returnNumbers;
3305
+ state.target.numberOfReturns = state.numberOfReturns;
3306
+ state.target.scannerChannels = state.scannerChannels;
3307
+ state.target.scanDirectionFlags = state.scanDirectionFlags;
3308
+ state.target.edgeOfFlightLines = state.edgeOfFlightLines;
3309
+ state.target.waveforms = state.waveforms;
3310
+ state.compatibilityExtraBytesSource = state.compatibilityExtraBytesSource
3311
+ ? new Uint8Array(state.compatibilityExtraBytesSource.length)
3312
+ : null;
3313
+ state.target.extraBytes =
3314
+ state.extraBytes || state.typedExtraBytesSource || state.compatibilityExtraBytesSource;
3315
+ }
3316
+ return table;
3317
+ }
3318
+
3319
+ /** Resolve optional Arrow columns once before allocating or decoding a point batch. */
3320
+ function getLASColumnSelection(options: LASLoaderOptions): {
3321
+ intensity: boolean;
3322
+ classification: boolean;
3323
+ synthetic: boolean;
3324
+ keyPoint: boolean;
3325
+ withheld: boolean;
3326
+ overlap: boolean;
3327
+ color: boolean;
3328
+ gpsTime: boolean;
3329
+ nir: boolean;
3330
+ scanAngle: boolean;
3331
+ userData: boolean;
3332
+ pointSourceId: boolean;
3333
+ returnNumber: boolean;
3334
+ numberOfReturns: boolean;
3335
+ scannerChannel: boolean;
3336
+ scanDirectionFlag: boolean;
3337
+ edgeOfFlightLine: boolean;
3338
+ waveform: boolean;
3339
+ extraBytes: boolean;
3340
+ } {
3341
+ const columns = options.las?.columns;
3342
+ if (!columns) {
3343
+ return {
3344
+ intensity: true,
3345
+ classification: true,
3346
+ synthetic: true,
3347
+ keyPoint: true,
3348
+ withheld: true,
3349
+ overlap: true,
3350
+ color: true,
3351
+ gpsTime: true,
3352
+ nir: true,
3353
+ scanAngle: true,
3354
+ userData: true,
3355
+ pointSourceId: true,
3356
+ returnNumber: true,
3357
+ numberOfReturns: true,
3358
+ scannerChannel: true,
3359
+ scanDirectionFlag: true,
3360
+ edgeOfFlightLine: true,
3361
+ waveform: true,
3362
+ extraBytes: options.las?.extraBytes === 'typed'
3363
+ };
3364
+ }
3365
+
3366
+ let intensity = false;
3367
+ let classification = false;
3368
+ let synthetic = false;
3369
+ let keyPoint = false;
3370
+ let withheld = false;
3371
+ let overlap = false;
3372
+ let color = false;
3373
+ let gpsTime = false;
3374
+ let nir = false;
3375
+ let scanAngle = false;
3376
+ let userData = false;
3377
+ let pointSourceId = false;
3378
+ let returnNumber = false;
3379
+ let numberOfReturns = false;
3380
+ let scannerChannel = false;
3381
+ let scanDirectionFlag = false;
3382
+ let edgeOfFlightLine = false;
3383
+ let waveform = false;
3384
+ let extraBytes = false;
3385
+ for (const column of columns as readonly string[]) {
3386
+ switch (column) {
3387
+ case 'POSITION':
3388
+ break;
3389
+ case 'intensity':
3390
+ intensity = true;
3391
+ break;
3392
+ case 'classification':
3393
+ classification = true;
3394
+ break;
3395
+ case 'synthetic':
3396
+ synthetic = true;
3397
+ break;
3398
+ case 'keyPoint':
3399
+ keyPoint = true;
3400
+ break;
3401
+ case 'withheld':
3402
+ withheld = true;
3403
+ break;
3404
+ case 'overlap':
3405
+ overlap = true;
3406
+ break;
3407
+ case 'COLOR_0':
3408
+ color = true;
3409
+ break;
3410
+ case 'GPS_TIME':
3411
+ gpsTime = true;
3412
+ break;
3413
+ case 'NIR':
3414
+ nir = true;
3415
+ break;
3416
+ case 'scanAngle':
3417
+ scanAngle = true;
3418
+ break;
3419
+ case 'userData':
3420
+ userData = true;
3421
+ break;
3422
+ case 'pointSourceId':
3423
+ pointSourceId = true;
3424
+ break;
3425
+ case 'returnNumber':
3426
+ returnNumber = true;
3427
+ break;
3428
+ case 'numberOfReturns':
3429
+ numberOfReturns = true;
3430
+ break;
3431
+ case 'scannerChannel':
3432
+ scannerChannel = true;
3433
+ break;
3434
+ case 'scanDirectionFlag':
3435
+ scanDirectionFlag = true;
3436
+ break;
3437
+ case 'edgeOfFlightLine':
3438
+ edgeOfFlightLine = true;
3439
+ break;
3440
+ case 'WAVEFORM':
3441
+ waveform = true;
3442
+ break;
3443
+ case 'EXTRA_BYTES':
3444
+ extraBytes = true;
3445
+ break;
3446
+ default:
3447
+ throw new Error(`LASLoader: unsupported column ${column}`);
3448
+ }
3449
+ }
3450
+ return {
3451
+ intensity,
3452
+ classification,
3453
+ synthetic,
3454
+ keyPoint,
3455
+ withheld,
3456
+ overlap,
3457
+ color,
3458
+ gpsTime,
3459
+ nir,
3460
+ scanAngle,
3461
+ userData,
3462
+ pointSourceId,
3463
+ returnNumber,
3464
+ numberOfReturns,
3465
+ scannerChannel,
3466
+ scanDirectionFlag,
3467
+ edgeOfFlightLine,
3468
+ waveform,
3469
+ extraBytes
3470
+ };
3471
+ }
3472
+
3473
+ function convertRawColorsToUint8(
3474
+ rawColors: Uint16Array,
3475
+ pointCount: number,
3476
+ options: LASLoaderOptions
3477
+ ): Uint8Array {
3478
+ const colors = new Uint8Array(pointCount * 4);
3479
+ const packedColors = new Uint32Array(colors.buffer);
3480
+ const twoByteColor = detectTwoByteRawColors(rawColors, pointCount, options.las?.colorDepth);
3481
+ if (twoByteColor) {
3482
+ for (let pointIndex = 0; pointIndex < pointCount; pointIndex++) {
3483
+ const sourceOffset = pointIndex * 3;
3484
+ const red = (rawColors[sourceOffset] >> 8) & 0xff;
3485
+ const green = (rawColors[sourceOffset + 1] >> 8) & 0xff;
3486
+ const blue = (rawColors[sourceOffset + 2] >> 8) & 0xff;
3487
+ packedColors[pointIndex] = 0xff000000 | (blue << 16) | (green << 8) | red;
3488
+ }
3489
+ } else {
3490
+ for (let pointIndex = 0; pointIndex < pointCount; pointIndex++) {
3491
+ const sourceOffset = pointIndex * 3;
3492
+ const red = rawColors[sourceOffset] & 0xff;
3493
+ const green = rawColors[sourceOffset + 1] & 0xff;
3494
+ const blue = rawColors[sourceOffset + 2] & 0xff;
3495
+ packedColors[pointIndex] = 0xff000000 | (blue << 16) | (green << 8) | red;
3496
+ }
3497
+ }
3498
+ return colors;
3499
+ }
3500
+
3501
+ function detectTwoByteRawColors(
3502
+ rawColors: Uint16Array,
3503
+ pointCount: number,
3504
+ colorDepth?: number | string
3505
+ ): boolean {
3506
+ if (colorDepth === 8) {
3507
+ return false;
3508
+ }
3509
+ if (colorDepth === 16) {
3510
+ return true;
3511
+ }
3512
+ if (colorDepth !== 'auto') {
3513
+ return false;
3514
+ }
3515
+ for (let index = 0; index < pointCount * 3; index++) {
3516
+ if (rawColors[index] > 255) {
3517
+ return true;
3518
+ }
3519
+ }
3520
+ return false;
3521
+ }
3522
+
3523
+ function readNullTerminatedAscii(bytes: Uint8Array, offset: number, length: number): string {
3524
+ let end = offset;
3525
+ const maxEnd = offset + length;
3526
+ while (end < maxEnd && bytes[end] !== 0) {
3527
+ end++;
3528
+ }
3529
+ return String.fromCharCode(...bytes.subarray(offset, end));
3530
+ }
3531
+
3532
+ function getHeaderBoundingBox(
3533
+ header: LASHeader
3534
+ ): [[number, number, number], [number, number, number]] {
3535
+ const minimum = header.mins || [0, 0, 0];
3536
+ const maximum = header.maxs || [0, 0, 0];
3537
+ return [
3538
+ [minimum[0], minimum[1], minimum[2]],
3539
+ [maximum[0], maximum[1], maximum[2]]
3540
+ ];
3541
+ }
3542
+
3543
+ function* readAvailablePointBatches(
3544
+ pending: Uint8Array,
3545
+ absoluteOffset: number,
3546
+ header: LASHeader,
3547
+ sourcePointIndex: number,
3548
+ totalRead: number,
3549
+ batchSize: number
3550
+ ): Iterable<{
3551
+ arrayBuffer: ArrayBufferLike;
3552
+ header: LASHeader;
3553
+ pending: Uint8Array;
3554
+ absoluteOffset: number;
3555
+ sourcePointIndex: number;
3556
+ totalRead: number;
3557
+ }> {
3558
+ while (sourcePointIndex < header.pointsCount && totalRead < header.totalToRead) {
3559
+ const batch = readAvailableContiguousPointBatch(
3560
+ pending,
3561
+ absoluteOffset,
3562
+ header,
3563
+ sourcePointIndex,
3564
+ totalRead,
3565
+ batchSize
3566
+ );
3567
+ if (!batch) {
3568
+ return;
3569
+ }
3570
+ pending = batch.pending;
3571
+ absoluteOffset = batch.absoluteOffset;
3572
+ sourcePointIndex = batch.sourcePointIndex;
3573
+ totalRead = batch.totalRead;
3574
+ yield batch;
3575
+ }
3576
+ }
3577
+
3578
+ function readAvailableContiguousPointBatch(
3579
+ pending: Uint8Array,
3580
+ absoluteOffset: number,
3581
+ header: LASHeader,
3582
+ sourcePointIndex: number,
3583
+ totalRead: number,
3584
+ batchSize: number
3585
+ ): {
3586
+ arrayBuffer: ArrayBufferLike;
3587
+ header: LASHeader;
3588
+ pending: Uint8Array;
3589
+ absoluteOffset: number;
3590
+ sourcePointIndex: number;
3591
+ totalRead: number;
3592
+ } | null {
3593
+ const pointRecordLength = header.pointsStructSize;
3594
+ const pointAbsoluteOffset = header.pointsOffset + sourcePointIndex * pointRecordLength;
3595
+ const pointPendingOffset = pointAbsoluteOffset - absoluteOffset;
3596
+ if (pointPendingOffset < 0) {
3597
+ throw new Error('LASLoader: invalid streaming point offset');
3598
+ }
3599
+ if (pointPendingOffset + pointRecordLength > pending.byteLength) {
3600
+ return null;
3601
+ }
3602
+
3603
+ const availablePointCount = Math.floor(
3604
+ (pending.byteLength - pointPendingOffset) / pointRecordLength
3605
+ );
3606
+ const batchPointCount = Math.min(
3607
+ batchSize,
3608
+ availablePointCount,
3609
+ header.pointsCount - sourcePointIndex,
3610
+ header.totalToRead - totalRead
3611
+ );
3612
+ if (batchPointCount <= 0) {
3613
+ return null;
3614
+ }
3615
+
3616
+ sourcePointIndex += batchPointCount;
3617
+ totalRead += batchPointCount;
3618
+ const batchHeader = {
3619
+ ...header,
3620
+ pointsOffset: pending.byteOffset + pointPendingOffset,
3621
+ pointsCount: batchPointCount,
3622
+ totalRead
3623
+ };
3624
+ const nextPointOffset = header.pointsOffset + sourcePointIndex * pointRecordLength;
3625
+ const discardByteCount = Math.max(
3626
+ 0,
3627
+ Math.min(pending.byteLength, nextPointOffset - absoluteOffset)
3628
+ );
3629
+
3630
+ return {
3631
+ arrayBuffer: pending.buffer,
3632
+ header: batchHeader,
3633
+ pending: pending.subarray(discardByteCount),
3634
+ absoluteOffset: absoluteOffset + discardByteCount,
3635
+ sourcePointIndex,
3636
+ totalRead
3637
+ };
3638
+ }
3639
+
3640
+ function tryParseHeader(pending: Uint8Array, absoluteOffset: number): LASHeader | null {
3641
+ if (absoluteOffset !== 0 || pending.byteLength < 227) {
3642
+ return null;
3643
+ }
3644
+ const dataView = new DataView(pending.buffer, pending.byteOffset, pending.byteLength);
3645
+ if (dataView.getUint32(0, true) !== LASF_SIGNATURE) {
3646
+ throw new Error('LASLoader: invalid LAS header');
3647
+ }
3648
+ const headerLength = dataView.getUint32(96, true);
3649
+ if (pending.byteLength < Math.max(headerLength, 255)) {
3650
+ return null;
3651
+ }
3652
+ const header = parseLASHeader(
3653
+ pending.buffer.slice(pending.byteOffset, pending.byteOffset + pending.byteLength)
3654
+ );
3655
+ return header;
3656
+ }
3657
+
3658
+ function concatenateUint8Arrays(
3659
+ first: Uint8Array<ArrayBufferLike>,
3660
+ second: Uint8Array<ArrayBufferLike>
3661
+ ): Uint8Array<ArrayBufferLike> {
3662
+ if (first.byteLength === 0) {
3663
+ return second;
3664
+ }
3665
+ const result = new Uint8Array(first.byteLength + second.byteLength);
3666
+ result.set(first);
3667
+ result.set(second, first.byteLength);
3668
+ return result;
3669
+ }
3670
+
3671
+ function toUint8Array(data: ArrayBufferLike | ArrayBufferView): Uint8Array<ArrayBuffer> {
3672
+ const source = ArrayBuffer.isView(data)
3673
+ ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
3674
+ : new Uint8Array(data);
3675
+ const copy = new Uint8Array(source.byteLength);
3676
+ copy.set(source);
3677
+ return copy;
3678
+ }
3679
+
3680
+ function toAsyncIterator(
3681
+ data:
3682
+ | AsyncIterable<ArrayBufferLike | ArrayBufferView>
3683
+ | Iterable<ArrayBufferLike | ArrayBufferView>
3684
+ ): AsyncIterator<ArrayBufferLike | ArrayBufferView> {
3685
+ if (Symbol.asyncIterator in data) {
3686
+ return data[Symbol.asyncIterator]();
3687
+ }
3688
+ const iterator = data[Symbol.iterator]();
3689
+ return {
3690
+ async next() {
3691
+ return iterator.next();
3692
+ }
3693
+ };
3694
+ }
3695
+
3696
+ /** Resolve the LAZ chunk-table offset, including the non-seekable-writer sentinel layout. */
3697
+ function readLAZChunkTableOffset(dataView: DataView, pointDataOffset: number): number {
3698
+ if (isNegativeOneUint64(dataView, pointDataOffset)) {
3699
+ if (dataView.byteLength < LAZ_CHUNK_TABLE_POINTER_LENGTH) {
3700
+ return 0;
3701
+ }
3702
+ return readUint64(dataView, dataView.byteLength - LAZ_CHUNK_TABLE_POINTER_LENGTH);
3703
+ }
3704
+ return readUint64(dataView, pointDataOffset);
3705
+ }
3706
+
3707
+ /** Read a streaming chunk-table pointer, returning -1 for the end-pointer sentinel. */
3708
+ function readStreamedLAZChunkTableOffset(dataView: DataView): number {
3709
+ return isNegativeOneUint64(dataView, 0) ? -1 : readUint64(dataView, 0);
3710
+ }
3711
+
3712
+ /** Return true when an eight-byte field contains signed little-endian -1. */
3713
+ function isNegativeOneUint64(dataView: DataView, byteOffset: number): boolean {
3714
+ return (
3715
+ dataView.byteLength >= byteOffset + 8 &&
3716
+ dataView.getUint32(byteOffset, true) === 0xffffffff &&
3717
+ dataView.getUint32(byteOffset + 4, true) === 0xffffffff
3718
+ );
3719
+ }
3720
+
3721
+ function readUint64(dataView: DataView, byteOffset: number): number {
3722
+ if (dataView.byteLength < byteOffset + 8) {
3723
+ return 0;
3724
+ }
3725
+ const low = dataView.getUint32(byteOffset, true);
3726
+ const high = dataView.getUint32(byteOffset + 4, true);
3727
+ return high * 2 ** 32 + low;
3728
+ }
3729
+
3730
+ export const LAS_1_4_HEADER_LENGTH = LAS_14_HEADER_LENGTH;