las-laz-js 0.3.1 → 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 +34 -0
- package/NOTICE +1 -1
- package/README.md +56 -14
- package/package.json +2 -2
- package/src/index.js +1 -1
- package/src/laz/ArithmeticEncoder.js +123 -0
- package/src/laz/IntegerCompressor.js +76 -0
- package/src/laz/LazVlr.js +63 -1
- package/src/laz/chunkTable.js +139 -109
- package/src/laz/chunkWorker.js +49 -18
- package/src/laz/compressChunk.js +107 -0
- package/src/laz/decompressChunk.js +40 -10
- package/src/laz/pool.js +32 -18
- package/src/laz/v2/byte.js +23 -7
- package/src/laz/v2/gpstime11.js +165 -25
- package/src/laz/v2/point10.js +91 -22
- package/src/laz/v2/rgb12.js +62 -7
- package/src/laz/v3/layered.js +138 -40
- package/src/laz/v3/point14.js +231 -33
- package/src/write/encodeRecords.js +3 -2
- package/src/write/writeLas.js +259 -6
package/src/laz/pool.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// src/laz/pool.js
|
|
2
2
|
//==========================================================
|
|
3
|
-
// A pool of workers that decode LAZ chunks in parallel
|
|
4
|
-
// independent, so N cores
|
|
5
|
-
// Workers or Node worker_threads, running
|
|
6
|
-
// queue and go to the next free worker;
|
|
7
|
-
// buffers
|
|
8
|
-
//
|
|
3
|
+
// A pool of workers that decode and compress LAZ chunks in parallel
|
|
4
|
+
// (chunks are independent, so N cores do about N times the work).
|
|
5
|
+
// Browser module Workers or Node worker_threads, running
|
|
6
|
+
// src/laz/chunkWorker.js. Tasks queue and go to the next free worker;
|
|
7
|
+
// their buffers go there, and the results come back, transferred.
|
|
8
|
+
// close() terminates every worker and fails what is still queued, so
|
|
9
|
+
// nothing outlives a read or a write. When workers cannot start (no
|
|
9
10
|
// Worker, a blocked or failing script), createLazPool returns null and
|
|
10
|
-
// the reader
|
|
11
|
+
// the reader or writer works on its own thread.
|
|
11
12
|
//
|
|
12
13
|
// Uses the chunk decoder translated from laz-rs (https://github.com/laz-rs/laz-rs),
|
|
13
14
|
// itself a port of LASzip by rapidlasso. (c) 2007-2015 martin isenburg,
|
|
@@ -23,7 +24,7 @@ var NODE_OS = ["node", "os"].join(":");
|
|
|
23
24
|
var MAX_THREADS = 16;
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* How many
|
|
27
|
+
* How many LAZ workers to use by default: one fewer than the cores, at most 16.
|
|
27
28
|
* @returns {Promise<number>}
|
|
28
29
|
*/
|
|
29
30
|
export async function defaultThreads() {
|
|
@@ -81,14 +82,14 @@ async function spawn(onMessage, onFail, nodeUrl) {
|
|
|
81
82
|
/**
|
|
82
83
|
* @param {number} n - workers to start (2 or more)
|
|
83
84
|
* @param {URL} [nodeUrl] - a Node worker entry in place of chunkWorker.js (tests)
|
|
84
|
-
* @returns {Promise<{size:number, decode:function(object): Promise<Uint8Array>, close:function(): void}|null>}
|
|
85
|
+
* @returns {Promise<{size:number, decode:function(object): Promise<Uint8Array>, compress:function(object): Promise<{bytes:Uint8Array, sizes:number[]}>, close:function(): void}|null>}
|
|
85
86
|
*/
|
|
86
87
|
export async function createLazPool(n, nodeUrl) {
|
|
87
88
|
var workers = [], idle = [], queue = [], waiting = new Map(), nextId = 1, closed = false;
|
|
88
89
|
|
|
89
90
|
function fail(err) {
|
|
90
91
|
// a worker that dies takes the pool with it: every waiting task fails
|
|
91
|
-
var e = err instanceof LasError ? err : new LasError(ERROR_CODES.UNSUPPORTED, "a LAZ
|
|
92
|
+
var e = err instanceof LasError ? err : new LasError(ERROR_CODES.UNSUPPORTED, "a LAZ worker failed: " + (err && err.message ? err.message : String(err)));
|
|
92
93
|
waiting.forEach(function (job) { job.reject(e); });
|
|
93
94
|
waiting.clear();
|
|
94
95
|
queue.forEach(function (job) { job.reject(e); });
|
|
@@ -101,7 +102,7 @@ export async function createLazPool(n, nodeUrl) {
|
|
|
101
102
|
job.worker = w;
|
|
102
103
|
waiting.set(job.msg.id, job);
|
|
103
104
|
if (w.hold) w.hold();
|
|
104
|
-
w.post(job.msg,
|
|
105
|
+
w.post(job.msg, job.transfer);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -112,7 +113,7 @@ export async function createLazPool(n, nodeUrl) {
|
|
|
112
113
|
if (job.worker.release) job.worker.release();
|
|
113
114
|
idle.push(job.worker);
|
|
114
115
|
if (m.error) job.reject(new LasError(m.error.code || ERROR_CODES.UNSUPPORTED, m.error.message, m.error.offset));
|
|
115
|
-
else job.resolve(
|
|
116
|
+
else job.resolve(job.result(m));
|
|
116
117
|
pump();
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -127,6 +128,14 @@ export async function createLazPool(n, nodeUrl) {
|
|
|
127
128
|
}
|
|
128
129
|
idle = workers.slice();
|
|
129
130
|
|
|
131
|
+
function run(msg, transfer, result) {
|
|
132
|
+
if (closed) return Promise.reject(new LasError(ERROR_CODES.ABORTED, "the LAZ pool is closed"));
|
|
133
|
+
return new Promise(function (resolve, reject) {
|
|
134
|
+
queue.push({ msg: Object.assign({ id: nextId++ }, msg), transfer: transfer, result: result, resolve: resolve, reject: reject });
|
|
135
|
+
pump();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
return {
|
|
131
140
|
size: n,
|
|
132
141
|
/**
|
|
@@ -135,16 +144,21 @@ export async function createLazPool(n, nodeUrl) {
|
|
|
135
144
|
* @returns {Promise<Uint8Array>} the chunks' raw LAS records, in order
|
|
136
145
|
*/
|
|
137
146
|
decode: function (task) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
return run(task, [task.packed], function (m) { return new Uint8Array(m.records); });
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* Compress whole chunks from their own copy of the raw records (transferred).
|
|
151
|
+
* @param {{vlr:object, recordLength:number, records:ArrayBuffer, counts:number[]}} task
|
|
152
|
+
* counts: each chunk's points, the chunks back to back from the start of records
|
|
153
|
+
* @returns {Promise<{bytes:Uint8Array, sizes:number[]}>} the chunks, back to back, and each one's length
|
|
154
|
+
*/
|
|
155
|
+
compress: function (task) {
|
|
156
|
+
return run(Object.assign({ op: "compress" }, task), [task.records], function (m) { return { bytes: new Uint8Array(m.chunks), sizes: m.sizes }; });
|
|
143
157
|
},
|
|
144
158
|
close: function () {
|
|
145
159
|
if (closed) return;
|
|
146
160
|
closed = true;
|
|
147
|
-
fail(new LasError(ERROR_CODES.ABORTED, "the
|
|
161
|
+
fail(new LasError(ERROR_CODES.ABORTED, "the LAZ pool was closed"));
|
|
148
162
|
workers.forEach(function (w) { w.close(); });
|
|
149
163
|
}
|
|
150
164
|
};
|
package/src/laz/v2/byte.js
CHANGED
|
@@ -8,19 +8,23 @@
|
|
|
8
8
|
// itself a port of LASzip by rapidlasso. (c) 2007-2014 martin isenburg,
|
|
9
9
|
// rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
|
|
10
10
|
// Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
|
|
11
|
-
// NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026
|
|
11
|
+
// NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026;
|
|
12
|
+
// the compressor (ByteCompressor) added beside the decompressor.
|
|
12
13
|
//==========================================================
|
|
13
14
|
|
|
14
15
|
import { ArithmeticModel } from "../models.js";
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
for (var i = 0; i < count; i++)
|
|
17
|
+
// The state and models both halves start a chunk with: the last bytes and one model per byte
|
|
18
|
+
function initByte(self, count) {
|
|
19
|
+
self.size = count;
|
|
20
|
+
self.last = new Uint8Array(count);
|
|
21
|
+
self.models = [];
|
|
22
|
+
for (var i = 0; i < count; i++) self.models.push(new ArithmeticModel(256));
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** Byte v2 decompressor for `count` extra bytes: one per chunk. */
|
|
26
|
+
export function ByteDecompressor(count) { initByte(this, count); }
|
|
27
|
+
|
|
24
28
|
ByteDecompressor.prototype.first = function (src, s, view, o) {
|
|
25
29
|
for (var i = 0; i < this.size; i++) { this.last[i] = src[s + i]; view.setUint8(o + i, src[s + i]); }
|
|
26
30
|
};
|
|
@@ -31,3 +35,15 @@ ByteDecompressor.prototype.next = function (dec, view, o) {
|
|
|
31
35
|
view.setUint8(o + i, this.last[i]);
|
|
32
36
|
}
|
|
33
37
|
};
|
|
38
|
+
|
|
39
|
+
/** Byte v2 compressor for `count` extra bytes: one per chunk, the mirror of ByteDecompressor. */
|
|
40
|
+
export function ByteCompressor(count) { initByte(this, count); }
|
|
41
|
+
|
|
42
|
+
ByteCompressor.prototype.first = function (src, s) { this.last.set(src.subarray(s, s + this.size)); };
|
|
43
|
+
|
|
44
|
+
ByteCompressor.prototype.next = function (enc, src, s) {
|
|
45
|
+
for (var i = 0; i < this.size; i++) {
|
|
46
|
+
enc.encodeSymbol(this.models[i], (src[s + i] - this.last[i]) & 255); // u8 wrapping_sub
|
|
47
|
+
this.last[i] = src[s + i];
|
|
48
|
+
}
|
|
49
|
+
};
|
package/src/laz/v2/gpstime11.js
CHANGED
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
// rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
|
|
13
13
|
// Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
|
|
14
14
|
// NOTICE). Changes: translated from Rust to JavaScript, i64 as two u32
|
|
15
|
-
// halves, Brent Buffham 2026
|
|
15
|
+
// halves, Brent Buffham 2026; the compressor (GpsTime11Compressor) added
|
|
16
|
+
// beside the decompressor, rounding its multiplier as LAZperf does; its
|
|
17
|
+
// multiplier and 64-bit difference shared with the Point14 compressor.
|
|
16
18
|
//==========================================================
|
|
17
19
|
|
|
18
20
|
import { ArithmeticModel } from "../models.js";
|
|
19
21
|
import { IntegerDecompressor } from "../IntegerDecompressor.js";
|
|
22
|
+
import { IntegerCompressor } from "../IntegerCompressor.js";
|
|
20
23
|
|
|
21
24
|
var LE = true;
|
|
22
25
|
var MULTI = 500, MULTI_MINUS = -10;
|
|
@@ -24,18 +27,39 @@ var MULTI_UNCHANGED = MULTI - MULTI_MINUS + 1; // 511
|
|
|
24
27
|
var MULTI_CODE_FULL = MULTI - MULTI_MINUS + 2; // 512
|
|
25
28
|
var MULTI_TOTAL = MULTI - MULTI_MINUS + 6; // 516
|
|
26
29
|
|
|
30
|
+
// The state and models both halves start a chunk with; IC is the integer coder
|
|
31
|
+
// (IntegerCompressor or IntegerDecompressor)
|
|
32
|
+
function initGps(self, IC) {
|
|
33
|
+
self.size = 8;
|
|
34
|
+
self.multiModel = new ArithmeticModel(MULTI_TOTAL);
|
|
35
|
+
self.zeroDiffModel = new ArithmeticModel(6);
|
|
36
|
+
self.ic = new IC(32, 9);
|
|
37
|
+
self.last = 0;
|
|
38
|
+
self.nextSeq = 0;
|
|
39
|
+
self.hi = new Int32Array(4);
|
|
40
|
+
self.lo = new Float64Array(4); // u32 values, held exactly
|
|
41
|
+
self.diffs = new Int32Array(4);
|
|
42
|
+
self.extremes = new Int32Array(4);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A full time (hi, lo) starts the next of the four sequences, with no difference yet
|
|
46
|
+
function startSequence(self, hi, lo) {
|
|
47
|
+
self.nextSeq = (self.nextSeq + 1) & 3;
|
|
48
|
+
self.hi[self.nextSeq] = hi;
|
|
49
|
+
self.lo[self.nextSeq] = lo;
|
|
50
|
+
self.last = self.nextSeq;
|
|
51
|
+
self.diffs[self.last] = 0;
|
|
52
|
+
self.extremes[self.last] = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// After three extreme differences in a row, the latest becomes the new reference
|
|
56
|
+
function extreme(self, d) {
|
|
57
|
+
if (++self.extremes[self.last] > 3) { self.diffs[self.last] = d; self.extremes[self.last] = 0; }
|
|
58
|
+
}
|
|
59
|
+
|
|
27
60
|
/** GpsTime11 v2 decompressor: one per chunk. */
|
|
28
61
|
export function GpsTime11Decompressor() {
|
|
29
|
-
this
|
|
30
|
-
this.multiModel = new ArithmeticModel(MULTI_TOTAL);
|
|
31
|
-
this.zeroDiffModel = new ArithmeticModel(6);
|
|
32
|
-
this.ic = new IntegerDecompressor(32, 9);
|
|
33
|
-
this.last = 0;
|
|
34
|
-
this.nextSeq = 0;
|
|
35
|
-
this.hi = new Int32Array(4);
|
|
36
|
-
this.lo = new Float64Array(4); // u32 values, held exactly
|
|
37
|
-
this.diffs = new Int32Array(4);
|
|
38
|
-
this.extremes = new Int32Array(4);
|
|
62
|
+
initGps(this, IntegerDecompressor);
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
GpsTime11Decompressor.prototype._add = function (i, d) {
|
|
@@ -46,17 +70,8 @@ GpsTime11Decompressor.prototype._add = function (i, d) {
|
|
|
46
70
|
};
|
|
47
71
|
|
|
48
72
|
GpsTime11Decompressor.prototype._full = function (dec) {
|
|
49
|
-
|
|
50
|
-
this
|
|
51
|
-
this.lo[this.nextSeq] = dec.readInt();
|
|
52
|
-
this.last = this.nextSeq;
|
|
53
|
-
this.diffs[this.last] = 0;
|
|
54
|
-
this.extremes[this.last] = 0;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// After three extreme differences in a row, the latest becomes the new reference
|
|
58
|
-
GpsTime11Decompressor.prototype._extreme = function (d) {
|
|
59
|
-
if (++this.extremes[this.last] > 3) { this.diffs[this.last] = d; this.extremes[this.last] = 0; }
|
|
73
|
+
var hi = this.ic.decompress(dec, this.hi[this.last], 8);
|
|
74
|
+
startSequence(this, hi, dec.readInt());
|
|
60
75
|
};
|
|
61
76
|
|
|
62
77
|
GpsTime11Decompressor.prototype.first = function (src, s, view, o) {
|
|
@@ -88,19 +103,19 @@ GpsTime11Decompressor.prototype.next = function (dec, view, o) {
|
|
|
88
103
|
} else if (multi < MULTI_UNCHANGED) {
|
|
89
104
|
if (multi === 0) {
|
|
90
105
|
d = this.ic.decompress(dec, 0, 7);
|
|
91
|
-
this
|
|
106
|
+
extreme(this, d);
|
|
92
107
|
} else if (multi < MULTI) {
|
|
93
108
|
d = this.ic.decompress(dec, Math.imul(multi, this.diffs[L]), multi < 10 ? 2 : 3);
|
|
94
109
|
} else if (multi === MULTI) {
|
|
95
110
|
d = this.ic.decompress(dec, Math.imul(MULTI, this.diffs[L]), 4);
|
|
96
|
-
this
|
|
111
|
+
extreme(this, d);
|
|
97
112
|
} else {
|
|
98
113
|
multi = MULTI - multi;
|
|
99
114
|
if (multi > MULTI_MINUS) {
|
|
100
115
|
d = this.ic.decompress(dec, Math.imul(multi, this.diffs[L]), 5);
|
|
101
116
|
} else {
|
|
102
117
|
d = this.ic.decompress(dec, Math.imul(MULTI_MINUS, this.diffs[L]), 6);
|
|
103
|
-
this
|
|
118
|
+
extreme(this, d);
|
|
104
119
|
}
|
|
105
120
|
}
|
|
106
121
|
this._add(L, d);
|
|
@@ -116,3 +131,128 @@ GpsTime11Decompressor.prototype.next = function (dec, view, o) {
|
|
|
116
131
|
view.setUint32(o, this.lo[this.last], LE);
|
|
117
132
|
view.setInt32(o + 4, this.hi[this.last], LE);
|
|
118
133
|
};
|
|
134
|
+
|
|
135
|
+
// The multiplier between this and the last difference, as LAZperf rounds it:
|
|
136
|
+
// std::round of the f32 quotient (half away from zero), then (int). LASzip and
|
|
137
|
+
// laz-rs add 0.5f in f32 instead, which differs only within an f32 step of a
|
|
138
|
+
// half; the fixtures' oracle is LAZperf. The one quotient past i32 (2^31, a last
|
|
139
|
+
// difference of +-1) converts as x86 does, to -2^31. Either choice decodes the same.
|
|
140
|
+
export function quantizeMulti(curr, last) {
|
|
141
|
+
var q = Math.fround(Math.fround(curr) / Math.fround(last));
|
|
142
|
+
var m = q >= 0 ? Math.floor(q + 0.5) : -Math.floor(0.5 - q);
|
|
143
|
+
return m > 2147483647 ? -2147483648 : m;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A DataView over the caller's records, made once per buffer
|
|
147
|
+
function viewOf(item, src) {
|
|
148
|
+
if (item._src !== src) { item._src = src; item._view = new DataView(src.buffer, src.byteOffset, src.byteLength); }
|
|
149
|
+
return item._view;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** GpsTime11 v2 compressor: one per chunk, the mirror of GpsTime11Decompressor. */
|
|
153
|
+
export function GpsTime11Compressor() {
|
|
154
|
+
initGps(this, IntegerCompressor);
|
|
155
|
+
this._src = null;
|
|
156
|
+
this._view = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
// (hi, lo) - (hi0, lo0) as an i32, or null when the 64-bit difference does not fit
|
|
161
|
+
// (hi signed, lo unsigned; shared with the Point14 compressor's GPS time)
|
|
162
|
+
export function diff32(hi, lo, hi0, lo0) {
|
|
163
|
+
var dl = lo - lo0, dh = hi - hi0;
|
|
164
|
+
if (dl < 0) { dl += 4294967296; dh -= 1; }
|
|
165
|
+
dh = dh | 0; // the high word of the i64 wrapping_sub
|
|
166
|
+
if (dh === 0 && dl < 2147483648) return dl;
|
|
167
|
+
if (dh === -1 && dl >= 2147483648) return dl - 4294967296;
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// (hi, lo) minus sequence i as an i32, or null when the 64-bit difference does not fit
|
|
172
|
+
GpsTime11Compressor.prototype._diff = function (i, hi, lo) { return diff32(hi, lo, this.hi[i], this.lo[i]); };
|
|
173
|
+
|
|
174
|
+
// The first other sequence (1-3 on from the last) this time is within i32 of, or 0
|
|
175
|
+
GpsTime11Compressor.prototype._other = function (hi, lo) {
|
|
176
|
+
for (var i = 1; i < 4; i++) if (this._diff((this.last + i) & 3, hi, lo) !== null) return i;
|
|
177
|
+
return 0;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
GpsTime11Compressor.prototype._full = function (enc, hi, lo) {
|
|
181
|
+
this.ic.compress(enc, this.hi[this.last], hi, 8);
|
|
182
|
+
enc.writeInt(lo);
|
|
183
|
+
startSequence(this, hi, lo);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
GpsTime11Compressor.prototype.first = function (src, s) {
|
|
187
|
+
var v = viewOf(this, src);
|
|
188
|
+
this.lo[0] = v.getUint32(s, LE);
|
|
189
|
+
this.hi[0] = v.getInt32(s + 4, LE);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
GpsTime11Compressor.prototype.next = function (enc, src, s) {
|
|
193
|
+
var v = viewOf(this, src);
|
|
194
|
+
var lo = v.getUint32(s, LE), hi = v.getInt32(s + 4, LE);
|
|
195
|
+
for (;;) {
|
|
196
|
+
var L = this.last, d, j, multi;
|
|
197
|
+
if (hi === this.hi[L] && lo === this.lo[L]) {
|
|
198
|
+
if (this.diffs[L] === 0) enc.encodeSymbol(this.zeroDiffModel, 0);
|
|
199
|
+
else enc.encodeSymbol(this.multiModel, MULTI_UNCHANGED);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
d = this._diff(L, hi, lo);
|
|
203
|
+
if (this.diffs[L] === 0) {
|
|
204
|
+
if (d !== null) {
|
|
205
|
+
enc.encodeSymbol(this.zeroDiffModel, 1);
|
|
206
|
+
this.ic.compress(enc, 0, d, 0);
|
|
207
|
+
this.diffs[L] = d;
|
|
208
|
+
this.extremes[L] = 0;
|
|
209
|
+
} else if ((j = this._other(hi, lo))) {
|
|
210
|
+
enc.encodeSymbol(this.zeroDiffModel, j + 2);
|
|
211
|
+
this.last = (L + j) & 3;
|
|
212
|
+
continue; // laz-rs recurses: code again against the new sequence
|
|
213
|
+
} else {
|
|
214
|
+
enc.encodeSymbol(this.zeroDiffModel, 2);
|
|
215
|
+
this._full(enc, hi, lo);
|
|
216
|
+
}
|
|
217
|
+
} else if (d !== null) {
|
|
218
|
+
multi = quantizeMulti(d, this.diffs[L]);
|
|
219
|
+
if (multi === 1) {
|
|
220
|
+
enc.encodeSymbol(this.multiModel, 1);
|
|
221
|
+
this.ic.compress(enc, this.diffs[L], d, 1);
|
|
222
|
+
this.extremes[L] = 0;
|
|
223
|
+
} else if (multi > 0) {
|
|
224
|
+
if (multi < MULTI) {
|
|
225
|
+
enc.encodeSymbol(this.multiModel, multi);
|
|
226
|
+
this.ic.compress(enc, Math.imul(multi, this.diffs[L]), d, multi < 10 ? 2 : 3);
|
|
227
|
+
} else {
|
|
228
|
+
enc.encodeSymbol(this.multiModel, MULTI);
|
|
229
|
+
this.ic.compress(enc, Math.imul(MULTI, this.diffs[L]), d, 4);
|
|
230
|
+
extreme(this, d);
|
|
231
|
+
}
|
|
232
|
+
} else if (multi < 0) {
|
|
233
|
+
if (multi > MULTI_MINUS) {
|
|
234
|
+
enc.encodeSymbol(this.multiModel, MULTI - multi);
|
|
235
|
+
this.ic.compress(enc, Math.imul(multi, this.diffs[L]), d, 5);
|
|
236
|
+
} else {
|
|
237
|
+
enc.encodeSymbol(this.multiModel, MULTI - MULTI_MINUS);
|
|
238
|
+
this.ic.compress(enc, Math.imul(MULTI_MINUS, this.diffs[L]), d, 6);
|
|
239
|
+
extreme(this, d);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
enc.encodeSymbol(this.multiModel, 0);
|
|
243
|
+
this.ic.compress(enc, 0, d, 7);
|
|
244
|
+
extreme(this, d);
|
|
245
|
+
}
|
|
246
|
+
} else if ((j = this._other(hi, lo))) {
|
|
247
|
+
enc.encodeSymbol(this.multiModel, MULTI_CODE_FULL + j);
|
|
248
|
+
this.last = (L + j) & 3;
|
|
249
|
+
continue;
|
|
250
|
+
} else {
|
|
251
|
+
enc.encodeSymbol(this.multiModel, MULTI_CODE_FULL);
|
|
252
|
+
this._full(enc, hi, lo);
|
|
253
|
+
}
|
|
254
|
+
this.hi[this.last] = hi;
|
|
255
|
+
this.lo[this.last] = lo;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
};
|
package/src/laz/v2/point10.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
// (formats 0-5) starts with. Each point codes which attributes changed
|
|
5
5
|
// since the last one; X and Y are corrections to a streaming median
|
|
6
6
|
// of the recent differences, Z to the last height at the same return
|
|
7
|
-
// level, all through integer
|
|
8
|
-
//
|
|
7
|
+
// level, all through integer coders whose contexts follow the return
|
|
8
|
+
// structure. The compressor and decompressor below make the same model
|
|
9
|
+
// choices from the same state, so they stay in step point for point.
|
|
9
10
|
//
|
|
10
11
|
// Ported from laz-rs src/las/point0.rs, module v2, and the median and
|
|
11
12
|
// return tables of src/las/utils.rs (https://github.com/laz-rs/laz-rs),
|
|
@@ -13,11 +14,13 @@
|
|
|
13
14
|
// rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
|
|
14
15
|
// Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
|
|
15
16
|
// NOTICE). Changes: translated from Rust to JavaScript, working on raw
|
|
16
|
-
// record bytes, Brent Buffham 2026
|
|
17
|
+
// record bytes, Brent Buffham 2026; the compressor (Point10Compressor)
|
|
18
|
+
// added beside the decompressor, sharing its models, median and tables.
|
|
17
19
|
//==========================================================
|
|
18
20
|
|
|
19
21
|
import { ArithmeticModel } from "../models.js";
|
|
20
22
|
import { IntegerDecompressor } from "../IntegerDecompressor.js";
|
|
23
|
+
import { IntegerCompressor } from "../IntegerCompressor.js";
|
|
21
24
|
|
|
22
25
|
var LE = true;
|
|
23
26
|
|
|
@@ -66,23 +69,34 @@ function models(n, symbols) { var out = []; for (var i = 0; i < n; i++) out.push
|
|
|
66
69
|
function lazyModel(table, i) { return table[i] || (table[i] = new ArithmeticModel(256)); }
|
|
67
70
|
function medians() { var out = []; for (var i = 0; i < 16; i++) out.push(new StreamingMedian()); return out; }
|
|
68
71
|
|
|
72
|
+
// The Y and Z coder contexts, from the bit lengths of the X (and Y) corrections just coded
|
|
73
|
+
// (Point14 uses them too)
|
|
74
|
+
export function yContext(single, icDx) { var k = icDx.k; return single + (k < 20 ? k & 0xFFFFFFFE : 20); }
|
|
75
|
+
export function zContext(single, icDx, icDy) { var k = Math.floor((icDx.k + icDy.k) / 2); return single + (k < 18 ? k & 0xFFFFFFFE : 18); }
|
|
76
|
+
|
|
77
|
+
// The state and models both halves start a chunk with; IC is the integer coder
|
|
78
|
+
// (IntegerCompressor or IntegerDecompressor), so each side gets the same widths and contexts
|
|
79
|
+
function initPoint10(self, IC) {
|
|
80
|
+
self.size = 20;
|
|
81
|
+
self.lastIntensity = new Uint16Array(16);
|
|
82
|
+
self.xMedian = medians();
|
|
83
|
+
self.yMedian = medians();
|
|
84
|
+
self.lastHeight = new Int32Array(8);
|
|
85
|
+
self.changedValues = new ArithmeticModel(64);
|
|
86
|
+
self.scanAngleRank = models(2, 256);
|
|
87
|
+
self.bitByte = new Array(256);
|
|
88
|
+
self.classification = new Array(256);
|
|
89
|
+
self.userData = new Array(256);
|
|
90
|
+
self.icIntensity = new IC(16, 4);
|
|
91
|
+
self.icPointSourceId = new IC(16, 1);
|
|
92
|
+
self.icDx = new IC(32, 2);
|
|
93
|
+
self.icDy = new IC(32, 22);
|
|
94
|
+
self.icZ = new IC(32, 20);
|
|
95
|
+
}
|
|
96
|
+
|
|
69
97
|
/** Point10 v2 decompressor: one per chunk. */
|
|
70
98
|
export function Point10Decompressor() {
|
|
71
|
-
this
|
|
72
|
-
this.lastIntensity = new Uint16Array(16);
|
|
73
|
-
this.xMedian = medians();
|
|
74
|
-
this.yMedian = medians();
|
|
75
|
-
this.lastHeight = new Int32Array(8);
|
|
76
|
-
this.changedValues = new ArithmeticModel(64);
|
|
77
|
-
this.scanAngleRank = models(2, 256);
|
|
78
|
-
this.bitByte = new Array(256);
|
|
79
|
-
this.classification = new Array(256);
|
|
80
|
-
this.userData = new Array(256);
|
|
81
|
-
this.icIntensity = new IntegerDecompressor(16, 4);
|
|
82
|
-
this.icPointSourceId = new IntegerDecompressor(16, 1);
|
|
83
|
-
this.icDx = new IntegerDecompressor(32, 2);
|
|
84
|
-
this.icDy = new IntegerDecompressor(32, 22);
|
|
85
|
-
this.icZ = new IntegerDecompressor(32, 20);
|
|
99
|
+
initPoint10(this, IntegerDecompressor);
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
/** The first point of a chunk is stored raw. */
|
|
@@ -125,12 +139,10 @@ Point10Decompressor.prototype.next = function (dec, view, o) {
|
|
|
125
139
|
var diff = this.icDx.decompress(dec, this.xMedian[m].get(), single);
|
|
126
140
|
this.x = (this.x + diff) | 0;
|
|
127
141
|
this.xMedian[m].add(diff);
|
|
128
|
-
|
|
129
|
-
diff = this.icDy.decompress(dec, this.yMedian[m].get(), single + (kx < 20 ? kx & 0xFFFFFFFE : 20));
|
|
142
|
+
diff = this.icDy.decompress(dec, this.yMedian[m].get(), yContext(single, this.icDx));
|
|
130
143
|
this.y = (this.y + diff) | 0;
|
|
131
144
|
this.yMedian[m].add(diff);
|
|
132
|
-
|
|
133
|
-
this.z = this.icZ.decompress(dec, this.lastHeight[l], single + (kz < 18 ? kz & 0xFFFFFFFE : 18));
|
|
145
|
+
this.z = this.icZ.decompress(dec, this.lastHeight[l], zContext(single, this.icDx, this.icDy));
|
|
134
146
|
this.lastHeight[l] = this.z;
|
|
135
147
|
view.setInt32(o, this.x, LE); view.setInt32(o + 4, this.y, LE); view.setInt32(o + 8, this.z, LE);
|
|
136
148
|
view.setUint16(o + 12, this.intensity, LE);
|
|
@@ -140,3 +152,60 @@ Point10Decompressor.prototype.next = function (dec, view, o) {
|
|
|
140
152
|
view.setUint8(o + 17, this.user);
|
|
141
153
|
view.setUint16(o + 18, this.psid, LE);
|
|
142
154
|
};
|
|
155
|
+
|
|
156
|
+
// A DataView over the caller's records, made once per buffer
|
|
157
|
+
function viewOf(item, src) {
|
|
158
|
+
if (item._src !== src) { item._src = src; item._view = new DataView(src.buffer, src.byteOffset, src.byteLength); }
|
|
159
|
+
return item._view;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Point10 v2 compressor: one per chunk, the mirror of Point10Decompressor. */
|
|
163
|
+
export function Point10Compressor() {
|
|
164
|
+
initPoint10(this, IntegerCompressor);
|
|
165
|
+
this._src = null;
|
|
166
|
+
this._view = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The first point of a chunk is stored raw; it only sets the state. */
|
|
170
|
+
Point10Compressor.prototype.first = function (src, s) {
|
|
171
|
+
var v = viewOf(this, src);
|
|
172
|
+
this.x = v.getInt32(s, LE); this.y = v.getInt32(s + 4, LE); this.z = v.getInt32(s + 8, LE);
|
|
173
|
+
this.bits = v.getUint8(s + 14);
|
|
174
|
+
this.cls = v.getUint8(s + 15);
|
|
175
|
+
this.angle = v.getInt8(s + 16);
|
|
176
|
+
this.user = v.getUint8(s + 17);
|
|
177
|
+
this.psid = v.getUint16(s + 18, LE);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
Point10Compressor.prototype.next = function (enc, src, s) {
|
|
181
|
+
var v = viewOf(this, src);
|
|
182
|
+
var x = v.getInt32(s, LE), y = v.getInt32(s + 4, LE), z = v.getInt32(s + 8, LE);
|
|
183
|
+
var intensity = v.getUint16(s + 12, LE), bits = v.getUint8(s + 14), cls = v.getUint8(s + 15);
|
|
184
|
+
var angle = v.getInt8(s + 16), user = v.getUint8(s + 17), psid = v.getUint16(s + 18, LE);
|
|
185
|
+
var r = bits & 7, n = (bits >>> 3) & 7;
|
|
186
|
+
var m = NUMBER_RETURN_MAP[n][r], l = NUMBER_RETURN_LEVEL[n][r];
|
|
187
|
+
// return number, number of returns, scan direction and edge fill the bit byte, so "any changed" is the byte changed
|
|
188
|
+
var changed = (bits !== this.bits ? 32 : 0) | (intensity !== this.lastIntensity[m] ? 16 : 0) | (cls !== this.cls ? 8 : 0) |
|
|
189
|
+
(angle !== this.angle ? 4 : 0) | (user !== this.user ? 2 : 0) | (psid !== this.psid ? 1 : 0);
|
|
190
|
+
enc.encodeSymbol(this.changedValues, changed);
|
|
191
|
+
if (changed & 32) enc.encodeSymbol(lazyModel(this.bitByte, this.bits), bits);
|
|
192
|
+
if (changed & 16) {
|
|
193
|
+
this.icIntensity.compress(enc, this.lastIntensity[m], intensity, m < 3 ? m : 3);
|
|
194
|
+
this.lastIntensity[m] = intensity;
|
|
195
|
+
}
|
|
196
|
+
if (changed & 8) enc.encodeSymbol(lazyModel(this.classification, this.cls), cls);
|
|
197
|
+
if (changed & 4) enc.encodeSymbol(this.scanAngleRank[(bits >>> 6) & 1], (angle - this.angle) & 255); // u8 wrapping_sub
|
|
198
|
+
if (changed & 2) enc.encodeSymbol(lazyModel(this.userData, this.user), user);
|
|
199
|
+
if (changed & 1) this.icPointSourceId.compress(enc, this.psid, psid, 0);
|
|
200
|
+
var single = n === 1 ? 1 : 0;
|
|
201
|
+
var diff = (x - this.x) | 0; // i32 wrapping_sub
|
|
202
|
+
this.icDx.compress(enc, this.xMedian[m].get(), diff, single);
|
|
203
|
+
this.xMedian[m].add(diff);
|
|
204
|
+
diff = (y - this.y) | 0;
|
|
205
|
+
this.icDy.compress(enc, this.yMedian[m].get(), diff, yContext(single, this.icDx));
|
|
206
|
+
this.yMedian[m].add(diff);
|
|
207
|
+
this.icZ.compress(enc, this.lastHeight[l], z, zContext(single, this.icDx, this.icDy));
|
|
208
|
+
this.lastHeight[l] = z;
|
|
209
|
+
this.x = x; this.y = y; this.z = z;
|
|
210
|
+
this.bits = bits; this.cls = cls; this.angle = angle; this.user = user; this.psid = psid;
|
|
211
|
+
};
|
package/src/laz/v2/rgb12.js
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
// LASzip RGB12 item, version 2: the 6 colour bytes. A symbol says which
|
|
4
4
|
// bytes changed and whether green and blue differ from red; red's byte
|
|
5
5
|
// differences predict green's, and the mean of red's and green's
|
|
6
|
-
// predicts blue's (clamped to a byte). Shared by the 1.4 RGB14 item
|
|
6
|
+
// predicts blue's (clamped to a byte). Shared by the 1.4 RGB14 item;
|
|
7
|
+
// encodeRgb is decodeRgb's mirror, over the same RgbModels.
|
|
7
8
|
//
|
|
8
9
|
// Ported from laz-rs src/las/rgb.rs, module v2 (https://github.com/laz-rs/laz-rs),
|
|
9
10
|
// itself a port of LASzip by rapidlasso. (c) 2007-2014 martin isenburg,
|
|
10
11
|
// rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
|
|
11
12
|
// Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
|
|
12
|
-
// NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026
|
|
13
|
+
// NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026;
|
|
14
|
+
// the compressor (encodeRgb, Rgb12Compressor) added beside the decompressor.
|
|
13
15
|
//==========================================================
|
|
14
16
|
|
|
15
17
|
import { ArithmeticModel } from "../models.js";
|
|
@@ -61,13 +63,16 @@ export function decodeRgb(dec, m, last) {
|
|
|
61
63
|
last[0] = r; last[1] = g; last[2] = b;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// The state and models both halves start a chunk with
|
|
67
|
+
function initRgb12(self) {
|
|
68
|
+
self.size = 6;
|
|
69
|
+
self.models = new RgbModels();
|
|
70
|
+
self.last = new Uint16Array(3);
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/** RGB12 v2 decompressor: one per chunk. */
|
|
74
|
+
export function Rgb12Decompressor() { initRgb12(this); }
|
|
75
|
+
|
|
71
76
|
Rgb12Decompressor.prototype.first = function (src, s, view, o) {
|
|
72
77
|
for (var i = 0; i < 6; i++) view.setUint8(o + i, src[s + i]);
|
|
73
78
|
this.last[0] = view.getUint16(o, LE); this.last[1] = view.getUint16(o + 2, LE); this.last[2] = view.getUint16(o + 4, LE);
|
|
@@ -77,3 +82,53 @@ Rgb12Decompressor.prototype.next = function (dec, view, o) {
|
|
|
77
82
|
decodeRgb(dec, this.models, this.last);
|
|
78
83
|
view.setUint16(o, this.last[0], LE); view.setUint16(o + 2, this.last[1], LE); view.setUint16(o + 4, this.last[2], LE);
|
|
79
84
|
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Encode one colour against the last one: the mirror of decodeRgb.
|
|
88
|
+
* @param {object} enc - ArithmeticEncoder
|
|
89
|
+
* @param {RgbModels} m
|
|
90
|
+
* @param {Uint16Array} last - [r, g, b] of the last point (not changed)
|
|
91
|
+
* @param {Uint16Array} cur - [r, g, b] of this point
|
|
92
|
+
* @returns {number} the symbol coded: which bytes changed, and bit 6 when it is not grey
|
|
93
|
+
*/
|
|
94
|
+
export function encodeRgb(enc, m, last, cur) {
|
|
95
|
+
var lr = last[0], lg = last[1], lb = last[2], r = cur[0], g = cur[1], b = cur[2];
|
|
96
|
+
var sym = ((lr ^ r) & 255 ? 1 : 0) | ((lr ^ r) & 0xFF00 ? 2 : 0) | ((lg ^ g) & 255 ? 4 : 0) | ((lg ^ g) & 0xFF00 ? 8 : 0) |
|
|
97
|
+
((lb ^ b) & 255 ? 16 : 0) | ((lb ^ b) & 0xFF00 ? 32 : 0) | (r !== g || r !== b ? 64 : 0);
|
|
98
|
+
var diffL = 0, diffH = 0;
|
|
99
|
+
enc.encodeSymbol(m.byteUsed, sym);
|
|
100
|
+
if (sym & 1) { diffL = (r & 255) - (lr & 255); enc.encodeSymbol(m.lowRed, diffL & 255); }
|
|
101
|
+
if (sym & 2) { diffH = (r >>> 8) - (lr >>> 8); enc.encodeSymbol(m.highRed, diffH & 255); }
|
|
102
|
+
if (sym & 64) {
|
|
103
|
+
if (sym & 4) enc.encodeSymbol(m.lowGreen, ((g & 255) - clamp8(diffL + (lg & 255))) & 255);
|
|
104
|
+
if (sym & 16) {
|
|
105
|
+
diffL = Math.trunc((diffL + (g & 255) - (lg & 255)) / 2);
|
|
106
|
+
enc.encodeSymbol(m.lowBlue, ((b & 255) - clamp8(diffL + (lb & 255))) & 255);
|
|
107
|
+
}
|
|
108
|
+
if (sym & 8) enc.encodeSymbol(m.highGreen, ((g >>> 8) - clamp8(diffH + (lg >>> 8))) & 255);
|
|
109
|
+
if (sym & 32) {
|
|
110
|
+
diffH = Math.trunc((diffH + (g >>> 8) - (lg >>> 8)) / 2);
|
|
111
|
+
enc.encodeSymbol(m.highBlue, ((b >>> 8) - clamp8(diffH + (lb >>> 8))) & 255);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return sym;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** RGB12 v2 compressor: one per chunk, the mirror of Rgb12Decompressor. */
|
|
118
|
+
export function Rgb12Compressor() {
|
|
119
|
+
initRgb12(this);
|
|
120
|
+
this.cur = new Uint16Array(3);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** [r, g, b] of the record bytes at s, into out (RGB14 reads them the same way). */
|
|
124
|
+
export function readRgb(out, src, s) {
|
|
125
|
+
out[0] = src[s] + src[s + 1] * 256; out[1] = src[s + 2] + src[s + 3] * 256; out[2] = src[s + 4] + src[s + 5] * 256;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Rgb12Compressor.prototype.first = function (src, s) { readRgb(this.last, src, s); };
|
|
129
|
+
|
|
130
|
+
Rgb12Compressor.prototype.next = function (enc, src, s) {
|
|
131
|
+
readRgb(this.cur, src, s);
|
|
132
|
+
encodeRgb(enc, this.models, this.last, this.cur);
|
|
133
|
+
var t = this.last; this.last = this.cur; this.cur = t;
|
|
134
|
+
};
|