@nika-js/onlymap 0.2.2 → 0.3.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.
- package/.vscode/onlymap.code-snippets +4 -4
- package/LICENSE.md +22 -12
- package/README.md +54 -11
- package/dist/actions.d.ts +2 -2
- package/dist/badge.d.ts +4 -0
- package/dist/{basemap-Bn4TmZtQ.js → basemap-DrK6zcu8.js} +2337 -2273
- package/dist/basemap-registry.d.ts +10 -0
- package/dist/basemap.d.ts +23 -0
- package/dist/color.d.ts +9 -0
- package/dist/ctx.d.ts +10 -1
- package/dist/deck.d.ts +28 -0
- package/dist/deck.js +16 -0
- package/dist/elements/om-map.d.ts +30 -0
- package/dist/error-reporting.d.ts +17 -0
- package/dist/history.d.ts +53 -0
- package/dist/html-data.d.ts +2 -2
- package/dist/{index-D2zVsZ79.js → index-C9tgnPNw.js} +1 -1
- package/dist/{index-BZs_x9Dx.js → index-CiuGqS0i.js} +2 -2
- package/dist/{index-oE1Kouy1.js → index-CsicbycJ.js} +18395 -16371
- package/dist/{index-3UyMg0Md.js → index-DXoRERAy.js} +1 -1
- package/dist/{index-DSjndBOf.js → index-Ztkd30f8.js} +1 -1
- package/dist/index.d.ts +19 -0
- package/dist/internal-ids.d.ts +7 -0
- package/dist/ir-diff.d.ts +1 -1
- package/dist/ir-snapshot.d.ts +2 -0
- package/dist/ir.d.ts +7 -0
- package/dist/license.d.ts +64 -0
- package/dist/onlymapjs.js +54 -32
- package/dist/onlymapjs.umd.cjs +426 -342
- package/dist/programmatic.d.ts +35 -0
- package/dist/react/om-layer.d.ts +2 -0
- package/dist/react.js +18 -16
- package/dist/runtime-core.d.ts +79 -1
- package/dist/scene-lighting.d.ts +83 -0
- package/dist/selection.d.ts +1 -1
- package/dist/snapshot.d.ts +14 -0
- package/dist/telemetry-schema.d.ts +53 -0
- package/dist/telemetry.d.ts +60 -0
- package/dist/terrain.d.ts +113 -0
- package/dist/version.d.ts +8 -0
- package/docs/custom-layers.md +99 -0
- package/docs/react.md +1 -0
- package/docs/telemetry.md +78 -0
- package/docs/testing.md +2 -2
- package/llms.txt +6 -5
- package/onlymapjs.html-data.json +172 -29
- package/package.json +25 -5
- package/skills/onlymapjs/SKILL.md +4 -1
- package/skills/onlymapjs/references/syntax.md +32 -1
- package/skills/onlymapjs/references/testing.md +2 -0
package/dist/programmatic.d.ts
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* runtime state through the same RuntimeContext contract widgets get (HU5).
|
|
16
16
|
*/
|
|
17
17
|
import { type CameraOptions } from "./runtime-core";
|
|
18
|
+
import { type LightingIR, type LightingOptions } from "./scene-lighting";
|
|
19
|
+
import type { TerrainIR } from "./terrain";
|
|
20
|
+
import { type SnapshotOptions } from "./snapshot";
|
|
18
21
|
import type { LayerIR } from "./ir";
|
|
19
22
|
import { type RuntimeContext } from "./ctx";
|
|
20
23
|
import type { Selection } from "./selection";
|
|
@@ -61,6 +64,8 @@ export interface LayerDescriptor {
|
|
|
61
64
|
updateTriggers?: Record<string, unknown>;
|
|
62
65
|
/** deck.gl props (camelCase), accessors as plain functions — passed through. */
|
|
63
66
|
props?: Record<string, unknown>;
|
|
67
|
+
/** Per-layer terrain mode (spec: "Terrain") — the `terrain` attribute's twin; absent = the type default. */
|
|
68
|
+
terrain?: "drape" | "offset" | "off";
|
|
64
69
|
}
|
|
65
70
|
/** Camera state, both an input (options/setView) and an output (getViewState). */
|
|
66
71
|
export interface CameraState {
|
|
@@ -88,6 +93,14 @@ export interface MapControllerOptions {
|
|
|
88
93
|
};
|
|
89
94
|
/** Runtime error boundary — deck.gl-level failures in the structured validation shape. */
|
|
90
95
|
onRuntimeError?: (entry: ValidationEntry) => void;
|
|
96
|
+
/**
|
|
97
|
+
* Camera settled (trailing-debounced, the `om-view-changed` twin) — for
|
|
98
|
+
* camera persistence. Per-frame viewport churn goes to `watch(["viewport"])`;
|
|
99
|
+
* this fires once with the destination.
|
|
100
|
+
*/
|
|
101
|
+
onViewChange?: (view: CameraState) => void;
|
|
102
|
+
/** Scene lighting (spec: "Scene Lighting") — the lighting* attributes' programmatic twin; omit for deck's default lights. */
|
|
103
|
+
lighting?: LightingOptions;
|
|
91
104
|
}
|
|
92
105
|
/** Descriptor → layer IR: the programmatic counterpart of one parse-manifest.ts loop iteration. */
|
|
93
106
|
export declare function descriptorToIR(desc: LayerDescriptor, onDataLoaded: () => void): LayerIR | null;
|
|
@@ -118,6 +131,7 @@ export declare class MapController {
|
|
|
118
131
|
private commitPending;
|
|
119
132
|
private destroyed;
|
|
120
133
|
private firstCommitDone;
|
|
134
|
+
private viewChangeTimer;
|
|
121
135
|
private readyFired;
|
|
122
136
|
private resolveReady;
|
|
123
137
|
readonly ready: Promise<void>;
|
|
@@ -176,6 +190,25 @@ export declare class MapController {
|
|
|
176
190
|
* (none ↔ maplibre) remount seeded with the current camera.
|
|
177
191
|
*/
|
|
178
192
|
setBasemap(basemap: string | null): void;
|
|
193
|
+
/** Scene lighting (spec: "Scene Lighting") — swap the LightingEffect live; null restores deck's default lights. */
|
|
194
|
+
setLighting(options: LightingOptions | null): void;
|
|
195
|
+
/** Resolved scene-lighting IR (test inspection) — null = deck default lights. */
|
|
196
|
+
getLighting(): LightingIR | null;
|
|
197
|
+
/**
|
|
198
|
+
* Terrain (spec: "Terrain") — a resolved TerrainIR, or null to turn the
|
|
199
|
+
* surface off. Note: terrain suppresses an active basemap while on
|
|
200
|
+
* (restored on null) — the flat-canvas desync rule.
|
|
201
|
+
*/
|
|
202
|
+
setTerrain(ir: TerrainIR | null): void;
|
|
203
|
+
/** Resolved terrain IR (test inspection) — null = no surface. */
|
|
204
|
+
getTerrain(): TerrainIR | null;
|
|
205
|
+
/**
|
|
206
|
+
* Canvas-only scene snapshot (spec: "Snapshot API") — basemap + deck
|
|
207
|
+
* composited at device pixels; framework-rendered widgets/overlays are
|
|
208
|
+
* NOT captured (render provider credits yourself on exports). Await
|
|
209
|
+
* `ready` first; headless controllers reject.
|
|
210
|
+
*/
|
|
211
|
+
snapshot(opts?: SnapshotOptions): Promise<string | Blob>;
|
|
179
212
|
flyTo(center: [number, number], zoom?: number, opts?: CameraOptions): void;
|
|
180
213
|
flyToBounds(bounds: [[number, number], [number, number]], padding?: number, opts?: CameraOptions): void;
|
|
181
214
|
setView(partial: Partial<CameraState>, opts?: CameraOptions): void;
|
|
@@ -183,4 +216,6 @@ export declare class MapController {
|
|
|
183
216
|
/** Projects a lng/lat through the current viewport — undefined before the viewport resolves. */
|
|
184
217
|
project(lngLat: [number, number]): [number, number] | undefined;
|
|
185
218
|
destroy(): void;
|
|
219
|
+
/** Trailing-debounced onViewChange — see MapControllerOptions.onViewChange. */
|
|
220
|
+
private scheduleViewChange;
|
|
186
221
|
}
|
package/dist/react/om-layer.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface OmLayerProps {
|
|
|
26
26
|
flush?: string;
|
|
27
27
|
/** Polling interval for REST snapshot URLs, e.g. "5s". */
|
|
28
28
|
refresh?: string;
|
|
29
|
+
/** Behavior under active map terrain; absent uses the layer type's default. */
|
|
30
|
+
terrain?: "drape" | "offset" | "off";
|
|
29
31
|
/** deck.gl updateTriggers — supply a changing value when an accessor's OUTPUT changes (deck ignores function identity). */
|
|
30
32
|
updateTriggers?: Record<string, unknown>;
|
|
31
33
|
/** Click pick on this layer (object flattened/materialized, same as ctx.selection). */
|
package/dist/react.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var J = Object.defineProperty;
|
|
2
2
|
var Q = (e, t, n) => t in e ? J(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
|
|
3
3
|
var A = (e, t, n) => Q(e, typeof t != "symbol" ? t + "" : t, n);
|
|
4
|
-
import { jsxs as U, jsx as
|
|
4
|
+
import { jsxs as U, jsx as C } from "react/jsx-runtime";
|
|
5
5
|
import { createContext as X, useContext as Y, forwardRef as Z, useRef as f, useState as k, useCallback as ee, useMemo as I, useLayoutEffect as te, useEffect as g, useImperativeHandle as re } from "react";
|
|
6
6
|
import { MapController as ne } from "@nika-js/onlymap";
|
|
7
7
|
import { createPortal as B } from "react-dom";
|
|
@@ -53,7 +53,7 @@ function ie(e, t) {
|
|
|
53
53
|
return Object.entries(t).some(([n, r]) => r !== void 0 && e[n] !== r);
|
|
54
54
|
}
|
|
55
55
|
const he = Z(function(t, n) {
|
|
56
|
-
const { center: r, zoom: l, pitch: a, bearing: u, basemap: i, basemapKey: c, attribution: y, headless: m, className: O, style: z, children: R } = t, d = f(null), [o, T] = k(null), [
|
|
56
|
+
const { center: r, zoom: l, pitch: a, bearing: u, basemap: i, basemapKey: c, attribution: y, headless: m, className: O, style: z, children: R } = t, d = f(null), [o, T] = k(null), [p, M] = k(null), [j, L] = k({
|
|
57
57
|
"top-left": null,
|
|
58
58
|
"top-right": null,
|
|
59
59
|
"bottom-left": null,
|
|
@@ -65,7 +65,7 @@ const he = Z(function(t, n) {
|
|
|
65
65
|
q.map((s) => [
|
|
66
66
|
s,
|
|
67
67
|
(h) => {
|
|
68
|
-
L((
|
|
68
|
+
L((x) => x[s] === h ? x : { ...x, [s]: h });
|
|
69
69
|
}
|
|
70
70
|
])
|
|
71
71
|
),
|
|
@@ -100,12 +100,12 @@ const he = Z(function(t, n) {
|
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
T(h);
|
|
103
|
-
let
|
|
103
|
+
let x = !0;
|
|
104
104
|
return h.ready.then(() => {
|
|
105
105
|
var F;
|
|
106
|
-
|
|
106
|
+
x && ((F = b.current) == null || F.call(b));
|
|
107
107
|
}), () => {
|
|
108
|
-
|
|
108
|
+
x = !1, T(null), h.destroy();
|
|
109
109
|
};
|
|
110
110
|
}, [G]), g(() => {
|
|
111
111
|
!o || V.current === i || (V.current = i, o.setBasemap(i ?? null));
|
|
@@ -121,19 +121,19 @@ const he = Z(function(t, n) {
|
|
|
121
121
|
});
|
|
122
122
|
}, [o]);
|
|
123
123
|
const W = I(() => o ? new se(o) : null, [o]), _ = I(
|
|
124
|
-
() => o && W &&
|
|
125
|
-
[o, W,
|
|
124
|
+
() => o && W && p ? { controller: o, layers: W, overlayPane: p, corners: j } : null,
|
|
125
|
+
[o, W, p, j]
|
|
126
126
|
);
|
|
127
127
|
return /* @__PURE__ */ U("div", { className: O, style: { position: "relative", overflow: "hidden", ...z }, children: [
|
|
128
|
-
/* @__PURE__ */
|
|
129
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ C("div", { ref: d, style: { position: "absolute", inset: 0 } }),
|
|
129
|
+
/* @__PURE__ */ C(
|
|
130
130
|
"div",
|
|
131
131
|
{
|
|
132
132
|
ref: v,
|
|
133
133
|
style: { position: "absolute", top: 0, left: 0, zIndex: 10, pointerEvents: "none" }
|
|
134
134
|
}
|
|
135
135
|
),
|
|
136
|
-
q.map((s) => /* @__PURE__ */
|
|
136
|
+
q.map((s) => /* @__PURE__ */ C(
|
|
137
137
|
"div",
|
|
138
138
|
{
|
|
139
139
|
"data-om-widget-corner": s,
|
|
@@ -142,7 +142,7 @@ const he = Z(function(t, n) {
|
|
|
142
142
|
},
|
|
143
143
|
s
|
|
144
144
|
)),
|
|
145
|
-
_ && /* @__PURE__ */
|
|
145
|
+
_ && /* @__PURE__ */ C(H.Provider, { value: _, children: R })
|
|
146
146
|
] });
|
|
147
147
|
}), ae = /* @__PURE__ */ new Set([
|
|
148
148
|
"id",
|
|
@@ -159,6 +159,7 @@ const he = Z(function(t, n) {
|
|
|
159
159
|
"streamKey",
|
|
160
160
|
"flush",
|
|
161
161
|
"refresh",
|
|
162
|
+
"terrain",
|
|
162
163
|
"updateTriggers",
|
|
163
164
|
"onClick",
|
|
164
165
|
"onHover"
|
|
@@ -182,6 +183,7 @@ function ge(e) {
|
|
|
182
183
|
key: e.streamKey,
|
|
183
184
|
flush: e.flush,
|
|
184
185
|
refresh: e.refresh,
|
|
186
|
+
terrain: e.terrain,
|
|
185
187
|
updateTriggers: e.updateTriggers,
|
|
186
188
|
props: l
|
|
187
189
|
};
|
|
@@ -206,7 +208,7 @@ function ge(e) {
|
|
|
206
208
|
function ve({ position: e = "top-left", className: t, style: n, children: r }) {
|
|
207
209
|
const { corners: l } = K("OmWidget"), a = l[e];
|
|
208
210
|
return a ? B(
|
|
209
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ C("div", { className: t, style: { pointerEvents: "auto", ...n }, children: r }),
|
|
210
212
|
a
|
|
211
213
|
) : null;
|
|
212
214
|
}
|
|
@@ -227,9 +229,9 @@ function be(e) {
|
|
|
227
229
|
() => t.registerOverlay((R, d) => {
|
|
228
230
|
const o = r.current;
|
|
229
231
|
if (!o) return;
|
|
230
|
-
const { anchor: T, anchorFrom:
|
|
232
|
+
const { anchor: T, anchorFrom: p, layer: M, visible: j = !0, anchorOffset: L } = i.current;
|
|
231
233
|
let v;
|
|
232
|
-
if (
|
|
234
|
+
if (p === "selection" ? ((!M || (d == null ? void 0 : d.layerId) === M) && (l.current = (d == null ? void 0 : d.coordinate) ?? void 0, u(d)), v = l.current) : v = T, !j || !v || !R) {
|
|
233
235
|
o.style.visibility = "hidden";
|
|
234
236
|
return;
|
|
235
237
|
}
|
|
@@ -246,7 +248,7 @@ function be(e) {
|
|
|
246
248
|
});
|
|
247
249
|
const { children: c, className: y, style: m, interactive: O = !0 } = e, z = typeof c == "function" ? c(a) : c;
|
|
248
250
|
return B(
|
|
249
|
-
/* @__PURE__ */
|
|
251
|
+
/* @__PURE__ */ C(
|
|
250
252
|
"div",
|
|
251
253
|
{
|
|
252
254
|
ref: r,
|
package/dist/runtime-core.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type LightingIR } from "./scene-lighting";
|
|
2
|
+
import { type TerrainIR } from "./terrain";
|
|
1
3
|
import type { LayerIR } from "./ir";
|
|
2
4
|
import { type Selection } from "./selection";
|
|
3
5
|
import type { ValidationEntry } from "./validation";
|
|
4
6
|
import type { MapViewport } from "./basemap";
|
|
5
|
-
/**
|
|
7
|
+
/** Mapbox presets/protocol URLs aren't implemented — kept maplibre-free so validation/runtime can check without loading the chunk. */
|
|
6
8
|
export declare function isMapboxBasemap(raw: string): boolean;
|
|
7
9
|
export interface InitialView {
|
|
8
10
|
longitude: number;
|
|
@@ -90,6 +92,48 @@ export declare class RuntimeCore {
|
|
|
90
92
|
private basemapOptions?;
|
|
91
93
|
/** Bumped on every renderer (re)init — a superseded lazy chunk load must not install its adapter over a newer renderer. */
|
|
92
94
|
private rendererGeneration;
|
|
95
|
+
/** Pre-gate descriptors — what a license settle re-reconciles from (lastIRs holds the GATED set the renderer sees). */
|
|
96
|
+
private pregateIRs;
|
|
97
|
+
/**
|
|
98
|
+
* Scene lighting (spec: "Scene Lighting") — the retained IR, rebuilt into
|
|
99
|
+
* a LightingEffect on apply. Survives renderer remounts (initRenderer
|
|
100
|
+
* reads it, the lastIRs contract) and the lazy basemap-chunk window (the
|
|
101
|
+
* adapter constructor reads it on arrival). null = deck default lights.
|
|
102
|
+
*/
|
|
103
|
+
private lighting;
|
|
104
|
+
/**
|
|
105
|
+
* Terrain (spec: "Terrain") — the retained surface IR. Non-null appends
|
|
106
|
+
* the internal terrain layer and patches drape/offset layers in
|
|
107
|
+
* buildLayers. `terrainGeneration` bumps on every terrain state change:
|
|
108
|
+
* extension sets must be BIRTH-stable, so patched layers get
|
|
109
|
+
* `#t<generation>`-based deck ids (fresh instances per flip — see
|
|
110
|
+
* applyTerrain); an explicit renderer-id map restores authored pick ids.
|
|
111
|
+
*/
|
|
112
|
+
private terrain;
|
|
113
|
+
private terrainGeneration;
|
|
114
|
+
/** Rendered deck layer id → authored manifest/controller id (terrain uses fresh renderer ids). */
|
|
115
|
+
private renderedLayerIds;
|
|
116
|
+
/**
|
|
117
|
+
* The basemap attribute suppressed while terrain is active — a flat
|
|
118
|
+
* MapLibre canvas at sea level visibly desyncs from a raised surface
|
|
119
|
+
* (true coexistence is the interleaved-compositing TODO), so terrain
|
|
120
|
+
* forces standalone rendering and restores the basemap when it turns off.
|
|
121
|
+
*/
|
|
122
|
+
private suppressedBasemapAttr?;
|
|
123
|
+
/**
|
|
124
|
+
* Snapshot capture queue (spec: "Snapshot API") — the WebGL context has
|
|
125
|
+
* no preserveDrawingBuffer, so pixels are only readable synchronously
|
|
126
|
+
* inside a post-render callback. snapshot() enqueues a capture and forces
|
|
127
|
+
* a redraw; the permanent onAfterRender (standalone Deck constructor)
|
|
128
|
+
* drains the queue while the drawing buffer is valid. `cancel` settles the
|
|
129
|
+
* promise when the renderer goes away before that frame renders (teardown/
|
|
130
|
+
* destroy) — a queued capture must never leave its caller hanging.
|
|
131
|
+
*/
|
|
132
|
+
private snapshotCaptures;
|
|
133
|
+
/** (layer, reason) pairs already reported — gate errors fire once per violation, not per reconcile. */
|
|
134
|
+
private readonly gateWarned;
|
|
135
|
+
private badge?;
|
|
136
|
+
private unsubscribeLicense?;
|
|
93
137
|
constructor(parent: HTMLElement, initialView: InitialView, callbacks?: RuntimeCoreCallbacks, basemapAttr?: string, headless?: HeadlessOptions, basemapOptions?: BasemapRuntimeOptions);
|
|
94
138
|
/** `basemap` attribute → concrete style, logging resolution problems (unknown preset / missing key) — the demo-style fallback still renders. */
|
|
95
139
|
private resolveBasemap;
|
|
@@ -99,6 +143,40 @@ export declare class RuntimeCore {
|
|
|
99
143
|
* (none ↔ maplibre). Reads/seeds the camera from `this.viewState`.
|
|
100
144
|
*/
|
|
101
145
|
private initRenderer;
|
|
146
|
+
/** The current lighting as a deck `effects` array — [] restores deck's default lights. */
|
|
147
|
+
private buildEffects;
|
|
148
|
+
/**
|
|
149
|
+
* Scene lighting (spec: "Scene Lighting") — swap the LightingEffect on
|
|
150
|
+
* the live renderer. null restores deck's default lights. Headless stores
|
|
151
|
+
* the IR (inspectable via the map's internal getter) with no renderer to
|
|
152
|
+
* drive; a change during the lazy basemap-chunk window is picked up by
|
|
153
|
+
* the adapter constructor (which reads `this.lighting` on arrival). A
|
|
154
|
+
* sunDate-driven sun resolves az/el HERE, against the current map center
|
|
155
|
+
* — deterministic per apply, not tracked per frame.
|
|
156
|
+
*/
|
|
157
|
+
setLighting(ir: LightingIR | null): void;
|
|
158
|
+
getLightingInternal(): LightingIR | null;
|
|
159
|
+
/**
|
|
160
|
+
* Terrain on/off/source change (spec: "Terrain"). Bumps the generation
|
|
161
|
+
* (fresh deck ids for extension-carrying layers — birth-stable extension
|
|
162
|
+
* sets), swaps the renderer mode when needed (terrain suppresses an
|
|
163
|
+
* active basemap; turning it off restores the suppressed one), and
|
|
164
|
+
* re-applies layers. Headless stores the IR for inspection.
|
|
165
|
+
*/
|
|
166
|
+
setTerrain(ir: TerrainIR | null): void;
|
|
167
|
+
getTerrainInternal(): TerrainIR | null;
|
|
168
|
+
private drainSnapshotCaptures;
|
|
169
|
+
/** Settle queued snapshot promises when the renderer they were waiting on goes away (mode flip / destroy) — never leave a caller hanging. */
|
|
170
|
+
private cancelSnapshotCaptures;
|
|
171
|
+
/**
|
|
172
|
+
* Canvas-only scene snapshot (spec: "Snapshot API") — the composite of
|
|
173
|
+
* basemap + deck canvases at device pixels, captured right after a forced
|
|
174
|
+
* repaint (no preserveDrawingBuffer in either context). DOM widgets,
|
|
175
|
+
* overlays, the badge, and the attribution control are NOT captured —
|
|
176
|
+
* consumers rendering exports must add provider credits themselves.
|
|
177
|
+
* Headless rejects (no renderer); pre-ready rejects (await map.ready).
|
|
178
|
+
*/
|
|
179
|
+
snapshot(): Promise<HTMLCanvasElement>;
|
|
102
180
|
/**
|
|
103
181
|
* Live basemap change (spec: "Basemap presets & switching"), both paths:
|
|
104
182
|
* - maplibre → maplibre: `map.setStyle()` — deck layers survive (the
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene lighting (spec: "Scene Lighting") — the manifest surface over
|
|
3
|
+
* deck.gl's LightingEffect, resolving the backlog TODO ("Lighting/effects
|
|
4
|
+
* manifest surface"). Attributes on <om-map>, the basemap= precedent:
|
|
5
|
+
* singleton scene state as attributes gets undo/redo for free (attribute
|
|
6
|
+
* history) and live switching through the same MutationObserver path.
|
|
7
|
+
*
|
|
8
|
+
* <om-map lighting="daylight">
|
|
9
|
+
* <om-map lighting="custom" lighting-ambient="0.6" lighting-sun="1.5"
|
|
10
|
+
* lighting-sun-azimuth="135" lighting-sun-elevation="45">
|
|
11
|
+
* <om-map lighting="daylight" lighting-sun-date="2026-07-15T14:00:00Z">
|
|
12
|
+
*
|
|
13
|
+
* The sun is ALWAYS a DirectionalLight driven by azimuth/elevation;
|
|
14
|
+
* `lighting-sun-date` is sugar that computes az/el from solar position at
|
|
15
|
+
* the map center when lighting is applied (deliberately NOT deck's
|
|
16
|
+
* experimental `_SunLight`, which recomputes per frame from the viewport —
|
|
17
|
+
* an attribute-declared scene should be deterministic, and the underscore
|
|
18
|
+
* API is upgrade churn). Absent `lighting` = deck's default lights —
|
|
19
|
+
* today's behavior, so the whole surface is additive.
|
|
20
|
+
*/
|
|
21
|
+
import { AmbientLight, _CameraLight as CameraLight, DirectionalLight, LightingEffect } from "@deck.gl/core";
|
|
22
|
+
export interface LightingIR {
|
|
23
|
+
/** Preset the values were seeded from (validation/UI echo; "custom" for a fully hand-tuned scene). */
|
|
24
|
+
preset: LightingPresetName;
|
|
25
|
+
/** Ambient intensity — always-on fill. */
|
|
26
|
+
ambient: number;
|
|
27
|
+
/** Sun (directional) intensity; 0 omits the sun light entirely. */
|
|
28
|
+
sun: number;
|
|
29
|
+
/** Sun compass bearing, ° clockwise from north. */
|
|
30
|
+
sunAzimuth: number;
|
|
31
|
+
/** Sun height above the horizon, ° (0 = horizon, 90 = zenith). */
|
|
32
|
+
sunElevation: number;
|
|
33
|
+
/** Camera-following fill intensity (model inspection); 0 omits it. */
|
|
34
|
+
camera: number;
|
|
35
|
+
/** Epoch ms — when set, az/el are recomputed from solar position at apply time. */
|
|
36
|
+
sunDate?: number;
|
|
37
|
+
}
|
|
38
|
+
export type LightingPresetName = "daylight" | "studio" | "flat" | "custom";
|
|
39
|
+
export declare const LIGHTING_PRESET_NAMES: readonly ["daylight", "studio", "flat", "custom"];
|
|
40
|
+
export declare function isLightingPreset(name: string): name is LightingPresetName;
|
|
41
|
+
/** Programmatic twin of the lighting-* attributes (MapControllerOptions.lighting / setLighting). */
|
|
42
|
+
export interface LightingOptions {
|
|
43
|
+
preset?: LightingPresetName;
|
|
44
|
+
ambient?: number;
|
|
45
|
+
sun?: number;
|
|
46
|
+
sunAzimuth?: number;
|
|
47
|
+
sunElevation?: number;
|
|
48
|
+
camera?: number;
|
|
49
|
+
/** Epoch ms or ISO 8601 — the lighting-sun-date twin. */
|
|
50
|
+
sunDate?: number | string;
|
|
51
|
+
}
|
|
52
|
+
/** Preset seeds values; explicit fields override individual seeds; sunDate (resolved at apply time) wins over az/el. */
|
|
53
|
+
export declare function resolveLighting(options: LightingOptions): LightingIR;
|
|
54
|
+
/**
|
|
55
|
+
* `lighting*` attributes → IR. Returns null when `lighting` itself is
|
|
56
|
+
* absent (deck default lights; stray lighting-* overrides without it are
|
|
57
|
+
* inert — validation warns). Malformed numbers fall back to the preset seed
|
|
58
|
+
* (validation flags them; the live path must still render something).
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseLightingAttrs(getAttr: (name: string) => string | null): LightingIR | null;
|
|
61
|
+
/**
|
|
62
|
+
* Solar azimuth/elevation at a timestamp + location, in the manual-sun
|
|
63
|
+
* convention (azimuth ° CW from north, elevation ° above horizon). The
|
|
64
|
+
* standard suncalc derivation (the same math deck's `_SunLight` uses via
|
|
65
|
+
* @math.gl/sun): solar azimuth measured south→west, so matching the
|
|
66
|
+
* DirectionalLight convention is `azimuth = solar + 180°`. Below-horizon
|
|
67
|
+
* sun clamps to the horizon — a flat scene can't render a sun beneath it.
|
|
68
|
+
*/
|
|
69
|
+
export declare function solarAzElDegrees(timestamp: number, latitude: number, longitude: number): {
|
|
70
|
+
azimuth: number;
|
|
71
|
+
elevation: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* IR → the named lights map a LightingEffect is constructed from —
|
|
75
|
+
* separated so tests can assert composition without reaching into
|
|
76
|
+
* LightingEffect's private fields. The ambient light is always present;
|
|
77
|
+
* sun and camera lights only at intensity > 0 (deck renders a 0-intensity
|
|
78
|
+
* light as subtle noise, not nothing). `center` (current map center) feeds
|
|
79
|
+
* the sunDate → az/el resolution.
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildLights(ir: LightingIR, center?: [longitude: number, latitude: number]): Record<string, AmbientLight | DirectionalLight | CameraLight>;
|
|
82
|
+
/** IR → deck.gl LightingEffect (see buildLights for the composition rules). */
|
|
83
|
+
export declare function buildLightingEffect(ir: LightingIR, center?: [longitude: number, latitude: number]): LightingEffect;
|
package/dist/selection.d.ts
CHANGED
|
@@ -27,5 +27,5 @@ interface PickingInfoLike {
|
|
|
27
27
|
x: number;
|
|
28
28
|
y: number;
|
|
29
29
|
}
|
|
30
|
-
export declare function toSelection(info: PickingInfoLike, type: "hover" | "click"): Selection | null;
|
|
30
|
+
export declare function toSelection(info: PickingInfoLike, type: "hover" | "click", resolveLayerId?: (renderedId: string) => string): Selection | null;
|
|
31
31
|
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot serialization (spec: "Snapshot API") — shared by OmMapElement
|
|
3
|
+
* and MapController: the composite canvas RuntimeCore.snapshot() produces,
|
|
4
|
+
* serialized to the consumer's preferred shape.
|
|
5
|
+
*/
|
|
6
|
+
export interface SnapshotOptions {
|
|
7
|
+
/** Image MIME type — default "image/png". */
|
|
8
|
+
type?: "image/png" | "image/jpeg" | "image/webp";
|
|
9
|
+
/** Lossy-format quality 0–1 (jpeg/webp). */
|
|
10
|
+
quality?: number;
|
|
11
|
+
/** Output shape — default "dataURL" (what print/export pipelines embed directly); "blob" for uploads/files. */
|
|
12
|
+
as?: "dataURL" | "blob";
|
|
13
|
+
}
|
|
14
|
+
export declare function serializeSnapshot(canvas: HTMLCanvasElement, opts: SnapshotOptions): Promise<string | Blob>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The telemetry WIRE SCHEMA (spec: "Monetization Gates / Usage telemetry";
|
|
3
|
+
* public doc: docs/telemetry.md — keep all three in sync). Deliberately a
|
|
4
|
+
* types-only module with zero DOM/browser references: the ingestion Worker
|
|
5
|
+
* (cloud/workers/telemetry, WebWorker lib) type-imports this same file, so
|
|
6
|
+
* client and server can never drift apart silently — a schema change breaks
|
|
7
|
+
* the other side's typecheck in the same commit.
|
|
8
|
+
*/
|
|
9
|
+
export interface TelemetryLayerSnapshot {
|
|
10
|
+
type: string;
|
|
11
|
+
rows: number;
|
|
12
|
+
streaming: boolean;
|
|
13
|
+
refresh: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Library-caused error report (spec: "Monetization Gates / Usage telemetry" — library-error reporting). Scrubbed: no manifest content, no layer data, no page paths. */
|
|
16
|
+
export interface TelemetryErrorReport {
|
|
17
|
+
event: "library_error";
|
|
18
|
+
pageLoadId: string;
|
|
19
|
+
version: string;
|
|
20
|
+
/** Hostname only — never the path. */
|
|
21
|
+
origin: string;
|
|
22
|
+
dev: boolean;
|
|
23
|
+
/** Hash of message + top own-code frame — the dedup/grouping key. */
|
|
24
|
+
signature: string;
|
|
25
|
+
message: string;
|
|
26
|
+
/** Top stack lines from the library's own code, query strings stripped. */
|
|
27
|
+
frames: string[];
|
|
28
|
+
ua: string;
|
|
29
|
+
}
|
|
30
|
+
export interface TelemetrySnapshot {
|
|
31
|
+
event: "map_ready";
|
|
32
|
+
/** Random per page load — dedups beacon retries & multi-map pages. NOT persistent, NOT a visitor id. */
|
|
33
|
+
pageLoadId: string;
|
|
34
|
+
/** The authored `map-id` attribute — identifies the map artifact, not the visitor. */
|
|
35
|
+
mapId: string | null;
|
|
36
|
+
version: string;
|
|
37
|
+
/** "free" until the license module (monetization M1) exists. */
|
|
38
|
+
plan: string;
|
|
39
|
+
keyId: string | null;
|
|
40
|
+
/** Hostname only — never the path. */
|
|
41
|
+
origin: string;
|
|
42
|
+
frontend: "html" | "react" | "programmatic";
|
|
43
|
+
renderer: "maplibre" | "standalone";
|
|
44
|
+
/** Dev context (localhost et al.) — the only beacons the server may GeoIP (country code, IP discarded). */
|
|
45
|
+
dev: boolean;
|
|
46
|
+
layers: TelemetryLayerSnapshot[];
|
|
47
|
+
story: {
|
|
48
|
+
steps: number;
|
|
49
|
+
} | null;
|
|
50
|
+
widgets: string[];
|
|
51
|
+
draw: boolean;
|
|
52
|
+
undoRedo: boolean;
|
|
53
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage telemetry — client side (spec: "Monetization Gates / Usage
|
|
3
|
+
* telemetry", phase M3 client half). One snapshot per <om-map> instance per
|
|
4
|
+
* page load, built when that map reaches `ready` (manifest fully parsed —
|
|
5
|
+
* late mutations go uncounted, by design). Deployment-scoped, never
|
|
6
|
+
* visitor-scoped: the payload describes the page's use of the library.
|
|
7
|
+
*
|
|
8
|
+
* LIVE by default since the ingestion Worker deployment (2026-07-14): the
|
|
9
|
+
* default endpoint is the first-party Worker at om-api.nika.eco (spec's
|
|
10
|
+
* "beacon must not ship before something is listening" — it is listening).
|
|
11
|
+
* Disable with `configureTelemetry({ disabled: true })`, per map with
|
|
12
|
+
* `telemetry="off"`, or clear the endpoint (`{ endpoint: undefined }`).
|
|
13
|
+
* Disclosure: LICENSE.md §11; public schema: docs/telemetry.md.
|
|
14
|
+
*
|
|
15
|
+
* Non-negotiables (spec, binding):
|
|
16
|
+
* - telemetry never affects function: everything is wrapped, fire-and-
|
|
17
|
+
* forget, no retry, silent on failure; nothing awaits it;
|
|
18
|
+
* - no PII, no page URLs (hostname only), no visitor identifiers —
|
|
19
|
+
* `pageLoadId` dies with the page; `map-id` identifies the map ARTIFACT;
|
|
20
|
+
* - opt-out: `OmMap.configureTelemetry({ disabled: true })` or
|
|
21
|
+
* `telemetry="off"` on the map element;
|
|
22
|
+
* - headless maps (consumer test suites) never report.
|
|
23
|
+
*/
|
|
24
|
+
import type { LayerIR } from "./ir";
|
|
25
|
+
import type { TelemetrySnapshot, TelemetryErrorReport } from "./telemetry-schema";
|
|
26
|
+
export type { TelemetrySnapshot, TelemetryLayerSnapshot } from "./telemetry-schema";
|
|
27
|
+
export interface TelemetryConfig {
|
|
28
|
+
/** Kill switch — disables the map-ready beacon and library-error reports globally. */
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
/** Ingestion endpoint — the first-party Worker by default; explicitly set `undefined` to silence all sends. */
|
|
31
|
+
endpoint?: string;
|
|
32
|
+
}
|
|
33
|
+
/** The first-party ingestion Worker (cloud/workers/telemetry) — a domain we control, never a vendor's. */
|
|
34
|
+
export declare const DEFAULT_TELEMETRY_ENDPOINT = "https://om-api.nika.eco/v1/t";
|
|
35
|
+
/** Merge-assign: only keys present in `partial` change (so `{ endpoint: undefined }` explicitly clears). */
|
|
36
|
+
export declare function configureTelemetry(partial: TelemetryConfig): void;
|
|
37
|
+
/** Read-only view for the error-reporting module (same gates, same switch). */
|
|
38
|
+
export declare function getTelemetryConfig(): Readonly<TelemetryConfig>;
|
|
39
|
+
/** One id per page load, shared by every map on the page. */
|
|
40
|
+
export declare const pageLoadId: string;
|
|
41
|
+
/** Hostname-only origin + dev flag — shared with error reporting. */
|
|
42
|
+
export declare function hostContext(): {
|
|
43
|
+
origin: string;
|
|
44
|
+
dev: boolean;
|
|
45
|
+
};
|
|
46
|
+
export declare function buildMapSnapshot(mapEl: Element, layerIRs: ReadonlyMap<string, LayerIR>, frontend?: TelemetrySnapshot["frontend"]): TelemetrySnapshot;
|
|
47
|
+
/**
|
|
48
|
+
* Fire-and-forget POST. `text/plain` is deliberate: it keeps the request
|
|
49
|
+
* CORS-simple (no preflight from every customer origin) — the Worker parses
|
|
50
|
+
* the body as JSON regardless of the content type. sendBeacon survives page
|
|
51
|
+
* unload; `fetch keepalive` is the fallback where sendBeacon is missing.
|
|
52
|
+
* Never throws, never retries, nothing observes the result.
|
|
53
|
+
*/
|
|
54
|
+
export declare function postPayload(payload: TelemetrySnapshot | TelemetryErrorReport): void;
|
|
55
|
+
/**
|
|
56
|
+
* The om-map `ready` hook — one call per map instance per page load
|
|
57
|
+
* (guarded by the caller's readyFired latch). All gates checked here so the
|
|
58
|
+
* call site stays a single unconditional line.
|
|
59
|
+
*/
|
|
60
|
+
export declare function reportMapReady(mapEl: Element, layerIRs: ReadonlyMap<string, LayerIR>): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terrain (spec: "Terrain") — 3D elevation surface + per-layer draping,
|
|
3
|
+
* declared as `terrain*` attributes on <om-map> (the basemap=/lighting=
|
|
4
|
+
* scene-attribute precedent: free undo/redo, live MutationObserver
|
|
5
|
+
* switching).
|
|
6
|
+
*
|
|
7
|
+
* <om-map terrain="terrarium" terrain-exaggeration="1.5" pitch="55">
|
|
8
|
+
* <om-map terrain="https://tiles.example.com/dem/{z}/{x}/{y}.png"
|
|
9
|
+
* terrain-decoder="terrarium" terrain-max-zoom="14"
|
|
10
|
+
* terrain-texture="https://…/satellite/{z}/{x}/{y}.jpg">
|
|
11
|
+
*
|
|
12
|
+
* The seventh plugin registry (`OmMap.registerTerrain`) resolves preset
|
|
13
|
+
* names; a `{z}/{x}/{y}` URL is bring-your-own DEM (requires
|
|
14
|
+
* terrain-decoder). Geographic layers DRAPE onto the surface by default;
|
|
15
|
+
* a per-layer `terrain="drape|offset|off"` attribute overrides (3D-model
|
|
16
|
+
* layers default to `offset` — models sit ON terrain, not painted onto
|
|
17
|
+
* it).
|
|
18
|
+
*
|
|
19
|
+
* Terrain REPLACES an active basemap: a flat MapLibre canvas sits at sea
|
|
20
|
+
* level and visibly desyncs from a raised surface (true coexistence is
|
|
21
|
+
* the interleaved-compositing TODO). RuntimeCore suppresses the basemap
|
|
22
|
+
* while terrain is active and restores it when terrain turns off.
|
|
23
|
+
*
|
|
24
|
+
* `applyTerrain` is PURE (unit-testable without a GPU — headless never
|
|
25
|
+
* reaches the deck handoff): it computes each layer's terrain patches and
|
|
26
|
+
* the terrain-surface layer props; RuntimeCore feeds them to buildLayers.
|
|
27
|
+
*/
|
|
28
|
+
import type { Layer } from "@deck.gl/core";
|
|
29
|
+
import type { LayerIR } from "./ir";
|
|
30
|
+
/** RGB DEM decoder: height = offset + R·rScaler + G·gScaler + B·bScaler. */
|
|
31
|
+
export interface ElevationDecoder {
|
|
32
|
+
rScaler: number;
|
|
33
|
+
gScaler: number;
|
|
34
|
+
bScaler: number;
|
|
35
|
+
offset: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const DECODERS: Readonly<Record<string, ElevationDecoder>>;
|
|
38
|
+
export interface TerrainPreset {
|
|
39
|
+
/** `{z}/{x}/{y}` DEM tile URL template (may carry a `{key}` placeholder for keyed providers). */
|
|
40
|
+
elevationData: string;
|
|
41
|
+
/** Decoder name ("terrarium" / "mapbox-rgb") or explicit scalers. */
|
|
42
|
+
decoder: keyof typeof DECODERS | ElevationDecoder;
|
|
43
|
+
/**
|
|
44
|
+
* The provider's REAL tileset cap — deck over-samples past it for closer
|
|
45
|
+
* cameras; a too-high cap requests tiles that 404 and blanks the terrain.
|
|
46
|
+
*/
|
|
47
|
+
maxZoom: number;
|
|
48
|
+
/** Optional draped imagery `{z}/{x}/{y}` URL template. */
|
|
49
|
+
texture?: string;
|
|
50
|
+
/** Display name (switcher UIs). */
|
|
51
|
+
label?: string;
|
|
52
|
+
/** Which key `{key}` substitutes — the basemap-registry convention. */
|
|
53
|
+
requiresKey?: "maptiler";
|
|
54
|
+
}
|
|
55
|
+
export declare function registerTerrain(name: string, preset: TerrainPreset): void;
|
|
56
|
+
export declare function getTerrain(name: string): TerrainPreset | undefined;
|
|
57
|
+
export declare function getAllTerrains(): ReadonlyMap<string, TerrainPreset>;
|
|
58
|
+
export interface TerrainIR {
|
|
59
|
+
elevationData: string;
|
|
60
|
+
elevationDecoder: ElevationDecoder;
|
|
61
|
+
maxZoom: number;
|
|
62
|
+
/** Multiplies decoded DEM heights (1 = true relief). */
|
|
63
|
+
exaggeration: number;
|
|
64
|
+
texture?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface ResolvedTerrain {
|
|
67
|
+
ir: TerrainIR | null;
|
|
68
|
+
/** Resolution problem (unknown preset / missing key / missing decoder) — the caller decides how loudly to fail; `ir` is null when unresolvable. */
|
|
69
|
+
error?: string;
|
|
70
|
+
}
|
|
71
|
+
/** Is this `terrain` value a bring-your-own DEM URL rather than a preset name? (The basemap isStyleUrl convention.) */
|
|
72
|
+
export declare function isDemUrl(raw: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* `terrain*` attributes → TerrainIR. Returns `{ir: null}` when `terrain`
|
|
75
|
+
* is absent/"off"; `{ir: null, error}` when set but unresolvable (unknown
|
|
76
|
+
* preset, keyless keyed preset, raw URL without a decoder) — terrain
|
|
77
|
+
* renders NOTHING in that case rather than a wrong surface, and validation
|
|
78
|
+
* carries the loud version of the same message.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseTerrainAttrs(getAttr: (name: string) => string | null, maptilerKey?: string): ResolvedTerrain;
|
|
81
|
+
export declare const TERRAIN_LAYER_ID = "__onlymapjs-terrain";
|
|
82
|
+
export type LayerTerrainMode = "drape" | "offset" | "off";
|
|
83
|
+
export declare const LAYER_TERRAIN_MODES: readonly ["drape", "offset", "off"];
|
|
84
|
+
export declare function defaultTerrainMode(type: string): LayerTerrainMode;
|
|
85
|
+
export interface TerrainLayerPatch {
|
|
86
|
+
/**
|
|
87
|
+
* The deck-layer id to construct with. Suffixed `#t<generation>` while
|
|
88
|
+
* terrain is active: adding/removing an extension on a LIVE layer
|
|
89
|
+
* recompiles shaders and corrupts attribute state (the DataFilterExtension
|
|
90
|
+
* birth-mount rule), and a stale TerrainExtension vertical offset survives
|
|
91
|
+
* a terrain toggle on a stable id — a fresh id forces clean teardown +
|
|
92
|
+
* rebuild. RuntimeCore maps these generated ids back to authored ids for
|
|
93
|
+
* picks without guessing from the string shape.
|
|
94
|
+
*/
|
|
95
|
+
deckId: string;
|
|
96
|
+
/** Extra constructor props: extensions (APPENDED to existing — DataFilterExtension stays mounted) + terrainDrawMode. Empty for mode "off". */
|
|
97
|
+
extraProps: Record<string, unknown>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Per-layer terrain patches, PURE. `terrainActive` false → identity patches
|
|
101
|
+
* (stable ids, no props). Runtime-internal layers (trace temps, draw
|
|
102
|
+
* preview, tooltip plumbing) never drape — they're screen-space/transient.
|
|
103
|
+
*/
|
|
104
|
+
export declare function applyTerrain(irs: readonly LayerIR[], terrainActive: boolean, generation: number): Map<string, TerrainLayerPatch>;
|
|
105
|
+
/**
|
|
106
|
+
* The terrain-surface layer itself. `operation: "terrain+draw"` makes it
|
|
107
|
+
* double as the elevation surface extension-carrying layers drape onto;
|
|
108
|
+
* `pickable: "3d"` runs the depth pick pass so click/hover coordinates
|
|
109
|
+
* unproject at the SURFACE's depth instead of z=0 sea level (without it,
|
|
110
|
+
* tilted-view picks/drawn vertices land behind the cursor). Exempt from
|
|
111
|
+
* license gates by the `__onlymapjs-` prefix.
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildTerrainLayer(ir: TerrainIR): Layer;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The library version, as a compile-time constant (telemetry payloads,
|
|
3
|
+
* future diagnostics). Kept in sync with package.json by a drift test
|
|
4
|
+
* (telemetry.test.ts) rather than a JSON import — package.json sits outside
|
|
5
|
+
* the build rootDir, and a `define` would need repeating across vite/vitest/
|
|
6
|
+
* vite-node configs.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LIBRARY_VERSION = "0.3.0";
|