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/v3/layered.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
62
|
-
|
|
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 =
|
|
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
|
|
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] =
|
|
91
|
-
this
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
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 =
|
|
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
|
+
};
|
package/src/laz/v3/point14.js
CHANGED
|
@@ -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 {
|
|
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
|
|
47
|
-
function
|
|
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
|
|
65
|
-
this.icDy = new
|
|
66
|
-
this.icZ = new
|
|
67
|
-
this.icIntensity = new
|
|
68
|
-
this.icAngle = new
|
|
69
|
-
this.icPsid = new
|
|
70
|
-
this.icGps = new
|
|
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
|
-
|
|
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] =
|
|
89
|
-
this.gLo[this.gNext] =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|