las-laz-js 0.6.1 → 0.6.2

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.2 (2026-10-22)
4
+
5
+ - `dedupe` and `dedupeSelection` handle more than 16,777,216 occupied cells. They used a
6
+ JavaScript Map, which stops at 2^24 entries, so a 20-30 M-point cloud at a small tolerance threw
7
+ `RangeError: Map maximum size exceeded`. A typed-array hash table (5 bytes a slot) replaces
8
+ it, with the same results and about 1.5 to 2 times the speed (2 M points: 0.9 s in 2D, 2.2 s in 3D).
9
+
3
10
  ## 0.6.1 (2026-10-22)
4
11
 
5
12
  - Demo: a read bigger than the draw cap (20 M) is drawn as an even spread over the whole cloud,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "las-laz-js",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Zero-dependency JavaScript reader and writer for ASPRS LAS point clouds, and reader and writer for LAZ (LASzip), for the browser and Node, built for files larger than memory.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { LasError, ERROR_CODES } from "../errors.js";
14
14
  import { columnsOf, gather, sameType } from "../ops/columns.js";
15
+ import { hash32 } from "../index/grid.js";
15
16
 
16
17
  /**
17
18
  * The points dedupe keeps, and where every input point went.
@@ -46,12 +47,40 @@ export function dedupeSelection(cols, tolerance, opts) {
46
47
  var dense = (hi[0] - lo[0] + 3) * SY * SZ <= 9007199254740992;
47
48
  function key(i, j, k) { return dense ? (i * SY + j) * SZ + k : (i * 73856093 + j * 19349663 + k * 83492791) % 9007199254740881; }
48
49
 
49
- // Each cell is a linked list through next[] (Map: key -> most recent point)
50
- var head = new Map();
50
+ function cellKey(p) {
51
+ return key(Math.floor(x[p] / tolerance) - lo[0] + 1, Math.floor(y[p] / tolerance) - lo[1] + 1, use3d ? Math.floor(z[p] / tolerance) - lo[2] + 1 : 1);
52
+ }
53
+
54
+ // Each cell is a linked list of kept points through next[]. Its newest point sits in
55
+ // an open-addressing table (linear probing, power-of-two capacity, at most half
56
+ // full). A slot holds the point number and an 8-bit tag of its key's hash; the key
57
+ // itself is recomputed from the point's cell, only when the tag matches. 5 bytes a
58
+ // slot. (A Map stops at 2^24 cells.)
59
+ var cells = (hi[0] - lo[0] + 1) * (hi[1] - lo[1] + 1) * (hi[2] - lo[2] + 1);
60
+ var cap = 16;
61
+ while (cap < Math.min(n, cells)) cap *= 2;
62
+ var mask = cap - 1, used = 0, table = new Int32Array(cap).fill(-1), tags = new Uint8Array(cap);
63
+ var tag = 0; // the tag of the key find() last looked up
64
+ // The slot of cell key c: its newest kept point, or -1 (the free slot c would take)
65
+ function find(c) {
66
+ var g = hash32(c), h = g & mask;
67
+ tag = Math.imul(g, 0x9E3779B1) >>> 24; // top bits of a multiply mix every bit of g
68
+ while (table[h] >= 0 && (tags[h] !== tag || cellKey(table[h]) !== c)) h = (h + 1) & mask;
69
+ return h;
70
+ }
71
+ function grow() {
72
+ var old = table, oldTags = tags;
73
+ cap *= 2; mask = cap - 1; table = new Int32Array(cap).fill(-1); tags = new Uint8Array(cap);
74
+ for (var s = 0; s < old.length; s++) {
75
+ if (old[s] < 0) continue;
76
+ var h = hash32(cellKey(old[s])) & mask;
77
+ while (table[h] >= 0) h = (h + 1) & mask;
78
+ table[h] = old[s]; tags[h] = oldTags[s];
79
+ }
80
+ }
51
81
  var next = new Int32Array(n);
52
82
  var kept = new Uint32Array(n);
53
83
  var map = new Uint32Array(n);
54
- var dropped = [];
55
84
  var nKept = 0;
56
85
  for (var i = 0; i < n; i++) {
57
86
  var cx = Math.floor(x[i] / tolerance) - lo[0] + 1, cy = Math.floor(y[i] / tolerance) - lo[1] + 1;
@@ -60,8 +89,8 @@ export function dedupeSelection(cols, tolerance, opts) {
60
89
  for (var dx = -1; dx <= 1 && !dup; dx++) {
61
90
  for (var dy = -1; dy <= 1 && !dup; dy++) {
62
91
  for (var dz = -dzRange; dz <= dzRange && !dup; dz++) {
63
- var k = head.get(key(cx + dx, cy + dy, cz + dz));
64
- while (k !== undefined && k >= 0) {
92
+ var k = table[find(key(cx + dx, cy + dy, cz + dz))];
93
+ while (k >= 0) {
65
94
  var ex = x[k] - x[i], ey = y[k] - y[i];
66
95
  var ez = use3d ? z[k] - z[i] : 0;
67
96
  if (ex * ex + ey * ey + ez * ez <= tol2) { dup = true; break; }
@@ -70,25 +99,31 @@ export function dedupeSelection(cols, tolerance, opts) {
70
99
  }
71
100
  }
72
101
  }
73
- if (dup) { dropped.push(i); continue; }
74
- var own = key(cx, cy, cz);
75
- var prev = head.get(own);
76
- next[i] = prev === undefined ? -1 : prev;
77
- head.set(own, i);
102
+ if (dup) continue;
103
+ var own = key(cx, cy, cz), h = find(own);
104
+ if (table[h] < 0) {
105
+ if (2 * (used + 1) > cap) { grow(); h = find(own); }
106
+ used++;
107
+ tags[h] = tag;
108
+ }
109
+ next[i] = table[h];
110
+ table[h] = i;
78
111
  map[i] = nKept;
79
112
  kept[nKept++] = i;
80
113
  }
81
114
 
82
- // Each dropped point maps to its nearest kept point (the cell lists hold kept points only)
83
- dropped.forEach(function (i) {
115
+ // Each dropped point (every point not in kept) maps to its nearest kept point;
116
+ // the cell lists hold kept points only
117
+ for (i = 0, p = 0; i < n; i++) {
118
+ if (p < nKept && kept[p] === i) { p++; continue; }
84
119
  var cx = Math.floor(x[i] / tolerance) - lo[0] + 1, cy = Math.floor(y[i] / tolerance) - lo[1] + 1;
85
120
  var cz = use3d ? Math.floor(z[i] / tolerance) - lo[2] + 1 : 1;
86
121
  var best = -1, bd = Infinity;
87
122
  for (var dx = -1; dx <= 1; dx++) {
88
123
  for (var dy = -1; dy <= 1; dy++) {
89
124
  for (var dz = -dzRange; dz <= dzRange; dz++) {
90
- var k = head.get(key(cx + dx, cy + dy, cz + dz));
91
- while (k !== undefined && k >= 0) {
125
+ var k = table[find(key(cx + dx, cy + dy, cz + dz))];
126
+ while (k >= 0) {
92
127
  var ex = x[k] - x[i], ey = y[k] - y[i];
93
128
  var ez = use3d ? z[k] - z[i] : 0;
94
129
  var d = ex * ex + ey * ey + ez * ez;
@@ -99,7 +134,7 @@ export function dedupeSelection(cols, tolerance, opts) {
99
134
  }
100
135
  }
101
136
  map[i] = map[best];
102
- });
137
+ }
103
138
  return { keep: kept.slice(0, nKept), map: map };
104
139
  }
105
140
 
package/src/index/grid.js CHANGED
@@ -56,6 +56,16 @@ function fmix32(h) {
56
56
  return (h ^ (h >>> 16)) >>> 0;
57
57
  }
58
58
 
59
+ /**
60
+ * A 32-bit hash of a cell key (an integer below 2^53).
61
+ * @param {number} key
62
+ * @returns {number}
63
+ */
64
+ export function hash32(key) {
65
+ var lo = key % 4294967296, hi = (key - lo) / 4294967296;
66
+ return fmix32(lo ^ fmix32(hi));
67
+ }
68
+
59
69
  /**
60
70
  * The home slot of a cell key (an integer below 2^53) in a table of mask + 1 slots.
61
71
  * @param {number} key
@@ -63,8 +73,7 @@ function fmix32(h) {
63
73
  * @returns {number}
64
74
  */
65
75
  export function hashKey(key, mask) {
66
- var lo = key % 4294967296, hi = (key - lo) / 4294967296;
67
- return fmix32(lo ^ fmix32(hi)) & mask;
76
+ return hash32(key) & mask;
68
77
  }
69
78
 
70
79
  function nextPow2(n) {
package/src/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  //==========================================================
7
7
 
8
8
  /** Library version, matches package.json. */
9
- export var VERSION = "0.6.1";
9
+ export var VERSION = "0.6.2";
10
10
 
11
11
  export { LasError, ERROR_CODES } from "./errors.js";
12
12
  export { openLas, LasReader } from "./LasReader.js";