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.
@@ -6,20 +6,27 @@
6
6
  // Point14 passes on, with the version-3 quirk preserved: a context
7
7
  // first used is seeded from the last one, and in version 3 later
8
8
  // values are predicted from whichever context it was entered from.
9
+ // Each compressor shares its decompressor's setup and context switch,
10
+ // so the two stay in step point for point.
9
11
  //
10
12
  // Ported from laz-rs src/las/rgb.rs, nir.rs and extra_bytes.rs, modules
11
13
  // v3_v4 (https://github.com/laz-rs/laz-rs), itself a port of LASzip by
12
14
  // rapidlasso. (c) 2007-2015 martin isenburg, rapidlasso; (c) 2019-2023
13
15
  // Thomas Montaigu.
14
16
  // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
15
- // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026.
17
+ // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026;
18
+ // the compressors (Rgb14Compressor, Nir14Compressor, Byte14Compressor)
19
+ // added beside the decompressors, writing a layer once a value changes
20
+ // as LAZperf (hobuinc/laz-perf, field_rgb14/nir14/byte14.cpp) does.
16
21
  //==========================================================
17
22
 
18
23
  import { ArithmeticDecoder } from "../ArithmeticDecoder.js";
24
+ import { ArithmeticEncoder } from "../ArithmeticEncoder.js";
19
25
  import { ArithmeticModel } from "../models.js";
20
- import { RgbModels, decodeRgb } from "../v2/rgb12.js";
26
+ import { RgbModels, decodeRgb, encodeRgb, readRgb } from "../v2/rgb12.js";
21
27
 
22
28
  var LE = true;
29
+ var EMPTY = new Uint8Array(0);
23
30
 
24
31
  // laz-rs's context switch, shared by the three items: returns the index whose
25
32
  // last value predicts this point (`last_idx`), after switching to `ctx`.
@@ -38,6 +45,13 @@ function enterContext(item, ctx) {
38
45
  return lastIdx;
39
46
  }
40
47
 
48
+ // The first point of a chunk: only its channel's context is in use
49
+ function firstContext(item, ctx) {
50
+ item.unused = [true, true, true, true];
51
+ item.unused[ctx] = false;
52
+ item.lastContext = ctx;
53
+ }
54
+
41
55
  function layer(item, bytes, q, n, fail, e, base) {
42
56
  if (q + n > e) fail(q);
43
57
  if (n === 0) return null;
@@ -46,27 +60,30 @@ function layer(item, bytes, q, n, fail, e, base) {
46
60
  return d;
47
61
  }
48
62
 
63
+ // The state and models both halves of RGB14 start a chunk with
64
+ function initRgb14(self, version) {
65
+ self.size = 6;
66
+ self.version = version;
67
+ self.models = [new RgbModels(), new RgbModels(), new RgbModels(), new RgbModels()];
68
+ self.last = [new Uint16Array(3), new Uint16Array(3), new Uint16Array(3), new Uint16Array(3)];
69
+ self.unused = [true, true, true, true];
70
+ self.lastContext = 0;
71
+ }
72
+ function seedRgb(from, to) { this.last[to].set(this.last[from]); }
73
+
49
74
  /** RGB14 (version 3 or 4). */
50
75
  export function Rgb14Decompressor(version) {
51
- this.size = 6;
52
- this.version = version;
53
- this.models = [new RgbModels(), new RgbModels(), new RgbModels(), new RgbModels()];
54
- this.last = [new Uint16Array(3), new Uint16Array(3), new Uint16Array(3), new Uint16Array(3)];
55
- this.unused = [true, true, true, true];
56
- this.lastContext = 0;
76
+ initRgb14(this, version);
57
77
  this.dec = null;
58
78
  }
59
79
  Rgb14Decompressor.prototype.first = function (src, s, view, o, ctx) {
60
80
  for (var i = 0; i < 6; i++) view.setUint8(o + i, src[s + i]);
61
- var c = ctx.v;
62
- 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);
63
- this.unused = [true, true, true, true];
64
- this.unused[c] = false;
65
- this.lastContext = c;
81
+ readRgb(this.last[ctx.v], src, s);
82
+ firstContext(this, ctx.v);
66
83
  };
67
84
  Rgb14Decompressor.prototype.readLayerSizes = function (dv, q) { this.layerSize = dv.getUint32(q, LE); return q + 4; };
68
85
  Rgb14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) { this.dec = layer(this, bytes, q, this.layerSize, fail, e, base); return q + this.layerSize; };
