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

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 +31514 -0
  8. package/dist/dist.min.js +54 -0
  9. package/dist/index.cjs +55 -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 +69 -0
  16. package/dist/las/bundled.cjs.map +7 -0
  17. package/dist/las/bundled.d.ts +11 -0
  18. package/dist/las/bundled.d.ts.map +1 -0
  19. package/dist/las/bundled.js +11 -0
  20. package/dist/las/bundled.js.map +1 -0
  21. package/dist/las/index.cjs +75 -0
  22. package/dist/las/index.cjs.map +7 -0
  23. package/dist/las/index.d.ts +15 -0
  24. package/dist/las/index.d.ts.map +1 -0
  25. package/dist/las/index.js +12 -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 +67 -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 +60 -0
  34. package/dist/las/las-copc-loader.cjs.map +7 -0
  35. package/dist/las/las-copc-loader.d.ts +39 -0
  36. package/dist/las/las-copc-loader.d.ts.map +1 -0
  37. package/dist/las/las-copc-loader.js +45 -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 +84 -0
  46. package/dist/las/las-loader-shared.cjs.map +7 -0
  47. package/dist/las/las-loader-shared.d.ts +68 -0
  48. package/dist/las/las-loader-shared.d.ts.map +1 -0
  49. package/dist/las/las-loader-shared.js +60 -0
  50. package/dist/las/las-loader-shared.js.map +1 -0
  51. package/dist/las/las-loader-types.cjs +51 -0
  52. package/dist/las/las-loader-types.cjs.map +7 -0
  53. package/dist/las/las-loader-types.d.ts +77 -0
  54. package/dist/las/las-loader-types.d.ts.map +1 -0
  55. package/dist/las/las-loader-types.js +17 -0
  56. package/dist/las/las-loader-types.js.map +1 -0
  57. package/dist/las/las-loader.cjs +128 -0
  58. package/dist/las/las-loader.cjs.map +7 -0
  59. package/dist/las/las-loader.d.ts +91 -0
  60. package/dist/las/las-loader.d.ts.map +1 -0
  61. package/dist/las/las-loader.js +108 -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 +70 -0
  66. package/dist/las/las-writer.d.ts.map +1 -0
  67. package/dist/las/las-writer.js +992 -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 +69 -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 +275 -0
  76. package/dist/las/laz-rs-loader.cjs.map +7 -0
  77. package/dist/las/laz-rs-loader.d.ts +39 -0
  78. package/dist/las/laz-rs-loader.d.ts.map +1 -0
  79. package/dist/las/laz-rs-loader.js +43 -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 +71 -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 +63 -0
  88. package/dist/las/lazperf-loader.cjs.map +7 -0
  89. package/dist/las/lazperf-loader.d.ts +41 -0
  90. package/dist/las/lazperf-loader.d.ts.map +1 -0
  91. package/dist/las/lazperf-loader.js +49 -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 +67 -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 +52 -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 +228 -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 +197 -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 +271 -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 +308 -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 +271 -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 +308 -0
  152. package/dist/las/lib/laz-rs-wasm/parse-las.js.map +1 -0
  153. package/dist/las/lib/typescript/parse-las.cjs +2889 -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 +2703 -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 +69 -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 +62 -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 +53 -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 +130 -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 +24024 -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 +277 -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 +65 -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 +31 -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 +37 -0
  245. package/dist/pcd/index.cjs.map +7 -0
  246. package/dist/pcd/index.d.ts +6 -0
  247. package/dist/pcd/index.d.ts.map +1 -0
  248. package/dist/pcd/index.js +7 -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 +63 -0
  281. package/dist/pcd/pcd-loader-types.cjs.map +7 -0
  282. package/dist/pcd/pcd-loader-types.d.ts +82 -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 +201 -0
  287. package/dist/pcd/pcd-loader.cjs.map +7 -0
  288. package/dist/pcd/pcd-loader.d.ts +80 -0
  289. package/dist/pcd/pcd-loader.d.ts.map +1 -0
  290. package/dist/pcd/pcd-loader.js +179 -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 +38 -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 +31 -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 +65 -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 +203 -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 +18561 -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 +31 -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 +37 -0
  336. package/dist/ply/index.cjs.map +7 -0
  337. package/dist/ply/index.d.ts +6 -0
  338. package/dist/ply/index.d.ts.map +1 -0
  339. package/dist/ply/index.js +8 -0
  340. package/dist/ply/index.js.map +1 -0
  341. package/dist/ply/lib/get-ply-schema.cjs +128 -0
  342. package/dist/ply/lib/get-ply-schema.cjs.map +7 -0
  343. package/dist/ply/lib/get-ply-schema.d.ts +16 -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 +613 -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 +630 -0
  364. package/dist/ply/lib/parse-ply-in-batches.js.map +1 -0
  365. package/dist/ply/lib/parse-ply.cjs +758 -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 +855 -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 +64 -0
  384. package/dist/ply/ply-loader-types.cjs.map +7 -0
  385. package/dist/ply/ply-loader-types.d.ts +88 -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 +192 -0
  390. package/dist/ply/ply-loader.cjs.map +7 -0
  391. package/dist/ply/ply-loader.d.ts +81 -0
  392. package/dist/ply/ply-loader.d.ts.map +1 -0
  393. package/dist/ply/ply-loader.js +173 -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 +38 -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 +31 -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 +66 -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 +194 -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 +20962 -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 +238 -4
  433. package/src/index.ts +19 -0
  434. package/src/las/bundled.ts +38 -0
  435. package/src/las/index.ts +66 -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 +126 -0
  440. package/src/las/las-loader-types.ts +24 -0
  441. package/src/las/las-loader.ts +163 -0
  442. package/src/las/las-writer.ts +1477 -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 +55 -0
  451. package/src/las/lib/las-compatibility.ts +178 -0
  452. package/src/las/lib/las-extra-bytes.ts +276 -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 +411 -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 +411 -0
  459. package/src/las/lib/typescript/parse-las.ts +3862 -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 +6 -0
  479. package/src/pcd/index.ts +9 -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 +52 -0
  486. package/src/pcd/pcd-loader.ts +247 -0
  487. package/src/pcd/pcd-writer.ts +156 -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 +6 -0
  494. package/src/ply/index.ts +11 -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 +832 -0
  499. package/src/ply/lib/parse-ply.ts +1084 -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 +60 -0
  503. package/src/ply/ply-loader.ts +255 -0
  504. package/src/ply/ply-writer.ts +219 -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,1477 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Table} from 'apache-arrow';
