@nika-js/onlymap 0.6.0 → 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.
Files changed (80) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.md +39 -14
  3. package/THIRD-PARTY-LICENSES.md +27 -0
  4. package/dist/{LercDecode.es-fbo6h5rt.js → LercDecode.es-BOJJr6Gx.js} +1 -1
  5. package/dist/{basemap-CprSan9q.js → basemap-BofXgXxy.js} +13 -3
  6. package/dist/basemap.d.ts +10 -1
  7. package/dist/cityjson-D_V5GY8b.js +332 -0
  8. package/dist/clip-box-controller.d.ts +94 -0
  9. package/dist/clip-box.d.ts +109 -0
  10. package/dist/crs-Ciu7Xs7a.js +108 -0
  11. package/dist/crs.d.ts +50 -0
  12. package/dist/ctx.d.ts +8 -1
  13. package/dist/data-layer.d.ts +11 -3
  14. package/dist/draw-controller.d.ts +75 -1
  15. package/dist/draw.d.ts +22 -4
  16. package/dist/elements/om-map.d.ts +45 -2
  17. package/dist/elements/om-overlay.d.ts +13 -1
  18. package/dist/elements/om-widget.d.ts +4 -0
  19. package/dist/feature-colors.d.ts +41 -0
  20. package/dist/geodesy.d.ts +29 -3
  21. package/dist/{geoparquet-Bm8pfxyo.js → geoparquet-DapHATA0.js} +1 -1
  22. package/dist/html-data.d.ts +2 -2
  23. package/dist/ifc-Dc-TdSJr.js +877 -0
  24. package/dist/ifc.d.ts +406 -0
  25. package/dist/{index-Bz-rbmhj.js → index-7B-6Cbzu.js} +31619 -26565
  26. package/dist/{index-DSRIZR81.js → index-BF9iO8Tq.js} +1 -1
  27. package/dist/{index-GOwMjqm6.js → index-BmX6IId3.js} +1 -1
  28. package/dist/{index-UzmNhuS0.js → index-SPJVn_n_.js} +1 -1
  29. package/dist/{index-CCQttJzO.js → index-xmJjZxQJ.js} +2 -2
  30. package/dist/index.d.ts +13 -2
  31. package/dist/ir-snapshot.d.ts +3 -1
  32. package/dist/layer-registry.d.ts +14 -0
  33. package/dist/layers/bim-layer.d.ts +127 -0
  34. package/dist/layers/feature-mesh-layer.d.ts +288 -0
  35. package/dist/layers/gltf-scene-walk.d.ts +55 -0
  36. package/dist/layers/popup-layer.d.ts +13 -0
  37. package/dist/legend-spec.d.ts +1 -1
  38. package/dist/{lerc-CuifOkoA.js → lerc-C6k7EzSN.js} +2 -2
  39. package/dist/license.d.ts +12 -5
  40. package/dist/measure-controller.d.ts +458 -4
  41. package/dist/meshopt_simplifier-Co6uRDDA.js +370 -0
  42. package/dist/onlymap.standalone.js +57077 -50729
  43. package/dist/onlymapjs.js +72 -69
  44. package/dist/parse-manifest.d.ts +3 -0
  45. package/dist/programmatic.d.ts +39 -5
  46. package/dist/{raster-dysYIQ_k.js → raster-Cuzhibe2.js} +2 -2
  47. package/dist/{raster-pipeline-ZdCOGfqV.js → raster-pipeline-DLrnJK8y.js} +1 -1
  48. package/dist/react/om-layer.d.ts +6 -1
  49. package/dist/react/om-overlay.d.ts +9 -0
  50. package/dist/react.js +176 -169
  51. package/dist/region-export-controller.d.ts +37 -0
  52. package/dist/region-export.d.ts +56 -0
  53. package/dist/runtime-core.d.ts +260 -3
  54. package/dist/selection.d.ts +44 -1
  55. package/dist/site-placement.d.ts +107 -0
  56. package/dist/snapping.d.ts +88 -0
  57. package/dist/terrain-heightfield.d.ts +53 -0
  58. package/dist/terrain-sample.d.ts +30 -0
  59. package/dist/terrain.d.ts +6 -1
  60. package/dist/testing.d.ts +6 -2
  61. package/dist/tile3d-metadata.d.ts +185 -0
  62. package/dist/units.d.ts +27 -0
  63. package/dist/version.d.ts +1 -1
  64. package/dist/volumetrics-run.d.ts +13 -0
  65. package/dist/volumetrics-worker.d.ts +1 -0
  66. package/dist/volumetrics.d.ts +201 -0
  67. package/dist/widget-registry.d.ts +11 -0
  68. package/dist/widgets/ifc-clash.d.ts +18 -0
  69. package/dist/widgets/ifc.d.ts +41 -0
  70. package/dist/{zarr-OewK7k2K.js → zarr-CUzEX8fB.js} +79 -66
  71. package/docs/3d-assets.md +48 -0
  72. package/docs/live-data.md +9 -1
  73. package/docs/testing.md +4 -2
  74. package/llms.txt +10 -5
  75. package/onlymapjs.html-data.json +313 -2
  76. package/package.json +6 -2
  77. package/skills/onlymapjs/SKILL.md +14 -2
  78. package/skills/onlymapjs/references/react.md +2 -2
  79. package/skills/onlymapjs/references/syntax.md +314 -15
  80. package/dist/cityjson-urQeujQv.js +0 -407
