las-laz-js 0.3.0 → 0.4.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-10-22)
4
+
5
+ Writes LAZ.
6
+
7
+ - `writeLas(points, { compress: true })` and `createLasWriter(sink, { compress: true })` write LAZ:
8
+ point formats 0–3 (point-wise) and 6–8 (layered), with a LASzip VLR, 50,000-point chunks and a
9
+ chunk table, as PDAL (LAZperf) lays it out. Plain JavaScript, translated from laz-rs
10
+ (Apache-2.0), like the decoders. Formats 4, 5, 9, 10 cannot be compressed (`UNSUPPORTED`).
11
+ - `chunkSize`: points per chunk, a whole number 1 to 4294967294 (default 50,000); ignored without
12
+ `compress`.
13
+ - `threads`: chunks compress on the worker pool the LAZ reader uses, written in chunk order, so the
14
+ bytes are the same for any thread count. Automatic by default: from 4 chunks up, with at most 4
15
+ workers, as gains flatten after about 4 (explicit `threads` are honoured); `1` is the calling
16
+ thread. A 10 M-point LAZ (format 3) rewritten: `writeLas` 7166 ms on one thread, 1708 ms
17
+ automatic (4 workers, 4.2×), 1624 ms with 16; `createLasWriter` 7109 ms, 2033–2246 ms, 1657 ms.
18
+ - With workers, up to about 2 tasks of 4 chunks per worker of records are in flight: about 55 MB
19
+ at the automatic 4 workers with 50,000-point format 3 chunks. That holds across awaited
20
+ `writePoints` calls; unawaited calls, or one huge batch, submit at once. An abandoned writer
21
+ keeps its workers until `close()`.
22
+ - A streaming LAZ writer stops at its first error (a chunk, a sink write, `close()`):
23
+ `writePoints` and `close` keep rejecting. Start a new writer. After `close()`, both reject
24
+ (`ABORTED`).
25
+ - The compressed bytes are PDAL's (LAZperf's) with one lossless exception: in formats 6–8 a GPS
26
+ time of −0 following +0 is coded as a change (LAZperf finds the doubles equal).
27
+ - Extra bytes are not written yet (as for LAS); they arrive with 0.7.0.
28
+ - Checked against PDAL 2.10: the committed fixtures' chunks, chunk tables and LASzip VLRs are
29
+ PDAL's bytes; PDAL reads what we write (`pdal info`, `pdal translate`) and finds the same points
30
+ field by field. Real files compress and read back to the same points: a 1.1 GB, 34-million-point
31
+ LAS streams to a 155 MB LAZ (0.14 of its size) in 6.4 s; a 10-million-point LAZ recompresses to
32
+ the same size in 2.7 s. That file, written by PDAL 1.6 in 2019, comes back as LAS 1.2 format 3 with
33
+ the original's point data byte for byte, and opens in CloudCompare as LAS 1.2 format 3 and as
34
+ LAS 1.4 format 7.
35
+ - Docs: README "Writing LAZ"; the demo says LAZ is read and written.
36
+
37
+ ## 0.3.1 (2026-10-21)
38
+
39
+ Worker fixes.
40
+
41
+ - Node: a script that stopped part way through a parallel LAZ read (one `chunks()` window, then
42
+ done) never exited, because an idle pool kept Node running. Workers now hold Node only while
43
+ they have work.
44
+ - Node: `node --input-type=module -e` scripts can decode in parallel. Workers used to inherit
45
+ `--input-type` and fail to start.
46
+ - A worker that cannot start (blocked or failing script) now means one-thread decoding, as
47
+ documented, rather than a failed read. The pool waits for every worker to report ready.
48
+
3
49
  ## 0.3.0 (2026-10-21)
4
50
 
5
51
  Faster LAZ.
package/NOTICE CHANGED
@@ -1,7 +1,7 @@
1
1
  las-laz-js