69
- Rgb14Decompressor.prototype.seed = function (from, to) { this.last[to].set(this.last[from]); };
86
+ Rgb14Decompressor.prototype.seed = seedRgb;
70
87
  Rgb14Decompressor.prototype.next = function (view, o, ctx) {
71
88
  var idx = enterContext(this, ctx.v);
72
89
  var rgb = this.last[idx];
@@ -74,32 +91,57 @@ Rgb14Decompressor.prototype.next = function (view, o, ctx) {
74
91
  view.setUint16(o, rgb[0], LE); view.setUint16(o + 2, rgb[1], LE); view.setUint16(o + 4, rgb[2], LE);
75
92
  };
76
93
 
94
+ /** RGB14 compressor (version 3 or 4): one layer, written once a colour differs or is not grey. */
95
+ export function Rgb14Compressor(version) {
96
+ initRgb14(this, version);
97
+ this.enc = new ArithmeticEncoder();
98
+ this.used = false;
99
+ this.cur = new Uint16Array(3);
100
+ }
101
+ Rgb14Compressor.prototype.first = function (src, s, ctx) {
102
+ readRgb(this.last[ctx.v], src, s);
103
+ firstContext(this, ctx.v);
104
+ };
105
+ Rgb14Compressor.prototype.seed = seedRgb;
106
+ Rgb14Compressor.prototype.next = function (src, s, ctx) {
107
+ var idx = enterContext(this, ctx.v);
108
+ readRgb(this.cur, src, s);
109
+ if (encodeRgb(this.enc, this.models[this.lastContext], this.last[idx], this.cur)) this.used = true;
110
+ this.last[idx].set(this.cur);
111
+ };
112
+ /** @returns {Array<Uint8Array>} the one layer (empty when nothing was coded) */
113
+ Rgb14Compressor.prototype.finish = function () { return [this.used ? this.enc.done() : EMPTY]; };
114
+
115
+ // The state and models both halves of NIR14 start a chunk with
116
+ function initNir14(self, version) {
117
+ self.size = 2;
118
+ self.version = version;
119
+ self.usedModel = []; self.low = []; self.high = [];
120
+ for (var i = 0; i < 4; i++) { self.usedModel.push(new ArithmeticModel(4)); self.low.push(new ArithmeticModel(256)); self.high.push(new ArithmeticModel(256)); }
121
+ self.last = new Uint16Array(4);
122
+ self.unused = [true, true, true, true];
123
+ self.lastContext = 0;
124
+ }
125
+ function seedNir(from, to) { this.last[to] = this.last[from]; }
126
+
77
127
  /** NIR14 (version 3 or 4): the near-infrared word of RGBNIR14. */
78
128
  export function Nir14Decompressor(version) {
79
- this.size = 2;
80
- this.version = version;
81
- this.used = []; this.low = []; this.high = [];
82
- 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)); }
83
- this.last = new Uint16Array(4);
84
- this.unused = [true, true, true, true];
85
- this.lastContext = 0;
129
+ initNir14(this, version);
86
130
  this.dec = null;
87
131
  }
88
132
  Nir14Decompressor.prototype.first = function (src, s, view, o, ctx) {
89
133
  view.setUint8(o, src[s]); view.setUint8(o + 1, src[s + 1]);
90
- this.last[ctx.v] = view.getUint16(o, LE);
91
- this.unused = [true, true, true, true];
92
- this.unused[ctx.v] = false;
93
- this.lastContext = ctx.v;
134
+ this.last[ctx.v] = src[s] + src[s + 1] * 256;
135
+ firstContext(this, ctx.v);
94
136
  };
95
137
  Nir14Decompressor.prototype.readLayerSizes = Rgb14Decompressor.prototype.readLayerSizes;
96
138
  Nir14Decompressor.prototype.readLayers = Rgb14Decompressor.prototype.readLayers;
