las-laz-js 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;