las-laz-js 0.1.0 → 0.2.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.
@@ -0,0 +1,79 @@
1
+ // src/laz/v2/rgb12.js
2
+ //==========================================================
3
+ // LASzip RGB12 item, version 2: the 6 colour bytes. A symbol says which
4
+ // bytes changed and whether green and blue differ from red; red's byte
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.
7
+ //
8
+ // Ported from laz-rs src/las/rgb.rs, module v2 (https://github.com/laz-rs/laz-rs),
9
+ // itself a port of LASzip by rapidlasso. (c) 2007-2014 martin isenburg,
10
+ // rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
11
+ // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
12
+ // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026.
13
+ //==========================================================
14
+
15
+ import { ArithmeticModel } from "../models.js";
16
+
17
+ var LE = true;
18
+
19
+ function clamp8(v) { return v < 0 ? 0 : v > 255 ? 255 : v; }
20
+
21
+ /** The seven models of one RGB context. */
22
+ export function RgbModels() {
23
+ this.byteUsed = new ArithmeticModel(128);
24
+ this.lowRed = new ArithmeticModel(256); this.highRed = new ArithmeticModel(256);
25
+ this.lowGreen = new ArithmeticModel(256); this.highGreen = new ArithmeticModel(256);
26
+ this.lowBlue = new ArithmeticModel(256); this.highBlue = new ArithmeticModel(256);
27
+ }
28
+
29
+ /**
30
+ * Decode one colour from the last one.
31
+ * @param {object} dec - ArithmeticDecoder
32
+ * @param {RgbModels} m
33
+ * @param {Uint16Array} last - [r, g, b]; overwritten with the new colour
34
+ */
35
+ export function decodeRgb(dec, m, last) {
36
+ var sym = dec.decodeSymbol(m.byteUsed);
37
+ var lr = last[0], lg = last[1], lb = last[2];
38
+ var r, g, b, diff;
39
+ r = sym & 1 ? (dec.decodeSymbol(m.lowRed) + (lr & 255)) & 255 : lr & 255;
40
+ r += sym & 2 ? ((dec.decodeSymbol(m.highRed) + (lr >>> 8)) & 255) * 256 : lr & 0xFF00;
41
+ if (sym & 64) {
42
+ diff = (r & 255) - (lr & 255);
43
+ g = sym & 4 ? (dec.decodeSymbol(m.lowGreen) + clamp8(diff + (lg & 255))) & 255 : lg & 255;
44
+ if (sym & 16) {
45
+ diff = Math.trunc((diff + (g & 255) - (lg & 255)) / 2);
46
+ b = (dec.decodeSymbol(m.lowBlue) + clamp8(diff + (lb & 255))) & 255;
47
+ } else {
48
+ b = lb & 255;
49
+ }
50
+ diff = (r >>> 8) - (lr >>> 8);
51
+ g += sym & 8 ? ((dec.decodeSymbol(m.highGreen) + clamp8(diff + (lg >>> 8))) & 255) * 256 : lg & 0xFF00;
52
+ if (sym & 32) {
53
+ diff = Math.trunc((diff + (g >>> 8) - (lg >>> 8)) / 2);
54
+ b += ((dec.decodeSymbol(m.highBlue) + clamp8(diff + (lb >>> 8))) & 255) * 256;
55
+ } else {
56
+ b += lb & 0xFF00;
57
+ }
58
+ } else {
59
+ g = r; b = r;
60
+ }
61
+ last[0] = r; last[1] = g; last[2] = b;
62
+ }
63
+
64
+ /** RGB12 v2 decompressor: one per chunk. */
65
+ export function Rgb12Decompressor() {
66
+ this.size = 6;
67
+ this.models = new RgbModels();
68
+ this.last = new Uint16Array(3);
69
+ }
70
+
71
+ Rgb12Decompressor.prototype.first = function (src, s, view, o) {
72
+ for (var i = 0; i < 6; i++) view.setUint8(o + i, src[s + i]);
73
+ this.last[0] = view.getUint16(o, LE); this.last[1] = view.getUint16(o + 2, LE); this.last[2] = view.getUint16(o + 4, LE);
74
+ };
75
+
76
+ Rgb12Decompressor.prototype.next = function (dec, view, o) {
77
+ decodeRgb(dec, this.models, this.last);
78
+ view.setUint16(o, this.last[0], LE); view.setUint16(o + 2, this.last[1], LE); view.setUint16(o + 4, this.last[2], LE);
79
+ };
@@ -0,0 +1,145 @@
1
+ // src/laz/v3/layered.js
2
+ //==========================================================
3
+ // The LAS 1.4 layered items that follow Point14: RGB14, NIR14 (the
4
+ // second half of RGBNIR14) and Byte14 (extra bytes, one layer per
5
+ // byte). Each keeps up to four contexts keyed by the scanner channel
6
+ // Point14 passes on, with the version-3 quirk preserved: a context
7
+ // first used is seeded from the last one, and in version 3 later
8
+ // values are predicted from whichever context it was entered from.
9
+ //
10
+ // Ported from laz-rs src/las/rgb.rs, nir.rs and extra_bytes.rs, modules
11
+ // v3_v4 (https://github.com/laz-rs/laz-rs), itself a port of LASzip by
12
+ // rapidlasso. (c) 2007-2015 martin isenburg, rapidlasso; (c) 2019-2023
13
+ // Thomas Montaigu.
14
+ // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
15
+ // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026.
16
+ //==========================================================
17
+
18
+ import { ArithmeticDecoder } from "../ArithmeticDecoder.js";
19
+ import { ArithmeticModel } from "../models.js";
20
+ import { RgbModels, decodeRgb } from "../v2/rgb12.js";
21
+
22
+ var LE = true;
23
+
24
+ // laz-rs's context switch, shared by the three items: returns the index whose
25
+ // last value predicts this point (`last_idx`), after switching to `ctx`.
26
+ function enterContext(item, ctx, seed) {
27
+ var lastIdx = item.lastContext;
28
+ if (item.lastContext !== ctx) {
29
+ item.lastContext = ctx;
30
+ if (item.unused[ctx]) {
31
+ seed(lastIdx, ctx);
32
+ item.unused[ctx] = false;
33
+ if (item.version === 3) lastIdx = ctx;
34
+ }
35
+ if (item.version === 4) lastIdx = ctx;
36
+ }
37
+ return lastIdx;
38
+ }
39
+
40
+ function layer(item, bytes, q, n, fail, e, base) {
41
+ if (q + n > e) fail(q);
42
+ if (n === 0) return null;
43
+ var d = new ArithmeticDecoder(bytes, q, q + n, base);
44
+ d.readInitBytes();
45
+ return d;
46
+ }
47
+
48
+ /** RGB14 (version 3 or 4). */
49
+ export function Rgb14Decompressor(version) {
50
+ this.size = 6;
51
+ this.version = version;
52
+ this.models = [new RgbModels(), new RgbModels(), new RgbModels(), new RgbModels()];
53
+ this.last = [new Uint16Array(3), new Uint16Array(3), new Uint16Array(3), new Uint16Array(3)];
54
+ this.unused = [true, true, true, true];
55
+ this.lastContext = 0;
56
+ this.dec = null;
57
+ }
58
+ Rgb14Decompressor.prototype.first = function (src, s, view, o, ctx) {
59
+ for (var i = 0; i < 6; i++) view.setUint8(o + i, src[s + i]);
60
+ var c = ctx.v;
61
+ this.last[c][0] = view.getUint16(o, LE); this.last[c][1] = view.getUint16(o + 2, LE); this.last[c][2] = view.getUint16(o + 4, LE);
62
+ this.unused = [true, true, true, true];
63
+ this.unused[c] = false;
64
+ this.lastContext = c;
65
+ };
66
+ Rgb14Decompressor.prototype.readLayerSizes = function (dv, q) { this.layerSize = dv.getUint32(q, LE); return q + 4; };
67
+ Rgb14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) { this.dec = layer(this, bytes, q, this.layerSize, fail, e, base); return q + this.layerSize; };
68
+ Rgb14Decompressor.prototype.next = function (view, o, ctx) {
69
+ var self = this;
70
+ var idx = enterContext(this, ctx.v, function (from, to) { self.last[to].set(self.last[from]); });
71
+ var rgb = this.last[idx];
72
+ if (this.dec) decodeRgb(this.dec, this.models[this.lastContext], rgb);
73
+ view.setUint16(o, rgb[0], LE); view.setUint16(o + 2, rgb[1], LE); view.setUint16(o + 4, rgb[2], LE);
74
+ };
75
+
76
+ /** NIR14 (version 3 or 4): the near-infrared word of RGBNIR14. */
77
+ export function Nir14Decompressor(version) {
78
+ this.size = 2;
79
+ this.version = version;
80
+ this.used = []; this.low = []; this.high = [];
81
+ for (var i = 0; i < 4; i++) { this.used.push(new ArithmeticModel(4)); this.low.push(new ArithmeticModel(256)); this.high.push(new ArithmeticModel(256)); }
82
+ this.last = new Uint16Array(4);
83
+ this.unused = [true, true, true, true];
84
+ this.lastContext = 0;
85
+ this.dec = null;
86
+ }
87
+ Nir14Decompressor.prototype.first = function (src, s, view, o, ctx) {
88
+ view.setUint8(o, src[s]); view.setUint8(o + 1, src[s + 1]);
89
+ this.last[ctx.v] = view.getUint16(o, LE);
90
+ this.unused = [true, true, true, true];
91
+ this.unused[ctx.v] = false;
92
+ this.lastContext = ctx.v;
93
+ };
94
+ Nir14Decompressor.prototype.readLayerSizes = Rgb14Decompressor.prototype.readLayerSizes;
95
+ Nir14Decompressor.prototype.readLayers = Rgb14Decompressor.prototype.readLayers;
96
+ Nir14Decompressor.prototype.next = function (view, o, ctx) {
97
+ var self = this;
98
+ var idx = enterContext(this, ctx.v, function (from, to) { self.last[to] = self.last[from]; });
99
+ if (this.dec) {
100
+ var c = this.lastContext, last = this.last[idx];
101
+ var sym = this.dec.decodeSymbol(this.used[c]);
102
+ var v = sym & 1 ? (this.dec.decodeSymbol(this.low[c]) + (last & 255)) & 255 : last & 255;
103
+ v += sym & 2 ? ((this.dec.decodeSymbol(this.high[c]) + (last >>> 8)) & 255) * 256 : last & 0xFF00;
104
+ this.last[idx] = v;
105
+ }
106
+ view.setUint16(o, this.last[idx], LE);
107
+ };
108
+
109
+ /** Byte14 (version 3 or 4): `count` extra bytes, one layer each. */
110
+ export function Byte14Decompressor(count, version) {
111
+ this.size = count;
112
+ this.version = version;
113
+ this.models = [null, null, null, null];
114
+ this.last = [new Uint8Array(count), new Uint8Array(count), new Uint8Array(count), new Uint8Array(count)];
115
+ this.unused = [true, true, true, true];
116
+ this.lastContext = 0;
117
+ this.sizes = new Array(count);
118
+ this.dec = new Array(count);
119
+ }
120
+ function byteModels(count) { var m = []; for (var i = 0; i < count; i++) m.push(new ArithmeticModel(256)); return m; }
121
+ Byte14Decompressor.prototype.first = function (src, s, view, o, ctx) {
122
+ for (var i = 0; i < this.size; i++) view.setUint8(o + i, src[s + i]);
123
+ this.last[ctx.v].set(src.subarray(s, s + this.size));
124
+ this.unused = [true, true, true, true];
125
+ this.unused[ctx.v] = false;
126
+ this.models[ctx.v] = byteModels(this.size);
127
+ this.lastContext = ctx.v;
128
+ };
129
+ Byte14Decompressor.prototype.readLayerSizes = function (dv, q) {
130
+ for (var i = 0; i < this.size; i++) { this.sizes[i] = dv.getUint32(q, LE); q += 4; }
131
+ return q;
132
+ };
133
+ Byte14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) {
134
+ for (var i = 0; i < this.size; i++) { this.dec[i] = layer(this, bytes, q, this.sizes[i], fail, e, base); q += this.sizes[i]; }
135
+ return q;
136
+ };
137
+ Byte14Decompressor.prototype.next = function (view, o, ctx) {
138
+ var self = this;
139
+ var idx = enterContext(this, ctx.v, function (from, to) { self.models[to] = byteModels(self.size); self.last[to].set(self.last[from]); });
140
+ var last = this.last[idx], models = this.models[ctx.v];
141
+ for (var i = 0; i < this.size; i++) {
142
+ if (this.dec[i]) last[i] = (last[i] + this.dec[i].decodeSymbol(models[i])) & 255;
143
+ view.setUint8(o + i, last[i]);
144
+ }
145
+ };
@@ -0,0 +1,266 @@
1
+ // src/laz/v3/point14.js
2
+ //==========================================================
3
+ // LASzip Point14 item, versions 3 and 4 (LAS 1.4 point formats 6-10):
4
+ // the 30-byte extended record, coded in nine LAYERS — channel/returns/
5
+ // XY, Z, classification, flags, intensity, scan angle, user data,
6
+ // point source, GPS time — each its own arithmetic stream, so a layer
7
+ // that never changes in a chunk costs nothing (size 0: the value stays
8
+ // the last one). Up to four contexts follow the scanner channel.
9
+ //
10
+ // Ported from laz-rs src/las/point6.rs, module v3_v4, and the 16x16
11
+ // return tables of src/las/utils.rs (https://github.com/laz-rs/laz-rs),
12
+ // itself a port of LASzip by rapidlasso. (c) 2007-2015 martin isenburg,
13
+ // rapidlasso; (c) 2019-2023 Thomas Montaigu.
14
+ // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
15
+ // NOTICE). Changes: translated from Rust to JavaScript, working on raw
16
+ // record bytes, GPS time as two u32 halves, Brent Buffham 2026.
17
+ //==========================================================
18
+
19
+ import { ArithmeticDecoder } from "../ArithmeticDecoder.js";
20
+ import { ArithmeticModel } from "../models.js";
21
+ import { IntegerDecompressor } from "../IntegerDecompressor.js";
22
+ import { StreamingMedian } from "../v2/point10.js";
23
+
24
+ var LE = true;
25
+ var MULTI = 500, MULTI_MINUS = -10;
26
+ var CODE_FULL = MULTI - MULTI_MINUS + 1; // 511
27
+ var MULTI_TOTAL = MULTI - MULTI_MINUS + 5; // 515
28
+
29
+ /** NUMBER_RETURN_MAP_6CTX[n][r] (LASzip). */
30
+ var MAP_6CTX = [
31
+ [0, 1, 2, 3, 4, 5, 3, 4, 4, 5, 5, 5, 5, 5, 5, 5], [1, 0, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
32
+ [2, 1, 2, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3], [3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
33
+ [4, 3, 4, 4, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], [5, 3, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
34
+ [3, 3, 4, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4], [4, 3, 4, 4, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4, 4, 4],
35
+ [4, 3, 4, 4, 4, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4, 4], [5, 3, 4, 4, 4, 4, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4],
36
+ [5, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 4, 4, 4, 4, 4], [5, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 4, 4, 4],
37
+ [5, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 4, 4], [5, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 4],
38
+ [5, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5], [5, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5]
39
+ ];
40
+ /** NUMBER_RETURN_LEVEL_8CT[n][r] (LASzip): |n - r|, capped at 7. */
41
+ var LEVEL_8CT = [];
42
+ for (var ln = 0; ln < 16; ln++) { LEVEL_8CT.push([]); for (var lr = 0; lr < 16; lr++) LEVEL_8CT[ln].push(Math.min(7, Math.abs(ln - lr))); }
43
+
44
+ function medians(n) { var out = []; for (var i = 0; i < n; i++) out.push(new StreamingMedian()); return out; }
45
+
46
+ // One context's state, started from the last point of another (laz-rs from_last_point)
47
+ function Context(p) {
48
+ this.unused = false;
49
+ this.p = { x: p.x, y: p.y, z: p.z, intensity: p.intensity, bits: p.bits, flags: p.flags, cls: p.cls, angle: p.angle, user: p.user, psid: p.psid, gpsHi: p.gpsHi, gpsLo: p.gpsLo, gpsChange: false };
50
+ this.lastIntensity = new Uint16Array(8).fill(p.intensity);
51
+ this.xMedian = medians(12);
52
+ this.yMedian = medians(12);
53
+ this.lastZ = new Int32Array(8).fill(p.z);
54
+ this.changed = []; for (var i = 0; i < 8; i++) this.changed.push(new ArithmeticModel(128));
55
+ this.channel = new ArithmeticModel(3);
56
+ this.nret = new Array(16);
57
+ this.rnum = new Array(16);
58
+ this.rnumGpsSame = new ArithmeticModel(13);
59
+ this.clsM = new Array(64);
60
+ this.flagsM = new Array(64);
61
+ this.userM = new Array(64);
62
+ this.gpsMulti = new ArithmeticModel(MULTI_TOTAL);
63
+ this.gpsNoDiff = new ArithmeticModel(5);
64
+ this.icDx = new IntegerDecompressor(32, 2);
65
+ this.icDy = new IntegerDecompressor(32, 22);
66
+ this.icZ = new IntegerDecompressor(32, 20);
67
+ this.icIntensity = new IntegerDecompressor(16, 4);
68
+ this.icAngle = new IntegerDecompressor(16, 2);
69
+ this.icPsid = new IntegerDecompressor(16, 1);
70
+ this.icGps = new IntegerDecompressor(32, 9);
71
+ this.gLast = 0; this.gNext = 0;
72
+ this.gHi = new Int32Array(4); this.gLo = new Float64Array(4);
73
+ this.gHi[0] = p.gpsHi; this.gLo[0] = p.gpsLo;
74
+ this.gDiff = new Int32Array(4);
75
+ this.gExtreme = new Int32Array(4);
76
+ }
77
+
78
+ function lazy(arr, i, symbols) { return arr[i] || (arr[i] = new ArithmeticModel(symbols)); }
79
+
80
+ Context.prototype.addGps = function (i, d) {
81
+ var lo = this.gLo[i] + d;
82
+ if (lo >= 4294967296) { lo -= 4294967296; this.gHi[i] = (this.gHi[i] + 1) | 0; }
83
+ else if (lo < 0) { lo += 4294967296; this.gHi[i] = (this.gHi[i] - 1) | 0; }
84
+ this.gLo[i] = lo;
85
+ };
86
+ Context.prototype.fullGps = function (dec) {
87
+ this.gNext = (this.gNext + 1) & 3;
88
+ this.gHi[this.gNext] = this.icGps.decompress(dec, this.gHi[this.gLast], 8);
89
+ this.gLo[this.gNext] = dec.readInt();
90
+ this.gLast = this.gNext;
91
+ this.gDiff[this.gLast] = 0;
92
+ this.gExtreme[this.gLast] = 0;
93
+ };
94
+ Context.prototype.extremeGps = function (d) {
95
+ if (++this.gExtreme[this.gLast] > 3) { this.gDiff[this.gLast] = d; this.gExtreme[this.gLast] = 0; }
96
+ };
97
+ // laz-rs read_gps_time (the 1.4 variant: no "unchanged" code, full = 511)
98
+ Context.prototype.readGps = function (dec) {
99
+ for (;;) {
100
+ var L = this.gLast, multi, d;
101
+ if (this.gDiff[L] === 0) {
102
+ multi = dec.decodeSymbol(this.gpsNoDiff);
103
+ if (multi === 0) {
104
+ this.gDiff[L] = this.icGps.decompress(dec, 0, 0);
105
+ this.addGps(L, this.gDiff[L]);
106
+ this.gExtreme[L] = 0;
107
+ } else if (multi === 1) {
108
+ this.fullGps(dec);
109
+ } else {
110
+ this.gLast = (L + multi - 1) & 3;
111
+ continue;
112
+ }
113
+ } else {
114
+ multi = dec.decodeSymbol(this.gpsMulti);
115
+ if (multi === 1) {
116
+ this.addGps(L, this.icGps.decompress(dec, this.gDiff[L], 1));
117
+ this.gExtreme[L] = 0;
118
+ } else if (multi < CODE_FULL) {
119
+ if (multi === 0) {
120
+ d = this.icGps.decompress(dec, 0, 7);
121
+ this.extremeGps(d);
122
+ } else if (multi < MULTI) {
123
+ d = this.icGps.decompress(dec, Math.imul(multi, this.gDiff[L]), multi < 10 ? 2 : 3);
124
+ } else if (multi === MULTI) {
125
+ d = this.icGps.decompress(dec, Math.imul(MULTI, this.gDiff[L]), 4);
126
+ this.extremeGps(d);
127
+ } else {
128
+ multi = MULTI - multi;
129
+ if (multi > MULTI_MINUS) {
130
+ d = this.icGps.decompress(dec, Math.imul(multi, this.gDiff[L]), 5);
131
+ } else {
132
+ d = this.icGps.decompress(dec, Math.imul(MULTI_MINUS, this.gDiff[L]), 6);
133
+ this.extremeGps(d);
134
+ }
135
+ }
136
+ this.addGps(L, d);
137
+ } else if (multi === CODE_FULL) {
138
+ this.fullGps(dec);
139
+ } else {
140
+ this.gLast = (L + multi - CODE_FULL) & 3;
141
+ continue;
142
+ }
143
+ }
144
+ return;
145
+ }
146
+ };
147
+
148
+ var LAYERS = 9; // channel/returns/xy, z, classification, flags, intensity, scan angle, user data, point source, gps time
149
+
150
+ /**
151
+ * Point14 decompressor: one per chunk.
152
+ * @param {number} version - LASzip item version, 3 or 4 (they pass the scanner channel on differently)
153
+ */
154
+ export function Point14Decompressor(version) {
155
+ this.size = 30;
156
+ this.version = version;
157
+ this.contexts = [null, null, null, null];
158
+ this.current = 0;
159
+ this.sizes = new Array(LAYERS);
160
+ this.dec = new Array(LAYERS);
161
+ this.on = new Array(LAYERS);
162
+ }
163
+
164
+ Point14Decompressor.prototype.first = function (src, s, view, o, ctx) {
165
+ for (var i = 0; i < 30; i++) view.setUint8(o + i, src[s + i]);
166
+ var p = {
167
+ x: view.getInt32(o, LE), y: view.getInt32(o + 4, LE), z: view.getInt32(o + 8, LE),
168
+ intensity: view.getUint16(o + 12, LE), bits: view.getUint8(o + 14), flags: view.getUint8(o + 15),
169
+ cls: view.getUint8(o + 16), user: view.getUint8(o + 17), angle: view.getInt16(o + 18, LE),
170
+ psid: view.getUint16(o + 20, LE), gpsLo: view.getUint32(o + 22, LE), gpsHi: view.getInt32(o + 26, LE)
171
+ };
172
+ this.current = (p.flags >>> 4) & 3;
173
+ this.contexts = [null, null, null, null];
174
+ this.contexts[this.current] = new Context(p);
175
+ ctx.v = this.current;
176
+ };
177
+
178
+ Point14Decompressor.prototype.readLayerSizes = function (dv, q) {
179
+ for (var i = 0; i < LAYERS; i++) { this.sizes[i] = dv.getUint32(q, LE); q += 4; }
180
+ return q;
181
+ };
182
+
183
+ Point14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) {
184
+ if (this.sizes[0] === 0) fail(q); // channel / returns / XY is coded for every point after the first
185
+ for (var i = 0; i < LAYERS; i++) {
186
+ var n = this.sizes[i];
187
+ if (q + n > e) fail(q);
188
+ this.on[i] = n > 0;
189
+ if (n > 0) { this.dec[i] = new ArithmeticDecoder(bytes, q, q + n, base); this.dec[i].readInitBytes(); }
190
+ q += n;
191
+ }
192
+ return q;
193
+ };
194
+
195
+ Point14Decompressor.prototype.next = function (view, o, ctx) {
196
+ var c = this.contexts[this.current];
197
+ var p = c.p, d0 = this.dec[0];
198
+ var r = p.bits & 15, n = p.bits >>> 4;
199
+ var lpr = (r === 1 ? 1 : 0) + (r >= n ? 2 : 0) + (p.gpsChange ? 4 : 0);
200
+ var changed = d0.decodeSymbol(c.changed[lpr]);
201
+ if (changed & 64) {
202
+ var channel = (this.current + d0.decodeSymbol(c.channel) + 1) % 4;
203
+ if (!this.contexts[channel]) this.contexts[channel] = new Context(p);
204
+ this.current = channel;
205
+ if (this.version === 3) ctx.v = channel;
206
+ c = this.contexts[channel];
207
+ p = c.p;
208
+ p.flags = (p.flags & 0xCF) | (channel << 4);
209
+ }
210
+ if (this.version !== 3) ctx.v = this.current;
211
+ var psidChanged = changed & 32, gpsChanged = changed & 16 ? 1 : 0, angleChanged = changed & 8;
212
+ var lastN = p.bits >>> 4, lastR = p.bits & 15;
213
+ n = changed & 4 ? d0.decodeSymbol(lazy(c.nret, lastN, 16)) : lastN;
214
+ var low = changed & 3;
215
+ if (low === 0) r = lastR;
216
+ else if (low === 1) r = (lastR + 1) % 16;
217
+ else if (low === 2) r = (lastR + 15) % 16;
218
+ else if (gpsChanged) r = d0.decodeSymbol(lazy(c.rnum, lastR, 16));
219
+ else r = (lastR + d0.decodeSymbol(c.rnumGpsSame) + 2) % 16;
220
+ p.bits = r + n * 16;
221
+ var m = MAP_6CTX[n][r], l = LEVEL_8CT[n][r];
222
+ var cpr = (r === 1 ? 2 : 0) + (r >= n ? 1 : 0);
223
+ var single = n === 1 ? 1 : 0;
224
+ var idx = m * 2 + gpsChanged;
225
+ var diff = c.icDx.decompress(d0, c.xMedian[idx].get(), single);
226
+ p.x = (p.x + diff) | 0;
227
+ c.xMedian[idx].add(diff);
228
+ var kx = c.icDx.k;
229
+ diff = c.icDy.decompress(d0, c.yMedian[idx].get(), single + (kx < 20 ? kx & 0xFFFFFFFE : 20));
230
+ p.y = (p.y + diff) | 0;
231
+ c.yMedian[idx].add(diff);
232
+ if (this.on[1]) {
233
+ var kz = Math.floor((c.icDx.k + c.icDy.k) / 2);
234
+ p.z = c.icZ.decompress(this.dec[1], c.lastZ[l], single + (kz < 18 ? kz & 0xFFFFFFFE : 18));
235
+ c.lastZ[l] = p.z;
236
+ }
237
+ if (this.on[2]) p.cls = this.dec[2].decodeSymbol(lazy(c.clsM, (p.cls & 31) * 2 + (cpr === 3 ? 1 : 0), 256));
238
+ if (this.on[3]) {
239
+ var lastFlags = ((p.flags >>> 7) & 1) * 32 + ((p.flags >>> 6) & 1) * 16 + (p.flags & 15);
240
+ var f = this.dec[3].decodeSymbol(lazy(c.flagsM, lastFlags, 64));
241
+ p.flags = ((f >>> 5) & 1) * 128 + ((f >>> 4) & 1) * 64 + (p.flags & 0x30) + (f & 15);
242
+ }
243
+ if (this.on[4]) {
244
+ var ii = cpr * 2 + gpsChanged;
245
+ p.intensity = c.icIntensity.decompress(this.dec[4], c.lastIntensity[ii], cpr) & 0xFFFF;
246
+ c.lastIntensity[ii] = p.intensity;
247
+ }
248
+ if (this.on[5] && angleChanged) p.angle = (c.icAngle.decompress(this.dec[5], p.angle, gpsChanged) << 16) >> 16; // as i16
249
+ if (this.on[6]) p.user = this.dec[6].decodeSymbol(lazy(c.userM, Math.floor(p.user / 4), 256));
250
+ if (this.on[7] && psidChanged) p.psid = c.icPsid.decompress(this.dec[7], p.psid, 0) & 0xFFFF;
251
+ p.gpsChange = !!gpsChanged;
252
+ if (this.on[8] && gpsChanged) {
253
+ c.readGps(this.dec[8]);
254
+ p.gpsHi = c.gHi[c.gLast]; p.gpsLo = c.gLo[c.gLast];
255
+ }
256
+ view.setInt32(o, p.x, LE); view.setInt32(o + 4, p.y, LE); view.setInt32(o + 8, p.z, LE);
257
+ view.setUint16(o + 12, p.intensity, LE);
258
+ view.setUint8(o + 14, p.bits);
259
+ view.setUint8(o + 15, p.flags);
260
+ view.setUint8(o + 16, p.cls);
261
+ view.setUint8(o + 17, p.user);
262
+ view.setInt16(o + 18, p.angle, LE);
263
+ view.setUint16(o + 20, p.psid, LE);
264
+ view.setUint32(o + 22, p.gpsLo, LE);
265
+ view.setInt32(o + 26, p.gpsHi, LE);
266
+ };
@@ -11,9 +11,12 @@
11
11
  // data, never on positions (marked // bits-ok for the guard test).
12
12
  //
13
13
  // opts.keep(view, recordOffset) rejects a record before it is
14
- // decoded (filters, see filter/filters.js). opts.stride keeps every
15
- // stride-th record counted from the file's first record
16
- // (opts.firstIndex = the window's first record number).
14
+ // decoded (filters, see filter/filters.js). opts.thin
15
+ // { total, target, acc } then keeps exactly target of the total
16
+ // records that pass, spread evenly: an integer accumulator gains
17
+ // target per passing record and keeps the record each time it
18
+ // reaches total (Bresenham), so record ceil(j * total / target) - 1
19
+ // is the j-th kept. acc is carried from window to window.
17
20
  //==========================================================
18
21
 
19
22
  import { LasError, ERROR_CODES } from "../errors.js";
@@ -69,7 +72,9 @@ export function resolveFields(header, fields) {
69
72
  * Decode a window of whole point records.
70
73
  * @param {Uint8Array} bytes - whole records, the first at bytes[0].
71
74
  * @param {object} header - from parseHeader.
72
- * @param {object} [opts] - { fields, keep, stride, firstIndex, localOrigin, float32 }
75
+ * @param {object} [opts] - { fields, keep, thin, localOrigin, float32, into }
76
+ * into: { cols, at } decodes straight into existing columns from index at
77
+ * (each sized for every record that can be kept) and returns only { count }
73
78
  * @returns {object} PointColumns: { count, <field>: TypedArray, ... }
74
79
  */
75
80
  export function decodeRecords(bytes, header, opts) {
@@ -79,8 +84,7 @@ export function decodeRecords(bytes, header, opts) {
79
84
  var len = header.recordLength;
80
85
  var n = Math.floor(bytes.length / len);
81
86
  var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
82
- var stride = opts.stride > 1 ? opts.stride : 1;
83
- var first = opts.firstIndex || 0;
87
+ var thin = opts.thin && opts.thin.target < opts.thin.total ? opts.thin : null;
84
88
  var keep = typeof opts.keep === "function" ? opts.keep : null;
85
89
  var origin = opts.localOrigin || null;
86
90
  var ox = origin && isFinite(origin.x) ? origin.x : 0;
@@ -88,12 +92,9 @@ export function decodeRecords(bytes, header, opts) {
88
92
  var oz = origin && isFinite(origin.z) ? origin.z : 0;
89
93
  var f32 = !!(opts.float32 && origin);
90
94
 
91
- var cols = {};
92
- fields.forEach(function (name) {
93
- var Type = FIELD_TYPES[name];
94
- if (f32 && (name === "x" || name === "y" || name === "z")) Type = Float32Array;
95
- cols[name] = new Type(n);
96
- });
95
+ var into = opts.into || null;
96
+ var cols = into ? into.cols : {};
97
+ if (!into) fields.forEach(function (name) { cols[name] = new (columnType(name, opts))(n); });
97
98
  var F = fmt.fields;
98
99
  var sx = header.scale.x, sy = header.scale.y, sz = header.scale.z;
99
100
  var tx = header.offset.x - ox, ty = header.offset.y - oy, tz = header.offset.z - oz;
@@ -101,11 +102,15 @@ export function decodeRecords(bytes, header, opts) {
101
102
  var want = {};
102
103
  fields.forEach(function (name) { want[name] = true; });
103
104
 
104
- var k = 0;
105
+ var k = into ? into.at : 0;
105
106
  for (var i = 0; i < n; i++) {
106
- if (stride > 1 && (first + i) % stride !== 0) continue;
107
107
  var at = i * len;
108
108
  if (keep && !keep(view, at)) continue;
109
+ if (thin) {
110
+ thin.acc += thin.target;
111
+ if (thin.acc < thin.total) continue;
112
+ thin.acc -= thin.total;
113
+ }
109
114
  if (want.x) cols.x[k] = view.getInt32(at, LE) * sx + tx;
110
115
  if (want.y) cols.y[k] = view.getInt32(at + 4, LE) * sy + ty;
111
116
  if (want.z) cols.z[k] = view.getInt32(at + 8, LE) * sz + tz;
@@ -140,7 +145,20 @@ export function decodeRecords(bytes, header, opts) {
140
145
  if (want.nir) cols.nir[k] = view.getUint16(at + F.nir.offset, LE);
141
146
  k++;
142
147
  }
148
+ if (into) return { count: k - into.at };
143
149
  if (k < n) fields.forEach(function (name) { cols[name] = cols[name].slice(0, k); });
144
150
  cols.count = k;
145
151
  return cols;
146
152
  }
153
+
154
+ /**
155
+ * The typed-array type a field is decoded into (x / y / z are Float32
156
+ * when float32 is asked for with a local origin).
157
+ * @param {string} name
158
+ * @param {{float32?:boolean, localOrigin?:object}} [opts]
159
+ * @returns {Function}
160
+ */
161
+ export function columnType(name, opts) {
162
+ var f32 = !!(opts && opts.float32 && opts.localOrigin);
163
+ return f32 && (name === "x" || name === "y" || name === "z") ? Float32Array : FIELD_TYPES[name];
164
+ }
@@ -22,3 +22,24 @@ export function windows(header, windowSize) {
22
22
  }
23
23
  return out;
24
24
  }
25
+
26
+ /**
27
+ * Group LAZ chunks into windows of whole chunks, about windowSize bytes of
28
+ * DECOMPRESSED records each (one chunk at least).
29
+ * @param {Array<{firstRecord:number, count:number, start:number, end:number}>} chunks - from readChunks
30
+ * @param {number} recordLength
31
+ * @param {number} windowSize
32
+ * @returns {Array<{start:number, end:number, firstRecord:number, count:number, chunks:Array}>}
33
+ */
34
+ export function lazWindows(chunks, recordLength, windowSize) {
35
+ var out = [], cur = null;
36
+ chunks.forEach(function (c) {
37
+ if (cur && (cur.count + c.count) * recordLength > windowSize) { out.push(cur); cur = null; }
38
+ if (!cur) cur = { start: c.start, end: c.end, firstRecord: c.firstRecord, count: 0, chunks: [] };
39
+ cur.end = c.end;
40
+ cur.count += c.count;
41
+ cur.chunks.push(c);
42
+ });
43
+ if (cur) out.push(cur);
44
+ return out;
45
+ }
@@ -4,10 +4,12 @@
4
4
  // Worker globals so it can be driven directly (tests, custom hosts).
5
5
  // Same shape as ecw-js's.
6
6
  // In: { id, type, payload }
7
- // open { blob | bytes, windowSize? } -> { header, vlrs, evlrs, crs, compressed }
7
+ // open { blob | bytes, windowSize? } -> { header, vlrs, evlrs, crs, compressed, laz }
8
8
  // readPoints readPoints options + colour? -> PointColumns (+ colours)
9
9
  // chunks chunks options + colour? -> streams { id, type: "chunk", chunk } per
10
- // window, then result { count }
10
+ // window, then result { count }; colours
11
+ // follow one rule for the whole stream
12
+ // (createChunkColourizer)
11
13
  // readPointsToScratch readPointsToScratch options -> { count, fields, types, bounds }
12
14
  // colourize { points, by, ramp?, range? } -> Uint8Array
13
15
  // abort { id } -> cancels that job (it rejects ABORTED)
@@ -19,7 +21,7 @@
19
21
 
20
22
  import { LasError, ERROR_CODES } from "../errors.js";
21
23
  import { openLas } from "../LasReader.js";
22
- import { colourize } from "../colour/colourize.js";
24
+ import { colourize, createChunkColourizer } from "../colour/colourize.js";
23
25
 
24
26
  function buffersOf(cols) {
25
27
  var out = [];
@@ -66,16 +68,17 @@ export function createWorkerHandler(post) {
66
68
  if (reader) await reader.close();
67
69
  reader = null;
68
70
  reader = await openLas((payload && (payload.blob || payload.bytes)), { windowSize: payload && payload.windowSize });
69
- return { result: { header: reader.header, vlrs: reader.vlrs, evlrs: reader.evlrs, crs: reader.crs, compressed: reader.compressed } };
71
+ return { result: { header: reader.header, vlrs: reader.vlrs, evlrs: reader.evlrs, crs: reader.crs, compressed: reader.compressed, laz: reader.laz } };
70
72
  },
71
73
  readPoints: async function (id, payload, controller) {
72
74
  var cols = withColour(await needReader().readPoints(readOptions(id, payload, controller)), payload && payload.colour);
73
75
  return { result: cols, transfer: buffersOf(cols) };
74
76
  },
75
77
  chunks: async function (id, payload, controller) {
76
- var count = 0;
77
- for await (var c of needReader().chunks(readOptions(id, payload, controller))) {
78
- withColour(c, payload && payload.colour);
78
+ var count = 0, r = needReader();
79
+ var paint = payload.colour ? createChunkColourizer(payload.colour, r.header.bounds, payload.localOrigin) : null;
80
+ for await (var c of r.chunks(readOptions(id, payload, controller))) {
81
+ if (paint) c.colours = paint(c);
79
82
  count += c.count;
80
83
  post({ id: id, type: "chunk", chunk: c }, buffersOf(c));
81
84
  }