las-laz-js 0.4.0 → 0.5.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.
@@ -0,0 +1,209 @@
1
+ // src/ops/columns.js
2
+ //==========================================================
3
+ // Point columns and selections, the two shapes every operation in
4
+ // src/ops takes. Columns are the object readPoints returns (or any
5
+ // caller's object of the same shape): { count, <name>: TypedArray }.
6
+ // A property is a column when it is a typed array of length
7
+ // count * k, k a whole number >= 1 (k = 1 for x, classification;
8
+ // k = 3 for an interleaved positions or rgb). A selection is a
9
+ // Uint32Array of point indices, strictly ascending, each < count.
10
+ //==========================================================
11
+
12
+ import { LasError, ERROR_CODES } from "../errors.js";
13
+
14
+ var MAX_INDEX = 4294967295;
15
+
16
+ function bad(message) { return new LasError(ERROR_CODES.INVALID_ARGUMENT, message); }
17
+
18
+ function isTyped(v) { return ArrayBuffer.isView(v) && !(v instanceof DataView); }
19
+
20
+ /**
21
+ * Check cols.count is a whole number >= 0 and return it.
22
+ * @param {object} cols - point columns.
23
+ * @returns {number}
24
+ */
25
+ export function countOf(cols) {
26
+ if (cols === null || typeof cols !== "object") throw bad("columns must be an object { count, <name>: TypedArray }");
27
+ var n = cols.count;
28
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 0) throw bad("count " + String(n) + " is not a whole number ≥ 0");
29
+ return n;
30
+ }
31
+
32
+ /**
33
+ * The columns of a point-column object: own typed-array properties of
34
+ * length count * k. With count 0, every typed array of length 0 is a
35
+ * k = 1 column. Everything else (numbers, bounds, a typed array of any
36
+ * other length) is skipped silently.
37
+ * @param {object} cols
38
+ * @returns {Array<{name: string, k: number}>}
39
+ */
40
+ export function columnsOf(cols) {
41
+ var n = countOf(cols);
42
+ var out = [];
43
+ Object.keys(cols).forEach(function (name) {
44
+ var a = cols[name];
45
+ if (!isTyped(a)) return;
46
+ if (n === 0) {
47
+ if (a.length === 0) out.push({ name: name, k: 1 });
48
+ return;
49
+ }
50
+ var k = a.length / n;
51
+ if (Number.isInteger(k) && k >= 1) out.push({ name: name, k: k });
52
+ });
53
+ return out;
54
+ }
55
+
56
+ /**
57
+ * Validate a selection and return it as a Uint32Array. Accepts a
58
+ * Uint32Array or a plain Array of whole numbers; it must be strictly
59
+ * ascending with every index below count.
60
+ * @param {Uint32Array|number[]} sel
61
+ * @param {number} count - points in the columns (Infinity: no upper limit).
62
+ * @param {string} [name] - how the message names it (default "selection").
63
+ * @returns {Uint32Array}
64
+ */
65
+ export function checkSelection(sel, count, name) {
66
+ name = name || "selection";
67
+ var isU32 = sel instanceof Uint32Array;
68
+ if (!isU32 && !Array.isArray(sel)) throw bad(name + " must be a Uint32Array or an Array of whole numbers");
69
+ var prev = -1;
70
+ for (var i = 0; i < sel.length; i++) {
71
+ var v = sel[i];
72
+ var at = name + "[" + i + "] = " + v;
73
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 0) throw bad(at + " is not a whole number ≥ 0");
74
+ if (v > MAX_INDEX) throw bad(at + " is not below " + (MAX_INDEX + 1));
75
+ if (v <= prev) throw bad(at + " is not above " + name + "[" + (i - 1) + "] = " + prev);
76
+ if (v >= count) throw bad(at + " is not below count " + count);
77
+ prev = v;
78
+ }
79
+ return isU32 ? sel : Uint32Array.from(sel);
80
+ }
81
+
82
+ /**
83
+ * The k = 1 column called name, or an error naming the function.
84
+ * @param {object} cols
85
+ * @param {string} name
86
+ * @param {string} fn - the calling function, for the message.
87
+ * @returns {TypedArray}
88
+ */
89
+ export function needColumn(cols, name, fn) {
90
+ var n = countOf(cols);
91
+ var a = cols[name];
92
+ if (!isTyped(a) || a.length !== n) throw bad(fn + " needs a " + name + " column");
93
+ return a;
94
+ }
95
+
96
+ /**
97
+ * A new typed array of the same type as a (a Node Buffer gives a Uint8Array).
98
+ * Internal; not exported from the package entry.
99
+ * @param {TypedArray} a
100
+ * @param {number} length
101
+ * @returns {TypedArray}
102
+ */
103
+ export function sameType(a, length) {
104
+ var Ctor = globalThis[Object.prototype.toString.call(a).slice(8, -1)];
105
+ return new Ctor(length);
106
+ }
107
+
108
+ /**
109
+ * dst[i] = src[sel[i]] for each i (k values each; internal, not exported from the package entry).
110
+ * dst may be src: i <= sel[i], so ascending writes never overwrite a value still to be read.
111
+ * @param {TypedArray} src
112
+ * @param {TypedArray} dst
113
+ * @param {number} k
114
+ * @param {Uint32Array} sel - strictly ascending.
115
+ */
116
+ export function gather(src, dst, k, sel) {
117
+ var n = sel.length;
118
+ if (k === 1) {
119
+ for (var i = 0; i < n; i++) dst[i] = src[sel[i]];
120
+ return;
121
+ }
122
+ for (var p = 0; p < n; p++) {
123
+ var from = sel[p] * k;
124
+ var to = p * k;
125
+ for (var j = 0; j < k; j++) dst[to + j] = src[from + j];
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Throw INVALID_ARGUMENT when two of the named columns overlap in memory (internal; not exported
131
+ * from the package entry). In-place compaction writes into every column's buffer, so two columns
132
+ * whose byte ranges overlap would corrupt each other: refuse before any write.
133
+ * @param {object} cols
134
+ * @param {Array<{name: string}>} list - the columns to check, by name.
135
+ */
136
+ export function checkNoSharedMemory(cols, list) {
137
+ for (var i = 0; i < list.length; i++) {
138
+ var a = cols[list[i].name];
139
+ if (a.byteLength === 0) continue;
140
+ for (var j = i + 1; j < list.length; j++) {
141
+ var b = cols[list[j].name];
142
+ if (b.byteLength === 0 || a.buffer !== b.buffer) continue;
143
+ if (a.byteOffset < b.byteOffset + b.byteLength && b.byteOffset < a.byteOffset + a.byteLength) {
144
+ throw bad("inPlace: columns \"" + list[i].name + "\" and \"" + list[j].name + "\" share memory");
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ var FLOATS = { Float64Array: 8, Float32Array: 4 };
151
+ function has(o, k) { return Object.prototype.hasOwnProperty.call(o, k); }
152
+
153
+ var INTS = {
154
+ Int8Array: [1, true], Int16Array: [2, true], Int32Array: [4, true],
155
+ Uint8Array: [1, false], Uint8ClampedArray: [1, false], Uint16Array: [2, false], Uint32Array: [4, false]
156
+ };
157
+ var BIGS = { BigInt64Array: true, BigUint64Array: true };
158
+
159
+ /**
160
+ * The typed-array type a merge of columns of these types is stored in
161
+ * (internal; not exported from the package entry). Float64 beats Float32
162
+ * beats any integer type, except that Float32 with a 32-bit integer type
163
+ * (Int32, Uint32) gives Float64, since Float32 holds integers exactly only
164
+ * to 2^24. Among integers the widest wins, and mixed
165
+ * signedness is allowed only when the widest is signed and strictly
166
+ * wider than every unsigned one (Uint8 + Int16 gives Int16); Int16 +
167
+ * Uint16 and Int8 + Uint16 throw, because the unsigned values would wrap.
168
+ * Uint8ClampedArray counts as Uint8Array. A BigInt array only merges
169
+ * with its own type.
170
+ * @param {Function[]} types - typed-array constructors, at least one.
171
+ * @param {string} name - the column, for the message.
172
+ * @returns {Function}
173
+ */
174
+ export function widestType(types, name) {
175
+ var label = "column \"" + name + "\"";
176
+ if (!Array.isArray(types) || types.length === 0) throw bad(label + " has no types to merge");
177
+ var names = types.map(function (t) {
178
+ var n = t && t.name;
179
+ if (typeof n !== "string" || (!has(FLOATS, n) && !has(INTS, n) && !has(BIGS, n))) throw bad(label + " has a type that is not a typed-array constructor");
180
+ return n === "Uint8ClampedArray" ? "Uint8Array" : n;
181
+ });
182
+ var first = names[0];
183
+ if (has(BIGS, first)) {
184
+ names.forEach(function (n) {
185
+ if (n !== first) throw bad(label + " cannot merge " + first + " with " + n);
186
+ });
187
+ return globalThis[first];
188
+ }
189
+ names.forEach(function (n) {
190
+ if (has(BIGS, n)) throw bad(label + " cannot merge " + n + " with " + first);
191
+ });
192
+ if (names.indexOf("Float64Array") >= 0) return Float64Array;
193
+ if (names.indexOf("Float32Array") >= 0) {
194
+ // Float32 holds integers exactly only to 2^24: 32-bit integers (an index column) go to Float64
195
+ if (names.indexOf("Int32Array") >= 0 || names.indexOf("Uint32Array") >= 0) return Float64Array;
196
+ return Float32Array;
197
+ }
198
+ var maxS = 0;
199
+ var maxU = 0;
200
+ names.forEach(function (n) {
201
+ var b = INTS[n][0];
202
+ if (INTS[n][1]) { if (b > maxS) maxS = b; } else if (b > maxU) maxU = b;
203
+ });
204
+ if (maxS > 0 && maxU > 0 && maxS <= maxU) {
205
+ throw bad(label + " cannot merge signed Int" + maxS * 8 + "Array with unsigned Uint" + maxU * 8 + "Array (values would wrap)");
206
+ }
207
+ if (maxS > 0) return globalThis["Int" + maxS * 8 + "Array"];
208
+ return globalThis["Uint" + maxU * 8 + "Array"];
209
+ }
@@ -0,0 +1,81 @@
1
+ // src/ops/interleave.js
2
+ //==========================================================
3
+ // Interleaved positions for a renderer: x, y[, z] packed per point
4
+ // into one typed array (Kirra uploads these to Three.js). Values
5
+ // are written in Float64 and shifted by an optional origin before
6
+ // they are stored, so large world coordinates keep their decimals
7
+ // in a Float32 buffer.
8
+ //==========================================================
9
+
10
+ import { LasError, ERROR_CODES } from "../errors.js";
11
+ import { countOf, needColumn } from "./columns.js";
12
+
13
+ function bad(message) { return new LasError(ERROR_CODES.INVALID_ARGUMENT, message); }
14
+
15
+ // The origin axis as a number to subtract, or 0 when it is absent or not finite.
16
+ function shiftOf(origin, axis) {
17
+ var v = origin[axis];
18
+ if (v === undefined || v === null) return 0;
19
+ if (typeof v !== "number") throw bad("origin." + axis + " must be a number");
20
+ return Number.isFinite(v) ? v : 0;
21
+ }
22
+
23
+ /**
24
+ * Pack point positions as interleaved values, x y z (stride 3) or x y
25
+ * (stride 2). Each value is shifted by the origin in Float64, then
26
+ * stored. An origin axis that is absent or not finite is not shifted,
27
+ * so { x, y } keeps world Z.
28
+ * @param {object} cols - point columns with x and y (and z for "xyz").
29
+ * @param {{layout?: "xyz"|"xy", into?: Float32Array|Float64Array, at?: number,
30
+ * origin?: {x?: number, y?: number, z?: number}, float32?: boolean}} [options]
31
+ * layout: default "xyz". into: a caller's buffer, written from element at × stride
32
+ * (default: a new Float32Array when float32 is true or omitted, else a Float64Array).
33
+ * at: whole number ≥ 0, default 0.
34
+ * @returns {Float32Array|Float64Array} into, or the new array.
35
+ * @throws {LasError} INVALID_ARGUMENT when an argument is wrong or into is too short.
36
+ */
37
+ export function interleavePositions(cols, options) {
38
+ var opt = options === undefined ? {} : options;
39
+ if (opt === null || typeof opt !== "object") throw bad("options must be an object");
40
+ var n = countOf(cols);
41
+ var layout = opt.layout === undefined ? "xyz" : opt.layout;
42
+ if (layout !== "xyz" && layout !== "xy") throw bad("layout " + String(layout) + " is not \"xyz\" or \"xy\"");
43
+ var stride = layout === "xyz" ? 3 : 2;
44
+ var at = opt.at === undefined ? 0 : opt.at;
45
+ if (typeof at !== "number" || !Number.isInteger(at) || at < 0) throw bad("at " + String(at) + " is not a whole number ≥ 0");
46
+ var origin = opt.origin === undefined || opt.origin === null ? {} : opt.origin;
47
+ if (typeof origin !== "object") throw bad("origin must be an object { x, y, z }");
48
+ var sx = shiftOf(origin, "x");
49
+ var sy = shiftOf(origin, "y");
50
+ var sz = layout === "xyz" ? shiftOf(origin, "z") : 0;
51
+
52
+ var x = needColumn(cols, "x", "interleavePositions");
53
+ var y = needColumn(cols, "y", "interleavePositions");
54
+ var z = layout === "xyz" ? needColumn(cols, "z", "interleavePositions") : null;
55
+
56
+ var into = opt.into;
57
+ if (into === undefined) {
58
+ var f32 = opt.float32 === undefined ? true : opt.float32;
59
+ into = f32 ? new Float32Array(n * stride) : new Float64Array(n * stride);
60
+ } else if (!(into instanceof Float32Array) && !(into instanceof Float64Array)) {
61
+ throw bad("into must be a Float32Array or a Float64Array");
62
+ }
63
+ if ((at + n) * stride > into.length) throw bad("into is too short: needs " + (at + n) * stride + " elements from " + at * stride + ", has " + into.length);
64
+
65
+ var base = at * stride;
66
+ if (stride === 3) {
67
+ for (var i = 0; i < n; i++) {
68
+ var o = base + i * 3;
69
+ into[o] = x[i] - sx;
70
+ into[o + 1] = y[i] - sy;
71
+ into[o + 2] = z[i] - sz;
72
+ }
73
+ } else {
74
+ for (var j = 0; j < n; j++) {
75
+ var p = base + j * 2;
76
+ into[p] = x[j] - sx;
77
+ into[p + 1] = y[j] - sy;
78
+ }
79
+ }
80
+ return into;
81
+ }
@@ -0,0 +1,83 @@
1
+ // src/ops/polygon.js
2
+ //==========================================================
3
+ // The point-in-polygon test behind selectPolygon and the reader's
4
+ // polygon crop (filters.js). Internal: not exported from index.js.
5
+ // The boundary decision comes first (a point within tolerance of an
6
+ // edge or vertex, measured in Float64), so an edge point never
7
+ // depends on crossing-test rounding; only a point clear of every
8
+ // edge goes to the even-odd crossing test (W. Randolph Franklin's
9
+ // PNPOLY).
10
+ //==========================================================
11
+
12
+ import { LasError, ERROR_CODES } from "../errors.js";
13
+
14
+ function bad(message) { return new LasError(ERROR_CODES.INVALID_ARGUMENT, message); }
15
+
16
+ // Validate a flat polygon [x0, y0, x1, y1, ...] and copy it to a Float64Array
17
+ function checkPolygon(polygon) {
18
+ if (!Array.isArray(polygon) && !(ArrayBuffer.isView(polygon) && !(polygon instanceof DataView))) throw bad("polygon must be an Array or typed array [x0, y0, x1, y1, ...]");
19
+ var n = polygon.length;
20
+ if (n % 2 !== 0) throw bad("polygon has an odd length " + n + ": it holds x, y pairs");
21
+ if (n < 6) throw bad("polygon needs at least 3 vertices (6 values), got " + n);
22
+ var p = new Float64Array(n);
23
+ for (var i = 0; i < n; i++) {
24
+ var v = polygon[i];
25
+ if (typeof v !== "number" || !Number.isFinite(v)) throw bad("polygon[" + i + "] = " + String(v) + " is not a finite number");
26
+ p[i] = v;
27
+ }
28
+ return p;
29
+ }
30
+
31
+ /**
32
+ * Build a point-in-polygon test.
33
+ * @param {number[]|TypedArray} polygon - [x0, y0, x1, y1, ...], at least 3 vertices, implicitly closed.
34
+ * @param {{boundary?: string, tolerance?: number}} [opts] - boundary "touching" (default: points on the
35
+ * boundary are in) or "inside" (they are out); tolerance (default 0): within this distance of an edge
36
+ * or vertex is on the boundary.
37
+ * @returns {{test: function(number, number): boolean, minX: number, maxX: number, minY: number, maxY: number}}
38
+ * test(x, y) decides a point (false outside the box at once); the box is the polygon's grown by tolerance.
39
+ */
40
+ export function makePolygonTest(polygon, opts) {
41
+ if (opts === undefined || opts === null) opts = {};
42
+ if (typeof opts !== "object") throw bad("options must be an object { boundary, tolerance }");
43
+ var p = checkPolygon(polygon);
44
+ var boundary = opts.boundary === undefined ? "touching" : opts.boundary;
45
+ if (boundary !== "touching" && boundary !== "inside") throw bad("boundary must be \"touching\" or \"inside\", got " + String(boundary));
46
+ var tol = opts.tolerance === undefined ? 0 : opts.tolerance;
47
+ if (typeof tol !== "number" || !Number.isFinite(tol) || tol < 0) throw bad("tolerance must be a finite number ≥ 0, got " + String(tol));
48
+ var touching = boundary === "touching", tol2 = tol * tol;
49
+ var n = p.length / 2;
50
+ var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
51
+ for (var k = 0; k < n; k++) {
52
+ if (p[2 * k] < minX) minX = p[2 * k];
53
+ if (p[2 * k] > maxX) maxX = p[2 * k];
54
+ if (p[2 * k + 1] < minY) minY = p[2 * k + 1];
55
+ if (p[2 * k + 1] > maxY) maxY = p[2 * k + 1];
56
+ }
57
+ minX -= tol; maxX += tol; minY -= tol; maxY += tol;
58
+
59
+ // Squared distance from (px, py) to the segment (ax, ay)-(bx, by); 0 when the point is exactly on it
60
+ function dist2(px, py, ax, ay, bx, by) {
61
+ var ex = bx - ax, ey = by - ay;
62
+ var wx = px - ax, wy = py - ay;
63
+ var len2 = ex * ex + ey * ey;
64
+ if (len2 === 0) return wx * wx + wy * wy;
65
+ if (ex * wy - ey * wx === 0 && px >= Math.min(ax, bx) && px <= Math.max(ax, bx) && py >= Math.min(ay, by) && py <= Math.max(ay, by)) return 0;
66
+ var t = (wx * ex + wy * ey) / len2;
67
+ if (t < 0) t = 0; else if (t > 1) t = 1;
68
+ var dx = px - (ax + t * ex), dy = py - (ay + t * ey);
69
+ return dx * dx + dy * dy;
70
+ }
71
+
72
+ function test(px, py) {
73
+ if (!(px >= minX && px <= maxX && py >= minY && py <= maxY)) return false;
74
+ var inside = false;
75
+ for (var i = 0, j = n - 1; i < n; j = i++) {
76
+ var ax = p[2 * j], ay = p[2 * j + 1], bx = p[2 * i], by = p[2 * i + 1];
77
+ if (dist2(px, py, ax, ay, bx, by) <= tol2) return touching;
78
+ if ((by > py) !== (ay > py) && px < (ax - bx) * (py - by) / (ay - by) + bx) inside = !inside;
79
+ }
80
+ return inside;
81
+ }
82
+ return { test: test, minX: minX, maxX: maxX, minY: minY, maxY: maxY };
83
+ }
@@ -0,0 +1,245 @@
1
+ // src/ops/select.js
2
+ //==========================================================
3
+ // Making selections: a Uint32Array of point indices, strictly
4
+ // ascending, from point columns (see columns.js). Box, class and
5
+ // return selections mean what the reader's filters mean; thinSelection
6
+ // is the reader's maxPoints rule, exported, and thinStep is the one
7
+ // place that rule lives (decodeRecords calls it per passing record).
8
+ // Set operations are linear merges of two ascending selections.
9
+ //==========================================================
10
+
11
+ import { LasError, ERROR_CODES } from "../errors.js";
12
+ import { countOf, checkSelection, needColumn } from "./columns.js";
13
+ import { makePolygonTest } from "./polygon.js";
14
+
15
+ function bad(message) { return new LasError(ERROR_CODES.INVALID_ARGUMENT, message); }
16
+
17
+ function whole(v) { return typeof v === "number" && Number.isInteger(v) && v >= 0; }
18
+
19
+ // out[0..n) as a copy that does not pin the full-size buffer.
20
+ function trim(out, n) { return n === out.length ? out : out.slice(0, n); }
21
+
22
+ // A lookup table of 256 flags for a list of whole numbers 0..255.
23
+ function table(list, name) {
24
+ if (!Array.isArray(list)) throw bad(name + " must be an Array of whole numbers");
25
+ var t = new Uint8Array(256);
26
+ list.forEach(function (v, i) {
27
+ if (!whole(v) || v > 255) throw bad(name + "[" + i + "] = " + v + " is not a whole number 0 to 255");
28
+ t[v] = 1;
29
+ });
30
+ return t;
31
+ }
32
+
33
+ function bound(box, key) {
34
+ var v = box[key];
35
+ if (v === undefined || v === null) return undefined;
36
+ if (typeof v !== "number" || v !== v) throw bad(key + " must be a number");
37
+ return v;
38
+ }
39
+
40
+ /**
41
+ * Indices of the points inside a box, bounds inclusive. An omitted
42
+ * (undefined) or null bound is no limit, as in the reader's bounds. Needs x and y; z only when minZ or
43
+ * maxZ is given.
44
+ * @param {object} cols - point columns.
45
+ * @param {{minX?:number, maxX?:number, minY?:number, maxY?:number, minZ?:number, maxZ?:number}} box
46
+ * @returns {Uint32Array}
47
+ */
48
+ export function selectBox(cols, box) {
49
+ var n = countOf(cols);
50
+ if (box === null || typeof box !== "object") throw bad("box must be an object { minX, maxX, minY, maxY, minZ, maxZ }");
51
+ var minX = bound(box, "minX"), maxX = bound(box, "maxX");
52
+ var minY = bound(box, "minY"), maxY = bound(box, "maxY");
53
+ var minZ = bound(box, "minZ"), maxZ = bound(box, "maxZ");
54
+ var x = needColumn(cols, "x", "selectBox"), y = needColumn(cols, "y", "selectBox");
55
+ var useZ = minZ !== undefined || maxZ !== undefined;
56
+ var z = useZ ? needColumn(cols, "z", "selectBox") : null;
57
+ if (minX === undefined) minX = -Infinity;
58
+ if (maxX === undefined) maxX = Infinity;
59
+ if (minY === undefined) minY = -Infinity;
60
+ if (maxY === undefined) maxY = Infinity;
61
+ if (minZ === undefined) minZ = -Infinity;
62
+ if (maxZ === undefined) maxZ = Infinity;
63
+ var out = new Uint32Array(n), m = 0;
64
+ for (var i = 0; i < n; i++) {
65
+ var vx = x[i];
66
+ if (!(vx >= minX && vx <= maxX)) continue;
67
+ var vy = y[i];
68
+ if (!(vy >= minY && vy <= maxY)) continue;
69
+ if (useZ) {
70
+ var vz = z[i];
71
+ if (!(vz >= minZ && vz <= maxZ)) continue;
72
+ }
73
+ out[m++] = i;
74
+ }
75
+ return trim(out, m);
76
+ }
77
+
78
+ /**
79
+ * Indices of the points inside a polygon (even-odd rule), in the columns' own frame. A point on the
80
+ * boundary (within tolerance of an edge or vertex, as the 2D distance to the segment in Float64) is
81
+ * kept by boundary "touching" (default) and dropped by "inside", so the two differ by exactly the
82
+ * boundary points. The boundary is decided before the crossing test. Needs x and y; z only when a Z
83
+ * limit is given.
84
+ * @param {object} cols - point columns.
85
+ * @param {number[]|TypedArray} polygon - [x0, y0, x1, y1, ...], at least 3 vertices, implicitly closed.
86
+ * @param {{boundary?: "touching"|"inside", tolerance?: number, minZ?: number, maxZ?: number}} [opts]
87
+ * @returns {Uint32Array}
88
+ */
89
+ export function selectPolygon(cols, polygon, opts) {
90
+ var n = countOf(cols);
91
+ if (opts === undefined || opts === null) opts = {};
92
+ if (typeof opts !== "object") throw bad("options must be an object { boundary, tolerance, minZ, maxZ }");
93
+ var poly = makePolygonTest(polygon, opts);
94
+ var minZ = bound(opts, "minZ"), maxZ = bound(opts, "maxZ");
95
+ var x = needColumn(cols, "x", "selectPolygon"), y = needColumn(cols, "y", "selectPolygon");
96
+ var useZ = minZ !== undefined || maxZ !== undefined;
97
+ var z = useZ ? needColumn(cols, "z", "selectPolygon") : null;
98
+ if (minZ === undefined) minZ = -Infinity;
99
+ if (maxZ === undefined) maxZ = Infinity;
100
+ var test = poly.test;
101
+ var out = new Uint32Array(n), m = 0;
102
+ for (var i = 0; i < n; i++) {
103
+ if (useZ) {
104
+ var vz = z[i];
105
+ if (!(vz >= minZ && vz <= maxZ)) continue;
106
+ }
107
+ if (test(x[i], y[i])) out[m++] = i;
108
+ }
109
+ return trim(out, m);
110
+ }
111
+
112
+ /**
113
+ * Indices of the points whose classification is in the list.
114
+ * @param {object} cols - point columns (needs classification).
115
+ * @param {number[]} classes
116
+ * @returns {Uint32Array}
117
+ */
118
+ export function selectClasses(cols, classes) {
119
+ var n = countOf(cols);
120
+ var cls = needColumn(cols, "classification", "selectClasses");
121
+ var t = table(classes, "classes");
122
+ var out = new Uint32Array(n), m = 0;
123
+ for (var i = 0; i < n; i++) if (cls[i] < 256 && t[cls[i]] === 1) out[m++] = i;
124
+ return trim(out, m);
125
+ }
126
+
127
+ /**
128
+ * Indices of the points matching any of the return criteria given:
129
+ * returns (returnNumber in the list), first (returnNumber 1), last
130
+ * (returnNumber equals numberOfReturns), single (numberOfReturns 1).
131
+ * @param {object} cols - point columns (returnNumber; numberOfReturns for last / single).
132
+ * @param {{returns?:number[], first?:boolean, last?:boolean, single?:boolean}} criteria
133
+ * @returns {Uint32Array}
134
+ */
135
+ export function selectReturns(cols, criteria) {
136
+ var n = countOf(cols);
137
+ if (criteria === null || typeof criteria !== "object") throw bad("criteria must be an object { returns, first, last, single }");
138
+ var hasList = criteria.returns !== undefined;
139
+ var first = !!criteria.first, last = !!criteria.last, single = !!criteria.single;
140
+ if (!hasList && !first && !last && !single) throw bad("selectReturns needs returns, first, last or single");
141
+ var t = hasList ? table(criteria.returns, "returns") : null;
142
+ var rn = needColumn(cols, "returnNumber", "selectReturns");
143
+ var nr = (last || single) ? needColumn(cols, "numberOfReturns", "selectReturns") : null;
144
+ var out = new Uint32Array(n), m = 0;
145
+ for (var i = 0; i < n; i++) {
146
+ var r = rn[i];
147
+ var hit = (hasList && r < 256 && t[r] === 1) || (first && r === 1) || (last && r === nr[i]) || (single && nr[i] === 1);
148
+ if (hit) out[m++] = i;
149
+ }
150
+ return trim(out, m);
151
+ }
152
+
153
+ /**
154
+ * One step of the reader's even-thinning rule. thin is
155
+ * { total, target, acc }: acc gains target per record and the record
156
+ * is kept each time acc reaches total (then acc -= total). The j-th
157
+ * kept record is number ceil(j * total / target) - 1.
158
+ * @param {{total:number, target:number, acc:number}} thin - updated in place.
159
+ * @returns {boolean} true when this record is kept.
160
+ */
161
+ export function thinStep(thin) {
162
+ thin.acc += thin.target;
163
+ if (thin.acc < thin.total) return false;
164
+ thin.acc -= thin.total;
165
+ return true;
166
+ }
167
+
168
+ /**
169
+ * The reader's maxPoints rule as a selection: exactly min(count, target)
170
+ * indices spread evenly over 0..count-1. readPoints({ maxPoints: target })
171
+ * keeps the same points.
172
+ * @param {number} count - points to choose from.
173
+ * @param {number} target - points to keep.
174
+ * @returns {Uint32Array}
175
+ */
176
+ export function thinSelection(count, target) {
177
+ if (!whole(count)) throw bad("count " + String(count) + " is not a whole number ≥ 0");
178
+ if (!whole(target)) throw bad("target " + String(target) + " is not a whole number ≥ 0");
179
+ var out, i;
180
+ if (target >= count) {
181
+ out = new Uint32Array(count);
182
+ for (i = 0; i < count; i++) out[i] = i;
183
+ return out;
184
+ }
185
+ out = new Uint32Array(target);
186
+ var thin = { total: count, target: target, acc: 0 }, m = 0;
187
+ for (i = 0; i < count && m < target; i++) if (thinStep(thin)) out[m++] = i;
188
+ return out;
189
+ }
190
+
191
+ /**
192
+ * Indices in a or in b.
193
+ * @param {Uint32Array|number[]} a - ascending selection.
194
+ * @param {Uint32Array|number[]} b - ascending selection.
195
+ * @returns {Uint32Array}
196
+ */
197
+ export function unionSelections(a, b) {
198
+ a = checkSelection(a, Infinity, "a");
199
+ b = checkSelection(b, Infinity, "b");
200
+ var out = new Uint32Array(a.length + b.length), i = 0, j = 0, m = 0;
201
+ while (i < a.length && j < b.length) {
202
+ if (a[i] < b[j]) out[m++] = a[i++];
203
+ else if (a[i] > b[j]) out[m++] = b[j++];
204
+ else { out[m++] = a[i++]; j++; }
205
+ }
206
+ while (i < a.length) out[m++] = a[i++];
207
+ while (j < b.length) out[m++] = b[j++];
208
+ return trim(out, m);
209
+ }
210
+
211
+ /**
212
+ * Indices in both a and b.
213
+ * @param {Uint32Array|number[]} a - ascending selection.
214
+ * @param {Uint32Array|number[]} b - ascending selection.
215
+ * @returns {Uint32Array}
216
+ */
217
+ export function intersectSelections(a, b) {
218
+ a = checkSelection(a, Infinity, "a");
219
+ b = checkSelection(b, Infinity, "b");
220
+ var out = new Uint32Array(Math.min(a.length, b.length)), i = 0, j = 0, m = 0;
221
+ while (i < a.length && j < b.length) {
222
+ if (a[i] < b[j]) i++;
223
+ else if (a[i] > b[j]) j++;
224
+ else { out[m++] = a[i++]; j++; }
225
+ }
226
+ return trim(out, m);
227
+ }
228
+
229
+ /**
230
+ * Indices in a that are not in b.
231
+ * @param {Uint32Array|number[]} a - ascending selection.
232
+ * @param {Uint32Array|number[]} b - ascending selection.
233
+ * @returns {Uint32Array}
234
+ */
235
+ export function subtractSelections(a, b) {
236
+ a = checkSelection(a, Infinity, "a");
237
+ b = checkSelection(b, Infinity, "b");
238
+ var out = new Uint32Array(a.length), i = 0, j = 0, m = 0;
239
+ while (i < a.length) {
240
+ while (j < b.length && b[j] < a[i]) j++;
241
+ if (j < b.length && b[j] === a[i]) { i++; continue; }
242
+ out[m++] = a[i++];
243
+ }
244
+ return trim(out, m);
245
+ }