@ngis/plugin-sdk 0.4.0 → 0.4.1
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/dist/index.cjs +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.mjs +1 -1
- package/dist/src/api/analysis.d.ts +38 -1
- package/dist/src/api/resources.d.ts +174 -10
- package/dist/src/api/scenes.d.ts +103 -4
- package/dist/src/domain/algorithms/flow-field-style.d.ts +68 -0
- package/dist/src/domain/algorithms/scene-layer-type.d.ts +12 -0
- package/dist/src/domain/map/rendered-layers.d.ts +1 -0
- package/dist/src/domain/map/types.d.ts +2 -0
- package/dist/src/domain/models/crs.d.ts +187 -0
- package/dist/src/domain/models/dataset-kind.d.ts +256 -0
- package/dist/src/domain/models/flow-field-style.d.ts +32 -0
- package/dist/src/domain/models/scene-layer-type.d.ts +28 -3
- package/dist/src/sdk/context/publishers.d.ts +37 -0
- package/dist/src/sdk/facets/types.d.ts +73 -2
- package/dist/src/sdk/map-api.d.ts +4 -1
- package/dist/src/sdk/plugin.d.ts +6 -0
- package/dist/src/sdk/scopes.d.ts +1 -0
- package/dist/src/systems/renderer/FlowFieldLayer.d.ts +9 -6
- package/dist/src/systems/renderer/flow-field/flow-field-resources.d.ts +3 -0
- package/dist/src/systems/renderer/flow-field/flow-field-shaders.d.ts +3 -3
- package/dist/src/types/analysis.d.ts +111 -7
- package/dist/src/types/extensions.d.ts +43 -0
- package/dist/src/types/gis.d.ts +1 -1
- package/package.json +3 -25
- package/LICENSE +0 -13
- package/README.md +0 -31
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { AnalysisToolSchema } from "../../api/analysis";
|
|
34
34
|
import type { SceneType } from "../../api/scenes";
|
|
35
|
+
import type { NgisCrsRef } from "../../domain/models/crs";
|
|
35
36
|
import type { NgisExecutionValidation, NgisToolUiSchema } from "../../types/analysis";
|
|
37
|
+
import type { NgisResolvedToolDependency } from "../../types/extensions";
|
|
36
38
|
import type { NgisBounds, NgisUnsubscribe } from "../map-api";
|
|
37
39
|
export interface NgisSceneInfo {
|
|
38
40
|
id: string;
|
|
@@ -59,6 +61,18 @@ export interface NgisSceneFacet {
|
|
|
59
61
|
* `"table"` would need `NgisFile.fileType`, i.e. a second
|
|
60
62
|
* `GET /api/files/{fileId}` per resource — an N+1 fan-out for a distinction no
|
|
61
63
|
* v1 consumer needs.
|
|
64
|
+
*
|
|
65
|
+
* **SURVIVES Stage 29 UNCHANGED (F29.1 row 3).** It is the SDK's v1
|
|
66
|
+
* plugin-facing classification, frozen by XD7/F13.6 and enforced by two live
|
|
67
|
+
* gates, and Stage 29 registers **no table dataset model** — so nothing about it
|
|
68
|
+
* moves (F29.2). It is a *projection* of `NgisDatasetKind` onto the coarser
|
|
69
|
+
* vocabulary v1 plugins compile against, never a second spelling of it: its
|
|
70
|
+
* membership is closed because a plugin's `when` expression is, and
|
|
71
|
+
* `NgisDatasetKind` is open because a kind is registered data. Widening this one
|
|
72
|
+
* to match would break every shipped plugin fixture.
|
|
73
|
+
*
|
|
74
|
+
* *(`GENERIC` no longer reaches the classifier — F29.3 removed it from the wire —
|
|
75
|
+
* but the "everything else" arm is unchanged and still catches it.)*
|
|
62
76
|
*/
|
|
63
77
|
export type NgisResourceType = "vector" | "raster" | "table" | "file";
|
|
64
78
|
/**
|
|
@@ -89,6 +103,13 @@ export type NgisResourceType = "vector" | "raster" | "table" | "file";
|
|
|
89
103
|
* against a builtin node's `"Raster"` and `400`s at workflow save. Y6 applies
|
|
90
104
|
* that column at, and **only** at, the `ngis-workflow` boundary. Nowhere else
|
|
91
105
|
* in either repo is Pascal correct.
|
|
106
|
+
*
|
|
107
|
+
* **SURVIVES Stage 29 UNCHANGED (F29.1 row 4).** It is a **tool port** data type
|
|
108
|
+
* (F14.1), not a dataset kind. The two are related by the compatibility matrix
|
|
109
|
+
* and **never by equality**: `table` and `pointcloud` are legal port types that
|
|
110
|
+
* no registered kind emits, and a registered kind the client has never seen binds
|
|
111
|
+
* to a port through the matrix rather than by name. Renaming or merging them
|
|
112
|
+
* would assert an identity that does not hold.
|
|
92
113
|
*/
|
|
93
114
|
export type NgisDataType = "raster" | "vector" | "table" | "pointcloud" | "raster3d" | "file";
|
|
94
115
|
export declare const NGIS_DATA_TYPES: readonly ["raster", "vector", "table", "pointcloud", "raster3d", "file"];
|
|
@@ -130,6 +151,25 @@ export interface NgisResourceSummary {
|
|
|
130
151
|
geometryType: string | null;
|
|
131
152
|
layerCount: number;
|
|
132
153
|
rendered: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* **Stage 25 (F25.1 / F25.9, `S25-V216`).** The dataset's one CRS, or `null`
|
|
156
|
+
* when the server has none for it (`NEEDS_CRS`).
|
|
157
|
+
*
|
|
158
|
+
* It is on the **summary**, not only the detail, and that placement is the
|
|
159
|
+
* point of the row. `ResourceSelect` lists datasets from `resources.list()`
|
|
160
|
+
* and must draw each row's projection state at pick time (F25.5); resolving a
|
|
161
|
+
* ref through `resources.get()` per row is the N+1 XD7 already refused for
|
|
162
|
+
* `resources.types`. Without this field the picker could draw only the
|
|
163
|
+
* *absent* state — which is what S25-22 shipped, with the limit recorded
|
|
164
|
+
* rather than papered over — because a dataset that *has* a projection had
|
|
165
|
+
* nothing to draw a chip from and inventing one is what G-CRS forbids.
|
|
166
|
+
*
|
|
167
|
+
* `null` is a value and not a gap: it is the server saying `NEEDS_CRS`. There
|
|
168
|
+
* is no `undefined` arm — the facet always answers, and a dataset it could not
|
|
169
|
+
* read at all reports `datasetStatus: "N/A"` with `crs: null`, which is the
|
|
170
|
+
* same honest "we cannot say what this is in".
|
|
171
|
+
*/
|
|
172
|
+
crs: NgisCrsRef | null;
|
|
133
173
|
/**
|
|
134
174
|
* Scene layers that render this resource, when the host can supply them.
|
|
135
175
|
*
|
|
@@ -160,7 +200,6 @@ export interface NgisResourceField {
|
|
|
160
200
|
type: string;
|
|
161
201
|
}
|
|
162
202
|
export interface NgisResourceDetail extends NgisResourceSummary {
|
|
163
|
-
srid: number | null;
|
|
164
203
|
/** Vector only; `null` otherwise. */
|
|
165
204
|
featureCount: number | null;
|
|
166
205
|
/** Vector only; `[]` otherwise. */
|
|
@@ -244,7 +283,21 @@ export interface NgisModelValidateRequest {
|
|
|
244
283
|
expectedSceneGeneration?: number;
|
|
245
284
|
outputMode?: "ADD_LAYER" | "UPDATE_RESOURCE";
|
|
246
285
|
targetResourceId?: string;
|
|
247
|
-
|
|
286
|
+
/**
|
|
287
|
+
* **Stage 25 (F25.4, and the `S25-V124` ruling that AMENDS F25.12).**
|
|
288
|
+
*
|
|
289
|
+
* Replaces `targetSrid?: number`. It is an {@link NgisCrsRef} **key** — the
|
|
290
|
+
* string `"EPSG:32650"` or `"custom:<ulid>"` — and never an integer, because
|
|
291
|
+
* an integer cannot name a custom CRS at all and because a bare number is the
|
|
292
|
+
* silent-default spelling G-CRS refuses.
|
|
293
|
+
*
|
|
294
|
+
* `targetSrid` was typed, forwarded to `validate-execution` and set by no
|
|
295
|
+
* component: dead plumbing. This member is the live one — `run-data.ts` fills
|
|
296
|
+
* it from the form's `ngis:outputCrs` parameter and the facade adapter puts it
|
|
297
|
+
* on the wire as `outputCrs` (the REST leg is camelCase here; the proto
|
|
298
|
+
* spelling is `output_crs`).
|
|
299
|
+
*/
|
|
300
|
+
outputCrs?: string;
|
|
248
301
|
}
|
|
249
302
|
export interface NgisModelSearchRequest {
|
|
250
303
|
query?: string;
|
|
@@ -300,6 +353,12 @@ export interface NgisModelsFacet {
|
|
|
300
353
|
/** The one normalized UI schema (F14.2). `null` when the tool or its schema
|
|
301
354
|
* is absent. Scope `models:read`. */
|
|
302
355
|
uiSchema(toolId: string): Promise<NgisToolUiSchema | null>;
|
|
356
|
+
/**
|
|
357
|
+
* Resolves a declared `manifest.toolDependencies[].alias` for this plugin and
|
|
358
|
+
* viewer. The unavailable arm is a normal, renderable result; it never leaks
|
|
359
|
+
* implementation UUIDs into plugin manifests. Scope `models:read`.
|
|
360
|
+
*/
|
|
361
|
+
dependency(alias: string): Promise<NgisResolvedToolDependency>;
|
|
303
362
|
/** REST projection of `ValidateToolExecution`. Returns the response DTO
|
|
304
363
|
* **verbatim**. Scope `models:read`, **not** `models:run` — it runs nothing,
|
|
305
364
|
* and returns only registry facts plus dataset metadata the caller can
|
|
@@ -324,6 +383,18 @@ export interface NgisTaskRunRequest {
|
|
|
324
383
|
expectedSceneGeneration?: number;
|
|
325
384
|
outputMode?: "ADD_LAYER" | "UPDATE_RESOURCE";
|
|
326
385
|
targetResourceId?: string;
|
|
386
|
+
/**
|
|
387
|
+
* **Stage 25 (F25.4).** The output CRS this run computes in and stores its
|
|
388
|
+
* result as — an {@link NgisCrsRef} key, never an integer. Absent means
|
|
389
|
+
* "compute in the primary input's own CRS", which is a real choice and not a
|
|
390
|
+
* default: the server never substitutes one (G-CRS).
|
|
391
|
+
*
|
|
392
|
+
* This is the member that makes exit criterion 6 true. `submitRun` fills it,
|
|
393
|
+
* the tasks facet spreads the request through to the DIRECT source, and
|
|
394
|
+
* `createGisTasksSource` hands it to `startTrackedAnalysisTask`, which puts
|
|
395
|
+
* `outputCrs` in the `POST /api/analysis/tasks` body.
|
|
396
|
+
*/
|
|
397
|
+
outputCrs?: string;
|
|
327
398
|
}
|
|
328
399
|
export interface NgisTaskHandle {
|
|
329
400
|
/** DIRECT: the analysis task id. WORKFLOW: the workflow instance id. */
|
|
@@ -73,7 +73,7 @@ import type { WorkspacePanelRegistry } from "./panels";
|
|
|
73
73
|
* semver compare, **not** by the unknown-capability check: `contributes` and
|
|
74
74
|
* `permissions` are top-level manifest siblings, and a 0.3 runtime inspects
|
|
75
75
|
* only `manifest.capabilities`. */
|
|
76
|
-
export declare const NGIS_MAP_API_VERSION = "0.4.
|
|
76
|
+
export declare const NGIS_MAP_API_VERSION = "0.4.1";
|
|
77
77
|
/** A scene layer as the SDK exposes it — a stable projection of the store's
|
|
78
78
|
* `NgisSceneLayer`, so plugins never depend on the store's internal shape. */
|
|
79
79
|
export interface NgisLayerSummary {
|
|
@@ -262,6 +262,9 @@ export interface NgisMapAPIBindings {
|
|
|
262
262
|
/** Raw-map getter for `getMap()`. Usually omitted at construction and wired
|
|
263
263
|
* later via `NgisMapAPIHandle.bindRawMap` (the post-C5 hookup). */
|
|
264
264
|
getRawMap?(): MapLibreMap | null;
|
|
265
|
+
/** Host hook run before direct raw-map camera commands. Keeps host-owned
|
|
266
|
+
* temporary camera controllers from racing this SDK escape hatch. */
|
|
267
|
+
beforeCameraCommand?(map: MapLibreMap): void;
|
|
265
268
|
}
|
|
266
269
|
/**
|
|
267
270
|
* The factory's return value: the public `api` plus the host-side "driver"
|
package/dist/src/sdk/plugin.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { NgisContributes, NgisPermissionScope } from "./contributions/types
|
|
|
26
26
|
import type { NgisSymbolRendererModule } from "./contributions/symbol-renderers";
|
|
27
27
|
import type { PanelRegistration } from "./panels";
|
|
28
28
|
import type { GisDynamicToolDef } from "./rail-tools";
|
|
29
|
+
import type { NgisToolDependencyDeclaration } from "../types/extensions";
|
|
29
30
|
/**
|
|
30
31
|
* **F13.1 / ED19 (Stage 13, X4b) — the `NgisPluginHost` widening.**
|
|
31
32
|
*
|
|
@@ -106,6 +107,11 @@ export interface NgisPluginManifest {
|
|
|
106
107
|
* here and is never prompted.
|
|
107
108
|
*/
|
|
108
109
|
permissions?: readonly NgisPermissionScope[];
|
|
110
|
+
/**
|
|
111
|
+
* Semantic tool requirements. Bundle code obtains the selected runtime tool
|
|
112
|
+
* through `host.models.dependency(alias)`; it must not contain a tool UUID.
|
|
113
|
+
*/
|
|
114
|
+
toolDependencies?: readonly NgisToolDependencyDeclaration[];
|
|
109
115
|
/** **Reserved, never read this stage** (D3): the future third-party loader's
|
|
110
116
|
* ESM-bundle path. First-party plugins are statically imported instead. */
|
|
111
117
|
entry?: string;
|
package/dist/src/sdk/scopes.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export declare const NGIS_SCOPE_MEMBERS: {
|
|
|
62
62
|
readonly "models.get": "models:read";
|
|
63
63
|
readonly "models.schema": "models:read";
|
|
64
64
|
readonly "models.uiSchema": "models:read";
|
|
65
|
+
readonly "models.dependency": "models:read";
|
|
65
66
|
readonly "models.validate": "models:read";
|
|
66
67
|
readonly "models.search": "models:read";
|
|
67
68
|
readonly "tasks.run": "models:run";
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { CustomLayerInterface, CustomRenderMethodInput, Map as MapLibreMap } from "maplibre-gl";
|
|
2
2
|
import type { SceneLayerStyle } from "../../domain/map/types";
|
|
3
|
+
import type { FlowFieldStyle } from "../../domain/models/flow-field-style";
|
|
3
4
|
import type { AuthenticatedFetch } from "./authenticated-deck-load";
|
|
4
|
-
|
|
5
|
+
type FlowFieldStyleOptions = Pick<FlowFieldStyle, "opacity" | "colorRamp" | "speedRange" | "speedFactor" | "particleCount" | "lineWidth" | "trailLength" | "frameDurationMs" | "paused" | "maxParticleAge" | "dropRate" | "dropRateBump">;
|
|
6
|
+
export type FlowFieldLayerOptions = Partial<FlowFieldStyleOptions> & {
|
|
5
7
|
id: string;
|
|
6
8
|
flowFieldId: string;
|
|
7
|
-
opacity: number;
|
|
8
|
-
speedFactor: number;
|
|
9
|
-
particleCount: number;
|
|
10
|
-
paused: boolean;
|
|
11
9
|
fetchWithAuth: AuthenticatedFetch;
|
|
12
10
|
triggerRepaint: () => void;
|
|
11
|
+
onReady?: () => void;
|
|
13
12
|
onError?: (error: Error) => void;
|
|
14
13
|
};
|
|
15
14
|
/**
|
|
@@ -38,6 +37,7 @@ export declare class FlowFieldLayer implements CustomLayerInterface {
|
|
|
38
37
|
private validTextures;
|
|
39
38
|
private domainMaskTexture;
|
|
40
39
|
private projectionTexture;
|
|
40
|
+
private colorRampTexture;
|
|
41
41
|
private simulationBuffers;
|
|
42
42
|
private simulationVaos;
|
|
43
43
|
private transformFeedbacks;
|
|
@@ -46,13 +46,15 @@ export declare class FlowFieldLayer implements CustomLayerInterface {
|
|
|
46
46
|
private stateIndex;
|
|
47
47
|
private trailHead;
|
|
48
48
|
private trailCount;
|
|
49
|
-
private
|
|
49
|
+
private frameElapsedMs;
|
|
50
|
+
private lastRenderTimeMs;
|
|
50
51
|
private particleCount;
|
|
51
52
|
constructor(options: FlowFieldLayerOptions);
|
|
52
53
|
onAdd(map: MapLibreMap, gl: WebGLRenderingContext | WebGL2RenderingContext): void;
|
|
53
54
|
private load;
|
|
54
55
|
private createGpuResources;
|
|
55
56
|
private createParticleResources;
|
|
57
|
+
private getFrameState;
|
|
56
58
|
private runSimulation;
|
|
57
59
|
private bindTexture;
|
|
58
60
|
private bindRenderBuffers;
|
|
@@ -66,3 +68,4 @@ export declare class FlowFieldLayer implements CustomLayerInterface {
|
|
|
66
68
|
private destroyParticleResources;
|
|
67
69
|
private releaseGpuResources;
|
|
68
70
|
}
|
|
71
|
+
export {};
|
|
@@ -71,6 +71,9 @@ export interface DecodedFlowFieldAssets {
|
|
|
71
71
|
domainMask: Uint8Array;
|
|
72
72
|
speedRange: [number, number];
|
|
73
73
|
}
|
|
74
|
+
type FlowFieldSpeedRangeListener = (range: [number, number]) => void;
|
|
75
|
+
export declare function getResolvedFlowFieldSpeedRange(fileId: string): [number, number] | null;
|
|
76
|
+
export declare function subscribeToResolvedFlowFieldSpeedRange(fileId: string, listener: FlowFieldSpeedRangeListener): () => void;
|
|
74
77
|
interface RgbaImage {
|
|
75
78
|
width: number;
|
|
76
79
|
height: number;
|
|
@@ -1,4 +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) ?
|
|
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;\nuniform float u_deltaSeconds;\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) ? length(velocity) : 0.0);\n}\n\nvoid main() {\n vec2 velocity = readVelocity(a_state.xy);\n float speed = isDefinedVelocity(velocity) ? normalizedSpeed(velocity) : 0.0;\n // 60 Hz preserves the fourth-round speed factor semantics while real elapsed\n // time makes 60 Hz and 144 Hz displays advance identically.\n vec2 nextPosition = a_state.xy + velocity * u_speedFactor * (u_deltaSeconds * 60.0) / 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, length(nextVelocity));\n}\n";
|
|
2
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.
|
|
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;\
|
|
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;\nuniform float u_lineWidth;\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 = max(1.0, u_lineWidth);\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 * u_lineWidth / 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;\nuniform sampler2D u_colorRamp;\nuniform vec2 u_speedRange;\n\nvoid main() {\n float antialias = 1.0 - smoothstep(0.45, 1.0, abs(v_edge));\n float normalizedSpeed = clamp(\n (v_speed - u_speedRange.x) / max(u_speedRange.y - u_speedRange.x, 1.0e-6),\n 0.0,\n 1.0\n );\n vec4 rampColor = texture(u_colorRamp, vec2(normalizedSpeed, 0.5));\n float alpha = rampColor.a * v_alpha * antialias;\n // MapLibre's custom-layer canvas is premultiplied. Keeping this output and\n // the render pass in the same convention prevents repeated trail fragments\n // from being composited as straight-alpha colours.\n fragColor = vec4(rampColor.rgb * alpha, alpha);\n}\n";
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* `src/sdk/facets/types.ts`; F14.2 fixes the schema family's home here.
|
|
27
27
|
*/
|
|
28
28
|
import type { NgisDataType } from "../sdk/facets/types";
|
|
29
|
+
import type { NgisValidationIssue } from "../domain/models/crs";
|
|
29
30
|
export type WorkSurface = "workflow" | "insight" | "code";
|
|
30
31
|
export type RightDockMode = "none" | "ai";
|
|
31
32
|
export type MapFocusMode = "split" | "map" | "work";
|
|
@@ -35,15 +36,31 @@ export type NgisParamRole = "input" | "option" | "output";
|
|
|
35
36
|
/**
|
|
36
37
|
* Total, frozen. **There is NO fallback widget:** an unknown key is a
|
|
37
38
|
* render-time error surfaced in place, never a silently-degraded text input.
|
|
38
|
-
* A Y3 widget registry missing any of the
|
|
39
|
+
* A Y3 widget registry missing any of the thirteen is a defect.
|
|
39
40
|
*
|
|
40
41
|
* `field-select`, `extent` and `textarea` are **never emitted by YB1 in v1** —
|
|
41
42
|
* no builtin schema expresses a field, a bbox or a long-text parameter. They
|
|
42
43
|
* are in the union because the registry must be total over it, and because an
|
|
43
44
|
* author's `ParamForm` `widgets` override and a recipe's promoted inputs can
|
|
44
45
|
* select them.
|
|
46
|
+
*
|
|
47
|
+
* ## `"crs"` is the thirteenth, and it **AMENDS F14.2** (Stage 25, F25.3)
|
|
48
|
+
*
|
|
49
|
+
* F14.2 froze twelve keys and called the union closed. `"crs"` is added by an
|
|
50
|
+
* explicit amendment, not by growth: it is the widget for `ngis:outputCrs`, the
|
|
51
|
+
* one **platform** parameter every tool's UI schema carries (F25.4), and there
|
|
52
|
+
* was no key in the twelve that could carry a coordinate reference system
|
|
53
|
+
* without lying about it — `select` would have needed a closed choice list over
|
|
54
|
+
* the EPSG registry, and `text` would have accepted anything.
|
|
55
|
+
*
|
|
56
|
+
* Because {@link NgisWidgetRegistry} is a *total* mapped type, an external
|
|
57
|
+
* implementer that does not add the key **fails to compile. That is the intended
|
|
58
|
+
* signal**, recorded here as an amendment rather than discovered as an accident.
|
|
59
|
+
* S25-02 enumerated every implementer in the repo — there are none outside
|
|
60
|
+
* `src/` — and the union has **nine** in-repo gate sites, not one; F25.3 lists
|
|
61
|
+
* them.
|
|
45
62
|
*/
|
|
46
|
-
export type NgisWidgetKey = "resource-select" | "resource-multi" | "select" | "multi-select" | "switch" | "number" | "integer" | "text" | "textarea" | "output-name" | "field-select" | "extent";
|
|
63
|
+
export type NgisWidgetKey = "resource-select" | "resource-multi" | "select" | "multi-select" | "switch" | "number" | "integer" | "text" | "textarea" | "output-name" | "field-select" | "extent" | "crs";
|
|
47
64
|
export interface NgisUiSchemaChoice {
|
|
48
65
|
value: string;
|
|
49
66
|
label: string;
|
|
@@ -109,7 +126,26 @@ export interface NgisToolUiSchema {
|
|
|
109
126
|
* rule `recipeVersion` carries in F14.6).
|
|
110
127
|
*/
|
|
111
128
|
export declare const NGIS_UI_SCHEMA_VERSION: 1;
|
|
112
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* `PASS | ADAPTABLE | UNKNOWN | FAIL`, passed through verbatim.
|
|
131
|
+
*
|
|
132
|
+
* **Stage 29 (F29.8) — `verdict` and issue code are INDEPENDENT axes.**
|
|
133
|
+
* `"ADAPTABLE"` is the same token for Stage 26's **CRS** adaptation and Stage
|
|
134
|
+
* 29's **kind** adaptation; `verdict` carries *what*, `issues[]` carries *why*,
|
|
135
|
+
* and **only the issue code distinguishes them**. A test that asserts nothing but
|
|
136
|
+
* `verdict === "ADAPTABLE"` passes on the CRS case (`binding-adaptable.json`
|
|
137
|
+
* `B02`) and proves Stage 26's code path rather than this stage's — which is why
|
|
138
|
+
* the predicate is `isKindAdaptableIssue` in
|
|
139
|
+
* `src/domain/models/dataset-kind.ts`, over the code and never over the verdict.
|
|
140
|
+
*
|
|
141
|
+
* The kind code is `KIND_ADAPTABLE`, severity `advisory`, `port` set, and **`fix`
|
|
142
|
+
* absent** — `NgisValidationIssueFix` means *"set this parameter to this value"*
|
|
143
|
+
* and there is no parameter that turns a kind adaptation on or off, so a client
|
|
144
|
+
* rendering a one-click fix here has invented an affordance the server cannot
|
|
145
|
+
* honour. (CRS differs: `ngis:outputCrs` is a real parameter.) `NgisCrsIssueCode`
|
|
146
|
+
* is already open, so the code needs no union edit. Rendering it is `S29-33`'s
|
|
147
|
+
* (W3), and the client never re-classifies a server verdict or severity (`G-U`).
|
|
148
|
+
*/
|
|
113
149
|
export type NgisBindingVerdict = "PASS" | "ADAPTABLE" | "UNKNOWN" | "FAIL";
|
|
114
150
|
/** `READY | NEEDS_CONFIGURATION | NOT_EXECUTABLE`, passed through verbatim. */
|
|
115
151
|
export type NgisExecutionVerdict = "READY" | "NEEDS_CONFIGURATION" | "NOT_EXECUTABLE";
|
|
@@ -130,9 +166,30 @@ export interface NgisBindingValidationEntry {
|
|
|
130
166
|
verdict: NgisBindingVerdict;
|
|
131
167
|
reason: string;
|
|
132
168
|
geometry_type: string;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
169
|
+
/**
|
|
170
|
+
* **Stage 25 (F25.1):** the numeric triple `srid` / `native_srid` /
|
|
171
|
+
* `working_srid` is gone **from this interface**. It was one of the three SRID
|
|
172
|
+
* vocabularies that never met, and the only one that could tell a caller
|
|
173
|
+
* "unknown" — it spelled it `0`, which every consumer read as a CRS. A
|
|
174
|
+
* binding's CRS is the dataset's `crs: NgisCrsRef | null`.
|
|
175
|
+
*
|
|
176
|
+
* **CORRECTION OF RECORD (`S25-V255`, S25-32, W3).** This comment used to say
|
|
177
|
+
* the triple was gone "from this entry **and from the wire**". The second half
|
|
178
|
+
* was false when it was written and is still false: `validation_rest.py:72-74`
|
|
179
|
+
* and `binding_validation.py:183-185` send all three fields on both the REST
|
|
180
|
+
* and the gRPC leg to this day. The W3 kickoff marked them
|
|
181
|
+
* `[deprecated = true]` in `geoanalysis.proto`; removing them needs
|
|
182
|
+
* `binding_validation.py`'s `x-required-srid` path to lose its only CRS
|
|
183
|
+
* source, which is not a W3 change.
|
|
184
|
+
*
|
|
185
|
+
* The distinction matters because this file is **quoted verbatim** into the
|
|
186
|
+
* published contract reference at `/market/devkit/docs`: a wrong sentence here
|
|
187
|
+
* is a wrong sentence in the SDK documentation a plugin author reads. What is
|
|
188
|
+
* true is that **the client drops them on the floor** — nothing in this repo
|
|
189
|
+
* declares or reads them, so no frontend code path can be misled by a `0`.
|
|
190
|
+
* That is a client guarantee, not a wire fact, and the two are only the same
|
|
191
|
+
* sentence when the server has actually stopped sending.
|
|
192
|
+
*/
|
|
136
193
|
access_mode: string;
|
|
137
194
|
executable: boolean;
|
|
138
195
|
}
|
|
@@ -145,6 +202,19 @@ export interface NgisBindingValidation {
|
|
|
145
202
|
unresolved_ports: readonly string[];
|
|
146
203
|
errors: readonly string[];
|
|
147
204
|
warnings: readonly string[];
|
|
205
|
+
/**
|
|
206
|
+
* **Stage 25 (F25.6), additive.** Typed per-port issues. The F14.9 string
|
|
207
|
+
* arrays above are unchanged and keep their meaning; this channel carries the
|
|
208
|
+
* `param`/`port` routing and the one-click `fix` a string cannot.
|
|
209
|
+
*
|
|
210
|
+
* This half and {@link NgisExecutionValidation.issues} are **one channel read
|
|
211
|
+
* at two levels**, exactly as the F14.9 string arrays are: a blocking entry
|
|
212
|
+
* here blocks the submit too. Reading only the other level was `S29-V234`.
|
|
213
|
+
*
|
|
214
|
+
* Optional because a server that has not shipped F25.6 omits it, and an
|
|
215
|
+
* absent `issues` is "the server said nothing", never "the server said fine".
|
|
216
|
+
*/
|
|
217
|
+
issues?: readonly NgisValidationIssue[];
|
|
148
218
|
}
|
|
149
219
|
/** `ValidateToolExecutionResponse`, field-for-field, with `normalized_params_json`
|
|
150
220
|
* replaced by the decoded `normalized_params` object (the one divergence). */
|
|
@@ -161,6 +231,15 @@ export interface NgisExecutionValidation {
|
|
|
161
231
|
execution_mode: NgisExecutionMode;
|
|
162
232
|
/** Decoded. The proto's `normalized_params_json` string is never sent to a browser. */
|
|
163
233
|
normalized_params: Readonly<Record<string, string>>;
|
|
234
|
+
/**
|
|
235
|
+
* **Stage 25 (F25.6), additive.** See {@link NgisBindingValidation.issues}.
|
|
236
|
+
* `isSubmitBlocked` counts a `severity: "blocking"` entry here **or on that
|
|
237
|
+
* half**, in addition to F14.9's existing rule; the client never suppresses,
|
|
238
|
+
* downgrades or re-classifies a server issue (G-U) — and declining to gate on
|
|
239
|
+
* a severity the server declared is a suppression, which is why reading only
|
|
240
|
+
* this level was a defect (`S29-V234`) rather than a narrower reading.
|
|
241
|
+
*/
|
|
242
|
+
issues?: readonly NgisValidationIssue[];
|
|
164
243
|
}
|
|
165
244
|
/** `POST /api/analysis/tools/{tool_id}/validate-execution` body. */
|
|
166
245
|
export interface NgisValidateExecutionBody {
|
|
@@ -172,7 +251,32 @@ export interface NgisValidateExecutionBody {
|
|
|
172
251
|
expected_scene_generation?: number;
|
|
173
252
|
output_mode?: NgisOutputMode;
|
|
174
253
|
target_resource_id?: string | null;
|
|
175
|
-
|
|
254
|
+
/**
|
|
255
|
+
* **Stage 25 (F25.12):** `target_srid` is removed. It was typed, forwarded to
|
|
256
|
+
* validation, and **set by no component** — dead plumbing, and its presence
|
|
257
|
+
* made a numeric SRID look like a supported way to ask for a projection. The
|
|
258
|
+
* replacement is F25.4's `outputCrs?: string` (a `NgisCrsRef` key, not an
|
|
259
|
+
* integer), added on the submit and validate bodies by the W2 session that
|
|
260
|
+
* owns `RunButton` / `run-data.ts`.
|
|
261
|
+
*
|
|
262
|
+
* **Added here by S25-23 (W2), which is that session.**
|
|
263
|
+
*/
|
|
264
|
+
/**
|
|
265
|
+
* **Stage 25 (F25.4).** The `ngis:outputCrs` platform parameter, as an
|
|
266
|
+
* `NgisCrsRef` **key** (`"EPSG:32650"`, `"custom:<ulid>"`).
|
|
267
|
+
*
|
|
268
|
+
* **This one field is camelCase in an otherwise snake_case body, and that is
|
|
269
|
+
* the server's spelling, not a slip.** The W2 dispatch is explicit: the proto
|
|
270
|
+
* carries `output_crs` on `SubmitTaskRequest` / the validate messages, and the
|
|
271
|
+
* REST legs take `outputCrs`. Re-spelling it here to match the neighbours
|
|
272
|
+
* would put a field on the wire that `ngis-geoanalysis` does not read — which
|
|
273
|
+
* is exactly the failure `target_srid` had, arriving forever and meaning
|
|
274
|
+
* nothing.
|
|
275
|
+
*
|
|
276
|
+
* Absent means "compute in the primary input's own CRS" (R4). The client never
|
|
277
|
+
* substitutes a value for an absent one (G-CRS).
|
|
278
|
+
*/
|
|
279
|
+
outputCrs?: string;
|
|
176
280
|
}
|
|
177
281
|
/** `POST /api/analysis/tools/{tool_id}/validate-bindings` body. */
|
|
178
282
|
export interface NgisValidateBindingsBody {
|
|
@@ -184,7 +184,44 @@ export interface PackageManifestDto {
|
|
|
184
184
|
homepage?: string;
|
|
185
185
|
publisher?: string;
|
|
186
186
|
changelog?: string;
|
|
187
|
+
/**
|
|
188
|
+
* Semantic tool requirements declared by a UI plugin. The alias is local to
|
|
189
|
+
* this manifest; only the host receives the resolved, opaque implementation
|
|
190
|
+
* id at runtime. A bundle must therefore bind a tool through its alias, never
|
|
191
|
+
* through a UUID copied from the tool catalogue.
|
|
192
|
+
*/
|
|
193
|
+
toolDependencies?: NgisToolDependencyDeclaration[];
|
|
194
|
+
}
|
|
195
|
+
/** Stable, publisher-owned identity for a tool dependency. */
|
|
196
|
+
export interface NgisToolDependencyReference {
|
|
197
|
+
namespace: string;
|
|
198
|
+
toolKey: string;
|
|
199
|
+
contractRange: string;
|
|
200
|
+
}
|
|
201
|
+
/** Optional structural contract that narrows a semantic dependency further. */
|
|
202
|
+
export interface NgisToolDependencyRequiredContract {
|
|
203
|
+
inputs?: Record<string, string>;
|
|
204
|
+
outputs?: Record<string, string>;
|
|
205
|
+
parameters?: string[];
|
|
187
206
|
}
|
|
207
|
+
export interface NgisToolDependencyDeclaration {
|
|
208
|
+
/** Locally unique manifest key, used by plugin code at runtime. */
|
|
209
|
+
alias: string;
|
|
210
|
+
ref: NgisToolDependencyReference;
|
|
211
|
+
requiredContract?: NgisToolDependencyRequiredContract;
|
|
212
|
+
}
|
|
213
|
+
/** Resolver outcomes are intentionally specific enough to render an honest UI. */
|
|
214
|
+
export type NgisToolDependencyUnavailableReason = "tool_missing" | "tool_disabled" | "tool_forbidden" | "tool_version_unavailable" | "tool_contract_incompatible" | "dependency_unavailable";
|
|
215
|
+
export type NgisResolvedToolDependency = {
|
|
216
|
+
available: true;
|
|
217
|
+
toolId: string;
|
|
218
|
+
toolRevision: number;
|
|
219
|
+
contractVersion: string;
|
|
220
|
+
schemaFingerprint: string;
|
|
221
|
+
} | {
|
|
222
|
+
available: false;
|
|
223
|
+
reason: NgisToolDependencyUnavailableReason;
|
|
224
|
+
};
|
|
188
225
|
export interface ExtensionInstallDto {
|
|
189
226
|
extId: string;
|
|
190
227
|
enabled: boolean;
|
|
@@ -267,6 +304,12 @@ export interface BootExtensionEntryDto {
|
|
|
267
304
|
manifest: PackageManifestDto;
|
|
268
305
|
bundleUrl: string;
|
|
269
306
|
integrity: string;
|
|
307
|
+
/**
|
|
308
|
+
* Per-viewer resolution of `manifest.toolDependencies`. This is host data,
|
|
309
|
+
* not bundle configuration: `toolId` is deliberately never written back into
|
|
310
|
+
* the manifest or exposed as an authoring-time identifier.
|
|
311
|
+
*/
|
|
312
|
+
dependencies?: Record<string, NgisResolvedToolDependency>;
|
|
270
313
|
/**
|
|
271
314
|
* The resolved version's declared scopes — a projection of
|
|
272
315
|
* `manifest.permissions` (Stage 13 XB1), where an absent key and `[]` mean
|
package/dist/src/types/gis.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export type GisToolId = "draw-point" | "draw-line" | "draw-polygon" | "draw-rect
|
|
|
83
83
|
* command catalog; host-only composite controls can ride on the rail without
|
|
84
84
|
* becoming invocable map tools.
|
|
85
85
|
*/
|
|
86
|
-
export type GisRailItemId = GisToolId | "multi-view" | "
|
|
86
|
+
export type GisRailItemId = GisToolId | "multi-view" | "smart-plotting";
|
|
87
87
|
/** How a tool behaves when invoked from the rail. */
|
|
88
88
|
export type GisToolKind = "mode" | "measure" | "action" | "dropdown";
|
|
89
89
|
/** One-shot actions the rail fires on the map via the imperative handle
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngis/plugin-sdk",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Types-first SDK for authoring
|
|
5
|
-
"license": "
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Types-first SDK for authoring NGIS map plugins (DevKit, Stage 11 E3). Distributed as an immutable-per-version tarball from ngis-extension (GET /api/extensions/sdk/{version}.tgz) \u2014 see the authoring guide.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
7
7
|
"type": "module",
|
|
8
|
-
"sideEffects": false,
|
|
9
8
|
"main": "./dist/index.cjs",
|
|
10
9
|
"module": "./dist/index.mjs",
|
|
11
10
|
"types": "./dist/index.d.ts",
|
|
@@ -19,27 +18,6 @@
|
|
|
19
18
|
"files": [
|
|
20
19
|
"dist"
|
|
21
20
|
],
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=20"
|
|
24
|
-
},
|
|
25
|
-
"repository": {
|
|
26
|
-
"type": "git",
|
|
27
|
-
"url": "git+https://github.com/GeoScenarioScripter/NeoGIS-Frontend.git",
|
|
28
|
-
"directory": "packages/plugin-sdk"
|
|
29
|
-
},
|
|
30
|
-
"homepage": "https://github.com/GeoScenarioScripter/NeoGIS-Frontend#readme",
|
|
31
|
-
"bugs": {
|
|
32
|
-
"url": "https://github.com/GeoScenarioScripter/NeoGIS-Frontend/issues"
|
|
33
|
-
},
|
|
34
|
-
"keywords": [
|
|
35
|
-
"neogis",
|
|
36
|
-
"gis",
|
|
37
|
-
"plugin",
|
|
38
|
-
"sdk"
|
|
39
|
-
],
|
|
40
|
-
"publishConfig": {
|
|
41
|
-
"access": "public"
|
|
42
|
-
},
|
|
43
21
|
"scripts": {
|
|
44
22
|
"build": "node scripts/build.mjs",
|
|
45
23
|
"prepack": "npm run build"
|
package/LICENSE
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Copyright 2026 NeoGIS contributors
|
|
2
|
-
|
|
3
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License.
|
|
5
|
-
You may obtain a copy of the License at
|
|
6
|
-
|
|
7
|
-
https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
|
|
9
|
-
Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
See the License for the specific language governing permissions and
|
|
13
|
-
limitations under the License.
|
package/README.md
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# @ngis/plugin-sdk
|
|
2
|
-
|
|
3
|
-
The public, types-first authoring contract for NeoGIS map plugins.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm install --save-dev @ngis/plugin-sdk
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { defineNgisPlugin, type NgisPlugin } from "@ngis/plugin-sdk";
|
|
11
|
-
|
|
12
|
-
const plugin: NgisPlugin = defineNgisPlugin({
|
|
13
|
-
manifest: {
|
|
14
|
-
id: "com.example.my-plugin",
|
|
15
|
-
name: "My plugin",
|
|
16
|
-
version: "0.1.0",
|
|
17
|
-
minNgisVersion: "0.4.0",
|
|
18
|
-
capabilities: {},
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export default plugin;
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Plugin builds must leave `@ngis/plugin-sdk` external. The running NeoGIS host
|
|
26
|
-
provides the runtime through its plugin import map. The npm package supplies the
|
|
27
|
-
real public declarations plus small host-delegating shims; bundling it into a
|
|
28
|
-
plugin would create a runtime copy that is disconnected from the host.
|
|
29
|
-
|
|
30
|
-
The package version matches `NGIS_MAP_API_VERSION`. Use
|
|
31
|
-
`create-ngis-plugin` for a complete buildable starter.
|