@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.
- package/CHANGELOG.md +63 -1
- package/README.md +39 -14
- package/THIRD-PARTY-LICENSES.md +27 -0
- package/dist/{LercDecode.es-fbo6h5rt.js → LercDecode.es-BOJJr6Gx.js} +1 -1
- package/dist/{basemap-CprSan9q.js → basemap-BofXgXxy.js} +13 -3
- package/dist/basemap.d.ts +10 -1
- package/dist/cityjson-D_V5GY8b.js +332 -0
- package/dist/clip-box-controller.d.ts +94 -0
- package/dist/clip-box.d.ts +109 -0
- package/dist/crs-Ciu7Xs7a.js +108 -0
- package/dist/crs.d.ts +50 -0
- package/dist/ctx.d.ts +8 -1
- package/dist/data-layer.d.ts +11 -3
- package/dist/draw-controller.d.ts +75 -1
- package/dist/draw.d.ts +22 -4
- package/dist/elements/om-map.d.ts +45 -2
- package/dist/elements/om-overlay.d.ts +13 -1
- package/dist/elements/om-widget.d.ts +4 -0
- package/dist/feature-colors.d.ts +41 -0
- package/dist/geodesy.d.ts +29 -3
- package/dist/{geoparquet-Bm8pfxyo.js → geoparquet-DapHATA0.js} +1 -1
- package/dist/html-data.d.ts +2 -2
- package/dist/ifc-Dc-TdSJr.js +877 -0
- package/dist/ifc.d.ts +406 -0
- package/dist/{index-Bz-rbmhj.js → index-7B-6Cbzu.js} +31619 -26565
- package/dist/{index-DSRIZR81.js → index-BF9iO8Tq.js} +1 -1
- package/dist/{index-GOwMjqm6.js → index-BmX6IId3.js} +1 -1
- package/dist/{index-UzmNhuS0.js → index-SPJVn_n_.js} +1 -1
- package/dist/{index-CCQttJzO.js → index-xmJjZxQJ.js} +2 -2
- package/dist/index.d.ts +13 -2
- package/dist/ir-snapshot.d.ts +3 -1
- package/dist/layer-registry.d.ts +14 -0
- package/dist/layers/bim-layer.d.ts +127 -0
- package/dist/layers/feature-mesh-layer.d.ts +288 -0
- package/dist/layers/gltf-scene-walk.d.ts +55 -0
- package/dist/layers/popup-layer.d.ts +13 -0
- package/dist/legend-spec.d.ts +1 -1
- package/dist/{lerc-CuifOkoA.js → lerc-C6k7EzSN.js} +2 -2
- package/dist/license.d.ts +12 -5
- package/dist/measure-controller.d.ts +458 -4
- package/dist/meshopt_simplifier-Co6uRDDA.js +370 -0
- package/dist/onlymap.standalone.js +57077 -50729
- package/dist/onlymapjs.js +72 -69
- package/dist/parse-manifest.d.ts +3 -0
- package/dist/programmatic.d.ts +39 -5
- package/dist/{raster-dysYIQ_k.js → raster-Cuzhibe2.js} +2 -2
- package/dist/{raster-pipeline-ZdCOGfqV.js → raster-pipeline-DLrnJK8y.js} +1 -1
- package/dist/react/om-layer.d.ts +6 -1
- package/dist/react/om-overlay.d.ts +9 -0
- package/dist/react.js +176 -169
- package/dist/region-export-controller.d.ts +37 -0
- package/dist/region-export.d.ts +56 -0
- package/dist/runtime-core.d.ts +260 -3
- package/dist/selection.d.ts +44 -1
- package/dist/site-placement.d.ts +107 -0
- package/dist/snapping.d.ts +88 -0
- package/dist/terrain-heightfield.d.ts +53 -0
- package/dist/terrain-sample.d.ts +30 -0
- package/dist/terrain.d.ts +6 -1
- package/dist/testing.d.ts +6 -2
- package/dist/tile3d-metadata.d.ts +185 -0
- package/dist/units.d.ts +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/volumetrics-run.d.ts +13 -0
- package/dist/volumetrics-worker.d.ts +1 -0
- package/dist/volumetrics.d.ts +201 -0
- package/dist/widget-registry.d.ts +11 -0
- package/dist/widgets/ifc-clash.d.ts +18 -0
- package/dist/widgets/ifc.d.ts +41 -0
- package/dist/{zarr-OewK7k2K.js → zarr-CUzEX8fB.js} +79 -66
- package/docs/3d-assets.md +48 -0
- package/docs/live-data.md +9 -1
- package/docs/testing.md +4 -2
- package/llms.txt +10 -5
- package/onlymapjs.html-data.json +313 -2
- package/package.json +6 -2
- package/skills/onlymapjs/SKILL.md +14 -2
- package/skills/onlymapjs/references/react.md +2 -2
- package/skills/onlymapjs/references/syntax.md +314 -15
- 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
|
+
};
|