97
- Nir14Decompressor.prototype.seed = function (from, to) { this.last[to] = this.last[from]; };
139
+ Nir14Decompressor.prototype.seed = seedNir;
98
140
  Nir14Decompressor.prototype.next = function (view, o, ctx) {
99
141
  var idx = enterContext(this, ctx.v);
100
142
  if (this.dec) {
101
143
  var c = this.lastContext, last = this.last[idx];
102
- var sym = this.dec.decodeSymbol(this.used[c]);
144
+ var sym = this.dec.decodeSymbol(this.usedModel[c]);
103
145
  var v = sym & 1 ? (this.dec.decodeSymbol(this.low[c]) + (last & 255)) & 255 : last & 255;
104
146
  v += sym & 2 ? ((this.dec.decodeSymbol(this.high[c]) + (last >>> 8)) & 255) * 256 : last & 0xFF00;
105
147
  this.last[idx] = v;
@@ -107,25 +149,56 @@ Nir14Decompressor.prototype.next = function (view, o, ctx) {
107
149
  view.setUint16(o, this.last[idx], LE);
108
150
  };
109
151
 
152
+ /** NIR14 compressor (version 3 or 4): one layer, written once the value changes. */
153
+ export function Nir14Compressor(version) {
154
+ initNir14(this, version);
155
+ this.enc = new ArithmeticEncoder();
156
+ this.used = false;
157
+ }
158
+ Nir14Compressor.prototype.first = function (src, s, ctx) {
159
+ this.last[ctx.v] = src[s] + src[s + 1] * 256;
160
+ firstContext(this, ctx.v);
161
+ };
162
+ Nir14Compressor.prototype.seed = seedNir;
163
+ Nir14Compressor.prototype.next = function (src, s, ctx) {
164
+ var idx = enterContext(this, ctx.v), c = this.lastContext;
165
+ var last = this.last[idx], v = src[s] + src[s + 1] * 256;
166
+ var sym = ((last ^ v) & 255 ? 1 : 0) + ((last ^ v) & 0xFF00 ? 2 : 0);
167
+ this.enc.encodeSymbol(this.usedModel[c], sym);
168
+ if (sym & 1) this.enc.encodeSymbol(this.low[c], ((v & 255) - (last & 255)) & 255);
169
+ if (sym & 2) this.enc.encodeSymbol(this.high[c], ((v >>> 8) - (last >>> 8)) & 255);
170
+ if (sym) this.used = true;
171
+ this.last[idx] = v;
172
+ };
173
+ /** @returns {Array<Uint8Array>} the one layer (empty when the value never changed) */
174
+ Nir14Compressor.prototype.finish = Rgb14Compressor.prototype.finish;
175
+
176
+ // The state both halves of Byte14 start a chunk with; a context's models are made when it is first used
177
+ function initByte14(self, count, version) {
178
+ self.size = count;
179
+ self.version = version;
180
+ self.models = [null, null, null, null];
181
+ self.last = [new Uint8Array(count), new Uint8Array(count), new Uint8Array(count), new Uint8Array(count)];
182
+ self.unused = [true, true, true, true];
183
+ self.lastContext = 0;
184
+ }
185
+ function byteModels(count) { var m = []; for (var i = 0; i < count; i++) m.push(new ArithmeticModel(256)); return m; }
186
+ function firstBytes(item, src, s, ctx) {
187
+ item.last[ctx.v].set(src.subarray(s, s + item.size));
188
+ firstContext(item, ctx.v);
189
+ item.models[ctx.v] = byteModels(item.size);
190
+ }
191
+ function seedBytes(from, to) { this.models[to] = byteModels(this.size); this.last[to].set(this.last[from]); }
192
+
110
193
  /** Byte14 (version 3 or 4): `count` extra bytes, one layer each. */
111
194
  export function Byte14Decompressor(count, version) {
112
- this.size = count;
113
- this.version = version;
114
- this.models = [null, null, null, null];
115
- this.last = [new Uint8Array(count), new Uint8Array(count), new Uint8Array(count), new Uint8Array(count)];
116
- this.unused = [true, true, true, true];
117
- this.lastContext = 0;
195
+ initByte14(this, count, version);
118
196
  this.sizes = new Array(count);
119
197
  this.dec = new Array(count);
120
198
  }
121
- function byteModels(count) { var m = []; for (var i = 0; i < count; i++) m.push(new ArithmeticModel(256)); return m; }
122
199
  Byte14Decompressor.prototype.first = function (src, s, view, o, ctx) {
123
200
  for (var i = 0; i < this.size; i++) view.setUint8(o + i, src[s + i]);
124
- this.last[ctx.v].set(src.subarray(s, s + this.size));
125
- this.unused = [true, true, true, true];
126
- this.unused[ctx.v] = false;
127
- this.models[ctx.v] = byteModels(this.size);
128
- this.lastContext = ctx.v;
201
+ firstBytes(this, src, s, ctx);
129
202
  };
130
203
  Byte14Decompressor.prototype.readLayerSizes = function (dv, q) {
131
204
  for (var i = 0; i < this.size; i++) { this.sizes[i] = dv.getUint32(q, LE); q += 4; }
@@ -135,7 +208,7 @@ Byte14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) {
135
208
  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]; }
136
209
  return q;
137
210
  };
138
- Byte14Decompressor.prototype.seed = function (from, to) { this.models[to] = byteModels(this.size); this.last[to].set(this.last[from]); };
211
+ Byte14Decompressor.prototype.seed = seedBytes;
139
212
  Byte14Decompressor.prototype.next = function (view, o, ctx) {
140
213
  var idx = enterContext(this, ctx.v);
141
214
  var last = this.last[idx], models = this.models[ctx.v];
@@ -144,3 +217,28 @@ Byte14Decompressor.prototype.next = function (view, o, ctx) {
144
217
  view.setUint8(o + i, last[i]);
145
218
  }
146
219
  };
