@jax-data-science/sci-image-visualizer 0.9.0 → 0.11.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.
Files changed (149) hide show
  1. package/esm2022/index.mjs +10 -1
  2. package/esm2022/lib/contracts/image.contract.mjs +1 -1
  3. package/esm2022/lib/contracts/ports/region-io.port.mjs +1 -1
  4. package/esm2022/lib/contracts/region-editor-api.contract.mjs +1 -1
  5. package/esm2022/lib/contracts/region-store.contract.mjs +1 -1
  6. package/esm2022/lib/contracts/section-view.contract.mjs +2 -0
  7. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  8. package/esm2022/lib/contracts/viz-config.mjs +1 -1
  9. package/esm2022/lib/geometry/loft.mjs +128 -0
  10. package/esm2022/lib/geometry/polylabel.mjs +156 -0
  11. package/esm2022/lib/geometry/reconstruct.mjs +315 -0
  12. package/esm2022/lib/geometry/reslice-image.mjs +57 -0
  13. package/esm2022/lib/geometry/section.mjs +215 -0
  14. package/esm2022/lib/implementations/napari-js/napari-loading-badge.mjs +11 -7
  15. package/esm2022/lib/implementations/napari-js/napari-loading-state.mjs +20 -3
  16. package/esm2022/lib/implementations/napari-js/napari-region-mesh-pointer.mjs +100 -0
  17. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +28 -5
  18. package/esm2022/lib/implementations/napari-js/napari-scene.mjs +1 -1
  19. package/esm2022/lib/implementations/napari-js/napari-spatial-hover.mjs +9 -2
  20. package/esm2022/lib/implementations/napari-js/napari-tool-bridge.mjs +5 -2
  21. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +81 -1
  22. package/esm2022/lib/implementations/napari-js/napari-volume-region-meshes.mjs +129 -0
  23. package/esm2022/lib/implementations/napari-js/napari-volume-scene.mjs +115 -4
  24. package/esm2022/lib/implementations/napari-js/napari-volume-slice-planes.mjs +103 -0
  25. package/esm2022/lib/implementations/napari-js/region-overlay/region-svg-renderer.mjs +15 -3
  26. package/esm2022/lib/implementations/osd/openseadragon-viewer-options.mjs +18 -2
  27. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +9 -1
  28. package/esm2022/lib/implementations/osd/osd-region-overlay-draw.mjs +18 -7
  29. package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +38 -8
  30. package/esm2022/lib/implementations/osd/osd-tile-loading.mjs +71 -0
  31. package/esm2022/lib/implementations/osd/osd-viewer-like.mjs +1 -1
  32. package/esm2022/lib/implementations/osd/slice-cache.mjs +35 -6
  33. package/esm2022/lib/implementations/tile-server/tile-protocol.mjs +1 -1
  34. package/esm2022/lib/models/class-preset.mjs +1 -1
  35. package/esm2022/lib/models/polygon-factory.mjs +17 -1
  36. package/esm2022/lib/models/region-geojson.mjs +44 -6
  37. package/esm2022/lib/models/region.mjs +1 -1
  38. package/esm2022/lib/region-editor/class-tree/class-tree.component.mjs +156 -0
  39. package/esm2022/lib/region-editor/class-tree/class-tree.mjs +108 -0
  40. package/esm2022/lib/region-editor/classes-panel/classes-panel.component.mjs +77 -13
  41. package/esm2022/lib/region-editor/region-editor.component.mjs +670 -42
  42. package/esm2022/lib/region-editor/region-metrics.mjs +20 -2
  43. package/esm2022/lib/region-editor/region-persistence.service.mjs +44 -10
  44. package/esm2022/lib/region-editor/region-table/region-table.component.mjs +24 -8
  45. package/esm2022/lib/region-import/label-classes.mjs +149 -0
  46. package/esm2022/lib/region-import/label-volume.mjs +194 -0
  47. package/esm2022/lib/region-import/region-import.mjs +71 -0
  48. package/esm2022/lib/region-overlay/region-geometry.mjs +3 -3
  49. package/esm2022/lib/region-overlay/region-source-layer.mjs +378 -0
  50. package/esm2022/lib/region-overlay/region-style.mjs +97 -0
  51. package/esm2022/lib/region-source/http-region-source.mjs +104 -0
  52. package/esm2022/lib/region-source/region-source-ops.mjs +53 -0
  53. package/esm2022/lib/region-source/region-source-worker.mjs +10 -0
  54. package/esm2022/lib/region-source/region-source.mjs +67 -0
  55. package/esm2022/lib/region-source/region-tiles.mjs +708 -0
  56. package/esm2022/lib/region-source/worker-region-source.mjs +229 -0
  57. package/esm2022/lib/routing-visualizer.service.mjs +510 -22
  58. package/esm2022/lib/section-pane/section-pane.component.mjs +299 -0
  59. package/esm2022/lib/store/class-color.util.mjs +10 -5
  60. package/esm2022/lib/store/class-signatures.mjs +63 -0
  61. package/esm2022/lib/store/region-scope-cache.mjs +5 -1
  62. package/esm2022/lib/store/region-source-scope.mjs +186 -0
  63. package/esm2022/lib/store/region-store.service.mjs +774 -12
  64. package/esm2022/lib/store/visualizer-store.service.mjs +33 -6
  65. package/esm2022/lib/toolbar/region-tools/region-tools.component.mjs +8 -2
  66. package/esm2022/lib/toolbar/stack-controls/stack-controls.component.mjs +55 -15
  67. package/esm2022/lib/toolbar/toolbar.component.mjs +19 -5
  68. package/esm2022/lib/visualizer/region-load.mjs +67 -4
  69. package/esm2022/lib/visualizer/volume-sections.mjs +175 -0
  70. package/esm2022/lib/visualizer.component.mjs +155 -7
  71. package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-Csxlnq7I.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.mjs} +9851 -2904
  72. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.mjs.map +1 -0
  73. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-Bkujqg7m.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs} +4 -4
  74. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-Bkujqg7m.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs.map} +1 -1
  75. package/fesm2022/jax-data-science-sci-image-visualizer-region-source-worker-CAqBjM5L.mjs +12 -0
  76. package/fesm2022/jax-data-science-sci-image-visualizer-region-source-worker-CAqBjM5L.mjs.map +1 -0
  77. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +3 -3
  78. package/fesm2022/region-source.worker.js +1067 -0
  79. package/index.d.ts +12 -0
  80. package/lib/contracts/image.contract.d.ts +4 -0
  81. package/lib/contracts/ports/region-io.port.d.ts +24 -0
  82. package/lib/contracts/region-editor-api.contract.d.ts +102 -0
  83. package/lib/contracts/region-store.contract.d.ts +22 -0
  84. package/lib/contracts/section-view.contract.d.ts +44 -0
  85. package/lib/contracts/visualizer.contract.d.ts +10 -0
  86. package/lib/contracts/viz-config.d.ts +8 -0
  87. package/lib/geometry/loft.d.ts +22 -0
  88. package/lib/geometry/polylabel.d.ts +23 -0
  89. package/lib/geometry/reconstruct.d.ts +54 -0
  90. package/lib/geometry/reslice-image.d.ts +33 -0
  91. package/lib/geometry/section.d.ts +52 -0
  92. package/lib/implementations/napari-js/napari-loading-badge.d.ts +4 -2
  93. package/lib/implementations/napari-js/napari-loading-state.d.ts +8 -1
  94. package/lib/implementations/napari-js/napari-region-mesh-pointer.d.ts +32 -0
  95. package/lib/implementations/napari-js/napari-region-overlay.d.ts +6 -1
  96. package/lib/implementations/napari-js/napari-scene.d.ts +33 -0
  97. package/lib/implementations/napari-js/napari-spatial-hover.d.ts +3 -1
  98. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +48 -0
  99. package/lib/implementations/napari-js/napari-volume-region-meshes.d.ts +66 -0
  100. package/lib/implementations/napari-js/napari-volume-scene.d.ts +45 -1
  101. package/lib/implementations/napari-js/napari-volume-slice-planes.d.ts +43 -0
  102. package/lib/implementations/osd/openseadragon-viewer-options.d.ts +2 -0
  103. package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +2 -0
  104. package/lib/implementations/osd/osd-region-overlay-draw.d.ts +4 -3
  105. package/lib/implementations/osd/osd-region-overlay.d.ts +10 -3
  106. package/lib/implementations/osd/osd-tile-loading.d.ts +43 -0
  107. package/lib/implementations/osd/osd-viewer-like.d.ts +14 -0
  108. package/lib/implementations/osd/slice-cache.d.ts +13 -3
  109. package/lib/implementations/tile-server/tile-protocol.d.ts +3 -0
  110. package/lib/models/class-preset.d.ts +7 -0
  111. package/lib/models/polygon-factory.d.ts +5 -0
  112. package/lib/models/region-geojson.d.ts +40 -0
  113. package/lib/models/region.d.ts +19 -0
  114. package/lib/region-editor/class-tree/class-tree.component.d.ts +78 -0
  115. package/lib/region-editor/class-tree/class-tree.d.ts +31 -0
  116. package/lib/region-editor/classes-panel/classes-panel.component.d.ts +36 -6
  117. package/lib/region-editor/region-editor.component.d.ts +159 -17
  118. package/lib/region-editor/region-metrics.d.ts +8 -0
  119. package/lib/region-editor/region-persistence.service.d.ts +17 -5
  120. package/lib/region-editor/region-table/region-table.component.d.ts +10 -4
  121. package/lib/region-import/label-classes.d.ts +39 -0
  122. package/lib/region-import/label-volume.d.ts +31 -0
  123. package/lib/region-import/region-import.d.ts +46 -0
  124. package/lib/region-overlay/region-geometry.d.ts +4 -3
  125. package/lib/region-overlay/region-source-layer.d.ts +96 -0
  126. package/lib/region-overlay/region-style.d.ts +52 -0
  127. package/lib/region-source/http-region-source.d.ts +45 -0
  128. package/lib/region-source/region-source-ops.d.ts +89 -0
  129. package/lib/region-source/region-source-worker.d.ts +7 -0
  130. package/lib/region-source/region-source.d.ts +82 -0
  131. package/lib/region-source/region-tiles.d.ts +289 -0
  132. package/lib/region-source/worker-region-source.d.ts +65 -0
  133. package/lib/routing-visualizer.service.d.ts +168 -1
  134. package/lib/section-pane/section-pane.component.d.ts +47 -0
  135. package/lib/store/class-color.util.d.ts +6 -3
  136. package/lib/store/class-signatures.d.ts +11 -0
  137. package/lib/store/region-scope-cache.d.ts +2 -0
  138. package/lib/store/region-source-scope.d.ts +74 -0
  139. package/lib/store/region-store.service.d.ts +216 -2
  140. package/lib/store/visualizer-store.service.d.ts +10 -0
  141. package/lib/toolbar/region-tools/region-tools.component.d.ts +3 -1
  142. package/lib/toolbar/stack-controls/stack-controls.component.d.ts +20 -9
  143. package/lib/toolbar/toolbar.component.d.ts +9 -2
  144. package/lib/visualizer/region-load.d.ts +23 -4
  145. package/lib/visualizer/volume-sections.d.ts +64 -0
  146. package/lib/visualizer.component.d.ts +40 -0
  147. package/package.json +2 -2
  148. package/src/lib/assets/slice.svg +23 -0
  149. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-Csxlnq7I.mjs.map +0 -1