6
+ import {
7
+ encodeLASzipVLR,
8
+ encodeLAZChunk,
9
+ encodeLAZChunkTable,
10
+ type LAZChunkTableEntry,
11
+ type WriterOptions,
12
+ type WriterWithEncoder
13
+ } from '@loaders.gl/core/loader-utils';
14
+ import type {Mesh, MeshArrowTable, MeshAttribute} from '@loaders.gl/core/schema';
15
+ import {convertMeshToArrowTableWrapper, convertTableToMesh} from '@loaders.gl/core/schema-utils';
16
+ import {LASFormat} from './las-format';
17
+
18
+ // __VERSION__ is injected by babel-plugin-version-inline
19
+ // @ts-ignore TS2304: Cannot find name '__VERSION__'.
20
+ const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';
21
+
22
+ const LAS_HEADER_LENGTH = 227;
23
+ const LAS_1_4_HEADER_LENGTH = 375;
24
+ const LAS_1_5_HEADER_LENGTH = 393;
25
+ const VLR_HEADER_LENGTH = 54;
26
+ const EXTRA_BYTES_DESCRIPTOR_LENGTH = 192;
27
+ const DEFAULT_LAZ_CHUNK_SIZE = 50_000;
28
+ const VARIABLE_LAZ_CHUNK_SIZE = 0xffffffff;
29
+ const LAS_RETURN_BUCKET_COUNT = 15;
30
+ const POINT_RECORD_LENGTHS: Record<number, number> = {
31
+ 0: 20,
32
+ 1: 28,
33
+ 2: 26,
34
+ 3: 34,
35
+ 4: 57,
36
+ 5: 63,
37
+ 6: 30,
38
+ 7: 36,
39
+ 8: 38,
40
+ 9: 59,
41
+ 10: 67
42
+ };
43
+
44
+ /** Description of a typed mesh attribute stored in LAS Extra Bytes. */
45
+ export type LASExtraBytesWriter = {
46
+ /** Name of the mesh attribute to append to each point record. */
47
+ attribute: string;
48
+ /** Optional LAS Extra Bytes field name. */
49
+ name?: string;
50
+ /** Optional LAS Extra Bytes field description. */
51
+ description?: string;
52
+ };
53
+
54
+ /** Options for `LASWriter`. */
55
+ export type LASWriterOptions = WriterOptions & {
56
+ /** LAS-specific writer options. */
57
+ las?: {
58
+ /** Output container format. COPC has its own writer in @loaders.gl/tiles-3d/copc. */
59
+ format?: 'las' | 'laz';
60
+ /** LAS file version to write. LAS 1.5 requires a WKT CRS string. */
61
+ version?: '1.0' | '1.1' | '1.2' | '1.3' | '1.4' | '1.5';
62
+ /** WKT coordinate system required for LAS 1.5 output. */
63
+ wkt?: string;
64
+ /** Optional WKT math transform VLR for LAS 1.5 output. */
65
+ wktMathTransform?: string;
66
+ /** LAS 1.5 GPS time offset in the public header. */
67
+ timeOffset?: number;
68
+ /** LAS point data record format to write. */
69
+ pointDataRecordFormat?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
70
+ /** Coordinate scale factors used to quantize positions into LAS integer coordinates. */
71
+ scale?: [number, number, number];
72
+ /** Coordinate offsets used to quantize positions into LAS integer coordinates. */
73
+ offset?: [number, number, number];
74
+ /** Source RGB depth: 8 scales to UInt16, 16 preserves values; auto retains legacy inference. Normalized attributes retain their declared normalization. */
75
+ colorDepth?: number | string;
76
+ /** Number of points per fixed-size LAZ chunk. */
77
+ chunkSize?: number;
78
+ /** Write a variable-size LAZ chunk table instead of a fixed-size table. */
79
+ variableChunkTable?: boolean;
80
+ /** Typed mesh attributes with one or three components to append as Extra Bytes fields. */
81
+ extraBytes?: LASExtraBytesWriter[];
82
+ };
83
+ };
84
+
85
+ /**
86
+ * Writer for LAS and LAZ point cloud data.
87
+ */
88
+ export const LASWriter = {
89
+ ...LASFormat,
90
+ dataType: null as unknown as Mesh | MeshArrowTable | Table,
91
+ batchType: null as never,
92
+ version: VERSION,
93
+ extensions: ['las', 'laz'],
94
+ options: {
95
+ las: {}
96
+ },
97
+ encode: async (data, options) => encodeLASSync(data, options),
98
+ encodeSync: encodeLASSync,
99
+ encodeInBatches: async function* (dataIterator, options) {
100
+ const batches: (Mesh | MeshArrowTable | Table)[] = [];
101
+ for await (const batch of dataIterator as AsyncIterable<Mesh | MeshArrowTable | Table>) {
102
+ batches.push(batch);
103
+ }
104
+ yield encodeLASSync(mergeMeshBatches(batches), options);
105
+ }
106
+ } as const satisfies WriterWithEncoder<Mesh | MeshArrowTable | Table, never, LASWriterOptions>;
107
+
108
+ /** Encode mesh category data as LAS or LAZ bytes. */
109
+ function encodeLASSync(
110
+ data: Mesh | MeshArrowTable | Table,
111
+ options: LASWriterOptions = {}
112
+ ): ArrayBuffer {
113
+ const format = options.las?.format || 'las';
114
+ const colorDepth = options.las?.colorDepth;
115
+ if (colorDepth !== undefined && colorDepth !== 'auto' && colorDepth !== 8 && colorDepth !== 16)
116
+ throw new Error('LASWriter: invalid source color depth');
117
+ const mesh = normalizeMesh(data);
118
+ const positionAttribute = getRequiredAttribute(mesh, 'POSITION');
119
+ validatePositionAttribute(positionAttribute);
120
+ const vertexCount = positionAttribute.value.length / positionAttribute.size;
121
+ validateOptionalPointAttributes(mesh, vertexCount);
122
+ const colorAttribute = mesh.attributes.COLOR_0;
123
+ const intensityAttribute = mesh.attributes.intensity;
124
+ const classificationAttribute = mesh.attributes.classification;
125
+ const nirAttribute = mesh.attributes.nir;
126
+ const extraByteFields = getExtraByteFields(mesh, options, vertexCount);
127
+ const gpsTimeAttribute = mesh.attributes.gpsTime;
128
+ const scanAngleAttribute = mesh.attributes.scanAngle;
129
+ const userDataAttribute = mesh.attributes.userData;
130
+ const pointSourceIdAttribute = mesh.attributes.pointSourceId;
131
+ const returnNumberAttribute = mesh.attributes.returnNumber;
132
+ const numberOfReturnsAttribute = mesh.attributes.numberOfReturns;
133
+ const scannerChannelAttribute = mesh.attributes.scannerChannel;
134
+ const scanDirectionFlagAttribute = mesh.attributes.scanDirectionFlag;
135
+ const edgeOfFlightLineAttribute = mesh.attributes.edgeOfFlightLine;
136
+ const syntheticAttribute = mesh.attributes.synthetic;
137
+ const keyPointAttribute = mesh.attributes.keyPoint;
138
+ const withheldAttribute = mesh.attributes.withheld;
139
+ const overlapAttribute = mesh.attributes.overlap;
140
+ const waveformDescriptorAttribute = mesh.attributes.wavePacketDescriptorIndex;
141
+ const waveformOffsetAttribute = mesh.attributes.wavePacketOffset;
142
+ const waveformSizeAttribute = mesh.attributes.wavePacketSize;
143
+ const waveformReturnPointAttribute = mesh.attributes.wavePacketReturnPoint;
144
+ const waveformVectorAttribute = mesh.attributes.wavePacketVector;
145
+ const boundingBox = getBoundingBox(positionAttribute, vertexCount);
146
+ const returnCounts = getReturnCounts(returnNumberAttribute, vertexCount);
147
+ const scale = getScale(mesh, options);
148
+ const offset = getOffset(mesh, options, boundingBox);
149
+ validateCoordinateEncoding(positionAttribute, vertexCount, scale, offset);
150
+ const pointDataRecordFormat =
151
+ options.las?.pointDataRecordFormat ?? getDefaultPointDataRecordFormat(options, colorAttribute);
152
+ validateReturnAttributes(mesh, vertexCount, pointDataRecordFormat);
153
+ const basePointDataRecordLength = POINT_RECORD_LENGTHS[pointDataRecordFormat];
154
+ if (!basePointDataRecordLength) {
155
+ throw new Error(`LASWriter: unsupported point data record format ${pointDataRecordFormat}`);
156
+ }
157
+ const pointDataRecordLength =
158
+ basePointDataRecordLength +
159
+ extraByteFields.reduce((byteLength, field) => byteLength + field.byteLength, 0);
160
+ if (pointDataRecordLength > 0xffff) {
161
+ throw new Error(
162
+ `LASWriter: point data record length ${pointDataRecordLength} exceeds the LAS limit`
163
+ );
164
+ }
165
+ validateExtraBytesVLR(extraByteFields);
166
+ const version = options.las?.version || (pointDataRecordFormat >= 6 ? '1.4' : '1.2');
167
+ const headerLength =
168
+ version === '1.5'
169
+ ? LAS_1_5_HEADER_LENGTH
170
+ : version === '1.4'
171
+ ? LAS_1_4_HEADER_LENGTH
172
+ : LAS_HEADER_LENGTH;
173
+ validateLASVersionOptions(version, pointDataRecordFormat, options);
174
+ const gpsTimeRange = getGpsTimeRange(gpsTimeAttribute, vertexCount);
175
+ if (version === '1.5' && options.las?.timeOffset && !gpsTimeAttribute) {
176
+ throw new Error('LASWriter: LAS 1.5 timeOffset requires a GPS time attribute');
177
+ }
178
+ if (format === 'laz') {
179
+ validateLAZOptions(version, pointDataRecordFormat, options.las?.chunkSize);
180
+ }
181
+ validatePointDataRecordVersion(version, pointDataRecordFormat);
182
+
183
+ const rawPointData = new Uint8Array(vertexCount * pointDataRecordLength);
184
+ const pointDataView = new DataView(rawPointData.buffer);
185
+
186
+ for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
187
+ const pointOffset = vertexIndex * pointDataRecordLength;
188
+ pointDataView.setInt32(
189
+ pointOffset,
190
+ Math.round((getComponent(positionAttribute, vertexIndex, 0) - offset[0]) / scale[0]),
191
+ true
192
+ );
193
+ pointDataView.setInt32(
194
+ pointOffset + 4,
195
+ Math.round((getComponent(positionAttribute, vertexIndex, 1) - offset[1]) / scale[1]),
196
+ true
197
+ );
198
+ pointDataView.setInt32(
199
+ pointOffset + 8,
200
+ Math.round((getComponent(positionAttribute, vertexIndex, 2) - offset[2]) / scale[2]),
201
+ true
202
+ );
203
+ writePointRecord(
204
+ pointDataView,
205
+ pointOffset,
206
+ vertexIndex,
207
+ pointDataRecordFormat,
208
+ basePointDataRecordLength,
209
+ {
210
+ intensityAttribute,
211
+ classificationAttribute,
212
+ nirAttribute,
213
+ colorAttribute,
214
+ colorDepth,
215
+ gpsTimeAttribute,
216
+ scanAngleAttribute,
217
+ userDataAttribute,
218
+ pointSourceIdAttribute,
219
+ returnNumberAttribute,
220
+ numberOfReturnsAttribute,
221
+ scannerChannelAttribute,
222
+ scanDirectionFlagAttribute,
223
+ edgeOfFlightLineAttribute,
224
+ syntheticAttribute,
225
+ keyPointAttribute,
226
+ withheldAttribute,
227
+ overlapAttribute,
228
+ waveformDescriptorAttribute,
229
+ waveformOffsetAttribute,
230
+ waveformSizeAttribute,
231
+ waveformReturnPointAttribute,
232
+ waveformVectorAttribute,
233
+ extraByteFields
234
+ }
235
+ );
236
+ }
237
+
238
+ const writeParameters: LASWriteParameters = {
239
+ vertexCount,
240
+ boundingBox,
241
+ scale,
242
+ offset,
243
+ version,
244
+ headerLength,
245
+ pointDataRecordFormat,
246
+ pointDataRecordLength,
247
+ returnCounts,
248
+ maxGpsTime: gpsTimeRange.maximum,
249
+ minGpsTime: gpsTimeRange.minimum,
250
+ timeOffset: options.las?.timeOffset || 0,
251
+ hasGpsTime: Boolean(gpsTimeAttribute),
252
+ hasWkt: Boolean(options.las?.wkt)
253
+ };
254
+ if (format === 'laz') {
255
+ return encodeLAZFile(
256
+ rawPointData,
257
+ writeParameters,
258
+ options.las?.chunkSize,
259
+ options.las?.variableChunkTable,
260
+ extraByteFields,
261
+ options
262
+ );
263
+ }
264
+
265
+ const metadataVLR = encodeMetadataVLRs(extraByteFields, options);
266
+ const pointDataOffset = headerLength + metadataVLR.byteLength;
267
+ const arrayBuffer = new ArrayBuffer(pointDataOffset + rawPointData.byteLength);
268
+ writeHeader(new DataView(arrayBuffer), {
269
+ ...writeParameters,
270
+ pointDataOffset,
271
+ vlrCount: countMetadataVLRs(extraByteFields, options)
272
+ });
273
+ const bytes = new Uint8Array(arrayBuffer);
274
+ bytes.set(metadataVLR, headerLength);
275
+ bytes.set(rawPointData, pointDataOffset);
276
+ return arrayBuffer;
277
+ }
278
+
279
+ /** Validate that the selected LAS header can represent the point record format. */
280
+ function validatePointDataRecordVersion(version: string, pointDataRecordFormat: number): void {
281
+ const minimumVersionByPointFormat: Record<number, string> = {
282
+ 0: '1.0',
283
+ 1: '1.1',
284
+ 2: '1.2',
285
+ 3: '1.2'
286
+ };
287
+ const minimumVersion = minimumVersionByPointFormat[pointDataRecordFormat];
288
+ if (minimumVersion && version < minimumVersion) {
289
+ throw new Error(
290
+ `LASWriter: point data record format ${pointDataRecordFormat} requires LAS ${minimumVersion} or newer; received ${version}`
291
+ );
292
+ }
293
+ if (
294
+ pointDataRecordFormat >= 4 &&
295
+ pointDataRecordFormat <= 5 &&
296
+ !['1.3', '1.4', '1.5'].includes(version)
297
+ ) {
298
+ throw new Error(
299
+ `LASWriter: point data record format ${pointDataRecordFormat} requires LAS 1.3; received ${version}`
300
+ );
301
+ }
302
+ if (pointDataRecordFormat >= 6 && !['1.4', '1.5'].includes(version)) {
303
+ throw new Error(
304
+ `LASWriter: point data record format ${pointDataRecordFormat} requires LAS 1.4; received ${version}`
305
+ );
306
+ }
307
+ }
308
+
309
+ /** Validate LAS 1.5 writer requirements before allocating point data. */
310
+ function validateLASVersionOptions(
311
+ version: string,
312
+ pointDataRecordFormat: number,
313
+ options: LASWriterOptions
314
+ ): void {
315
+ if (version === '1.5') {
316
+ if (pointDataRecordFormat < 6 || pointDataRecordFormat > 10) {
317
+ throw new Error('LASWriter: LAS 1.5 output requires point data record format 6-10');
318
+ }
319
+ if (!options.las?.wkt?.trim()) {
320
+ throw new Error('LASWriter: LAS 1.5 output requires las.wkt');
321
+ }
322
+ const timeOffset = options.las?.timeOffset ?? 0;
323
+ if (!Number.isInteger(timeOffset) || timeOffset < 0 || timeOffset > 0xffff) {
324
+ throw new Error('LASWriter: LAS 1.5 timeOffset must be an unsigned 16-bit integer');
325
+ }
326
+ }
327
+ }
328
+
329
+ /** Values shared by LAS header and point-data encoding. */
330
+ type LASWriteParameters = {
331
+ /** Number of point records in the output. */
332
+ vertexCount: number;
333
+ /** World-coordinate bounds. */
334
+ boundingBox: [[number, number, number], [number, number, number]];
335
+ /** Coordinate quantization scales. */
336
+ scale: [number, number, number];
337
+ /** Coordinate quantization offsets. */
338
+ offset: [number, number, number];
339
+ /** LAS file version. */
340
+ version: '1.0' | '1.1' | '1.2' | '1.3' | '1.4' | '1.5';
341
+ /** Public header byte length. */
342
+ headerLength: number;
343
+ /** LAS point data record format. */
344
+ pointDataRecordFormat: number;
345
+ /** Byte length of each raw point record. */
346
+ pointDataRecordLength: number;
347
+ /** Counts for the fifteen LAS 1.4 return-number buckets. */
348
+ returnCounts: number[];
349
+ /** LAS 1.5 maximum GPS time, or zero when no GPS time is present. */
350
+ maxGpsTime: number;
351
+ /** LAS 1.5 minimum GPS time, or zero when no GPS time is present. */
352
+ minGpsTime: number;
353
+ /** LAS 1.5 GPS time offset. */
354
+ timeOffset: number;
355
+ /** Whether the input contained a GPS time attribute. */
356
+ hasGpsTime: boolean;
357
+ /** Whether a WKT coordinate-system VLR is emitted. */
358
+ hasWkt: boolean;
359
+ };
360
+
361
+ /** Internal description of one encoded LAS Extra Bytes field. */
362
+ type LASExtraByteField = LASExtraBytesWriter & {
363
+ /** Mesh attribute containing the field values. */
364
+ meshAttribute: MeshAttribute;
365
+ /** LAS Extra Bytes data type code. */
366
+ dataType: number;
367
+ /** Number of bytes occupied by one field value. */
368
+ byteLength: number;
369
+ /** Number of components in one field value. */
370
+ componentCount: number;
371
+ };
372
+
373
+ /** Assemble raw LAS point records into a complete fixed-chunk LAZ file. */
374
+ function encodeLAZFile(
375
+ rawPointData: Uint8Array,
376
+ parameters: LASWriteParameters,
377
+ requestedChunkSize?: number,
378
+ variableChunkTable = false,
379
+ extraByteFields: LASExtraByteField[] = [],
380
+ options: LASWriterOptions = {}
381
+ ): ArrayBuffer {
382
+ const chunkSize = requestedChunkSize || DEFAULT_LAZ_CHUNK_SIZE;
383
+ const laszipVLR = encodeLASzipVLR({
384
+ pointDataRecordFormat: parameters.pointDataRecordFormat,
385
+ pointDataRecordLength: parameters.pointDataRecordLength,
386
+ chunkSize: variableChunkTable ? VARIABLE_LAZ_CHUNK_SIZE : chunkSize
387
+ });
388
+ const metadataVLR = encodeMetadataVLRs(extraByteFields, options);
389
+ const pointDataOffset = parameters.headerLength + metadataVLR.byteLength + laszipVLR.byteLength;
390
+ const compressedChunks: Uint8Array[] = [];
391
+ const chunkTableEntries: LAZChunkTableEntry[] = [];
392
+
393
+ for (let pointOffset = 0; pointOffset < parameters.vertexCount; pointOffset += chunkSize) {
394
+ const pointCount = Math.min(chunkSize, parameters.vertexCount - pointOffset);
395
+ const byteOffset = pointOffset * parameters.pointDataRecordLength;
396
+ const compressed = encodeLAZChunk(
397
+ rawPointData.subarray(byteOffset, byteOffset + pointCount * parameters.pointDataRecordLength),
398
+ {
399
+ pointDataRecordFormat: parameters.pointDataRecordFormat,
400
+ pointDataRecordLength: parameters.pointDataRecordLength,
401
+ pointCount,
402
+ point14ItemVersion: 3,
403
+ rgb14ItemVersion: 3,
404
+ byte14ItemVersion: 3
405
+ }
406
+ );
407
+ compressedChunks.push(compressed);
408
+ chunkTableEntries.push({pointCount, byteLength: compressed.byteLength});
409
+ }
410
+
411
+ const compressedPointDataByteLength = compressedChunks.reduce(
412
+ (byteLength, chunk) => byteLength + chunk.byteLength,
413
+ 0
414
+ );
415
+ const chunkTablePayload = encodeLAZChunkTable(chunkTableEntries, {
416
+ variable: variableChunkTable
417
+ });
418
+ const chunkTableOffset = pointDataOffset + 8 + compressedPointDataByteLength;
419
+ const arrayBuffer = new ArrayBuffer(chunkTableOffset + 8 + chunkTablePayload.byteLength);
420
+ const bytes = new Uint8Array(arrayBuffer);
421
+ const dataView = new DataView(arrayBuffer);
422
+
423
+ writeHeader(dataView, {
424
+ ...parameters,
425
+ pointDataOffset,
426
+ vlrCount: countMetadataVLRs(extraByteFields, options) + 1,
427
+ compressed: true
428
+ });
429
+ bytes.set(metadataVLR, parameters.headerLength);
430
+ bytes.set(laszipVLR, parameters.headerLength + metadataVLR.byteLength);
431
+ writeUint64Fallback(dataView, pointDataOffset, chunkTableOffset);
432
+ let compressedOffset = pointDataOffset + 8;
433
+ for (const chunk of compressedChunks) {
434
+ bytes.set(chunk, compressedOffset);
435
+ compressedOffset += chunk.byteLength;
436
+ }
437
+ dataView.setUint32(chunkTableOffset, 0, true);
438
+ dataView.setUint32(chunkTableOffset + 4, chunkTableEntries.length, true);
439
+ bytes.set(chunkTablePayload, chunkTableOffset + 8);
440
+ return arrayBuffer;
441
+ }
442
+
443
+ /** Build validated Extra Bytes field descriptions from mesh attributes. */
444
+ function getExtraByteFields(
445
+ mesh: Mesh,
446
+ options: LASWriterOptions,
447
+ vertexCount: number
448
+ ): LASExtraByteField[] {
449
+ const extraBytes = options.las?.extraBytes || [];
450
+ const attributes = new Set<string>();
451
+ return extraBytes.map(field => {
452
+ if (attributes.has(field.attribute)) {
453
+ throw new Error(`LASWriter: duplicate Extra Bytes attribute ${field.attribute}`);
454
+ }
455
+ attributes.add(field.attribute);
456
+ const meshAttribute = mesh.attributes[field.attribute];
457
+ if (!meshAttribute) {
458
+ throw new Error(`LASWriter: Extra Bytes attribute ${field.attribute} is missing`);
459
+ }
460
+ if (![1, 3].includes(meshAttribute.size)) {
461
+ throw new Error(`LASWriter: Extra Bytes attribute ${field.attribute} must have size 1 or 3`);
462
+ }
463
+ if (meshAttribute.value.length < vertexCount * meshAttribute.size) {
464
+ throw new Error(`LASWriter: Extra Bytes attribute ${field.attribute} is too short`);
465
+ }
466
+ validateFiniteAttributeValues(field.attribute, meshAttribute);
467
+ const dataType = getExtraBytesDataType(meshAttribute.value, meshAttribute.size);
468
+ return {
469
+ ...field,
470
+ meshAttribute,
471
+ dataType,
472
+ byteLength: getExtraBytesDataTypeByteLength(dataType, meshAttribute.size),
473
+ componentCount: meshAttribute.size
474
+ };
475
+ });
476
+ }
477
+
478
+ /** Return the LAS Extra Bytes data type code for a typed array and component count. */
479
+ function getExtraBytesDataType(values: MeshAttribute['value'], componentCount: number): number {
480
+ let scalarDataType = 0;
481
+ if (values instanceof Uint8Array) scalarDataType = 1;
482
+ else if (values instanceof Int8Array) scalarDataType = 2;
483
+ else if (values instanceof Uint16Array) scalarDataType = 3;
484
+ else if (values instanceof Int16Array) scalarDataType = 4;
485
+ else if (values instanceof Uint32Array) scalarDataType = 5;
486
+ else if (values instanceof Int32Array) scalarDataType = 6;
487
+ else if (values instanceof BigUint64Array) scalarDataType = 7;
488
+ else if (values instanceof BigInt64Array) scalarDataType = 8;
489
+ else if (values instanceof Float32Array) scalarDataType = 9;
490
+ else if (values instanceof Float64Array) scalarDataType = 10;
491
+ if (scalarDataType) {
492
+ return scalarDataType + (componentCount === 3 ? 20 : 0);
493
+ }
494
+ throw new Error('LASWriter: Extra Bytes attributes require a supported typed array');
495
+ }
496
+
497
+ /** Return the byte width for a LAS Extra Bytes data type. */
498
+ function getExtraBytesDataTypeByteLength(dataType: number, componentCount: number): number {
499
+ const scalarDataType = dataType > 20 ? dataType - 20 : dataType > 10 ? dataType - 10 : dataType;
500
+ const scalarByteLength =
501
+ scalarDataType <= 2
502
+ ? 1
503
+ : scalarDataType <= 4
504
+ ? 2
505
+ : scalarDataType <= 6 || scalarDataType === 9
506
+ ? 4
507
+ : 8;
508
+ return scalarByteLength * componentCount;
509
+ }
510
+
511
+ /** Encode the LAS Extra Bytes VLR, or an empty buffer when no fields are configured. */
512
+ function encodeExtraBytesVLR(fields: readonly LASExtraByteField[]): Uint8Array {
513
+ if (fields.length === 0) {
514
+ return new Uint8Array(0);
515
+ }
516
+ const payload = new Uint8Array(fields.length * EXTRA_BYTES_DESCRIPTOR_LENGTH);
517
+ const dataView = new DataView(payload.buffer);
518
+ let byteOffset = 0;
519
+ for (const field of fields) {
520
+ dataView.setUint8(byteOffset + 2, field.dataType);
521
+ writeString(dataView, byteOffset + 4, field.name || field.attribute, 32);
522
+ writeString(dataView, byteOffset + 160, field.description || '', 32);
523
+ byteOffset += EXTRA_BYTES_DESCRIPTOR_LENGTH;
524
+ }
525
+ const bytes = new Uint8Array(VLR_HEADER_LENGTH + payload.byteLength);
526
+ const headerView = new DataView(bytes.buffer);
527
+ writeString(headerView, 2, 'LASF_Spec', 16);
528
+ headerView.setUint16(18, 4, true);
529
+ headerView.setUint16(20, payload.byteLength, true);
530
+ writeString(headerView, 22, 'Extra Bytes', 32);
531
+ bytes.set(payload, VLR_HEADER_LENGTH);
532
+ return bytes;
533
+ }
534
+
535
+ /** Encode projection and Extra Bytes VLRs in the order used by the public header. */
536
+ function encodeMetadataVLRs(
537
+ fields: readonly LASExtraByteField[],
538
+ options: LASWriterOptions
539
+ ): Uint8Array {
540
+ const records: Uint8Array[] = [];
541
+ if (options.las?.wktMathTransform) {
542
+ records.push(encodeProjectionVLR(2111, 'WKT Math Transform', options.las.wktMathTransform));
543
+ }
544
+ if (options.las?.wkt) {
545
+ records.push(encodeProjectionVLR(2112, 'WKT Coordinate System', options.las.wkt));
546
+ }
547
+ const extraBytesVLR = encodeExtraBytesVLR(fields);
548
+ if (extraBytesVLR.byteLength) {
549
+ records.push(extraBytesVLR);
550
+ }
551
+ const byteLength = records.reduce((total, record) => total + record.byteLength, 0);
552
+ const bytes = new Uint8Array(byteLength);
553
+ let offset = 0;
554
+ for (const record of records) {
555
+ bytes.set(record, offset);
556
+ offset += record.byteLength;
557
+ }
558
+ return bytes;
559
+ }
560
+
561
+ /** Encode one LAS WKT projection VLR. */
562
+ function encodeProjectionVLR(recordId: 2111 | 2112, description: string, wkt: string): Uint8Array {
563
+ const payload = new TextEncoder().encode(`${wkt}\0`);
564
+ if (payload.byteLength > 0xffff) {
565
+ throw new Error(`LASWriter: WKT VLR ${recordId} exceeds the LAS VLR size limit`);
566
+ }
567
+ const bytes = new Uint8Array(VLR_HEADER_LENGTH + payload.byteLength);
568
+ const dataView = new DataView(bytes.buffer);
569
+ writeString(dataView, 2, 'LASF_Projection', 16);
570
+ dataView.setUint16(18, recordId, true);
571
+ dataView.setUint16(20, payload.byteLength, true);
572
+ writeString(dataView, 22, description, 32);
573
+ bytes.set(payload, VLR_HEADER_LENGTH);
574
+ return bytes;
575
+ }
576
+
577
+ /** Count the VLR records emitted by the metadata encoder. */
578
+ function countMetadataVLRs(
579
+ fields: readonly LASExtraByteField[],
580
+ options: LASWriterOptions
581
+ ): number {
582
+ return (
583
+ (options.las?.wktMathTransform ? 1 : 0) + (options.las?.wkt ? 1 : 0) + (fields.length ? 1 : 0)
584
+ );
585
+ }
586
+
587
+ /** Return the finite GPS time range needed by the LAS 1.5 public header. */
588
+ function getGpsTimeRange(
589
+ attribute: MeshAttribute | undefined,
590
+ vertexCount: number
591
+ ): {minimum: number; maximum: number} {
592
+ if (!attribute) {
593
+ return {minimum: 0, maximum: 0};
594
+ }
595
+ let minimum = Number(attribute.value[0]);
596
+ let maximum = minimum;
597
+ for (let vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
598
+ const value = Number(attribute.value[vertexIndex * attribute.size]);
599
+ minimum = Math.min(minimum, value);
600
+ maximum = Math.max(maximum, value);
601
+ }
602
+ return {minimum, maximum};
603
+ }
604
+
605
+ /** Validate that the Extra Bytes VLR payload fits its 16-bit length field. */
606
+ function validateExtraBytesVLR(fields: readonly LASExtraByteField[]): void {
607
+ const payloadByteLength = fields.length * EXTRA_BYTES_DESCRIPTOR_LENGTH;
608
+ if (payloadByteLength > 0xffff) {
609
+ throw new Error(
610
+ `LASWriter: Extra Bytes VLR payload ${payloadByteLength} exceeds the LAS limit`
611
+ );
612
+ }
613
+ }
614
+
615
+ /** Validate the currently supported LAZ container writer surface. */
616
+ function validateLAZOptions(
617
+ version: string,
618
+ pointDataRecordFormat: number,
619
+ chunkSize?: number
620
+ ): void {
621
+ if (pointDataRecordFormat === 1 && !['1.1', '1.2', '1.3', '1.4', '1.5'].includes(version)) {
622
+ throw new Error(`LASWriter: LAZ PDRF 1 output requires LAS 1.1 or newer; received ${version}`);
623
+ }
624
+ if (
625
+ pointDataRecordFormat >= 2 &&
626
+ pointDataRecordFormat <= 3 &&
627
+ !['1.2', '1.3', '1.4', '1.5'].includes(version)
628
+ ) {
629
+ throw new Error(
630
+ `LASWriter: LAZ PDRF ${pointDataRecordFormat} output requires LAS 1.2 or newer; received ${version}`
631
+ );
632
+ }
633
+ if (
634
+ pointDataRecordFormat >= 4 &&
635
+ pointDataRecordFormat <= 5 &&
636
+ !['1.3', '1.4', '1.5'].includes(version)
637
+ ) {
638
+ throw new Error(
639
+ `LASWriter: LAZ PDRF ${pointDataRecordFormat} requires LAS 1.3 or newer; received ${version}`
640
+ );
641
+ }
642
+ if (pointDataRecordFormat >= 6 && !['1.4', '1.5'].includes(version)) {
643
+ throw new Error(`LASWriter: LAZ output requires LAS 1.4; received ${version}`);
644
+ }
645
+ if (![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].includes(pointDataRecordFormat)) {
646
+ throw new Error(
647
+ `LASWriter: LAZ output currently supports point data record formats 0-10; received ${pointDataRecordFormat}`
648
+ );
649
+ }
650
+ if (
651
+ chunkSize !== undefined &&
652
+ (!Number.isInteger(chunkSize) || chunkSize <= 0 || chunkSize > 0xffffffff)
653
+ ) {
654
+ throw new Error(`LASWriter: invalid LAZ chunk size ${chunkSize}`);
655
+ }
656
+ }
657
+
658
+ /** Return mesh data as a Mesh, converting MeshArrowTable input first. */
659
+ function normalizeMesh(data: Mesh | MeshArrowTable | Table): Mesh {
660
+ if (!('attributes' in data)) {
661
+ return convertTableToMesh(data);
662
+ }
663
+ return data as Mesh;
664
+ }
665
+
666
+ /** Return mesh data as a MeshArrowTable, converting plain Mesh data first. */
667
+ function normalizeMeshArrowTable(data: Mesh | MeshArrowTable | Table): MeshArrowTable | Table {
668
+ return !('attributes' in data) ? data : convertMeshToArrowTableWrapper(data as Mesh);
669
+ }
670
+
671
+ /** Merge buffered mesh batches for `encodeInBatches`. */
672
+ function mergeMeshBatches(
673
+ batches: (Mesh | MeshArrowTable | Table)[]
674
+ ): Mesh | MeshArrowTable | Table {
675
+ if (batches.length === 0) {
676
+ throw new Error('LASWriter: at least one input batch is required');
677
+ }
678
+ if (batches.length === 1) {
679
+ return batches[0];
680
+ }
681
+
682
+ const meshes = batches.map(batch => convertTableToMesh(normalizeMeshArrowTable(batch)));
683
+ const firstMesh = meshes[0];
684
+ const mergedAttributes: Mesh['attributes'] = {};
685
+
686
+ validateBatchAttributes(meshes);
687
+
688
+ for (const attributeName of Object.keys(firstMesh.attributes)) {
689
+ const firstAttribute = firstMesh.attributes[attributeName];
690
+ const values = meshes.map(mesh => mesh.attributes[attributeName]?.value);
691
+ if (values.some(value => !value)) {
692
+ continue;
693
+ }
694
+ const totalLength = values.reduce((length, value) => length + value!.length, 0);
695
+ const Constructor = firstAttribute.value.constructor as new (
696
+ length: number
697
+ ) => typeof firstAttribute.value;
698
+ const mergedValue = new Constructor(totalLength);
699
+ let offset = 0;
700
+ for (const value of values) {
701
+ mergedValue.set(value!, offset);
702
+ offset += value!.length;
703
+ }
704
+ mergedAttributes[attributeName] = {...firstAttribute, value: mergedValue};
705
+ }
706
+
707
+ return {
708
+ ...firstMesh,
709
+ attributes: mergedAttributes,
710
+ header: {
711
+ ...firstMesh.header,
712
+ vertexCount:
713
+ getRequiredAttribute({...firstMesh, attributes: mergedAttributes}, 'POSITION').value
714
+ .length / 3
715
+ }
716
+ };
717
+ }
718
+
719
+ /** Ensure batched meshes expose the same attributes and typed layouts. */
720
+ function validateBatchAttributes(meshes: Mesh[]): void {
721
+ const attributeNames = Object.keys(meshes[0].attributes);
722
+ for (const mesh of meshes.slice(1)) {
723
+ const meshAttributeNames = Object.keys(mesh.attributes);
724
+ if (
725
+ meshAttributeNames.length !== attributeNames.length ||
726
+ attributeNames.some(attributeName => !mesh.attributes[attributeName])
727
+ ) {
728
+ throw new Error('LASWriter: encodeInBatches requires consistent attribute names');
729
+ }
730
+ for (const attributeName of attributeNames) {
731
+ const firstAttribute = meshes[0].attributes[attributeName];
732
+ const attribute = mesh.attributes[attributeName];
733
+ if (
734
+ attribute.size !== firstAttribute.size ||
735
+ attribute.value.constructor !== firstAttribute.value.constructor
736
+ ) {
737
+ throw new Error(`LASWriter: encodeInBatches requires a consistent ${attributeName} layout`);
738
+ }
739
+ }
740
+ }
741
+ }
742
+
743
+ /** Return a required mesh attribute or throw a format-specific error. */
744
+ function getRequiredAttribute(mesh: Mesh, attributeName: string): MeshAttribute {
745
+ const attribute = mesh.attributes[attributeName];
746
+ if (!attribute) {
747
+ throw new Error(`LASWriter: ${attributeName} attribute is required`);
748
+ }
749
+ return attribute;
750
+ }
751
+
752
+ /** Validate the required LAS position attribute shape. */
753
+ function validatePositionAttribute(attribute: MeshAttribute): void {
754
+ if (attribute.size !== 3) {
755
+ throw new Error(`LASWriter: POSITION attribute must have size 3`);
756
+ }
757
+ if (attribute.value.length % attribute.size !== 0) {
758
+ throw new Error(`LASWriter: POSITION attribute length must be divisible by its size`);
759
+ }
760
+ }
761
+
762
+ /** Validate coordinate quantization before writing LAS signed 32-bit coordinates. */
763
+ function validateCoordinateEncoding(
764
+ attribute: MeshAttribute,
765
+ vertexCount: number,
766
+ scale: [number, number, number],
767
+ offset: [number, number, number]
768
+ ): void {
769
+ for (let componentIndex = 0; componentIndex < 3; componentIndex++) {
770
+ if (!Number.isFinite(scale[componentIndex]) || scale[componentIndex] <= 0) {
771
+ throw new Error(`LASWriter: coordinate scale must be finite and positive`);
772
+ }
773
+ if (!Number.isFinite(offset[componentIndex])) {
774
+ throw new Error(`LASWriter: coordinate offset must be finite`);
775
+ }
776
+ }
777
+ for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
778
+ for (let componentIndex = 0; componentIndex < 3; componentIndex++) {
779
+ const positionValue = attribute.value[vertexIndex * attribute.size + componentIndex];
780
+ if (!Number.isFinite(positionValue)) {
781
+ throw new Error(`LASWriter: POSITION values must be finite`);
782
+ }
783
+ const encodedValue = Math.round(
784
+ (positionValue - offset[componentIndex]) / scale[componentIndex]
785
+ );
786
+ if (encodedValue < -2147483648 || encodedValue > 2147483647) {
787
+ throw new Error(`LASWriter: encoded coordinate exceeds the signed 32-bit LAS range`);
788
+ }
789
+ }
790
+ }
791
+ }
792
+
793
+ /** Validate lengths and component shapes for mapped optional point attributes. */
794
+ function validateOptionalPointAttributes(mesh: Mesh, vertexCount: number): void {
795
+ const scalarAttributeNames = [
796
+ 'intensity',
797
+ 'classification',
798
+ 'gpsTime',
799
+ 'scanAngle',
800
+ 'userData',
801
+ 'pointSourceId',
802
+ 'returnNumber',
803
+ 'numberOfReturns',
804
+ 'scannerChannel',
805
+ 'scanDirectionFlag',
806
+ 'edgeOfFlightLine',
807
+ 'synthetic',
808
+ 'keyPoint',
809
+ 'withheld',
810
+ 'overlap',
811
+ 'nir',
812
+ 'wavePacketDescriptorIndex',
813
+ 'wavePacketOffset',
814
+ 'wavePacketSize',
815
+ 'wavePacketReturnPoint'
816
+ ];
817
+ for (const attributeName of scalarAttributeNames) {
818
+ const attribute = mesh.attributes[attributeName];
819
+ if (attribute) {
820
+ validateOptionalAttribute(attributeName, attribute, vertexCount, 1);
821
+ }
822
+ }
823
+ const colorAttribute = mesh.attributes.COLOR_0;
824
+ if (colorAttribute) {
825
+ validateOptionalAttribute('COLOR_0', colorAttribute, vertexCount, 3, false);
826
+ }
827
+ const waveformVectorAttribute = mesh.attributes.wavePacketVector;
828
+ if (waveformVectorAttribute) {
829
+ validateOptionalAttribute('wavePacketVector', waveformVectorAttribute, vertexCount, 3);
830
+ }
831
+ }
832
+
833
+ /** Validate return-number ranges against the selected LAS point-data format. */
834
+ function validateReturnAttributes(
835
+ mesh: Mesh,
836
+ vertexCount: number,
837
+ pointDataRecordFormat: number
838
+ ): void {
839
+ const maximumReturnNumber = pointDataRecordFormat >= 6 ? 15 : 5;
840
+ const returnNumberAttribute = mesh.attributes.returnNumber;
841
+ const numberOfReturnsAttribute = mesh.attributes.numberOfReturns;
842
+ for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
843
+ const returnNumber = getUInt8Attribute(returnNumberAttribute, vertexIndex, 1);
844
+ const numberOfReturns = getUInt8Attribute(numberOfReturnsAttribute, vertexIndex, 1);
845
+ if (returnNumber < 1 || returnNumber > maximumReturnNumber) {
846
+ throw new Error(
847
+ `LASWriter: returnNumber must be between 1 and ${maximumReturnNumber} for point data record format ${pointDataRecordFormat}`
848
+ );
849
+ }
850
+ if (numberOfReturns < 1 || numberOfReturns > maximumReturnNumber) {
851
+ throw new Error(
852
+ `LASWriter: numberOfReturns must be between 1 and ${maximumReturnNumber} for point data record format ${pointDataRecordFormat}`
853
+ );
854
+ }
855
+ if (returnNumber > numberOfReturns) {
856
+ throw new Error(`LASWriter: returnNumber cannot exceed numberOfReturns`);
857
+ }
858
+ }
859
+ }
860
+
861
+ /** Validate one optional mapped attribute against the point count. */
862
+ function validateOptionalAttribute(
863
+ attributeName: string,
864
+ attribute: MeshAttribute,
865
+ vertexCount: number,
866
+ minimumSize: number,
867
+ requirePointCount = true
868
+ ): void {
869
+ if (minimumSize === 1 ? attribute.size !== 1 : attribute.size < minimumSize) {
870
+ throw new Error(`LASWriter: ${attributeName} attribute must have size ${minimumSize}`);
871
+ }
872
+ validateFiniteAttributeValues(attributeName, attribute);
873
+ if (requirePointCount && attribute.value.length < vertexCount * attribute.size) {
874
+ throw new Error(`LASWriter: ${attributeName} attribute is too short`);
875
+ }
876
+ if (!requirePointCount && attribute.value.length < attribute.size) {
877
+ throw new Error(`LASWriter: ${attributeName} attribute is too short`);
878
+ }
879
+ }
880
+
881
+ /** Reject NaN and infinite values before numeric LAS conversion. */
882
+ function validateFiniteAttributeValues(attributeName: string, attribute: MeshAttribute): void {
883
+ for (const value of attribute.value) {
884
+ if (!Number.isFinite(Number(value))) {
885
+ throw new Error(`LASWriter: ${attributeName} attribute must contain finite values`);
886
+ }
887
+ }
888
+ }
889
+
890
+ /** Write the LAS 1.2 public header block. */
891
+ function writeHeader(
892
+ dataView: DataView,
893
+ parameters: LASWriteParameters & {
894
+ /** Absolute point-data byte offset. */
895
+ pointDataOffset: number;
896
+ /** Number of VLR records before point data. */
897
+ vlrCount?: number;
898
+ /** Whether the point format byte carries the LASzip compression flag. */
899
+ compressed?: boolean;
900
+ }
901
+ ): void {
902
+ const [versionMajor, versionMinor] = parameters.version.split('.').map(Number);
903
+ writeString(dataView, 0, 'LASF', 4);
904
+ dataView.setUint8(24, versionMajor);
905
+ dataView.setUint8(25, versionMinor);
906
+ writeString(dataView, 26, 'loaders.gl', 32);
907
+ writeString(dataView, 58, 'loaders.gl', 32);
908
+ dataView.setUint16(94, parameters.headerLength, true);
909
+ dataView.setUint32(96, parameters.pointDataOffset, true);
910
+ dataView.setUint32(100, parameters.vlrCount || 0, true);
911
+ dataView.setUint8(104, parameters.pointDataRecordFormat | (parameters.compressed ? 0x80 : 0));
912
+ dataView.setUint16(105, parameters.pointDataRecordLength, true);
913
+ const isExtendedHeader = parameters.version === '1.4' || parameters.version === '1.5';
914
+ dataView.setUint32(107, isExtendedHeader ? 0 : parameters.vertexCount, true);
915
+ for (let returnIndex = 0; returnIndex < 5; returnIndex++) {
916
+ dataView.setUint32(
917
+ 111 + returnIndex * 4,
918
+ isExtendedHeader ? 0 : parameters.returnCounts[returnIndex],
919
+ true
920
+ );
921
+ }
922
+
923
+ if (parameters.version === '1.5') {
924
+ // LAS 1.5 requires WKT and stores the time range in the extended header.
925
+ dataView.setUint16(
926
+ 6,
927
+ 0x10 | (parameters.hasGpsTime ? 1 : 0) | (parameters.timeOffset ? 0x40 : 0),
928
+ true
929
+ );
930
+ } else if (parameters.version === '1.4' && parameters.hasWkt) {
931
+ dataView.setUint16(6, 0x10, true);
932
+ }
933
+
934
+ dataView.setFloat64(131, parameters.scale[0], true);
935
+ dataView.setFloat64(139, parameters.scale[1], true);
936
+ dataView.setFloat64(147, parameters.scale[2], true);
937
+ dataView.setFloat64(155, parameters.offset[0], true);
938
+ dataView.setFloat64(163, parameters.offset[1], true);
939
+ dataView.setFloat64(171, parameters.offset[2], true);
940
+ dataView.setFloat64(179, parameters.boundingBox[1][0], true);
941
+ dataView.setFloat64(187, parameters.boundingBox[0][0], true);
942
+ dataView.setFloat64(195, parameters.boundingBox[1][1], true);
943
+ dataView.setFloat64(203, parameters.boundingBox[0][1], true);
944
+ dataView.setFloat64(211, parameters.boundingBox[1][2], true);
945
+ dataView.setFloat64(219, parameters.boundingBox[0][2], true);
946
+
947
+ if (isExtendedHeader) {
948
+ writeUint64Fallback(dataView, 247, parameters.vertexCount);
949
+ for (let returnIndex = 0; returnIndex < LAS_RETURN_BUCKET_COUNT; returnIndex++) {
950
+ writeUint64Fallback(dataView, 255 + returnIndex * 8, parameters.returnCounts[returnIndex]);
951
+ }
952
+ }
953
+ if (parameters.version === '1.5') {
954
+ dataView.setFloat64(375, parameters.maxGpsTime, true);
955
+ dataView.setFloat64(383, parameters.minGpsTime, true);
956
+ dataView.setUint16(391, parameters.timeOffset, true);
957
+ }
958
+ }
959
+
960
+ /** Write one LAS point record. */
961
+ function writePointRecord(
962
+ dataView: DataView,
963
+ pointOffset: number,
964
+ vertexIndex: number,
965
+ pointDataRecordFormat: number,
966
+ basePointDataRecordLength: number,
967
+ attributes: {
968
+ intensityAttribute?: MeshAttribute;
969
+ classificationAttribute?: MeshAttribute;
970
+ nirAttribute?: MeshAttribute;
971
+ colorAttribute?: MeshAttribute;
972
+ /** Declared source RGB component depth. */
973
+ colorDepth?: number | string;
974
+ gpsTimeAttribute?: MeshAttribute;
975
+ scanAngleAttribute?: MeshAttribute;
976
+ userDataAttribute?: MeshAttribute;
977
+ pointSourceIdAttribute?: MeshAttribute;
978
+ returnNumberAttribute?: MeshAttribute;
979
+ numberOfReturnsAttribute?: MeshAttribute;
980
+ scannerChannelAttribute?: MeshAttribute;
981
+ scanDirectionFlagAttribute?: MeshAttribute;
982
+ edgeOfFlightLineAttribute?: MeshAttribute;
983
+ syntheticAttribute?: MeshAttribute;
984
+ keyPointAttribute?: MeshAttribute;
985
+ withheldAttribute?: MeshAttribute;
986
+ overlapAttribute?: MeshAttribute;
987
+ waveformDescriptorAttribute?: MeshAttribute;
988
+ waveformOffsetAttribute?: MeshAttribute;
989
+ waveformSizeAttribute?: MeshAttribute;
990
+ waveformReturnPointAttribute?: MeshAttribute;
991
+ waveformVectorAttribute?: MeshAttribute;
992
+ extraByteFields: readonly LASExtraByteField[];
993
+ }
994
+ ): void {
995
+ dataView.setUint16(
996
+ pointOffset + 12,
997
+ getUInt16Attribute(attributes.intensityAttribute, vertexIndex),
998
+ true
999
+ );
1000
+
1001
+ if (pointDataRecordFormat <= 5) {
1002
+ const returnNumber = getUInt8Attribute(attributes.returnNumberAttribute, vertexIndex, 1) & 7;
1003
+ const numberOfReturns =
1004
+ getUInt8Attribute(attributes.numberOfReturnsAttribute, vertexIndex, 1) & 7;
1005
+ dataView.setUint8(pointOffset + 14, returnNumber | (numberOfReturns << 3));
1006
+ dataView.setUint8(
1007
+ pointOffset + 15,
1008
+ getUInt8Attribute(attributes.classificationAttribute, vertexIndex) & 0x1f
1009
+ );
1010
+ dataView.setInt8(
1011
+ pointOffset + 16,
1012
+ getInt16Attribute(attributes.scanAngleAttribute, vertexIndex)
1013
+ );
1014
+ dataView.setUint8(
1015
+ pointOffset + 17,
1016
+ getUInt8Attribute(attributes.userDataAttribute, vertexIndex)
1017
+ );
1018
+ dataView.setUint16(
1019
+ pointOffset + 18,
1020
+ getUInt16Attribute(attributes.pointSourceIdAttribute, vertexIndex),
1021
+ true
1022
+ );
1023
+ if (pointDataRecordFormat === 1 || pointDataRecordFormat === 3 || pointDataRecordFormat >= 4) {
1024
+ dataView.setFloat64(
1025
+ pointOffset + 20,
1026
+ getAttributeValue(attributes.gpsTimeAttribute, vertexIndex),
1027
+ true
1028
+ );
1029
+ }
1030
+ if (pointDataRecordFormat === 4 || pointDataRecordFormat === 5) {
1031
+ const waveformOffset = pointDataRecordFormat === 4 ? 28 : 34;
1032
+ writeWaveformPacket(dataView, pointOffset + waveformOffset, vertexIndex, attributes);
1033
+ }
1034
+ } else {
1035
+ const returnNumber = getUInt8Attribute(attributes.returnNumberAttribute, vertexIndex, 1) & 0x0f;
1036
+ const numberOfReturns =
1037
+ getUInt8Attribute(attributes.numberOfReturnsAttribute, vertexIndex, 1) & 0x0f;
1038
+ const scannerChannel = getUInt8Attribute(attributes.scannerChannelAttribute, vertexIndex) & 3;
1039
+ const returnFlags = returnNumber | (numberOfReturns << 4);
1040
+ const classificationFlags =
1041
+ (getBooleanAttribute(attributes.syntheticAttribute, vertexIndex) ? 1 << 0 : 0) |
1042
+ (getBooleanAttribute(attributes.keyPointAttribute, vertexIndex) ? 1 << 1 : 0) |
1043
+ (getBooleanAttribute(attributes.withheldAttribute, vertexIndex) ? 1 << 2 : 0) |
1044
+ (getBooleanAttribute(attributes.overlapAttribute, vertexIndex) ? 1 << 3 : 0) |
1045
+ (scannerChannel << 4) |
1046
+ (getBooleanAttribute(attributes.scanDirectionFlagAttribute, vertexIndex) ? 1 << 6 : 0) |
1047
+ (getBooleanAttribute(attributes.edgeOfFlightLineAttribute, vertexIndex) ? 1 << 7 : 0);
1048
+ dataView.setUint8(pointOffset + 14, returnFlags);
1049
+ dataView.setUint8(pointOffset + 15, classificationFlags);
1050
+ dataView.setUint8(
1051
+ pointOffset + 16,
1052
+ getUInt8Attribute(attributes.classificationAttribute, vertexIndex)
1053
+ );
1054
+ dataView.setUint8(
1055
+ pointOffset + 17,
1056
+ getUInt8Attribute(attributes.userDataAttribute, vertexIndex)
1057
+ );
1058
+ dataView.setInt16(
1059
+ pointOffset + 18,
1060
+ getInt16Attribute(attributes.scanAngleAttribute, vertexIndex),
1061
+ true
1062
+ );
1063
+ dataView.setUint16(
1064
+ pointOffset + 20,
1065
+ getUInt16Attribute(attributes.pointSourceIdAttribute, vertexIndex),
1066
+ true
1067
+ );
1068
+ dataView.setFloat64(
1069
+ pointOffset + 22,
1070
+ getAttributeValue(attributes.gpsTimeAttribute, vertexIndex),
1071
+ true
1072
+ );
1073
+ if (pointDataRecordFormat === 9 || pointDataRecordFormat === 10) {
1074
+ const waveformOffset = pointDataRecordFormat === 9 ? 30 : 38;
1075
+ writeWaveformPacket(dataView, pointOffset + waveformOffset, vertexIndex, attributes);
1076
+ }
1077
+ }
1078
+
1079
+ writePointColor(
1080
+ dataView,
1081
+ pointOffset,
1082
+ vertexIndex,
1083
+ pointDataRecordFormat,
1084
+ attributes.colorAttribute,
1085
+ attributes.colorDepth
1086
+ );
1087
+ if (pointDataRecordFormat === 8 || pointDataRecordFormat === 10) {
1088
+ dataView.setUint16(
1089
+ pointOffset + 36,
1090
+ getUInt16Attribute(attributes.nirAttribute, vertexIndex),
1091
+ true
1092
+ );
1093
+ }
1094
+ writeExtraBytes(
1095
+ dataView,
1096
+ pointOffset,
1097
+ vertexIndex,
1098
+ basePointDataRecordLength,
1099
+ attributes.extraByteFields
1100
+ );
1101
+ }
1102
+
1103
+ /** Write one 29-byte LAS waveform packet reference. */
1104
+ function writeWaveformPacket(
1105
+ dataView: DataView,
1106
+ byteOffset: number,
1107
+ vertexIndex: number,
1108
+ attributes: {
1109
+ waveformDescriptorAttribute?: MeshAttribute;
1110
+ waveformOffsetAttribute?: MeshAttribute;
1111
+ waveformSizeAttribute?: MeshAttribute;
1112
+ waveformReturnPointAttribute?: MeshAttribute;
1113
+ waveformVectorAttribute?: MeshAttribute;
1114
+ }
1115
+ ): void {
1116
+ dataView.setUint8(
1117
+ byteOffset,
1118
+ getUInt8Attribute(attributes.waveformDescriptorAttribute, vertexIndex)
1119
+ );
1120
+ dataView.setBigUint64(
1121
+ byteOffset + 1,
1122
+ getBigUint64Attribute(attributes.waveformOffsetAttribute, vertexIndex),
1123
+ true
1124
+ );
1125
+ dataView.setUint32(
1126
+ byteOffset + 9,
1127
+ getUInt32Attribute(attributes.waveformSizeAttribute, vertexIndex),
1128
+ true
1129
+ );
1130
+ dataView.setFloat32(
1131
+ byteOffset + 13,
1132
+ getAttributeValue(attributes.waveformReturnPointAttribute, vertexIndex),
1133
+ true
1134
+ );
1135
+ for (let componentIndex = 0; componentIndex < 3; componentIndex++) {
1136
+ dataView.setFloat32(
1137
+ byteOffset + 17 + componentIndex * 4,
1138
+ getComponent(attributes.waveformVectorAttribute, vertexIndex, componentIndex),
1139
+ true
1140
+ );
1141
+ }
1142
+ }
1143
+
1144
+ /** Write configured Extra Bytes values into one raw LAS point record. */
1145
+ function writeExtraBytes(
1146
+ dataView: DataView,
1147
+ pointOffset: number,
1148
+ vertexIndex: number,
1149
+ basePointDataRecordLength: number,
1150
+ fields: readonly LASExtraByteField[]
1151
+ ): void {
1152
+ let byteOffset = pointOffset + basePointDataRecordLength;
1153
+ for (const field of fields) {
1154
+ for (let componentIndex = 0; componentIndex < field.componentCount; componentIndex++) {
1155
+ writeExtraBytesValue(dataView, byteOffset, vertexIndex, componentIndex, field);
1156
+ byteOffset += field.byteLength / field.componentCount;
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ /** Write one typed Extra Bytes component using its LAS data type code. */
1162
+ function writeExtraBytesValue(
1163
+ dataView: DataView,
1164
+ byteOffset: number,
1165
+ vertexIndex: number,
1166
+ componentIndex: number,
1167
+ field: LASExtraByteField
1168
+ ): void {
1169
+ const value = field.meshAttribute.value[vertexIndex * field.componentCount + componentIndex];
1170
+ const scalarDataType =
1171
+ field.dataType > 20
1172
+ ? field.dataType - 20
1173
+ : field.dataType > 10
1174
+ ? field.dataType - 10
1175
+ : field.dataType;
1176
+ switch (scalarDataType) {
1177
+ case 1:
1178
+ dataView.setUint8(byteOffset, Number(value));
1179
+ break;
1180
+ case 2:
1181
+ dataView.setInt8(byteOffset, Number(value));
1182
+ break;
1183
+ case 3:
1184
+ dataView.setUint16(byteOffset, Number(value), true);
1185
+ break;
1186
+ case 4:
1187
+ dataView.setInt16(byteOffset, Number(value), true);
1188
+ break;
1189
+ case 5:
1190
+ dataView.setUint32(byteOffset, Number(value), true);
1191
+ break;
1192
+ case 6:
1193
+ dataView.setInt32(byteOffset, Number(value), true);
1194
+ break;
1195
+ case 7:
1196
+ dataView.setBigUint64(byteOffset, BigInt(value), true);
1197
+ break;
1198
+ case 8:
1199
+ dataView.setBigInt64(byteOffset, BigInt(value), true);
1200
+ break;
1201
+ case 9:
1202
+ dataView.setFloat32(byteOffset, Number(value), true);
1203
+ break;
1204
+ case 10:
1205
+ dataView.setFloat64(byteOffset, Number(value), true);
1206
+ break;
1207
+ default:
1208
+ throw new Error(`LASWriter: unsupported Extra Bytes data type ${field.dataType}`);
1209
+ }
1210
+ }
1211
+
1212
+ /** Write RGB values for point formats that include color. */
1213
+ function writePointColor(
1214
+ dataView: DataView,
1215
+ pointOffset: number,
1216
+ vertexIndex: number,
1217
+ pointDataRecordFormat: number,
1218
+ colorAttribute?: MeshAttribute,
1219
+ colorDepth?: number | string
1220
+ ): void {
1221
+ const colorOffset = getColorOffset(pointDataRecordFormat);
1222
+ if (colorOffset < 0 || !colorAttribute) {
1223
+ return;
1224
+ }
1225
+ dataView.setUint16(
1226
+ pointOffset + colorOffset,
1227
+ getLASColor(colorAttribute, vertexIndex, 0, colorDepth),
1228
+ true
1229
+ );
1230
+ dataView.setUint16(
1231
+ pointOffset + colorOffset + 2,
1232
+ getLASColor(colorAttribute, vertexIndex, 1, colorDepth),
1233
+ true
1234
+ );
1235
+ dataView.setUint16(
1236
+ pointOffset + colorOffset + 4,
1237
+ getLASColor(colorAttribute, vertexIndex, 2, colorDepth),
1238
+ true
1239
+ );
1240
+ }
1241
+
1242
+ function getColorOffset(pointDataRecordFormat: number): number {
1243
+ switch (pointDataRecordFormat) {
1244
+ case 2:
1245
+ return 20;
1246
+ case 3:
1247
+ return 28;
1248
+ case 5:
1249
+ return 57;
1250
+ case 7:
1251
+ case 8:
1252
+ case 10:
1253
+ return 30;
1254
+ default:
1255
+ return -1;
1256
+ }
1257
+ }
1258
+
1259
+ function getDefaultPointDataRecordFormat(
1260
+ options: LASWriterOptions,
1261
+ colorAttribute?: MeshAttribute
1262
+ ): number {
1263
+ if (options.las?.format === 'laz' || options.las?.version === '1.4') {
1264
+ return colorAttribute ? 7 : 6;
1265
+ }
1266
+ return colorAttribute ? 2 : 0;
1267
+ }
1268
+
1269
+ function writeUint64Fallback(dataView: DataView, byteOffset: number, value: number): void {
1270
+ dataView.setUint32(byteOffset, value >>> 0, true);
1271
+ dataView.setUint32(byteOffset + 4, Math.floor(value / 2 ** 32), true);
1272
+ }
1273
+
1274
+ /** Write a fixed-length ASCII string into a DataView. */
1275
+ function writeString(
1276
+ dataView: DataView,
1277
+ byteOffset: number,
1278
+ text: string,
1279
+ byteLength: number
1280
+ ): void {
1281
+ if (/[^\x00-\x7f]/.test(text)) {
1282
+ throw new Error('LASWriter fixed-width text must be ASCII');
1283
+ }
1284
+ for (let characterIndex = 0; characterIndex < byteLength; characterIndex++) {
1285
+ dataView.setUint8(byteOffset + characterIndex, text.charCodeAt(characterIndex) || 0);
1286
+ }
1287
+ }
1288
+
1289
+ /** Return a single attribute component with 0 as the missing component fallback. */
1290
+ function getComponent(
1291
+ attribute: MeshAttribute | undefined,
1292
+ vertexIndex: number,
1293
+ componentIndex: number
1294
+ ): number {
1295
+ return attribute ? attribute.value[vertexIndex * attribute.size + componentIndex] || 0 : 0;
1296
+ }
1297
+
1298
+ /** Return a LAS UInt16 attribute value. */
1299
+ function getUInt16Attribute(attribute: MeshAttribute | undefined, vertexIndex: number): number {
1300
+ return attribute
1301
+ ? Math.max(0, Math.min(65535, Math.round(getComponent(attribute, vertexIndex, 0))))
1302
+ : 0;
1303
+ }
1304
+
1305
+ /** Return a LAS unsigned 32-bit attribute value. */
1306
+ function getUInt32Attribute(attribute: MeshAttribute | undefined, vertexIndex: number): number {
1307
+ return attribute
1308
+ ? Math.max(0, Math.min(0xffffffff, Math.round(getComponent(attribute, vertexIndex, 0))))
1309
+ : 0;
1310
+ }
1311
+
1312
+ /** Return a lossless unsigned 64-bit waveform offset attribute value. */
1313
+ function getBigUint64Attribute(attribute: MeshAttribute | undefined, vertexIndex: number): bigint {
1314
+ if (!attribute) {
1315
+ return 0n;
1316
+ }
1317
+ const value = attribute.value[vertexIndex * attribute.size];
1318
+ return typeof value === 'bigint' ? value : BigInt(Math.max(0, Math.round(Number(value))));
1319
+ }
1320
+
1321
+ /** Return a LAS UInt8 attribute value. */
1322
+ function getUInt8Attribute(
1323
+ attribute: MeshAttribute | undefined,
1324
+ vertexIndex: number,
1325
+ defaultValue = 0
1326
+ ): number {
1327
+ return attribute
1328
+ ? Math.max(0, Math.min(255, Math.round(getComponent(attribute, vertexIndex, 0))))
1329
+ : defaultValue;
1330
+ }
1331
+
1332
+ /** Return a LAS signed 16-bit attribute value. */
1333
+ function getInt16Attribute(attribute: MeshAttribute | undefined, vertexIndex: number): number {
1334
+ return attribute
1335
+ ? Math.max(-32768, Math.min(32767, Math.round(getComponent(attribute, vertexIndex, 0))))
1336
+ : 0;
1337
+ }
1338
+
1339
+ /** Return a numeric point attribute value, using zero when it is absent. */
1340
+ function getAttributeValue(attribute: MeshAttribute | undefined, vertexIndex: number): number {
1341
+ return attribute ? getComponent(attribute, vertexIndex, 0) : 0;
1342
+ }
1343
+
1344
+ /** Return a point flag attribute as a boolean. */
1345
+ function getBooleanAttribute(attribute: MeshAttribute | undefined, vertexIndex: number): boolean {
1346
+ return Boolean(getAttributeValue(attribute, vertexIndex));
1347
+ }
1348
+
1349
+ /** Count point records by return number for the LAS header histograms. */
1350
+ function getReturnCounts(
1351
+ returnNumberAttribute: MeshAttribute | undefined,
1352
+ vertexCount: number
1353
+ ): number[] {
1354
+ const returnCounts = new Array<number>(LAS_RETURN_BUCKET_COUNT).fill(0);
1355
+ for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
1356
+ const returnNumber = getUInt8Attribute(returnNumberAttribute, vertexIndex, 1) & 0x0f;
1357
+ if (returnNumber >= 1 && returnNumber <= LAS_RETURN_BUCKET_COUNT) {
1358
+ returnCounts[returnNumber - 1]++;
1359
+ }
1360
+ }
1361
+ return returnCounts;
1362
+ }
1363
+
1364
+ /** Return one color component as a LAS UInt16 color value. */
1365
+ function getLASColor(
1366
+ attribute: MeshAttribute,
1367
+ vertexIndex: number,
1368
+ componentIndex: number,
1369
+ colorDepth?: number | string
1370
+ ): number {
1371
+ const value = getComponent(attribute, vertexIndex, componentIndex);
1372
+ if (attribute.normalized) {
1373
+ return scaleNormalizedColor(attribute, value);
1374
+ }
1375
+
1376
+ if (colorDepth === 8) return clampUInt16(value * 257);
1377
+ if (colorDepth === 16) return clampUInt16(value);
1378
+
1379
+ if (value <= 1) {
1380
+ return clampUInt16(value * 65535);
1381
+ }
1382
+
1383
+ if (value <= 255) {
1384
+ return clampUInt16(value * 257);
1385
+ }
1386
+
1387
+ return clampUInt16(value);
1388
+ }
1389
+
1390
+ /** Return a normalized color component scaled to the LAS UInt16 color range. */
1391
+ function scaleNormalizedColor(attribute: MeshAttribute, value: number): number {
1392
+ const componentMaximum = getComponentMaximum(attribute.value);
1393
+ return clampUInt16((value / componentMaximum) * 65535);
1394
+ }
1395
+
1396
+ /** Return the component maximum used by the color attribute's typed array. */
1397
+ function getComponentMaximum(values: MeshAttribute['value']): number {
1398
+ if (values instanceof Uint8Array || values instanceof Uint8ClampedArray) {
1399
+ return 255;
1400
+ }
1401
+ if (values instanceof Uint16Array) {
1402
+ return 65535;
1403
+ }
1404
+ return 1;
1405
+ }
1406
+
1407
+ /** Clamp a number to the LAS UInt16 range. */
1408
+ function clampUInt16(value: number): number {
1409
+ return Math.max(0, Math.min(65535, Math.round(value)));
1410
+ }
1411
+
1412
+ /** Compute position bounds, using a finite zero box when there are no points. */
1413
+ function getBoundingBox(
1414
+ positionAttribute: MeshAttribute,
1415
+ vertexCount: number
1416
+ ): [[number, number, number], [number, number, number]] {
1417
+ if (vertexCount === 0) {
1418
+ return [
1419
+ [0, 0, 0],
1420
+ [0, 0, 0]
1421
+ ];
1422
+ }
1423
+ const minimum: [number, number, number] = [
1424
+ Number.POSITIVE_INFINITY,
1425
+ Number.POSITIVE_INFINITY,
1426
+ Number.POSITIVE_INFINITY
1427
+ ];
1428
+ const maximum: [number, number, number] = [
1429
+ Number.NEGATIVE_INFINITY,
1430
+ Number.NEGATIVE_INFINITY,
1431
+ Number.NEGATIVE_INFINITY
1432
+ ];
1433
+
1434
+ for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
1435
+ for (let componentIndex = 0; componentIndex < 3; componentIndex++) {
1436
+ const value = getComponent(positionAttribute, vertexIndex, componentIndex);
1437
+ minimum[componentIndex] = Math.min(minimum[componentIndex], value);
1438
+ maximum[componentIndex] = Math.max(maximum[componentIndex], value);
1439
+ }
1440
+ }
1441
+
1442
+ return [minimum, maximum];
1443
+ }
1444
+
1445
+ /** Return LAS coordinate scale factors from options, metadata, or defaults. */
1446
+ function getScale(mesh: Mesh, options: LASWriterOptions): [number, number, number] {
1447
+ return (
1448
+ options.las?.scale || getNumericTuple(mesh.schema.metadata?.las_scale) || [0.001, 0.001, 0.001]
1449
+ );
1450
+ }
1451
+
1452
+ /** Return LAS coordinate offsets from options, metadata, or the mesh minimum position. */
1453
+ function getOffset(
1454
+ mesh: Mesh,
1455
+ options: LASWriterOptions,
1456
+ boundingBox: [[number, number, number], [number, number, number]]
1457
+ ): [number, number, number] {
1458
+ return options.las?.offset || getNumericTuple(mesh.schema.metadata?.las_offset) || boundingBox[0];
1459
+ }
1460
+
1461
+ /** Parse a three-number metadata tuple encoded as JSON. */
1462
+ function getNumericTuple(value: unknown): [number, number, number] | undefined {
1463
+ if (typeof value !== 'string') {
1464
+ return undefined;
1465
+ }
1466
+
1467
+ try {
1468
+ const parsedValue = JSON.parse(value);
1469
+ return Array.isArray(parsedValue) &&
1470
+ parsedValue.length === 3 &&
1471
+ parsedValue.every(component => typeof component === 'number')
1472
+ ? [parsedValue[0], parsedValue[1], parsedValue[2]]
1473
+ : undefined;
1474
+ } catch {
1475
+ return undefined;
1476
+ }
1477
+ }