@ngis/plugin-ui 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.
Files changed (42) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.mjs +1 -1
  4. package/dist/src/api/analysis.d.ts +38 -1
  5. package/dist/src/api/crs.d.ts +110 -0
  6. package/dist/src/api/resources.d.ts +174 -10
  7. package/dist/src/api/scenes.d.ts +103 -4
  8. package/dist/src/api/workflows.d.ts +10 -1
  9. package/dist/src/components/plugin-ui/ChartCanvas.d.ts +3 -0
  10. package/dist/src/components/plugin-ui/ParamForm.d.ts +67 -5
  11. package/dist/src/components/plugin-ui/PickerField.d.ts +17 -0
  12. package/dist/src/components/plugin-ui/RunButton.d.ts +8 -0
  13. package/dist/src/components/plugin-ui/crs-issue-messages.d.ts +157 -0
  14. package/dist/src/components/plugin-ui/flow-ports.d.ts +17 -0
  15. package/dist/src/components/plugin-ui/param-widgets.d.ts +2 -2
  16. package/dist/src/components/plugin-ui/picker-model.d.ts +49 -0
  17. package/dist/src/components/plugin-ui/run-data.d.ts +32 -4
  18. package/dist/src/components/plugin-ui/w2-contracts.d.ts +61 -0
  19. package/dist/src/components/plugin-ui/w3-contracts.d.ts +16 -1
  20. package/dist/src/components/resources/CrsPicker/CrsChip.d.ts +60 -0
  21. package/dist/src/components/resources/CrsPicker/CrsParamControl.d.ts +10 -0
  22. package/dist/src/components/resources/CrsPicker/CrsPicker.d.ts +41 -0
  23. package/dist/src/components/resources/CrsPicker/crs-recent.d.ts +40 -0
  24. package/dist/src/components/resources/CrsPicker/index.d.ts +14 -0
  25. package/dist/src/components/resources/resource-library-crs.d.ts +117 -0
  26. package/dist/src/components/ui/dialog.d.ts +25 -0
  27. package/dist/src/domain/algorithms/flow-field-style.d.ts +68 -0
  28. package/dist/src/domain/map/types.d.ts +2 -0
  29. package/dist/src/domain/models/crs.d.ts +187 -0
  30. package/dist/src/domain/models/dataset-kind.d.ts +256 -0
  31. package/dist/src/domain/models/flow-field-style.d.ts +32 -0
  32. package/dist/src/features/gis/components/GisAttributeTable.d.ts +11 -3
  33. package/dist/src/features/gis/hooks/useDatasetFeatureWindow.d.ts +5 -9
  34. package/dist/src/features/gis/lib/attributeFilters.d.ts +24 -0
  35. package/dist/src/sdk/facets/types.d.ts +73 -2
  36. package/dist/src/sdk/map-api.d.ts +5 -2
  37. package/dist/src/types/analysis.d.ts +111 -7
  38. package/dist/src/types/extensions.d.ts +43 -0
  39. package/dist/src/types/gis.d.ts +1 -1
  40. package/package.json +6 -37
  41. package/LICENSE +0 -13
  42. package/README.md +0 -25
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The CRS chip and its unit sigil (Stage 25, **F25.2 / F25.13**).
3
+ *
4
+ * ## The design thesis, in one rule: round means degrees, square means metres
5
+ *
6
+ * Every CRS in the product opens with a **unit sigil** — a small box carrying
7
+ * the unit glyph (`°` / `m` / `ft` / `?`). A **geographic** CRS gets a
8
+ * **circle**; a **projected** CRS gets a **square**. That single shape rule is
9
+ * the whole visual system, and it is chosen for exactly the thing this stage
10
+ * exists to prevent: G-U says *no distance in degrees*, and a user who has
11
+ * learned one shape rule sees the hazard across the room before reading a digit.
12
+ *
13
+ * Colour reinforces it and never carries it alone. S25-02 verified the rule
14
+ * under the Iris family, where `--ring` and `--primary` are both violet and the
15
+ * shape still reads. That is the accessibility floor here, not a nicety: the
16
+ * distinction this chip draws is the one a colour-blind user most needs.
17
+ *
18
+ * `NEEDS_CRS` is drawn as **absence** — a hollow *dashed* circle and muted type
19
+ * — never as an error. D25-3 makes it a state, not a failure; colouring it
20
+ * `destructive` would say the opposite, and a user who reads "broken" deletes
21
+ * the dataset instead of setting its projection.
22
+ *
23
+ * ## The grammar is `code · name`
24
+ *
25
+ * The code sits in `--font-mono` (tabular, tight), the name in the UI sans,
26
+ * separated by a middot. The pairing *is* the chip: it reads as
27
+ * "identifier · human name" rather than one undifferentiated blob. The string
28
+ * itself comes from {@link formatCrs} and is never assembled here — that is how
29
+ * the chip and the table cell are kept from drifting, and it is where G-CRS
30
+ * lives.
31
+ */
32
+ import { type NgisCrsRef } from "../../../domain/models/crs";
33
+ export interface CrsChipProps {
34
+ /** `null` renders the NEEDS_CRS state. It is never treated as EPSG:4326. */
35
+ crs: NgisCrsRef | null;
36
+ /**
37
+ * `full` — sigil, code and name (detail panels, the picker footer).
38
+ * `compact` — sigil and code only (table cells, list rows, `ResourceSelect`).
39
+ */
40
+ density?: "full" | "compact";
41
+ /**
42
+ * Already-translated. Host-supplied for the same reason `PickerField`'s
43
+ * notices are: this component holds no translator and cannot invent copy.
44
+ * Defaults to `formatCrs`'s own English fallback.
45
+ */
46
+ needsCrsText?: string;
47
+ /** Rendered after the text when the CRS is absent — the ⚠ of F25.5's badge. */
48
+ warn?: boolean;
49
+ className?: string;
50
+ }
51
+ /**
52
+ * The sigil alone. Exported inside this folder only — five call sites draw it
53
+ * (full chip, compact chip, picker trigger, list row, the "needs projection"
54
+ * filter toggle) and an inlined copy would drift on the first restyle.
55
+ */
56
+ export declare function CrsUnitSigil({ crs, className, }: {
57
+ crs: NgisCrsRef | null;
58
+ className?: string;
59
+ }): import("react/jsx-runtime").JSX.Element;
60
+ export declare function CrsChip({ crs, density, needsCrsText, warn, className, }: CrsChipProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { NgisCrsRequirement } from "../../../domain/models/crs";
2
+ export interface CrsParamControlProps {
3
+ /** The wire value: a `NgisCrsRef.key`, or `null` for "compute in the input's own CRS". */
4
+ value: string | null;
5
+ onChange: (value: string | null) => void;
6
+ requirement?: NgisCrsRequirement;
7
+ id?: string;
8
+ contentClassName?: string;
9
+ }
10
+ export declare function CrsParamControl({ value, onChange, requirement, id, contentClassName, }: CrsParamControlProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { type NgisCrsRef, type NgisCrsRequirement } from "../../../domain/models/crs";
2
+ import type { NgisBounds } from "../../../sdk/map-api";
3
+ export type CrsPickerMode = "select" | "assign";
4
+ export interface CrsPickerProps {
5
+ /**
6
+ * `select` — choose a CRS to compute/export in. `assign` — declare what a
7
+ * dataset's coordinates already are. **The two never share copy** (G-A).
8
+ */
9
+ mode: CrsPickerMode;
10
+ /** `null` is a real value in `select` mode: "compute in the input's own CRS". */
11
+ value: NgisCrsRef | null;
12
+ onChange: (value: NgisCrsRef | null) => void;
13
+ /** Enables the Suggested tab, and makes it the default. `[w, s, e, n]` in EPSG:4326. */
14
+ suggestFrom?: NgisBounds;
15
+ /** Default `true`. The Custom tab reaches the oracle's resolve leg. */
16
+ allowCustom?: boolean;
17
+ /** The tool's `x-crs-requirement`. Filters Search, and the picker says so. */
18
+ requirement?: NgisCrsRequirement;
19
+ /** Named in `assign` mode's header — the thing being labelled. */
20
+ datasetName?: string;
21
+ /** Control id, so a `<label for>` reaches the trigger. */
22
+ id?: string;
23
+ className?: string;
24
+ /**
25
+ * Class for the portalled dialog content. Radix portals to `document.body`,
26
+ * **outside** `.gis-theme` — pass `"gis-theme"` when this is mounted inside the
27
+ * GIS workspace. The `VersionPolicyControl` / `ResourceSelect` precedent, and
28
+ * it has already cost two sessions.
29
+ */
30
+ contentClassName?: string;
31
+ }
32
+ export declare function CrsPicker({ mode, value, onChange, suggestFrom, allowCustom, requirement, datasetName, id, className, contentClassName, }: CrsPickerProps): import("react/jsx-runtime").JSX.Element;
33
+ /**
34
+ * `CrsSelect` — the `@ngis/plugin-ui` spelling of the same component in `select`
35
+ * mode (F25.9), so it sits beside `ResourceSelect` / `ModelSelect` /
36
+ * `FieldSelect` on a plugin's import line. The internal component keeps the name
37
+ * `CrsPicker` because it is a **dialog**, not a `<select>`, and naming a dialog
38
+ * after a form control is how a component ends up rebuilt by someone who read
39
+ * only its name.
40
+ */
41
+ export declare function CrsSelect(props: Omit<CrsPickerProps, "mode" | "datasetName">): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The picker's **Recent** tab (F25.2): the last CRSs this user chose, at most
3
+ * eight, in `localStorage`.
4
+ *
5
+ * ## Why per user, and why the id is in the key
6
+ *
7
+ * A shared browser is the normal case on a survey workstation. Keying the list
8
+ * by the token subject means signing in as someone else does not hand you their
9
+ * working projections — which look exactly like your own and are wrong for your
10
+ * data. With no subject available (signed out, or a token this build cannot
11
+ * read) the list is scoped to `anon` rather than to a global bucket.
12
+ *
13
+ * ## Every read is defensive, and a failed read is empty — never a default
14
+ *
15
+ * `localStorage` throws in a private window and under a blocked-site-data
16
+ * setting, and its contents are user-editable text. So the whole surface is
17
+ * try/caught and the parse is validated: a corrupt entry yields an empty list,
18
+ * never a fabricated CRS. G-CRS is not only about the server — a recent list
19
+ * that invented `EPSG:4326` after a bad parse would be the same defect wearing
20
+ * a different hat.
21
+ *
22
+ * The functions are pure over an injected `Storage`, so they are asserted
23
+ * without a DOM.
24
+ */
25
+ import type { NgisCrsRef } from "../../../domain/models/crs";
26
+ /** F25.2 caps the list at eight. Beyond that it stops being *recent*. */
27
+ export declare const CRS_RECENT_LIMIT = 8;
28
+ export declare function crsRecentStorageKey(userId: string | null): string;
29
+ /** Parse a raw storage string. Never throws; a corrupt value reads as empty. */
30
+ export declare function parseCrsRecent(raw: string | null): readonly NgisCrsRef[];
31
+ /**
32
+ * The list after choosing `crs`: most recent first, de-duplicated **by key**,
33
+ * capped. Re-choosing a CRS moves it to the front rather than adding a second
34
+ * row — and the newly-stored ref wins, so a ref that has since been resolved
35
+ * replaces the bare one that was stored before the oracle answered.
36
+ */
37
+ export declare function withCrsRecent(current: readonly NgisCrsRef[], crs: NgisCrsRef | null): readonly NgisCrsRef[];
38
+ export declare function readCrsRecent(storage: Storage | null | undefined, userId: string | null): readonly NgisCrsRef[];
39
+ /** Returns the list it wrote, so a caller can update state without re-reading. */
40
+ export declare function pushCrsRecent(storage: Storage | null | undefined, userId: string | null, crs: NgisCrsRef | null): readonly NgisCrsRef[];
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `CrsPicker` — the Stage 25 CRS picker (F25.2, F25.13).
3
+ *
4
+ * The exported names are **frozen**: `CrsPicker`, `CrsPickerProps`,
5
+ * `CrsPickerMode`, `CrsChip`, `CrsChipProps`, plus `CrsSelect` — the spelling
6
+ * `@ngis/plugin-ui` publishes, so it sits beside `ResourceSelect` /
7
+ * `ModelSelect` / `FieldSelect` on a plugin's import line. A session needing
8
+ * another name records it and raises; it never invents one.
9
+ */
10
+ export { CrsPicker, CrsSelect } from "./CrsPicker";
11
+ export type { CrsPickerMode, CrsPickerProps } from "./CrsPicker";
12
+ export { CrsChip, CrsUnitSigil } from "./CrsChip";
13
+ export type { CrsChipProps } from "./CrsChip";
14
+ export { CRS_RECENT_LIMIT, crsRecentStorageKey, parseCrsRecent, pushCrsRecent, readCrsRecent, withCrsRecent, } from "./crs-recent";
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The projection rules of the resource library, as pure functions (Stage 25,
3
+ * **F25.5**).
4
+ *
5
+ * Everything here is decided *outside* a component on purpose. This repo has no
6
+ * DOM in tests (`S25-V127`), so a rule that lives inside a `useMemo` cannot be
7
+ * asserted; a rule that cannot be asserted is a rule that regresses. The three
8
+ * that matter are all here:
9
+ *
10
+ * - **what "needs projection" means** — a state, read from two independent
11
+ * fields, never from the pipeline status alone;
12
+ * - **which formats a dataset can be written as** — and that a dataset with no
13
+ * projection may only leave as its original bytes;
14
+ * - **which body an assign sends** — `{ key }` or `{ wkt2 }`, never a srid.
15
+ *
16
+ * G-CRS lives above all of them: nothing here invents a CRS, and no branch has a
17
+ * `4326` fallback.
18
+ */
19
+ import type { ResourceListItem, ResourceType } from "../../api/resources";
20
+ import type { NgisAssignCrsBody } from "../../api/crs";
21
+ import type { NgisCrsRef, NgisCrsSource } from "../../domain/models/crs";
22
+ /**
23
+ * `DatasetInfo.status` for a dataset whose CRS the server does not know
24
+ * (D25-3). Kept as one constant because it is compared in four places and a
25
+ * literal typo would read as "this dataset is fine".
26
+ */
27
+ export declare const NEEDS_CRS_STATUS = "NEEDS_CRS";
28
+ /**
29
+ * **Two fields, either of which is decisive.**
30
+ *
31
+ * `datasetStatus` is the server's word and is authoritative when it is present.
32
+ * But `ResourceListItem` is assembled from a hydration that can fail
33
+ * (`getDatasetHydration`), and a row whose dataset read failed carries
34
+ * `datasetStatus: "N/A"` with `crs: null` — which is *also* "we cannot say what
35
+ * this is in", and the badge must say so rather than draw a confident blank.
36
+ *
37
+ * Note what this does **not** do: it never infers "has a projection" from a
38
+ * status of `DONE`. A `DONE` row with no `crs` is still a row with no
39
+ * projection, and treating the status as the answer is how `"-"` got printed
40
+ * over an unknown CRS for two stages (F25.1).
41
+ */
42
+ export declare function needsProjection(item: Pick<ResourceListItem, "crs" | "datasetStatus">): boolean;
43
+ /** How many rows of a list are in the state the filter selects. */
44
+ export declare function countNeedsProjection(items: readonly Pick<ResourceListItem, "crs" | "datasetStatus">[]): number;
45
+ /**
46
+ * Whether the caller may declare this dataset's projection. **Ownership, not
47
+ * status**: `NEEDS_CRS` is a state a shared or public dataset can be in too, and
48
+ * the person who may fix it is its owner (F25.5). A dataset in the trash is not
49
+ * assignable — the assign dispatches a re-import, and re-importing something the
50
+ * user has deleted is not a state the pipeline should be asked to reach.
51
+ */
52
+ export declare function canAssignProjection(item: Pick<ResourceListItem, "scope" | "canDelete">): boolean;
53
+ /**
54
+ * The i18n leaf under `ResourceLibrary.projection.sources.*` for a ref's
55
+ * provenance. `"unknown"` for a ref the server sent without a `source` — which
56
+ * is a real case (a `crs` carried on a search hit, a suggestion) and is
57
+ * deliberately *not* collapsed into `DETECTED`: "we do not know how this was
58
+ * decided" and "the file said so" are the two facts G-CRS exists to keep apart.
59
+ */
60
+ export declare function crsSourceKey(ref: NgisCrsRef | null): NgisCrsSource | "unknown";
61
+ /**
62
+ * The download formats offered per resource type, plus `original` — the bytes
63
+ * as uploaded, which is the only honest option for a dataset the platform has
64
+ * not imported.
65
+ *
66
+ * `original` is first in every list because it is the default and because it is
67
+ * the only one that cannot fail: everything else is a conversion the server
68
+ * performs.
69
+ */
70
+ export type ResourceDownloadFormat = "original" | "geojson" | "gpkg" | "shp" | "tif";
71
+ export declare function downloadFormatsFor(type: ResourceType): readonly ResourceDownloadFormat[];
72
+ /**
73
+ * The query the download leg is called with (F25.5).
74
+ *
75
+ * Three rules, and each of them is a defect this stage is closing:
76
+ *
77
+ * 1. **`original` sends no `format`** — the original bytes are the absence of a
78
+ * conversion, not a conversion named "original".
79
+ * 2. **A CRS equal to the dataset's own sends no `crs`** — asking the server to
80
+ * reproject a file into the CRS it is already in would spend a reprojection
81
+ * to produce the same coordinates, and (worse) would register a variant under
82
+ * a key the original already owns.
83
+ * 3. **Never `srid`.** `?srid=` survives this stage only as a deprecated alias
84
+ * the server accepts from older clients (F25.12); nothing here emits it.
85
+ */
86
+ export declare function downloadQuery(options: {
87
+ format: ResourceDownloadFormat;
88
+ /** The CRS the user chose, or `null` for "the dataset's own". */
89
+ crs: NgisCrsRef | null;
90
+ /** The dataset's own CRS, so an unchanged choice sends nothing. */
91
+ datasetCrs: NgisCrsRef | null;
92
+ }): {
93
+ format?: string;
94
+ crs?: string;
95
+ };
96
+ /**
97
+ * What the browser saves the download as.
98
+ *
99
+ * `original` keeps the stored filename verbatim. A converted download takes the
100
+ * dataset's name with the **format's real** extension — including `.zip` for a
101
+ * Shapefile, which is a bundle and not a file.
102
+ */
103
+ export declare function downloadFileName(resource: Pick<ResourceListItem, "filename" | "name">, format?: string): string;
104
+ /** Whether the chosen CRS means bytes move on the way out — the copy switches on this. */
105
+ export declare function downloadReprojects(options: {
106
+ crs: NgisCrsRef | null;
107
+ datasetCrs: NgisCrsRef | null;
108
+ }): boolean;
109
+ /**
110
+ * The `PUT /api/datasets/{id}/crs` body for a ref the picker returned.
111
+ *
112
+ * A ref with a `custom:` key that the caller has **already registered** is sent
113
+ * by key; a ref that carries only a definition is sent as `wkt2` so the server
114
+ * registers it. `null` is not an assignable value — un-declaring a projection is
115
+ * a different operation and this returns `null` rather than inventing one.
116
+ */
117
+ export declare function assignBodyFor(ref: NgisCrsRef | null): NgisAssignCrsBody | null;
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
3
+ declare function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>): import("react/jsx-runtime").JSX.Element;
4
+ declare function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>): import("react/jsx-runtime").JSX.Element;
5
+ declare function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>): import("react/jsx-runtime").JSX.Element;
6
+ declare function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>): import("react/jsx-runtime").JSX.Element;
7
+ declare function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>): import("react/jsx-runtime").JSX.Element;
8
+ declare function DialogContent({ className, children, showCloseButton, showOverlay, overlayClassName, sizeClassName, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
9
+ showCloseButton?: boolean;
10
+ showOverlay?: boolean;
11
+ overlayClassName?: string;
12
+ sizeClassName?: string;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ declare function DraggableDialogContent({ children, resizable, minWidth, minHeight, onOpenAutoFocus, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, style, ...props }: React.ComponentProps<typeof DialogContent> & {
15
+ resizable?: boolean;
16
+ minWidth?: number;
17
+ minHeight?: number;
18
+ }): import("react/jsx-runtime").JSX.Element;
19
+ declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
20
+ declare function DialogFooter({ className, showCloseButton, children, ...props }: React.ComponentProps<"div"> & {
21
+ showCloseButton?: boolean;
22
+ }): import("react/jsx-runtime").JSX.Element;
23
+ declare function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>): import("react/jsx-runtime").JSX.Element;
24
+ declare function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>): import("react/jsx-runtime").JSX.Element;
25
+ export { Dialog, DialogClose, DialogContent, DraggableDialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, };
@@ -0,0 +1,68 @@
1
+ import type { FlowFieldStyle } from "../models/flow-field-style";
2
+ export declare const FLOW_FIELD_STYLE_VERSION: 1;
3
+ export declare const FLOW_FIELD_COLOR_RAMP_TEXTURE_SIZE = 256;
4
+ export declare const FLOW_FIELD_MAX_PARTICLES = 262144;
5
+ /**
6
+ * Practical ranges for the style editor. These are intentionally narrower
7
+ * than the parser's safety bounds so normal operation has useful low-end
8
+ * precision, without invalidating richer styles saved by earlier releases.
9
+ */
10
+ export declare const FLOW_FIELD_STYLE_CONTROL_RANGES: {
11
+ readonly lineWidth: {
12
+ readonly min: 0.5;
13
+ readonly max: 2.5;
14
+ readonly step: 0.25;
15
+ };
16
+ readonly speedFactor: {
17
+ readonly min: 0.1;
18
+ readonly max: 1.4;
19
+ readonly step: 0.05;
20
+ };
21
+ readonly frameDurationMs: {
22
+ readonly min: 250;
23
+ readonly max: 1750;
24
+ readonly step: 50;
25
+ };
26
+ readonly particleCount: {
27
+ readonly min: 1000;
28
+ readonly max: 11000;
29
+ readonly step: 500;
30
+ };
31
+ readonly trailLength: {
32
+ readonly min: 2;
33
+ readonly max: 10;
34
+ readonly step: 1;
35
+ };
36
+ readonly maxParticleAge: {
37
+ readonly min: 30;
38
+ readonly max: 150;
39
+ readonly step: 1;
40
+ };
41
+ readonly dropRate: {
42
+ readonly min: 0.001;
43
+ readonly max: 0.007;
44
+ readonly step: 0.001;
45
+ };
46
+ readonly dropRateBump: {
47
+ readonly min: 0;
48
+ readonly max: 0.004;
49
+ readonly step: 0.001;
50
+ };
51
+ };
52
+ export declare const DEFAULT_FLOW_FIELD_COLORS: readonly ["#3288bd", "#66c2a5", "#abdda4", "#e6f598", "#fee08b", "#fdae61", "#f46d43", "#d53e4f"];
53
+ export declare const DEFAULT_FLOW_FIELD_STYLE: Readonly<FlowFieldStyle>;
54
+ export declare function createDefaultFlowFieldStyle(): FlowFieldStyle;
55
+ export declare function parseFlowFieldColors(value: unknown): string[] | null;
56
+ /**
57
+ * Migrates every pre-v1 flow style into the v1 contract and removes unknown
58
+ * properties so a layer style remains a safe, portable JSON payload.
59
+ */
60
+ export declare function parseFlowFieldStyle(value: unknown): FlowFieldStyle;
61
+ export declare function parseFlowFieldStyleJson(styleJson: string | null | undefined): FlowFieldStyle;
62
+ export declare function serializeFlowFieldStyle(value: unknown): string;
63
+ export declare function sanitizeFlowFieldStyleJson(styleJson: string | null | undefined): {
64
+ styleJson: string;
65
+ changed: boolean;
66
+ };
67
+ export declare function isFlowFieldStyle(value: unknown): boolean;
68
+ export declare function sampleFlowFieldColorRamp(colors: readonly string[], count?: number): Uint8Array<ArrayBuffer>;
@@ -99,6 +99,8 @@ export interface SceneLayerHighlight {
99
99
  allFeatures?: boolean;
100
100
  /** Only meaningful with `allFeatures`; rows unchecked after select-all. */
101
101
  excludedFeatureIds?: string[];
102
+ /** Optional MapLibre predicate narrowing an all-matching table selection. */
103
+ attributeFilter?: HighlightFilterExpression;
102
104
  }
