las-laz-js 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/LICENSE-APACHE +51 -0
- package/NOTICE +14 -0
- package/README.md +71 -27
- package/package.json +5 -3
- package/src/LasReader.js +134 -42
- package/src/colour/colourize.js +35 -3
- package/src/filter/dedupe.js +31 -14
- package/src/filter/filters.js +14 -10
- package/src/format/LasVlr.js +8 -5
- package/src/index.js +3 -3
- package/src/io/NodeFileSource.js +24 -2
- package/src/laz/ArithmeticDecoder.js +136 -0
- package/src/laz/IntegerDecompressor.js +77 -0
- package/src/laz/LazVlr.js +65 -0
- package/src/laz/chunkTable.js +109 -0
- package/src/laz/decompressChunk.js +115 -0
- package/src/laz/models.js +102 -0
- package/src/laz/v2/byte.js +33 -0
- package/src/laz/v2/gpstime11.js +118 -0
- package/src/laz/v2/point10.js +139 -0
- package/src/laz/v2/rgb12.js +79 -0
- package/src/laz/v3/layered.js +145 -0
- package/src/laz/v3/point14.js +266 -0
- package/src/read/decodeRecords.js +32 -14
- package/src/read/windows.js +21 -0
- package/src/workers/workerProtocol.js +10 -7
- package/src/write/writeLas.js +46 -13
package/src/write/writeLas.js
CHANGED
|
@@ -30,10 +30,16 @@ function setU64(v, pos, value) {
|
|
|
30
30
|
|
|
31
31
|
function isGeographic(epsg) { return epsg >= 4000 && epsg < 5000; }
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// LAS 1.4 R15: point formats 6-10 carry the CRS as OGC WKT only; GeoTIFF
|
|
34
|
+
// keys belong to formats 0-5. The library does not turn an EPSG code into
|
|
35
|
+
// WKT (that needs a projection database), so an EPSG-only CRS is refused there.
|
|
36
|
+
function crsRecords(crs, fmt) {
|
|
34
37
|
var out = [];
|
|
35
38
|
if (!crs) return out;
|
|
36
|
-
if (crs.epsg) {
|
|
39
|
+
if (fmt >= 6 && !crs.wkt && crs.epsg) {
|
|
40
|
+
throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "point format " + fmt + " must carry its coordinate system as WKT (LAS 1.4); pass crs.wkt — an EPSG code alone is GeoTIFF keys, which formats 6-10 do not allow");
|
|
41
|
+
}
|
|
42
|
+
if (crs.epsg && fmt < 6) {
|
|
37
43
|
var geo = isGeographic(crs.epsg);
|
|
38
44
|
var keys = [[1024, geo ? 2 : 1], [1025, 1], [geo ? 2048 : 3072, crs.epsg]];
|
|
39
45
|
var b = new Uint8Array(8 + keys.length * 8);
|
|
@@ -73,7 +79,7 @@ function freshTotals() {
|
|
|
73
79
|
function headerBytes(s, so, totals) {
|
|
74
80
|
var o = s.opts;
|
|
75
81
|
var headerSize = HEADER_SIZE[s.version];
|
|
76
|
-
var vlrs = crsRecords(o.crs);
|
|
82
|
+
var vlrs = crsRecords(o.crs, s.fmt);
|
|
77
83
|
var vlrSize = vlrs.reduce(function (n, r) { return n + 54 + r.bytes.length; }, 0);
|
|
78
84
|
var out = new Uint8Array(headerSize + vlrSize);
|
|
79
85
|
var v = new DataView(out.buffer);
|
|
@@ -150,19 +156,38 @@ export async function writeLas(points, opts) {
|
|
|
150
156
|
if (o.scale && !o.offset) so = { scale: o.scale, offset: chooseScaleOffset(boundsOfPoints(points, o.localOrigin)).offset };
|
|
151
157
|
var len = POINT_FORMATS[s.fmt].size;
|
|
152
158
|
var totals = freshTotals();
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
// One buffer: records are encoded in place after the header, whose
|
|
160
|
+
// size does not depend on the totals; the header is filled in last
|
|
161
|
+
var headLength = headerBytes(s, so, totals).length;
|
|
162
|
+
var out = new Uint8Array(headLength + points.count * len);
|
|
163
|
+
encodeRecords(points, s.fmt, len, so, o.localOrigin, new DataView(out.buffer), headLength, totals);
|
|
155
164
|
totals.count = points.count;
|
|
156
|
-
|
|
157
|
-
var out = new Uint8Array(head.length + records.length);
|
|
158
|
-
out.set(head, 0);
|
|
159
|
-
out.set(records, head.length);
|
|
165
|
+
out.set(headerBytes(s, so, totals), 0);
|
|
160
166
|
return o.as === "blob" ? new Blob([out], { type: "application/octet-stream" }) : out;
|
|
161
167
|
}
|
|
162
168
|
|
|
169
|
+
// One positioned write for any sink: an OPFS scratch writer (writeAt),
|
|
170
|
+
// a Node FileHandle (write(buffer, offset, length, position), which may
|
|
171
|
+
// write short), or a plain { write(bytes, position) }
|
|
172
|
+
function positionedWrite(sink) {
|
|
173
|
+
if (typeof sink.writeAt === "function") return async function (bytes, pos) { await sink.writeAt(bytes, pos); };
|
|
174
|
+
if (typeof sink.fd === "number" && typeof sink.write === "function") {
|
|
175
|
+
return async function (bytes, pos) {
|
|
176
|
+
for (var done = 0; done < bytes.length;) {
|
|
177
|
+
var r = await sink.write(bytes, done, bytes.length - done, pos + done);
|
|
178
|
+
if (!(r && r.bytesWritten > 0)) throw new LasError(ERROR_CODES.TRUNCATED, "the file handle stopped writing", pos + done);
|
|
179
|
+
done += r.bytesWritten;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (typeof sink.write === "function") return async function (bytes, pos) { await sink.write(bytes, pos); };
|
|
184
|
+
throw new LasError(ERROR_CODES.INVALID_ARGUMENT, "createLasWriter needs a sink with writeAt(bytes, position) or write(bytes, position)");
|
|
185
|
+
}
|
|
186
|
+
|
|
163
187
|
/**
|
|
164
188
|
* Stream a LAS file to a sink. Header totals are patched on close().
|
|
165
|
-
* @param {
|
|
189
|
+
* @param {object} sink - an OPFS scratch writer (openScratchSyncWriter), a Node
|
|
190
|
+
* FileHandle, or any { write(bytes, position) }
|
|
166
191
|
* @param {object} opts - as writeLas; scale and offset are required (the bounds are not known yet)
|
|
167
192
|
* @returns {Promise<{writePoints: function(object): Promise, close: function(): Promise<{count:number}>}>}
|
|
168
193
|
*/
|
|
@@ -173,17 +198,25 @@ export async function createLasWriter(sink, opts) {
|
|
|
173
198
|
var so = { scale: opts.scale, offset: opts.offset };
|
|
174
199
|
var len = POINT_FORMATS[s.fmt].size;
|
|
175
200
|
var totals = freshTotals();
|
|
201
|
+
var put = positionedWrite(sink);
|
|
176
202
|
var start = headerBytes(s, so, totals).length;
|
|
177
|
-
|
|
203
|
+
var pending = [];
|
|
204
|
+
await put(headerBytes(s, so, totals), 0);
|
|
178
205
|
return {
|
|
179
206
|
writePoints: async function (points) {
|
|
180
207
|
var buf = new Uint8Array(points.count * len);
|
|
181
208
|
encodeRecords(points, s.fmt, len, so, opts.localOrigin, new DataView(buf.buffer), 0, totals);
|
|
182
|
-
|
|
209
|
+
// Reserve this batch's place BEFORE awaiting, so calls made without
|
|
210
|
+
// waiting for each other never land on the same bytes
|
|
211
|
+
var pos = start + totals.count * len;
|
|
183
212
|
totals.count += points.count;
|
|
213
|
+
var job = put(buf, pos);
|
|
214
|
+
pending.push(job);
|
|
215
|
+
await job;
|
|
184
216
|
},
|
|
185
217
|
close: async function () {
|
|
186
|
-
await
|
|
218
|
+
await Promise.all(pending);
|
|
219
|
+
await put(headerBytes(s, so, totals), 0);
|
|
187
220
|
return { count: totals.count };
|
|
188
221
|
}
|
|
189
222
|
};
|