2
2
  Copyright (c) 2026 Brent Buffham (https://kirra-design.com). MIT licence (see LICENSE).
3
3
 
4
- The LAZ decoder in src/laz/ is a JavaScript translation of laz-rs
4
+ The LAZ decoder and encoder in src/laz/ are a JavaScript translation of laz-rs
5
5
  (https://github.com/laz-rs/laz-rs), which is itself a Rust port of LASzip
6
6
  (https://laszip.org) by rapidlasso. Those files are licensed under the
7
7
  Apache License, Version 2.0 (see LICENSE-APACHE), not the MIT licence:
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # las-laz-js
2
2
 
3
- A JavaScript reader and writer for ASPRS **LAS** point clouds, and a reader for **LAZ**
3
+ A JavaScript reader and writer for ASPRS **LAS** point clouds and **LAZ**
4
4
  (LASzip-compressed LAS), published on npm as `las-laz-js`. Plain ES modules, no dependencies,
5
5
  runs in the browser and in Node — and built for files far larger than memory.
6
6
 
@@ -8,15 +8,16 @@ runs in the browser and in Node — and built for files far larger than memory.
8
8
 
9
9
  - Working: `openLas` reads the header, VLRs / EVLRs and coordinate system of LAS 1.0–1.4;
10
10
  `readPoints` and `chunks` decode every point format (0–10) a window at a time, with filters
11
- and thinning; `colourize` colours points; `writeLas` writes LAS 1.0–1.4.
12
- - LAZ: point formats 0–3 and 6–8, with extra bytes, read exactly like LAS — the same calls, the
13
- same options, decoded chunk by chunk in plain JavaScript (no WebAssembly).
11
+ and thinning; `colourize` colours points; `writeLas` and `createLasWriter` write LAS 1.0–1.4.
12
+ - LAZ: point formats 0–3 and 6–8, read exactly like LAS — the same calls, the same options,
13
+ decoded chunk by chunk in plain JavaScript (no WebAssembly) — and written with
14
+ `compress: true`, byte for byte what PDAL (LAZperf) writes (bar one lossless exception, see
15
+ Writing LAZ), compressed in parallel.
14
16
  - Big files: a 1.1 GB, 34-million-point LAS reads in under a second in Node, and a 10-million-point
15
17
  LAZ decodes on every core (a 10-million-point file in about 0.6 s); neither is held in memory
16
18
  whole (see LAZ below for the one exception).
17
19
  - Also: a ready-made module Worker (`las-laz-js/worker`) and a
18
20
  [Three.js demo](https://brentbuffham.github.io/las-laz-js/).
19
- - Not yet: writing LAZ (0.3.0).
20
21
 
21
22
  ## Install
22
23
 
@@ -135,6 +136,7 @@ import { writeLas, createLasWriter } from "las-laz-js";
135
136
 
136
137
  var bytes = await writeLas(pts, { version: "1.2", pointFormat: 3, crs: { epsg: 28350 } });
137
138
  var v14 = await writeLas(pts, { version: "1.4", pointFormat: 7, crs: { wkt: las.crs.wkt } });
139
+ var laz = await writeLas(pts, { compress: true, pointFormat: 3 }); // LAZ
138
140
 
139
141
  var w = await createLasWriter(sink, { version: "1.2", pointFormat: 3, scale, offset });
140
142
  // sink: an OPFS writer from openScratchSyncWriter, a Node FileHandle, or any { write(bytes, position) }
@@ -146,7 +148,44 @@ A version and point format that do not go together (format 6 in LAS 1.2) are ref
146
148
  silently changed. Scale and offset are chosen from the bounds when not given. `crs.epsg`
147
149
  writes a GeoKey record; `crs.wkt` a WKT record. Point formats 6–10 must carry WKT (LAS 1.4), so
148
150
  an EPSG code alone is refused for them — the library has no projection database to turn a code
149
- into WKT. Writing LAZ arrives in 0.3.0.
151
+ into WKT.
152
+
153
+ | Option | Default | Meaning |
154
+ |---|---|---|
155
+ | `compress` | `false` | write LAZ (LASzip) instead of LAS: point formats 0–3 and 6–8; formats 4, 5, 9, 10 are refused (`UNSUPPORTED`) |
156
+ | `chunkSize` | `50000` | points per LAZ chunk, a whole number from 1 to 4294967294; ignored without `compress` |
157
+ | `threads` | automatic | workers that compress chunks: automatic by default (from 4 chunks up, at most 4 workers), `1` compresses on the calling thread, `n` asks for n workers; only with `compress` |
158
+
159
+ ### Writing LAZ
160
+
161
+ `writeLas(pts, { compress: true })` and `createLasWriter(sink, { compress: true, ... })` write the
162
+ same records as the LAS writers, compressed `chunkSize` points at a time (LASzip's 50,000 by
163
+ default) behind a LASzip VLR, with the chunk table at the end of the point data. The layout and
164
+ the compressed bytes are those PDAL (LAZperf) writes: for the same points, version, format, scale
165
+ and offset, the point data is identical to PDAL's — with one exception: in formats 6–8 a GPS time
166
+ of −0 following +0 is coded as a change, so it reads back as −0 (lossless), where LAZperf compares
167
+ the doubles, finds them equal and writes no change. Point formats 0–3 are written point-wise
168
+ (LASzip item version 2), 6–8 layered (item version 3).
169
+
170
+ - **Speed.** Chunks are independent, so they compress on the same pool of workers that decode
171
+ LAZ, and are written strictly in chunk order: the bytes are the same for any `threads`. Gains
172
+ flatten after about four workers, so with `threads` automatic the writers use at most four (one
173
+ fewer than the cores, if that is less): a 10-million-point format 3 file writes in about 1.7 s
174
+ that way on a 24-core machine, 1.6 s with `threads: 16`, about 7 s on one thread. A write of
175
+ under four chunks (200,000 points) stays on one thread, where workers would cost more than they
176
+ save. If workers cannot start, it falls back to one thread.
177
+ - **Memory.** With workers, up to about 2 tasks of 4 chunks per worker are in flight at once —
178
+ about 55 MB of records at the automatic four workers with 50,000-point format 3 chunks (about
179
+ 220 MB at `threads: 16`). That bound holds across awaited `writePoints` calls; calls made
180
+ without waiting, or one huge batch, submit all at once. Pass a smaller `threads` or `chunkSize`
181
+ to bound it further. A writer that is abandoned without `close()` keeps its workers in a
182
+ browser until it is closed.
183
+ - **Errors.** A streaming LAZ writer stops for good at its first error — in a chunk, in a sink
184
+ write or in `close()` — and `writePoints` and `close` keep rejecting with it. Start a new
185
+ writer. After a successful `close()`, both reject (`ABORTED`, "the LAZ writer is closed").
186
+ - **Extra bytes** are not written (the writers have never written them): a file read with
187
+ extra bytes is written without them. They arrive with 0.7.0.
188
+ - The streaming writer needs `scale` and `offset` up front, as for LAS.
150
189
 
151
190
  ## In a Worker
152
191
 
@@ -160,7 +199,7 @@ worker.postMessage({ id: 2, type: "chunks", payload: { fields: ["x", "y", "z"],
160
199
  The protocol is documented in `src/workers/workerProtocol.js` and can be driven directly with
161
200
  `createWorkerHandler(post)`.
162
201
 
163
- **Bundlers.** The worker entry and the LAZ decode workers are found through
202
+ **Bundlers.** The worker entry and the LAZ decode and encode workers are found through
164
203
  `new Worker(new URL(..., import.meta.url))`, which Vite and webpack 5 follow. With Vite, keep the
165
204
  package out of dependency pre-bundling (`optimizeDeps: { exclude: ["las-laz-js"] }`), so the
166
205
  worker files stay next to the module that loads them.
@@ -182,11 +221,12 @@ COPC, EPT and E57 are not read.
182
221
  ## Provenance
183
222
 
184
223
  The LAS reader, writer and big-file handling come from [Kirra](https://kirra-design.com),
185
- Brent Buffham's blast-design app, rewritten as a standalone library. The LAZ decoder in
186
- `src/laz/` is a JavaScript translation of [laz-rs](https://github.com/laz-rs/laz-rs) by Thomas
224
+ Brent Buffham's blast-design app, rewritten as a standalone library. The LAZ decoder and encoder in
225
+ `src/laz/` are a JavaScript translation of [laz-rs](https://github.com/laz-rs/laz-rs) by Thomas
187
226
  Montaigu, itself a Rust port of [LASzip](https://laszip.org) by Martin Isenburg (rapidlasso).
188
- It is checked against an independent encoder: the test fixtures were compressed by
189
- [PDAL](https://pdal.io), and every record must equal PDAL's own decompression.
227
+ It is checked against an independent implementation: the test fixtures were compressed by
228
+ [PDAL](https://pdal.io), every record must equal PDAL's own decompression, and what this
229
+ library compresses must equal PDAL's bytes and be read by PDAL.
190
230
  Formats are written to the ASPRS LAS 1.4 R15 specification; see `docs/LAS_FORMAT_NOTES.md`.
191
231
 
192
232
  ## Development
@@ -200,12 +240,14 @@ node _dev/makeLazFixtures.mjs # remake test/fixtures/laz with PDAL (needs PDAL
200
240
  ```
201
241
 
202
242
  Private sample files go in the gitignored `samples/` folder, or are listed by path in
203
- `samples/paths.txt`; the sample tests compare each file with its own header, and each LAZ with a
204
- LAS twin (same count and bounds) field by field.
243
+ `samples/paths.txt`; the sample tests compare each file with its own header, each LAZ with a
244
+ LAS twin (same count and bounds) field by field, and compress each file to LAZ and read it back.
245
+ Set `LAS_PDAL=<path to pdal>` to also have PDAL read what this library writes
246
+ (`test/laz/pdalWrite.test.js` and the sample tests).
205
247
 
206
248
  ## Licence
207
249
 
208
- `MIT AND Apache-2.0`. The library is MIT © Brent Buffham (`LICENSE`). The LAZ decoder in
250
+ `MIT AND Apache-2.0`. The library is MIT © Brent Buffham (`LICENSE`). The LAZ decoder and encoder in
209
251
  `src/laz/`, translated from laz-rs / LASzip, is under the Apache License 2.0 (`LICENSE-APACHE`,
210
252
  `NOTICE`). Both are permissive: use, change and ship it in open or closed software, keeping the
211
253
  notices.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "las-laz-js",
3
- "version": "0.3.0",
4
- "description": "Zero-dependency JavaScript reader and writer for ASPRS LAS point clouds, and reader for LAZ (LASzip), for the browser and Node, built for files larger than memory.",
3
+ "version": "0.4.0",
4
+ "description": "Zero-dependency JavaScript reader and writer for ASPRS LAS point clouds, and reader and writer for LAZ (LASzip), for the browser and Node, built for files larger than memory.",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./src/index.js",
package/src/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  //==========================================================
7
7
 
8
8
  /** Library version, matches package.json. */
9
- export var VERSION = "0.3.0";
9
+ export var VERSION = "0.4.0";
10
10
 
11
11
  export { LasError, ERROR_CODES } from "./errors.js";
12
12
  export { openLas, LasReader } from "./LasReader.js";
@@ -0,0 +1,123 @@
1
+ // src/laz/ArithmeticEncoder.js
2
+ //==========================================================
3
+ // The LASzip arithmetic encoder, the writing half of ArithmeticDecoder:
4
+ // whatever it writes, the decoder reads back exactly. Bytes go into a
5
+ // growable Uint8Array (64 KiB to start, doubled when full); done()
6
+ // flushes the coder and returns a copy of the bytes written. A carry out
7
+ // of base walks back over those bytes, as laz-rs walks its buffer.
8
+ //
9
+ // Ported from laz-rs src/encoders.rs (https://github.com/laz-rs/laz-rs),
10
+ // itself a port of LASzip by rapidlasso. (c) 2007-2014 martin isenburg,
11
+ // rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
12
+ // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
13
+ // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026;
14
+ // written into a growable byte array instead of laz-rs's ring buffer.
15
+ // base and range are u32 in Numbers (no bit operators: sums and
16
+ // products stay below 2^53 and wrap by subtraction or modulo).
17
+ // The models are the library's (models.js): the encoder and decoder
18
+ // must share them, as LASzip's do.
19
+ //==========================================================
20
+
21
+ var AC_MIN_LENGTH = 0x01000000;
22
+ var FIRST_ALLOCATION = 65536;
23
+
24
+ /** An arithmetic encoder over a growable byte buffer. */
25
+ export function ArithmeticEncoder() {
26
+ this.buf = new Uint8Array(FIRST_ALLOCATION);
27
+ this.n = 0; // bytes written
28
+ this.base = 0;
29
+ this.range = 0xFFFFFFFF;
30
+ }
31
+
32
+ ArithmeticEncoder.prototype._put = function (b) {
33
+ if (this.n === this.buf.length) {
34
+ var bigger = new Uint8Array(this.buf.length * 2);
35
+ bigger.set(this.buf);
36
+ this.buf = bigger;
37
+ }
38
+ this.buf[this.n++] = b;
39
+ };
40
+
41
+ /** Add one to the bytes already written (base overflowed 2^32). */
42
+ ArithmeticEncoder.prototype._carry = function () {
43
+ var p = this.n - 1;
44
+ while (this.buf[p] === 0xFF) { this.buf[p] = 0; p--; }
45
+ this.buf[p]++;
46
+ };
47
+
48
+ ArithmeticEncoder.prototype._add = function (x) {
49
+ var b = this.base + x;
50
+ if (b >= 4294967296) { b -= 4294967296; this._carry(); }
51
+ this.base = b;
52
+ };
53
+
54
+ ArithmeticEncoder.prototype._renorm = function () {
55
+ do {
56
+ this._put(Math.floor(this.base / 16777216));
57
+ this.base = (this.base % 16777216) * 256;
58
+ this.range = (this.range * 256) % 4294967296;
59
+ } while (this.range < AC_MIN_LENGTH);
60
+ };
61
+
62
+ /** Encode one bit with an adaptive binary model. */
63
+ ArithmeticEncoder.prototype.encodeBit = function (m, sym) {
64
+ var x = m.bit0Prob * Math.floor(this.range / 8192); // BM_LENGTH_SHIFT
65
+ if (sym === 0) { this.range = x; m.bit0Count++; }
66
+ else { this._add(x); this.range -= x; }
67
+ if (this.range < AC_MIN_LENGTH) this._renorm();
68
+ if (--m.bitsUntilUpdate === 0) m.update();
69
+ };
70
+
71
+ /** Encode one symbol with an adaptive multi-symbol model. */
72
+ ArithmeticEncoder.prototype.encodeSymbol = function (m, sym) {
73
+ var x;
74
+ if (sym === m.lastSymbol) {
75
+ x = m.distribution[sym] * Math.floor(this.range / 32768); // DM_LENGTH_SHIFT
76
+ this._add(x);
77
+ this.range -= x;
78
+ } else {
79
+ this.range = Math.floor(this.range / 32768);
80
+ x = m.distribution[sym] * this.range;
81
+ this._add(x);
82
+ this.range = m.distribution[sym + 1] * this.range - x;
83
+ }
84
+ if (this.range < AC_MIN_LENGTH) this._renorm();
85
+ m.symbolCount[sym]++;
86
+ if (--m.symbolsUntilUpdate === 0) m.update();
87
+ };
88
+
89
+ /** `bits` raw bits (1-32) of an unsigned Number. */
90
+ ArithmeticEncoder.prototype.writeBits = function (bits, sym) {
91
+ if (bits > 19) { this.writeShort(sym % 65536); sym = Math.floor(sym / 65536); bits -= 16; }
92
+ this.range = Math.floor(this.range / Math.pow(2, bits));
93
+ this._add(sym * this.range);
94
+ if (this.range < AC_MIN_LENGTH) this._renorm();
95
+ };
96
+
97
+ /** 16 raw bits. */
98
+ ArithmeticEncoder.prototype.writeShort = function (sym) {
99
+ this.range = Math.floor(this.range / 65536);
100
+ this._add(sym * this.range);
101
+ if (this.range < AC_MIN_LENGTH) this._renorm();
102
+ };
103
+
104
+ /** 32 raw bits (low 16 first), from an unsigned Number. */
105
+ ArithmeticEncoder.prototype.writeInt = function (sym) {
106
+ this.writeShort(sym % 65536);
107
+ this.writeShort(Math.floor(sym / 65536));
108
+ };
109
+
110
+ /**
111
+ * Flush the coder, as laz-rs done().
112
+ * @returns {Uint8Array} a copy of the bytes written (the encoder is finished)
113
+ */
114
+ ArithmeticEncoder.prototype.done = function () {
115
+ var another = true;
116
+ if (this.range > 2 * AC_MIN_LENGTH) { this._add(AC_MIN_LENGTH); this.range = AC_MIN_LENGTH / 2; }
117
+ else { this._add(AC_MIN_LENGTH / 2); this.range = AC_MIN_LENGTH / 512; another = false; }
118
+ this._renorm();
119
+ this._put(0);
120
+ this._put(0);
121
+ if (another) this._put(0);
122
+ return this.buf.slice(0, this.n);
123
+ };
@@ -0,0 +1,76 @@
1
+ // src/laz/IntegerCompressor.js
2
+ //==========================================================
3
+ // LASzip's integer compressor, the writing half of IntegerDecompressor:
4
+ // an integer is coded as a correction to a prediction — first k, the
5
+ // bit length of the correction (one model per context), then the
6
+ // correction's bits (modelled up to bitsHigh bits, raw above). The
7
+ // correction wraps into the integer's range (i32 wrap for 32-bit
8
+ // integers), so the decoder's wrap undoes it.
9
+ //
10
+ // Ported from laz-rs src/compressors.rs (https://github.com/laz-rs/laz-rs),
11
+ // itself a port of LASzip by rapidlasso. (c) 2007-2014 martin isenburg,
12
+ // rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
13
+ // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
14
+ // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026.
15
+ //==========================================================
16
+
17
+ import { ArithmeticModel, ArithmeticBitModel } from "./models.js";
18
+
19
+ /**
20
+ * @param {number} [bits] - integer width (default 16; 32 wraps as i32)
21
+ * @param {number} [contexts] - number of k models (default 1)
22
+ * @param {number} [bitsHigh] - correction bits that are modelled (default 8)
23
+ * (laz-rs also takes an explicit range; no LAZ item uses one, so it is left out)
24
+ */
25
+ export function IntegerCompressor(bits, contexts, bitsHigh) {
26
+ bits = bits === undefined ? 16 : bits;
27
+ this.contexts = contexts === undefined ? 1 : contexts;
28
+ this.bitsHigh = bitsHigh === undefined ? 8 : bitsHigh;
29
+ if (bits && bits < 32) {
30
+ this.corrBits = bits;
31
+ this.corrRange = Math.pow(2, bits);
32
+ this.corrMin = -this.corrRange / 2;
33
+ this.corrMax = this.corrMin + this.corrRange - 1;
34
+ } else {
35
+ this.corrBits = 32;
36
+ this.corrRange = 0;
37
+ this.corrMin = -2147483648;
38
+ this.corrMax = 2147483647;
39
+ }
40
+ this.k = 0; // bit length of the last correction, as IntegerDecompressor.k
41
+ this.mBits = [];
42
+ for (var c = 0; c < this.contexts; c++) this.mBits.push(new ArithmeticModel(this.corrBits + 1));
43
+ this.mCorrector0 = new ArithmeticBitModel();
44
+ this.mCorrector = [];
45
+ for (var i = 1; i <= this.corrBits; i++) this.mCorrector.push(new ArithmeticModel(Math.pow(2, i <= this.bitsHigh ? i : this.bitsHigh)));
46
+ }
47
+
48
+ /**
49
+ * @param {import("./ArithmeticEncoder.js").ArithmeticEncoder} enc
50
+ * @param {number} pred - the prediction (i32)
51
+ * @param {number} real - the integer to write
52
+ * @param {number} context
53
+ */
54
+ IntegerCompressor.prototype.compress = function (enc, pred, real, context) {
55
+ var corr = (real - pred) | 0; // i32 wrapping_sub
56
+ if (corr < this.corrMin) corr += this.corrRange;
57
+ else if (corr > this.corrMax) corr -= this.corrRange;
58
+ var c1 = corr <= 0 ? -corr : corr - 1, k = 0;
59
+ while (c1 >= 1) { c1 = Math.floor(c1 / 2); k++; }
60
+ this.k = k;
61
+ enc.encodeSymbol(this.mBits[context], k);
62
+ if (k !== 0) {
63
+ if (k < 32) {
64
+ var c = corr >= 0 ? corr - 1 : corr + Math.pow(2, k) - 1;
65
+ if (k <= this.bitsHigh) {
66
+ enc.encodeSymbol(this.mCorrector[k - 1], c);
67
+ } else {
68
+ var k1 = k - this.bitsHigh, low = c % Math.pow(2, k1);
69
+ enc.encodeSymbol(this.mCorrector[k - 1], Math.floor(c / Math.pow(2, k1)));
70
+ enc.writeBits(k1, low);
71
+ }
72
+ }
73
+ } else {
74
+ enc.encodeBit(this.mCorrector0, corr);
75
+ }
76
+ };
package/src/laz/LazVlr.js CHANGED
@@ -12,10 +12,12 @@
12
12
  // rapidlasso; (c) 2019-2023 Thomas Montaigu.
13
13
  // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
14
14
  // NOTICE). Changes: written
15
- // in JavaScript from that layout, Brent Buffham 2026.
15
+ // in JavaScript from that layout; lazVlrFor (the writing half) added,
16
+ // Brent Buffham 2026.
16
17
  //==========================================================
17
18
 
18
19
  import { LasError, ERROR_CODES } from "../errors.js";
20
+ import { POINT_FORMATS } from "../format/PointFormats.js";
19
21
 
20
22
  /** LASzip compressor kinds. */
21
23
  export var COMPRESSOR = Object.freeze({ NONE: 0, POINTWISE: 1, POINTWISE_CHUNKED: 2, LAYERED_CHUNKED: 3 });
@@ -63,3 +65,63 @@ export function findLazVlr(vlrs) {
63
65
  }
64
66
  return null;
65
67
  }
68
+
69
+ // The items of each writable point format as [type, size, version]; the
70
+ // extra-bytes item (Byte / Byte14) follows when the record is longer
71
+ var FORMAT_ITEMS = {
72
+ 0: [[ITEM.POINT10, 20, 2]],
73
+ 1: [[ITEM.POINT10, 20, 2], [ITEM.GPSTIME11, 8, 2]],
74
+ 2: [[ITEM.POINT10, 20, 2], [ITEM.RGB12, 6, 2]],
75
+ 3: [[ITEM.POINT10, 20, 2], [ITEM.GPSTIME11, 8, 2], [ITEM.RGB12, 6, 2]],
76
+ 6: [[ITEM.POINT14, 30, 3]],
77
+ 7: [[ITEM.POINT14, 30, 3], [ITEM.RGB14, 6, 3]],
78
+ 8: [[ITEM.POINT14, 30, 3], [ITEM.RGBNIR14, 8, 3]]
79
+ };
80
+
81
+ /**
82
+ * The LASzip VLR a writer puts in a file: compressor 2 (formats 0-3) or 3 (6-8),
83
+ * LASzip 3.4 r3, chunked.
84
+ * @param {number} pointFormat - 0-3 or 6-8 (4, 5, 9, 10 carry waveform or 1.3 items that are not written)
85
+ * @param {number} recordLength - the file's point record length
86
+ * @param {number} chunkSize - points per chunk
87
+ * @returns {{vlr: object, bytes: Uint8Array}} vlr has the parseLazVlr shape; bytes is its 34 + 6 * items payload
88
+ */
89
+ export function lazVlrFor(pointFormat, recordLength, chunkSize) {
90
+ var layered = pointFormat >= 6;
91
+ if (!FORMAT_ITEMS[pointFormat]) {
92
+ throw new LasError(ERROR_CODES.UNSUPPORTED, "LAZ compression of point format " + pointFormat + " is not written (formats 0-3 and 6-8 only)");
93
+ }
94
+ var extra = recordLength - POINT_FORMATS[pointFormat].size;
95
+ if (!(extra >= 0 && extra <= 0xFFFF)) {
96
+ throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "record length " + recordLength + " does not fit point format " + pointFormat + " (" + POINT_FORMATS[pointFormat].size + " bytes)");
97
+ }
98
+ var items = FORMAT_ITEMS[pointFormat].map(function (it) {
99
+ return { type: it[0], size: it[1], version: it[2] };
100
+ });
101
+ if (extra > 0) items.push({ type: layered ? ITEM.BYTE14 : ITEM.BYTE, size: extra, version: layered ? 3 : 2 });
102
+ var vlr = {
103
+ compressor: layered ? COMPRESSOR.LAYERED_CHUNKED : COMPRESSOR.POINTWISE_CHUNKED,
104
+ coder: 0,
105
+ version: { major: 3, minor: 4, revision: 3 },
106
+ options: 0,
107
+ chunkSize: chunkSize,
108
+ variableChunks: false,
109
+ items: items
110
+ };
111
+ var bytes = new Uint8Array(34 + 6 * items.length), v = new DataView(bytes.buffer);
112
+ v.setUint16(0, vlr.compressor, true);
113
+ v.setUint16(2, 0, true);
114
+ v.setUint8(4, 3);
115
+ v.setUint8(5, 4);
116
+ v.setUint16(6, 3, true);
117
+ v.setUint32(8, 0, true);
118
+ v.setUint32(12, chunkSize, true);
119
+ for (var at = 16; at < 32; at += 4) v.setUint32(at, 0xFFFFFFFF, true); // no special EVLRs: count -1, offset -1 (two i64)
120
+ v.setUint16(32, items.length, true);
121
+ items.forEach(function (it, i) {
122
+ v.setUint16(34 + i * 6, it.type, true);
123
+ v.setUint16(36 + i * 6, it.size, true);
124
+ v.setUint16(38 + i * 6, it.version, true);
125
+ });
126
+ return { vlr: vlr, bytes: bytes };
127
+ }