@@ -0,0 +1,332 @@
1
+ import { C as F, a as q } from "./crs-Ciu7Xs7a.js";
2
+ const j = 0.5;
3
+ function G(e, t) {
4
+ if (!Array.isArray(e)) return [];
5
+ const n = t?.scale?.[0] ?? 1, o = t?.scale?.[1] ?? 1, s = t?.scale?.[2] ?? 1, a = t?.translate?.[0] ?? 0, i = t?.translate?.[1] ?? 0, f = t?.translate?.[2] ?? 0, r = new Array(e.length);
6
+ for (let c = 0; c < e.length; c++) {
7
+ const u = e[c];
8
+ r[c] = [u[0] * n + a, u[1] * o + i, (u[2] ?? 0) * s + f];
9
+ }
10
+ return r;
11
+ }
12
+ const T = {
13
+ MultiPoint: 1,
14
+ MultiLineString: 2,
15
+ MultiSurface: 3,
16
+ CompositeSurface: 3,
17
+ Solid: 4,
18
+ MultiSolid: 5,
19
+ CompositeSolid: 5
20
+ };
21
+ function J(e, t) {
22
+ let n = e;
23
+ for (let o = 0; o < t; o++) n = n.flat();
24
+ return n;
25
+ }
26
+ function N(e) {
27
+ const t = T[e.type ?? ""];
28
+ return !t || t < 3 || !Array.isArray(e.boundaries) ? [] : J(e.boundaries, t - 3);
29
+ }
30
+ function X(e, t) {
31
+ const n = new Array(t).fill(void 0), o = T[e.type ?? ""], s = e.semantics?.surfaces, a = e.semantics?.values;
32
+ if (!o || !Array.isArray(s) || !Array.isArray(a)) return n;
33
+ const i = J(a, o - 3);
34
+ for (let f = 0; f < t; f++) {
35
+ const r = i[f];
36
+ typeof r == "number" && (n[f] = s[r]?.type);
37
+ }
38
+ return n;
39
+ }
40
+ function _(e) {
41
+ const t = typeof e == "number" ? e : Number.parseFloat(String(e ?? ""));
42
+ return Number.isFinite(t) ? t : -1;
43
+ }
44
+ function K(e, t) {
45
+ if (!Array.isArray(e)) return;
46
+ const n = e.filter((o) => N(o).length > 0);
47
+ if (n.length !== 0) {
48
+ if (t !== void 0) {
49
+ const o = n.find((s) => _(s.lod) === t);
50
+ if (o) return o;
51
+ }
52
+ return n.reduce((o, s) => _(s.lod) > _(o.lod) ? s : o);
53
+ }
54
+ }
55
+ const Q = /EPSG.*?(\d{4,6})\D*$/i;
56
+ function tt(e) {
57
+ if (!e) return;
58
+ const t = Number(Q.exec(e)?.[1]);
59
+ return Number.isFinite(t) ? t : void 0;
60
+ }
61
+ let P = !1;
62
+ async function U(e, t) {
63
+ const n = { toLngLat: (r, c) => [r, c], projected: !1 }, o = tt(e);
64
+ if (o === void 0)
65
+ return P || (P = !0, console.warn(
66
+ `[onlymapjs] data="${t}": CityJSON has no metadata.referenceSystem — assuming coordinates are already WGS84 lon/lat. Add a referenceSystem to the file if they are not.`
67
+ )), n;
68
+ const s = q[o] ?? o, a = F[s];
69
+ if (!a)
70
+ throw new Error(
71
+ `CityJSON declares EPSG:${o}${s === o ? "" : ` (horizontal component EPSG:${s})`}, which is not one of the bundled coordinate systems (${Object.keys(F).join(", ")}). Reproject the file to EPSG:4326 server-side (cjio --reproject 4326), or claim the URL with OmMap.registerFormat() and supply your own projection.`
72
+ );
73
+ if (a.geographic)
74
+ return a.swapXY ? { toLngLat: (r, c) => [c, r], projected: !1 } : n;
75
+ const i = (await import("./index-CW1n5LdO.js")).default, f = i(a.def, "+proj=longlat +datum=WGS84 +no_defs");
76
+ return {
77
+ toLngLat: (r, c) => {
78
+ const [u, p] = f.forward(a.swapXY ? [c, r] : [r, c]);
79
+ return [u, p];
80
+ },
81
+ projected: !0
82
+ };
83
+ }
84
+ function I(e) {
85
+ let t = 0, n = 0, o = 0;
86
+ for (let s = 0; s < e.length; s++) {
87
+ const a = e[s], i = e[(s + 1) % e.length];
88
+ t += (a[1] - i[1]) * (a[2] + i[2]), n += (a[2] - i[2]) * (a[0] + i[0]), o += (a[0] - i[0]) * (a[1] + i[1]);
89
+ }
90
+ return Math.hypot(t, n, o) / 2;
91
+ }
92
+ function et(e) {
93
+ const t = e.slice().sort((o, s) => o - s), n = t.length >> 1;
94
+ return t.length % 2 ? t[n] : (t[n - 1] + t[n]) / 2;
95
+ }
96
+ function B(e, t, n, o, s) {
97
+ const a = K(t.geometry, s);
98
+ if (!a) return;
99
+ const i = N(a);
100
+ if (i.length === 0) return;
101
+ const f = X(a, i.length), r = i.map(
102
+ (d) => d.map((y) => y.map((l) => n[l]).filter((l) => l !== void 0))
103
+ );
104
+ let c = 1 / 0, u = -1 / 0;
105
+ for (const d of r)
106
+ for (const y of d)
107
+ for (const [, , l] of y)
108
+ l < c && (c = l), l > u && (u = l);
109
+ if (!Number.isFinite(c)) return;
110
+ const p = c, g = f.some((d) => d === "RoofSurface"), h = [];
111
+ let S = 1 / 0, m = -1 / 0;
112
+ for (let d = 0; d < r.length; d++)
113
+ if (!(g && f[d] !== "RoofSurface"))
114
+ for (const y of r[d])
115
+ for (const [, , l] of y)
116
+ (g || l > p + j) && (h.push(l), l < S && (S = l), l > m && (m = l));
117
+ h.length === 0 && (h.push(u), S = u, m = u);
118
+ let w = f.indexOf("GroundSurface");
119
+ if (w < 0) {
120
+ let d = -1;
121
+ for (let y = 0; y < r.length; y++) {
122
+ const l = r[y][0];
123
+ if (!l || l.length < 3) continue;
124
+ const b = l.every(([, , R]) => R <= p + j), C = I(l) * (b ? 1e3 : 1);
125
+ C > d && (d = C, w = y);
126
+ }
127
+ }
128
+ if (w < 0) return;
129
+ const O = r[w];
130
+ if (!O[0] || O[0].length < 3) return;
131
+ const [, k] = o.toLngLat(O[0][0][0], O[0][0][1]), z = 111320 * Math.cos(k * Math.PI / 180), Y = 110540, V = (d) => o.projected ? d : d.map(([y, l, b]) => {
132
+ const [C, R] = o.toLngLat(y, l);
133
+ return [C * z, R * Y, b];
134
+ });
135
+ let L = 0, E = 0;
136
+ for (let d = 0; d < r.length; d++) {
137
+ if (g && f[d] !== "RoofSurface") continue;
138
+ const y = r[d][0];
139
+ if (!y || y.length < 3) continue;
140
+ const l = I(V(y));
141
+ L += l, E += l * (y.reduce((b, [, , C]) => b + C, 0) / y.length);
142
+ }
143
+ const W = L > 0 ? E / L : et(h), H = {
144
+ cityobject_id: e,
145
+ cityobject_type: t.type ?? "Unknown",
146
+ ...t.parents?.[0] !== void 0 ? { parent_id: t.parents[0] } : {},
147
+ ...a.lod !== void 0 ? { lod: String(a.lod) } : {},
148
+ ground_height: v(p),
149
+ roof_height: v(W - p),
150
+ eaves_height: v(S - p),
151
+ ridge_height: v(m - p),
152
+ roof_area: v(L),
153
+ surface_count: i.length
154
+ };
155
+ return { faceRings: r, semantics: f, footprintIndex: w, derived: H };
156
+ }
157
+ function nt(e, t, n, o, s, a) {
158
+ const i = B(e, t, n, o, a);
159
+ if (!i) return;
160
+ const r = i.faceRings[i.footprintIndex].filter((c) => c.length >= 3).map((c) => {
161
+ const u = c.map(([p, g]) => o.toLngLat(p, g));
162
+ return u.push(u[0]), u;
163
+ });
164
+ return {
165
+ type: "Feature",
166
+ id: e,
167
+ geometry: { type: "Polygon", coordinates: r },
168
+ // Source attributes first: the derived names are the ones manifests bind
169
+ // to, so they must be deterministic even against a same-named source field.
170
+ properties: { ...s, ...t.attributes, ...i.derived }
171
+ };
172
+ }
173
+ const M = {
174
+ GroundSurface: "#999999",
175
+ WallSurface: "#ffffff",
176
+ RoofSurface: "#ff0000",
177
+ TrafficArea: "#6e6e6e",
178
+ AuxiliaryTrafficArea: "#2c8200",
179
+ Window: "#0059ff",
180
+ Door: "#640000"
181
+ }, x = {
182
+ Building: "#7497df",
183
+ BuildingPart: "#7497df",
184
+ BuildingInstallation: "#7497df",
185
+ Bridge: "#999999",
186
+ BridgePart: "#999999",
187
+ BridgeInstallation: "#999999",
188
+ BridgeConstructiveElement: "#999999",
189
+ CityObjectGroup: "#ffffb3",
190
+ LandUse: "#ffffb3",
191
+ CityFurniture: "#cc0000",
192
+ GenericCityObject: "#cc0000",
193
+ PlantCover: "#39ac39",
194
+ SolitaryVegetationObject: "#39ac39",
195
+ Railway: "#000000",
196
+ Road: "#999999",
197
+ TransportSquare: "#999999",
198
+ TINRelief: "#ffdb99",
199
+ Tunnel: "#999999",
200
+ TunnelPart: "#999999",
201
+ TunnelInstallation: "#999999",
202
+ WaterBody: "#4da6ff"
203
+ }, ot = "#b0b0b0";
204
+ function rt(e, t) {
205
+ return e && M[e] ? M[e] : t && x[t] ? x[t] : ot;
206
+ }
207
+ function it(e, t, n, o, s, a) {
208
+ const i = B(e, t, n, o, a);
209
+ if (!i) return [];
210
+ const f = [];
211
+ for (let r = 0; r < i.faceRings.length; r++) {
212
+ const c = i.faceRings[r], u = c[0];
213
+ if (!u || u.length < 3) continue;
214
+ const p = c.filter((h) => h.length >= 3).map((h) => h.map(([S, m, w]) => [...o.toLngLat(S, m), w])), g = p[0];
215
+ f.push({
216
+ ...s,
217
+ ...t.attributes,
218
+ ...i.derived,
219
+ surface_type: i.semantics[r],
220
+ fill_color: rt(i.semantics[r], t.type),
221
+ polygon: p,
222
+ outline: [...g, g[0]]
223
+ });
224
+ }
225
+ return f;
226
+ }
227
+ function v(e) {
228
+ return Number.isFinite(e) ? Math.round(e * 1e3) / 1e3 : 0;
229
+ }
230
+ function D(e, t, n, o, s = "footprint") {
231
+ if (!e) return [];
232
+ const a = (f) => Array.isArray(f.children) && f.children.some((r) => {
233
+ const c = e[r];
234
+ return c?.type === `${f.type}Part` && (c.geometry?.some((u) => N(u).length > 0) ?? !1);
235
+ }), i = [];
236
+ for (const [f, r] of Object.entries(e)) {
237
+ if (!r || typeof r != "object" || a(r)) continue;
238
+ const u = (r.parents?.[0] ? e[r.parents[0]] : void 0)?.attributes ?? {};
239
+ if (s === "surfaces")
240
+ i.push(...it(f, r, t, n, u, o));
241
+ else {
242
+ const p = nt(f, r, t, n, u, o);
243
+ p && i.push(p);
244
+ }
245
+ }
246
+ return i;
247
+ }
248
+ function A(e, t, n) {
249
+ if (e?.type !== n)
250
+ throw new Error(
251
+ `expected a ${n} object but found type="${e?.type ?? "undefined"}" — this does not look like CityJSON. If it is a different newline-delimited format, claim the URL with OmMap.registerFormat().`
252
+ );
253
+ }
254
+ function $(e) {
255
+ const t = /[?&]om-lod=([^&#]+)/.exec(e)?.[1];
256
+ if (t === void 0) return;
257
+ const n = Number.parseFloat(decodeURIComponent(t));
258
+ if (!Number.isFinite(n)) {
259
+ console.warn(`[onlymapjs] om-lod="${t}" is not a number — ignoring and using the highest available LoD.`);
260
+ return;
261
+ }
262
+ return n;
263
+ }
264
+ function Z(e) {
265
+ const t = /[?&]om-surfaces=([^&#]+)/.exec(e)?.[1];
266
+ if (t === void 0) return !1;
267
+ const n = decodeURIComponent(t).toLowerCase();
268
+ return n === "1" || n === "true";
269
+ }
270
+ async function ut(e, t) {
271
+ const n = await e.json();
272
+ A(n, t, "CityJSON");
273
+ const o = await U(n.metadata?.referenceSystem, t), s = G(n.vertices, n.transform), a = Z(t) ? "surfaces" : "footprint";
274
+ return D(n.CityObjects, s, o, $(t), a);
275
+ }
276
+ const st = 250, at = 512;
277
+ async function* ct(e) {
278
+ if (!e.body) {
279
+ for (const s of (await e.text()).split(`
280
+ `)) yield s;
281
+ return;
282
+ }
283
+ const t = e.body.getReader(), n = new TextDecoder();
284
+ let o = "";
285
+ for (; ; ) {
286
+ const { done: s, value: a } = await t.read();
287
+ if (s) break;
288
+ o += n.decode(a, { stream: !0 });
289
+ let i;
290
+ for (; (i = o.indexOf(`
291
+ `)) >= 0; )
292
+ yield o.slice(0, i), o = o.slice(i + 1);
293
+ }
294
+ o += n.decode(), o.length > 0 && (yield o);
295
+ }
296
+ async function lt(e, t, n) {
297
+ const o = $(t), s = Z(t) ? "surfaces" : "footprint";
298
+ let a, i, f = !1;
299
+ const r = [];
300
+ let c = 0, u = 0;
301
+ for await (const p of ct(e)) {
302
+ const g = p.trim();
303
+ if (g.length === 0) continue;
304
+ const h = JSON.parse(g);
305
+ if (f)
306
+ A(h, t, "CityJSONFeature");
307
+ else if (A(h, t, "CityJSON"), i = h.transform, a = await U(h.metadata?.referenceSystem, t), f = !0, h.CityObjects && Object.keys(h.CityObjects).length === 0) continue;
308
+ const S = G(h.vertices, i);
309
+ r.push(...D(h.CityObjects, S, a, o, s)), c++;
310
+ const m = performance.now();
311
+ (c >= at || m - u >= st) && (c = 0, u = m, n(r.slice()));
312
+ }
313
+ if (!f)
314
+ throw new Error(
315
+ 'CityJSONSeq file is empty or has no header line — the first line must be a CityJSON object carrying "transform" and "metadata", followed by one CityJSONFeature per line.'
316
+ );
317
+ return r;
318
+ }
319
+ export {
320
+ K as chooseGeometry,
321
+ D as decodeCityObjects,
322
+ G as decompressVertices,
323
+ rt as defaultFillColor,
324
+ X as faceSemantics,
325
+ N as flattenToFaces,
326
+ ut as parseCityJsonDocument,
327
+ lt as parseCityJsonSeq,
328
+ tt as parseEpsgCode,
329
+ $ as parseLodPin,
330
+ Z as parseSurfacesFlag,
331
+ U as resolveCrs
332
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Clip box controller (spec: issue #34 §"Cutting / Clipping" — visible,
3
+ * draggable box). `clip-box.ts` owns the pure shader/patch math; this file
4
+ * owns the DOM-touching half: a wireframe box + 6 face-handle gizmos so the
5
+ * box is something you can SEE and GRAB, not just type numbers into. Same
6
+ * architecture as `measure-controller.ts`'s volume-mode height gizmo (real
7
+ * `<om-layer>` elements driven by `core.patchAnimatedProps` on the per-frame
8
+ * channel) — generalized from that gizmo's one hardcoded vertical axis to 6
9
+ * axis-constrained handles (one per box face), since a face can move along
10
+ * lng, lat, OR elevation depending on which one it is.
11
+ *
12
+ * Owned per `<om-map>` (the draw/measure precedent) and kept alive for as
13
+ * long as a clip box is configured, independent of whether the `clip-box`
14
+ * widget is even mounted — set clip-box-min/max programmatically and the
15
+ * wireframe still shows up, the same way the box itself still clips content
16
+ * with no widget present.
17
+ */
18
+ import type { PickingInfo } from "@deck.gl/core";
19
+ import type { RuntimeCore, GizmoDragEvent } from "./runtime-core";
20
+ export declare class ClipBoxController {
21
+ private readonly mapEl;
22
+ private readonly core;
23
+ private grab;
24
+ /**
25
+ * Whether the visual box + drag handles are showing. OFF by default and
26
+ * OFF whenever a drag/hover isn't in progress — the box's CLIPPING effect
27
+ * is entirely independent of this (that's driven straight off the
28
+ * clip-box-min/max attributes, always active once set). This is purely
29
+ * about the interactive gizmo: reported that its handles — always
30
+ * pickable once rendered, several of them sitting right around screen
31
+ * center where a normal pan gesture naturally starts — were hijacking
32
+ * ordinary map panning. Gating them behind an explicit "Edit box" toggle
33
+ * (see the clip-box-edit action) means panning is never at risk, and the
34
+ * gizmo only exists on screen when someone actually asked to adjust it.
35
+ */
36
+ private editing;
37
+ constructor(mapEl: Element, core: RuntimeCore);
38
+ setEditing(active: boolean): void;
39
+ isEditing(): boolean;
40
+ /** Re-reads the current box from `core` and repaints the wireframe + handles — call after ANY change, drag or attribute. */
41
+ syncVisuals(): void;
42
+ /**
43
+ * Repaints the wireframe + handles for a GIVEN box — factored out of
44
+ * `syncVisuals()` so `handleGizmoDrag` can drive this every mousemove
45
+ * frame from its own LOCAL `grab.liveBox` without going anywhere near
46
+ * `core.setClipBox`/DOM attributes (see that method's own doc comment).
47
+ * `patchAnimatedProps` alone is cheap and frame-rate-safe; this is the
48
+ * same reason `measure-controller.ts`'s volume gizmo redraws through
49
+ * `patchAnimatedProps` on every drag frame instead of writing anything
50
+ * to the DOM until the gesture actually ends.
51
+ */
52
+ private renderBox;
53
+ /**
54
+ * Grab a face handle. Computes a LOCAL screen-space drag basis for this
55
+ * handle's axis by projecting a known small step (`AXIS_EPSILON`) along
56
+ * it — the same linearization trick `measure-controller.ts`'s height
57
+ * gizmo uses for its one hardcoded vertical axis, generalized to a full
58
+ * 2D screen vector so it's correct for lng/lat faces too (which project
59
+ * to some diagonal screen direction depending on the camera's bearing/
60
+ * pitch, not a pure vertical like elevation).
61
+ */
62
+ handleGizmoDragStart(info: PickingInfo, event: GizmoDragEvent): void;
63
+ /**
64
+ * Every mousemove during a drag updates ONLY `grab.liveBox` and repaints
65
+ * the gizmo via `renderBox` (the cheap `patchAnimatedProps` channel) —
66
+ * it does NOT write clip-box-min/max attributes or call
67
+ * `core.setClipBox` per frame. Those go through `RuntimeCore.applyLayers`,
68
+ * which rebuilds the ENTIRE layer list (every layer, not just this box) —
69
+ * fine for an occasional attribute change, but firing it on every one of
70
+ * a mousemove's ~60 frames/sec froze the whole map (reported: "dragging
71
+ * the gizmos ... the whole map just hangs"). The real content stays
72
+ * clipped to the box's PRE-drag position for the gesture's duration and
73
+ * catches up in ONE `applyLayers` pass at `handleGizmoDragEnd` — the same
74
+ * "cheap live preview, expensive commit once at the end" split the proven
75
+ * volume-measurement gizmo already uses (it never touches the DOM mid-
76
+ * drag either).
77
+ */
78
+ handleGizmoDrag(info: PickingInfo): void;
79
+ /**
80
+ * The ONE expensive commit for the whole gesture: write the final
81
+ * min/max to the authored attributes — same channel the widget's own
82
+ * number inputs use, so the drag is undoable (history.ts's 500ms
83
+ * coalesce window merges it with any other same-attribute writes into a
84
+ * single step) and reflects live in the widget. The map's own
85
+ * MutationObserver picks this up and calls core.setClipBox, which
86
+ * re-applies the ACTUAL clip to real layers and (via that same observer
87
+ * hook) resyncs this controller's own gizmo from the now-authoritative
88
+ * state.
89
+ */
90
+ handleGizmoDragEnd(): void;
91
+ private ensureLayers;
92
+ }
93
+ export declare function getClipBoxController(mapEl: Element, core: RuntimeCore): ClipBoxController;
94
+ export declare function peekClipBoxController(mapEl: Element): ClipBoxController | undefined;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Clip box (spec: issue #34 §"Cutting / Clipping", tier 2 — the geometric
3
+ * complement to issue #26's clip PLANE). A real oriented... well, AXIS-
4
+ * ALIGNED (v1 scope, confirmed explicitly — rotation is a documented
5
+ * follow-up, not this) 3D box: geometry outside it is discarded (or, in
6
+ * `highlight` mode, dimmed rather than removed — "non-destructive"), so a
7
+ * dense scene (a BIM model, a point cloud, a city block) can be cut open to
8
+ * see what's inside.
9
+ *
10
+ * Map-level scene state (spec: `<om-map clip-box-min="..." clip-box-max="..."
11
+ * clip-box-invert clip-box-highlight>`), same architecture as `lighting`/
12
+ * `terrain`: attribute-backed, undoable, live-editable, applied to every
13
+ * layer by default with a per-layer `clip="off"` escape hatch — see
14
+ * `applyClipBox`'s own doc comment for why that default (not opt-in) is
15
+ * the right one here specifically.
16
+ *
17
+ * Three corrections from the original design review, all reflected below:
18
+ * 1. "Outside the box" is `!inside` (De Morgan's law on the AND of 6
19
+ * half-spaces) — NOT a literal OR of 6 flipped half-space tests, which
20
+ * would (incorrectly) require re-deriving 6 new inequalities; negating
21
+ * the single boolean `inside` result is simpler and exactly equivalent.
22
+ * 2. Plane/box bounds are expressed in deck's COMMON SPACE (via
23
+ * `Layer#projectPosition`, the SAME mechanism the built-in `ClipExtension`
24
+ * uses for its own 2D bounds) — never raw lng/lat — so one authored box
25
+ * clips layers in different coordinate systems (geographic, meter-offset
26
+ * BIM placements, etc.) correctly.
27
+ * 3. Picking (including the `pickable:'3d'` depth pass) is covered for
28
+ * free: `discard` inside `fs:DECKGL_FILTER_COLOR` is the SAME shader
29
+ * stage deck's own picking pass reuses — no separate picking-specific
30
+ * code needed, confirmed by the built-in `ClipExtension` doing nothing
31
+ * extra for picking either.
32
+ */
33
+ import { LayerExtension } from "@deck.gl/core";
34
+ import type { Layer } from "@deck.gl/core";
35
+ import type { LayerIR } from "./ir";
36
+ export interface ClipBoxIR {
37
+ /** [lng, lat, elevationMeters] — one corner. Order vs. `max` doesn't matter; both get componentwise min/max'd after projecting. */
38
+ min: [number, number, number];
39
+ max: [number, number, number];
40
+ /** Show OUTSIDE the box instead of inside. */
41
+ invert: boolean;
42
+ /** Dim clipped-out geometry instead of discarding it — "non-destructive": nothing disappears, it just de-emphasizes. */
43
+ highlight: boolean;
44
+ }
45
+ /** `<om-map clip-box-min="[...]" clip-box-max="[...]" clip-box-invert clip-box-highlight>` → `ClipBoxIR`, or `null` when min/max aren't both present/valid (no box = no clipping, not a broken one). */
46
+ export declare function parseClipBoxAttrs(getAttr: (name: string) => string | null): ClipBoxIR | null;
47
+ export interface ClipBoxExtensionProps {
48
+ clipBoxMin?: [number, number, number];
49
+ clipBoxMax?: [number, number, number];
50
+ clipBoxInvert?: boolean;
51
+ clipBoxHighlight?: boolean;
52
+ }
53
+ /** Fragment-level box clip (targets extruded/mesh geometry — GeoJsonLayer extrusions, Tile3DLayer/BIMLayer triangles — not point/icon anchors, unlike the built-in ClipExtension's dual vertex/fragment modes; this library's clip-box use case is "cut into a 3D scene," not "hide points outside a rect"). */
54
+ export declare class ClipBoxExtension extends LayerExtension {
55
+ static extensionName: string;
56
+ static defaultProps: {
57
+ clipBoxMin: number[];
58
+ clipBoxMax: number[];
59
+ clipBoxInvert: boolean;
60
+ clipBoxHighlight: boolean;
61
+ };
62
+ getShaders(): {
63
+ modules: {
64
+ readonly name: "clipBox";
65
+ readonly fs: "\nlayout(std140) uniform clipBoxUniforms {\n vec3 boxMin;\n vec3 boxMax;\n float invert;\n float highlight;\n vec4 highlightTint;\n} clipBox;\n\nbool clipBox_isInside(vec3 commonPos) {\n return commonPos.x >= clipBox.boxMin.x && commonPos.x <= clipBox.boxMax.x\n && commonPos.y >= clipBox.boxMin.y && commonPos.y <= clipBox.boxMax.y\n && commonPos.z >= clipBox.boxMin.z && commonPos.z <= clipBox.boxMax.z;\n}\n";
66
+ readonly uniformTypes: {
67
+ readonly boxMin: "vec3<f32>";
68
+ readonly boxMax: "vec3<f32>";
69
+ readonly invert: "f32";
70
+ readonly highlight: "f32";
71
+ readonly highlightTint: "vec4<f32>";
72
+ };
73
+ }[];
74
+ inject: {
75
+ "vs:#decl": string;
76
+ "vs:DECKGL_FILTER_GL_POSITION": string;
77
+ "fs:#decl": string;
78
+ "fs:DECKGL_FILTER_COLOR": string;
79
+ };
80
+ };
81
+ draw(this: Layer<Required<ClipBoxExtensionProps>>): void;
82
+ }
83
+ export interface ClipBoxLayerPatch {
84
+ /**
85
+ * The deck-layer id to construct with. Suffixed `#c<generation>` while a
86
+ * box is active and this layer is opted in: adding/removing an extension
87
+ * on a LIVE (already-mounted) layer corrupts DataFilterExtension's
88
+ * attribute state for some layer types (ColumnLayer, Tile3DLayer/BIMLayer
89
+ * sublayers — empirically verified: a box present from the FIRST render
90
+ * works fine, but toggling one on live on an already-mounted filtered
91
+ * layer silently blanks it, with no console error) — the exact same
92
+ * "birth-stable extension set" issue `applyTerrain`'s own `deckId`
93
+ * suffixing already works around. A fresh id forces clean teardown +
94
+ * rebuild instead of a live prop update.
95
+ */
96
+ deckId: string;
97
+ extraProps: Record<string, unknown>;
98
+ }
99
+ /**
100
+ * Per-layer clip-box patches, PURE — mirrors `applyTerrain`'s exact shape
101
+ * (same file this gets merged alongside in runtime-core.ts's `buildLayers`).
102
+ * Default is APPLIED (every layer gets clipped once a box is configured),
103
+ * unlike terrain's per-layer OPT-IN default — the clip box's whole point is
104
+ * cutting into "the scene," and requiring every single layer to opt in
105
+ * individually would make the common case ("cut through everything, show me
106
+ * what's inside") the awkward one; `clip="off"` is the escape hatch for the
107
+ * layers that shouldn't be touched (a basemap, most often).
108
+ */
109
+ export declare function applyClipBox(irs: readonly LayerIR[], clipBox: ClipBoxIR | null, generation: number): Map<string, ClipBoxLayerPatch>;
@@ -0,0 +1,108 @@
1
+ const c = {
2
+ 7415: 28992,
3
+ // Amersfoort / RD New + NAP height — the 3DBAG default
4
+ 6697: 6668,
5
+ // JGD2011 geographic 3D + height — the PLATEAU default
6
+ 5555: 25832,
7
+ // ETRS89 / UTM 32N + DHHN92 height
8
+ 5556: 25833
9
+ // ETRS89 / UTM 33N + DHHN92 height
10
+ }, f = [
11
+ [33, 129.5],
12
+ [33, 131],
13
+ [36, 132.166666666667],
14
+ [33, 133.5],
15
+ [36, 134.333333333333],
16
+ [36, 136],
17
+ [36, 137.166666666667],
18
+ [36, 138.5],
19
+ [36, 139.833333333333],
20
+ [40, 140.833333333333],
21
+ [44, 140.25],
22
+ [44, 142.25],
23
+ [44, 144.25],
24
+ [26, 142],
25
+ [26, 127.5],
26
+ [26, 124],
27
+ [26, 131],
28
+ [20, 136],
29
+ [26, 154]
30
+ ], u = {
31
+ // Netherlands — Amersfoort / RD New (3DBAG).
32
+ 28992: {
33
+ def: "+proj=sterea +lat_0=52.1561605555556 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.4171,50.3319,465.5524,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs"
34
+ },
35
+ // Switzerland — CH1903+ / LV95.
36
+ 2056: {
37
+ def: "+proj=somerc +lat_0=46.9524055555556 +lon_0=7.43958333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs"
38
+ },
39
+ // Germany — ETRS89 / UTM zones 32N and 33N.
40
+ 25832: { def: "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs" },
41
+ 25833: { def: "+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs" },
42
+ // Plain WGS84 UTM zones 32N/33N (EPSG:326xx) — the generic, non-European-specific
43
+ // code many CityJSON files declare instead of the ETRS89 pair above (e.g.
44
+ // Ingolstadt's own open-data LoD3 export). Numerically near-identical to
45
+ // 25832/25833 at map-rendering precision (WGS84 vs. ETRS89 differ by ~cm in
46
+ // Europe), so no +towgs84 shift is needed — same bare projection string.
47
+ 32632: { def: "+proj=utm +zone=32 +datum=WGS84 +units=m +no_defs" },
48
+ 32633: { def: "+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs" },
49
+ // Austria — MGI / Austria GK West/Central/East (the national cadastral
50
+ // system's three meridian-strip zones; Vienna sits in GK East). Same
51
+ // Bessel ellipsoid + towgs84 shift and y_0=-5,000,000 false-northing
52
+ // convention across all three — proj4 strings verified against epsg.io,
53
+ // not hand-derived, and cross-checked by reprojecting a real Vienna
54
+ // open-data file's own extent and confirming it lands in Vienna.
55
+ 31254: { def: "+proj=tmerc +lat_0=0 +lon_0=10.3333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs" },
56
+ 31255: { def: "+proj=tmerc +lat_0=0 +lon_0=13.3333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs" },
57
+ 31256: { def: "+proj=tmerc +lat_0=0 +lon_0=16.3333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs" },
58
+ // Singapore — SVY21 / Singapore TM (the national grid; NUS's own Singapore
59
+ // HDB buildings dataset declares this via metadata.referenceSystem
60
+ // directly, unlike the Austria/3DBAG files, which had none). proj4 string
61
+ // verified against epsg.io, cross-checked by reprojecting a real dataset's
62
+ // own extent and confirming it lands in Singapore (103.68-103.99°E,
63
+ // 1.27-1.46°N — matches).
64
+ 3414: { def: "+proj=tmerc +lat_0=1.36666666666667 +lon_0=103.833333333333 +k=1 +x_0=28001.642 +y_0=38744.572 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs" },
65
+ // Japan — JGD2011 geographic 2D. EPSG axis order is (lat, lon).
66
+ 6668: { def: "+proj=longlat +ellps=GRS80 +no_defs", geographic: !0, swapXY: !0 },
67
+ // WGS 84. Files in the wild store lon/lat despite EPSG's formal (lat, lon).
68
+ 4326: { def: "+proj=longlat +datum=WGS84 +no_defs", geographic: !0 },
69
+ 3857: { def: "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +no_defs" }
70
+ };
71
+ for (let t = 0; t < f.length; t++) {
72
+ const [e, o] = f[t];
73
+ u[6669 + t] = {
74
+ def: `+proj=tmerc +lat_0=${e} +lon_0=${o} +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +units=m +no_defs`,
75
+ swapXY: !0
76
+ };
77
+ }
78
+ let p = null;
79
+ async function m() {
80
+ return (await import("./index-CW1n5LdO.js")).default;
81
+ }
82
+ function g() {
83
+ return p ??= m(), p;
84
+ }
85
+ function j() {
86
+ return Object.keys(u).map(Number).sort((t, e) => t - e);
87
+ }
88
+ async function i(t, e, o) {
89
+ const a = c[t] ?? t, n = u[a];
90
+ if (!n) return null;
91
+ const [s, l] = n.swapXY ? [o, e] : [e, o];
92
+ if (n.geographic) return [s, l];
93
+ const _ = await g(), [r, d] = _(n.def, "+proj=longlat +datum=WGS84 +no_defs").forward([s, l]);
94
+ return Number.isFinite(r) && Number.isFinite(d) ? [r, d] : null;
95
+ }
96
+ async function w(t, e, o, a = 100) {
97
+ const n = await i(t, e, o), s = await i(t, e, o + a);
98
+ if (!n || !s) return null;
99
+ const l = (n[1] + s[1]) / 2 * Math.PI / 180, _ = (s[0] - n[0]) * Math.cos(l), r = s[1] - n[1];
100
+ return Math.atan2(_, r) * 180 / Math.PI;
101
+ }
102
+ export {
103
+ u as C,
104
+ c as a,
105
+ j as b,
106
+ w as g,
107
+ i as p
108
+ };