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.
- package/CHANGELOG.md +46 -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 +51 -18
- package/src/laz/compressChunk.js +107 -0
- package/src/laz/decompressChunk.js +40 -10
- package/src/laz/pool.js +77 -34
- 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/write/writeLas.js
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
// file handle) and patches the header totals on close. Both share
|
|
7
7
|
// one header builder, so they produce identical bytes.
|
|
8
8
|
//
|
|
9
|
+
// With compress: true both write LAZ (point formats 0-3 and 6-8): the same
|
|
10
|
+
// records, compressed chunkSize points at a time (LASzip's 50,000 by
|
|
11
|
+
// default), after a LASzip VLR; the point data is an 8-byte offset to
|
|
12
|
+
// the chunk table, the chunks, then the table — laid out as LAZperf
|
|
13
|
+
// (PDAL) writes it. Chunks compress independently, so with threads
|
|
14
|
+
// they compress on the LAZ worker pool (src/laz/pool.js), several to a
|
|
15
|
+
// task, and are written strictly in chunk order whatever order they
|
|
16
|
+
// finish in: the bytes are the same for any thread count.
|
|
17
|
+
//
|
|
9
18
|
// Ported from Kirra's LASWriter, with two changes: an invalid
|
|
10
19
|
// version / format pair is REFUSED (Kirra silently downgraded the
|
|
11
20
|
// format), and the coordinate system is written (GeoKey EPSG
|
|
@@ -16,9 +25,24 @@ import { LasError, ERROR_CODES } from "../errors.js";
|
|
|
16
25
|
import { POINT_FORMATS } from "../format/PointFormats.js";
|
|
17
26
|
import { chooseScaleOffset } from "./scaleOffset.js";
|
|
18
27
|
import { encodeRecords } from "./encodeRecords.js";
|
|
28
|
+
import { lazVlrFor, LASZIP_USER_ID, LASZIP_RECORD_ID } from "../laz/LazVlr.js";
|
|
29
|
+
import { createChunkCompressor } from "../laz/compressChunk.js";
|
|
30
|
+
import { encodeChunkTable } from "../laz/chunkTable.js";
|
|
31
|
+
import { createLazPool, defaultThreads } from "../laz/pool.js";
|
|
19
32
|
|
|
20
33
|
var LE = true;
|
|
21
34
|
var HEADER_SIZE = { "1.0": 227, "1.1": 227, "1.2": 227, "1.3": 235, "1.4": 375 };
|
|
35
|
+
var DEFAULT_CHUNK_SIZE = 50000;
|
|
36
|
+
// 0xFFFFFFFF in the VLR's chunk size field means variable-size chunks
|
|
37
|
+
var MAX_CHUNK_SIZE = 4294967294;
|
|
38
|
+
// threads, automatic: a pool once a write has this many chunks (as openLas),
|
|
39
|
+
// of at most AUTO_MAX_THREADS workers — gains flatten there, while the records
|
|
40
|
+
// in flight grow with every worker (an explicit threads is honoured as given)
|
|
41
|
+
var AUTO_MIN_CHUNKS = 4;
|
|
42
|
+
var AUTO_MAX_THREADS = 4;
|
|
43
|
+
// chunks per pool task, and tasks in flight per worker (one working, one queued)
|
|
44
|
+
var CHUNKS_PER_TASK = 4;
|
|
45
|
+
var TASKS_PER_WORKER = 2;
|
|
22
46
|
|
|
23
47
|
function ascii(v, pos, text, max) {
|
|
24
48
|
for (var i = 0; i < max; i++) v.setUint8(pos + i, i < text.length ? text.charCodeAt(i) & 0x7F : 0); // bits-ok
|
|
@@ -68,7 +92,19 @@ function settle(points, opts) {
|
|
|
68
92
|
var minor = Number(version.split(".")[1]);
|
|
69
93
|
if (fmt >= 6 && minor < 4) throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "point format " + fmt + " needs LAS 1.4 (asked for " + version + ")");
|
|
70
94
|
if ((fmt === 4 || fmt === 5) && minor < 3) throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "point format " + fmt + " needs LAS 1.3 or later (asked for " + version + ")");
|
|
71
|
-
return { version: version, minor: minor, fmt: fmt, opts: opts };
|
|
95
|
+
return { version: version, minor: minor, fmt: fmt, opts: opts, laz: lazSetup(fmt, opts) };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// compress: the LASzip VLR and the chunk compressor. chunkSize is ignored without compress.
|
|
99
|
+
function lazSetup(fmt, opts) {
|
|
100
|
+
if (!opts.compress) return null;
|
|
101
|
+
var chunkSize = opts.chunkSize == null ? DEFAULT_CHUNK_SIZE : opts.chunkSize;
|
|
102
|
+
if (!Number.isInteger(chunkSize) || chunkSize < 1 || chunkSize > MAX_CHUNK_SIZE) {
|
|
103
|
+
throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "chunkSize must be a whole number of points from 1 to " + MAX_CHUNK_SIZE + " (got " + chunkSize + ")");
|
|
104
|
+
}
|
|
105
|
+
var len = POINT_FORMATS[fmt].size;
|
|
106
|
+
var made = lazVlrFor(fmt, len, chunkSize); // UNSUPPORTED for formats 4, 5, 9, 10
|
|
107
|
+
return { chunkSize: chunkSize, vlr: made.vlr, vlrBytes: made.bytes, compress: createChunkCompressor(made.vlr, len) };
|
|
72
108
|
}
|
|
73
109
|
|
|
74
110
|
function freshTotals() {
|
|
@@ -80,6 +116,7 @@ function headerBytes(s, so, totals) {
|
|
|
80
116
|
var o = s.opts;
|
|
81
117
|
var headerSize = HEADER_SIZE[s.version];
|
|
82
118
|
var vlrs = crsRecords(o.crs, s.fmt);
|
|
119
|
+
if (s.laz) vlrs.push({ userId: LASZIP_USER_ID, recordId: LASZIP_RECORD_ID, description: "http://laszip.org", bytes: s.laz.vlrBytes });
|
|
83
120
|
var vlrSize = vlrs.reduce(function (n, r) { return n + 54 + r.bytes.length; }, 0);
|
|
84
121
|
var out = new Uint8Array(headerSize + vlrSize);
|
|
85
122
|
var v = new DataView(out.buffer);
|
|
@@ -101,7 +138,7 @@ function headerBytes(s, so, totals) {
|
|
|
101
138
|
v.setUint16(94, headerSize, LE);
|
|
102
139
|
v.setUint32(96, headerSize + vlrSize, LE);
|
|
103
140
|
v.setUint32(100, vlrs.length, LE);
|
|
104
|
-
v.setUint8(104, s.fmt);
|
|
141
|
+
v.setUint8(104, s.laz ? s.fmt + 128 : s.fmt); // bit 7: compressed
|
|
105
142
|
v.setUint16(105, fmt.size, LE);
|
|
106
143
|
var legacyOk = s.fmt < 6 && totals.count < 4294967296;
|
|
107
144
|
v.setUint32(107, legacyOk ? totals.count : 0, LE);
|
|
@@ -144,9 +181,11 @@ function boundsOfPoints(points, origin) {
|
|
|
144
181
|
}
|
|
145
182
|
|
|
146
183
|
/**
|
|
147
|
-
* Write a whole LAS file.
|
|
184
|
+
* Write a whole LAS file, or LAZ with compress: true (point formats 0-3 and 6-8).
|
|
148
185
|
* @param {object} points - PointColumns (world coordinates, or relative to opts.localOrigin)
|
|
149
|
-
* @param {object} [opts] - version, pointFormat, scale, offset, crs {epsg|wkt}, software, systemId, date, localOrigin, as ("bytes" | "blob")
|
|
186
|
+
* @param {object} [opts] - version, pointFormat, scale, offset, crs {epsg|wkt}, software, systemId, date, localOrigin, as ("bytes" | "blob"),
|
|
187
|
+
* compress (write LAZ), chunkSize (points per LAZ chunk, default 50000; only with compress),
|
|
188
|
+
* threads (LAZ compression workers: default automatic, from 4 chunks and at most 4 workers; 1 = this thread; n = n workers)
|
|
150
189
|
* @returns {Promise<Uint8Array|Blob>}
|
|
151
190
|
*/
|
|
152
191
|
export async function writeLas(points, opts) {
|
|
@@ -156,6 +195,10 @@ export async function writeLas(points, opts) {
|
|
|
156
195
|
if (o.scale && !o.offset) so = { scale: o.scale, offset: chooseScaleOffset(boundsOfPoints(points, o.localOrigin)).offset };
|
|
157
196
|
var len = POINT_FORMATS[s.fmt].size;
|
|
158
197
|
var totals = freshTotals();
|
|
198
|
+
if (s.laz) {
|
|
199
|
+
var parts = await lazParts(points, s, so, len, totals);
|
|
200
|
+
return o.as === "blob" ? new Blob(parts, { type: "application/octet-stream" }) : joinParts(parts);
|
|
201
|
+
}
|
|
159
202
|
// One buffer: records are encoded in place after the header, whose
|
|
160
203
|
// size does not depend on the totals; the header is filled in last
|
|
161
204
|
var headLength = headerBytes(s, so, totals).length;
|
|
@@ -166,6 +209,181 @@ export async function writeLas(points, opts) {
|
|
|
166
209
|
return o.as === "blob" ? new Blob([out], { type: "application/octet-stream" }) : out;
|
|
167
210
|
}
|
|
168
211
|
|
|
212
|
+
// Columns a..b of a PointColumns (views where the columns are typed arrays)
|
|
213
|
+
function rangeOf(cols, a, b) {
|
|
214
|
+
if (a === 0 && b === cols.count) return cols;
|
|
215
|
+
var out = { count: b - a };
|
|
216
|
+
for (var k in cols) {
|
|
217
|
+
var c = cols[k];
|
|
218
|
+
if (k !== "count" && c && typeof c.length === "number") out[k] = typeof c.subarray === "function" ? c.subarray(a, b) : c.slice(a, b);
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function joinParts(parts) {
|
|
224
|
+
var total = parts.reduce(function (n, p) { return n + p.length; }, 0);
|
|
225
|
+
var out = new Uint8Array(total), at = 0;
|
|
226
|
+
parts.forEach(function (p) { out.set(p, at); at += p.length; });
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// The i64 offset to the chunk table (an absolute file position), as two u32 halves
|
|
231
|
+
function tableOffsetBytes(tablePos) {
|
|
232
|
+
var b = new Uint8Array(8);
|
|
233
|
+
setU64(new DataView(b.buffer), 0, tablePos);
|
|
234
|
+
return b;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// threads as openLas: 0 = automatic
|
|
238
|
+
function threadsOf(opts) { return opts.threads > 0 ? Math.floor(opts.threads) : 0; }
|
|
239
|
+
|
|
240
|
+
// A pool of up to `most` workers (one per chunk at most, when the count is known), or null;
|
|
241
|
+
// automatic: at most AUTO_MAX_THREADS
|
|
242
|
+
async function startPool(opts, most) {
|
|
243
|
+
var n = Math.min(threadsOf(opts) || Math.min(AUTO_MAX_THREADS, await defaultThreads()), most);
|
|
244
|
+
if (n < 2) return null;
|
|
245
|
+
return opts._createPool ? opts._createPool(n) : createLazPool(n);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// The chunk loop both LAZ writers share. Records fill a buffer; each full
|
|
249
|
+
// chunk is compressed on this thread, until the poolAt-th full chunk calls
|
|
250
|
+
// makePool — from then on chunks go to the pool's workers CHUNKS_PER_TASK
|
|
251
|
+
// at a time (or, with no pool, still to this thread). place(bytes) gets
|
|
252
|
+
// each task's chunks, back to back, strictly in chunk order: a task that
|
|
253
|
+
// finishes early waits for the ones before it. Records are encoded before
|
|
254
|
+
// write()'s first await, so calls made without waiting keep their
|
|
255
|
+
// order. write() returns once fewer than TASKS_PER_WORKER tasks per
|
|
256
|
+
// worker are in flight (on this thread, once its chunks are placed), so
|
|
257
|
+
// memory is bounded. Any error — on a worker, on this thread, in place()
|
|
258
|
+
// or reported through fail(err) — stops the loop for good and closes the
|
|
259
|
+
// pool, as finish() does once it has placed the last chunk; after
|
|
260
|
+
// finish() is called, write() refuses more points (ABORTED).
|
|
261
|
+
function lazChunks(s, so, len, totals, place, poolAt, makePool) {
|
|
262
|
+
var size = s.laz.chunkSize;
|
|
263
|
+
var buf = new Uint8Array(0), view = null, filled = 0, full = 0;
|
|
264
|
+
var pooled = null, closing = false, failure = null, ended = false;
|
|
265
|
+
var order = Promise.resolve(), inflight = [], chunks = [];
|
|
266
|
+
|
|
267
|
+
// grow the buffer to hold n records (doubling; one chunk on this thread, a task's on the pool)
|
|
268
|
+
function room(n) {
|
|
269
|
+
if (buf.length >= n * len) return;
|
|
270
|
+
var most = pooled ? CHUNKS_PER_TASK * size : size;
|
|
271
|
+
var grown = new Uint8Array(Math.max(n, Math.min(most, 2 * buf.length / len)) * len);
|
|
272
|
+
grown.set(buf.subarray(0, filled * len));
|
|
273
|
+
buf = grown;
|
|
274
|
+
view = new DataView(buf.buffer);
|
|
275
|
+
}
|
|
276
|
+
function shut() {
|
|
277
|
+
if (!pooled || closing) return;
|
|
278
|
+
closing = true;
|
|
279
|
+
pooled.then(function (pool) { if (pool) pool.close(); });
|
|
280
|
+
}
|
|
281
|
+
function stop(err) {
|
|
282
|
+
if (!failure) failure = err;
|
|
283
|
+
shut();
|
|
284
|
+
}
|
|
285
|
+
function refuse() {
|
|
286
|
+
if (failure) throw failure;
|
|
287
|
+
if (ended) throw new LasError(ERROR_CODES.ABORTED, "the LAZ writer is closed");
|
|
288
|
+
}
|
|
289
|
+
function onThisThread(records, counts) {
|
|
290
|
+
var parts = [], p = 0;
|
|
291
|
+
counts.forEach(function (n) { parts.push(s.laz.compress(records, p, n)); p += n * len; });
|
|
292
|
+
return { bytes: parts.length === 1 ? parts[0] : joinParts(parts), sizes: parts.map(function (c) { return c.length; }) };
|
|
293
|
+
}
|
|
294
|
+
function submit(records, counts) {
|
|
295
|
+
// on this thread now (its buffer is reused); on the pool from records the task then owns.
|
|
296
|
+
// An error on this thread rejects the task, as a worker's does, so it stops the loop below
|
|
297
|
+
var task;
|
|
298
|
+
if (!pooled) {
|
|
299
|
+
try { task = Promise.resolve(onThisThread(records, counts)); } catch (err) { task = Promise.reject(err); }
|
|
300
|
+
} else {
|
|
301
|
+
task = pooled.then(function (pool) {
|
|
302
|
+
return pool ? pool.compress({ vlr: s.laz.vlr, recordLength: len, records: records.buffer, counts: counts }) : onThisThread(records, counts);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
task.catch(function () {}); // tasks after a failure are never waited for
|
|
306
|
+
var placed = order.then(function () { return task; }).then(function (r) {
|
|
307
|
+
r.sizes.forEach(function (n, i) { chunks.push({ pointCount: counts[i], byteCount: n }); });
|
|
308
|
+
return place(r.bytes);
|
|
309
|
+
});
|
|
310
|
+
placed.catch(stop);
|
|
311
|
+
order = placed;
|
|
312
|
+
inflight.push(placed);
|
|
313
|
+
}
|
|
314
|
+
// everything in the buffer as one task: full chunks, then a partial one at the end
|
|
315
|
+
function submitBuffer() {
|
|
316
|
+
var counts = [];
|
|
317
|
+
for (var k = 0; k < filled; k += size) counts.push(Math.min(size, filled - k));
|
|
318
|
+
var records = buf;
|
|
319
|
+
buf = new Uint8Array(0);
|
|
320
|
+
view = null;
|
|
321
|
+
filled = 0;
|
|
322
|
+
submit(records, counts);
|
|
323
|
+
}
|
|
324
|
+
function chunkFull() {
|
|
325
|
+
full++;
|
|
326
|
+
if (!pooled && full >= poolAt) pooled = makePool().catch(function () { return null; });
|
|
327
|
+
if (!pooled) { submit(buf, [size]); filled = 0; }
|
|
328
|
+
else if (filled === CHUNKS_PER_TASK * size) submitBuffer();
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
// points, the first of which is point `first` of the caller's (for error messages)
|
|
332
|
+
write: async function (points, first) {
|
|
333
|
+
refuse();
|
|
334
|
+
try {
|
|
335
|
+
for (var a = 0; a < points.count;) {
|
|
336
|
+
var n = Math.min(size - filled % size, points.count - a);
|
|
337
|
+
room(filled + n);
|
|
338
|
+
encodeRecords(rangeOf(points, a, a + n), s.fmt, len, so, s.opts.localOrigin, view, filled * len, totals, first + a);
|
|
339
|
+
filled += n;
|
|
340
|
+
a += n;
|
|
341
|
+
totals.count += n;
|
|
342
|
+
if (filled % size === 0) chunkFull();
|
|
343
|
+
}
|
|
344
|
+
} catch (err) {
|
|
345
|
+
stop(err);
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
var pool = pooled ? await pooled : null;
|
|
349
|
+
var limit = pool ? TASKS_PER_WORKER * pool.size : 0;
|
|
350
|
+
while (inflight.length && inflight.length >= limit) await inflight.shift();
|
|
351
|
+
},
|
|
352
|
+
finish: async function () {
|
|
353
|
+
refuse();
|
|
354
|
+
ended = true;
|
|
355
|
+
try {
|
|
356
|
+
if (filled) submitBuffer();
|
|
357
|
+
await order;
|
|
358
|
+
} catch (err) {
|
|
359
|
+
stop(err);
|
|
360
|
+
throw err;
|
|
361
|
+
} finally { shut(); }
|
|
362
|
+
return chunks;
|
|
363
|
+
},
|
|
364
|
+
fail: stop
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// A whole LAZ file as parts: header + VLRs, table offset, chunks, chunk table.
|
|
369
|
+
// The points go through the chunk loop a task's worth at a time, so only
|
|
370
|
+
// the tasks in flight hold encoded records.
|
|
371
|
+
async function lazParts(points, s, so, len, totals) {
|
|
372
|
+
var size = s.laz.chunkSize, count = Math.ceil(points.count / size), t = threadsOf(s.opts);
|
|
373
|
+
var parts = [null, null];
|
|
374
|
+
var poolAt = t === 1 || (!t && count < AUTO_MIN_CHUNKS) ? Infinity : 1;
|
|
375
|
+
var loop = lazChunks(s, so, len, totals, function (bytes) { parts.push(bytes); }, poolAt, function () { return startPool(s.opts, count); });
|
|
376
|
+
var step = CHUNKS_PER_TASK * size;
|
|
377
|
+
for (var a = 0; a < points.count; a += step) await loop.write(rangeOf(points, a, Math.min(points.count, a + step)), a);
|
|
378
|
+
var chunks = await loop.finish();
|
|
379
|
+
// the header's size does not depend on the totals
|
|
380
|
+
var tablePos = chunks.reduce(function (n, c) { return n + c.byteCount; }, headerBytes(s, so, totals).length + 8);
|
|
381
|
+
parts.push(encodeChunkTable(chunks, false));
|
|
382
|
+
parts[0] = headerBytes(s, so, totals);
|
|
383
|
+
parts[1] = tableOffsetBytes(tablePos);
|
|
384
|
+
return parts;
|
|
385
|
+
}
|
|
386
|
+
|
|
169
387
|
// One positioned write for any sink: an OPFS scratch writer (writeAt),
|
|
170
388
|
// a Node FileHandle (write(buffer, offset, length, position), which may
|
|
171
389
|
// write short), or a plain { write(bytes, position) }
|
|
@@ -184,11 +402,45 @@ function positionedWrite(sink) {
|
|
|
184
402
|
throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "createLasWriter needs a sink with writeAt(bytes, position) or write(bytes, position)");
|
|
185
403
|
}
|
|
186
404
|
|
|
405
|
+
// The streaming LAZ writer: the chunk loop, placing chunks at the running
|
|
406
|
+
// position. close() places the partial chunk, writes the chunk table after
|
|
407
|
+
// it, then patches the table offset and the header totals. A failed write
|
|
408
|
+
// there fails the loop too, so later calls keep rejecting (ruling R11); once
|
|
409
|
+
// close() has begun, writePoints and close reject (ABORTED).
|
|
410
|
+
async function createLazWriter(s, so, len, totals, put, start) {
|
|
411
|
+
var pos = start + 8, t = threadsOf(s.opts);
|
|
412
|
+
// until close(), the table offset is LASzip's "not written yet" (-1)
|
|
413
|
+
await put(joinParts([headerBytes(s, so, totals), new Uint8Array(8).fill(255)]), 0);
|
|
414
|
+
var poolAt = t === 1 ? Infinity : t ? 1 : AUTO_MIN_CHUNKS;
|
|
415
|
+
var loop = lazChunks(s, so, len, totals, function (bytes) {
|
|
416
|
+
var at = pos;
|
|
417
|
+
pos += bytes.length;
|
|
418
|
+
return put(bytes, at);
|
|
419
|
+
}, poolAt, function () { return startPool(s.opts, Infinity); });
|
|
420
|
+
return {
|
|
421
|
+
writePoints: function (points) { return loop.write(points, 0); },
|
|
422
|
+
close: async function () {
|
|
423
|
+
var chunks = await loop.finish();
|
|
424
|
+
var tablePos = pos;
|
|
425
|
+
try {
|
|
426
|
+
await put(encodeChunkTable(chunks, false), tablePos);
|
|
427
|
+
await put(tableOffsetBytes(tablePos), start);
|
|
428
|
+
await put(headerBytes(s, so, totals), 0);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
loop.fail(err);
|
|
431
|
+
throw err;
|
|
432
|
+
}
|
|
433
|
+
return { count: totals.count };
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
187
438
|
/**
|
|
188
|
-
* Stream a LAS file to a sink. Header totals are patched on close().
|
|
439
|
+
* Stream a LAS (or, with compress: true, LAZ) file to a sink. Header totals are patched on close().
|
|
189
440
|
* @param {object} sink - an OPFS scratch writer (openScratchSyncWriter), a Node
|
|
190
441
|
* FileHandle, or any { write(bytes, position) }
|
|
191
|
-
* @param {object} opts - as writeLas; scale and offset are required (the bounds are not known yet)
|
|
442
|
+
* @param {object} opts - as writeLas; scale and offset are required (the bounds are not known yet).
|
|
443
|
+
* With compress and threads automatic, the pool (at most 4 workers) starts at the 4th full chunk
|
|
192
444
|
* @returns {Promise<{writePoints: function(object): Promise, close: function(): Promise<{count:number}>}>}
|
|
193
445
|
*/
|
|
194
446
|
export async function createLasWriter(sink, opts) {
|
|
@@ -200,6 +452,7 @@ export async function createLasWriter(sink, opts) {
|
|
|
200
452
|
var totals = freshTotals();
|
|
201
453
|
var put = positionedWrite(sink);
|
|
202
454
|
var start = headerBytes(s, so, totals).length;
|
|
455
|
+
if (s.laz) return createLazWriter(s, so, len, totals, put, start);
|
|
203
456
|
var pending = [];
|
|
204
457
|
await put(headerBytes(s, so, totals), 0);
|
|
205
458
|
return {
|