@ngis/plugin-sdk 0.4.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/LICENSE +13 -0
- package/README.md +31 -0
- package/dist/index.cjs +76 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.mjs +102 -0
- package/dist/src/api/analysis.d.ts +140 -0
- package/dist/src/api/resources.d.ts +530 -0
- package/dist/src/api/scenes.d.ts +251 -0
- package/dist/src/api/users.d.ts +22 -0
- package/dist/src/domain/algorithms/deck-layer-style.d.ts +34 -0
- package/dist/src/domain/algorithms/maplibre-style.d.ts +63 -0
- package/dist/src/domain/algorithms/raster-color-ramps.d.ts +37 -0
- package/dist/src/domain/algorithms/scene-layer-type.d.ts +25 -0
- package/dist/src/domain/algorithms/symbol-compile.d.ts +100 -0
- package/dist/src/domain/algorithms/symbol-params.d.ts +90 -0
- package/dist/src/domain/map/bounds.d.ts +8 -0
- package/dist/src/domain/map/constants.d.ts +40 -0
- package/dist/src/domain/map/layer-builders.d.ts +32 -0
- package/dist/src/domain/map/rendered-layers.d.ts +30 -0
- package/dist/src/domain/map/style-ready.d.ts +24 -0
- package/dist/src/domain/map/tile-url.d.ts +20 -0
- package/dist/src/domain/map/types.d.ts +128 -0
- package/dist/src/domain/models/deck-layer-style.d.ts +211 -0
- package/dist/src/domain/models/scene-layer-type.d.ts +23 -0
- package/dist/src/domain/models/symbol.d.ts +424 -0
- package/dist/src/lib/api.d.ts +82 -0
- package/dist/src/lib/auth-session.d.ts +22 -0
- package/dist/src/lib/raster-color-layer/RasterColorLayer.d.ts +48 -0
- package/dist/src/lib/raster-color-layer/TileGrid.d.ts +31 -0
- package/dist/src/lib/raster-color-layer/colorRamp.d.ts +18 -0
- package/dist/src/lib/raster-color-layer/shaders/raster-color.frag.d.ts +2 -0
- package/dist/src/lib/raster-color-layer/shaders/raster-color.vert.d.ts +2 -0
- package/dist/src/lib/raster-color-layer/zoomInterpolate.d.ts +2 -0
- package/dist/src/sdk/commands.d.ts +113 -0
- package/dist/src/sdk/context/keys.d.ts +148 -0
- package/dist/src/sdk/context/publishers.d.ts +198 -0
- package/dist/src/sdk/context/store.d.ts +78 -0
- package/dist/src/sdk/context/when.d.ts +69 -0
- package/dist/src/sdk/contributions/dispatch.d.ts +78 -0
- package/dist/src/sdk/contributions/panel-id.d.ts +25 -0
- package/dist/src/sdk/contributions/registry.d.ts +199 -0
- package/dist/src/sdk/contributions/symbol-renderers.d.ts +107 -0
- package/dist/src/sdk/contributions/types.d.ts +288 -0
- package/dist/src/sdk/contributions/views-bridge.d.ts +92 -0
- package/dist/src/sdk/define-plugin.d.ts +17 -0
- package/dist/src/sdk/facets/storage.d.ts +152 -0
- package/dist/src/sdk/facets/types.d.ts +370 -0
- package/dist/src/sdk/host-api.d.ts +256 -0
- package/dist/src/sdk/map-api.d.ts +288 -0
- package/dist/src/sdk/panels.d.ts +130 -0
- package/dist/src/sdk/plugin.d.ts +145 -0
- package/dist/src/sdk/rail-tools.d.ts +58 -0
- package/dist/src/sdk/scopes.d.ts +158 -0
- package/dist/src/systems/renderer/DeckSceneRenderer.d.ts +26 -0
- package/dist/src/systems/renderer/FlowFieldLayer.d.ts +68 -0
- package/dist/src/systems/renderer/LayerRendererRegistry.d.ts +132 -0
- package/dist/src/systems/renderer/authenticated-deck-load.d.ts +4 -0
- package/dist/src/systems/renderer/deck/deck-layer-builders.d.ts +29 -0
- package/dist/src/systems/renderer/deck/deck-layer-helpers.d.ts +21 -0
- package/dist/src/systems/renderer/deck/mvt-point-aggregation-layer.d.ts +107 -0
- package/dist/src/systems/renderer/flow-field/flow-field-resources.d.ts +84 -0
- package/dist/src/systems/renderer/flow-field/flow-field-shaders.d.ts +4 -0
- package/dist/src/systems/renderer/symbol/SpriteRegistry.d.ts +34 -0
- package/dist/src/systems/renderer/symbol/entries.d.ts +67 -0
- package/dist/src/types/analysis.d.ts +205 -0
- package/dist/src/types/common.d.ts +17 -0
- package/dist/src/types/extensions.d.ts +560 -0
- package/dist/src/types/gis.d.ts +242 -0
- package/dist/src/types/market-social.d.ts +378 -0
- package/package.json +47 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission-scope enforcement over the 0.4 facade (Stage 13 X5, W3 — F13.5).
|
|
3
|
+
*
|
|
4
|
+
* **This file's type surface is frozen by the W3 kickoff.** X9 owns the *call
|
|
5
|
+
* site* — it applies the wrapper inside `createGisPluginHostFactory` — and X6's
|
|
6
|
+
* three dogfood plugins declare their `permissions` against
|
|
7
|
+
* {@link NGIS_SCOPE_MEMBERS}. Three sessions depend on these exact names and
|
|
8
|
+
* this exact member map, which is why the kickoff authored them rather than a
|
|
9
|
+
* session (the W1 `NgisResourceType` lesson, restated at the W2 kickoff).
|
|
10
|
+
*
|
|
11
|
+
* `withScopeEnforcement` **shipped as a labelled pass-through stub and X5 filled
|
|
12
|
+
* it in** (merged 2026-07-27); the header is corrected here rather than left
|
|
13
|
+
* describing a stub that no longer exists. The pass-through was deliberate over
|
|
14
|
+
* throwing, so X9 could wire and test the real host path against a compiling
|
|
15
|
+
* wrapper before X5 merged, and so the failure mode if X5 had been dropped was
|
|
16
|
+
* "nothing is enforced" — visible in X5's own tests — rather than "the
|
|
17
|
+
* workspace does not boot".
|
|
18
|
+
*
|
|
19
|
+
* ## What enforcement is, and is not
|
|
20
|
+
*
|
|
21
|
+
* Facade-level and **documented as such**. Plugin code shares the origin and the
|
|
22
|
+
* session; a determined bundle can reach `fetch` directly and bypass every line
|
|
23
|
+
* here. This layer makes the *declared* contract real and auditable — it is not
|
|
24
|
+
* a security boundary. `docs/frontend-map-sdk.md` §3.5's stored-XSS posture is
|
|
25
|
+
* re-stated by X5, not resolved. Hard isolation is Stage 15.
|
|
26
|
+
*/
|
|
27
|
+
import type { NgisPermissionScope } from "./contributions/types";
|
|
28
|
+
import type { NgisHostAPI } from "./host-api";
|
|
29
|
+
/**
|
|
30
|
+
* The refusal, verbatim from F13.5.
|
|
31
|
+
*
|
|
32
|
+
* Never an empty result: a silent empty list is indistinguishable from a real
|
|
33
|
+
* empty scene, and a plugin that cannot tell the two apart will render "no
|
|
34
|
+
* data" where it should render "not permitted".
|
|
35
|
+
*/
|
|
36
|
+
export declare class NgisScopeError extends Error {
|
|
37
|
+
readonly code: "scope-denied";
|
|
38
|
+
/** The scope that was missing. */
|
|
39
|
+
readonly scope: NgisPermissionScope;
|
|
40
|
+
/** Dotted facet member, e.g. `"tasks.run"` — a key of {@link NGIS_SCOPE_MEMBERS}. */
|
|
41
|
+
readonly member: string;
|
|
42
|
+
constructor(scope: NgisPermissionScope, member: string);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* **The frozen member → scope map (F13.5).** Every gated member of the 0.4
|
|
46
|
+
* facade, spelled as `facet.member` (`storage` legs as `storage.user.get`).
|
|
47
|
+
*
|
|
48
|
+
* A member absent from this map is **ungated by decision, not by omission** —
|
|
49
|
+
* see {@link NGIS_UNGATED_MEMBERS}. X5 must not add or remove an entry: doing
|
|
50
|
+
* so silently changes what X6's manifests must declare and what XB1 validated
|
|
51
|
+
* at publish time.
|
|
52
|
+
*/
|
|
53
|
+
export declare const NGIS_SCOPE_MEMBERS: {
|
|
54
|
+
readonly "scene.get": "scene:read";
|
|
55
|
+
readonly "scene.onChange": "scene:read";
|
|
56
|
+
readonly "resources.list": "resources:read";
|
|
57
|
+
readonly "resources.get": "resources:read";
|
|
58
|
+
readonly "resources.fieldValues": "resources:read";
|
|
59
|
+
readonly "resources.onChange": "resources:read";
|
|
60
|
+
readonly "models.list": "models:read";
|
|
61
|
+
readonly "models.tree": "models:read";
|
|
62
|
+
readonly "models.get": "models:read";
|
|
63
|
+
readonly "models.schema": "models:read";
|
|
64
|
+
readonly "models.uiSchema": "models:read";
|
|
65
|
+
readonly "models.validate": "models:read";
|
|
66
|
+
readonly "models.search": "models:read";
|
|
67
|
+
readonly "tasks.run": "models:run";
|
|
68
|
+
readonly "tasks.get": "tasks:read";
|
|
69
|
+
readonly "tasks.list": "tasks:read";
|
|
70
|
+
readonly "tasks.watch": "tasks:read";
|
|
71
|
+
readonly "tasks.outputs": "tasks:read";
|
|
72
|
+
readonly "tasks.cancel": "tasks:cancel";
|
|
73
|
+
readonly "viz.addResultLayer": "viz:write";
|
|
74
|
+
readonly "storage.user.get": "storage:user";
|
|
75
|
+
readonly "storage.user.set": "storage:user";
|
|
76
|
+
readonly "storage.user.canWrite": "storage:user";
|
|
77
|
+
readonly "storage.user.onChange": "storage:user";
|
|
78
|
+
readonly "storage.scene.get": "storage:scene";
|
|
79
|
+
readonly "storage.scene.set": "storage:scene";
|
|
80
|
+
readonly "storage.scene.canWrite": "storage:scene";
|
|
81
|
+
readonly "storage.scene.onChange": "storage:scene";
|
|
82
|
+
readonly "ui.toast": "ui:notify";
|
|
83
|
+
readonly "ui.confirm": "ui:notify";
|
|
84
|
+
readonly "ui.quickPick": "ui:notify";
|
|
85
|
+
readonly "ui.progress": "ui:notify";
|
|
86
|
+
};
|
|
87
|
+
export type NgisGatedMember = keyof typeof NGIS_SCOPE_MEMBERS;
|
|
88
|
+
/**
|
|
89
|
+
* Ungated on record, with the reason — F13.5's list, made executable so a test
|
|
90
|
+
* can assert the two sets partition the facade and nothing fell through.
|
|
91
|
+
*
|
|
92
|
+
* - `api.*` — **the back-compat rule.** A 0.3-era plugin declares no
|
|
93
|
+
* `permissions`, is never prompted, and keeps working byte-for-byte.
|
|
94
|
+
* - `context.*` — host-published state the plugin's own contributions already
|
|
95
|
+
* gate on; `set` cannot escape its `plugin:<pluginId>:` namespace (F14.11).
|
|
96
|
+
* - `commands.list` / `invoke` — the E7 registry is an explicitly-declared seam
|
|
97
|
+
* already (XD15). **F14.16 confirms this and settles what it implies
|
|
98
|
+
* (Stage 14, resolving `V-145`):** a command runs with the grants of the plugin
|
|
99
|
+
* that **owns** it — the callee's, not the caller's and not the intersection —
|
|
100
|
+
* so a plugin holding no scopes at all may invoke another plugin's command and
|
|
101
|
+
* that command runs with its own. No `commands:invoke` scope is added. The
|
|
102
|
+
* escalation this describes already existed via the module-scope host capture in
|
|
103
|
+
* `activate()`; intersection would stop a plugin relying on its own grants
|
|
104
|
+
* whenever a scope-less caller invoked it, and the caller's grants would run the
|
|
105
|
+
* callee's code under scopes it never declared. Since Stage 14 Y4a
|
|
106
|
+
* (`V-141`) all four invoke paths supply the **owner's** scope-enforced facade
|
|
107
|
+
* as `ctx.host`, derived from the command id.
|
|
108
|
+
* - `viz.openTable` / `viz.fit` — no mutation, and `host.api.camera.flyTo` is
|
|
109
|
+
* ungated; gating these would be inconsistent for no gain.
|
|
110
|
+
* - `viz.chart` — **F14.3, AMENDS F13.5.** Same reason as the two above: it
|
|
111
|
+
* mutates nothing. The spec is declarative data the host renders.
|
|
112
|
+
*/
|
|
113
|
+
export declare const NGIS_UNGATED_MEMBERS: readonly ["api", "context.get", "context.getAll", "context.set", "context.evaluate", "context.onChange", "commands.list", "commands.invoke", "viz.openTable", "viz.fit", "viz.chart"];
|
|
114
|
+
/** Reported to XB1's `POST /api/extensions/{extId}/scope-events` by X5. */
|
|
115
|
+
export interface NgisScopeDenial {
|
|
116
|
+
pluginId: string;
|
|
117
|
+
scope: NgisPermissionScope;
|
|
118
|
+
member: string;
|
|
119
|
+
}
|
|
120
|
+
export interface NgisScopeEnforcementOptions {
|
|
121
|
+
pluginId: string;
|
|
122
|
+
/**
|
|
123
|
+
* Effective grants, **read at call time, not at construction**.
|
|
124
|
+
*
|
|
125
|
+
* Two reasons this is a thunk. Re-consent must take effect without rebuilding
|
|
126
|
+
* a facade the plugin already holds; and the value must be the *intersection*
|
|
127
|
+
* of the stored `granted_scopes` with the resolved version's declared
|
|
128
|
+
* `permissions`. `V-85` established that the backend column is not
|
|
129
|
+
* recomputed when a version **narrows** its `permissions`, so a stale grant
|
|
130
|
+
* outlives the declaration that justified it. Intersecting at read time is
|
|
131
|
+
* the only place that can be fixed on the client.
|
|
132
|
+
*/
|
|
133
|
+
grants: () => ReadonlySet<NgisPermissionScope>;
|
|
134
|
+
/** Fire-and-forget; a failed audit POST must never turn into a second error
|
|
135
|
+
* on the plugin's call path. */
|
|
136
|
+
onDenied?: (denial: NgisScopeDenial) => void;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Wraps a facade so every member in {@link NGIS_SCOPE_MEMBERS} checks its scope
|
|
140
|
+
* first.
|
|
141
|
+
*
|
|
142
|
+
* **Refusal shape is part of the contract, and it is not uniform.** The gated
|
|
143
|
+
* members are a mix of async and synchronous, and the wrapper must preserve
|
|
144
|
+
* each signature exactly:
|
|
145
|
+
*
|
|
146
|
+
* - async members (`scene.get`, `tasks.run`, `storage.user.set`, …) **reject**
|
|
147
|
+
* with {@link NgisScopeError};
|
|
148
|
+
* - **synchronous** members — every `*.onChange` and `tasks.watch` — **throw**
|
|
149
|
+
* it synchronously.
|
|
150
|
+
*
|
|
151
|
+
* Do not make a synchronous member async to make refusal uniform: `onChange`
|
|
152
|
+
* returns an unsubscribe function that call sites invoke in a `useEffect`
|
|
153
|
+
* cleanup, and `tasks.watch` returns a watcher handle. Turning either into a
|
|
154
|
+
* promise breaks shipped call sites and the F13.2 subscription contract.
|
|
155
|
+
*
|
|
156
|
+
* `host.pluginId` and `host.version` pass through untouched.
|
|
157
|
+
*/
|
|
158
|
+
export declare function withScopeEnforcement(host: NgisHostAPI, options: NgisScopeEnforcementOptions): NgisHostAPI;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Layer } from "@deck.gl/core";
|
|
2
|
+
import type { Map as MapLibreMap } from "maplibre-gl";
|
|
3
|
+
export interface DeckSceneSyncResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
error: string | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* MapLibre v6 moved the camera transform off the map instance — what used to be
|
|
9
|
+
* `map.transform` now lives on the private `map._camera.transform` — but
|
|
10
|
+
* `@deck.gl/mapbox` still reads `map.transform.height` (and `._nearZ`/`._farZ`)
|
|
11
|
+
* when it builds the deck viewport (`deck-utils.js` `getViewport`). With
|
|
12
|
+
* `transform` undefined the overlay throws "Cannot read properties of undefined
|
|
13
|
+
* (reading 'height')" from inside MapLibre's render loop (`map.ts` →
|
|
14
|
+
* `triggerRepaint` → `_render`) and no deck layer ever draws.
|
|
15
|
+
*
|
|
16
|
+
* No published deck.gl release handles v6 yet (checked through 9.3.7 and
|
|
17
|
+
* 9.4.0-alpha.1 — all still read `map.transform`), so bridge the two private
|
|
18
|
+
* APIs here. Remove once deck.gl supports MapLibre v6.
|
|
19
|
+
*/
|
|
20
|
+
export declare function applyDeckTransformCompat(map: MapLibreMap): void;
|
|
21
|
+
export declare class DeckSceneRenderer {
|
|
22
|
+
private overlay;
|
|
23
|
+
/** Replace the full set of active deck.gl layers in one batched update. */
|
|
24
|
+
sync(map: MapLibreMap, layers: Layer[]): DeckSceneSyncResult;
|
|
25
|
+
destroy(): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CustomLayerInterface, CustomRenderMethodInput, Map as MapLibreMap } from "maplibre-gl";
|
|
2
|
+
import type { SceneLayerStyle } from "../../domain/map/types";
|
|
3
|
+
import type { AuthenticatedFetch } from "./authenticated-deck-load";
|
|
4
|
+
export type FlowFieldLayerOptions = {
|
|
5
|
+
id: string;
|
|
6
|
+
flowFieldId: string;
|
|
7
|
+
opacity: number;
|
|
8
|
+
speedFactor: number;
|
|
9
|
+
particleCount: number;
|
|
10
|
+
paused: boolean;
|
|
11
|
+
fetchWithAuth: AuthenticatedFetch;
|
|
12
|
+
triggerRepaint: () => void;
|
|
13
|
+
onError?: (error: Error) => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* MapLibre custom layer backed by one isolated WebGL2 particle simulation.
|
|
17
|
+
* Every program, texture, VAO, buffer and transform-feedback object belongs
|
|
18
|
+
* to this instance; removing one flow field cannot clear another instance.
|
|
19
|
+
*/
|
|
20
|
+
export declare class FlowFieldLayer implements CustomLayerInterface {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly type: "custom";
|
|
23
|
+
readonly renderingMode: "2d";
|
|
24
|
+
private opts;
|
|
25
|
+
private map;
|
|
26
|
+
private gl;
|
|
27
|
+
private abort;
|
|
28
|
+
private generation;
|
|
29
|
+
private visible;
|
|
30
|
+
private ready;
|
|
31
|
+
private assets;
|
|
32
|
+
private updateProgram;
|
|
33
|
+
private renderProgram;
|
|
34
|
+
private updateUniforms;
|
|
35
|
+
private renderUniforms;
|
|
36
|
+
private velocityTextures;
|
|
37
|
+
private seedTextures;
|
|
38
|
+
private validTextures;
|
|
39
|
+
private domainMaskTexture;
|
|
40
|
+
private projectionTexture;
|
|
41
|
+
private simulationBuffers;
|
|
42
|
+
private simulationVaos;
|
|
43
|
+
private transformFeedbacks;
|
|
44
|
+
private trailBuffers;
|
|
45
|
+
private renderVao;
|
|
46
|
+
private stateIndex;
|
|
47
|
+
private trailHead;
|
|
48
|
+
private trailCount;
|
|
49
|
+
private frameCounter;
|
|
50
|
+
private particleCount;
|
|
51
|
+
constructor(options: FlowFieldLayerOptions);
|
|
52
|
+
onAdd(map: MapLibreMap, gl: WebGLRenderingContext | WebGL2RenderingContext): void;
|
|
53
|
+
private load;
|
|
54
|
+
private createGpuResources;
|
|
55
|
+
private createParticleResources;
|
|
56
|
+
private runSimulation;
|
|
57
|
+
private bindTexture;
|
|
58
|
+
private bindRenderBuffers;
|
|
59
|
+
private drawFlow;
|
|
60
|
+
render(gl: WebGLRenderingContext | WebGL2RenderingContext, input: CustomRenderMethodInput): void;
|
|
61
|
+
update(style: SceneLayerStyle): boolean;
|
|
62
|
+
setVisible(visible: boolean): void;
|
|
63
|
+
onRemove(): void;
|
|
64
|
+
destroy(): void;
|
|
65
|
+
private invalidateAndRelease;
|
|
66
|
+
private destroyParticleResources;
|
|
67
|
+
private releaseGpuResources;
|
|
68
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LayerRendererRegistry` — the unified extension point behind the three
|
|
3
|
+
* scene-layer render paths (session C3, see
|
|
4
|
+
* docs/componentization_stage/session-C3-layer-renderer-registry-and-basemap.md
|
|
5
|
+
* and README.md "Target architecture" > L0/L2). Generalizes the old
|
|
6
|
+
* `DeckSceneRenderer`-local `deckLayerBuilders` map (SceneLayerTypeId ->
|
|
7
|
+
* builder) to cover native MapLibre layers and the custom raster-color WebGL
|
|
8
|
+
* layer too, so render-path selection is data-driven (one entry per
|
|
9
|
+
* `SceneLayerTypeId`) instead of if/else branching. This is what C8's SDK
|
|
10
|
+
* contracts will expose for plugin-contributed layer types next stage.
|
|
11
|
+
*
|
|
12
|
+
* `reconcileSceneLayers` is the registry-driven replacement for the old
|
|
13
|
+
* inline `syncSceneLayers` reconciliation loop that used to live in
|
|
14
|
+
* `SceneMapCanvas.tsx` (moved here + `SceneLayersController.tsx`, session
|
|
15
|
+
* C3). Its outer control flow (sort -> filter -> split deck/non-deck ->
|
|
16
|
+
* prune -> update-or-rebuild -> reposition -> flush deck) is unchanged from
|
|
17
|
+
* the proven, tested `domain/map/rendered-layers.ts#syncSceneLayers` (C1) —
|
|
18
|
+
* only the "how do I build/update/remove *one* layer" step now dispatches
|
|
19
|
+
* through a registered `LayerRendererEntry` instead of calling
|
|
20
|
+
* `buildMapLayer`/`buildCustomRasterColorLayer`/the deck builder map
|
|
21
|
+
* directly. `domain/map/rendered-layers.ts` itself is left byte-stable
|
|
22
|
+
* (owned/read by C6 this wave too) — its pure per-layer helpers
|
|
23
|
+
* (`applySceneLayerStyleUpdate`, `applyCustomRasterColorLayerStyleUpdate`,
|
|
24
|
+
* `removeRenderedSceneLayer`, `getSceneLayerBeforeId`,
|
|
25
|
+
* `moveSceneLayerToRenderSlot`) are reused unchanged, satisfying the design
|
|
26
|
+
* constraint that style-update diffing reuses that module.
|
|
27
|
+
*
|
|
28
|
+
* Deck entries are a deliberate special case, documented up front so future
|
|
29
|
+
* readers (and C8, wiring the SDK facade) aren't surprised:
|
|
30
|
+
*
|
|
31
|
+
* - Deck.gl layers are never added to the `RenderedSceneLayer[]` bookkeeping
|
|
32
|
+
* (`mount`/`update` return `null`) — this matches `syncSceneLayers`'s
|
|
33
|
+
* pre-existing behavior verified against `domain/map/query.ts`'s
|
|
34
|
+
* `getQueryableLayers` (only `fill`/`line`/`circle` maplibre-tracked
|
|
35
|
+
* layers were ever queryable; deck layers were not, before or after this
|
|
36
|
+
* session).
|
|
37
|
+
* - Deck.gl layers are built fresh into a shared per-sync-pass accumulator
|
|
38
|
+
* (`LayerRenderContext.deckLayers`) and flushed in ONE batched
|
|
39
|
+
* `DeckSceneRenderer.sync` call at the end of `reconcileSceneLayers` —
|
|
40
|
+
* exactly how the original code worked (deck.gl's own `MapboxOverlay`
|
|
41
|
+
* props-diffing decides what actually re-renders; nothing here calls
|
|
42
|
+
* `deckRenderer.sync` per-layer, which would churn the overlay control on
|
|
43
|
+
* every layer). `mount` and `update` are therefore the same
|
|
44
|
+
* implementation for deck entries, and `unmount` is a no-op: omitting a
|
|
45
|
+
* layer from `deckLayers` before the flush *is* the removal.
|
|
46
|
+
*/
|
|
47
|
+
import type { Map as MapLibreMap } from "maplibre-gl";
|
|
48
|
+
import type { Layer } from "@deck.gl/core";
|
|
49
|
+
import type { NgisSceneLayer, SceneResourceDto } from "../../api/scenes";
|
|
50
|
+
import type { SceneLayerTypeId } from "../../domain/models/scene-layer-type";
|
|
51
|
+
import type { RenderedSceneLayer, SceneLayerStyle } from "../../domain/map/types";
|
|
52
|
+
import type { TileRequestInitFactory } from "../../lib/raster-color-layer/TileGrid";
|
|
53
|
+
import type { AuthenticatedFetch } from "./authenticated-deck-load";
|
|
54
|
+
import type { DeckSceneRenderer } from "./DeckSceneRenderer";
|
|
55
|
+
import { type SymbolLayerRendererDeps } from "./symbol/entries";
|
|
56
|
+
/** Per-sync-pass state handed to every registry entry. */
|
|
57
|
+
export interface LayerRenderContext {
|
|
58
|
+
map: MapLibreMap;
|
|
59
|
+
deckRenderer: DeckSceneRenderer;
|
|
60
|
+
getTileRequestInit: TileRequestInitFactory;
|
|
61
|
+
/**
|
|
62
|
+
* Deck.gl layer instances accumulated during this reconciliation pass —
|
|
63
|
+
* see module doc. `reconcileSceneLayers` creates this fresh per call and
|
|
64
|
+
* flushes it once via `deckRenderer.sync` at the end.
|
|
65
|
+
*/
|
|
66
|
+
deckLayers: Layer[];
|
|
67
|
+
fetchWithAuth: AuthenticatedFetch;
|
|
68
|
+
triggerRepaint: () => void;
|
|
69
|
+
onLayerError?: (error: Error) => void;
|
|
70
|
+
}
|
|
71
|
+
/** One scene layer's resolved inputs, handed to `mount`/`update`. */
|
|
72
|
+
export interface SceneLayerRenderInput {
|
|
73
|
+
sceneLayer: NgisSceneLayer;
|
|
74
|
+
resource: SceneResourceDto;
|
|
75
|
+
style: SceneLayerStyle;
|
|
76
|
+
/**
|
|
77
|
+
* Deck-family only: the first symbol-layer id, so deck features insert
|
|
78
|
+
* below basemap labels (matches native layer stacking). Computed once per
|
|
79
|
+
* sync pass by `reconcileSceneLayers`.
|
|
80
|
+
*/
|
|
81
|
+
deckBeforeId?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One render path's lifecycle for one `SceneLayerTypeId`. `mount`/`update`
|
|
85
|
+
* return the bookkeeping entry to track (or `null` when the render path
|
|
86
|
+
* manages its own state outside `RenderedSceneLayer[]` — see the deck note
|
|
87
|
+
* above). `update` returns `false` when it refuses to diff in place (e.g. a
|
|
88
|
+
* geometry-type change) — the caller must then `unmount` + `mount` fresh.
|
|
89
|
+
*/
|
|
90
|
+
export interface LayerRendererEntry {
|
|
91
|
+
readonly typeId: SceneLayerTypeId;
|
|
92
|
+
mount(ctx: LayerRenderContext, input: SceneLayerRenderInput): RenderedSceneLayer | null;
|
|
93
|
+
update(ctx: LayerRenderContext, input: SceneLayerRenderInput, prev: RenderedSceneLayer): boolean;
|
|
94
|
+
unmount(ctx: LayerRenderContext, prev: RenderedSceneLayer): void;
|
|
95
|
+
}
|
|
96
|
+
export interface LayerRendererRegistry {
|
|
97
|
+
/** Register (or replace) the entry for `entry.typeId`. Returns an unregister function. */
|
|
98
|
+
register(entry: LayerRendererEntry): () => void;
|
|
99
|
+
get(typeId: SceneLayerTypeId): LayerRendererEntry | undefined;
|
|
100
|
+
readonly size: number;
|
|
101
|
+
}
|
|
102
|
+
export declare function createEmptyLayerRendererRegistry(): LayerRendererRegistry;
|
|
103
|
+
/**
|
|
104
|
+
* Build the registry with the three built-in render-path families
|
|
105
|
+
* registered. `fetchWithAuth` is threaded through to the deck family the
|
|
106
|
+
* same way `DeckSceneRenderer`/the raster loaders already do it (DI, not a
|
|
107
|
+
* module-level import) — see `authenticated-deck-load.ts`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function createLayerRendererRegistry(fetchWithAuth: AuthenticatedFetch, symbolDeps?: SymbolLayerRendererDeps): LayerRendererRegistry;
|
|
110
|
+
export interface ReconcileSceneLayersParams {
|
|
111
|
+
map: MapLibreMap;
|
|
112
|
+
layers: NgisSceneLayer[];
|
|
113
|
+
resources: SceneResourceDto[];
|
|
114
|
+
/** Mutated in place (push/splice) — same contract as the old `syncSceneLayers`. */
|
|
115
|
+
renderedLayers: RenderedSceneLayer[];
|
|
116
|
+
registry: LayerRendererRegistry;
|
|
117
|
+
deckRenderer: DeckSceneRenderer;
|
|
118
|
+
getTileRequestInit: TileRequestInitFactory;
|
|
119
|
+
onLayerError?: (error: Error) => void;
|
|
120
|
+
}
|
|
121
|
+
export interface ReconcileSceneLayersResult {
|
|
122
|
+
ok: boolean;
|
|
123
|
+
error: string | null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Registry-driven scene-layer reconciler — see module doc. Behaviorally
|
|
127
|
+
* equivalent to `domain/map/rendered-layers.ts#syncSceneLayers` (same
|
|
128
|
+
* control flow, ported deliberately), except each layer's actual
|
|
129
|
+
* mount/update/unmount now dispatches through `registry` instead of being
|
|
130
|
+
* inline. `SceneLayersController` (session C3) is the sole caller.
|
|
131
|
+
*/
|
|
132
|
+
export declare function reconcileSceneLayers(params: ReconcileSceneLayersParams): ReconcileSceneLayersResult;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Loader, type LoaderOptions } from "@loaders.gl/core";
|
|
2
|
+
export type AuthenticatedFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export declare function getDeckTileRetryDelayMs(retryAfter: string | null, retryCount: number): number;
|
|
4
|
+
export declare function loadDeckDataWithAuth(fetchWithAuth: AuthenticatedFetch, url: string, loaders: Loader[], loadOptions?: LoaderOptions, signal?: AbortSignal): Promise<any>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Layer } from "@deck.gl/core";
|
|
2
|
+
import { MVTLayer } from "@deck.gl/geo-layers";
|
|
3
|
+
import { type ImplementedDeckSceneLayerTypeId } from "../../../domain/models/deck-layer-style";
|
|
4
|
+
import { type AuthenticatedFetch } from "../authenticated-deck-load";
|
|
5
|
+
import { type DeckFeatureProperties, type DeckSceneLayerInput } from "./deck-layer-helpers";
|
|
6
|
+
import { MvtHeatmapLayer, MvtHexagonLayer } from "./mvt-point-aggregation-layer";
|
|
7
|
+
export { getDeckPointFeatures as getColumnPointFeatures } from "./deck-layer-helpers";
|
|
8
|
+
export type { DeckPointFeature as DeckColumnPointFeature } from "./deck-layer-helpers";
|
|
9
|
+
export type DeckLayerBuilder = (input: DeckSceneLayerInput, fetchWithAuth: AuthenticatedFetch) => Layer | null;
|
|
10
|
+
export interface DeckLayerBuilderRegistration {
|
|
11
|
+
typeId: ImplementedDeckSceneLayerTypeId;
|
|
12
|
+
build: DeckLayerBuilder;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildExtrudedPolygonLayer(input: DeckSceneLayerInput, fetchWithAuth: AuthenticatedFetch): MVTLayer<DeckFeatureProperties, {
|
|
15
|
+
beforeId?: string;
|
|
16
|
+
}> | null;
|
|
17
|
+
export declare function buildColumnLayer(input: DeckSceneLayerInput, fetchWithAuth: AuthenticatedFetch): MVTLayer<DeckFeatureProperties, {
|
|
18
|
+
beforeId?: string;
|
|
19
|
+
}> | null;
|
|
20
|
+
export declare function buildHeatmapLayer(input: DeckSceneLayerInput, fetchWithAuth: AuthenticatedFetch): MvtHeatmapLayer | null;
|
|
21
|
+
export declare function buildHexagonLayer(input: DeckSceneLayerInput, fetchWithAuth: AuthenticatedFetch): MvtHexagonLayer | null;
|
|
22
|
+
export declare function buildGraduatedLineLayer(input: DeckSceneLayerInput, fetchWithAuth: AuthenticatedFetch): MVTLayer<DeckFeatureProperties, {
|
|
23
|
+
beforeId?: string;
|
|
24
|
+
}> | null;
|
|
25
|
+
/** Renderer catalog ordered and keyed by the shared Deck capability source. */
|
|
26
|
+
export declare const BUILT_IN_DECK_LAYER_BUILDERS: {
|
|
27
|
+
typeId: "DECK_MVT_EXTRUDED_POLYGON" | "DECK_COLUMN" | "DECK_HEATMAP" | "DECK_HEXAGON" | "DECK_MVT_LINE";
|
|
28
|
+
build: typeof buildExtrudedPolygonLayer | typeof buildColumnLayer | typeof buildHeatmapLayer | typeof buildHexagonLayer | typeof buildGraduatedLineLayer;
|
|
29
|
+
}[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Feature, Geometry, Point } from "geojson";
|
|
2
|
+
import type { SceneLayerTypeId } from "../../../domain/models/scene-layer-type";
|
|
3
|
+
export type DeckColor = [number, number, number, number];
|
|
4
|
+
export type DeckFeatureProperties = Record<string, unknown>;
|
|
5
|
+
export type DeckPointFeature = Feature<Point, DeckFeatureProperties>;
|
|
6
|
+
export interface DeckSceneLayerInput {
|
|
7
|
+
id: string;
|
|
8
|
+
layerType: SceneLayerTypeId;
|
|
9
|
+
tileUrl: string;
|
|
10
|
+
styleJson: string | null;
|
|
11
|
+
bounds?: [number, number, number, number];
|
|
12
|
+
minZoom?: number;
|
|
13
|
+
maxZoom?: number;
|
|
14
|
+
beforeId?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function getDeckFeatureNumber(feature: Feature<Geometry, DeckFeatureProperties>, field: string): number;
|
|
17
|
+
export declare function getDeckFeatureValue(feature: Feature<Geometry, DeckFeatureProperties>, field: string): unknown;
|
|
18
|
+
export declare function toDeckColor(hex: string): DeckColor;
|
|
19
|
+
export declare function toDeckRgbColor(hex: string): [number, number, number];
|
|
20
|
+
/** Convert non-binary MVT Point/MultiPoint features into one datum per point. */
|
|
21
|
+
export declare function getDeckPointFeatures(data: unknown): DeckPointFeature[];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { HeatmapLayer, HexagonLayer } from "@deck.gl/aggregation-layers";
|
|
2
|
+
import { type Layer } from "@deck.gl/core";
|
|
3
|
+
import { MVTLayer } from "@deck.gl/geo-layers";
|
|
4
|
+
import type { Feature, Geometry, Position } from "geojson";
|
|
5
|
+
import type { DeckHeatmapStyle, DeckHexagonStyle } from "../../../domain/models/deck-layer-style";
|
|
6
|
+
import { type DeckColor, type DeckFeatureProperties } from "./deck-layer-helpers";
|
|
7
|
+
export type MvtTileIndex = {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
z: number;
|
|
11
|
+
};
|
|
12
|
+
export interface MvtAggregationSourcePoint {
|
|
13
|
+
dedupeKey: string;
|
|
14
|
+
feature: Feature<Geometry, DeckFeatureProperties>;
|
|
15
|
+
position: [number, number, number];
|
|
16
|
+
}
|
|
17
|
+
export interface MvtAggregationDatum {
|
|
18
|
+
feature: Feature<Geometry, DeckFeatureProperties>;
|
|
19
|
+
position: [number, number, number];
|
|
20
|
+
}
|
|
21
|
+
export interface MvtAggregationTilePointSet {
|
|
22
|
+
id: string;
|
|
23
|
+
zoom: number;
|
|
24
|
+
points: MvtAggregationSourcePoint[];
|
|
25
|
+
}
|
|
26
|
+
export interface MvtPointAggregationExtraProps {
|
|
27
|
+
beforeId?: string;
|
|
28
|
+
}
|
|
29
|
+
type MvtAggregationBase = MVTLayer<DeckFeatureProperties, MvtPointAggregationExtraProps>;
|
|
30
|
+
export type MvtAggregationTile = Parameters<MvtAggregationBase["_onTileLoad"]>[0];
|
|
31
|
+
interface CachedMvtAggregationTile extends MvtAggregationTilePointSet {
|
|
32
|
+
content: unknown;
|
|
33
|
+
revision: number;
|
|
34
|
+
}
|
|
35
|
+
interface MvtAggregationCache {
|
|
36
|
+
nextRevision: number;
|
|
37
|
+
tiles: Map<string, CachedMvtAggregationTile>;
|
|
38
|
+
mergedKey: string;
|
|
39
|
+
mergedData: MvtAggregationDatum[];
|
|
40
|
+
}
|
|
41
|
+
type MvtAggregationState = MvtAggregationBase["state"] & {
|
|
42
|
+
aggregationCache?: MvtAggregationCache;
|
|
43
|
+
};
|
|
44
|
+
/** Convert the MVT loader's normalized tile coordinates to longitude/latitude. */
|
|
45
|
+
export declare function tilePointToWgs84(coordinates: Position, tileIndex: MvtTileIndex): [number, number, number];
|
|
46
|
+
/** Normalize one non-binary MVT tile into reusable geographic point records. */
|
|
47
|
+
export declare function getMvtAggregationTilePoints(data: unknown, tileId: string, tileIndex: MvtTileIndex, uniqueIdProperty?: string): MvtAggregationSourcePoint[];
|
|
48
|
+
/** Merge child-first visible tile records and remove parent/buffer duplicates. */
|
|
49
|
+
export declare function mergeMvtAggregationTilePoints(tiles: readonly MvtAggregationTilePointSet[]): MvtAggregationDatum[];
|
|
50
|
+
/**
|
|
51
|
+
* Shared MVT adapter for viewport-wide point aggregation layers.
|
|
52
|
+
*
|
|
53
|
+
* MVT remains responsible for loading, caching and refining visible tiles.
|
|
54
|
+
* The adapter converts and deduplicates their points, then asks the concrete
|
|
55
|
+
* layer to render exactly one viewport-wide aggregation result.
|
|
56
|
+
*/
|
|
57
|
+
export declare abstract class MvtPointAggregationLayer<ExtraProps extends object = object> extends MVTLayer<DeckFeatureProperties, ExtraProps & MvtPointAggregationExtraProps> {
|
|
58
|
+
static layerName: string;
|
|
59
|
+
state: MvtAggregationState;
|
|
60
|
+
initializeState(): void;
|
|
61
|
+
finalizeState(): void;
|
|
62
|
+
_onTileLoad(tile: MvtAggregationTile): void;
|
|
63
|
+
_onTileUnload(tile: MvtAggregationTile): void;
|
|
64
|
+
/** A viewport aggregation result is not owned by any one source tile. */
|
|
65
|
+
filterSubLayer(): boolean;
|
|
66
|
+
renderLayers(): Layer | null | [];
|
|
67
|
+
protected abstract renderAggregationLayer(data: readonly MvtAggregationDatum[]): Layer | null;
|
|
68
|
+
private getAggregationCache;
|
|
69
|
+
private cacheTile;
|
|
70
|
+
private getMergedData;
|
|
71
|
+
}
|
|
72
|
+
interface HeatmapLayerStyleProps {
|
|
73
|
+
aggregation: DeckHeatmapStyle["aggregation"];
|
|
74
|
+
colorDomain: DeckHeatmapStyle["colorDomain"];
|
|
75
|
+
colorRange: DeckColor[];
|
|
76
|
+
debounceTimeout: number;
|
|
77
|
+
intensity: number;
|
|
78
|
+
radiusPixels: number;
|
|
79
|
+
threshold: number;
|
|
80
|
+
weight: DeckHeatmapStyle["weight"];
|
|
81
|
+
weightsTextureSize: number;
|
|
82
|
+
}
|
|
83
|
+
interface MvtHeatmapExtraProps {
|
|
84
|
+
heatmapStyle: HeatmapLayerStyleProps;
|
|
85
|
+
}
|
|
86
|
+
export type HeatmapDatum = MvtAggregationDatum;
|
|
87
|
+
export declare class MvtHeatmapLayer extends MvtPointAggregationLayer<MvtHeatmapExtraProps> {
|
|
88
|
+
static layerName: string;
|
|
89
|
+
protected renderAggregationLayer(data: readonly HeatmapDatum[]): HeatmapLayer<MvtAggregationDatum, {
|
|
90
|
+
beforeId?: string;
|
|
91
|
+
}>;
|
|
92
|
+
}
|
|
93
|
+
type HexagonLayerStyleProps = Omit<DeckHexagonStyle, "version" | "type" | "colorRange" | "opacity"> & {
|
|
94
|
+
colorRange: DeckColor[];
|
|
95
|
+
};
|
|
96
|
+
interface MvtHexagonExtraProps {
|
|
97
|
+
hexagonStyle: HexagonLayerStyleProps;
|
|
98
|
+
}
|
|
99
|
+
export type HexagonDatum = MvtAggregationDatum;
|
|
100
|
+
/** One viewport-wide HexagonLayer backed by the shared visible-MVT adapter. */
|
|
101
|
+
export declare class MvtHexagonLayer extends MvtPointAggregationLayer<MvtHexagonExtraProps> {
|
|
102
|
+
static layerName: string;
|
|
103
|
+
protected renderAggregationLayer(data: readonly HexagonDatum[]): HexagonLayer<MvtAggregationDatum, {
|
|
104
|
+
beforeId?: string;
|
|
105
|
+
}>;
|
|
106
|
+
}
|
|
107
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { AuthenticatedFetch } from "../authenticated-deck-load";
|
|
2
|
+
export interface FlowFieldManifest {
|
|
3
|
+
schema: "ngis-flow-field/1" | "ngis-flow-field/2";
|
|
4
|
+
id: string;
|
|
5
|
+
datasetVersion: string;
|
|
6
|
+
renderer: "flow-field";
|
|
7
|
+
crs: string;
|
|
8
|
+
bounds: [number, number, number, number];
|
|
9
|
+
grid: {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
encoding: string;
|
|
13
|
+
nodata: number;
|
|
14
|
+
};
|
|
15
|
+
frames: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
time?: string;
|
|
18
|
+
uv: string;
|
|
19
|
+
seed: string;
|
|
20
|
+
valid?: string;
|
|
21
|
+
}>;
|
|
22
|
+
clip?: {
|
|
23
|
+
mode: "bbox" | "vector-resource" | "inline-geojson" | string;
|
|
24
|
+
sourceFileId?: string | null;
|
|
25
|
+
sourceCrs?: string | null;
|
|
26
|
+
geometryType?: string | null;
|
|
27
|
+
geometryHash?: string | null;
|
|
28
|
+
rasterization: "pixel-center";
|
|
29
|
+
wgs84Bounds?: [number, number, number, number];
|
|
30
|
+
};
|
|
31
|
+
domainMask?: {
|
|
32
|
+
url: string;
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
encoding: "r8-binary";
|
|
36
|
+
bytes?: number;
|
|
37
|
+
sha256?: string;
|
|
38
|
+
};
|
|
39
|
+
assets?: Record<string, {
|
|
40
|
+
bytes: number;
|
|
41
|
+
sha256: string;
|
|
42
|
+
}>;
|
|
43
|
+
projection: {
|
|
44
|
+
url: string;
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
physicalWidth?: number;
|
|
48
|
+
encoding: string;
|
|
49
|
+
};
|
|
50
|
+
valueRange?: {
|
|
51
|
+
u?: [number, number];
|
|
52
|
+
v?: [number, number];
|
|
53
|
+
speed?: [number, number];
|
|
54
|
+
};
|
|
55
|
+
constraints?: {
|
|
56
|
+
maxParticles?: number;
|
|
57
|
+
maxSegments?: number;
|
|
58
|
+
maxTextureSize?: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface DecodedFlowFrame {
|
|
62
|
+
id: string;
|
|
63
|
+
velocity: Float32Array;
|
|
64
|
+
seed: Uint8Array;
|
|
65
|
+
valid: Uint8Array;
|
|
66
|
+
}
|
|
67
|
+
export interface DecodedFlowFieldAssets {
|
|
68
|
+
manifest: FlowFieldManifest;
|
|
69
|
+
frames: DecodedFlowFrame[];
|
|
70
|
+
projection: Float32Array;
|
|
71
|
+
domainMask: Uint8Array;
|
|
72
|
+
speedRange: [number, number];
|
|
73
|
+
}
|
|
74
|
+
interface RgbaImage {
|
|
75
|
+
width: number;
|
|
76
|
+
height: number;
|
|
77
|
+
pixels: Uint8Array;
|
|
78
|
+
}
|
|
79
|
+
export declare function digestFlowFieldAssetSha256(bytes: ArrayBuffer, subtle?: Pick<SubtleCrypto, "digest"> | null): Promise<string>;
|
|
80
|
+
export declare function parseFlowFieldManifest(value: unknown): FlowFieldManifest;
|
|
81
|
+
export declare function decodePackedFloatRgba(pixels: Uint8Array, physicalWidth: number, height: number, logicalWidth: number): Float32Array<ArrayBuffer>;
|
|
82
|
+
export declare function decodeBinaryMask(image: RgbaImage, width: number, height: number, label: string): Uint8Array<ArrayBuffer>;
|
|
83
|
+
export declare function loadFlowFieldAssets(fetchWithAuth: AuthenticatedFetch, fileId: string, signal: AbortSignal): Promise<DecodedFlowFieldAssets>;
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const FLOW_UPDATE_VERTEX_SHADER = "#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec4 a_state;\n\nuniform sampler2D u_velocity0;\nuniform sampler2D u_velocity1;\nuniform sampler2D u_seed;\nuniform sampler2D u_domainMask;\nuniform sampler2D u_valid0;\nuniform sampler2D u_valid1;\nuniform vec2 u_gridSize;\nuniform float u_frameMix;\nuniform float u_speedFactor;\nuniform float u_dropRate;\nuniform float u_dropRateBump;\nuniform float u_randomSeed;\nuniform float u_maxAge;\nuniform float u_nodata;\nuniform vec2 u_speedRange;\n\nout vec4 v_state;\n\nfloat randomValue(vec2 value) {\n return fract(sin(dot(value, vec2(12.9898, 78.233))) * 43758.5453123);\n}\n\nbool isDefinedVelocity(vec2 velocity) {\n return !any(isnan(velocity)) && !any(isinf(velocity))\n && abs(velocity.x - u_nodata) > 0.5\n && abs(velocity.y - u_nodata) > 0.5;\n}\n\nvec2 readVelocity(vec2 uv) {\n return mix(texture(u_velocity0, uv).rg, texture(u_velocity1, uv).rg, u_frameMix);\n}\n\nivec2 decodeSeedCell(vec4 packed) {\n ivec4 bytes = ivec4(floor(packed * 255.0 + 0.5));\n return ivec2(\n (bytes.r << 8) + bytes.g,\n (bytes.b << 8) + bytes.a\n );\n}\n\nbool readMask(sampler2D maskTexture, vec2 uv) {\n ivec2 size = textureSize(maskTexture, 0);\n ivec2 cell = clamp(ivec2(floor(uv * vec2(size))), ivec2(0), size - ivec2(1));\n return texelFetch(maskTexture, cell, 0).r > 0.5;\n}\n\nbool isFlowCell(vec2 uv) {\n return readMask(u_domainMask, uv)\n && readMask(u_valid0, uv)\n && readMask(u_valid1, uv);\n}\n\nfloat normalizedSpeed(vec2 velocity) {\n return clamp(\n (length(velocity) - u_speedRange.x) / max(u_speedRange.y - u_speedRange.x, 1.0e-6),\n 0.0,\n 1.0\n );\n}\n\nvec2 seedPosition(vec2 randomUv) {\n vec2 cell = vec2(decodeSeedCell(texture(u_seed, randomUv)));\n vec2 jitter = vec2(\n randomValue(randomUv + vec2(1.31, u_randomSeed)),\n randomValue(randomUv + vec2(u_randomSeed, 2.17))\n );\n return clamp((cell + jitter) / u_gridSize, vec2(0.0), vec2(1.0));\n}\n\nvec4 respawn(float salt) {\n vec2 randomUv = vec2(\n randomValue(vec2(float(gl_VertexID) + salt, u_randomSeed + 0.37)),\n randomValue(vec2(u_randomSeed + 0.73, float(gl_VertexID) + salt))\n );\n vec2 uv = seedPosition(randomUv);\n vec2 velocity = readVelocity(uv);\n return vec4(uv, 0.0, isDefinedVelocity(velocity) ? normalizedSpeed(velocity) : 0.0);\n}\n\nvoid main() {\n vec2 velocity = readVelocity(a_state.xy);\n float speed = isDefinedVelocity(velocity) ? normalizedSpeed(velocity) : 0.0;\n vec2 nextPosition = a_state.xy + velocity * u_speedFactor / u_gridSize;\n bool outside = any(lessThan(nextPosition, vec2(0.0))) || any(greaterThan(nextPosition, vec2(1.0)));\n float randomDrop = randomValue(a_state.xy + vec2(u_randomSeed, float(gl_VertexID) * 0.000001));\n bool dropped = randomDrop < (u_dropRate + speed * u_dropRateBump);\n bool expired = a_state.z >= u_maxAge;\n\n if (!isDefinedVelocity(velocity) || !isFlowCell(a_state.xy) || outside || dropped || expired) {\n v_state = respawn(a_state.z + 1.0);\n return;\n }\n\n vec2 nextVelocity = readVelocity(nextPosition);\n if (!isDefinedVelocity(nextVelocity) || !isFlowCell(nextPosition)) {\n v_state = respawn(a_state.z + 2.0);\n return;\n }\n\n v_state = vec4(nextPosition, a_state.z + 1.0, normalizedSpeed(nextVelocity));\n}\n";
|
|
2
|
+
export declare const FLOW_UPDATE_FRAGMENT_SHADER = "#version 300 es\nprecision highp float;\nvoid main() {}\n";
|
|
3
|
+
export declare const FLOW_RENDER_VERTEX_SHADER = "#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec4 a_fromState;\nlayout(location = 1) in vec4 a_toState;\n\nuniform sampler2D u_projection;\nuniform sampler2D u_domainMask;\nuniform sampler2D u_valid0;\nuniform sampler2D u_valid1;\nuniform mat4 u_matrix;\nuniform float u_alpha;\nuniform int u_points;\nuniform vec2 u_viewport;\n\nout float v_speed;\nout float v_alpha;\nout float v_edge;\n\nbool validProjection(vec2 projected) {\n return !any(isnan(projected)) && !any(isinf(projected));\n}\n\nbool readMask(sampler2D maskTexture, vec2 uv) {\n ivec2 size = textureSize(maskTexture, 0);\n ivec2 cell = clamp(ivec2(floor(uv * vec2(size))), ivec2(0), size - ivec2(1));\n return texelFetch(maskTexture, cell, 0).r > 0.5;\n}\n\nbool validCell(vec2 uv) {\n return readMask(u_domainMask, uv)\n && readMask(u_valid0, uv)\n && readMask(u_valid1, uv);\n}\n\nbool validCellAt(ivec2 cell) {\n ivec2 size = textureSize(u_domainMask, 0);\n if (any(lessThan(cell, ivec2(0))) || any(greaterThanEqual(cell, size))) return false;\n return texelFetch(u_domainMask, cell, 0).r > 0.5\n && texelFetch(u_valid0, cell, 0).r > 0.5\n && texelFetch(u_valid1, cell, 0).r > 0.5;\n}\n\nbool validSegment(vec2 start, vec2 end) {\n if (any(lessThan(start, vec2(0.0))) || any(greaterThan(start, vec2(1.0)))\n || any(lessThan(end, vec2(0.0))) || any(greaterThan(end, vec2(1.0)))) return false;\n vec2 size = vec2(textureSize(u_domainMask, 0));\n vec2 from = min(start * size, size - vec2(0.000001));\n vec2 to = min(end * size, size - vec2(0.000001));\n ivec2 cell = ivec2(floor(from));\n ivec2 target = ivec2(floor(to));\n vec2 delta = to - from;\n ivec2 cellStep = ivec2(sign(delta));\n vec2 tDelta = vec2(\n delta.x == 0.0 ? 1.0e30 : abs(1.0 / delta.x),\n delta.y == 0.0 ? 1.0e30 : abs(1.0 / delta.y)\n );\n vec2 nextBoundary = vec2(cell) + vec2(\n cellStep.x > 0 ? 1.0 : 0.0,\n cellStep.y > 0 ? 1.0 : 0.0\n );\n vec2 tMax = vec2(\n delta.x == 0.0 ? 1.0e30 : (nextBoundary.x - from.x) / delta.x,\n delta.y == 0.0 ? 1.0e30 : (nextBoundary.y - from.y) / delta.y\n );\n for (int visited = 0; visited < 256; visited += 1) {\n if (!validCellAt(cell)) return false;\n if (all(equal(cell, target))) return true;\n bool advanceX = tMax.x <= tMax.y;\n bool advanceY = tMax.y <= tMax.x;\n if (advanceX) {\n cell.x += cellStep.x;\n tMax.x += tDelta.x;\n }\n if (advanceY) {\n cell.y += cellStep.y;\n tMax.y += tDelta.y;\n }\n }\n return false;\n}\n\nvoid main() {\n bool continuous = a_toState.z > a_fromState.z\n && distance(a_fromState.xy, a_toState.xy) < 0.1;\n vec2 fromProjected = texture(u_projection, clamp(a_fromState.xy, vec2(0.0), vec2(1.0))).rg;\n vec2 toProjected = texture(u_projection, clamp(a_toState.xy, vec2(0.0), vec2(1.0))).rg;\n\n if ((!continuous && u_points == 0)\n || !validSegment(a_fromState.xy, a_toState.xy)\n || !validProjection(fromProjected)\n || !validProjection(toProjected)) {\n gl_Position = vec4(2.0, 2.0, 2.0, 1.0);\n v_speed = 0.0;\n v_alpha = 0.0;\n v_edge = 1.0;\n return;\n }\n\n vec4 fromClip = u_matrix * vec4(fromProjected, 0.0, 1.0);\n vec4 toClip = u_matrix * vec4(toProjected, 0.0, 1.0);\n if (u_points == 1) {\n gl_Position = toClip;\n gl_PointSize = 1.5;\n v_edge = 0.0;\n } else {\n vec2 fromScreen = fromClip.xy / fromClip.w;\n vec2 toScreen = toClip.xy / toClip.w;\n vec2 direction = normalize((toScreen - fromScreen) * u_viewport);\n vec2 normal = vec2(-direction.y, direction.x);\n float side = (gl_VertexID == 0 || gl_VertexID == 2) ? -1.0 : 1.0;\n bool useTo = gl_VertexID >= 2;\n vec4 base = useTo ? toClip : fromClip;\n vec2 screen = base.xy / base.w;\n vec2 offset = normal * side * 2.0 / u_viewport;\n gl_Position = vec4((screen + offset) * base.w, base.z, base.w);\n v_edge = side;\n }\n v_speed = a_toState.w;\n v_alpha = u_alpha;\n}\n";
|
|
4
|
+
export declare const FLOW_RENDER_FRAGMENT_SHADER = "#version 300 es\nprecision highp float;\n\nin float v_speed;\nin float v_alpha;\nin float v_edge;\nout vec4 fragColor;\n\nconst vec3 COLORS[8] = vec3[](\n vec3(0.196, 0.533, 0.741),\n vec3(0.400, 0.761, 0.647),\n vec3(0.671, 0.867, 0.643),\n vec3(0.902, 0.961, 0.596),\n vec3(0.996, 0.878, 0.545),\n vec3(0.992, 0.682, 0.380),\n vec3(0.957, 0.427, 0.263),\n vec3(0.835, 0.243, 0.310)\n);\n\nvec3 velocityColor(float speed) {\n float scaled = clamp(speed, 0.0, 1.0) * 7.0;\n int low = int(floor(scaled));\n int high = min(low + 1, 7);\n return mix(COLORS[low], COLORS[high], fract(scaled));\n}\n\nvoid main() {\n float antialias = 1.0 - smoothstep(0.45, 1.0, abs(v_edge));\n fragColor = vec4(velocityColor(v_speed), v_alpha * antialias);\n}\n";
|