103
105
  export type SceneLayerStyle = Record<string, unknown>;
104
106
  export type SceneLayerRenderMode = "maplibre" | "custom-raster-color" | "custom-flow-field";
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Stage 25 (F25.1) — the frontend's CRS domain model.
3
+ *
4
+ * **Authored by the W1 kickoff: TYPES AND CONSTANTS ONLY. Read-only to every W1 session
5
+ * except S25-13, which owns this file and adds the function bodies below.**
6
+ *
7
+ * This is the TypeScript leg of the Stage 25 twin triple. The other two are `CrsDto`
8
+ * (ngis-file, `io.ngis.file.dto`) and `crs_ref.py` (ngis-geoanalysis). All three mirror
9
+ * `ngis-protos/crs.proto`'s `CrsRef` and the fixtures in `__fixtures__/crs/`, which are
10
+ * byte-identical across four copies (F25.14 — compare with `diff --strip-trailing-cr`,
11
+ * never an on-disk hash: the two repos normalise line endings differently).
12
+ *
13
+ * ## G-CRS lives here
14
+ *
15
+ * `key` is the identity and the only field a caller must round-trip. An absent CRS is
16
+ * `null` — never `key: ""`, never `epsg: 0`. Zero is not "unknown", it is absent. The whole
17
+ * stage exists because `crs.py:78` silently turned an undetectable vector CRS into 4326 and
18
+ * nothing downstream could tell the difference.
19
+ *
20
+ * @see docs/projection_management_stage/README.md — F25.1, F25.13 (frozen names)
21
+ */
22
+ /** How a CRS came to be attached to a dataset. Never absent on a stored CRS (G-CRS). */
23
+ export type NgisCrsSource = "DETECTED" | "SPEC_DEFAULT" | "USER_ASSIGNED" | "DERIVED";
24
+ /**
25
+ * The display accessor over `NgisCrsRef.geographic`. The boolean is right on the wire, but
26
+ * `!ref.geographic` reads as a double negative at every UI call site (F25.13).
27
+ */
28
+ export type NgisCrsKind = "geographic" | "projected";
29
+ /**
30
+ * What a tool requires of its inputs' CRS.
31
+ *
32
+ * `"uniform"` is **not** a member — D25-5 struck it. `"geographic"` is hand-authored only.
33
+ */
34
+ export type NgisCrsRequirement = "any" | "projected" | "geographic";
35
+ /**
36
+ * Where a tool's `crsRequirement` came from. A `CLASSIFIER` requirement may only ever raise
37
+ * an **advisory** issue (R10 / D25-5 as amended): no classifier rule measured better than
38
+ * 0.800 precision, and a false `"projected"` refuses a run the user is entitled to make.
39
+ */
40
+ export type NgisCrsRequirementSource = "HAND_AUTHORED" | "CLASSIFIER";
41
+ /** One coordinate reference system. `key` is `"EPSG:<code>"` or `"custom:<ulid>"`. */
42
+ export interface NgisCrsRef {
43
+ /** The identity, and the only field a caller must round-trip. */
44
+ readonly key: string;
45
+ /** Set iff `key` is an `EPSG:` ref. Never `0` — an absent CRS is a `null` ref. */
46
+ readonly epsg?: number;
47
+ /** Always on a resolved custom ref; on an EPSG ref only when asked. */
48
+ readonly wkt2?: string;
49
+ /** e.g. `"WGS 84 / UTM zone 50N"`. Present iff the oracle has resolved the ref. */
50
+ readonly name?: string;
51
+ readonly geographic?: boolean;
52
+ /** `"metre"` | `"degree"` | `"foot"` | `"us-foot"` | … */
53
+ readonly unit?: string;
54
+ readonly source?: NgisCrsSource;
55
+ }
56
+ /** Blocking refuses the submit with 400; advisory never gates Run (R9). */
57
+ export type NgisValidationIssueSeverity = "blocking" | "advisory";
58
+ /**
59
+ * Open by design — a later stage may add a code, and an unknown code must render as its
60
+ * `messageKey` rather than being dropped (the `ExtensionSettingsForm` rule, F14.9).
61
+ */
62
+ export type NgisCrsIssueCode = "CRS_UNKNOWN" | "CRS_PROJECTED_REQUIRED" | "CRS_GEOGRAPHIC_REQUIRED" | "CRS_REPROJECT_NOTICE" | "CRS_UNSUPPORTED_BY_PROVIDER" | (string & {});
63
+ /** The one-click remedy attached to a blocking issue. Carries the `NgisPickerNotice.action`. */
64
+ export interface NgisValidationIssueFix {
65
+ /** The parameter the fix sets — e.g. `"ngis:outputCrs"`. */
66
+ readonly param: string;
67
+ readonly value: string;
68
+ /** Client-localised under `Crs.issues.fix.*`. */
69
+ readonly labelKey: string;
70
+ readonly params?: Readonly<Record<string, string | number>>;
71
+ }
72
+ /**
73
+ * A typed, per-parameter validation issue (F25.6). **Additive** to F14.9's string arrays:
74
+ * `missingParameters` / `parameterErrors` / `outputErrors` / `warnings` keep their meaning.
75
+ *
76
+ * `param` routes it to that field's `notices`; `port` routes it to that input port.
77
+ */
78
+ export interface NgisValidationIssue {
79
+ readonly code: NgisCrsIssueCode;
80
+ readonly severity: NgisValidationIssueSeverity;
81
+ /** Client-localised under `Crs.issues.*`. */
82
+ readonly messageKey: string;
83
+ readonly params?: Readonly<Record<string, string | number>>;
84
+ readonly param?: string;
85
+ readonly port?: string;
86
+ readonly fix?: NgisValidationIssueFix;
87
+ readonly crs?: {
88
+ readonly input?: NgisCrsRef;
89
+ readonly suggested?: NgisCrsRef;
90
+ readonly compute?: NgisCrsRef;
91
+ };
92
+ }
93
+ /** One input as the compiled plan sees it. */
94
+ export interface NgisCrsPlanInput {
95
+ readonly port: string;
96
+ /** The CRS the stored dataset is in. */
97
+ readonly from: string;
98
+ /** The CRS it is staged into for this run. Equal to `from` when no staging is needed. */
99
+ readonly to: string;
100
+ /** Written back as a cached export variant above `STAGING_CACHE_MIN_BYTES` (20 MiB). */
101
+ readonly cacheable?: boolean;
102
+ }
103
+ /**
104
+ * The server-compiled CRS plan (F25.4). Every executor path executes **this** rather than
105
+ * re-deriving a CRS from dataset metadata or a fallback — that is G-U.
106
+ *
107
+ * `null` whenever a blocking issue was raised (R9).
108
+ */
109
+ export interface NgisCrsPlan {
110
+ /** The CRS the run computes in. */
111
+ readonly computeCrs: string;
112
+ /**
113
+ * The integer that reaches the PostGIS SQL, the `-t_srs` of ogr2ogr/gdalwarp and the
114
+ * iObjects `inputSrids`: the EPSG code, or the registry's `spatial_ref_sys` srid
115
+ * (>= 900001) for a custom CRS. No executor re-derives it (R7).
116
+ */
117
+ readonly pgSrid: number;
118
+ /** The CRS the result is registered in. */
119
+ readonly resultCrs: string;
120
+ /** Always `"DERIVED"` when present (R6). */
121
+ readonly resultCrsSource?: NgisCrsSource;
122
+ /** One entry per input that had to be reprojected for the run. Staging never mutates storage. */
123
+ readonly stage: readonly NgisCrsPlanInput[];
124
+ }
125
+ /** The one platform projection parameter, injected into every tool's UI schema (F25.4). */
126
+ export declare const NGIS_OUTPUT_CRS_PARAM = "ngis:outputCrs";
127
+ /** `x-role` of a synthetic platform parameter. `w2-fixture.test.ts:252` excludes these. */
128
+ export declare const NGIS_PLATFORM_ROLE = "platform";
129
+ /**
130
+ * The identifier half of the chip, alone — `"EPSG:32650"`, `"Custom"`, or the
131
+ * NEEDS_CRS text. It exists so the chip, the table cell and the picker footer
132
+ * cannot drift: `formatCrs` is this plus the name, never a second spelling.
133
+ *
134
+ * **G-CRS.** A bare integer is never returned. A key that *is* a bare integer —
135
+ * which is what a server still on the old numeric `srid` would send — is
136
+ * rendered `EPSG:<n>`, because the one thing the user must never see is a naked
137
+ * number they cannot tell from a feature count.
138
+ */
139
+ export declare function formatCrsKey(key: string | null, needsCrsText?: string): string;
140
+ /**
141
+ * The full chip string — `"EPSG:32650 · WGS 84 / UTM zone 50N"`, `"Custom · <name>"`.
142
+ *
143
+ * **This is where G-CRS lives on the client.** It never prints a bare integer,
144
+ * and `formatCrs(null)` returns the NEEDS_CRS text — never `"-"`, and never
145
+ * `"EPSG:4326"`, which is the silent default the whole stage exists to remove.
146
+ * A ref whose oracle lookup has not happened yet has no `name`, and renders as
147
+ * its identifier alone rather than borrowing one.
148
+ */
149
+ export declare function formatCrs(ref: NgisCrsRef | null, needsCrsText?: string): string;
150
+ /**
151
+ * `true` only for a ref the oracle has resolved *and* reported as projected.
152
+ *
153
+ * An **unresolved** ref (no `geographic`) is `false` here, exactly as
154
+ * `crs_ref.py`'s `.projected` is — but it is never `true`, so "we have not asked
155
+ * yet" can never read as "this is safe to measure distance in". That asymmetry
156
+ * is G-U, restated in one predicate.
157
+ */
158
+ export declare function isProjectedCrs(ref: NgisCrsRef | null): boolean;
159
+ /**
160
+ * The display accessor over `NgisCrsRef.geographic` (F25.13). The boolean is
161
+ * right on the wire; `!ref.geographic` reads as a double negative at every UI
162
+ * call site, and a UI that has to negate to say "projected" eventually forgets to.
163
+ *
164
+ * Reports `"projected"` on exactly the refs {@link isProjectedCrs} accepts, so
165
+ * the two cannot drift. An unresolved ref reports `"geographic"` — the
166
+ * conservative half — and the chip draws it with the unknown sigil instead,
167
+ * because {@link crsUnitGlyph} has no unit to show.
168
+ */
169
+ export declare function crsKind(ref: NgisCrsRef): NgisCrsKind;
170
+ /**
171
+ * The unit sigil's glyph: `°` · `m` · `ft` · `?`.
172
+ *
173
+ * Exported because the sigil is drawn in five places (full chip, compact chip,
174
+ * picker trigger, list row, "needs projection" filter) and an inlined map would
175
+ * drift between them. `"?"` is the honest answer for both an absent ref and a
176
+ * unit no client mapping knows — a guessed glyph on an unknown unit is exactly
177
+ * the silent default this stage removes.
178
+ */
179
+ export declare function crsUnitGlyph(ref: NgisCrsRef | null): string;
180
+ /**
181
+ * The wire value — the ref's `key`, or `null`. This is what the `crs` widget
182
+ * puts in the params payload and what `PUT /api/datasets/{id}/crs` echoes.
183
+ *
184
+ * An empty-string key is `null`, not `""`: `key: ""` is not a CRS, and letting
185
+ * it through is how an absent CRS gets laundered into a present one.
186
+ */
187
+ export declare function crsKeyOf(ref: NgisCrsRef | null): string | null;