/** * Manual drawing — sketch-capture state machine (spec: "Manual Drawing — * Sketch Capture", phase D1). PURE and render-free: it turns a sequence of * map clicks + mode transitions into RFC 7946 GeoJSON features. The widget * (D3) feeds it pointer/keyboard events; the live source (D2) reads its * FeatureCollection and pushes commits through the streaming channel. No * deck.gl, no DOM, no GL — so the whole capture contract is unit-testable * with synthetic coordinates. */ export type DrawMode = "point" | "line" | "polygon"; /** [lng, lat] — the map's own coordinate order, same as every accessor. */ export type Position = [number, number]; export interface PointGeometry { type: "Point"; coordinates: Position; } export interface LineGeometry { type: "LineString"; coordinates: Position[]; } export interface PolygonGeometry { type: "Polygon"; coordinates: Position[][]; } export type DrawGeometry = PointGeometry | LineGeometry | PolygonGeometry; export interface DrawFeature { type: "Feature"; geometry: DrawGeometry; /** RFC 7946 top-level id — the same slot findRowIndexById / anchors read. */ id: string; properties: { drawMode: DrawMode; }; } export interface FeatureCollection { type: "FeatureCollection"; features: DrawFeature[]; } /** The in-progress shape, for the runtime-internal preview layer (D4). */ export interface DrawPreview { mode: DrawMode; /** Committed-so-far vertices of the current shape. */ vertices: Position[]; /** The live cursor position, for the rubber-band segment (null before first move). */ cursor: Position | null; } export interface DrawSessionOpts { /** Injectable id generator — a deterministic counter by default (tests assert structure, not entropy). */ idGen?: () => string; /** Fired whenever the COMMITTED feature set changes (commit / delete / clear / seed) — D2 wires this to the store push. */ onChange?: (fc: FeatureCollection) => void; } export declare function samePoint(a: Position, b: Position): boolean; /** * One sketch session. Owns the committed features and the single in-progress * shape. Every mutation that changes the committed set fires `onChange` once. */ export declare class DrawSession { private mode; private pending; private cursor; private features; private readonly nextId; private readonly onChange?; constructor(opts?: DrawSessionOpts); /** Switch tool (or `null` to deactivate). Discards any in-progress shape. */ setMode(mode: DrawMode | null): void; getMode(): DrawMode | null; /** * A map click. Point commits immediately (the tool stays active for the * next point); line/polygon append a vertex. Returns the committed feature * when this click finalized one (point only), else null. */ click(pos: Position): DrawFeature | null; /** Cursor moved — updates the rubber-band target. No committed-set change. */ move(pos: Position): void; /** * Finalize the in-progress line/polygon (from double-click / Enter). The * two clicks that make up a double-click are individually unreliable — * deck.gl's click/dblclick gesture disambiguation can drop either or both * of them depending on exact timing — so rather than depend on them having * landed in `pending`, the closing vertex is the live cursor position * (already updated by the hover that preceded the double-click, via a * separate, non-gesture-gated code path). A trailing duplicate — the * cursor exactly repeating the last real vertex — is dropped. No-op (shape * kept pending) if there aren't enough distinct vertices yet. Returns the * committed feature or null. */ commit(): DrawFeature | null; /** Esc — discard the in-progress shape, keep the tool active. */ cancel(): void; /** Delete one committed feature by id. Returns whether anything was removed. */ delete(id: string): boolean; /** Remove the most recently committed feature — the toolbar's "delete last" (v1 has no per-feature selection). */ deleteLast(): boolean; /** Remove every committed feature and any in-progress shape. */ clear(): void; /** Load an existing FeatureCollection to append to (autosave restore / seed `data`). */ seed(fc: FeatureCollection | { features?: unknown; } | null | undefined): void; toFeatureCollection(): FeatureCollection; /** * Committed-feature count for THIS session object (spec: "Cut/fill volume * measurement"). Distinct from reading the shared `getDrawData` store * mirror: that mirror is a module-global `Map` keyed by URL string with no * per-map/per-session isolation, so it can carry a PRIOR session's data * (a different `` sharing the same runtime-internal target id, or — * the case that surfaced this — a freshly re-mounted map in a test) until * this session's own first commit overwrites it. This reads `this.features` * directly, which always starts empty for a genuinely new session. */ count(): number; /** The in-progress shape for the preview layer, or null when nothing is pending. */ preview(): DrawPreview | null; private finalize; /** Re-key a seeded feature to this session's id space, preserving its geometry. */ private adopt; private emit; }