@@ -0,0 +1,1067 @@
1
+ // src/lib/geometry/reconstruct.ts
2
+ import { pointInRing, ringArea, smoothMesh, surfaceNets } from "napari-js/geometry";
3
+
4
+ // src/lib/geometry/loft.ts
5
+ var MAX_POINTS = 2048;
6
+ var MATCH_POINTS = 32;
7
+ function loftLines(slices, step = 1) {
8
+ const stack = slices.map((s) => ({ z: s.z, lines: s.lines.filter((l) => l.length >= 4) })).filter((s) => s.lines.length).sort((a, b) => a.z - b.z);
9
+ if (stack.length < 2)
10
+ return { vertices: new Float32Array(0), faces: new Uint32Array(0) };
11
+ let longest = 0;
12
+ for (const s of stack)
13
+ for (const l of s.lines)
14
+ longest = Math.max(longest, lineLength(l));
15
+ const n = Math.min(MAX_POINTS, Math.max(2, Math.ceil(longest / Math.max(step, 1e-6)) + 1));
16
+ const rows = [];
17
+ const verts = [];
18
+ for (const s of stack) {
19
+ rows.push(
20
+ s.lines.map((l) => {
21
+ const first = verts.length / 3;
22
+ const pts = resample(l, n);
23
+ for (let i = 0; i < n; i++)
24
+ verts.push(pts[2 * i], pts[2 * i + 1], s.z);
25
+ return { first, probe: resample(l, MATCH_POINTS), box: lineBox(l) };
26
+ })
27
+ );
28
+ }
29
+ const faces = [];
30
+ for (let k = 0; k + 1 < rows.length; k++) {
31
+ for (const { a, b, reversed } of matchLines(rows[k], rows[k + 1])) {
32
+ const at = (i) => b.first + (reversed ? n - 1 - i : i);
33
+ for (let i = 0; i + 1 < n; i++) {
34
+ const a0 = a.first + i, a1 = a0 + 1, b0 = at(i), b1 = at(i + 1);
35
+ faces.push(a0, b0, a1, a1, b0, b1);
36
+ }
37
+ }
38
+ }
39
+ if (!faces.length)
40
+ return { vertices: new Float32Array(0), faces: new Uint32Array(0) };
41
+ return { vertices: Float32Array.from(verts), faces: Uint32Array.from(faces) };
42
+ }
43
+ function matchLines(as, bs) {
44
+ const pairs = [];
45
+ as.forEach(
46
+ (a, i) => bs.forEach((b, j) => {
47
+ if (!boxesOverlap(a.box, b.box))
48
+ return;
49
+ const forward = meanDistance(a.probe, b.probe, false);
50
+ const backward = meanDistance(a.probe, b.probe, true);
51
+ pairs.push({ a: i, b: j, cost: Math.min(forward, backward), reversed: backward < forward });
52
+ })
53
+ );
54
+ pairs.sort((p, q) => p.cost - q.cost);
55
+ const usedA = /* @__PURE__ */ new Set();
56
+ const usedB = /* @__PURE__ */ new Set();
57
+ const out = [];
58
+ for (const p of pairs) {
59
+ if (usedA.has(p.a) || usedB.has(p.b))
60
+ continue;
61
+ usedA.add(p.a);
62
+ usedB.add(p.b);
63
+ out.push({ a: as[p.a], b: bs[p.b], reversed: p.reversed });
64
+ }
65
+ return out;
66
+ }
67
+ function meanDistance(a, b, reversed) {
68
+ const n = a.length >> 1;
69
+ let sum = 0;
70
+ for (let i = 0; i < n; i++) {
71
+ const j = reversed ? n - 1 - i : i;
72
+ sum += Math.hypot(a[2 * i] - b[2 * j], a[2 * i + 1] - b[2 * j + 1]);
73
+ }
74
+ return sum / n;
75
+ }
76
+ function resample(line, n) {
77
+ const m = line.length >> 1;
78
+ const cumulative = new Float64Array(m);
79
+ for (let i = 1; i < m; i++) {
80
+ cumulative[i] = cumulative[i - 1] + Math.hypot(line[2 * i] - line[2 * i - 2], line[2 * i + 1] - line[2 * i - 1]);
81
+ }
82
+ const total = cumulative[m - 1];
83
+ const out = new Float64Array(2 * n);
84
+ let seg = 1;
85
+ for (let i = 0; i < n; i++) {
86
+ const d = n === 1 ? 0 : total * i / (n - 1);
87
+ while (seg < m - 1 && cumulative[seg] < d)
88
+ seg++;
89
+ const span = cumulative[seg] - cumulative[seg - 1];
90
+ const t = span > 0 ? Math.min(1, Math.max(0, (d - cumulative[seg - 1]) / span)) : 0;
91
+ out[2 * i] = line[2 * seg - 2] + t * (line[2 * seg] - line[2 * seg - 2]);
92
+ out[2 * i + 1] = line[2 * seg - 1] + t * (line[2 * seg + 1] - line[2 * seg - 1]);
93
+ }
94
+ return out;
95
+ }
96
+ function lineLength(line) {
97
+ let len = 0;
98
+ for (let i = 2; i + 1 < line.length; i += 2)
99
+ len += Math.hypot(line[i] - line[i - 2], line[i + 1] - line[i - 1]);
100
+ return len;
101
+ }
102
+ function lineBox(line) {
103
+ const box = [Infinity, Infinity, -Infinity, -Infinity];
104
+ for (let i = 0; i + 1 < line.length; i += 2) {
105
+ box[0] = Math.min(box[0], line[i]);
106
+ box[1] = Math.min(box[1], line[i + 1]);
107
+ box[2] = Math.max(box[2], line[i]);
108
+ box[3] = Math.max(box[3], line[i + 1]);
109
+ }
110
+ return box;
111
+ }
112
+ function boxesOverlap(a, b) {
113
+ const ga = 0.1 * Math.max(a[2] - a[0], a[3] - a[1]);
114
+ const gb = 0.1 * Math.max(b[2] - b[0], b[3] - b[1]);
115
+ const g = ga + gb;
116
+ return a[0] - g <= b[2] && b[0] - g <= a[2] && a[1] - g <= b[3] && b[1] - g <= a[3];
117
+ }
118
+
119
+ // src/lib/geometry/reconstruct.ts
120
+ function reconstruct(slices, opts = {}) {
121
+ const filled = fill(slices, opts);
122
+ const lines = slices.filter((s) => s.lines?.length);
123
+ if (!lines.length)
124
+ return filled;
125
+ const sheet = loftLines(
126
+ lines.map((s) => ({ z: s.z, lines: s.lines ?? [] })),
127
+ opts.pixelStep ?? 1
128
+ );
129
+ return mergeMeshes(filled, sheet);
130
+ }
131
+ function fill(slices, opts) {
132
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity, z0 = Infinity, z1 = -Infinity;
133
+ for (const s of slices) {
134
+ for (const r of s.rings) {
135
+ for (let i = 0; i + 1 < r.length; i += 2) {
136
+ if (r[i] < x0)
137
+ x0 = r[i];
138
+ if (r[i] > x1)
139
+ x1 = r[i];
140
+ if (r[i + 1] < y0)
141
+ y0 = r[i + 1];
142
+ if (r[i + 1] > y1)
143
+ y1 = r[i + 1];
144
+ }
145
+ }
146
+ if (s.rings.length) {
147
+ z0 = Math.min(z0, s.z);
148
+ z1 = Math.max(z1, s.z);
149
+ }
150
+ }
151
+ if (!(x1 > x0) || !(y1 > y0))
152
+ return { vertices: new Float32Array(0), faces: new Uint32Array(0) };
153
+ const nz = z1 - z0 + 1;
154
+ const maxVoxels = opts.maxVoxels ?? 32e6;
155
+ let step = Math.max(opts.pixelStep ?? 1, 1e-6);
156
+ while (Math.ceil((x1 - x0) / step) * Math.ceil((y1 - y0) / step) * nz > maxVoxels)
157
+ step *= 1.25;
158
+ const nx = Math.max(1, Math.ceil((x1 - x0) / step));
159
+ const ny = Math.max(1, Math.ceil((y1 - y0) / step));
160
+ const mask = new Uint8Array(nx * ny * nz);
161
+ for (const s of slices) {
162
+ for (const rings of featureRings(s.rings, s.ringGroups)) {
163
+ fillSlice(mask, nx, ny, (s.z - z0) * nx * ny, rings, x0, y0, step);
164
+ }
165
+ }
166
+ const mesh = smoothMesh(surfaceNets(mask, [nx, ny, nz], { origin: [0.5, 0.5, 0.5] }), {
167
+ iterations: opts.smooth ?? 2
168
+ });
169
+ const v = mesh.vertices;
170
+ for (let i = 0; i < v.length; i += 3) {
171
+ v[i] = x0 + v[i] * step;
172
+ v[i + 1] = y0 + v[i + 1] * step;
173
+ v[i + 2] = z0 + v[i + 2] - 0.5;
174
+ }
175
+ return mesh;
176
+ }
177
+ function mergeMeshes(a, b) {
178
+ if (!b.faces.length)
179
+ return a;
180
+ if (!a.faces.length)
181
+ return b;
182
+ const vertices = new Float32Array(a.vertices.length + b.vertices.length);
183
+ vertices.set(a.vertices);
184
+ vertices.set(b.vertices, a.vertices.length);
185
+ const faces = new Uint32Array(a.faces.length + b.faces.length);
186
+ faces.set(a.faces);
187
+ const offset = a.vertices.length / 3;
188
+ for (let i = 0; i < b.faces.length; i++)
189
+ faces[a.faces.length + i] = b.faces[i] + offset;
190
+ return { vertices, faces };
191
+ }
192
+ function fillSlice(mask, nx, ny, offset, rings, x0, y0, step) {
193
+ const crossings = [];
194
+ let top = Infinity, bottom = -Infinity;
195
+ for (const r of rings) {
196
+ for (let i = 1; i < r.length; i += 2) {
197
+ if (r[i] < top)
198
+ top = r[i];
199
+ if (r[i] > bottom)
200
+ bottom = r[i];
201
+ }
202
+ }
203
+ const j0 = Math.max(0, Math.floor((top - y0) / step - 0.5));
204
+ const j1 = Math.min(ny - 1, Math.ceil((bottom - y0) / step - 0.5));
205
+ for (let j = j0; j <= j1; j++) {
206
+ const y = y0 + (j + 0.5) * step;
207
+ crossings.length = 0;
208
+ for (const r of rings) {
209
+ const n = r.length >> 1;
210
+ for (let i = 0, k = n - 1; i < n; k = i++) {
211
+ const ya = r[2 * i + 1], yb = r[2 * k + 1];
212
+ if (ya > y !== yb > y) {
213
+ const xa = r[2 * i], xb = r[2 * k];
214
+ crossings.push(xa + (y - ya) * (xb - xa) / (yb - ya));
215
+ }
216
+ }
217
+ }
218
+ if (crossings.length < 2)
219
+ continue;
220
+ crossings.sort((a, b) => a - b);
221
+ const row = offset + j * nx;
222
+ for (let c = 0; c + 1 < crossings.length; c += 2) {
223
+ const from = Math.max(0, Math.ceil((crossings[c] - x0) / step - 0.5));
224
+ const to = Math.min(nx - 1, Math.floor((crossings[c + 1] - x0) / step - 0.5));
225
+ for (let i = from; i <= to; i++)
226
+ mask[row + i] = 1;
227
+ }
228
+ }
229
+ }
230
+ function ringsArea(rings) {
231
+ const boxes = rings.map(ringBox);
232
+ let area = 0;
233
+ for (let i = 0; i < rings.length; i++) {
234
+ const r = rings[i];
235
+ if (r.length < 6)
236
+ continue;
237
+ const x = r[0], y = r[1];
238
+ let depth = 0;
239
+ for (let j = 0; j < rings.length; j++) {
240
+ if (j === i)
241
+ continue;
242
+ const b = boxes[j];
243
+ if (x < b[0] || x > b[2] || y < b[1] || y > b[3])
244
+ continue;
245
+ if (pointInRing(x, y, rings[j]))
246
+ depth++;
247
+ }
248
+ area += (depth % 2 ? -1 : 1) * Math.abs(ringArea(r));
249
+ }
250
+ return Math.max(0, area);
251
+ }
252
+ function featureRings(rings, groups) {
253
+ if (!groups || !rings.length)
254
+ return rings.length ? [rings.slice()] : [];
255
+ const byGroup = /* @__PURE__ */ new Map();
256
+ rings.forEach((r, i) => {
257
+ const g = groups[i] ?? 0;
258
+ const feature = byGroup.get(g);
259
+ if (feature)
260
+ feature.push(r);
261
+ else
262
+ byGroup.set(g, [r]);
263
+ });
264
+ return [...byGroup.values()];
265
+ }
266
+ function ringBox(r) {
267
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
268
+ for (let i = 0; i + 1 < r.length; i += 2) {
269
+ if (r[i] < x0)
270
+ x0 = r[i];
271
+ if (r[i] > x1)
272
+ x1 = r[i];
273
+ if (r[i + 1] < y0)
274
+ y0 = r[i + 1];
275
+ if (r[i + 1] > y1)
276
+ y1 = r[i + 1];
277
+ }
278
+ return [x0, y0, x1, y1];
279
+ }
280
+
281
+ // src/lib/contracts/color.ts
282
+ function rgbToHex(rgb) {
283
+ const h = (n) => clampByte(n).toString(16).padStart(2, "0");
284
+ return `#${h(rgb[0])}${h(rgb[1])}${h(rgb[2])}`;
285
+ }
286
+ function clampByte(n) {
287
+ return Math.max(0, Math.min(255, Math.round(n)));
288
+ }
289
+
290
+ // src/lib/geometry/ring.ts
291
+ function perpDistance(px, py, ax, ay, bx, by) {
292
+ const dx = bx - ax, dy = by - ay;
293
+ const len2 = dx * dx + dy * dy;
294
+ if (len2 === 0)
295
+ return Math.hypot(px - ax, py - ay);
296
+ let t = ((px - ax) * dx + (py - ay) * dy) / len2;
297
+ t = Math.max(0, Math.min(1, t));
298
+ return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
299
+ }
300
+ function simplifyRing(xs, ys, epsilon) {
301
+ const n = xs.length;
302
+ if (n <= 3 || !(epsilon > 0))
303
+ return { xs: xs.slice(), ys: ys.slice() };
304
+ const px = [...xs, xs[0]];
305
+ const py = [...ys, ys[0]];
306
+ const m = px.length;
307
+ const keep = new Array(m).fill(false);
308
+ keep[0] = true;
309
+ keep[m - 1] = true;
310
+ const stack = [[0, m - 1]];
311
+ while (stack.length) {
312
+ const [a, b] = stack.pop();
313
+ let maxD = -1, idx = -1;
314
+ for (let i = a + 1; i < b; i++) {
315
+ const d = perpDistance(px[i], py[i], px[a], py[a], px[b], py[b]);
316
+ if (d > maxD) {
317
+ maxD = d;
318
+ idx = i;
319
+ }
320
+ }
321
+ if (maxD > epsilon && idx > a) {
322
+ keep[idx] = true;
323
+ stack.push([a, idx], [idx, b]);
324
+ }
325
+ }
326
+ const outX = [], outY = [];
327
+ for (let i = 0; i < m - 1; i++) {
328
+ if (keep[i]) {
329
+ outX.push(px[i]);
330
+ outY.push(py[i]);
331
+ }
332
+ }
333
+ return { xs: outX, ys: outY };
334
+ }
335
+
336
+ // src/lib/geometry/polylabel.ts
337
+ function polylabel(rings, precision) {
338
+ const outer = rings[0];
339
+ if (!outer || outer.length < 6)
340
+ return null;
341
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
342
+ for (let i = 0; i + 1 < outer.length; i += 2) {
343
+ if (outer[i] < minX)
344
+ minX = outer[i];
345
+ if (outer[i] > maxX)
346
+ maxX = outer[i];
347
+ if (outer[i + 1] < minY)
348
+ minY = outer[i + 1];
349
+ if (outer[i + 1] > maxY)
350
+ maxY = outer[i + 1];
351
+ }
352
+ const width = maxX - minX;
353
+ const height = maxY - minY;
354
+ const cellSize = Math.min(width, height);
355
+ const eps = precision ?? Math.max(0.5, cellSize / 100);
356
+ if (cellSize <= 0)
357
+ return { x: minX, y: minY, distance: 0 };
358
+ const cell = (x, y, h2) => {
359
+ const d = signedDistance(x, y, rings);
360
+ return { x, y, h: h2, d, max: d + h2 * Math.SQRT2 };
361
+ };
362
+ const queue = new MaxHeap();
363
+ const h = cellSize / 2;
364
+ for (let x = minX; x < maxX; x += cellSize) {
365
+ for (let y = minY; y < maxY; y += cellSize)
366
+ queue.push(cell(x + h, y + h, h));
367
+ }
368
+ let best = cell(minX + width / 2, minY + height / 2, 0);
369
+ const centroid = areaCentroid(outer);
370
+ if (centroid) {
371
+ const c = cell(centroid[0], centroid[1], 0);
372
+ if (c.d > best.d)
373
+ best = c;
374
+ }
375
+ let guard = 0;
376
+ while (queue.size && guard++ < 1e4) {
377
+ const c = queue.pop();
378
+ if (c.d > best.d)
379
+ best = c;
380
+ if (c.max - best.d <= eps)
381
+ continue;
382
+ const q = c.h / 2;
383
+ queue.push(cell(c.x - q, c.y - q, q));
384
+ queue.push(cell(c.x + q, c.y - q, q));
385
+ queue.push(cell(c.x - q, c.y + q, q));
386
+ queue.push(cell(c.x + q, c.y + q, q));
387
+ }
388
+ return { x: best.x, y: best.y, distance: Math.max(0, best.d) };
389
+ }
390
+ function signedDistance(x, y, rings) {
391
+ let inside = false;
392
+ let min = Infinity;
393
+ for (const r of rings) {
394
+ const n = r.length >> 1;
395
+ for (let i = 0, j = n - 1; i < n; j = i++) {
396
+ const ax = r[2 * i], ay = r[2 * i + 1], bx = r[2 * j], by = r[2 * j + 1];
397
+ if (ay > y !== by > y && x < (bx - ax) * (y - ay) / (by - ay) + ax)
398
+ inside = !inside;
399
+ const d = segmentDistanceSq(x, y, ax, ay, bx, by);
400
+ if (d < min)
401
+ min = d;
402
+ }
403
+ }
404
+ return (inside ? 1 : -1) * Math.sqrt(min);
405
+ }
406
+ function segmentDistanceSq(px, py, ax, ay, bx, by) {
407
+ let x = ax, y = ay;
408
+ let dx = bx - x, dy = by - y;
409
+ if (dx !== 0 || dy !== 0) {
410
+ const t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
411
+ if (t > 1) {
412
+ x = bx;
413
+ y = by;
414
+ } else if (t > 0) {
415
+ x += dx * t;
416
+ y += dy * t;
417
+ }
418
+ }
419
+ dx = px - x;
420
+ dy = py - y;
421
+ return dx * dx + dy * dy;
422
+ }
423
+ function areaCentroid(r) {
424
+ const n = r.length >> 1;
425
+ let area = 0, cx = 0, cy = 0;
426
+ for (let i = 0, j = n - 1; i < n; j = i++) {
427
+ const f = r[2 * i] * r[2 * j + 1] - r[2 * j] * r[2 * i + 1];
428
+ cx += (r[2 * i] + r[2 * j]) * f;
429
+ cy += (r[2 * i + 1] + r[2 * j + 1]) * f;
430
+ area += f * 3;
431
+ }
432
+ return area === 0 ? null : [cx / area, cy / area];
433
+ }
434
+ var MaxHeap = class {
435
+ constructor() {
436
+ this.items = [];
437
+ }
438
+ get size() {
439
+ return this.items.length;
440
+ }
441
+ push(c) {
442
+ const a = this.items;
443
+ a.push(c);
444
+ let i = a.length - 1;
445
+ while (i > 0) {
446
+ const p = i - 1 >> 1;
447
+ if (a[p].max >= a[i].max)
448
+ break;
449
+ [a[p], a[i]] = [a[i], a[p]];
450
+ i = p;
451
+ }
452
+ }
453
+ pop() {
454
+ const a = this.items;
455
+ const top = a[0];
456
+ const last = a.pop();
457
+ if (a.length && last) {
458
+ a[0] = last;
459
+ let i = 0;
460
+ for (; ; ) {
461
+ const l = 2 * i + 1, r = l + 1;
462
+ let m = i;
463
+ if (l < a.length && a[l].max > a[m].max)
464
+ m = l;
465
+ if (r < a.length && a[r].max > a[m].max)
466
+ m = r;
467
+ if (m === i)
468
+ break;
469
+ [a[m], a[i]] = [a[i], a[m]];
470
+ i = m;
471
+ }
472
+ }
473
+ return top;
474
+ }
475
+ };
476
+
477
+ // src/lib/region-source/region-tiles.ts
478
+ var REGION_TILE_SIZE = 512;
479
+ function levelTolerance(level) {
480
+ return level <= 0 ? 0 : 2 ** (level - 1);
481
+ }
482
+ function levelMinExtent(level) {
483
+ return level <= 0 ? 0 : 2 ** level;
484
+ }
485
+ function levelTileSize(level, tileSize = REGION_TILE_SIZE) {
486
+ return tileSize * 2 ** level;
487
+ }
488
+ function segmentDistance(px, py, ax, ay, bx, by) {
489
+ const dx = bx - ax, dy = by - ay;
490
+ const len2 = dx * dx + dy * dy;
491
+ const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / len2));
492
+ return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
493
+ }
494
+ function simplifyPolyline(xs, ys, epsilon) {
495
+ const n = xs.length;
496
+ if (n <= 2 || !(epsilon > 0))
497
+ return { xs: xs.slice(), ys: ys.slice() };
498
+ const keep = new Uint8Array(n);
499
+ keep[0] = keep[n - 1] = 1;
500
+ const stack = [[0, n - 1]];
501
+ while (stack.length) {
502
+ const [a, b] = stack.pop();
503
+ let maxD = -1, idx = -1;
504
+ for (let i = a + 1; i < b; i++) {
505
+ const d = segmentDistance(xs[i], ys[i], xs[a], ys[a], xs[b], ys[b]);
506
+ if (d > maxD) {
507
+ maxD = d;
508
+ idx = i;
509
+ }
510
+ }
511
+ if (maxD > epsilon) {
512
+ keep[idx] = 1;
513
+ stack.push([a, idx], [idx, b]);
514
+ }
515
+ }
516
+ const ox = [], oy = [];
517
+ for (let i = 0; i < n; i++) {
518
+ if (keep[i]) {
519
+ ox.push(xs[i]);
520
+ oy.push(ys[i]);
521
+ }
522
+ }
523
+ return { xs: ox, ys: oy };
524
+ }
525
+ function simplifyRings(rings, open, level) {
526
+ const tol = levelTolerance(level);
527
+ const minExtent = levelMinExtent(level);
528
+ const minVertices = open ? 2 : 3;
529
+ const out = [];
530
+ for (const ring of rings) {
531
+ const n = ring.length >> 1;
532
+ if (n < minVertices)
533
+ continue;
534
+ if (tol === 0) {
535
+ out.push(Float32Array.from(ring));
536
+ continue;
537
+ }
538
+ const xs = new Array(n), ys = new Array(n);
539
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
540
+ for (let i = 0; i < n; i++) {
541
+ const x = ring[2 * i], y = ring[2 * i + 1];
542
+ xs[i] = x;
543
+ ys[i] = y;
544
+ if (x < x0)
545
+ x0 = x;
546
+ if (x > x1)
547
+ x1 = x;
548
+ if (y < y0)
549
+ y0 = y;
550
+ if (y > y1)
551
+ y1 = y;
552
+ }
553
+ if (Math.max(x1 - x0, y1 - y0) < minExtent)
554
+ continue;
555
+ const s = open ? simplifyPolyline(xs, ys, tol) : simplifyRing(xs, ys, tol);
556
+ if (s.xs.length < minVertices)
557
+ continue;
558
+ const flat = new Float32Array(s.xs.length * 2);
559
+ for (let i = 0; i < s.xs.length; i++) {
560
+ flat[2 * i] = s.xs[i];
561
+ flat[2 * i + 1] = s.ys[i];
562
+ }
563
+ out.push(flat);
564
+ }
565
+ return out;
566
+ }
567
+ var SIMPLIFIED_CACHE_LIMIT = 2e5;
568
+ var RegionFeatureTable = class _RegionFeatureTable {
569
+ constructor(raw, tileSize = REGION_TILE_SIZE) {
570
+ this.raw = raw;
571
+ this.tileSize = tileSize;
572
+ this.ids = [];
573
+ this.labels = [];
574
+ this.colors = [];
575
+ this.planes = [];
576
+ this.boxes = [];
577
+ this.rings = [];
578
+ this.open = [];
579
+ /** Per feature, its parts as ring indices (the exterior first, then its holes). */
580
+ this.parts = [];
581
+ this.labelPoints = /* @__PURE__ */ new Map();
582
+ /** Each feature's area, worked out the first time its row is asked for. */
583
+ this.areas = /* @__PURE__ */ new Map();
584
+ this.byId = /* @__PURE__ */ new Map();
585
+ this.buckets = /* @__PURE__ */ new Map();
586
+ this.simplified = /* @__PURE__ */ new Map();
587
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
588
+ raw.forEach((feature, i) => {
589
+ const own = featureIdOf(feature);
590
+ let id = own;
591
+ if (id === void 0 || this.byId.has(id)) {
592
+ id = `#${i}`;
593
+ for (let k = 1; this.byId.has(id); k++)
594
+ id = `#${i}-${k}`;
595
+ }
596
+ this.byId.set(id, i);
597
+ this.ids.push(id);
598
+ const { label, color } = classificationOf(feature);
599
+ this.labels.push(label);
600
+ this.colors.push(color);
601
+ this.planes.push(feature?.geometry?.plane?.z ?? 0);
602
+ const { rings, open, parts } = ringsOf(feature);
603
+ this.rings.push(rings);
604
+ this.open.push(open);
605
+ this.parts.push(parts);
606
+ const box = ringsBox(rings);
607
+ this.boxes.push(box);
608
+ if (rings.length) {
609
+ x0 = Math.min(x0, box[0]);
610
+ y0 = Math.min(y0, box[1]);
611
+ x1 = Math.max(x1, box[2]);
612
+ y1 = Math.max(y1, box[3]);
613
+ }
614
+ });
615
+ this.bounds = x0 <= x1 ? [x0, y0, x1, y1] : null;
616
+ const extent = this.bounds ? Math.max(Math.abs(x0), Math.abs(y0), Math.abs(x1), Math.abs(y1)) : 0;
617
+ this.levels = 1 + Math.max(0, Math.ceil(Math.log2(Math.max(extent, tileSize) / tileSize)));
618
+ }
619
+ /** Parse a GeoJSON FeatureCollection string. Throws on invalid JSON or no `features`. */
620
+ static fromGeoJson(text, tileSize = REGION_TILE_SIZE) {
621
+ const parsed = JSON.parse(text);
622
+ if (!parsed || !Array.isArray(parsed.features)) {
623
+ throw new Error("Invalid GeoJson file: must contain the 'features' key.");
624
+ }
625
+ const table = new _RegionFeatureTable(parsed.features, tileSize);
626
+ table.describe(parsed.classHierarchy, parsed.style);
627
+ return table;
628
+ }
629
+ /** Attach a class hierarchy and a style hint (each ignored when not well formed). */
630
+ describe(hierarchy, style) {
631
+ this.hierarchy = readHierarchy(hierarchy) ?? this.hierarchy;
632
+ this.style = readStyle(style) ?? this.style;
633
+ }
634
+ /**
635
+ * The outlines of every feature of these classes, plane by plane, simplified for `level` (what a
636
+ * 3D reconstruction of the classes stacks), without the `exclude`d ids: closed features' rings,
637
+ * open features' polylines as `lines`, each ring with its feature (`ringGroups`). Planes without
638
+ * one are left out. `classes` null reads every labelled feature, each outline with its class
639
+ * (`ringLabels`, `lineLabels`).
640
+ */
641
+ classOutlines(classes, level, exclude = []) {
642
+ const wanted = classes ? new Set(classes) : null;
643
+ const skip = new Set(exclude);
644
+ const byPlane = /* @__PURE__ */ new Map();
645
+ for (let i = 0; i < this.ids.length; i++) {
646
+ const label = this.labels[i];
647
+ if (!label || wanted && !wanted.has(label) || skip.has(this.ids[i]))
648
+ continue;
649
+ const rings = this.ringsAt(i, level);
650
+ if (!rings.length)
651
+ continue;
652
+ const z = this.planes[i];
653
+ let plane = byPlane.get(z);
654
+ if (!plane)
655
+ byPlane.set(z, plane = { z, rings: [] });
656
+ if (this.open[i]) {
657
+ (plane.lines ?? (plane.lines = [])).push(...rings);
658
+ if (!wanted)
659
+ (plane.lineLabels ?? (plane.lineLabels = [])).push(...rings.map(() => label));
660
+ } else {
661
+ plane.rings.push(...rings);
662
+ (plane.ringGroups ?? (plane.ringGroups = [])).push(...rings.map(() => i));
663
+ if (!wanted)
664
+ (plane.ringLabels ?? (plane.ringLabels = [])).push(...rings.map(() => label));
665
+ }
666
+ }
667
+ return Array.from(byPlane.values()).sort((a, b) => a.z - b.z);
668
+ }
669
+ /** Features per class on plane `z` (null: on every plane), without the `exclude`d ids. */
670
+ classCounts(z, exclude = []) {
671
+ const skip = new Set(exclude);
672
+ const counts = {};
673
+ for (let i = 0; i < this.ids.length; i++) {
674
+ const label = this.labels[i];
675
+ if (z !== null && this.planes[i] !== z || !label || skip.has(this.ids[i]))
676
+ continue;
677
+ counts[label] = (counts[label] ?? 0) + 1;
678
+ }
679
+ return counts;
680
+ }
681
+ info() {
682
+ const classColors = {};
683
+ this.labels.forEach((label, i) => {
684
+ const color = this.colors[i];
685
+ if (label && color && !(label in classColors))
686
+ classColors[label] = color;
687
+ });
688
+ return {
689
+ featureCount: this.ids.length,
690
+ bounds: this.bounds,
691
+ tileSize: this.tileSize,
692
+ levels: this.levels,
693
+ planes: Array.from(new Set(this.planes)).sort((a, b) => a - b),
694
+ classColors,
695
+ ...this.hierarchy ? { hierarchy: this.hierarchy } : {},
696
+ ...this.style ? { style: this.style } : {}
697
+ };
698
+ }
699
+ /** One tile of plane `z`. */
700
+ tile(z, level, gx, gy) {
701
+ const { cells, wide } = this.bucketsFor(z, level);
702
+ const bucket = cells.get(`${gx}/${gy}`) ?? [];
703
+ const size = levelTileSize(level, this.tileSize);
704
+ const features = [];
705
+ const order = [];
706
+ for (let a = 0, w = 0; a < bucket.length || w < wide.length; ) {
707
+ if (w >= wide.length || a < bucket.length && bucket[a] < wide[w])
708
+ order.push(bucket[a++]);
709
+ else {
710
+ const i = wide[w++];
711
+ if (cellMeets(this.boxes[i], size, gx, gy))
712
+ order.push(i);
713
+ }
714
+ }
715
+ for (const i of order) {
716
+ const rings = this.ringsAt(i, level);
717
+ if (!rings.length)
718
+ continue;
719
+ const labels = this.labelsAt(i, level);
720
+ features.push({
721
+ id: this.ids[i],
722
+ label: this.labels[i],
723
+ color: this.colors[i],
724
+ bbox: this.boxes[i],
725
+ rings,
726
+ open: this.open[i],
727
+ ...labels ? { labels } : {}
728
+ });
729
+ }
730
+ return { z, level, gx, gy, features };
731
+ }
732
+ /** The raw GeoJSON features with these ids, in the order asked (unknown ids skipped). */
733
+ features(ids) {
734
+ const out = [];
735
+ for (const id of ids) {
736
+ const i = this.byId.get(id);
737
+ if (i !== void 0)
738
+ out.push({ id, feature: this.raw[i] });
739
+ }
740
+ return out;
741
+ }
742
+ /** Feature `i`'s area in px² (0 for a polyline). */
743
+ areaOf(i) {
744
+ let area = this.areas.get(i);
745
+ if (area === void 0) {
746
+ area = this.open[i] ? 0 : ringsArea(this.rings[i]);
747
+ this.areas.set(i, area);
748
+ }
749
+ return area;
750
+ }
751
+ /** A page of plane `z`'s features, in file order. */
752
+ index(z, query) {
753
+ const exclude = new Set(query.exclude ?? []);
754
+ const classes = query.classes ? new Set(query.classes) : null;
755
+ const needle = query.label?.trim().toLowerCase() || null;
756
+ const rows = [];
757
+ let total = 0;
758
+ for (let i = 0; i < this.ids.length; i++) {
759
+ if (this.planes[i] !== z || exclude.has(this.ids[i]))
760
+ continue;
761
+ if (needle && !(this.labels[i] ?? "").toLowerCase().includes(needle))
762
+ continue;
763
+ if (classes && !classes.has(this.labels[i] ?? ""))
764
+ continue;
765
+ if (total >= query.offset && rows.length < query.limit) {
766
+ rows.push({
767
+ id: this.ids[i],
768
+ label: this.labels[i],
769
+ color: this.colors[i],
770
+ bbox: this.boxes[i],
771
+ area: this.areaOf(i)
772
+ });
773
+ }
774
+ total++;
775
+ }
776
+ return { total, rows };
777
+ }
778
+ /**
779
+ * The source as a GeoJSON FeatureCollection string, through the working set: features whose
780
+ * id is in `exclude` (taken for editing, or deleted) are left out, `extra` (the working set's
781
+ * features) appended. Every feature keeps an `id`; the set keeps its hierarchy and style.
782
+ */
783
+ exportGeoJson(exclude, extra) {
784
+ const skip = new Set(exclude);
785
+ const parts = [];
786
+ this.raw.forEach((feature, i) => {
787
+ const id = this.ids[i];
788
+ if (skip.has(id))
789
+ return;
790
+ parts.push(JSON.stringify(feature.id === void 0 ? { type: "Feature", id, ...feature } : feature));
791
+ });
792
+ for (const f of extra)
793
+ parts.push(JSON.stringify(f));
794
+ return featureCollectionJson(parts, this.hierarchy, this.style);
795
+ }
796
+ /**
797
+ * Feature `i`'s label points (one per part with room for a label at `level`: a part under the
798
+ * level's minimum extent is not drawn there), from the exact geometry, computed once.
799
+ */
800
+ labelsAt(i, level) {
801
+ if (this.open[i] || !this.labels[i])
802
+ return null;
803
+ let points = this.labelPoints.get(i);
804
+ if (!points) {
805
+ const out = [];
806
+ for (const part of this.parts[i]) {
807
+ const pole = polylabel(part.map((r) => this.rings[i][r]));
808
+ if (pole && pole.distance > 0)
809
+ out.push(pole.x, pole.y, pole.distance);
810
+ }
811
+ points = Float32Array.from(out);
812
+ this.labelPoints.set(i, points);
813
+ }
814
+ const min = levelMinExtent(level) / 2;
815
+ if (!min)
816
+ return points.length ? points : null;
817
+ const kept = [];
818
+ for (let k = 0; k + 2 < points.length; k += 3)
819
+ if (points[k + 2] >= min)
820
+ kept.push(points[k], points[k + 1], points[k + 2]);
821
+ return kept.length ? Float32Array.from(kept) : null;
822
+ }
823
+ /** Feature `i`'s rings at `level`, simplified once and cached. */
824
+ ringsAt(i, level) {
825
+ const key = `${level}/${i}`;
826
+ let rings = this.simplified.get(key);
827
+ if (!rings) {
828
+ rings = simplifyRings(this.rings[i], this.open[i], level);
829
+ if (this.simplified.size >= SIMPLIFIED_CACHE_LIMIT) {
830
+ const oldest = this.simplified.keys().next().value;
831
+ this.simplified.delete(oldest);
832
+ }
833
+ this.simplified.set(key, rings);
834
+ }
835
+ return rings;
836
+ }
837
+ /** Entries in plane `z`'s level-`level` index: cell listings plus wide features (tests). */
838
+ indexEntries(z, level) {
839
+ const { cells, wide } = this.bucketsFor(z, level);
840
+ let n = wide.length;
841
+ for (const list of cells.values())
842
+ n += list.length;
843
+ return n;
844
+ }
845
+ /**
846
+ * Plane `z`'s level-`level` index, built on first use: features by tile cell (`gx/gy` → feature
847
+ * indices), and the wide ones (more than {@link MAX_CELLS_PER_FEATURE} cells, or past the
848
+ * number range), tested against a tile when it is asked for.
849
+ */
850
+ bucketsFor(z, level) {
851
+ const key = `${z}/${level}`;
852
+ let index = this.buckets.get(key);
853
+ if (index)
854
+ return index;
855
+ const cells = /* @__PURE__ */ new Map();
856
+ const wide = [];
857
+ const size = levelTileSize(level, this.tileSize);
858
+ const minExtent = levelMinExtent(level);
859
+ for (let i = 0; i < this.ids.length; i++) {
860
+ if (this.planes[i] !== z || !this.rings[i].length)
861
+ continue;
862
+ const b = this.boxes[i];
863
+ if (Math.max(b[2] - b[0], b[3] - b[1]) < minExtent)
864
+ continue;
865
+ const gx0 = Math.floor(b[0] / size), gx1 = Math.floor(b[2] / size), gy0 = Math.floor(b[1] / size), gy1 = Math.floor(b[3] / size);
866
+ if ([gx0, gx1, gy0, gy1].some(Number.isNaN))
867
+ continue;
868
+ const count = (gx1 - gx0 + 1) * (gy1 - gy0 + 1);
869
+ if (!(count <= MAX_CELLS_PER_FEATURE) || ![gx0, gx1, gy0, gy1].every(Number.isSafeInteger)) {
870
+ wide.push(i);
871
+ continue;
872
+ }
873
+ for (let gy = gy0; gy <= gy1; gy++) {
874
+ for (let gx = gx0; gx <= gx1; gx++) {
875
+ const id = `${gx}/${gy}`;
876
+ const list = cells.get(id);
877
+ if (list)
878
+ list.push(i);
879
+ else
880
+ cells.set(id, [i]);
881
+ }
882
+ }
883
+ }
884
+ index = { cells, wide };
885
+ this.buckets.set(key, index);
886
+ return index;
887
+ }
888
+ };
889
+ var MAX_CELLS_PER_FEATURE = 64;
890
+ function cellMeets(b, size, gx, gy) {
891
+ return Math.floor(b[0] / size) <= gx && gx <= Math.floor(b[2] / size) && Math.floor(b[1] / size) <= gy && gy <= Math.floor(b[3] / size);
892
+ }
893
+ function featureIdOf(feature) {
894
+ const id = feature?.id ?? feature?.properties?.id;
895
+ return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
896
+ }
897
+ function classificationOf(feature) {
898
+ const c = feature?.properties?.classification;
899
+ if (!c)
900
+ return { label: "Cell", color: null };
901
+ if (typeof c === "string")
902
+ return { label: c, color: null };
903
+ const color = Array.isArray(c.color) && c.color.length >= 3 ? rgbToHex([c.color[0], c.color[1], c.color[2]]) : null;
904
+ return { label: c.name ?? null, color };
905
+ }
906
+ function ringsOf(feature) {
907
+ const g = feature?.geometry;
908
+ const coords = g?.coordinates;
909
+ if (!g || !Array.isArray(coords))
910
+ return { rings: [], open: false, parts: [] };
911
+ const polygons = (list) => {
912
+ const rings = [];
913
+ const parts = [];
914
+ for (const polygon of list) {
915
+ const part = [];
916
+ for (const r of polygon) {
917
+ part.push(rings.length);
918
+ rings.push(flatRing(r, true));
919
+ }
920
+ if (part.length)
921
+ parts.push(part);
922
+ }
923
+ return { rings, open: false, parts };
924
+ };
925
+ switch (g.type) {
926
+ case "Polygon":
927
+ return polygons([coords]);
928
+ case "MultiPolygon":
929
+ return polygons(coords);
930
+ case "LineString":
931
+ return { rings: [flatRing(coords, false)], open: true, parts: [] };
932
+ case "MultiLineString":
933
+ return { rings: coords.map((r) => flatRing(r, false)), open: true, parts: [] };
934
+ default:
935
+ return { rings: [], open: false, parts: [] };
936
+ }
937
+ }
938
+ function featureCollectionJson(features, hierarchy, style) {
939
+ const head = ['"type":"FeatureCollection"'];
940
+ if (hierarchy?.length)
941
+ head.push(`"classHierarchy":${JSON.stringify(hierarchy)}`);
942
+ if (style)
943
+ head.push(`"style":${JSON.stringify(style)}`);
944
+ return `{${head.join(",")},"features":[${features.join(",")}]}`;
945
+ }
946
+ function readHierarchy(raw) {
947
+ if (!Array.isArray(raw))
948
+ return void 0;
949
+ const out = [];
950
+ for (const n of raw) {
951
+ if (!n || typeof n.name !== "string")
952
+ continue;
953
+ const color = Array.isArray(n.color) ? rgbToHex([+n.color[0], +n.color[1], +n.color[2]]) : n.color;
954
+ out.push({
955
+ name: n.name,
956
+ ...typeof n.title === "string" ? { title: n.title } : {},
957
+ ...typeof n.parent === "string" ? { parent: n.parent } : {},
958
+ ...typeof color === "string" ? { color } : {},
959
+ ...typeof n.order === "number" ? { order: n.order } : {}
960
+ });
961
+ }
962
+ return out.length ? out : void 0;
963
+ }
964
+ function readStyle(raw) {
965
+ if (!raw || typeof raw !== "object")
966
+ return void 0;
967
+ const r = raw;
968
+ const style = {};
969
+ if (typeof r.fillOpacity === "number")
970
+ style.fillOpacity = Math.max(0, Math.min(1, r.fillOpacity));
971
+ if (typeof r.stroke === "string")
972
+ style.stroke = r.stroke;
973
+ if (typeof r.strokeWidth === "number" && r.strokeWidth > 0)
974
+ style.strokeWidth = r.strokeWidth;
975
+ if (typeof r.labels === "boolean")
976
+ style.labels = r.labels;
977
+ if (typeof r.highlight === "string")
978
+ style.highlight = r.highlight;
979
+ return Object.keys(style).length ? style : void 0;
980
+ }
981
+ function flatRing(points, closed) {
982
+ let n = points?.length ?? 0;
983
+ if (closed && n > 1) {
984
+ const a = points[0], b = points[n - 1];
985
+ if (a[0] === b[0] && a[1] === b[1])
986
+ n--;
987
+ }
988
+ const out = new Float64Array(n * 2);
989
+ for (let i = 0; i < n; i++) {
990
+ out[2 * i] = +points[i][0];
991
+ out[2 * i + 1] = +points[i][1];
992
+ }
993
+ return out;
994
+ }
995
+ function ringsBox(rings) {
996
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
997
+ for (const r of rings) {
998
+ for (let i = 0; i + 1 < r.length; i += 2) {
999
+ if (r[i] < x0)
1000
+ x0 = r[i];
1001
+ if (r[i] > x1)
1002
+ x1 = r[i];
1003
+ if (r[i + 1] < y0)
1004
+ y0 = r[i + 1];
1005
+ if (r[i + 1] > y1)
1006
+ y1 = r[i + 1];
1007
+ }
1008
+ }
1009
+ return [x0, y0, x1, y1];
1010
+ }
1011
+
1012
+ // src/lib/region-source/region-source-ops.ts
1013
+ var RegionSourceTables = class {
1014
+ constructor() {
1015
+ this.tables = /* @__PURE__ */ new Map();
1016
+ this.nextHandle = 1;
1017
+ }
1018
+ run(request) {
1019
+ return this.dispatch(request);
1020
+ }
1021
+ dispatch(r) {
1022
+ switch (r.op) {
1023
+ case "open": {
1024
+ const table = RegionFeatureTable.fromGeoJson(r.text, r.tileSize);
1025
+ const handle = this.nextHandle++;
1026
+ this.tables.set(handle, table);
1027
+ return { handle, info: table.info() };
1028
+ }
1029
+ case "tile":
1030
+ return this.table(r.handle).tile(r.z, r.level, r.gx, r.gy);
1031
+ case "features":
1032
+ return this.table(r.handle).features(r.ids);
1033
+ case "index":
1034
+ return this.table(r.handle).index(r.z, r.query);
1035
+ case "classes":
1036
+ return this.table(r.handle).classCounts(r.z, r.exclude);
1037
+ case "outlines":
1038
+ return this.table(r.handle).classOutlines(r.classes, r.level, r.exclude);
1039
+ case "mesh":
1040
+ return reconstruct(r.slices, r.options);
1041
+ case "export":
1042
+ return this.table(r.handle).exportGeoJson(r.exclude, r.extra);
1043
+ case "close":
1044
+ this.tables.delete(r.handle);
1045
+ return null;
1046
+ }
1047
+ }
1048
+ table(handle) {
1049
+ const table = this.tables.get(handle);
1050
+ if (!table)
1051
+ throw new Error(`No open region source ${handle}.`);
1052
+ return table;
1053
+ }
1054
+ };
1055
+
1056
+ // src/lib/region-source/region-source.worker.ts
1057
+ var tables = new RegionSourceTables();
1058
+ self.onmessage = (event) => {
1059
+ const { id } = event.data;
1060
+ let reply;
1061
+ try {
1062
+ reply = { id, ok: true, result: tables.run(event.data) };
1063
+ } catch (err) {
1064
+ reply = { id, ok: false, error: err?.message ?? String(err) };
1065
+ }
1066
+ self.postMessage(reply);
1067
+ };