220
+
221
+ /** Byte14 compressor (version 3 or 4): `count` extra bytes, a layer each, written once that byte changes. */
222
+ export function Byte14Compressor(count, version) {
223
+ initByte14(this, count, version);
224
+ this.enc = [];
225
+ this.used = [];
226
+ for (var i = 0; i < count; i++) { this.enc.push(new ArithmeticEncoder()); this.used.push(false); }
227
+ }
228
+ Byte14Compressor.prototype.first = function (src, s, ctx) { firstBytes(this, src, s, ctx); };
229
+ Byte14Compressor.prototype.seed = seedBytes;
230
+ Byte14Compressor.prototype.next = function (src, s, ctx) {
231
+ var idx = enterContext(this, ctx.v);
232
+ var last = this.last[idx], models = this.models[ctx.v];
233
+ for (var i = 0; i < this.size; i++) {
234
+ var d = (src[s + i] - last[i]) & 255; // u8 wrapping_sub
235
+ this.enc[i].encodeSymbol(models[i], d);
236
+ if (d) { this.used[i] = true; last[i] = src[s + i]; }
237
+ }
238
+ };
239
+ /** @returns {Array<Uint8Array>} one layer per byte (empty when that byte never changed) */
240
+ Byte14Compressor.prototype.finish = function () {
241
+ var out = [];
242
+ for (var i = 0; i < this.size; i++) out.push(this.used[i] ? this.enc[i].done() : EMPTY);
243
+ return out;
244
+ };
@@ -5,7 +5,9 @@
5
5
  // XY, Z, classification, flags, intensity, scan angle, user data,
6
6
  // point source, GPS time — each its own arithmetic stream, so a layer
7
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.
8
+ // the last one). Up to four contexts follow the scanner channel. The
9
+ // compressor and decompressor below share each context's models,
10
+ // medians and coders (Context), so they stay in step point for point.
9
11
  //
10
12
  // Ported from laz-rs src/las/point6.rs, module v3_v4, and the 16x16
11
13
  // return tables of src/las/utils.rs (https://github.com/laz-rs/laz-rs),
@@ -13,13 +15,21 @@
13
15
  // rapidlasso; (c) 2019-2023 Thomas Montaigu.
14
16
  // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
15
17
  // NOTICE). Changes: translated from Rust to JavaScript, working on raw
16
- // record bytes, GPS time as two u32 halves, Brent Buffham 2026.
18
+ // record bytes, GPS time as two u32 halves, Brent Buffham 2026; the
19
+ // compressor (Point14Compressor) added beside the decompressor, sharing
20
+ // its contexts, deciding which layers to write as LAZperf
21
+ // (hobuinc/laz-perf, field_point14.cpp) does, with one change: a GPS
22
+ // time is "changed" when its bits differ (or it is NaN), so -0 after +0
23
+ // survives, where LAZperf's double compare would drop the sign.
17
24
  //==========================================================
18
25
 
19
26
  import { ArithmeticDecoder } from "../ArithmeticDecoder.js";
27
+ import { ArithmeticEncoder } from "../ArithmeticEncoder.js";
20
28
  import { ArithmeticModel } from "../models.js";
21
29
  import { IntegerDecompressor } from "../IntegerDecompressor.js";
22
- import { StreamingMedian } from "../v2/point10.js";
30
+ import { IntegerCompressor } from "../IntegerCompressor.js";
31
+ import { StreamingMedian, yContext, zContext } from "../v2/point10.js";
32
+ import { quantizeMulti, diff32 } from "../v2/gpstime11.js";
23
33
 
24
34
  var LE = true;
25
35
  var MULTI = 500, MULTI_MINUS = -10;
