/** * The layer IR is the contract the reconcile core operates on (see HU2: * "one IR core, two front-ends"). The DOM manifest front-end (parse-manifest.ts) * produces this by reading elements; a future programmatic/React * front-end would produce the same shape directly, without a DOM in between. * The runtime core (runtime-core.ts) never reads the DOM — only this. */ /** Library-level metadata that never reaches deck.gl (legend label/swatch, etc). */ export interface LayerMeta { label?: string; color?: string; /** Statically-derived color symbology (spec: "Built-in widgets / legend") — set when get-fill-color is a recognizable scale()/categorical expression. */ legend?: LegendSpec; } export type { Shape } from "./expr"; import type { Shape } from "./expr"; import type { LegendSpec } from "./legend-spec"; /** * Columnar (GeoArrow-style) data: one array per field instead of one object * per row (spec: "Columnar / GeoArrow Ingestion"). Produced by the Data * Layer from Arrow IPC files or column-oriented JSON; handed to deck.gl * as-is (deck treats any non-iterable object with `length` as N rows and * calls accessors with `(object, { index, data })` — compiled columnar * accessors read `data.columns.[index]`, never a row object). */ export interface ColumnarData { length: number; columns: Record>; } /** A layer's resolved data: row objects (flat/geojson) or columns (columnar). */ export type LayerData = unknown[] | ColumnarData; /** * One numeric dimension of the layer's active declarative filter (spec: * "Filtering"). Carried on the IR (not just `meta`) because `ctx.stats`/ * `ctx.dataInViewport` apply it CPU-side by default (the "coherence rule") * — it's operationally read, not just display metadata. */ export interface LayerFilterDimension { field: string; range: [number, number]; softRange?: [number, number]; } /** * The layer's active declarative filter — one entry per numeric dimension, * up to 4 (deck.gl `DataFilterExtension`'s own `filterSize` ceiling; a * GLSL compile-time define, verified against the extension's own source — * see `applyFilterWiring`'s doc comment). The single-field case (the * original, still-primary authoring path via `filter-field`/`filter-range`) * is simply a one-entry array — every consumer treats N=1 the same as * N>1, no special-casing. */ export type LayerFilter = LayerFilterDimension[]; /** * One categorical dimension of the layer's active declarative filter — a * discrete keep-list rather than a numeric range (deck.gl's `categorySize` * slot, a SEPARATE bitmask mechanism from `filterSize`'s range test; values * may be string or number). Carried on the IR for the same reason * `LayerFilterDimension` is: `ctx.stats`/`ctx.dataInViewport` apply it * CPU-side by default too. */ export interface LayerCategoryFilterDimension { field: string; categories: (string | number)[]; } /** The layer's active categorical filter — one entry per dimension, up to 4 (categorySize's own ceiling). Independent of, and combinable with, the numeric `LayerFilter`. */ export type LayerCategoryFilter = LayerCategoryFilterDimension[]; /** Async non-row asset resolution carried by the IR (currently ImageOverlay EXIF only). */ export interface LayerAssetState { kind: "image-overlay"; /** Cache identity: source URL + resolver overrides. */ key: string; sourceUrl: string; status: "pending" | "resolved" | "error"; /** JSON-safe resolved metadata; absent while pending and for explicit-bounds reconstruction. */ metadata?: unknown; /** Settled failure text. Errors never keep map readiness pending. */ error?: string; } export interface LayerIR { /** Stable identity — keyed 1:1 to the `id` attribute. Required. */ id: string; /** Registry lookup key, e.g. "ScatterplotLayer". */ type: string; /** Resolved deck.gl props (accessors + shorthand-expansions + scalar pass-through merged). */ props: Record; /** Resolved data — a feature array, or columns for columnar layers. Empty array = not yet loaded (Q6 placeholder). */ data: LayerData; /** flat vs. GeoJSON — drives $field resolution and ctx.stats/dataInViewport field access. */ shape: Shape; /** The raw `data` URL attribute, if any (ctx.layers metadata) — inline-JSON layers have none. */ dataUrl?: string; /** * The authored live-source options that select this layer's transport * (spec: "Live & Streaming Data"), retained on the IR rather than consumed * by `getData` alone. Both front-ends populate it, so anything asking "is * this layer polling?" reads resolved state instead of querying the DOM — * which only the HTML lane has (see telemetry's per-layer snapshot). * Absent when the layer declares none. */ live?: { source?: string; key?: string; flush?: string; refresh?: string; }; /** * A `{z}/{x}/{y}` tile URL template (spec: "Tiled layers"): deck's `data` * for TileLayer/MVTLayer. When set, the renderer passes THIS as `data` * (deck expands it per tile) instead of the fetched-rows `data` placeholder, * and stats/filter/viewport machinery no-ops (there are no local rows). */ tileData?: string; /** * A layer-owned asset that resolves outside the row Data Layer. Pending * assets stay in the authored IR/license count but are skipped by the * renderer until a follow-up reconcile supplies resolved props. */ asset?: LayerAssetState; meta: LayerMeta; /** The layer's active declarative filter (one entry per dimension), if `filter-field`/`filter-fields` are present. */ filter?: LayerFilter; /** The layer's active categorical filter (one entry per dimension), if `filter-category`/`filter-category-fields` are present. Independent of `filter`. */ categoryFilter?: LayerCategoryFilter; /** * Per-layer terrain mode (spec: "Terrain") — the `terrain` attribute: * drape onto / sit on / ignore an active terrain surface. Absent = the * type's default (3D-model layers offset, tiled 3D layers off, everything * else drapes). Inert without map-level terrain. */ terrain?: "drape" | "offset" | "off"; /** * deck.gl `updateTriggers` — one behavioural fingerprint (`shape|paramName|source`) * per compiled accessor deckProp, so deck.gl recomputes an attribute iff the * accessor's source actually changed (see Layer Reconciliation). Every * successfully-compiled accessor gets an entry; omitting one would silently * fall back to deck.gl's default (no-recompute-on-identity-change) behaviour. */ updateTriggers: Record; }