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