@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.
Files changed (70) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +31 -0
  3. package/dist/index.cjs +76 -0
  4. package/dist/index.d.ts +77 -0
  5. package/dist/index.mjs +102 -0
  6. package/dist/src/api/analysis.d.ts +140 -0
  7. package/dist/src/api/resources.d.ts +530 -0
  8. package/dist/src/api/scenes.d.ts +251 -0
  9. package/dist/src/api/users.d.ts +22 -0
  10. package/dist/src/domain/algorithms/deck-layer-style.d.ts +34 -0
  11. package/dist/src/domain/algorithms/maplibre-style.d.ts +63 -0
  12. package/dist/src/domain/algorithms/raster-color-ramps.d.ts +37 -0
  13. package/dist/src/domain/algorithms/scene-layer-type.d.ts +25 -0
  14. package/dist/src/domain/algorithms/symbol-compile.d.ts +100 -0
  15. package/dist/src/domain/algorithms/symbol-params.d.ts +90 -0
  16. package/dist/src/domain/map/bounds.d.ts +8 -0
  17. package/dist/src/domain/map/constants.d.ts +40 -0
  18. package/dist/src/domain/map/layer-builders.d.ts +32 -0
  19. package/dist/src/domain/map/rendered-layers.d.ts +30 -0
  20. package/dist/src/domain/map/style-ready.d.ts +24 -0
  21. package/dist/src/domain/map/tile-url.d.ts +20 -0
  22. package/dist/src/domain/map/types.d.ts +128 -0
  23. package/dist/src/domain/models/deck-layer-style.d.ts +211 -0
  24. package/dist/src/domain/models/scene-layer-type.d.ts +23 -0
  25. package/dist/src/domain/models/symbol.d.ts +424 -0
  26. package/dist/src/lib/api.d.ts +82 -0
  27. package/dist/src/lib/auth-session.d.ts +22 -0
  28. package/dist/src/lib/raster-color-layer/RasterColorLayer.d.ts +48 -0
  29. package/dist/src/lib/raster-color-layer/TileGrid.d.ts +31 -0
  30. package/dist/src/lib/raster-color-layer/colorRamp.d.ts +18 -0
  31. package/dist/src/lib/raster-color-layer/shaders/raster-color.frag.d.ts +2 -0
  32. package/dist/src/lib/raster-color-layer/shaders/raster-color.vert.d.ts +2 -0
  33. package/dist/src/lib/raster-color-layer/zoomInterpolate.d.ts +2 -0
  34. package/dist/src/sdk/commands.d.ts +113 -0
  35. package/dist/src/sdk/context/keys.d.ts +148 -0
  36. package/dist/src/sdk/context/publishers.d.ts +198 -0
  37. package/dist/src/sdk/context/store.d.ts +78 -0
  38. package/dist/src/sdk/context/when.d.ts +69 -0
  39. package/dist/src/sdk/contributions/dispatch.d.ts +78 -0
  40. package/dist/src/sdk/contributions/panel-id.d.ts +25 -0
  41. package/dist/src/sdk/contributions/registry.d.ts +199 -0
  42. package/dist/src/sdk/contributions/symbol-renderers.d.ts +107 -0
  43. package/dist/src/sdk/contributions/types.d.ts +288 -0
  44. package/dist/src/sdk/contributions/views-bridge.d.ts +92 -0
  45. package/dist/src/sdk/define-plugin.d.ts +17 -0
  46. package/dist/src/sdk/facets/storage.d.ts +152 -0
  47. package/dist/src/sdk/facets/types.d.ts +370 -0
  48. package/dist/src/sdk/host-api.d.ts +256 -0
  49. package/dist/src/sdk/map-api.d.ts +288 -0
  50. package/dist/src/sdk/panels.d.ts +130 -0
  51. package/dist/src/sdk/plugin.d.ts +145 -0
  52. package/dist/src/sdk/rail-tools.d.ts +58 -0
  53. package/dist/src/sdk/scopes.d.ts +158 -0
  54. package/dist/src/systems/renderer/DeckSceneRenderer.d.ts +26 -0
  55. package/dist/src/systems/renderer/FlowFieldLayer.d.ts +68 -0
  56. package/dist/src/systems/renderer/LayerRendererRegistry.d.ts +132 -0
  57. package/dist/src/systems/renderer/authenticated-deck-load.d.ts +4 -0
  58. package/dist/src/systems/renderer/deck/deck-layer-builders.d.ts +29 -0
  59. package/dist/src/systems/renderer/deck/deck-layer-helpers.d.ts +21 -0
  60. package/dist/src/systems/renderer/deck/mvt-point-aggregation-layer.d.ts +107 -0
  61. package/dist/src/systems/renderer/flow-field/flow-field-resources.d.ts +84 -0
  62. package/dist/src/systems/renderer/flow-field/flow-field-shaders.d.ts +4 -0
  63. package/dist/src/systems/renderer/symbol/SpriteRegistry.d.ts +34 -0
  64. package/dist/src/systems/renderer/symbol/entries.d.ts +67 -0
  65. package/dist/src/types/analysis.d.ts +205 -0
  66. package/dist/src/types/common.d.ts +17 -0
  67. package/dist/src/types/extensions.d.ts +560 -0
  68. package/dist/src/types/gis.d.ts +242 -0
  69. package/dist/src/types/market-social.d.ts +378 -0
  70. 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";