@@ -43,8 +53,20 @@ for (var ln = 0; ln < 16; ln++) { LEVEL_8CT.push([]); for (var lr = 0; lr < 16;
43
53
 
44
54
  function medians(n) { var out = []; for (var i = 0; i < n; i++) out.push(new StreamingMedian()); return out; }
45
55
 
46
- // One context's state, started from the last point of another (laz-rs from_last_point)
47
- function Context(p) {
56
+ // One record's fields from a view (into q, or a new object)
57
+ function readPoint14(v, o, q) {
58
+ q = q || {};
59
+ q.x = v.getInt32(o, LE); q.y = v.getInt32(o + 4, LE); q.z = v.getInt32(o + 8, LE);
60
+ q.intensity = v.getUint16(o + 12, LE); q.bits = v.getUint8(o + 14); q.flags = v.getUint8(o + 15);
61
+ q.cls = v.getUint8(o + 16); q.user = v.getUint8(o + 17); q.angle = v.getInt16(o + 18, LE);
62
+ q.psid = v.getUint16(o + 20, LE); q.gpsLo = v.getUint32(o + 22, LE); q.gpsHi = v.getInt32(o + 26, LE);
63
+ return q;
64
+ }
65
+
66
+ // One context's state, started from the last point of another (laz-rs from_last_point);
67
+ // IC is the integer coder (IntegerCompressor or IntegerDecompressor), so both halves get
68
+ // the same widths and contexts
69
+ function Context(p, IC) {
48
70
  this.unused = false;
49
71
  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
72
  this.lastIntensity = new Uint16Array(8).fill(p.intensity);
@@ -61,13 +83,13 @@ function Context(p) {
61
83
  this.userM = new Array(64);
62
84
  this.gpsMulti = new ArithmeticModel(MULTI_TOTAL);
63
85
  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);
86
+ this.icDx = new IC(32, 2);
87
+ this.icDy = new IC(32, 22);
88
+ this.icZ = new IC(32, 20);
89
+ this.icIntensity = new IC(16, 4);
90
+ this.icAngle = new IC(16, 2);
91
+ this.icPsid = new IC(16, 1);
92
+ this.icGps = new IC(32, 9);
71
93
  this.gLast = 0; this.gNext = 0;
72
94
  this.gHi = new Int32Array(4); this.gLo = new Float64Array(4);
73
95
  this.gHi[0] = p.gpsHi; this.gLo[0] = p.gpsLo;
@@ -77,20 +99,37 @@ function Context(p) {
77
99
 
78
100
  function lazy(arr, i, symbols) { return arr[i] || (arr[i] = new ArithmeticModel(symbols)); }
79
101
 
102
+ // The changed-values model context from the last point: single / first / last / intermediate,
103
+ // and whether its GPS time changed
104
+ function lastReturnContext(p) {
105
+ var r = p.bits & 15, n = p.bits >>> 4;
106
+ return (r === 1 ? 1 : 0) + (r >= n ? 2 : 0) + (p.gpsChange ? 4 : 0);
107
+ }
108
+
109
+ // The flags symbol: classification flags (bits 0-3), scan direction (4), edge of flight line (5)
110
+ // — the flags byte without the scanner channel, which layer 0 codes
111
+ function flagsSymbol(flags) { return ((flags >>> 7) & 1) * 32 + ((flags >>> 6) & 1) * 16 + (flags & 15); }
112
+
80
113
  Context.prototype.addGps = function (i, d) {
81
114
  var lo = this.gLo[i] + d;
82
115
  if (lo >= 4294967296) { lo -= 4294967296; this.gHi[i] = (this.gHi[i] + 1) | 0; }
83
116
  else if (lo < 0) { lo += 4294967296; this.gHi[i] = (this.gHi[i] - 1) | 0; }
84
117
  this.gLo[i] = lo;
85
118
  };
86
- Context.prototype.fullGps = function (dec) {
119
+ // A full time (hi, lo) starts the next of the four sequences, with no difference yet
120
+ Context.prototype.startGps = function (hi, lo) {
87
121
  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();
122
+ this.gHi[this.gNext] = hi;
123
+ this.gLo[this.gNext] = lo;
90
124
  this.gLast = this.gNext;
91
125
  this.gDiff[this.gLast] = 0;
92
126
  this.gExtreme[this.gLast] = 0;
93
127
  };
128
+ Context.prototype.fullGps = function (dec) {
129
+ var hi = this.icGps.decompress(dec, this.gHi[this.gLast], 8);
130
+ this.startGps(hi, dec.readInt());
131
+ };
132
+ // After three extreme differences in a row, the latest becomes the new reference
94
133
  Context.prototype.extremeGps = function (d) {
95
134
  if (++this.gExtreme[this.gLast] > 3) { this.gDiff[this.gLast] = d; this.gExtreme[this.gLast] = 0; }
96
135
  };
@@ -145,6 +184,70 @@ Context.prototype.readGps = function (dec) {
145
184
  }
146
185
  };
147
186
 
187
+ // The first other sequence (1-3 on from the last) the time (hi, lo) is within i32 of, or 0
188
+ Context.prototype.otherGps = function (hi, lo) {
189
+ for (var i = 1; i < 4; i++) { var j = (this.gLast + i) & 3; if (diff32(hi, lo, this.gHi[j], this.gLo[j]) !== null) return i; }
190
+ return 0;
191
+ };
192
+ Context.prototype.writeFullGps = function (enc, hi, lo) {
193
+ this.icGps.compress(enc, this.gHi[this.gLast], hi, 8);
194
+ enc.writeInt(lo);
195
+ this.startGps(hi, lo);
196
+ };
197
+ // The mirror of readGps (LAZperf encodeGpsTime): the time (hi, lo), known to have changed
198
+ Context.prototype.writeGps = function (enc, hi, lo) {
199
+ for (;;) {
200
+ var L = this.gLast, d = diff32(hi, lo, this.gHi[L], this.gLo[L]), j, multi;
201
+ if (this.gDiff[L] === 0) {
202
+ if (d !== null) {
203
+ enc.encodeSymbol(this.gpsNoDiff, 0);
204
+ this.icGps.compress(enc, 0, d, 0);
205
+ this.gDiff[L] = d;
206
+ this.gExtreme[L] = 0;
207
+ } else if ((j = this.otherGps(hi, lo))) {
208
+ enc.encodeSymbol(this.gpsNoDiff, j + 1);
209
+ this.gLast = (L + j) & 3;
210
+ continue; // code again against the other sequence
211
+ } else {
212
+ enc.encodeSymbol(this.gpsNoDiff, 1);
213
+ this.writeFullGps(enc, hi, lo);
214
+ }
215
+ } else if (d !== null) {
216
+ multi = quantizeMulti(d, this.gDiff[L]);
217
+ if (multi > 0 && multi < MULTI) {
218
+ enc.encodeSymbol(this.gpsMulti, multi);
219
+ this.icGps.compress(enc, Math.imul(multi, this.gDiff[L]), d, multi === 1 ? 1 : multi < 10 ? 2 : 3);
220
+ if (multi === 1) this.gExtreme[L] = 0;
221
+ } else if (multi >= MULTI) {
222
+ enc.encodeSymbol(this.gpsMulti, MULTI);
223
+ this.icGps.compress(enc, Math.imul(MULTI, this.gDiff[L]), d, 4);
224
+ this.extremeGps(d);
225
+ } else if (multi < 0 && multi > MULTI_MINUS) {
226
+ enc.encodeSymbol(this.gpsMulti, MULTI - multi);
227
+ this.icGps.compress(enc, Math.imul(multi, this.gDiff[L]), d, 5);
228
+ } else if (multi < 0) {
229
+ enc.encodeSymbol(this.gpsMulti, MULTI - MULTI_MINUS);
230
+ this.icGps.compress(enc, Math.imul(MULTI_MINUS, this.gDiff[L]), d, 6);
231
+ this.extremeGps(d);
232
+ } else {
233
+ enc.encodeSymbol(this.gpsMulti, 0);
234
+ this.icGps.compress(enc, 0, d, 7);
235
+ this.extremeGps(d);
236
+ }
237
+ } else if ((j = this.otherGps(hi, lo))) {
238
+ enc.encodeSymbol(this.gpsMulti, CODE_FULL + j);
239
+ this.gLast = (L + j) & 3;
240
+ continue;
241
+ } else {
242
+ enc.encodeSymbol(this.gpsMulti, CODE_FULL);
243
+ this.writeFullGps(enc, hi, lo);
244
+ }
245
+ this.gHi[this.gLast] = hi;
246
+ this.gLo[this.gLast] = lo;
247
+ return;
248
+ }
249
+ };
250
+
148
251
  var LAYERS = 9; // channel/returns/xy, z, classification, flags, intensity, scan angle, user data, point source, gps time
149
252
 
150
253
  /**
@@ -163,15 +266,10 @@ export function Point14Decompressor(version) {
163
266
 
164
267
  Point14Decompressor.prototype.first = function (src, s, view, o, ctx) {
165
268
  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
- };
269
+ var p = readPoint14(view, o);
172
270
  this.current = (p.flags >>> 4) & 3;
173
271
  this.contexts = [null, null, null, null];
174
- this.contexts[this.current] = new Context(p);
272
+ this.contexts[this.current] = new Context(p, IntegerDecompressor);
175
273
  ctx.v = this.current;
176
274
  };
177
275
 
@@ -195,12 +293,10 @@ Point14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) {
195
293
  Point14Decompressor.prototype.next = function (view, o, ctx) {
196
294
  var c = this.contexts[this.current];
197
295
  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]);
296
+ var changed = d0.decodeSymbol(c.changed[lastReturnContext(p)]);
201
297
  if (changed & 64) {
202
298
  var channel = (this.current + d0.decodeSymbol(c.channel) + 1) % 4;
203
- if (!this.contexts[channel]) this.contexts[channel] = new Context(p);
299
+ if (!this.contexts[channel]) this.contexts[channel] = new Context(p, IntegerDecompressor);
204
300
  this.current = channel;
205
301
  if (this.version === 3) ctx.v = channel;
206
302
  c = this.contexts[channel];
@@ -209,7 +305,7 @@ Point14Decompressor.prototype.next = function (view, o, ctx) {
209
305
  }
210
306
  if (this.version !== 3) ctx.v = this.current;
211
307
  var psidChanged = changed & 32, gpsChanged = changed & 16 ? 1 : 0, angleChanged = changed & 8;
212
- var lastN = p.bits >>> 4, lastR = p.bits & 15;
308
+ var lastN = p.bits >>> 4, lastR = p.bits & 15, n, r;
213
309
  n = changed & 4 ? d0.decodeSymbol(lazy(c.nret, lastN, 16)) : lastN;
214
310
  var low = changed & 3;
215
311
  if (low === 0) r = lastR;
@@ -225,19 +321,16 @@ Point14Decompressor.prototype.next = function (view, o, ctx) {
225
321
  var diff = c.icDx.decompress(d0, c.xMedian[idx].get(), single);
226
322
  p.x = (p.x + diff) | 0;
227
323
  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));
324
+ diff = c.icDy.decompress(d0, c.yMedian[idx].get(), yContext(single, c.icDx));
230
325
  p.y = (p.y + diff) | 0;
231
326
  c.yMedian[idx].add(diff);
232
327
  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));
328
+ p.z = c.icZ.decompress(this.dec[1], c.lastZ[l], zContext(single, c.icDx, c.icDy));
235
329
  c.lastZ[l] = p.z;
236
330
  }
237
331
  if (this.on[2]) p.cls = this.dec[2].decodeSymbol(lazy(c.clsM, (p.cls & 31) * 2 + (cpr === 3 ? 1 : 0), 256));
238
332
  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));
333
+ var f = this.dec[3].decodeSymbol(lazy(c.flagsM, flagsSymbol(p.flags), 64));
241
334
  p.flags = ((f >>> 5) & 1) * 128 + ((f >>> 4) & 1) * 64 + (p.flags & 0x30) + (f & 15);
242
335
  }
243
336
  if (this.on[4]) {
@@ -264,3 +357,108 @@ Point14Decompressor.prototype.next = function (view, o, ctx) {
264
357
  view.setUint32(o + 22, p.gpsLo, LE);
265
358
  view.setInt32(o + 26, p.gpsHi, LE);
266
359
  };
360
+
361
+ // A DataView over the caller's records, made once per buffer
362
+ function viewOf(item, src) {
363
+ if (item._src !== src) { item._src = src; item._view = new DataView(src.buffer, src.byteOffset, src.byteLength); }
364
+ return item._view;
365
+ }
366
+
367
+ // Whether a point's GPS time changed from the last: its bits differ, or it is NaN (a NaN never
368
+ // equals itself, as LAZperf's double compare has it). Unlike that compare, -0 and +0 differ.
369
+ function gpsChangedFrom(p, q) {
370
+ if (q.gpsHi !== p.gpsHi || q.gpsLo !== p.gpsLo) return true;
371
+ return (q.gpsHi & 0x7FF00000) === 0x7FF00000 && ((q.gpsHi & 0xFFFFF) !== 0 || q.gpsLo !== 0);
372
+ }
373
+
374
+ /**
375
+ * Point14 compressor: one per chunk, the mirror of Point14Decompressor. Each layer has its own
376
+ * encoder; channel/returns/XY and Z are always written, the others once a value changes (LAZperf).
377
+ * @param {number} version - LASzip item version, 3 or 4
378
+ */
379
+ export function Point14Compressor(version) {
380
+ this.size = 30;
381
+ this.version = version;
382
+ this.contexts = [null, null, null, null];
383
+ this.current = 0;
384
+ this.enc = [];
385
+ this.used = [];
386
+ for (var i = 0; i < LAYERS; i++) { this.enc.push(new ArithmeticEncoder()); this.used.push(i < 2); }
387
+ this.q = {};
388
+ this._src = null;
389
+ this._view = null;
390
+ }
391
+
392
+ /** The first point of a chunk is stored raw; it starts its channel's context. */
393
+ Point14Compressor.prototype.first = function (src, s, ctx) {
394
+ var p = readPoint14(viewOf(this, src), s);
395
+ this.current = (p.flags >>> 4) & 3;
396
+ this.contexts[this.current] = new Context(p, IntegerCompressor);
397
+ ctx.v = this.current;
398
+ };
399
+
400
+ Point14Compressor.prototype.next = function (src, s, ctx) {
401
+ var q = readPoint14(viewOf(this, src), s, this.q);
402
+ var prev = this.contexts[this.current], sc = (q.flags >>> 4) & 3;
403
+ var c = this.contexts[sc];
404
+ // the last point of this channel, or (a channel not seen yet) of the last one
405
+ var p = c ? c.p : prev.p, e0 = this.enc[0];
406
+ var gpsChanged = gpsChangedFrom(p, q) ? 1 : 0;
407
+ var psidChanged = q.psid !== p.psid, angleChanged = q.angle !== p.angle;
408
+ var lastN = p.bits >>> 4, lastR = p.bits & 15, n = q.bits >>> 4, r = q.bits & 15;
409
+ var low = r === lastR ? 0 : r === (lastR + 1) % 16 ? 1 : r === (lastR + 15) % 16 ? 2 : 3;
410
+ var changed = (sc !== this.current ? 64 : 0) + (psidChanged ? 32 : 0) + gpsChanged * 16 + (angleChanged ? 8 : 0) + (n !== lastN ? 4 : 0) + low;
411
+ e0.encodeSymbol(prev.changed[lastReturnContext(prev.p)], changed);
412
+ if (changed & 64) {
413
+ e0.encodeSymbol(prev.channel, (sc - this.current + 3) % 4);
414
+ if (!c) { c = this.contexts[sc] = new Context(prev.p, IntegerCompressor); p = c.p; }
415
+ this.current = sc;
416
+ if (this.version === 3) ctx.v = sc;
417
+ }
418
+ if (this.version !== 3) ctx.v = this.current;
419
+ if (changed & 4) e0.encodeSymbol(lazy(c.nret, lastN, 16), n);
420
+ if (low === 3) {
421
+ if (gpsChanged) e0.encodeSymbol(lazy(c.rnum, lastR, 16), r);
422
+ else e0.encodeSymbol(c.rnumGpsSame, (r - lastR + 14) % 16); // r - lastR - 2, mod 16
423
+ }
424
+ var m = MAP_6CTX[n][r], l = LEVEL_8CT[n][r];
425
+ var cpr = (r === 1 ? 2 : 0) + (r >= n ? 1 : 0);
426
+ var single = n === 1 ? 1 : 0;
427
+ var idx = m * 2 + gpsChanged;
428
+ var diff = (q.x - p.x) | 0; // i32 wrapping_sub
429
+ c.icDx.compress(e0, c.xMedian[idx].get(), diff, single);
430
+ c.xMedian[idx].add(diff);
431
+ diff = (q.y - p.y) | 0;
432
+ c.icDy.compress(e0, c.yMedian[idx].get(), diff, yContext(single, c.icDx));
433
+ c.yMedian[idx].add(diff);
434
+ c.icZ.compress(this.enc[1], c.lastZ[l], q.z, zContext(single, c.icDx, c.icDy));
435
+ c.lastZ[l] = q.z;
436
+ var used = this.used;
437
+ if (q.cls !== p.cls) used[2] = true;
438
+ this.enc[2].encodeSymbol(lazy(c.clsM, (p.cls & 31) * 2 + (cpr === 3 ? 1 : 0), 256), q.cls);
439
+ var lastF = flagsSymbol(p.flags), f = flagsSymbol(q.flags);
440
+ if (f !== lastF) used[3] = true;
441
+ this.enc[3].encodeSymbol(lazy(c.flagsM, lastF, 64), f);
442
+ var ii = cpr * 2 + gpsChanged;
443
+ if (q.intensity !== p.intensity) used[4] = true;
444
+ c.icIntensity.compress(this.enc[4], c.lastIntensity[ii], q.intensity, cpr);
445
+ c.lastIntensity[ii] = q.intensity;
446
+ if (angleChanged) { used[5] = true; c.icAngle.compress(this.enc[5], p.angle, q.angle, gpsChanged); }
447
+ if (q.user !== p.user) used[6] = true;
448
+ this.enc[6].encodeSymbol(lazy(c.userM, Math.floor(p.user / 4), 256), q.user);
449
+ if (psidChanged) { used[7] = true; c.icPsid.compress(this.enc[7], p.psid, q.psid, 0); }
450
+ if (gpsChanged) { used[8] = true; c.writeGps(this.enc[8], q.gpsHi, q.gpsLo); }
451
+ p.x = q.x; p.y = q.y; p.z = q.z; p.intensity = q.intensity; p.bits = q.bits; p.flags = q.flags;
452
+ p.cls = q.cls; p.angle = q.angle; p.user = q.user; p.psid = q.psid; p.gpsHi = q.gpsHi; p.gpsLo = q.gpsLo;
453
+ p.gpsChange = gpsChanged === 1;
454
+ };
455
+
456
+ /**
457
+ * Finish the chunk's layers.
458
+ * @returns {Array<Uint8Array>} the nine layers in readLayerSizes order; an unchanged layer is empty
459
+ */
460
+ Point14Compressor.prototype.finish = function () {
461
+ var out = [];
462
+ for (var i = 0; i < LAYERS; i++) out.push(this.used[i] ? this.enc[i].done() : new Uint8Array(0));
463
+ return out;
464
+ };
@@ -25,8 +25,9 @@ function col(cols, name) { var c = cols[name]; return c ? function (i) { return
25
25
  * @param {DataView} out
26
26
  * @param {number} at
27
27
  * @param {object} totals - updated: bounds (quantised world values) and byReturn counts
28
+ * @param {number} [first=0] - the index of cols' first point in the caller's points (named in errors)
28
29
  */
29
- export function encodeRecords(cols, pointFormat, recordLength, so, origin, out, at, totals) {
30
+ export function encodeRecords(cols, pointFormat, recordLength, so, origin, out, at, totals, first) {
30
31
  var fmt = POINT_FORMATS[pointFormat];
31
32
  var F = fmt.fields;
32
33
  var ox = origin && isFinite(origin.x) ? origin.x : 0;
@@ -41,7 +42,7 @@ export function encodeRecords(cols, pointFormat, recordLength, so, origin, out,
41
42
  var Y = Math.round((cols.y[i] + oy - so.offset.y) / so.scale.y);
42
43
  var Z = Math.round(((cols.z ? cols.z[i] : 0) + oz - so.offset.z) / so.scale.z);
43
44
  if (X > INT32_MAX || X < INT32_MIN || Y > INT32_MAX || Y < INT32_MIN || Z > INT32_MAX || Z < INT32_MIN) {
44
- throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "point " + i + " does not fit the scale and offset (int32 overflow); use a coarser scale");
45
+ throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "point " + ((first || 0) + i) + " does not fit the scale and offset (int32 overflow); use a coarser scale");
45
46
  }
46
47
  out.setInt32(p, X, LE); out.setInt32(p + 4, Y, LE); out.setInt32(p + 8, Z, LE);
47
48
  var wx = X * so.scale.x + so.offset.x, wy = Y * so.scale.y + so.offset.y, wz = Z * so.scale.z + so.offset.z;