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.
@@ -30,10 +30,16 @@ function setU64(v, pos, value) {
30
30
 
31
31
  function isGeographic(epsg) { return epsg >= 4000 && epsg < 5000; }
32
32
 
33
- function crsRecords(crs) {
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
- var records = new Uint8Array(points.count * len);
154
- encodeRecords(points, s.fmt, len, so, o.localOrigin, new DataView(records.buffer), 0, totals);
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
- var head = headerBytes(s, so, totals);
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 {{write: function(Uint8Array, number): (void|Promise)}} sink
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
- await sink.write(headerBytes(s, so, totals), 0);
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
- await sink.write(buf, start + totals.count * len);
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 sink.write(headerBytes(s, so, totals), 0);
218
+ await Promise.all(pending);
219
+ await put(headerBytes(s, so, totals), 0);
187
220
  return { count: totals.count };
188
221
  }
189
222
  };