signalk-livedepth-plugin 0.3.0 → 0.3.3

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../node_modules/signalk-plotterext-bus/src/protocol.ts", "../../node_modules/signalk-plotterext-bus/src/wildcard.ts", "../../node_modules/signalk-plotterext-bus/src/bounds.ts", "../../node_modules/signalk-plotterext-bus/src/codec.ts", "../../node_modules/signalk-plotterext-bus/src/port.ts", "../../node_modules/signalk-plotterext-bus/src/endpoint.ts", "../../node_modules/signalk-plotterext-bus/src/extension.ts", "../../src/web/shared/live-depth.ts", "../../src/web/shared/settings.ts", "../../src/tiles/core/grid.ts", "../../src/tiles/core/store.ts", "../../src/tiles/core/accumulator.ts", "../../src/tiles/core/tile-source.ts", "../../src/tiles/core/correction.ts", "../../src/web/renderer.ts"],
4
- "sourcesContent": ["/**\n * Wire protocol types and constants for the Signal K plotter extension bus.\n *\n * The wire format is JSON-RPC 2.0 (https://www.jsonrpc.org/specification)\n * inside a routing envelope: `{ bus: \"plotterExt/1\", msg: <JSON-RPC object> }`.\n * Calls are JSON-RPC requests; events are JSON-RPC notifications whose\n * `method` is a hierarchical dot-separated event name.\n */\n\nexport const BUS_ID = 'plotterExt/1'\n\nexport type JsonRpcId = string | number\n\nexport interface JsonRpcRequest {\n jsonrpc: '2.0'\n id: JsonRpcId\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcNotification {\n jsonrpc: '2.0'\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcErrorObject {\n code: number\n message: string\n data?: { reason?: string; [key: string]: unknown }\n}\n\nexport interface JsonRpcSuccessResponse {\n jsonrpc: '2.0'\n id: JsonRpcId\n result: unknown\n}\n\nexport interface JsonRpcErrorResponse {\n jsonrpc: '2.0'\n id: JsonRpcId | null\n error: JsonRpcErrorObject\n}\n\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse\n\nexport type JsonRpcMessage =\n | JsonRpcRequest\n | JsonRpcNotification\n | JsonRpcResponse\n\nexport interface Envelope {\n bus: typeof BUS_ID\n msg: JsonRpcMessage\n}\n\n/**\n * JSON-RPC reserved codes for protocol errors, plus implementation-defined\n * codes (-32000..-32099 range) used by this package. Host API errors should\n * use HOST_ERROR with a stable string identifier in `error.data.reason`.\n */\nexport const RPC_ERRORS = {\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n HOST_ERROR: -32000,\n TIMEOUT: -32001,\n CONNECTION_CLOSED: -32002\n} as const\n\nexport class RpcError extends Error {\n readonly code: number\n readonly data?: { reason?: string; [key: string]: unknown }\n\n constructor(\n message: string,\n opts: {\n code?: number\n reason?: string\n data?: Record<string, unknown>\n } = {}\n ) {\n super(message)\n this.name = 'RpcError'\n this.code = opts.code ?? RPC_ERRORS.HOST_ERROR\n const data: Record<string, unknown> = { ...(opts.data ?? {}) }\n if (opts.reason !== undefined) data.reason = opts.reason\n this.data = Object.keys(data).length > 0 ? data : undefined\n }\n\n get reason(): string | undefined {\n return typeof this.data?.reason === 'string' ? this.data.reason : undefined\n }\n\n toErrorObject(): JsonRpcErrorObject {\n return {\n code: this.code,\n message: this.message,\n ...(this.data ? { data: this.data } : {})\n }\n }\n\n static fromErrorObject(err: JsonRpcErrorObject): RpcError {\n return new RpcError(err.message, { code: err.code, data: err.data })\n }\n\n /** Normalize any thrown value into an RpcError suitable for the wire. */\n static from(err: unknown): RpcError {\n if (err instanceof RpcError) return err\n if (err instanceof Error) {\n return new RpcError(err.message, { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n return new RpcError(String(err), { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n}\n\n/** Reserved event names used to establish a connection. */\nexport const EVENT_READY = 'bus.ready'\nexport const EVENT_HANDSHAKE = 'bus.handshake'\n\n/**\n * Optional payload a caller may send with `bus.ready`. Additive and\n * backward-compatible: a host that predates it (or does not adopt caller ids)\n * ignores it.\n */\nexport interface ReadyParams {\n /**\n * Caller-asserted context id. A host that adopts caller ids — an\n * embedding-host connection, where the host does not know the caller in\n * advance — uses it as `context.id`; otherwise it is ignored.\n */\n id?: string\n}\n\nexport type ContextKind =\n | 'panel'\n | 'widget'\n | 'window'\n | 'background'\n | 'embedding-host'\n\nexport interface HandshakeContext {\n kind: ContextKind\n /** Manifest-local contribution id. */\n id: string\n /** Host-assigned unique id for this placed instance (widgets). */\n instanceId?: string | null\n /** Widget instance a configuration panel was opened for. */\n targetInstance?: string | null\n /** Manifest-local widget id of the target instance (configuration panels). */\n targetWidget?: string | null\n /** Image overlay a configuration panel was opened for (capability `overlays.image`). */\n targetOverlay?: string | null\n /** Host-assigned id of this window (`window` contexts; capability `windows`). */\n windowId?: string\n /** The `params` the window was opened with (`window` contexts). */\n params?: Record<string, unknown>\n}\n\nexport interface Handshake {\n host: string\n hostVersion: string\n apiVersion: string\n capabilities: string[]\n context: HandshakeContext\n}\n\n/** Payload of an `sk.<path>` Signal K value event. */\nexport interface SignalKValueEvent {\n path: string\n value: unknown\n timestamp?: string\n $source?: string\n}\n\nexport type StateScope = 'instance' | 'extension'\n\n/** Payload of a `state.changed` host event. */\nexport interface StateChangedEvent {\n scope: StateScope\n instanceId?: string | null\n keys: string[]\n}\n\n/**\n * Types for the `routes` capability — the routes the host currently has visible\n * on the chart (drafts plus stored routes the user is displaying), each\n * addressed by an opaque host-assigned `routeId`. Extensions read/write them and\n * follow lifecycle + mutation events (`route.visible` / `route.dirty` /\n * `route.saved` / `route.hidden`). Two orthogonal flags travel with a route:\n * `saved` (backed by a persisted resource) and `dirty` (has pending unsaved\n * changes). See the Plotter Extensions API spec, \"Live routes\".\n */\n\n/** A single point in a route edit buffer (`[lon, lat, alt?]`). */\nexport interface RoutePoint {\n position: [number, number, number?]\n name?: string\n description?: string\n /**\n * Link to a saved waypoint, as a Signal K resource path\n * (`/resources/waypoints/<id>`). A reference, not a copy: `position` alone\n * defines the route's geometry, and the host does not keep the point and the\n * waypoint in step — drop the link when moving the point off its waypoint. A\n * host built before this field existed drops it. See the Plotter Extensions\n * API spec, \"Waypoint links\".\n */\n href?: string\n}\n\n/** Snapshot of a route — result of `route.get`. */\nexport interface RouteData {\n routeId: string\n name: string | null\n /** Route-level description (distinct from a waypoint's RoutePoint.description). */\n description: string | null\n /** Monotonic revision; increments on every mutation. */\n rev: number\n /** Whether the route is backed by a persisted routes resource. */\n saved: boolean\n /** Whether the in-memory route has pending unsaved changes. */\n dirty: boolean\n points: RoutePoint[]\n}\n\n/** Summary entry in a `route.list` result (the visible set). */\nexport interface RouteSummary {\n routeId: string\n name: string | null\n rev: number\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.visible` host event — a route entered the visible set\n * (became rendered on the chart). A freshly drawn/created draft arrives\n * `saved:false, dirty:true`; a stored route brought into view arrives\n * `saved:true, dirty:false`.\n */\nexport interface RouteVisibleEvent {\n routeId: string\n rev: number\n name: string | null\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.hidden` host event — a route left the visible set.\n * `saved:true` ⇒ a stored route was made invisible (the resource is untouched);\n * `saved:false` ⇒ an unsaved draft was deleted (gone for good).\n */\nexport interface RouteHiddenEvent {\n routeId: string\n rev: number\n saved: boolean\n}\n\n/**\n * Payload of a `route.dirty` host event — the conformance-floor catch-all for\n * any structural/bulk change the host does not express granularly. A subscriber\n * should re-seed via `route.get`.\n */\nexport interface RouteDirtyEvent {\n routeId: string\n rev: number\n reason?: string\n}\n\n/**\n * Payload of a `route.saved` host event — the route's current state was\n * persisted to the routes resource collection. Arrives `saved:true,\n * dirty:false`; the route stays visible under the same `routeId`.\n */\nexport interface RouteSavedEvent {\n routeId: string\n rev: number\n /** Stored resource id of the persisted route. */\n href: string\n /** The route's name as persisted (may have just been set in the host's save\n * dialog), so followers can update a label without re-fetching. */\n name: string | null\n saved: boolean\n dirty: boolean\n}\n\n/** Stable `error.data.reason` strings for `route.*` host-method failures. */\nexport type RouteErrorReason =\n | 'routes.unknownId'\n | 'routes.badRequest'\n | 'routes.badRef'\n | 'routes.saveFailed'\n | 'routes.deleteFailed'\n | 'routes.saveCancelled'\n | 'routes.notSupported'\n\n/**\n * Types for the `charts` capability — a lightweight facade over the chart\n * layers the host already manages. An extension enumerates them, reads which\n * are shown and in what stacking order, and toggles visibility, opacity and\n * order. It does **not** add, create, or delete chart sources — only manages\n * charts the host already knows about. Follow changes (from any origin,\n * including the user's own chart controls) via the `chart.visibility` /\n * `chart.opacity` / `chart.order` events. With the `charts.time` sub-capability\n * a host also retargets time-varying charts (`chart.setTime`, `ChartTime`,\n * `chart.time`). See the Plotter Extensions API spec, \"Chart layers\" and\n * \"Time-varying charts\".\n */\n\n/**\n * One chart layer the host manages — an entry in a `chart.list` result. The\n * array order is the display/stacking order (index 0 = topmost/frontmost).\n */\nexport interface ChartLayer {\n /** Opaque, stable, host-assigned id. Treat as a token; never parse it. */\n id: string\n /** Human-readable chart name. */\n name: string\n /** Whether the chart is currently displayed. */\n visible: boolean\n /** Display opacity, 0..1. */\n opacity: number\n /** Best-effort source kind, e.g. 'raster' | 'vector' | 'S-57' | 'WMS'. */\n type?: string\n /**\n * Geographic extent `[west, south, east, north]`, when known. Longitudes are\n * in `[-180, 180]`; a chart that crosses the antimeridian has `west > east`.\n * See `LonLatBounds`.\n */\n bounds?: [number, number, number, number]\n /** Minimum usable zoom level, when known. */\n minZoom?: number\n /** Maximum usable zoom level, when known. */\n maxZoom?: number\n /**\n * Present only on a time-addressable chart (hosts with `charts.time`): the\n * instant currently shown and the source's timeline, as far as the host knows\n * it. Absent on a chart with no time dimension.\n */\n time?: ChartTime\n}\n\n/**\n * The time dimension of a time-varying chart (weather radar, satellite,\n * nowcast) — the `time` object on a `chart.list` entry. `value` and `current`\n * are always present; the timeline fields are best-effort metadata as the host\n * last learned them (from the chart resource or the source's capabilities),\n * not the contract's source of truth — a rolling product's newest frame moves\n * on between reads.\n */\nexport interface ChartTime {\n /** Instant currently shown (ISO 8601), or `null` for the live/current frame. */\n value: string | null\n /** Whether the source serves a live/current frame (so `null` is a valid target). */\n current: boolean\n /** Earliest instant offered (ISO 8601), when known. */\n from?: string\n /** Latest instant offered (ISO 8601), when known. */\n to?: string\n /** Milliseconds between frames, when the timeline is regular. */\n step?: number\n /** The explicit instants offered (ISO 8601), when the timeline is a list. */\n values?: string[]\n}\n\n/**\n * Params of `chart.setTime` — retarget each named time-varying chart to an\n * instant (ISO 8601) or back to its live frame (`null`). The host passes the\n * instant through to the source without snapping or clamping.\n */\nexport interface ChartSetTimeParams {\n ids: string[]\n time: string | null\n}\n\n/**\n * Payload of a `chart.time` host event — a time-varying chart was retargeted\n * (`time` is an ISO 8601 instant) or returned to live (`time: null`). Emitted\n * for every change regardless of origin; a batch `chart.setTime` emits one\n * event per changed chart.\n */\nexport interface ChartTimeEvent {\n id: string\n time: string | null\n}\n\n/**\n * Payload of a `chart.visibility` host event — one chart's display was turned\n * on or off. A batch `chart.setVisibility` emits one event per changed chart,\n * as does the user toggling a chart in the host's own chart controls.\n */\nexport interface ChartVisibilityEvent {\n id: string\n visible: boolean\n}\n\n/** Payload of a `chart.opacity` host event — one chart's opacity changed. */\nexport interface ChartOpacityEvent {\n id: string\n /** New opacity, 0..1. */\n opacity: number\n}\n\n/**\n * Payload of a `chart.order` host event — the display/stacking order changed.\n * `order` is the new full order, topmost first (the same order `chart.list`\n * returns).\n */\nexport interface ChartOrderEvent {\n order: string[]\n}\n\n/** Stable `error.data.reason` strings for `chart.*` host-method failures. */\nexport type ChartErrorReason =\n | 'charts.unknownId'\n | 'charts.badRequest'\n | 'charts.notSupported'\n | 'charts.notTemporal'\n\n/**\n * Types for the `nightMode` capability — the host's night-vision display mode\n * (a dimmed, low-blue appearance for use after dark). An extension reads the\n * current state, changes it, and follows changes so an embedded panel matches\n * the host instead of glowing white on a dark bridge. See the Plotter\n * Extensions API spec, \"Night mode\".\n */\n\n/**\n * The host's night-mode state — the `nightMode.get` result and the payload of\n * the `nightMode.changed` event.\n */\nexport interface NightModeState {\n /** Whether night mode is currently applied (the resolved state the user sees). */\n enabled: boolean\n /**\n * Whether the host is deriving `enabled` from the server's `environment.mode`\n * (`night` -> on). While `true`, `enabled` tracks the server automatically.\n */\n auto: boolean\n}\n\n/**\n * Payload of a `nightMode.changed` host event — the night-mode state changed.\n * Emitted **origin-transparently** for every change: an extension's own\n * `nightMode.set`, the user toggling the host's night-mode control, or the\n * server's `environment.mode` flipping while `auto` is on.\n */\nexport type NightModeChangedEvent = NightModeState\n\n/** Stable `error.data.reason` strings for `nightMode.*` host-method failures. */\nexport type NightModeErrorReason =\n | 'nightMode.badRequest'\n | 'nightMode.notSupported'\n\n/**\n * Types for the `map` capability — the chart viewport. An extension reads it\n * with `map.getView`, drives it with `map.center` / `map.fitBounds`, and follows\n * it with the `map.view` event. See the Plotter Extensions API spec, \"Map view\".\n */\n\n/**\n * The chart viewport — the `map.getView` result and the payload of the\n * `map.view` event, which carry the same shape.\n */\nexport interface MapView {\n /** Viewport centre as `[lon, lat]`. */\n center: [number, number]\n /** Current zoom level; may be fractional. */\n zoom: number\n /**\n * Axis-aligned lon/lat box covering what is rendered, as\n * `[west, south, east, north]`. On a host whose map can be rotated this is the\n * box containing the rotated view — \"at least this much is on screen\".\n * Longitudes are always in `[-180, 180]`, even when the chart has been panned\n * into another copy of the world; a view straddling the antimeridian has\n * `west > east`, and one showing every longitude is `-180` to `180`. See\n * `LonLatBounds`.\n */\n bounds: [number, number, number, number]\n}\n\n/**\n * Payload of a `map.view` host event — the chart viewport was panned and/or\n * zoomed. Emitted once the view has **settled** (the gesture and any kinetic\n * glide have come to rest), not continuously during the gesture, and\n * **origin-transparently**: for the user dragging the chart, the host recentring\n * on the vessel, or an extension's own `map.center` / `map.fitBounds`.\n */\nexport type MapViewEvent = MapView\n\n/**\n * Types for the `resourceGroups` capability — applying a stored resource group\n * to the host's display. Groups are ordinary Signal K resources in the `groups`\n * collection; an extension creates, edits and lists them through the server's\n * resources API. The capability adds only what the host alone can do: apply one.\n * See the Plotter Extensions API spec, \"Resource groups\".\n */\n\n/** The resource types a group can carry an instruction for. */\nexport type ResourceGroupType = 'routes' | 'waypoints' | 'regions' | 'charts'\n\n/**\n * A resource group document, as stored at `/resources/groups/{id}`. Each list\n * is an instruction for its type, with three distinct meanings:\n * - `[\"a\", …]` — display these (ids that resolve to no resource are ignored);\n * - `[]` — display none of this type;\n * - key absent — leave this type's display as it is.\n * `[]` and an absent key are **not** interchangeable.\n */\nexport interface ResourceGroup {\n name: string\n description?: string\n routes?: string[]\n waypoints?: string[]\n regions?: string[]\n charts?: string[]\n}\n\n/** Params of `resourceGroup.apply`. */\nexport interface ResourceGroupApplyParams {\n /** Id of the group in the server's `groups` collection. */\n id: string\n}\n\n/**\n * Result of `resourceGroup.apply`. `applied` lists every type for which the\n * host made a best-effort attempt to carry out the group's instruction. A type\n * the host did not act on — or whose key is absent from the group — is omitted.\n * It reports what the host **attempted**, not the resulting display.\n */\nexport interface ResourceGroupApplyResult {\n applied: ResourceGroupType[]\n}\n\n/**\n * Payload of a `resourceGroup.applied` host event — a group was applied.\n * Emitted **origin-transparently**: for an extension's `resourceGroup.apply`\n * and for the user choosing a group in the host's own UI. It reports an action,\n * not a readable state — there is no \"current group\" query.\n */\nexport interface ResourceGroupAppliedEvent extends ResourceGroupApplyResult {\n /** Id of the group that was applied. */\n id: string\n}\n\n/** Stable `error.data.reason` strings for `resourceGroup.*` host-method failures. */\nexport type ResourceGroupErrorReason =\n | 'resourceGroups.unknownId'\n | 'resourceGroups.fetchFailed'\n | 'resourceGroups.badRequest'\n | 'resourceGroups.notSupported'\n\n/**\n * Types for the `windows` capability — an extension's iframe panels shown as\n * floating windows over the chart. Each window is its own context\n * (`context.kind === 'window'`) carrying `windowId` and `params`. Geometry the\n * extension sends is a request; the host clamps it and reports the actual\n * `bounds` in the window state. See the Plotter Extensions API spec, \"Windows\".\n */\n\n/** Where a window is anchored in the host's window area. */\nexport type WindowAnchor =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'center-left'\n | 'center'\n | 'center-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right'\n\n/**\n * A length: CSS pixels as a number, or a percentage of the window area as a\n * string such as `\"40%\"`.\n */\nexport type WindowLength = number | `${number}%`\n\n/**\n * Requested size and position, relative to the host's window area. `offset` is\n * measured inward from the anchored edges (on a centered axis, positive moves\n * right/down). Fields left out take the host's choice (on open) or keep their\n * current value (on update).\n */\nexport interface WindowGeometry {\n anchor?: WindowAnchor\n offset?: { x?: WindowLength; y?: WindowLength }\n width?: WindowLength\n height?: WindowLength\n minWidth?: WindowLength\n minHeight?: WindowLength\n maxWidth?: WindowLength\n maxHeight?: WindowLength\n}\n\n/** Params of `ui.openWindow`. */\nexport interface WindowOpenParams {\n /** Id of an iframe panel in the caller's manifest. */\n panel: string\n /** Handed to the window in its handshake as `context.params`. */\n params?: Record<string, unknown>\n /** Title-bar text; defaults to the panel's `title`. */\n title?: string\n geometry?: WindowGeometry\n /** Block the chart and every other window until closed. Default `false`. */\n modal?: boolean\n /** Whether the user may resize it. Default `true`. */\n resizable?: boolean\n /** Whether the user may move it. Default `true`. */\n movable?: boolean\n /** Default `'fixed'`; `'autoHide'` hides the title bar when idle. */\n titleBar?: 'fixed' | 'autoHide'\n /** What the user's close control does. Default `'close'`. */\n userClose?: 'close' | 'hide'\n /** `false` opens the window hidden (loaded, not shown). Default `true`. */\n visible?: boolean\n /** Reuse an open window of this panel instead of opening another. */\n single?: boolean\n /** Remember this window's geometry under this key (per extension). */\n restoreKey?: string\n}\n\n/** Params of `ui.updateWindow`. `windowId` defaults to self in a window context. */\nexport interface WindowUpdateParams {\n windowId?: string\n title?: string\n geometry?: WindowGeometry\n visible?: boolean\n}\n\n/** A window's actual position and size, in CSS px from the window area's top-left. */\nexport interface WindowBounds {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** The size of the host's window area, in CSS px. */\nexport interface WindowArea {\n width: number\n height: number\n}\n\n/** How the host is showing a window. */\nexport type WindowPresentation = 'floating' | 'sheet' | 'fullscreen'\n\n/**\n * A window's state — the result of `ui.openWindow` / `ui.updateWindow`, an\n * entry of `ui.listWindows`, and the payload of `window.state`.\n */\nexport interface WindowState {\n windowId: string\n /** The manifest panel the window shows. */\n panel: string\n title: string\n presentation: WindowPresentation\n bounds: WindowBounds\n area: WindowArea\n visible: boolean\n /** Host feature: collapsed to its title bar (still running). */\n collapsed: boolean\n /** Host feature: popped out into a separate browser window. */\n poppedOut: boolean\n modal: boolean\n}\n\n/** Result of `ui.listWindows` — the caller's extension's open windows. */\nexport interface WindowListResult {\n windows: WindowState[]\n}\n\n/** Payload of a `window.bounds` host event — a window's actual geometry changed. */\nexport interface WindowBoundsEvent {\n windowId: string\n bounds: WindowBounds\n area: WindowArea\n}\n\n/** Payload of a `window.state` host event (visibility, collapse, pop-out, presentation or title changed). */\nexport type WindowStateEvent = WindowState\n\n/** Why a window closed. */\nexport type WindowCloseReason = 'user' | 'extension' | 'host'\n\n/** Payload of a `window.closed` host event — the window closed and its page unloaded. */\nexport interface WindowClosedEvent {\n windowId: string\n reason: WindowCloseReason\n}\n\n/** Stable `error.data.reason` strings for window host-method failures. */\nexport type WindowErrorReason =\n | 'windows.badRequest'\n | 'windows.unknownId'\n | 'windows.limit'\n | 'windows.modalOpen'\n | 'windows.notSupported'\n | 'UNKNOWN_PANEL'\n\n/**\n * Types for the `overlays.image` capability — map layers whose pixels an\n * extension's background runtime draws and the host composites. The host asks\n * for frames (`overlay.request`); the runtime answers with `overlay.frame`,\n * whose `image` is an `ImageBitmap` sent in the `postMessage` transfer list —\n * the one non-JSON value on the wire. See the Plotter Extensions API spec,\n * \"Map overlays\".\n */\n\n/** Where an overlay sits relative to the host's own content. */\nexport type OverlayBand = 'aboveCharts' | 'aboveResources'\n\n/** An overlay's current state — an entry of `overlay.list`. */\nexport interface OverlayInfo {\n /** The overlay's manifest id. */\n id: string\n title: string\n band: OverlayBand\n visible: boolean\n /** 0..1 */\n opacity: number\n /** The host has paused the overlay to stay within its budget. */\n suspended: boolean\n}\n\n/** Result of `overlay.list` — the caller's extension's overlays. */\nexport interface OverlayListResult {\n overlays: OverlayInfo[]\n}\n\n/**\n * Payload of an `overlay.request` host event — the host wants a frame. The\n * image covers `extent`, a north-up axis-aligned box in EPSG:3857 metres\n * `[minX, minY, maxX, maxY]` (it may run past ±20037508.34 across the\n * antimeridian), with pixels spaced evenly in Mercator; row 0 is the north\n * edge.\n */\nexport interface OverlayRequestEvent {\n overlayId: string\n requestId: string\n extent: [number, number, number, number]\n /** The same box as lon/lat `[west, south, east, north]` (west > east across the antimeridian). */\n bounds: [number, number, number, number]\n /** Image size in pixels. */\n width: number\n height: number\n /** Image pixels per CSS pixel, chosen by the host. */\n pixelRatio: number\n /** Metres per image pixel. */\n resolution: number\n zoom: number\n /** Map rotation, radians clockwise. Advisory: for keeping labels upright only. */\n rotation: number\n /**\n * Least time, in ms, the host leaves between live frames it accepts. Absent\n * when the host does not say; a faster live frame is refused as\n * `overlays.throttled`.\n */\n liveInterval?: number\n}\n\n/** Params of `overlay.frame`. */\nexport interface OverlayFrameParams {\n overlayId: string\n /** The request this frame answers; omit for a live update of the latest request's extent. */\n requestId?: string\n extent: [number, number, number, number]\n /** Transparent, premultiplied-alpha image of the requested size; transferred. */\n image: ImageBitmap\n}\n\n/** Why the host refused a frame (a result, not an error). */\nexport type OverlayFrameRefusal = 'overlays.superseded' | 'overlays.throttled'\n\n/** Result of `overlay.frame`. */\nexport interface OverlayFrameResult {\n accepted: boolean\n reason?: OverlayFrameRefusal\n}\n\n/** Payload of an `overlay.state` host event — an overlay was shown, hidden, faded, suspended or resumed. */\nexport interface OverlayStateEvent {\n overlayId: string\n visible: boolean\n opacity: number\n suspended: boolean\n}\n\n/** Stable `error.data.reason` strings for `overlay.*` host-method failures. */\nexport type OverlayErrorReason =\n | 'overlays.unknownId'\n | 'overlays.badRequest'\n | 'overlays.notSupported'\n\n/**\n * Types for the `panels.state` capability — whether each of an extension's\n * loaded panels is on screen. `visible` is the host's presentation (its drawer\n * showing the panel, its dialog open), not the browser tab's visibility. A\n * panel shown in a window is a `window` context and is not covered here. See\n * the Plotter Extensions API spec, \"Panel state\".\n */\n\n/** A loaded panel's state — an entry of `ui.listPanels` and the payload of `panel.state`. */\nexport interface PanelState {\n /** The panel's manifest id. */\n panel: string\n /** Whether the host is presenting the panel in its UI. */\n visible: boolean\n /** Host feature: shown reduced to its header (still `visible`). Always `false` without it. */\n collapsed: boolean\n /** Configuration panels opened for a widget instance: the instance being configured. */\n targetInstance?: string\n /** Configuration panels opened for an image overlay: the overlay being configured. */\n targetOverlay?: string\n}\n\n/** Result of `ui.listPanels` — the caller's extension's loaded panels. */\nexport interface PanelListResult {\n panels: PanelState[]\n}\n\n/** Payload of a `panel.state` host event — a loaded panel was shown, hidden, collapsed or expanded. */\nexport type PanelStateEvent = PanelState\n\n/** Stable `error.data.reason` strings for `ui.listPanels` failures. */\nexport type PanelErrorReason = 'panels.notSupported'\n\n/**\n * Types for the `events.publish` capability — extension-originated events. A\n * context publishes a topic and the host delivers it, exactly like a host\n * event, to every subscribed context in `scope` (the publisher included). The\n * delivered event carries no sender identity. The `publish` button action\n * (deprecated alias `sendMessage`) is the same operation. See the Plotter\n * Extensions API spec, \"Publishing events\".\n */\n\n/** Who may receive a published event. */\nexport type PublishScope = 'all' | 'extension'\n\n/** Params of `events.publish`. */\nexport interface PublishParams {\n /** Literal event name: non-empty, no `*`, not in the `bus.*` namespace. */\n topic: string\n /** Delivered unchanged as the event's `params`. */\n params?: unknown\n /** Default `'all'`. */\n scope?: PublishScope\n}\n\n/** Stable `error.data.reason` strings for `events.publish` failures. */\nexport type PublishErrorReason = 'events.badRequest'\n", "/**\n * eventemitter2-style wildcard matching for dot-separated event names.\n *\n * - `*` matches exactly one segment.\n * - `**` matches zero or more segments (any remainder when trailing).\n */\nexport function matchesPattern(pattern: string, name: string): boolean {\n if (pattern === name) return true\n return match(pattern.split('.'), 0, name.split('.'), 0)\n}\n\nfunction match(p: string[], pi: number, n: string[], ni: number): boolean {\n while (pi < p.length) {\n const seg = p[pi]\n if (seg === '**') {\n if (pi === p.length - 1) return true\n for (let skip = ni; skip <= n.length; skip++) {\n if (match(p, pi + 1, n, skip)) return true\n }\n return false\n }\n if (ni >= n.length) return false\n if (seg !== '*' && seg !== n[ni]) return false\n pi++\n ni++\n }\n return ni === n.length\n}\n\nexport function matchesAny(patterns: Iterable<string>, name: string): boolean {\n for (const pattern of patterns) {\n if (matchesPattern(pattern, name)) return true\n }\n return false\n}\n", "/**\n * Lon/lat bounding boxes, as every box in the Plotter Extensions API is written:\n * `[west, south, east, north]` in decimal degrees (the GeoJSON / RFC 7946 §5\n * order), with longitudes in `[-180, 180]`. A box that crosses the antimeridian\n * has `west` greater than `east` (RFC 7946 §5.2): `[175, -21, -175, -13]` is ten\n * degrees wide around 180°. See the spec, \"Bounding boxes\".\n *\n * Map engines work in the *unwrapped* form instead — `west < east`, with\n * longitudes running past ±180 once the view scrolls into another copy of the\n * world. {@link normalizeBounds} converts either form to the API's.\n */\nexport type LonLatBounds = [number, number, number, number]\n\n/** `lon` wrapped into `[-180, 180]`; a longitude already in range is kept. */\nfunction wrapLon(lon: number): number {\n if (lon >= -180 && lon <= 180) {\n return lon\n }\n return ((((lon + 180) % 360) + 360) % 360) - 180\n}\n\n/**\n * Degrees of longitude a box in the API's form spans, 0–360: `east - west`, or\n * the way round the antimeridian when `west > east`. A box from `-180` to `180`\n * spans 360.\n */\nexport function boundsLonSpan(bounds: LonLatBounds): number {\n const [west, , east] = bounds\n return east >= west ? east - west : east - west + 360\n}\n\n/** Whether a box in the API's form takes in longitude `lon`. */\nexport function boundsContainsLon(bounds: LonLatBounds, lon: number): boolean {\n const [west, , east] = bounds\n const l = wrapLon(lon)\n return west <= east ? l >= west && l <= east : l >= west || l <= east\n}\n\n/**\n * A box in the API's form, from either that form or the unwrapped form map\n * engines use (`east` past 180 or `west` past -180, with `west < east`).\n * Longitudes are wrapped into `[-180, 180]`, so a box straddling 180° comes out\n * with `west > east`; a box 360 or more degrees wide becomes\n * `[-180, south, 180, north]`. Undefined when it is not a box: not four finite\n * numbers, a latitude outside `[-90, 90]`, or `south` greater than `north`.\n */\nexport function normalizeBounds(bounds: unknown): LonLatBounds | undefined {\n if (\n !Array.isArray(bounds) ||\n bounds.length !== 4 ||\n !bounds.every((v) => typeof v === 'number' && Number.isFinite(v))\n ) {\n return undefined\n }\n const [west, south, east, north] = bounds as LonLatBounds\n if (south < -90 || north > 90 || south > north) {\n return undefined\n }\n const w = wrapLon(west)\n const e = wrapLon(east)\n const span = west <= east ? east - west : boundsLonSpan([w, south, e, north])\n if (span >= 360) {\n return [-180, south, 180, north]\n }\n return [w, south, e, north]\n}\n", "import {\n BUS_ID,\n Envelope,\n JsonRpcMessage,\n JsonRpcNotification,\n JsonRpcRequest,\n JsonRpcResponse\n} from './protocol'\n\nexport function wrap(msg: JsonRpcMessage): Envelope {\n return { bus: BUS_ID, msg }\n}\n\n/**\n * Returns the JSON-RPC message inside a bus envelope, or null when the data\n * is not valid protocol traffic (so unrelated postMessage noise is ignored).\n */\nexport function unwrap(data: unknown): JsonRpcMessage | null {\n if (typeof data !== 'object' || data === null) return null\n const env = data as Record<string, unknown>\n if (env.bus !== BUS_ID) return null\n return isJsonRpcMessage(env.msg) ? env.msg : null\n}\n\nexport function isJsonRpcMessage(v: unknown): v is JsonRpcMessage {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n if (m.jsonrpc !== '2.0') return false\n if (typeof m.method === 'string') {\n // Request (with id) or notification (without).\n return (\n m.id === undefined ||\n typeof m.id === 'string' ||\n typeof m.id === 'number'\n )\n }\n // Response: id required (null allowed for unroutable errors), exactly one\n // of result / error.\n const idOk =\n typeof m.id === 'string' || typeof m.id === 'number' || m.id === null\n if (!idOk) return false\n const hasResult = 'result' in m\n const err = m.error as Record<string, unknown> | undefined\n const hasError =\n typeof err === 'object' &&\n err !== null &&\n typeof err.code === 'number' &&\n typeof err.message === 'string'\n return hasResult ? !('error' in m) : hasError\n}\n\nexport function isRequest(msg: JsonRpcMessage): msg is JsonRpcRequest {\n return 'method' in msg && 'id' in msg && msg.id !== undefined\n}\n\nexport function isNotification(\n msg: JsonRpcMessage\n): msg is JsonRpcNotification {\n return 'method' in msg && (!('id' in msg) || msg.id === undefined)\n}\n\nexport function isResponse(msg: JsonRpcMessage): msg is JsonRpcResponse {\n return !('method' in msg)\n}\n", "/**\n * Transport abstraction. The bus runs over anything that can post and\n * receive structured-cloneable data: window.postMessage between a host page\n * and an iframe, a MessageChannel port, or a test double.\n */\nexport interface BusPort {\n /**\n * Send `data`. `transfer` lists objects (an `ImageBitmap`, an `ArrayBuffer`)\n * to move rather than copy; after the post the sender no longer owns them.\n */\n post(data: unknown, transfer?: Transferable[]): void\n /** Register a receive handler; returns an unlisten function. */\n listen(handler: (data: unknown) => void): () => void\n}\n\nexport interface WindowPortOptions {\n /**\n * targetOrigin for outgoing postMessage and required origin of incoming\n * messages. Defaults to the listening window's own origin (the baseline\n * deployment is same-origin). Pass '*' to disable origin checks.\n */\n origin?: string\n /** Window whose 'message' events are listened to. Defaults to globalThis. */\n listenWindow?: Window\n}\n\n/**\n * A port over window.postMessage to a peer window. On the host side the peer\n * is an iframe's contentWindow; on the extension side it is window.parent.\n * Only messages whose source is the peer (and whose origin matches) are\n * delivered.\n */\nexport function windowPort(peer: Window, opts: WindowPortOptions = {}): BusPort {\n const listenWindow =\n opts.listenWindow ?? (globalThis as unknown as Window)\n const origin = opts.origin ?? listenWindow.location?.origin ?? '*'\n return {\n post(data, transfer) {\n if (transfer?.length) {\n peer.postMessage(data, { targetOrigin: origin, transfer })\n } else {\n peer.postMessage(data, origin)\n }\n },\n listen(handler) {\n const fn = (ev: MessageEvent) => {\n if (ev.source !== peer) return\n if (origin !== '*' && ev.origin !== origin) return\n handler(ev.data)\n }\n listenWindow.addEventListener('message', fn as EventListener)\n return () =>\n listenWindow.removeEventListener('message', fn as EventListener)\n }\n }\n}\n\ninterface MessagePortLike {\n postMessage(data: unknown, transfer?: Transferable[]): void\n addEventListener(type: 'message', fn: (ev: { data: unknown }) => void): void\n removeEventListener(\n type: 'message',\n fn: (ev: { data: unknown }) => void\n ): void\n start?(): void\n}\n\n/** A port over a MessageChannel/MessagePort (browser or Node). */\nexport function messagePort(port: MessagePortLike): BusPort {\n return {\n post(data, transfer) {\n if (transfer?.length) {\n port.postMessage(data, transfer)\n } else {\n port.postMessage(data)\n }\n },\n listen(handler) {\n const fn = (ev: { data: unknown }) => handler(ev.data)\n port.addEventListener('message', fn)\n port.start?.()\n return () => port.removeEventListener('message', fn)\n }\n }\n}\n", "import {\n isNotification,\n isRequest,\n isResponse,\n unwrap,\n wrap\n} from './codec'\nimport {\n JsonRpcId,\n JsonRpcMessage,\n JsonRpcResponse,\n RPC_ERRORS,\n RpcError\n} from './protocol'\nimport { BusPort } from './port'\nimport { matchesAny } from './wildcard'\n\nexport interface MethodContext {\n endpoint: BusEndpoint\n}\n\nexport type MethodHandler = (\n params: unknown,\n ctx: MethodContext\n) => unknown | Promise<unknown>\n\nexport type EventHandler = (name: string, params: unknown) => void\n\nexport interface BusEndpointOptions {\n port: BusPort\n /** Default timeout for outgoing calls. */\n callTimeoutMs?: number\n /** Reporter for handler/dispatch errors. Defaults to console.warn. */\n onError?: (err: unknown) => void\n}\n\ninterface PendingCall {\n resolve: (value: unknown) => void\n reject: (err: RpcError) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000\n\n/**\n * One end of the bus: sends/receives envelopes over a BusPort, dispatches\n * incoming requests to registered methods, correlates responses to pending\n * calls by per-call nonce, and fans incoming notifications out to wildcard\n * event handlers. Symmetric — both host and extension build on this.\n */\nexport class BusEndpoint {\n readonly callTimeoutMs: number\n\n private readonly port: BusPort\n private readonly unlisten: () => void\n private readonly onError: (err: unknown) => void\n private readonly pending = new Map<JsonRpcId, PendingCall>()\n private readonly methods = new Map<string, MethodHandler>()\n private readonly eventHandlers = new Set<{\n patterns: string[]\n fn: EventHandler\n }>()\n private readonly idPrefix = Math.random().toString(36).slice(2, 8)\n private seq = 0\n private closed = false\n\n constructor(opts: BusEndpointOptions) {\n this.port = opts.port\n this.callTimeoutMs = opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS\n this.onError =\n opts.onError ??\n ((err) => console.warn('[plotterext-bus]', err))\n this.unlisten = this.port.listen((data) => this.onData(data))\n }\n\n registerMethod(name: string, handler: MethodHandler): void {\n this.methods.set(name, handler)\n }\n\n unregisterMethod(name: string): void {\n this.methods.delete(name)\n }\n\n /**\n * Handle incoming notifications whose names match any of the wildcard\n * patterns. Returns an unsubscribe function. This is local dispatch only;\n * telling the peer which events to forward is a separate concern\n * (`events.subscribe`).\n */\n onEvent(patterns: string[], fn: EventHandler): () => void {\n const entry = { patterns, fn }\n this.eventHandlers.add(entry)\n return () => this.eventHandlers.delete(entry)\n }\n\n /**\n * Send a notification (an event) to the peer. `opts.transfer` moves the\n * listed objects instead of copying them.\n */\n notify(\n method: string,\n params?: unknown,\n opts: { transfer?: Transferable[] } = {}\n ): void {\n this.send(\n { jsonrpc: '2.0', method, ...(params !== undefined ? { params } : {}) },\n opts.transfer\n )\n }\n\n /**\n * Call a method on the peer; resolves with its result. `opts.transfer`\n * moves the listed objects (e.g. an `ImageBitmap` inside `params`) instead\n * of copying them.\n */\n call(\n method: string,\n params?: unknown,\n opts: { timeoutMs?: number; transfer?: Transferable[] } = {}\n ): Promise<unknown> {\n if (this.closed) {\n return Promise.reject(\n new RpcError('Bus endpoint is closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n const id = `${this.idPrefix}-${++this.seq}`\n const timeoutMs = opts.timeoutMs ?? this.callTimeoutMs\n return new Promise<unknown>((resolve, reject) => {\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n this.pending.delete(id)\n reject(\n new RpcError(`Call timed out after ${timeoutMs}ms: ${method}`, {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'TIMEOUT'\n })\n )\n }, timeoutMs)\n : null\n this.pending.set(id, { resolve, reject, timer })\n this.send(\n {\n jsonrpc: '2.0',\n id,\n method,\n ...(params !== undefined ? { params } : {})\n },\n opts.transfer\n )\n })\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.unlisten()\n for (const [, p] of this.pending) {\n if (p.timer) clearTimeout(p.timer)\n p.reject(\n new RpcError('Bus endpoint closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n this.pending.clear()\n this.eventHandlers.clear()\n }\n\n private send(msg: JsonRpcMessage, transfer?: Transferable[]): void {\n if (this.closed) return\n this.port.post(wrap(msg), transfer)\n }\n\n private onData(data: unknown): void {\n const msg = unwrap(data)\n if (!msg) return\n if (isResponse(msg)) {\n this.onResponse(msg)\n } else if (isRequest(msg)) {\n void this.onRequest(msg)\n } else if (isNotification(msg)) {\n this.onNotification(msg.method, msg.params)\n }\n }\n\n private onResponse(msg: JsonRpcResponse): void {\n if (msg.id === null) return\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (p.timer) clearTimeout(p.timer)\n if ('error' in msg) {\n p.reject(RpcError.fromErrorObject(msg.error))\n } else {\n p.resolve(msg.result)\n }\n }\n\n private async onRequest(msg: {\n id: JsonRpcId\n method: string\n params?: unknown\n }): Promise<void> {\n const handler = this.methods.get(msg.method)\n if (!handler) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: {\n code: RPC_ERRORS.METHOD_NOT_FOUND,\n message: `Method not found: ${msg.method}`\n }\n })\n return\n }\n try {\n const result = await handler(msg.params, { endpoint: this })\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n result: result === undefined ? null : result\n })\n } catch (err) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: RpcError.from(err).toErrorObject()\n })\n }\n }\n\n private onNotification(name: string, params: unknown): void {\n for (const entry of [...this.eventHandlers]) {\n if (matchesAny(entry.patterns, name)) {\n try {\n entry.fn(name, params)\n } catch (err) {\n this.onError(err)\n }\n }\n }\n }\n}\n", "import { BusEndpoint, EventHandler } from './endpoint'\nimport { BusPort, windowPort } from './port'\nimport {\n ChartLayer,\n EVENT_HANDSHAKE,\n EVENT_READY,\n Handshake,\n HandshakeContext,\n MapView,\n NightModeState,\n OverlayFrameParams,\n OverlayFrameResult,\n OverlayInfo,\n OverlayListResult,\n PanelListResult,\n PanelState,\n PublishScope,\n ReadyParams,\n ResourceGroupApplyResult,\n RouteData,\n RoutePoint,\n RouteSummary,\n RPC_ERRORS,\n RpcError,\n SignalKValueEvent,\n StateScope,\n WindowListResult,\n WindowOpenParams,\n WindowState,\n WindowUpdateParams\n} from './protocol'\n\nexport interface ConnectOptions {\n /** Transport. Defaults to window.postMessage to window.parent. */\n port?: BusPort\n /**\n * Caller-asserted context id, sent in the `bus.ready` payload. A host that\n * adopts caller ids (an embedding-host connection) uses it as `context.id`;\n * standard extension hosts ignore it. Omit for the standard extension case.\n */\n id?: string\n /** Interval for re-sending bus.ready until the handshake arrives. */\n readyIntervalMs?: number\n /** How long to wait for the host handshake before rejecting. */\n timeoutMs?: number\n /** Default timeout for host API calls. */\n callTimeoutMs?: number\n onError?: (err: unknown) => void\n}\n\nexport type Unsubscribe = () => Promise<void>\n\n/**\n * The extension side of the bus: one per iframe context (panel, widget, or\n * background runtime). Create via connectExtension().\n */\nexport class ExtensionClient {\n readonly handshake: Handshake\n readonly endpoint: BusEndpoint\n\n constructor(endpoint: BusEndpoint, handshake: Handshake) {\n this.endpoint = endpoint\n this.handshake = handshake\n }\n\n get context(): HandshakeContext {\n return this.handshake.context\n }\n\n get apiVersion(): string {\n return this.handshake.apiVersion\n }\n\n get capabilities(): string[] {\n return this.handshake.capabilities\n }\n\n hasCapability(id: string): boolean {\n return this.handshake.capabilities.includes(id)\n }\n\n /**\n * Call a host API method. `opts.transfer` moves the listed objects instead\n * of copying them — only `overlay.frame` needs it in this version.\n */\n call(\n method: string,\n params?: unknown,\n opts?: { timeoutMs?: number; transfer?: Transferable[] }\n ): Promise<unknown> {\n return this.endpoint.call(method, params, opts)\n }\n\n /** Send a notification to the host. */\n notify(method: string, params?: unknown): void {\n this.endpoint.notify(method, params)\n }\n\n /**\n * Subscribe to host events matching wildcard patterns. Registers both the\n * host-side forwarding subscription and local dispatch; the returned\n * function tears down both.\n */\n async subscribe(\n patterns: string[],\n handler: EventHandler\n ): Promise<Unsubscribe> {\n const off = this.endpoint.onEvent(patterns, handler)\n let subscriptionId: string\n try {\n const result = (await this.call('events.subscribe', { patterns })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n off()\n throw err\n }\n return async () => {\n off()\n await this.call('events.unsubscribe', { subscriptionId }).catch(() => {\n // Best-effort: the host may already have dropped the connection.\n })\n }\n }\n\n /**\n * Publish an event onto the bus (capability `events.publish`). The host\n * delivers it to every context in `scope` whose subscriptions match —\n * including this one — exactly as it delivers host events. `scope` defaults\n * to `'all'` (any extension); `'extension'` keeps it to this extension's own\n * contexts. Resolves once the host has delivered it; there is no reply.\n */\n async publish(\n topic: string,\n params?: unknown,\n scope?: PublishScope\n ): Promise<void> {\n await this.call('events.publish', {\n topic,\n ...(params !== undefined ? { params } : {}),\n ...(scope ? { scope } : {})\n })\n }\n\n /** Host-persisted key/value state (see spec: State Storage). */\n readonly state = {\n get: async (\n keys?: string[],\n scope?: StateScope\n ): Promise<Record<string, unknown>> => {\n const result = (await this.call('state.get', {\n ...(scope ? { scope } : {}),\n ...(keys ? { keys } : {})\n })) as { values: Record<string, unknown> }\n return result.values ?? {}\n },\n set: async (\n values: Record<string, unknown>,\n scope?: StateScope\n ): Promise<void> => {\n await this.call('state.set', {\n ...(scope ? { scope } : {}),\n values\n })\n }\n }\n\n /** Signal K data relayed by the host (capabilities signalk.stream / .put). */\n readonly signalk = {\n /**\n * Subscribe to Signal K path values. The host publishes them as\n * `sk.<path>` events; this helper hides the event-name mapping and\n * establishes both the event-forwarding subscription and the host's\n * upstream Signal K subscription.\n */\n subscribe: async (\n paths: string[],\n handler: (ev: SignalKValueEvent) => void\n ): Promise<Unsubscribe> => {\n const patterns = paths.map((p) => `sk.${p}`)\n const offEvents = await this.subscribe(patterns, (_name, params) =>\n handler(params as SignalKValueEvent)\n )\n let subscriptionId: string\n try {\n const result = (await this.call('signalk.subscribe', { paths })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n await offEvents()\n throw err\n }\n return async () => {\n await offEvents()\n await this.call('signalk.unsubscribe', { subscriptionId }).catch(\n () => {}\n )\n }\n },\n put: (path: string, value: unknown): Promise<unknown> => {\n return this.call('signalk.put', { path, value })\n }\n }\n\n /**\n * The host's visible routes (capability `routes`). Thin **typed** wrappers over\n * the host's `route.*` methods — each just delegates to `this.call(...)`, so a\n * plain-JS extension can call `client.call('route.replace', …)` directly and a\n * TypeScript extension gets the typed `client.route.replace(…)` sugar with no\n * behavioural difference. Follow lifecycle + mutations by subscribing to\n * `route.**` events (`RouteVisibleEvent` / `RouteDirtyEvent` / `RouteSavedEvent`\n * / `RouteHiddenEvent`). Further operations (rename/point ops) extend this\n * surface as the capability fills out.\n */\n readonly route = {\n list: async (): Promise<RouteSummary[]> => {\n const result = (await this.call('route.list')) as {\n routes?: RouteSummary[]\n }\n return result.routes ?? []\n },\n create: async (opts: {\n points: RoutePoint[]\n name?: string\n description?: string\n }): Promise<{ routeId: string; rev: number }> => {\n // A route needs at least two points to form a segment; the host rejects\n // fewer with routes.badRequest.\n return (await this.call('route.create', opts)) as {\n routeId: string\n rev: number\n }\n },\n get: async (routeId: string): Promise<RouteData> => {\n return (await this.call('route.get', { routeId })) as RouteData\n },\n replace: async (\n routeId: string,\n points: RoutePoint[]\n ): Promise<{ rev: number }> => {\n return (await this.call('route.replace', { routeId, points })) as {\n rev: number\n }\n },\n save: async (\n routeId: string,\n opts?: { name?: string; description?: string; dialog?: boolean }\n ): Promise<{ href: string; rev: number }> => {\n return (await this.call('route.save', {\n routeId,\n ...(opts ?? {})\n })) as { href: string; rev: number }\n },\n show: async (ref: string): Promise<{ routeId: string; rev: number }> => {\n return (await this.call('route.show', { ref })) as {\n routeId: string\n rev: number\n }\n },\n hide: async (routeId: string): Promise<void> => {\n await this.call('route.hide', { routeId })\n },\n delete: async (routeId: string): Promise<void> => {\n await this.call('route.delete', { routeId })\n }\n }\n\n /**\n * The host's chart layers (capability `charts`). Thin **typed** wrappers over\n * the host's `chart.*` methods — a lightweight facade over the charts the host\n * already manages: enumerate them, toggle visibility, opacity and stacking\n * order (all batch). It does not create, add, or delete chart sources. Follow\n * changes (from any origin, including the host's own chart controls) by\n * subscribing to `chart.**` events (`ChartVisibilityEvent` / `ChartOpacityEvent`\n * / `ChartOrderEvent` / `ChartTimeEvent`) and re-reading `chart.list` where\n * needed. As with every wrapper, a plain-JS extension can call\n * `client.call('chart.list', …)` directly with no behavioural difference.\n */\n readonly chart = {\n list: async (): Promise<ChartLayer[]> => {\n const result = (await this.call('chart.list')) as { charts?: ChartLayer[] }\n return result.charts ?? []\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('chart.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('chart.setOpacity', { ids, opacity })\n },\n setOrder: async (order: string[]): Promise<void> => {\n await this.call('chart.setOrder', { order })\n },\n /**\n * Retarget time-varying charts to an ISO 8601 instant, or back to their\n * live frame with `null` (sub-capability `charts.time`). Follow changes via\n * the `chart.time` event (`ChartTimeEvent`).\n */\n setTime: async (ids: string[], time: string | null): Promise<void> => {\n await this.call('chart.setTime', { ids, time })\n }\n }\n\n /**\n * The host's night-vision display mode (capability `nightMode`). Read the\n * current `{ enabled, auto }` state, change it, and follow changes by\n * subscribing to the `nightMode.changed` event (`NightModeChangedEvent`).\n * `set` expresses the three states the spec defines: force on\n * (`{ enabled: true }`), force off (`{ enabled: false }`), follow the server\n * (`{ auto: true }`); setting `enabled` implies `auto: false`. As with every\n * wrapper, a plain-JS extension can call `client.call('nightMode.get' |\n * 'nightMode.set', …)` directly with no behavioural difference.\n */\n readonly nightMode = {\n get: async (): Promise<NightModeState> => {\n return (await this.call('nightMode.get')) as NightModeState\n },\n set: async (state: Partial<NightModeState>): Promise<void> => {\n await this.call('nightMode.set', state)\n }\n }\n\n /**\n * Panel visibility (capability `panels.state`): the state of each of this\n * extension's loaded panels. Follow changes with the `panel.state` event,\n * which every context of the extension receives; subscribe first, then\n * `list()`, so no change is missed. A plain-JS extension can call\n * `client.call('ui.listPanels')` with no behavioural difference.\n */\n readonly panels = {\n list: async (): Promise<PanelState[]> => {\n const r = (await this.call('ui.listPanels')) as PanelListResult\n return r.panels\n }\n }\n\n /**\n * Image map overlays (capability `overlays.image`): map layers this\n * extension's background runtime draws. The host asks for frames with the\n * `overlay.request` event (`OverlayRequestEvent`) and reports visibility\n * with `overlay.state` (`OverlayStateEvent`); the runtime answers each\n * request with `frame()`, keeping one frame in flight. `frame()` transfers\n * `image`, so the caller no longer owns it afterwards. A plain-JS extension\n * can call `client.call('overlay.frame', params, { transfer: [image] })`\n * with no behavioural difference.\n */\n readonly overlays = {\n list: async (): Promise<OverlayInfo[]> => {\n const r = (await this.call('overlay.list')) as OverlayListResult\n return r.overlays\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('overlay.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('overlay.setOpacity', { ids, opacity })\n },\n frame: async (params: OverlayFrameParams): Promise<OverlayFrameResult> => {\n return (await this.call('overlay.frame', params, {\n transfer: [params.image]\n })) as OverlayFrameResult\n }\n }\n\n /**\n * Floating windows (capability `windows`): show one of this extension's\n * iframe panels in a window over the chart. Geometry is a request — read the\n * actual `bounds` from the returned `WindowState`, and follow changes with\n * the `window.bounds` / `window.state` / `window.closed` events. Windows\n * belong to the extension, so any of its contexts may manage them; in a\n * `window` context, `update` / `focus` / `close` default to the window\n * itself when `windowId` is left out. A plain-JS extension can call\n * `client.call('ui.openWindow', …)` and friends directly with no\n * behavioural difference.\n */\n readonly windows = {\n open: async (params: WindowOpenParams): Promise<WindowState> => {\n return (await this.call('ui.openWindow', params)) as WindowState\n },\n update: async (params: WindowUpdateParams): Promise<WindowState> => {\n return (await this.call('ui.updateWindow', params)) as WindowState\n },\n focus: async (windowId?: string): Promise<void> => {\n await this.call('ui.focusWindow', windowId === undefined ? {} : { windowId })\n },\n close: async (windowId?: string): Promise<void> => {\n await this.call('ui.closeWindow', windowId === undefined ? {} : { windowId })\n },\n list: async (): Promise<WindowState[]> => {\n const r = (await this.call('ui.listWindows')) as WindowListResult\n return r.windows\n }\n }\n\n /**\n * Apply a stored resource group to the host's display (capability\n * `resourceGroups`). Resolves to `{ applied }` — the types the host made a\n * best-effort attempt to carry out; a type it did not act on is omitted.\n * Follow applies from any origin (including the user's own group picker) via\n * the `resourceGroup.applied` event (`ResourceGroupAppliedEvent`). Group CRUD\n * is not here: use the server's resources API (`/resources/groups`). A\n * plain-JS extension can call `client.call('resourceGroup.apply', { id })`\n * directly with no behavioural difference.\n */\n readonly resourceGroup = {\n apply: async (id: string): Promise<ResourceGroupApplyResult> => {\n return (await this.call('resourceGroup.apply', {\n id\n })) as ResourceGroupApplyResult\n }\n }\n\n /**\n * The host's chart viewport (capability `map`). Read the current\n * `{ center, zoom, bounds }`, drive it, and follow it by subscribing to the\n * `map.view` event (`MapViewEvent`), which carries the same shape and fires\n * once per settled pan/zoom. Seed with `getView` so a follower has a view\n * before the first change. As with every wrapper, a plain-JS extension can\n * call `client.call('map.getView' | 'map.center' | 'map.fitBounds', …)`\n * directly with no behavioural difference.\n */\n readonly map = {\n getView: async (): Promise<MapView> => {\n return (await this.call('map.getView')) as MapView\n },\n center: async (\n position: [number, number],\n zoom?: number\n ): Promise<void> => {\n await this.call('map.center', {\n position,\n ...(typeof zoom === 'number' ? { zoom } : {})\n })\n },\n /**\n * Fit the chart to `[west, south, east, north]`. A box crossing the\n * antimeridian may be given as `west > east` (the API's form) or unwrapped\n * (`east` past 180); the host fits it the short way round.\n */\n fitBounds: async (\n bounds: [number, number, number, number]\n ): Promise<void> => {\n await this.call('map.fitBounds', { bounds })\n }\n }\n\n close(): void {\n this.endpoint.close()\n }\n}\n\n/**\n * Connect to the host from inside an extension iframe. Sends `bus.ready`\n * (repeating until answered) and resolves once the host's `bus.handshake`\n * arrives.\n */\nexport function connectExtension(\n opts: ConnectOptions = {}\n): Promise<ExtensionClient> {\n // Default transport: postMessage to the embedding window. Origin checks\n // are relaxed to '*' on the extension side because the host page's origin\n // may legitimately differ from the extension asset origin (e.g. a host dev\n // server embedding extension assets served by the Signal K server); the\n // peer-source check in windowPort still applies, and the host side\n // enforces a strict origin for the extension's frame.\n const port =\n opts.port ??\n windowPort((globalThis as unknown as Window).parent as Window, {\n origin: '*'\n })\n const endpoint = new BusEndpoint({\n port,\n callTimeoutMs: opts.callTimeoutMs,\n onError: opts.onError\n })\n const readyParams: ReadyParams | undefined = opts.id ? { id: opts.id } : undefined\n return new Promise<ExtensionClient>((resolve, reject) => {\n let done = false\n const off = endpoint.onEvent([EVENT_HANDSHAKE], (_name, params) => {\n if (done) return\n done = true\n cleanup()\n resolve(new ExtensionClient(endpoint, params as Handshake))\n })\n const interval = setInterval(\n () => endpoint.notify(EVENT_READY, readyParams),\n opts.readyIntervalMs ?? 250\n )\n const timeout = setTimeout(() => {\n if (done) return\n done = true\n cleanup()\n endpoint.close()\n reject(\n new RpcError('Timed out waiting for host handshake', {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'HANDSHAKE_TIMEOUT'\n })\n )\n }, opts.timeoutMs ?? 10_000)\n const cleanup = () => {\n off()\n clearInterval(interval)\n clearTimeout(timeout)\n }\n endpoint.notify(EVENT_READY, readyParams)\n })\n}\n\nexport * from './protocol'\nexport * from './wildcard'\nexport * from './bounds'\nexport { BusEndpoint } from './endpoint'\nexport type { MethodHandler, MethodContext, EventHandler } from './endpoint'\nexport * from './port'\n", "/** Live depth layer: pure helpers turning depth-at-position samples (v2 Track\n * API `properties`) into a safe, drawable aggregate.\n *\n * The overriding rule is safety: a rendered mark must never display deeper than\n * the shallowest sample it covers. Every aggregate therefore takes the\n * MINIMUM depth \u2014 never a mean, which would let a shoal vanish into deeper\n * water \u2014 and the layer draws shallower marks over deeper ones. Samples at or\n * below a shoal threshold are returned individually so a critical minimum is\n * never smeared to a cell centre.\n *\n * Pure functions (no Angular, no map) so they can be unit tested directly.\n */\n\n/** A `[lon, lat]` position. */\nexport type Position = [number, number];\n\n/** A URL query string from the given params, skipping absent or empty ones. */\nfunction queryString(\n params: Record<string, string | number | undefined>,\n): string {\n return Object.entries(params)\n .filter(([, v]) => v !== undefined && v !== \"\")\n .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n}\n\n/** A recorded depth at a position, depth in metres (canonical Signal K). */\nexport interface DepthSample {\n lon: number;\n lat: number;\n depth: number;\n}\n\n/** One grid cell of the aggregate. */\nexport interface DepthCell {\n /** Grid indices (Web Mercator cell coordinates), for neighbour lookup. */\n ix: number;\n iy: number;\n /** Cell centre (mean of the samples in it), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth among the cell's samples, in metres. */\n depth: number;\n /** How many samples fell in the cell. */\n count: number;\n}\n\n/** One drawn circle: a group of adjacent cells at a similar depth, merged. */\nexport interface DepthCluster {\n /** Circle centre (weighted centroid), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth in the group, in metres. */\n depth: number;\n /** Samples the group covers. */\n count: number;\n /** Spatial radius of the group, in metres (Web Mercator). */\n radiusMetres: number;\n}\n\n/** Depth (metres) the shading runs to by default: at this depth and beyond the\n * layer draws nothing, so the chart shows through. */\nexport const DEFAULT_SHADE_LIMIT_M = 10;\n\n/** Default shoal threshold (metres): samples at or below this are marked\n * individually. Matches the S-57 renderer's default safety depth. */\nexport const DEFAULT_SAFETY_DEPTH_M = 3;\n\nconst EARTH_RADIUS = 6378137;\nconst MERCATOR_MAX_LAT = 85.05112878;\nconst RAD = Math.PI / 180;\n\n/** Web Mercator easting (metres) of a longitude. */\nexport function mercX(lon: number): number {\n return lon * RAD * EARTH_RADIUS;\n}\n\n/** Web Mercator northing (metres) of a latitude, clamped to the projection. */\nexport function mercY(lat: number): number {\n const c = Math.max(-MERCATOR_MAX_LAT, Math.min(MERCATOR_MAX_LAT, lat));\n return EARTH_RADIUS * Math.log(Math.tan(Math.PI / 4 + (c * RAD) / 2));\n}\n\n/** Longitude of a Web Mercator easting. */\nexport function lonOf(x: number): number {\n return x / (RAD * EARTH_RADIUS);\n}\n\n/** Latitude of a Web Mercator northing. */\nexport function latOf(y: number): number {\n return (2 * Math.atan(Math.exp(y / EARTH_RADIUS)) - Math.PI / 2) / RAD;\n}\n\n/** Depth tint, Navionics-like: a saturated azure at the surface, fading\n * through pale blue to white at the shade limit. Stops are `[t, r, g, b]` with\n * `t` the fraction from the surface (0) to the shade limit (1). */\nconst SHADE_RAMP: ReadonlyArray<readonly [number, number, number, number]> = [\n [0, 26, 132, 216],\n [0.3, 96, 178, 232],\n [0.55, 156, 209, 240],\n [0.78, 205, 232, 248],\n [1, 255, 255, 255],\n];\n\n/** Isobaths drawn between the surface and the shade limit. */\nexport const SHADE_CONTOUR_LEVELS = 6;\n\n/** RGB at fraction `t` (0 = surface, 1 = the shade limit), sampling\n * {@link SHADE_RAMP}. */\nexport function shadeColor(t: number): [number, number, number] {\n const c = Math.min(1, Math.max(0, Number.isFinite(t) ? t : 0));\n let lo = SHADE_RAMP[0];\n let hi = SHADE_RAMP[SHADE_RAMP.length - 1];\n for (let i = 0; i < SHADE_RAMP.length - 1; i++) {\n if (c >= SHADE_RAMP[i][0] && c <= SHADE_RAMP[i + 1][0]) {\n lo = SHADE_RAMP[i];\n hi = SHADE_RAMP[i + 1];\n break;\n }\n }\n const span = hi[0] - lo[0];\n const f = span > 0 ? (c - lo[0]) / span : 0;\n const lerp = (a: number, b: number) => Math.round(a + (b - a) * f);\n return [lerp(lo[1], hi[1]), lerp(lo[2], hi[2]), lerp(lo[3], hi[3])];\n}\n\n/** RGB for a depth: azure at the surface, white at `shadeLimit` and beyond. */\nexport function depthColor(\n depth: number,\n shadeLimit: number,\n): [number, number, number] {\n const limit = shadeLimit > 0 ? shadeLimit : DEFAULT_SHADE_LIMIT_M;\n return shadeColor(depth / limit);\n}\n\n/** {@link depthColor} as a CSS `rgb()`/`rgba()` string. */\nexport function depthCss(depth: number, shadeLimit: number, alpha = 1): string {\n const [r, g, b] = depthColor(depth, shadeLimit);\n return `rgba(${r},${g},${b},${alpha})`;\n}\n\n/** Aggregate samples into a Web Mercator grid, keeping the SHALLOWEST depth in\n * each cell. `maxDepthMetres` drops samples deeper than the display range\n * (they can never form a cell's minimum anyway); a sample that idles in the\n * grid alone still carries its real depth. Samples with a non-finite or\n * negative depth are ignored \u2014 a negative reading is not water. */\nexport function aggregateMinDepth(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n maxDepthMetres = Infinity,\n): DepthCell[] {\n if (!(cellSizeMetres > 0)) {\n return [];\n }\n const cells = new Map<\n string,\n {\n ix: number;\n iy: number;\n sx: number;\n sy: number;\n n: number;\n depth: number;\n }\n >();\n for (const s of samples) {\n if (\n !Number.isFinite(s.lon) ||\n !Number.isFinite(s.lat) ||\n !Number.isFinite(s.depth) ||\n s.depth < 0 ||\n s.depth > maxDepthMetres\n ) {\n continue;\n }\n const x = mercX(s.lon);\n const y = mercY(s.lat);\n const ix = Math.floor(x / cellSizeMetres);\n const iy = Math.floor(y / cellSizeMetres);\n const key = `${ix},${iy}`;\n const c = cells.get(key);\n if (c) {\n c.sx += x;\n c.sy += y;\n c.n++;\n if (s.depth < c.depth) {\n c.depth = s.depth;\n }\n } else {\n cells.set(key, { ix, iy, sx: x, sy: y, n: 1, depth: s.depth });\n }\n }\n const out: DepthCell[] = [];\n cells.forEach((c) => {\n out.push({\n ix: c.ix,\n iy: c.iy,\n lon: lonOf(c.sx / c.n),\n lat: latOf(c.sy / c.n),\n depth: c.depth,\n count: c.n,\n });\n });\n return out;\n}\n\n/** Cells ordered deepest first, so a shallow cell drawn later paints over a\n * deeper one that overlaps it \u2014 a shoal is never occluded. */\nexport function orderDeepToShallow<T extends { depth: number }>(\n cells: readonly T[],\n): T[] {\n return [...cells].sort((a, b) => b.depth - a.depth);\n}\n\n/** Samples at or below `safetyDepthMetres`, drawn individually on top of the\n * aggregate so a critical minimum keeps its exact position and depth. */\nexport function selectShoals(\n samples: readonly DepthSample[],\n safetyDepthMetres: number,\n): DepthSample[] {\n return samples.filter(\n (s) =>\n Number.isFinite(s.depth) && s.depth >= 0 && s.depth <= safetyDepthMetres,\n );\n}\n\n/** Share of the anchor's depth two samples may differ by and still merge.\n * Depth readings are noisy, so exact equality merges almost nothing. */\nexport const DEFAULT_MERGE_TOLERANCE = 0.15;\n\n/** How far a merged group may grow from its shallowest cell, in cells. Bounds\n * a circle's size so a uniform-depth passage does not become one giant blob. */\nexport const MERGE_MAX_RADIUS_CELLS = 2.5;\n\n/** Merge adjacent grid cells whose depths are within `tolerance` of the\n * group's shallowest cell, into one circle per group. The shallowest cell is\n * the anchor and comparisons are always against the anchor (not the previous\n * neighbour), so depths cannot chain up to merge a wide range; and a group is\n * not allowed to grow beyond `maxRadiusCells` from its anchor, so a long\n * uniform run becomes a row of modest circles rather than one huge one. */\nexport function mergeDepthCells(\n cells: readonly DepthCell[],\n cellSizeMetres: number,\n toleranceOrMaxRadius: number = DEFAULT_MERGE_TOLERANCE,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n if (cells.length === 0 || !(cellSizeMetres > 0)) {\n return [];\n }\n const tolerance = Math.max(0, toleranceOrMaxRadius);\n const maxRadius = Math.max(cellSizeMetres, maxRadiusCells * cellSizeMetres);\n const byKey = new Map<string, DepthCell>();\n cells.forEach((c) => byKey.set(`${c.ix},${c.iy}`, c));\n const visited = new Set<string>();\n const ordered = [...cells].sort((a, b) => a.depth - b.depth);\n const neighbours = [\n [1, 0],\n [-1, 0],\n [0, 1],\n [0, -1],\n [1, 1],\n [1, -1],\n [-1, 1],\n [-1, -1],\n ];\n const clusters: DepthCluster[] = [];\n\n for (const start of ordered) {\n if (visited.has(`${start.ix},${start.iy}`)) {\n continue;\n }\n const anchor = start.depth;\n const ax = mercX(start.lon);\n const ay = mercY(start.lat);\n const members: DepthCell[] = [];\n const queue: DepthCell[] = [start];\n visited.add(`${start.ix},${start.iy}`);\n\n while (queue.length) {\n const cell = queue.pop();\n if (!cell) {\n continue;\n }\n members.push(cell);\n for (const [dx, dy] of neighbours) {\n const key = `${cell.ix + dx},${cell.iy + dy}`;\n const nb = byKey.get(key);\n if (!nb || visited.has(key)) {\n continue;\n }\n // anchor-relative, not neighbour-to-neighbour, so depth cannot drift\n if (nb.depth - anchor > Math.max(tolerance * anchor, 0.1)) {\n continue;\n }\n if (Math.hypot(mercX(nb.lon) - ax, mercY(nb.lat) - ay) > maxRadius) {\n continue;\n }\n visited.add(key);\n queue.push(nb);\n }\n }\n\n let cx = 0;\n let cy = 0;\n let total = 0;\n let minDepth = Infinity;\n members.forEach((m) => {\n cx += mercX(m.lon) * m.count;\n cy += mercY(m.lat) * m.count;\n total += m.count;\n if (m.depth < minDepth) {\n minDepth = m.depth;\n }\n });\n cx /= total;\n cy /= total;\n let radius = 0;\n members.forEach((m) => {\n radius = Math.max(\n radius,\n Math.hypot(mercX(m.lon) - cx, mercY(m.lat) - cy),\n );\n });\n clusters.push({\n lon: lonOf(cx),\n lat: latOf(cy),\n depth: minDepth,\n count: total,\n radiusMetres: Math.min(maxRadius, radius + cellSizeMetres / 2),\n });\n }\n return clusters;\n}\n\n/** Aggregate, merge similar-depth neighbours and order deep \u2192 shallow, ready\n * to draw. */\nexport function depthClusters(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n tolerance: number = DEFAULT_MERGE_TOLERANCE,\n maxDepthMetres = Infinity,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n return orderDeepToShallow(\n mergeDepthCells(\n aggregateMinDepth(samples, cellSizeMetres, maxDepthMetres),\n cellSizeMetres,\n tolerance,\n maxRadiusCells,\n ),\n );\n}\n\n// ******** History API ********\n//\n// The History API supplies the depth series (position and depth together) over\n// a recent time window; the Track API is not used, as on servers whose track\n// provider does not co-record depth it costs seconds per query just to find a\n// time span. Each depth sample carries its own position.\n\n/** Aggregation requested for depth in the History API: the SHALLOWEST sample.\n * At native resolution (no `resolution`) each bucket is one recorded sample, so\n * this is an identity; it only matters if a provider buckets unasked, where it\n * keeps the shallowest value rather than an average. */\nexport const DEPTH_HISTORY_METHOD = \"min\";\n\n/** Query for the depth series with the position of each sample. An explicit\n * `resolution` (seconds, fractional allowed) is honoured by the provider; when\n * it is omitted most providers auto-downsample to a few hundred points, so the\n * caller should always pass the finest interval it wants. `context` defaults to\n * the server's own vessel. */\nexport function historyValuesQuery(opts: {\n depthPath: string;\n from: number;\n to: number;\n resolution?: number;\n context?: string;\n}): string {\n return queryString({\n context: opts.context,\n paths: `navigation.position,${opts.depthPath}:${DEPTH_HISTORY_METHOD},navigation.headingTrue:average`,\n from: new Date(opts.from).toISOString(),\n to: new Date(opts.to).toISOString(),\n resolution: opts.resolution,\n });\n}\n\n/** One depth sample from the History API, with its recorded position. */\nexport interface DepthHistoryRow {\n time: number;\n depth: number;\n position?: Position;\n /** True heading (radians) at the sample, to place it at the transducer. */\n heading?: number;\n}\n\n/** A `[lon, lat]` from either array or `{latitude, longitude}` form. */\nfunction toPosition(value: unknown): Position | undefined {\n if (Array.isArray(value) && value.length >= 2) {\n const [lon, lat] = value as number[];\n if (Number.isFinite(lon) && Number.isFinite(lat)) {\n return [lon, lat];\n }\n return undefined;\n }\n if (value && typeof value === \"object\") {\n const v = value as { latitude?: unknown; longitude?: unknown };\n if (typeof v.latitude === \"number\" && typeof v.longitude === \"number\") {\n return [v.longitude, v.latitude];\n }\n }\n return undefined;\n}\n\n/** Depth rows from a History API `values` response, matched to `depthPath`.\n * The `position` column is attached when the response carried one; rows with a\n * null, non-number or negative depth are skipped. */\nexport function parseDepthSeries(\n result: unknown,\n depthPath: string,\n): DepthHistoryRow[] {\n const values = (result as { values?: Array<{ path?: string }> })?.values;\n const data = (result as { data?: unknown[] })?.data;\n if (!Array.isArray(values) || !Array.isArray(data)) {\n return [];\n }\n const depthCol = values.findIndex((v) => v?.path === depthPath);\n if (depthCol < 0) {\n return [];\n }\n const posCol = values.findIndex((v) => v?.path === \"navigation.position\");\n const headingCol = values.findIndex(\n (v) => v?.path === \"navigation.headingTrue\",\n );\n const rows: DepthHistoryRow[] = [];\n for (const row of data) {\n if (!Array.isArray(row)) {\n continue;\n }\n const time = Date.parse(String(row[0]));\n const depth = row[depthCol + 1];\n if (\n !Number.isFinite(time) ||\n typeof depth !== \"number\" ||\n !Number.isFinite(depth) ||\n depth < 0\n ) {\n continue;\n }\n const rec: DepthHistoryRow = { time, depth };\n if (posCol >= 0) {\n const position = toPosition(row[posCol + 1]);\n if (position) {\n rec.position = position;\n }\n }\n if (headingCol >= 0) {\n const heading = row[headingCol + 1];\n if (typeof heading === \"number\" && Number.isFinite(heading)) {\n rec.heading = heading;\n }\n }\n rows.push(rec);\n }\n return rows;\n}\n", "/** The extension's user settings, shared by the config panel (which edits\n * them), the runtime (which applies them) and the render Worker (which draws\n * with them). Persisted in host state under the extension scope. */\n\nexport type DepthSource = \"history\" | \"tiles\";\n\nexport interface LiveDepthSettings {\n enabled: boolean;\n /** Where the soundings come from: the server's History API (the vessel's own\n * recent track) or the plugin's gridded bathymetry tiles. */\n source: DepthSource;\n /** History look-back window, in hours. */\n historyWindowHours: number;\n /** History sample interval, in seconds. */\n historyResolutionS: number;\n /** Depth (metres) the shading runs to; deeper is left to the chart. */\n shadeLimitMetres: number;\n /** Depth (metres) between isobaths. */\n contourIntervalMetres: number;\n /** Grid cell size in screen pixels; also the base label size. */\n cellPx: number;\n /** Fractional depth difference (0.15 = 15%) within which adjacent cells\n * merge into one group. */\n mergeTolerance: number;\n /** Samples deeper than this (metres) are not drawn. */\n maxDepthMetres: number;\n /** Samples at or below this depth (metres) are marked individually. */\n safetyDepthMetres: number;\n}\n\nexport const DEFAULT_SETTINGS: LiveDepthSettings = {\n enabled: true,\n source: \"history\",\n historyWindowHours: 1,\n historyResolutionS: 0.1,\n shadeLimitMetres: 10,\n contourIntervalMetres: 1,\n cellPx: 24,\n mergeTolerance: 0.15,\n maxDepthMetres: 100,\n safetyDepthMetres: 3,\n};\n\n/** The settings keys, with the bounds the config panel clamps to. */\nexport const SETTINGS_BOUNDS: Partial<\n Record<keyof LiveDepthSettings, { min: number; max: number; step: number }>\n> = {\n historyWindowHours: { min: 0.1, max: 168, step: 0.1 },\n historyResolutionS: { min: 0.01, max: 60, step: 0.01 },\n shadeLimitMetres: { min: 1, max: 200, step: 1 },\n contourIntervalMetres: { min: 0.5, max: 50, step: 0.5 },\n cellPx: { min: 4, max: 128, step: 1 },\n mergeTolerance: { min: 0, max: 1, step: 0.01 },\n maxDepthMetres: { min: 1, max: 500, step: 1 },\n safetyDepthMetres: { min: 0, max: 50, step: 0.5 },\n};\n\n/** Merge stored state over the defaults, ignoring unknown/ill-typed keys. */\nexport function mergeSettings(\n stored: Record<string, unknown> | undefined,\n): LiveDepthSettings {\n const out = { ...DEFAULT_SETTINGS };\n if (!stored) {\n return out;\n }\n for (const key of Object.keys(\n DEFAULT_SETTINGS,\n ) as (keyof LiveDepthSettings)[]) {\n const value = stored[key];\n if (typeof value === typeof DEFAULT_SETTINGS[key]) {\n (out[key] as unknown) = value;\n }\n }\n return out;\n}\n", "/**\n * Fixed-grid geometry for the bathymetry tile store.\n *\n * Shared by the LiveDepth server plugin and the chart client, so the two build\n * and read identical tiles. The byte codec uses `DataView`/`Uint8Array`, which\n * works both under Node and in the browser, against the `sparse-v1` wire\n * format.\n *\n * A Web-Mercator XYZ quadtree: level `z` splits the world into `2^z` tiles of\n * {@link TILE_SIZE} cells each, so a tile's cell grid is square on the ground.\n * The base level is chosen so a cell is a fraction of a metre; coarser levels\n * are the min-pooled parents (see {@link minPoolTile}), so a shoal is never lost\n * when the chart is zoomed out.\n *\n * A tile is stored sparsely \u2014 only cells that have a sounding \u2014 because a\n * vessel's track is a thin line through a tile and a dense 256x256 array would\n * be almost entirely empty. Each occupied cell is a 5-byte record:\n * `cx:u8, cy:u8, depthCm:u16le, count:u8`.\n */\n\nexport const TILE_SIZE = 256;\n/** Base level: cell \u2248 0.15 m at the equator (156543.03 / 2^20). */\nexport const BASE_ZOOM = 20;\n/** Coarsest level built and served (\u2248 2.4 km/cell at z4). Low enough that a\n * regional zoom-out is covered by a handful of tiles; the min-pool keeps a\n * shoal visible however far the chart is zoomed out. */\nexport const MIN_ZOOM = 4;\n/** Decoded value for a cell with no sounding. Never stored. */\nexport const NODATA = 0xffff;\n/** Bytes per occupied cell: cx(1) + cy(1) + depth(2) + count(1). */\nexport const RECORD_BYTES = 5;\n\nconst EARTH_RADIUS = 6378137;\nconst ORIGIN_SHIFT = Math.PI * EARTH_RADIUS; // 20037508.342789244\nconst WORLD_WIDTH = 2 * ORIGIN_SHIFT;\n\n/** One cell's aggregate: shallowest depth in centimetres and how many soundings. */\nexport type Cell = { depth: number; count: number };\n/** A tile's occupied cells, keyed by `cy * TILE_SIZE + cx`. */\nexport type TileCells = Map<number, Cell>;\n\nconst clamp = (v: number, lo: number, hi: number) =>\n v < lo ? lo : v > hi ? hi : v;\n\n/** Ground size of one cell, in metres, on the equator. */\nexport function cellSize(z: number): number {\n return WORLD_WIDTH / (TILE_SIZE * Math.pow(2, z));\n}\n\n/** Fractional tile coordinates of a lon/lat at level z (x east, y south). */\nfunction tileFraction(lon: number, lat: number, z: number) {\n const n = Math.pow(2, z);\n const latRad = (clamp(lat, -85.05112878, 85.05112878) * Math.PI) / 180;\n const x = ((lon + 180) / 360) * n;\n const y =\n ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * n;\n return { x, y, n };\n}\n\n/** The level-z tile and the cell within it that contains a lon/lat. */\nexport function lngLatToCell(lon: number, lat: number, z: number) {\n const { x: fx, y: fy, n } = tileFraction(lon, lat, z);\n const x = clamp(Math.floor(fx), 0, n - 1);\n const y = clamp(Math.floor(fy), 0, n - 1);\n const cx = clamp(Math.floor((fx - x) * TILE_SIZE), 0, TILE_SIZE - 1);\n const cy = clamp(Math.floor((fy - y) * TILE_SIZE), 0, TILE_SIZE - 1);\n return { x, y, cx, cy };\n}\n\n/** Quantise a depth in metres to the store's centimetre resolution. */\nexport function encodeDepth(metres: number): number {\n if (!Number.isFinite(metres)) return NODATA;\n return clamp(Math.round(metres * 100), 0, NODATA - 1);\n}\n\n/** Decode a stored depth in centimetres back to metres. */\nexport function decodeDepth(cm: number): number {\n return cm === NODATA ? NaN : cm / 100;\n}\n\n/** Merge a sounding into a cell, keeping the shallowest and counting samples. */\nexport function mergeCell(\n cells: TileCells,\n key: number,\n depthCm: number,\n count = 1,\n): void {\n const existing = cells.get(key);\n if (!existing) {\n cells.set(key, { depth: depthCm, count: Math.min(255, count) });\n return;\n }\n if (depthCm < existing.depth) existing.depth = depthCm;\n existing.count = Math.min(255, existing.count + count);\n}\n\n/** Encode a tile's cells to its sparse on-the-wire representation. */\nexport function encodeTile(cells: TileCells): Uint8Array {\n const bytes = new Uint8Array(cells.size * RECORD_BYTES);\n const view = new DataView(bytes.buffer);\n let offset = 0;\n for (const [key, { depth, count }] of cells) {\n view.setUint8(offset, key % TILE_SIZE);\n view.setUint8(offset + 1, Math.floor(key / TILE_SIZE));\n view.setUint16(offset + 2, depth, true);\n view.setUint8(offset + 4, count);\n offset += RECORD_BYTES;\n }\n return bytes;\n}\n\n/** Decode a sparse tile buffer back to its cells. */\nexport function decodeTile(bytes: Uint8Array): TileCells {\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const cells: TileCells = new Map();\n for (let o = 0; o + RECORD_BYTES <= view.byteLength; o += RECORD_BYTES) {\n const cx = view.getUint8(o);\n const cy = view.getUint8(o + 1);\n const depth = view.getUint16(o + 2, true);\n const count = view.getUint8(o + 4);\n cells.set(cy * TILE_SIZE + cx, { depth, count });\n }\n return cells;\n}\n\n/**\n * Build a parent tile's cells from its four child tiles (level z+1, ordered\n * `[southwest, southeast, northwest, northeast]` = `[(0,0),(1,0),(0,1),(1,1)]`\n * in child-tile offsets). A parent cell keeps the **minimum** depth of its four\n * children and the summed count, so zooming out never deepens a shoal.\n */\nexport function minPoolTile(children: (TileCells | undefined)[]): TileCells {\n const parent: TileCells = new Map();\n children.forEach((child, index) => {\n if (!child) return;\n const dx = index & 1;\n const dy = index >> 1;\n for (const [key, { depth, count }] of child) {\n const cx = key % TILE_SIZE;\n const cy = Math.floor(key / TILE_SIZE);\n const pcx = (dx * TILE_SIZE + cx) >> 1;\n const pcy = (dy * TILE_SIZE + cy) >> 1;\n mergeCell(parent, pcy * TILE_SIZE + pcx, depth, count);\n }\n });\n return parent;\n}\n", "/**\n * The tile-store interface shared by both sides, plus an in-memory\n * implementation.\n *\n * The LiveDepth plugin backs it with SQLite; the chart client keeps the tiles\n * in a `Map` (bounded by the viewport it fetches, not the whole record). The\n * shared {@link createTileAccumulator} and {@link minPoolTile} logic works\n * against this interface so both build identical tiles.\n */\n\nimport { MIN_ZOOM, TileCells, minPoolTile } from \"./grid.js\";\n\nexport interface TileStore {\n /** A tile's cells, or undefined when it holds no soundings. */\n read(z: number, x: number, y: number): TileCells | undefined;\n /** Replace a tile (deletes it when empty). */\n write(z: number, x: number, y: number, cells: TileCells): void;\n /** Merge cells into a tile, keeping the shallowest depth and summing counts. */\n merge(z: number, x: number, y: number, incoming: TileCells): void;\n /** Rebuild every ancestor of a changed base tile, up to {@link MIN_ZOOM}. */\n updateAncestors(z: number, x: number, y: number): void;\n /** Every occupied tile at a level (for a bulk pyramid rebuild). */\n listTiles(z: number): { x: number; y: number }[];\n /** Read a store-level key. */\n getMeta(key: string): string | undefined;\n /** Write a store-level key. */\n setMeta(key: string, value: string): void;\n /** Run `fn` inside a single transaction (a no-op batch on the client). */\n transaction<T>(fn: () => T): T;\n /** Drop every tile. */\n clear(): void;\n /** Release any resources (a no-op for the in-memory store). */\n close(): void;\n}\n\nexport function createMemoryTileStore(): TileStore {\n const tiles = new Map<string, TileCells>();\n const meta = new Map<string, string>();\n const key = (z: number, x: number, y: number) => `${z}/${x}/${y}`;\n\n const read = (z: number, x: number, y: number) => tiles.get(key(z, x, y));\n\n const write = (z: number, x: number, y: number, cells: TileCells): void => {\n if (cells.size === 0) {\n tiles.delete(key(z, x, y));\n return;\n }\n tiles.set(key(z, x, y), cells);\n };\n\n const merge = (\n z: number,\n x: number,\n y: number,\n incoming: TileCells,\n ): void => {\n const cells = read(z, x, y) ?? new Map();\n for (const [k, cell] of incoming) {\n const existing = cells.get(k);\n if (!existing) {\n cells.set(k, { depth: cell.depth, count: cell.count });\n } else {\n if (cell.depth < existing.depth) existing.depth = cell.depth;\n existing.count = Math.min(255, existing.count + cell.count);\n }\n }\n write(z, x, y, cells);\n };\n\n const updateAncestors = (z: number, x: number, y: number): void => {\n let lz = z;\n let lx = x;\n let ly = y;\n while (lz > MIN_ZOOM) {\n const pz = lz - 1;\n const px = lx >> 1;\n const py = ly >> 1;\n const children = [\n read(lz, px * 2, py * 2),\n read(lz, px * 2 + 1, py * 2),\n read(lz, px * 2, py * 2 + 1),\n read(lz, px * 2 + 1, py * 2 + 1),\n ];\n write(pz, px, py, minPoolTile(children));\n lz = pz;\n lx = px;\n ly = py;\n }\n };\n\n return {\n read,\n write,\n merge,\n updateAncestors,\n listTiles: (z) => {\n const out: { x: number; y: number }[] = [];\n for (const key of tiles.keys()) {\n const [tz, tx, ty] = key.split(\"/\");\n if (Number(tz) === z) {\n out.push({ x: Number(tx), y: Number(ty) });\n }\n }\n return out;\n },\n getMeta: (k) => meta.get(k),\n setMeta: (k, v) => {\n meta.set(k, v);\n },\n transaction: (fn) => fn(),\n clear: () => {\n tiles.clear();\n },\n close: () => {\n tiles.clear();\n },\n };\n}\n", "/**\n * Folds soundings into the base-level tiles and, on\n * {@link TileAccumulator.flush}, writes the changed tiles and min-pools their\n * ancestors.\n *\n * Shared by the LiveDepth server plugin and the chart client, so both build\n * identical tiles from the same live stream. Batched so many soundings become\n * one transaction; used by the live writer and any bulk load.\n */\n\nimport { Sounding } from \"./types.js\";\nimport {\n BASE_ZOOM,\n MIN_ZOOM,\n TILE_SIZE,\n TileCells,\n encodeDepth,\n lngLatToCell,\n mergeCell,\n minPoolTile,\n} from \"./grid.js\";\nimport { TileStore } from \"./store.js\";\n\nexport interface TileAccumulator {\n add(sample: Sounding): void;\n flush(): void;\n /** Tiles touched since the last flush. */\n readonly pending: number;\n}\n\nexport interface AccumulatorOptions {\n baseZoom?: number;\n /** Rebuild each changed tile's ancestors on flush (the default). Set `false`\n * for a bulk load and call {@link rebuildAncestors} once at the end: walking\n * the whole pyramid for every changed tile on every batch repeats the same\n * work whenever a tile is touched in many batches. */\n ancestors?: boolean;\n}\n\nexport function createTileAccumulator(\n store: TileStore,\n { baseZoom = BASE_ZOOM, ancestors = true }: AccumulatorOptions = {},\n): TileAccumulator {\n const dirty = new Map<\n string,\n { z: number; x: number; y: number; cells: TileCells }\n >();\n\n return {\n add(sample: Sounding) {\n if (\n !Number.isFinite(sample.lon) ||\n !Number.isFinite(sample.lat) ||\n !Number.isFinite(sample.depth) ||\n sample.depth < 0\n ) {\n return;\n }\n const { x, y, cx, cy } = lngLatToCell(sample.lon, sample.lat, baseZoom);\n const key = `${x},${y}`;\n let tile = dirty.get(key);\n if (!tile) {\n tile = { z: baseZoom, x, y, cells: new Map() };\n dirty.set(key, tile);\n }\n mergeCell(tile.cells, cy * TILE_SIZE + cx, encodeDepth(sample.depth));\n },\n\n flush() {\n if (dirty.size === 0) return;\n const tiles = [...dirty.values()];\n dirty.clear();\n store.transaction(() => {\n for (const tile of tiles) {\n store.merge(tile.z, tile.x, tile.y, tile.cells);\n if (ancestors) {\n store.updateAncestors(tile.z, tile.x, tile.y);\n }\n }\n });\n },\n\n get pending() {\n return dirty.size;\n },\n };\n}\n\n/**\n * Build every level of the pyramid from the base tiles in one bottom-up pass:\n * pool each level into the one below, reading each child once. Used after a\n * bulk load that ran with `ancestors: false`, so the whole pyramid is built\n * once rather than re-walked per changed tile per batch.\n */\nexport function rebuildAncestors(\n store: TileStore,\n {\n baseZoom = BASE_ZOOM,\n minZoom = MIN_ZOOM,\n }: { baseZoom?: number; minZoom?: number } = {},\n): void {\n for (let z = baseZoom; z > minZoom; z--) {\n const parents = new Map<string, { x: number; y: number }>();\n for (const { x, y } of store.listTiles(z)) {\n const px = x >> 1;\n const py = y >> 1;\n parents.set(`${px},${py}`, { x: px, y: py });\n }\n store.transaction(() => {\n for (const { x, y } of parents.values()) {\n store.write(\n z - 1,\n x,\n y,\n minPoolTile([\n store.read(z, x * 2, y * 2),\n store.read(z, x * 2 + 1, y * 2),\n store.read(z, x * 2, y * 2 + 1),\n store.read(z, x * 2 + 1, y * 2 + 1),\n ]),\n );\n }\n });\n }\n}\n", "/** Helpers for reading the LiveDepth bathymetry tiles.\n *\n * Glue around the grid: pick the tile level for a target ground resolution,\n * work out which tiles cover a viewport, and turn a decoded tile's cells back\n * into lon/lat depth soundings. Pure functions (no HTTP, no framework) so they\n * can be unit tested directly.\n */\n\nimport { Sounding } from \"./types.js\";\nimport { TransducerOffset } from \"./correction.js\";\nimport {\n BASE_ZOOM,\n MIN_ZOOM,\n TILE_SIZE,\n TileCells,\n cellSize,\n decodeDepth,\n decodeTile,\n} from \"./grid.js\";\n\n/** Grid spec served by `/plugins/<id>/bathymetry/meta`. */\nexport interface TileMeta {\n format: string;\n baseZoom: number;\n minZoom: number;\n tileSize: number;\n recordBytes: number;\n depthPath?: string;\n /** Offset from the position the server reports to the depth transducer, when\n * the plugin corrects for it; absent or null otherwise. A client applies the\n * same shift to its own soundings so they match the served tiles. */\n transducer?: TransducerOffset | null;\n}\n\nconst RAD = Math.PI / 180;\n\n/** The finest tile level whose cells are no coarser than `cellMetres`, clamped\n * to the served range. Finer than needed wastes requests; coarser loses detail\n * the field could have shown. */\nexport function zoomForCellSize(\n cellMetres: number,\n minZoom = MIN_ZOOM,\n baseZoom = BASE_ZOOM,\n): number {\n if (!(cellMetres > 0)) return baseZoom;\n // Round up, so the chosen level's cells are no coarser than requested.\n const ideal = Math.ceil(\n Math.log2((2 * Math.PI * 6378137) / (TILE_SIZE * cellMetres)),\n );\n return Math.max(minZoom, Math.min(baseZoom, ideal));\n}\n\n/** Tile x for a longitude at level z; may fall outside `[0, 2^z)` for a view\n * panned a world away, and is wrapped by the caller. */\nfunction lonToTileX(lon: number, z: number): number {\n return ((lon + 180) / 360) * Math.pow(2, z);\n}\n\n/** Tile y for a latitude at level z (south = larger y). */\nfunction latToTileY(lat: number, z: number): number {\n const clamped = Math.max(-85.05112878, Math.min(85.05112878, lat));\n const latRad = clamped * RAD;\n return (\n ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) *\n Math.pow(2, z)\n );\n}\n\n/** The tiles (wrapped x) covering a `[west, south, east, north]` box at level\n * z. `west > east` is read the short way round the antimeridian. */\nexport function tileRangeForBbox(\n bbox: readonly number[],\n z: number,\n): { xs: number[]; ys: number[] } {\n const n = Math.pow(2, z);\n if (bbox.length !== 4) {\n return { xs: [], ys: [] };\n }\n const [west, south, east, north] = bbox;\n\n const xStart = Math.floor(lonToTileX(west, z));\n let xEnd = Math.floor(lonToTileX(east, z));\n if (xEnd < xStart) {\n xEnd += n; // wrapped past the antimeridian\n }\n const xCount = Math.min(n, xEnd - xStart + 1);\n const xs: number[] = [];\n for (let i = 0; i < xCount; i++) {\n xs.push((((xStart + i) % n) + n) % n);\n }\n\n const yStart = Math.max(0, Math.floor(latToTileY(north, z)));\n const yEnd = Math.min(n - 1, Math.floor(latToTileY(south, z)));\n const ys: number[] = [];\n for (let y = yStart; y <= yEnd; y++) {\n ys.push(y);\n }\n\n return { xs, ys };\n}\n\n/** Lon/lat of the centre of a cell within a tile. */\nexport function tileCellCentre(\n z: number,\n x: number,\n y: number,\n key: number,\n): [number, number] {\n const n = Math.pow(2, z);\n const cx = key % TILE_SIZE;\n const cy = Math.floor(key / TILE_SIZE);\n const fx = (x + (cx + 0.5) / TILE_SIZE) / n;\n const fy = (y + (cy + 0.5) / TILE_SIZE) / n;\n const lon = fx * 360 - 180;\n const lat = Math.atan(Math.sinh(Math.PI * (1 - 2 * fy))) / RAD;\n return [lon, lat];\n}\n\n/** Turn a tile's cells into lon/lat depth samples. */\nexport function cellsToSamples(\n z: number,\n x: number,\n y: number,\n cells: TileCells,\n): Sounding[] {\n const samples: Sounding[] = [];\n for (const [key, cell] of cells) {\n const depth = decodeDepth(cell.depth);\n if (!Number.isFinite(depth)) continue;\n const [lon, lat] = tileCellCentre(z, x, y, key);\n samples.push({ lon, lat, depth });\n }\n return samples;\n}\n\n/** Decode a served tile blob into lon/lat depth samples. */\nexport function decodeTileSamples(\n z: number,\n x: number,\n y: number,\n bytes: Uint8Array,\n): Sounding[] {\n return cellsToSamples(z, x, y, decodeTile(bytes));\n}\n\nexport { cellSize };\n", "/**\n * Vessel reference point \u2192 depth transducer correction.\n *\n * The recorded position is the reference point the server reports as\n * `navigation.position`: the GNSS antenna, or \u2014 when the server's\n * `gnssCorrection` is active \u2014 the Consistent Common Reference Point (CCRP,\n * midships on the centreline). A depth sounding is measured at the transducer,\n * which sits elsewhere on the hull, so its position must be shifted by the\n * body-frame offset from that reference to the transducer, rotated by the true\n * heading.\n *\n * The caller supplies the offset; this module neither knows nor guesses the\n * reference, so it stays clear of the server's GNSS correction.\n */\n\n/** Body-frame offset of the transducer from the position reference, in metres:\n * `forward` toward the bow, `starboard` to starboard. */\nexport interface TransducerOffset {\n forward: number;\n starboard: number;\n}\n\n/** A lon/lat position. */\nexport interface LonLat {\n lon: number;\n lat: number;\n}\n\nconst EARTH_RADIUS_M = 6378137;\nconst DEG_TO_RAD = Math.PI / 180;\nconst RAD_TO_DEG = 180 / Math.PI;\n\n/**\n * Shift `position` from the reference point to the transducer, rotating the\n * body-frame `offset` by `heading` (true, radians, clockwise from north).\n * Returns `position` unchanged when there is no heading or no offset.\n */\nexport function correctToTransducer(\n position: LonLat,\n heading: number | undefined,\n offset: TransducerOffset,\n): LonLat {\n if (\n heading === undefined ||\n !Number.isFinite(heading) ||\n (!offset.forward && !offset.starboard)\n ) {\n return position;\n }\n const cos = Math.cos(heading);\n const sin = Math.sin(heading);\n // +x body (toward bow) points along the heading; +y body (starboard) is 90\u00B0\n // clockwise from it.\n const north = offset.forward * cos - offset.starboard * sin;\n const east = offset.forward * sin + offset.starboard * cos;\n const lat = position.lat + (north / EARTH_RADIUS_M) * RAD_TO_DEG;\n const lon =\n position.lon +\n (east / (EARTH_RADIUS_M * Math.cos(position.lat * DEG_TO_RAD))) *\n RAD_TO_DEG;\n // wrap into [-180, 180] so a shift near the antimeridian stays in range\n const wrapped = ((((lon + 180) % 360) + 360) % 360) - 180;\n return { lon: wrapped, lat };\n}\n", "// The LiveDepth background runtime: a headless iframe the host loads while the\n// plugin is enabled. It owns the soundings, asks its render Worker for a frame\n// on each `overlay.request`, and answers with `overlay.frame`.\n//\n// Data has two sources, matching the plugin's two modes: the server's History\n// API (the vessel's own recent track) or the plugin's gridded bathymetry tiles\n// \u2014 both reached over same-origin REST, since the runtime is served by the same\n// server as the data. The own vessel's live position + depth arrive over the\n// bus (`signalk.subscribe`) and extend the tile source in place.\n\nimport { connectExtension } from \"signalk-plotterext-bus/extension\";\nimport type {\n OverlayRequestEvent,\n OverlayStateEvent,\n} from \"signalk-plotterext-bus/extension\";\nimport type { DepthSample } from \"./shared/live-depth.js\";\nimport { historyValuesQuery, parseDepthSeries } from \"./shared/live-depth.js\";\nimport {\n DEFAULT_SETTINGS,\n mergeSettings,\n type LiveDepthSettings,\n} from \"./shared/settings.js\";\nimport type { RenderRequest, RenderWorkerOut } from \"./shared/protocol.js\";\nimport {\n createMemoryTileStore,\n createTileAccumulator,\n cellsToSamples,\n correctToTransducer,\n decodeTileSamples,\n tileRangeForBbox,\n zoomForCellSize,\n BASE_ZOOM,\n MIN_ZOOM,\n type TileMeta,\n type TransducerOffset,\n} from \"../tiles/core/index.js\";\n\nconst PLUGIN_ID = \"signalk-livedepth-plugin\";\nconst ASSET_BASE = `/plotterext/${PLUGIN_ID}`;\nconst BATHY_BASE = `${ASSET_BASE}/bathymetry`;\n\n/** Settle time before a moved view or a changed window is fetched. */\nconst DEBOUNCE_MS = 400;\n/** How often the history source is refreshed while the layer is visible. */\nconst POLL_MS = 15000;\n/** Most tile fetches a single view may need before the layer draws nothing. */\nconst MAX_TILE_FETCHES = 24;\n/** How long live soundings are batched before folding into the client store. */\nconst LIVE_FLUSH_MS = 2000;\n/** Depth path when the plugin advertises none. */\nconst DEFAULT_DEPTH_PATH = \"environment.depth.belowTransducer\";\n\nconst client = await connectExtension();\nconst worker = new Worker(new URL(\"./render-worker.js\", import.meta.url), {\n type: \"module\",\n});\n\nlet settings: LiveDepthSettings = { ...DEFAULT_SETTINGS };\nlet transducer: TransducerOffset | null = null;\nlet depthPath = DEFAULT_DEPTH_PATH;\n\nlet samples: DepthSample[] = [];\nlet lastReq: RenderRequest | null = null;\nlet busy = false;\nlet dirty = false;\n/** The request currently being rendered. Its frame carries this request's own\n * id and extent, even if a newer one arrives mid-render, so the host refuses a\n * stale frame as `superseded` rather than reading it as a size mismatch. */\nlet rendering: RenderRequest | null = null;\n/** Whether the frame being rendered is a live (data-driven) one, sent without a\n * request id and paced by the host's `liveInterval`. */\nlet renderingLive = false;\n/** The host's least interval between two live frames, in ms. */\nlet liveInterval = 200;\n/** When the last live frame was sent, to pace the next. */\nlet lastLiveAt = 0;\nlet timer: ReturnType<typeof setTimeout> | undefined;\n\n// Client-side mirror of the tile store, extended from the live stream.\nlet lastZoom = 0;\nlet clientStore = createMemoryTileStore();\nlet clientAccumulator = createTileAccumulator(clientStore);\nlet liveDirty = false;\nlet liveFlushTimer: ReturnType<typeof setInterval> | undefined;\n\n// ******** settings ********\n\nasync function loadSettings(): Promise<void> {\n try {\n const values = await client.state.get();\n settings = mergeSettings(values as Record<string, unknown>);\n } catch {\n settings = { ...DEFAULT_SETTINGS };\n }\n worker.postMessage({ type: \"settings\", settings });\n // The live extension belongs to the tile source only; start or stop it as the\n // source changes, so switching depth source takes effect.\n if (settings.source === \"tiles\") {\n startLive();\n } else {\n stopLive();\n }\n schedule();\n}\n\n// ******** data ********\n\nasync function loadMeta(): Promise<void> {\n try {\n const res = await fetch(`${BATHY_BASE}/meta`, {\n credentials: \"same-origin\",\n });\n if (!res.ok) return;\n const meta = (await res.json()) as TileMeta;\n transducer = meta.transducer ?? null;\n if (meta.depthPath) depthPath = meta.depthPath;\n } catch {\n // No plugin meta: the history source still works, uncorrected.\n }\n}\n\n/** Shift a sounding from the reported position to the transducer, when the\n * plugin's meta told us to correct. */\nfunction correct(\n lon: number,\n lat: number,\n heading: number | undefined,\n): { lon: number; lat: number } {\n return transducer\n ? correctToTransducer({ lon, lat }, heading, transducer)\n : { lon, lat };\n}\n\n/** Ask the host for the user's preferred depth unit, for the labels. */\nasync function loadUnits(): Promise<void> {\n try {\n const res = (await client.call(\"units.get\")) as {\n units?: { depth?: string };\n };\n if (res?.units?.depth) {\n worker.postMessage({ type: \"units\", depth: res.units.depth });\n }\n } catch {\n // No units capability: the labels stay in metres.\n }\n}\n\nasync function fetchHistory(): Promise<void> {\n const windowMs = settings.historyWindowHours * 3600000;\n const to = Date.now();\n const query = historyValuesQuery({\n depthPath,\n from: to - windowMs,\n to,\n resolution: Number(settings.historyResolutionS.toPrecision(3)),\n });\n const res = await fetch(`/signalk/v2/api/history/values?${query}`, {\n credentials: \"same-origin\",\n });\n if (!res.ok) return;\n const rows = parseDepthSeries(await res.json(), depthPath);\n const out: DepthSample[] = [];\n for (const row of rows) {\n if (!row.position) continue;\n const { lon, lat } = correct(row.position[0], row.position[1], row.heading);\n out.push({ lon, lat, depth: row.depth });\n }\n samples = out;\n publish();\n}\n\nasync function fetchTiles(req: RenderRequest): Promise<void> {\n const cellMetres = Math.max(req.resolution, 0.01);\n let zoom = zoomForCellSize(cellMetres, MIN_ZOOM, BASE_ZOOM);\n while (zoom > MIN_ZOOM) {\n const { xs, ys } = tileRangeForBbox(req.bounds, zoom);\n if (xs.length * ys.length <= MAX_TILE_FETCHES) break;\n zoom--;\n }\n lastZoom = zoom;\n const { xs, ys } = tileRangeForBbox(req.bounds, zoom);\n const out: DepthSample[] = [];\n await Promise.all(\n xs.flatMap((x) =>\n ys.map(async (y) => {\n try {\n const res = await fetch(`${BATHY_BASE}/tiles/${zoom}/${x}/${y}`, {\n credentials: \"same-origin\",\n });\n if (!res.ok) return;\n const bytes = new Uint8Array(await res.arrayBuffer());\n out.push(...decodeTileSamples(zoom, x, y, bytes));\n } catch {\n // A missing tile is simply no data there.\n }\n }),\n ),\n );\n // Fold in the own vessel's live extension at the same level.\n for (const x of xs) {\n for (const y of ys) {\n const cells = clientStore.read(lastZoom, x, y);\n if (cells && cells.size) {\n out.push(...cellsToSamples(lastZoom, x, y, cells));\n }\n }\n }\n samples = out;\n publish();\n}\n\nfunction schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => void refresh(), DEBOUNCE_MS);\n}\n\nasync function refresh(): Promise<void> {\n if (!settings.enabled) return;\n if (settings.source === \"history\") {\n await fetchHistory();\n } else if (lastReq) {\n await fetchTiles(lastReq);\n }\n}\n\nfunction publish(): void {\n worker.postMessage({ type: \"samples\", samples });\n dirty = true;\n // A live frame for the last request, so the layer updates without a new one.\n if (lastReq) draw(true);\n}\n\n// ******** live extension (tiles source) ********\n\nlet liveUnsub: (() => Promise<void>) | undefined;\n\nfunction startLive(): void {\n if (settings.source !== \"tiles\" || liveUnsub) return;\n void client.signalk\n .subscribe(\n [\"navigation.position\", depthPath, \"navigation.headingTrue\"],\n (ev) => {\n if (ev.path === \"navigation.position\" && Array.isArray(ev.value)) {\n livePosition = ev.value as [number, number];\n } else if (ev.path === depthPath && typeof ev.value === \"number\") {\n liveDepth = ev.value;\n } else if (\n ev.path === \"navigation.headingTrue\" &&\n typeof ev.value === \"number\"\n ) {\n liveHeading = ev.value;\n }\n sampleLive();\n },\n )\n .then((un) => {\n liveUnsub = un;\n })\n .catch(() => {});\n clearInterval(liveFlushTimer);\n liveFlushTimer = setInterval(flushLive, LIVE_FLUSH_MS);\n}\n\nfunction stopLive(): void {\n clearInterval(liveFlushTimer);\n liveFlushTimer = undefined;\n void liveUnsub?.();\n liveUnsub = undefined;\n}\n\nlet livePosition: [number, number] | null = null;\nlet liveDepth: number | null = null;\nlet liveHeading: number | undefined;\n\nfunction sampleLive(): void {\n if (!livePosition || liveDepth === null) return;\n const { lon, lat } = correct(livePosition[0], livePosition[1], liveHeading);\n clientAccumulator.add({ lon, lat, depth: liveDepth });\n liveDirty = true;\n}\n\nfunction flushLive(): void {\n if (!liveDirty) return;\n liveDirty = false;\n clientAccumulator.flush();\n if (settings.source === \"tiles\" && lastReq) void fetchTiles(lastReq);\n}\n\n// ******** frame cycle ********\n\nfunction draw(live = false): void {\n if (busy || !lastReq || !settings.enabled) return;\n if (live && Date.now() - lastLiveAt < liveInterval) return;\n busy = true;\n rendering = lastReq;\n renderingLive = live;\n dirty = false;\n worker.postMessage({ type: \"render\", req: rendering });\n}\n\nworker.onmessage = (e: MessageEvent<RenderWorkerOut>) => {\n const msg = e.data;\n const req = rendering;\n const live = renderingLive;\n busy = false;\n rendering = null;\n if (msg.type !== \"frame\" || !req) {\n return;\n }\n if (live) lastLiveAt = Date.now();\n // The host owns the image once it is transferred, and closes a refused one\n // itself, so there is nothing to release here. A live frame carries no\n // request id, so the host paces it against `liveInterval`.\n void client.overlays\n .frame({\n overlayId: req.overlayId,\n requestId: live ? undefined : req.requestId,\n extent: req.extent,\n image: msg.image,\n })\n .catch(() => {});\n // A newer request arrived, or the data changed, while this frame rendered.\n if (dirty || lastReq !== req) draw(live);\n};\n\n// ******** bus wiring ********\n\nawait client.subscribe([\"overlay.request\"], (_name, payload) => {\n const req = payload as OverlayRequestEvent;\n lastReq = req;\n if (typeof req.liveInterval === \"number\" && req.liveInterval > 0) {\n liveInterval = req.liveInterval;\n }\n draw();\n});\n\nawait client.subscribe([\"overlay.state\"], (_name, payload) => {\n const state = payload as OverlayStateEvent;\n if (!state.visible || state.suspended) {\n worker.postMessage({ type: \"release\" });\n stopLive();\n } else {\n startLive();\n schedule();\n }\n});\n\nawait client.subscribe([\"state.changed\"], () => void loadSettings());\n\nawait loadMeta();\nawait loadSettings();\nawait loadUnits();\nsetInterval(() => {\n if (settings.enabled && settings.source === \"history\") schedule();\n}, POLL_MS);\n"],
5
- "mappings": "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",
6
- "names": ["timer", "depthPath", "key", "dirty", "RAD", "samples", "xs", "ys"]
3
+ "sources": ["../../node_modules/signalk-plotterext-bus/src/protocol.ts", "../../node_modules/signalk-plotterext-bus/src/wildcard.ts", "../../node_modules/signalk-plotterext-bus/src/bounds.ts", "../../node_modules/signalk-plotterext-bus/src/codec.ts", "../../node_modules/signalk-plotterext-bus/src/port.ts", "../../node_modules/signalk-plotterext-bus/src/endpoint.ts", "../../node_modules/signalk-plotterext-bus/src/extension.ts", "../../src/web/shared/live-depth.ts", "../../src/web/shared/settings.ts", "../../src/tiles/core/grid.ts", "../../src/tiles/core/tile-source.ts", "../../src/tiles/core/correction.ts", "../../src/web/renderer.ts"],
4
+ "sourcesContent": ["/**\n * Wire protocol types and constants for the Signal K plotter extension bus.\n *\n * The wire format is JSON-RPC 2.0 (https://www.jsonrpc.org/specification)\n * inside a routing envelope: `{ bus: \"plotterExt/1\", msg: <JSON-RPC object> }`.\n * Calls are JSON-RPC requests; events are JSON-RPC notifications whose\n * `method` is a hierarchical dot-separated event name.\n */\n\nexport const BUS_ID = 'plotterExt/1'\n\nexport type JsonRpcId = string | number\n\nexport interface JsonRpcRequest {\n jsonrpc: '2.0'\n id: JsonRpcId\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcNotification {\n jsonrpc: '2.0'\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcErrorObject {\n code: number\n message: string\n data?: { reason?: string; [key: string]: unknown }\n}\n\nexport interface JsonRpcSuccessResponse {\n jsonrpc: '2.0'\n id: JsonRpcId\n result: unknown\n}\n\nexport interface JsonRpcErrorResponse {\n jsonrpc: '2.0'\n id: JsonRpcId | null\n error: JsonRpcErrorObject\n}\n\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse\n\nexport type JsonRpcMessage =\n | JsonRpcRequest\n | JsonRpcNotification\n | JsonRpcResponse\n\nexport interface Envelope {\n bus: typeof BUS_ID\n msg: JsonRpcMessage\n}\n\n/**\n * JSON-RPC reserved codes for protocol errors, plus implementation-defined\n * codes (-32000..-32099 range) used by this package. Host API errors should\n * use HOST_ERROR with a stable string identifier in `error.data.reason`.\n */\nexport const RPC_ERRORS = {\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n HOST_ERROR: -32000,\n TIMEOUT: -32001,\n CONNECTION_CLOSED: -32002\n} as const\n\nexport class RpcError extends Error {\n readonly code: number\n readonly data?: { reason?: string; [key: string]: unknown }\n\n constructor(\n message: string,\n opts: {\n code?: number\n reason?: string\n data?: Record<string, unknown>\n } = {}\n ) {\n super(message)\n this.name = 'RpcError'\n this.code = opts.code ?? RPC_ERRORS.HOST_ERROR\n const data: Record<string, unknown> = { ...(opts.data ?? {}) }\n if (opts.reason !== undefined) data.reason = opts.reason\n this.data = Object.keys(data).length > 0 ? data : undefined\n }\n\n get reason(): string | undefined {\n return typeof this.data?.reason === 'string' ? this.data.reason : undefined\n }\n\n toErrorObject(): JsonRpcErrorObject {\n return {\n code: this.code,\n message: this.message,\n ...(this.data ? { data: this.data } : {})\n }\n }\n\n static fromErrorObject(err: JsonRpcErrorObject): RpcError {\n return new RpcError(err.message, { code: err.code, data: err.data })\n }\n\n /** Normalize any thrown value into an RpcError suitable for the wire. */\n static from(err: unknown): RpcError {\n if (err instanceof RpcError) return err\n if (err instanceof Error) {\n return new RpcError(err.message, { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n return new RpcError(String(err), { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n}\n\n/** Reserved event names used to establish a connection. */\nexport const EVENT_READY = 'bus.ready'\nexport const EVENT_HANDSHAKE = 'bus.handshake'\n\n/**\n * Optional payload a caller may send with `bus.ready`. Additive and\n * backward-compatible: a host that predates it (or does not adopt caller ids)\n * ignores it.\n */\nexport interface ReadyParams {\n /**\n * Caller-asserted context id. A host that adopts caller ids — an\n * embedding-host connection, where the host does not know the caller in\n * advance — uses it as `context.id`; otherwise it is ignored.\n */\n id?: string\n}\n\nexport type ContextKind =\n | 'panel'\n | 'widget'\n | 'window'\n | 'background'\n | 'embedding-host'\n\nexport interface HandshakeContext {\n kind: ContextKind\n /** Manifest-local contribution id. */\n id: string\n /** Host-assigned unique id for this placed instance (widgets). */\n instanceId?: string | null\n /** Widget instance a configuration panel was opened for. */\n targetInstance?: string | null\n /** Manifest-local widget id of the target instance (configuration panels). */\n targetWidget?: string | null\n /** Image overlay a configuration panel was opened for (capability `overlays.image`). */\n targetOverlay?: string | null\n /** Host-assigned id of this window (`window` contexts; capability `windows`). */\n windowId?: string\n /** The `params` the window was opened with (`window` contexts). */\n params?: Record<string, unknown>\n}\n\nexport interface Handshake {\n host: string\n hostVersion: string\n apiVersion: string\n capabilities: string[]\n context: HandshakeContext\n}\n\n/** Payload of an `sk.<path>` Signal K value event. */\nexport interface SignalKValueEvent {\n path: string\n value: unknown\n timestamp?: string\n $source?: string\n}\n\nexport type StateScope = 'instance' | 'extension'\n\n/** Payload of a `state.changed` host event. */\nexport interface StateChangedEvent {\n scope: StateScope\n instanceId?: string | null\n keys: string[]\n}\n\n/**\n * Types for the `routes` capability — the routes the host currently has visible\n * on the chart (drafts plus stored routes the user is displaying), each\n * addressed by an opaque host-assigned `routeId`. Extensions read/write them and\n * follow lifecycle + mutation events (`route.visible` / `route.dirty` /\n * `route.saved` / `route.hidden`). Two orthogonal flags travel with a route:\n * `saved` (backed by a persisted resource) and `dirty` (has pending unsaved\n * changes). See the Plotter Extensions API spec, \"Live routes\".\n */\n\n/** A single point in a route edit buffer (`[lon, lat, alt?]`). */\nexport interface RoutePoint {\n position: [number, number, number?]\n name?: string\n description?: string\n /**\n * Link to a saved waypoint, as a Signal K resource path\n * (`/resources/waypoints/<id>`). A reference, not a copy: `position` alone\n * defines the route's geometry, and the host does not keep the point and the\n * waypoint in step — drop the link when moving the point off its waypoint. A\n * host built before this field existed drops it. See the Plotter Extensions\n * API spec, \"Waypoint links\".\n */\n href?: string\n}\n\n/** Snapshot of a route — result of `route.get`. */\nexport interface RouteData {\n routeId: string\n name: string | null\n /** Route-level description (distinct from a waypoint's RoutePoint.description). */\n description: string | null\n /** Monotonic revision; increments on every mutation. */\n rev: number\n /** Whether the route is backed by a persisted routes resource. */\n saved: boolean\n /** Whether the in-memory route has pending unsaved changes. */\n dirty: boolean\n points: RoutePoint[]\n}\n\n/** Summary entry in a `route.list` result (the visible set). */\nexport interface RouteSummary {\n routeId: string\n name: string | null\n rev: number\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.visible` host event — a route entered the visible set\n * (became rendered on the chart). A freshly drawn/created draft arrives\n * `saved:false, dirty:true`; a stored route brought into view arrives\n * `saved:true, dirty:false`.\n */\nexport interface RouteVisibleEvent {\n routeId: string\n rev: number\n name: string | null\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.hidden` host event — a route left the visible set.\n * `saved:true` ⇒ a stored route was made invisible (the resource is untouched);\n * `saved:false` ⇒ an unsaved draft was deleted (gone for good).\n */\nexport interface RouteHiddenEvent {\n routeId: string\n rev: number\n saved: boolean\n}\n\n/**\n * Payload of a `route.dirty` host event — the conformance-floor catch-all for\n * any structural/bulk change the host does not express granularly. A subscriber\n * should re-seed via `route.get`.\n */\nexport interface RouteDirtyEvent {\n routeId: string\n rev: number\n reason?: string\n}\n\n/**\n * Payload of a `route.saved` host event — the route's current state was\n * persisted to the routes resource collection. Arrives `saved:true,\n * dirty:false`; the route stays visible under the same `routeId`.\n */\nexport interface RouteSavedEvent {\n routeId: string\n rev: number\n /** Stored resource id of the persisted route. */\n href: string\n /** The route's name as persisted (may have just been set in the host's save\n * dialog), so followers can update a label without re-fetching. */\n name: string | null\n saved: boolean\n dirty: boolean\n}\n\n/** Stable `error.data.reason` strings for `route.*` host-method failures. */\nexport type RouteErrorReason =\n | 'routes.unknownId'\n | 'routes.badRequest'\n | 'routes.badRef'\n | 'routes.saveFailed'\n | 'routes.deleteFailed'\n | 'routes.saveCancelled'\n | 'routes.notSupported'\n\n/**\n * Types for the `charts` capability — a lightweight facade over the chart\n * layers the host already manages. An extension enumerates them, reads which\n * are shown and in what stacking order, and toggles visibility, opacity and\n * order. It does **not** add, create, or delete chart sources — only manages\n * charts the host already knows about. Follow changes (from any origin,\n * including the user's own chart controls) via the `chart.visibility` /\n * `chart.opacity` / `chart.order` events. With the `charts.time` sub-capability\n * a host also retargets time-varying charts (`chart.setTime`, `ChartTime`,\n * `chart.time`). See the Plotter Extensions API spec, \"Chart layers\" and\n * \"Time-varying charts\".\n */\n\n/**\n * One chart layer the host manages — an entry in a `chart.list` result. The\n * array order is the display/stacking order (index 0 = topmost/frontmost).\n */\nexport interface ChartLayer {\n /** Opaque, stable, host-assigned id. Treat as a token; never parse it. */\n id: string\n /** Human-readable chart name. */\n name: string\n /** Whether the chart is currently displayed. */\n visible: boolean\n /** Display opacity, 0..1. */\n opacity: number\n /** Best-effort source kind, e.g. 'raster' | 'vector' | 'S-57' | 'WMS'. */\n type?: string\n /**\n * Geographic extent `[west, south, east, north]`, when known. Longitudes are\n * in `[-180, 180]`; a chart that crosses the antimeridian has `west > east`.\n * See `LonLatBounds`.\n */\n bounds?: [number, number, number, number]\n /** Minimum usable zoom level, when known. */\n minZoom?: number\n /** Maximum usable zoom level, when known. */\n maxZoom?: number\n /**\n * Present only on a time-addressable chart (hosts with `charts.time`): the\n * instant currently shown and the source's timeline, as far as the host knows\n * it. Absent on a chart with no time dimension.\n */\n time?: ChartTime\n}\n\n/**\n * The time dimension of a time-varying chart (weather radar, satellite,\n * nowcast) — the `time` object on a `chart.list` entry. `value` and `current`\n * are always present; the timeline fields are best-effort metadata as the host\n * last learned them (from the chart resource or the source's capabilities),\n * not the contract's source of truth — a rolling product's newest frame moves\n * on between reads.\n */\nexport interface ChartTime {\n /** Instant currently shown (ISO 8601), or `null` for the live/current frame. */\n value: string | null\n /** Whether the source serves a live/current frame (so `null` is a valid target). */\n current: boolean\n /** Earliest instant offered (ISO 8601), when known. */\n from?: string\n /** Latest instant offered (ISO 8601), when known. */\n to?: string\n /** Milliseconds between frames, when the timeline is regular. */\n step?: number\n /** The explicit instants offered (ISO 8601), when the timeline is a list. */\n values?: string[]\n}\n\n/**\n * Params of `chart.setTime` — retarget each named time-varying chart to an\n * instant (ISO 8601) or back to its live frame (`null`). The host passes the\n * instant through to the source without snapping or clamping.\n */\nexport interface ChartSetTimeParams {\n ids: string[]\n time: string | null\n}\n\n/**\n * Payload of a `chart.time` host event — a time-varying chart was retargeted\n * (`time` is an ISO 8601 instant) or returned to live (`time: null`). Emitted\n * for every change regardless of origin; a batch `chart.setTime` emits one\n * event per changed chart.\n */\nexport interface ChartTimeEvent {\n id: string\n time: string | null\n}\n\n/**\n * Payload of a `chart.visibility` host event — one chart's display was turned\n * on or off. A batch `chart.setVisibility` emits one event per changed chart,\n * as does the user toggling a chart in the host's own chart controls.\n */\nexport interface ChartVisibilityEvent {\n id: string\n visible: boolean\n}\n\n/** Payload of a `chart.opacity` host event — one chart's opacity changed. */\nexport interface ChartOpacityEvent {\n id: string\n /** New opacity, 0..1. */\n opacity: number\n}\n\n/**\n * Payload of a `chart.order` host event — the display/stacking order changed.\n * `order` is the new full order, topmost first (the same order `chart.list`\n * returns).\n */\nexport interface ChartOrderEvent {\n order: string[]\n}\n\n/** Stable `error.data.reason` strings for `chart.*` host-method failures. */\nexport type ChartErrorReason =\n | 'charts.unknownId'\n | 'charts.badRequest'\n | 'charts.notSupported'\n | 'charts.notTemporal'\n\n/**\n * Types for the `nightMode` capability — the host's night-vision display mode\n * (a dimmed, low-blue appearance for use after dark). An extension reads the\n * current state, changes it, and follows changes so an embedded panel matches\n * the host instead of glowing white on a dark bridge. See the Plotter\n * Extensions API spec, \"Night mode\".\n */\n\n/**\n * The host's night-mode state — the `nightMode.get` result and the payload of\n * the `nightMode.changed` event.\n */\nexport interface NightModeState {\n /** Whether night mode is currently applied (the resolved state the user sees). */\n enabled: boolean\n /**\n * Whether the host is deriving `enabled` from the server's `environment.mode`\n * (`night` -> on). While `true`, `enabled` tracks the server automatically.\n */\n auto: boolean\n}\n\n/**\n * Payload of a `nightMode.changed` host event — the night-mode state changed.\n * Emitted **origin-transparently** for every change: an extension's own\n * `nightMode.set`, the user toggling the host's night-mode control, or the\n * server's `environment.mode` flipping while `auto` is on.\n */\nexport type NightModeChangedEvent = NightModeState\n\n/** Stable `error.data.reason` strings for `nightMode.*` host-method failures. */\nexport type NightModeErrorReason =\n | 'nightMode.badRequest'\n | 'nightMode.notSupported'\n\n/**\n * Types for the `map` capability — the chart viewport. An extension reads it\n * with `map.getView`, drives it with `map.center` / `map.fitBounds`, and follows\n * it with the `map.view` event. See the Plotter Extensions API spec, \"Map view\".\n */\n\n/**\n * The chart viewport — the `map.getView` result and the payload of the\n * `map.view` event, which carry the same shape.\n */\nexport interface MapView {\n /** Viewport centre as `[lon, lat]`. */\n center: [number, number]\n /** Current zoom level; may be fractional. */\n zoom: number\n /**\n * Axis-aligned lon/lat box covering what is rendered, as\n * `[west, south, east, north]`. On a host whose map can be rotated this is the\n * box containing the rotated view — \"at least this much is on screen\".\n * Longitudes are always in `[-180, 180]`, even when the chart has been panned\n * into another copy of the world; a view straddling the antimeridian has\n * `west > east`, and one showing every longitude is `-180` to `180`. See\n * `LonLatBounds`.\n */\n bounds: [number, number, number, number]\n}\n\n/**\n * Payload of a `map.view` host event — the chart viewport was panned and/or\n * zoomed. Emitted once the view has **settled** (the gesture and any kinetic\n * glide have come to rest), not continuously during the gesture, and\n * **origin-transparently**: for the user dragging the chart, the host recentring\n * on the vessel, or an extension's own `map.center` / `map.fitBounds`.\n */\nexport type MapViewEvent = MapView\n\n/**\n * Types for the `resourceGroups` capability — applying a stored resource group\n * to the host's display. Groups are ordinary Signal K resources in the `groups`\n * collection; an extension creates, edits and lists them through the server's\n * resources API. The capability adds only what the host alone can do: apply one.\n * See the Plotter Extensions API spec, \"Resource groups\".\n */\n\n/** The resource types a group can carry an instruction for. */\nexport type ResourceGroupType = 'routes' | 'waypoints' | 'regions' | 'charts'\n\n/**\n * A resource group document, as stored at `/resources/groups/{id}`. Each list\n * is an instruction for its type, with three distinct meanings:\n * - `[\"a\", …]` — display these (ids that resolve to no resource are ignored);\n * - `[]` — display none of this type;\n * - key absent — leave this type's display as it is.\n * `[]` and an absent key are **not** interchangeable.\n */\nexport interface ResourceGroup {\n name: string\n description?: string\n routes?: string[]\n waypoints?: string[]\n regions?: string[]\n charts?: string[]\n}\n\n/** Params of `resourceGroup.apply`. */\nexport interface ResourceGroupApplyParams {\n /** Id of the group in the server's `groups` collection. */\n id: string\n}\n\n/**\n * Result of `resourceGroup.apply`. `applied` lists every type for which the\n * host made a best-effort attempt to carry out the group's instruction. A type\n * the host did not act on — or whose key is absent from the group — is omitted.\n * It reports what the host **attempted**, not the resulting display.\n */\nexport interface ResourceGroupApplyResult {\n applied: ResourceGroupType[]\n}\n\n/**\n * Payload of a `resourceGroup.applied` host event — a group was applied.\n * Emitted **origin-transparently**: for an extension's `resourceGroup.apply`\n * and for the user choosing a group in the host's own UI. It reports an action,\n * not a readable state — there is no \"current group\" query.\n */\nexport interface ResourceGroupAppliedEvent extends ResourceGroupApplyResult {\n /** Id of the group that was applied. */\n id: string\n}\n\n/** Stable `error.data.reason` strings for `resourceGroup.*` host-method failures. */\nexport type ResourceGroupErrorReason =\n | 'resourceGroups.unknownId'\n | 'resourceGroups.fetchFailed'\n | 'resourceGroups.badRequest'\n | 'resourceGroups.notSupported'\n\n/**\n * Types for the `windows` capability — an extension's iframe panels shown as\n * floating windows over the chart. Each window is its own context\n * (`context.kind === 'window'`) carrying `windowId` and `params`. Geometry the\n * extension sends is a request; the host clamps it and reports the actual\n * `bounds` in the window state. See the Plotter Extensions API spec, \"Windows\".\n */\n\n/** Where a window is anchored in the host's window area. */\nexport type WindowAnchor =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'center-left'\n | 'center'\n | 'center-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right'\n\n/**\n * A length: CSS pixels as a number, or a percentage of the window area as a\n * string such as `\"40%\"`.\n */\nexport type WindowLength = number | `${number}%`\n\n/**\n * Requested size and position, relative to the host's window area. `offset` is\n * measured inward from the anchored edges (on a centered axis, positive moves\n * right/down). Fields left out take the host's choice (on open) or keep their\n * current value (on update).\n */\nexport interface WindowGeometry {\n anchor?: WindowAnchor\n offset?: { x?: WindowLength; y?: WindowLength }\n width?: WindowLength\n height?: WindowLength\n minWidth?: WindowLength\n minHeight?: WindowLength\n maxWidth?: WindowLength\n maxHeight?: WindowLength\n}\n\n/** Params of `ui.openWindow`. */\nexport interface WindowOpenParams {\n /** Id of an iframe panel in the caller's manifest. */\n panel: string\n /** Handed to the window in its handshake as `context.params`. */\n params?: Record<string, unknown>\n /** Title-bar text; defaults to the panel's `title`. */\n title?: string\n geometry?: WindowGeometry\n /** Block the chart and every other window until closed. Default `false`. */\n modal?: boolean\n /** Whether the user may resize it. Default `true`. */\n resizable?: boolean\n /** Whether the user may move it. Default `true`. */\n movable?: boolean\n /** Default `'fixed'`; `'autoHide'` hides the title bar when idle. */\n titleBar?: 'fixed' | 'autoHide'\n /** What the user's close control does. Default `'close'`. */\n userClose?: 'close' | 'hide'\n /** `false` opens the window hidden (loaded, not shown). Default `true`. */\n visible?: boolean\n /** Reuse an open window of this panel instead of opening another. */\n single?: boolean\n /** Remember this window's geometry under this key (per extension). */\n restoreKey?: string\n}\n\n/** Params of `ui.updateWindow`. `windowId` defaults to self in a window context. */\nexport interface WindowUpdateParams {\n windowId?: string\n title?: string\n geometry?: WindowGeometry\n visible?: boolean\n}\n\n/** A window's actual position and size, in CSS px from the window area's top-left. */\nexport interface WindowBounds {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** The size of the host's window area, in CSS px. */\nexport interface WindowArea {\n width: number\n height: number\n}\n\n/** How the host is showing a window. */\nexport type WindowPresentation = 'floating' | 'sheet' | 'fullscreen'\n\n/**\n * A window's state — the result of `ui.openWindow` / `ui.updateWindow`, an\n * entry of `ui.listWindows`, and the payload of `window.state`.\n */\nexport interface WindowState {\n windowId: string\n /** The manifest panel the window shows. */\n panel: string\n title: string\n presentation: WindowPresentation\n bounds: WindowBounds\n area: WindowArea\n visible: boolean\n /** Host feature: collapsed to its title bar (still running). */\n collapsed: boolean\n /** Host feature: popped out into a separate browser window. */\n poppedOut: boolean\n modal: boolean\n}\n\n/** Result of `ui.listWindows` — the caller's extension's open windows. */\nexport interface WindowListResult {\n windows: WindowState[]\n}\n\n/** Payload of a `window.bounds` host event — a window's actual geometry changed. */\nexport interface WindowBoundsEvent {\n windowId: string\n bounds: WindowBounds\n area: WindowArea\n}\n\n/** Payload of a `window.state` host event (visibility, collapse, pop-out, presentation or title changed). */\nexport type WindowStateEvent = WindowState\n\n/** Why a window closed. */\nexport type WindowCloseReason = 'user' | 'extension' | 'host'\n\n/** Payload of a `window.closed` host event — the window closed and its page unloaded. */\nexport interface WindowClosedEvent {\n windowId: string\n reason: WindowCloseReason\n}\n\n/** Stable `error.data.reason` strings for window host-method failures. */\nexport type WindowErrorReason =\n | 'windows.badRequest'\n | 'windows.unknownId'\n | 'windows.limit'\n | 'windows.modalOpen'\n | 'windows.notSupported'\n | 'UNKNOWN_PANEL'\n\n/**\n * Types for the `overlays.image` capability — map layers whose pixels an\n * extension's background runtime draws and the host composites. The host asks\n * for frames (`overlay.request`); the runtime answers with `overlay.frame`,\n * whose `image` is an `ImageBitmap` sent in the `postMessage` transfer list —\n * the one non-JSON value on the wire. See the Plotter Extensions API spec,\n * \"Map overlays\".\n */\n\n/** Where an overlay sits relative to the host's own content. */\nexport type OverlayBand = 'aboveCharts' | 'aboveResources'\n\n/** An overlay's current state — an entry of `overlay.list`. */\nexport interface OverlayInfo {\n /** The overlay's manifest id. */\n id: string\n title: string\n band: OverlayBand\n visible: boolean\n /** 0..1 */\n opacity: number\n /** The host has paused the overlay to stay within its budget. */\n suspended: boolean\n}\n\n/** Result of `overlay.list` — the caller's extension's overlays. */\nexport interface OverlayListResult {\n overlays: OverlayInfo[]\n}\n\n/**\n * Payload of an `overlay.request` host event — the host wants a frame. The\n * image covers `extent`, a north-up axis-aligned box in EPSG:3857 metres\n * `[minX, minY, maxX, maxY]` (it may run past ±20037508.34 across the\n * antimeridian), with pixels spaced evenly in Mercator; row 0 is the north\n * edge.\n */\nexport interface OverlayRequestEvent {\n overlayId: string\n requestId: string\n extent: [number, number, number, number]\n /** The same box as lon/lat `[west, south, east, north]` (west > east across the antimeridian). */\n bounds: [number, number, number, number]\n /** Image size in pixels. */\n width: number\n height: number\n /** Image pixels per CSS pixel, chosen by the host. */\n pixelRatio: number\n /** Metres per image pixel. */\n resolution: number\n zoom: number\n /** Map rotation, radians clockwise. Advisory: for keeping labels upright only. */\n rotation: number\n /**\n * Least time, in ms, the host leaves between live frames it accepts. Absent\n * when the host does not say; a faster live frame is refused as\n * `overlays.throttled`.\n */\n liveInterval?: number\n}\n\n/** Params of `overlay.frame`. */\nexport interface OverlayFrameParams {\n overlayId: string\n /** The request this frame answers; omit for a live update of the latest request's extent. */\n requestId?: string\n extent: [number, number, number, number]\n /** Transparent, premultiplied-alpha image of the requested size; transferred. */\n image: ImageBitmap\n}\n\n/** Why the host refused a frame (a result, not an error). */\nexport type OverlayFrameRefusal = 'overlays.superseded' | 'overlays.throttled'\n\n/** Result of `overlay.frame`. */\nexport interface OverlayFrameResult {\n accepted: boolean\n reason?: OverlayFrameRefusal\n}\n\n/** Payload of an `overlay.state` host event — an overlay was shown, hidden, faded, suspended or resumed. */\nexport interface OverlayStateEvent {\n overlayId: string\n visible: boolean\n opacity: number\n suspended: boolean\n}\n\n/** Stable `error.data.reason` strings for `overlay.*` host-method failures. */\nexport type OverlayErrorReason =\n | 'overlays.unknownId'\n | 'overlays.badRequest'\n | 'overlays.notSupported'\n\n/**\n * Types for the `panels.state` capability — whether each of an extension's\n * loaded panels is on screen. `visible` is the host's presentation (its drawer\n * showing the panel, its dialog open), not the browser tab's visibility. A\n * panel shown in a window is a `window` context and is not covered here. See\n * the Plotter Extensions API spec, \"Panel state\".\n */\n\n/** A loaded panel's state — an entry of `ui.listPanels` and the payload of `panel.state`. */\nexport interface PanelState {\n /** The panel's manifest id. */\n panel: string\n /** Whether the host is presenting the panel in its UI. */\n visible: boolean\n /** Host feature: shown reduced to its header (still `visible`). Always `false` without it. */\n collapsed: boolean\n /** Configuration panels opened for a widget instance: the instance being configured. */\n targetInstance?: string\n /** Configuration panels opened for an image overlay: the overlay being configured. */\n targetOverlay?: string\n}\n\n/** Result of `ui.listPanels` — the caller's extension's loaded panels. */\nexport interface PanelListResult {\n panels: PanelState[]\n}\n\n/** Payload of a `panel.state` host event — a loaded panel was shown, hidden, collapsed or expanded. */\nexport type PanelStateEvent = PanelState\n\n/** Stable `error.data.reason` strings for `ui.listPanels` failures. */\nexport type PanelErrorReason = 'panels.notSupported'\n\n/**\n * Types for the `events.publish` capability — extension-originated events. A\n * context publishes a topic and the host delivers it, exactly like a host\n * event, to every subscribed context in `scope` (the publisher included). The\n * delivered event carries no sender identity. The `publish` button action\n * (deprecated alias `sendMessage`) is the same operation. See the Plotter\n * Extensions API spec, \"Publishing events\".\n */\n\n/** Who may receive a published event. */\nexport type PublishScope = 'all' | 'extension'\n\n/** Params of `events.publish`. */\nexport interface PublishParams {\n /** Literal event name: non-empty, no `*`, not in the `bus.*` namespace. */\n topic: string\n /** Delivered unchanged as the event's `params`. */\n params?: unknown\n /** Default `'all'`. */\n scope?: PublishScope\n}\n\n/** Stable `error.data.reason` strings for `events.publish` failures. */\nexport type PublishErrorReason = 'events.badRequest'\n", "/**\n * eventemitter2-style wildcard matching for dot-separated event names.\n *\n * - `*` matches exactly one segment.\n * - `**` matches zero or more segments (any remainder when trailing).\n */\nexport function matchesPattern(pattern: string, name: string): boolean {\n if (pattern === name) return true\n return match(pattern.split('.'), 0, name.split('.'), 0)\n}\n\nfunction match(p: string[], pi: number, n: string[], ni: number): boolean {\n while (pi < p.length) {\n const seg = p[pi]\n if (seg === '**') {\n if (pi === p.length - 1) return true\n for (let skip = ni; skip <= n.length; skip++) {\n if (match(p, pi + 1, n, skip)) return true\n }\n return false\n }\n if (ni >= n.length) return false\n if (seg !== '*' && seg !== n[ni]) return false\n pi++\n ni++\n }\n return ni === n.length\n}\n\nexport function matchesAny(patterns: Iterable<string>, name: string): boolean {\n for (const pattern of patterns) {\n if (matchesPattern(pattern, name)) return true\n }\n return false\n}\n", "/**\n * Lon/lat bounding boxes, as every box in the Plotter Extensions API is written:\n * `[west, south, east, north]` in decimal degrees (the GeoJSON / RFC 7946 §5\n * order), with longitudes in `[-180, 180]`. A box that crosses the antimeridian\n * has `west` greater than `east` (RFC 7946 §5.2): `[175, -21, -175, -13]` is ten\n * degrees wide around 180°. See the spec, \"Bounding boxes\".\n *\n * Map engines work in the *unwrapped* form instead — `west < east`, with\n * longitudes running past ±180 once the view scrolls into another copy of the\n * world. {@link normalizeBounds} converts either form to the API's.\n */\nexport type LonLatBounds = [number, number, number, number]\n\n/** `lon` wrapped into `[-180, 180]`; a longitude already in range is kept. */\nfunction wrapLon(lon: number): number {\n if (lon >= -180 && lon <= 180) {\n return lon\n }\n return ((((lon + 180) % 360) + 360) % 360) - 180\n}\n\n/**\n * Degrees of longitude a box in the API's form spans, 0–360: `east - west`, or\n * the way round the antimeridian when `west > east`. A box from `-180` to `180`\n * spans 360.\n */\nexport function boundsLonSpan(bounds: LonLatBounds): number {\n const [west, , east] = bounds\n return east >= west ? east - west : east - west + 360\n}\n\n/** Whether a box in the API's form takes in longitude `lon`. */\nexport function boundsContainsLon(bounds: LonLatBounds, lon: number): boolean {\n const [west, , east] = bounds\n const l = wrapLon(lon)\n return west <= east ? l >= west && l <= east : l >= west || l <= east\n}\n\n/**\n * A box in the API's form, from either that form or the unwrapped form map\n * engines use (`east` past 180 or `west` past -180, with `west < east`).\n * Longitudes are wrapped into `[-180, 180]`, so a box straddling 180° comes out\n * with `west > east`; a box 360 or more degrees wide becomes\n * `[-180, south, 180, north]`. Undefined when it is not a box: not four finite\n * numbers, a latitude outside `[-90, 90]`, or `south` greater than `north`.\n */\nexport function normalizeBounds(bounds: unknown): LonLatBounds | undefined {\n if (\n !Array.isArray(bounds) ||\n bounds.length !== 4 ||\n !bounds.every((v) => typeof v === 'number' && Number.isFinite(v))\n ) {\n return undefined\n }\n const [west, south, east, north] = bounds as LonLatBounds\n if (south < -90 || north > 90 || south > north) {\n return undefined\n }\n const w = wrapLon(west)\n const e = wrapLon(east)\n const span = west <= east ? east - west : boundsLonSpan([w, south, e, north])\n if (span >= 360) {\n return [-180, south, 180, north]\n }\n return [w, south, e, north]\n}\n", "import {\n BUS_ID,\n Envelope,\n JsonRpcMessage,\n JsonRpcNotification,\n JsonRpcRequest,\n JsonRpcResponse\n} from './protocol'\n\nexport function wrap(msg: JsonRpcMessage): Envelope {\n return { bus: BUS_ID, msg }\n}\n\n/**\n * Returns the JSON-RPC message inside a bus envelope, or null when the data\n * is not valid protocol traffic (so unrelated postMessage noise is ignored).\n */\nexport function unwrap(data: unknown): JsonRpcMessage | null {\n if (typeof data !== 'object' || data === null) return null\n const env = data as Record<string, unknown>\n if (env.bus !== BUS_ID) return null\n return isJsonRpcMessage(env.msg) ? env.msg : null\n}\n\nexport function isJsonRpcMessage(v: unknown): v is JsonRpcMessage {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n if (m.jsonrpc !== '2.0') return false\n if (typeof m.method === 'string') {\n // Request (with id) or notification (without).\n return (\n m.id === undefined ||\n typeof m.id === 'string' ||\n typeof m.id === 'number'\n )\n }\n // Response: id required (null allowed for unroutable errors), exactly one\n // of result / error.\n const idOk =\n typeof m.id === 'string' || typeof m.id === 'number' || m.id === null\n if (!idOk) return false\n const hasResult = 'result' in m\n const err = m.error as Record<string, unknown> | undefined\n const hasError =\n typeof err === 'object' &&\n err !== null &&\n typeof err.code === 'number' &&\n typeof err.message === 'string'\n return hasResult ? !('error' in m) : hasError\n}\n\nexport function isRequest(msg: JsonRpcMessage): msg is JsonRpcRequest {\n return 'method' in msg && 'id' in msg && msg.id !== undefined\n}\n\nexport function isNotification(\n msg: JsonRpcMessage\n): msg is JsonRpcNotification {\n return 'method' in msg && (!('id' in msg) || msg.id === undefined)\n}\n\nexport function isResponse(msg: JsonRpcMessage): msg is JsonRpcResponse {\n return !('method' in msg)\n}\n", "/**\n * Transport abstraction. The bus runs over anything that can post and\n * receive structured-cloneable data: window.postMessage between a host page\n * and an iframe, a MessageChannel port, or a test double.\n */\nexport interface BusPort {\n /**\n * Send `data`. `transfer` lists objects (an `ImageBitmap`, an `ArrayBuffer`)\n * to move rather than copy; after the post the sender no longer owns them.\n */\n post(data: unknown, transfer?: Transferable[]): void\n /** Register a receive handler; returns an unlisten function. */\n listen(handler: (data: unknown) => void): () => void\n}\n\nexport interface WindowPortOptions {\n /**\n * targetOrigin for outgoing postMessage and required origin of incoming\n * messages. Defaults to the listening window's own origin (the baseline\n * deployment is same-origin). Pass '*' to disable origin checks.\n */\n origin?: string\n /** Window whose 'message' events are listened to. Defaults to globalThis. */\n listenWindow?: Window\n}\n\n/**\n * A port over window.postMessage to a peer window. On the host side the peer\n * is an iframe's contentWindow; on the extension side it is window.parent.\n * Only messages whose source is the peer (and whose origin matches) are\n * delivered.\n */\nexport function windowPort(peer: Window, opts: WindowPortOptions = {}): BusPort {\n const listenWindow =\n opts.listenWindow ?? (globalThis as unknown as Window)\n const origin = opts.origin ?? listenWindow.location?.origin ?? '*'\n return {\n post(data, transfer) {\n if (transfer?.length) {\n peer.postMessage(data, { targetOrigin: origin, transfer })\n } else {\n peer.postMessage(data, origin)\n }\n },\n listen(handler) {\n const fn = (ev: MessageEvent) => {\n if (ev.source !== peer) return\n if (origin !== '*' && ev.origin !== origin) return\n handler(ev.data)\n }\n listenWindow.addEventListener('message', fn as EventListener)\n return () =>\n listenWindow.removeEventListener('message', fn as EventListener)\n }\n }\n}\n\ninterface MessagePortLike {\n postMessage(data: unknown, transfer?: Transferable[]): void\n addEventListener(type: 'message', fn: (ev: { data: unknown }) => void): void\n removeEventListener(\n type: 'message',\n fn: (ev: { data: unknown }) => void\n ): void\n start?(): void\n}\n\n/** A port over a MessageChannel/MessagePort (browser or Node). */\nexport function messagePort(port: MessagePortLike): BusPort {\n return {\n post(data, transfer) {\n if (transfer?.length) {\n port.postMessage(data, transfer)\n } else {\n port.postMessage(data)\n }\n },\n listen(handler) {\n const fn = (ev: { data: unknown }) => handler(ev.data)\n port.addEventListener('message', fn)\n port.start?.()\n return () => port.removeEventListener('message', fn)\n }\n }\n}\n", "import {\n isNotification,\n isRequest,\n isResponse,\n unwrap,\n wrap\n} from './codec'\nimport {\n JsonRpcId,\n JsonRpcMessage,\n JsonRpcResponse,\n RPC_ERRORS,\n RpcError\n} from './protocol'\nimport { BusPort } from './port'\nimport { matchesAny } from './wildcard'\n\nexport interface MethodContext {\n endpoint: BusEndpoint\n}\n\nexport type MethodHandler = (\n params: unknown,\n ctx: MethodContext\n) => unknown | Promise<unknown>\n\nexport type EventHandler = (name: string, params: unknown) => void\n\nexport interface BusEndpointOptions {\n port: BusPort\n /** Default timeout for outgoing calls. */\n callTimeoutMs?: number\n /** Reporter for handler/dispatch errors. Defaults to console.warn. */\n onError?: (err: unknown) => void\n}\n\ninterface PendingCall {\n resolve: (value: unknown) => void\n reject: (err: RpcError) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000\n\n/**\n * One end of the bus: sends/receives envelopes over a BusPort, dispatches\n * incoming requests to registered methods, correlates responses to pending\n * calls by per-call nonce, and fans incoming notifications out to wildcard\n * event handlers. Symmetric — both host and extension build on this.\n */\nexport class BusEndpoint {\n readonly callTimeoutMs: number\n\n private readonly port: BusPort\n private readonly unlisten: () => void\n private readonly onError: (err: unknown) => void\n private readonly pending = new Map<JsonRpcId, PendingCall>()\n private readonly methods = new Map<string, MethodHandler>()\n private readonly eventHandlers = new Set<{\n patterns: string[]\n fn: EventHandler\n }>()\n private readonly idPrefix = Math.random().toString(36).slice(2, 8)\n private seq = 0\n private closed = false\n\n constructor(opts: BusEndpointOptions) {\n this.port = opts.port\n this.callTimeoutMs = opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS\n this.onError =\n opts.onError ??\n ((err) => console.warn('[plotterext-bus]', err))\n this.unlisten = this.port.listen((data) => this.onData(data))\n }\n\n registerMethod(name: string, handler: MethodHandler): void {\n this.methods.set(name, handler)\n }\n\n unregisterMethod(name: string): void {\n this.methods.delete(name)\n }\n\n /**\n * Handle incoming notifications whose names match any of the wildcard\n * patterns. Returns an unsubscribe function. This is local dispatch only;\n * telling the peer which events to forward is a separate concern\n * (`events.subscribe`).\n */\n onEvent(patterns: string[], fn: EventHandler): () => void {\n const entry = { patterns, fn }\n this.eventHandlers.add(entry)\n return () => this.eventHandlers.delete(entry)\n }\n\n /**\n * Send a notification (an event) to the peer. `opts.transfer` moves the\n * listed objects instead of copying them.\n */\n notify(\n method: string,\n params?: unknown,\n opts: { transfer?: Transferable[] } = {}\n ): void {\n this.send(\n { jsonrpc: '2.0', method, ...(params !== undefined ? { params } : {}) },\n opts.transfer\n )\n }\n\n /**\n * Call a method on the peer; resolves with its result. `opts.transfer`\n * moves the listed objects (e.g. an `ImageBitmap` inside `params`) instead\n * of copying them.\n */\n call(\n method: string,\n params?: unknown,\n opts: { timeoutMs?: number; transfer?: Transferable[] } = {}\n ): Promise<unknown> {\n if (this.closed) {\n return Promise.reject(\n new RpcError('Bus endpoint is closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n const id = `${this.idPrefix}-${++this.seq}`\n const timeoutMs = opts.timeoutMs ?? this.callTimeoutMs\n return new Promise<unknown>((resolve, reject) => {\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n this.pending.delete(id)\n reject(\n new RpcError(`Call timed out after ${timeoutMs}ms: ${method}`, {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'TIMEOUT'\n })\n )\n }, timeoutMs)\n : null\n this.pending.set(id, { resolve, reject, timer })\n this.send(\n {\n jsonrpc: '2.0',\n id,\n method,\n ...(params !== undefined ? { params } : {})\n },\n opts.transfer\n )\n })\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.unlisten()\n for (const [, p] of this.pending) {\n if (p.timer) clearTimeout(p.timer)\n p.reject(\n new RpcError('Bus endpoint closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n this.pending.clear()\n this.eventHandlers.clear()\n }\n\n private send(msg: JsonRpcMessage, transfer?: Transferable[]): void {\n if (this.closed) return\n this.port.post(wrap(msg), transfer)\n }\n\n private onData(data: unknown): void {\n const msg = unwrap(data)\n if (!msg) return\n if (isResponse(msg)) {\n this.onResponse(msg)\n } else if (isRequest(msg)) {\n void this.onRequest(msg)\n } else if (isNotification(msg)) {\n this.onNotification(msg.method, msg.params)\n }\n }\n\n private onResponse(msg: JsonRpcResponse): void {\n if (msg.id === null) return\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (p.timer) clearTimeout(p.timer)\n if ('error' in msg) {\n p.reject(RpcError.fromErrorObject(msg.error))\n } else {\n p.resolve(msg.result)\n }\n }\n\n private async onRequest(msg: {\n id: JsonRpcId\n method: string\n params?: unknown\n }): Promise<void> {\n const handler = this.methods.get(msg.method)\n if (!handler) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: {\n code: RPC_ERRORS.METHOD_NOT_FOUND,\n message: `Method not found: ${msg.method}`\n }\n })\n return\n }\n try {\n const result = await handler(msg.params, { endpoint: this })\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n result: result === undefined ? null : result\n })\n } catch (err) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: RpcError.from(err).toErrorObject()\n })\n }\n }\n\n private onNotification(name: string, params: unknown): void {\n for (const entry of [...this.eventHandlers]) {\n if (matchesAny(entry.patterns, name)) {\n try {\n entry.fn(name, params)\n } catch (err) {\n this.onError(err)\n }\n }\n }\n }\n}\n", "import { BusEndpoint, EventHandler } from './endpoint'\nimport { BusPort, windowPort } from './port'\nimport {\n ChartLayer,\n EVENT_HANDSHAKE,\n EVENT_READY,\n Handshake,\n HandshakeContext,\n MapView,\n NightModeState,\n OverlayFrameParams,\n OverlayFrameResult,\n OverlayInfo,\n OverlayListResult,\n PanelListResult,\n PanelState,\n PublishScope,\n ReadyParams,\n ResourceGroupApplyResult,\n RouteData,\n RoutePoint,\n RouteSummary,\n RPC_ERRORS,\n RpcError,\n SignalKValueEvent,\n StateScope,\n WindowListResult,\n WindowOpenParams,\n WindowState,\n WindowUpdateParams\n} from './protocol'\n\nexport interface ConnectOptions {\n /** Transport. Defaults to window.postMessage to window.parent. */\n port?: BusPort\n /**\n * Caller-asserted context id, sent in the `bus.ready` payload. A host that\n * adopts caller ids (an embedding-host connection) uses it as `context.id`;\n * standard extension hosts ignore it. Omit for the standard extension case.\n */\n id?: string\n /** Interval for re-sending bus.ready until the handshake arrives. */\n readyIntervalMs?: number\n /** How long to wait for the host handshake before rejecting. */\n timeoutMs?: number\n /** Default timeout for host API calls. */\n callTimeoutMs?: number\n onError?: (err: unknown) => void\n}\n\nexport type Unsubscribe = () => Promise<void>\n\n/**\n * The extension side of the bus: one per iframe context (panel, widget, or\n * background runtime). Create via connectExtension().\n */\nexport class ExtensionClient {\n readonly handshake: Handshake\n readonly endpoint: BusEndpoint\n\n constructor(endpoint: BusEndpoint, handshake: Handshake) {\n this.endpoint = endpoint\n this.handshake = handshake\n }\n\n get context(): HandshakeContext {\n return this.handshake.context\n }\n\n get apiVersion(): string {\n return this.handshake.apiVersion\n }\n\n get capabilities(): string[] {\n return this.handshake.capabilities\n }\n\n hasCapability(id: string): boolean {\n return this.handshake.capabilities.includes(id)\n }\n\n /**\n * Call a host API method. `opts.transfer` moves the listed objects instead\n * of copying them — only `overlay.frame` needs it in this version.\n */\n call(\n method: string,\n params?: unknown,\n opts?: { timeoutMs?: number; transfer?: Transferable[] }\n ): Promise<unknown> {\n return this.endpoint.call(method, params, opts)\n }\n\n /** Send a notification to the host. */\n notify(method: string, params?: unknown): void {\n this.endpoint.notify(method, params)\n }\n\n /**\n * Subscribe to host events matching wildcard patterns. Registers both the\n * host-side forwarding subscription and local dispatch; the returned\n * function tears down both.\n */\n async subscribe(\n patterns: string[],\n handler: EventHandler\n ): Promise<Unsubscribe> {\n const off = this.endpoint.onEvent(patterns, handler)\n let subscriptionId: string\n try {\n const result = (await this.call('events.subscribe', { patterns })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n off()\n throw err\n }\n return async () => {\n off()\n await this.call('events.unsubscribe', { subscriptionId }).catch(() => {\n // Best-effort: the host may already have dropped the connection.\n })\n }\n }\n\n /**\n * Publish an event onto the bus (capability `events.publish`). The host\n * delivers it to every context in `scope` whose subscriptions match —\n * including this one — exactly as it delivers host events. `scope` defaults\n * to `'all'` (any extension); `'extension'` keeps it to this extension's own\n * contexts. Resolves once the host has delivered it; there is no reply.\n */\n async publish(\n topic: string,\n params?: unknown,\n scope?: PublishScope\n ): Promise<void> {\n await this.call('events.publish', {\n topic,\n ...(params !== undefined ? { params } : {}),\n ...(scope ? { scope } : {})\n })\n }\n\n /** Host-persisted key/value state (see spec: State Storage). */\n readonly state = {\n get: async (\n keys?: string[],\n scope?: StateScope\n ): Promise<Record<string, unknown>> => {\n const result = (await this.call('state.get', {\n ...(scope ? { scope } : {}),\n ...(keys ? { keys } : {})\n })) as { values: Record<string, unknown> }\n return result.values ?? {}\n },\n set: async (\n values: Record<string, unknown>,\n scope?: StateScope\n ): Promise<void> => {\n await this.call('state.set', {\n ...(scope ? { scope } : {}),\n values\n })\n }\n }\n\n /** Signal K data relayed by the host (capabilities signalk.stream / .put). */\n readonly signalk = {\n /**\n * Subscribe to Signal K path values. The host publishes them as\n * `sk.<path>` events; this helper hides the event-name mapping and\n * establishes both the event-forwarding subscription and the host's\n * upstream Signal K subscription.\n */\n subscribe: async (\n paths: string[],\n handler: (ev: SignalKValueEvent) => void\n ): Promise<Unsubscribe> => {\n const patterns = paths.map((p) => `sk.${p}`)\n const offEvents = await this.subscribe(patterns, (_name, params) =>\n handler(params as SignalKValueEvent)\n )\n let subscriptionId: string\n try {\n const result = (await this.call('signalk.subscribe', { paths })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n await offEvents()\n throw err\n }\n return async () => {\n await offEvents()\n await this.call('signalk.unsubscribe', { subscriptionId }).catch(\n () => {}\n )\n }\n },\n put: (path: string, value: unknown): Promise<unknown> => {\n return this.call('signalk.put', { path, value })\n }\n }\n\n /**\n * The host's visible routes (capability `routes`). Thin **typed** wrappers over\n * the host's `route.*` methods — each just delegates to `this.call(...)`, so a\n * plain-JS extension can call `client.call('route.replace', …)` directly and a\n * TypeScript extension gets the typed `client.route.replace(…)` sugar with no\n * behavioural difference. Follow lifecycle + mutations by subscribing to\n * `route.**` events (`RouteVisibleEvent` / `RouteDirtyEvent` / `RouteSavedEvent`\n * / `RouteHiddenEvent`). Further operations (rename/point ops) extend this\n * surface as the capability fills out.\n */\n readonly route = {\n list: async (): Promise<RouteSummary[]> => {\n const result = (await this.call('route.list')) as {\n routes?: RouteSummary[]\n }\n return result.routes ?? []\n },\n create: async (opts: {\n points: RoutePoint[]\n name?: string\n description?: string\n }): Promise<{ routeId: string; rev: number }> => {\n // A route needs at least two points to form a segment; the host rejects\n // fewer with routes.badRequest.\n return (await this.call('route.create', opts)) as {\n routeId: string\n rev: number\n }\n },\n get: async (routeId: string): Promise<RouteData> => {\n return (await this.call('route.get', { routeId })) as RouteData\n },\n replace: async (\n routeId: string,\n points: RoutePoint[]\n ): Promise<{ rev: number }> => {\n return (await this.call('route.replace', { routeId, points })) as {\n rev: number\n }\n },\n save: async (\n routeId: string,\n opts?: { name?: string; description?: string; dialog?: boolean }\n ): Promise<{ href: string; rev: number }> => {\n return (await this.call('route.save', {\n routeId,\n ...(opts ?? {})\n })) as { href: string; rev: number }\n },\n show: async (ref: string): Promise<{ routeId: string; rev: number }> => {\n return (await this.call('route.show', { ref })) as {\n routeId: string\n rev: number\n }\n },\n hide: async (routeId: string): Promise<void> => {\n await this.call('route.hide', { routeId })\n },\n delete: async (routeId: string): Promise<void> => {\n await this.call('route.delete', { routeId })\n }\n }\n\n /**\n * The host's chart layers (capability `charts`). Thin **typed** wrappers over\n * the host's `chart.*` methods — a lightweight facade over the charts the host\n * already manages: enumerate them, toggle visibility, opacity and stacking\n * order (all batch). It does not create, add, or delete chart sources. Follow\n * changes (from any origin, including the host's own chart controls) by\n * subscribing to `chart.**` events (`ChartVisibilityEvent` / `ChartOpacityEvent`\n * / `ChartOrderEvent` / `ChartTimeEvent`) and re-reading `chart.list` where\n * needed. As with every wrapper, a plain-JS extension can call\n * `client.call('chart.list', …)` directly with no behavioural difference.\n */\n readonly chart = {\n list: async (): Promise<ChartLayer[]> => {\n const result = (await this.call('chart.list')) as { charts?: ChartLayer[] }\n return result.charts ?? []\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('chart.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('chart.setOpacity', { ids, opacity })\n },\n setOrder: async (order: string[]): Promise<void> => {\n await this.call('chart.setOrder', { order })\n },\n /**\n * Retarget time-varying charts to an ISO 8601 instant, or back to their\n * live frame with `null` (sub-capability `charts.time`). Follow changes via\n * the `chart.time` event (`ChartTimeEvent`).\n */\n setTime: async (ids: string[], time: string | null): Promise<void> => {\n await this.call('chart.setTime', { ids, time })\n }\n }\n\n /**\n * The host's night-vision display mode (capability `nightMode`). Read the\n * current `{ enabled, auto }` state, change it, and follow changes by\n * subscribing to the `nightMode.changed` event (`NightModeChangedEvent`).\n * `set` expresses the three states the spec defines: force on\n * (`{ enabled: true }`), force off (`{ enabled: false }`), follow the server\n * (`{ auto: true }`); setting `enabled` implies `auto: false`. As with every\n * wrapper, a plain-JS extension can call `client.call('nightMode.get' |\n * 'nightMode.set', …)` directly with no behavioural difference.\n */\n readonly nightMode = {\n get: async (): Promise<NightModeState> => {\n return (await this.call('nightMode.get')) as NightModeState\n },\n set: async (state: Partial<NightModeState>): Promise<void> => {\n await this.call('nightMode.set', state)\n }\n }\n\n /**\n * Panel visibility (capability `panels.state`): the state of each of this\n * extension's loaded panels. Follow changes with the `panel.state` event,\n * which every context of the extension receives; subscribe first, then\n * `list()`, so no change is missed. A plain-JS extension can call\n * `client.call('ui.listPanels')` with no behavioural difference.\n */\n readonly panels = {\n list: async (): Promise<PanelState[]> => {\n const r = (await this.call('ui.listPanels')) as PanelListResult\n return r.panels\n }\n }\n\n /**\n * Image map overlays (capability `overlays.image`): map layers this\n * extension's background runtime draws. The host asks for frames with the\n * `overlay.request` event (`OverlayRequestEvent`) and reports visibility\n * with `overlay.state` (`OverlayStateEvent`); the runtime answers each\n * request with `frame()`, keeping one frame in flight. `frame()` transfers\n * `image`, so the caller no longer owns it afterwards. A plain-JS extension\n * can call `client.call('overlay.frame', params, { transfer: [image] })`\n * with no behavioural difference.\n */\n readonly overlays = {\n list: async (): Promise<OverlayInfo[]> => {\n const r = (await this.call('overlay.list')) as OverlayListResult\n return r.overlays\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('overlay.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('overlay.setOpacity', { ids, opacity })\n },\n frame: async (params: OverlayFrameParams): Promise<OverlayFrameResult> => {\n return (await this.call('overlay.frame', params, {\n transfer: [params.image]\n })) as OverlayFrameResult\n }\n }\n\n /**\n * Floating windows (capability `windows`): show one of this extension's\n * iframe panels in a window over the chart. Geometry is a request — read the\n * actual `bounds` from the returned `WindowState`, and follow changes with\n * the `window.bounds` / `window.state` / `window.closed` events. Windows\n * belong to the extension, so any of its contexts may manage them; in a\n * `window` context, `update` / `focus` / `close` default to the window\n * itself when `windowId` is left out. A plain-JS extension can call\n * `client.call('ui.openWindow', …)` and friends directly with no\n * behavioural difference.\n */\n readonly windows = {\n open: async (params: WindowOpenParams): Promise<WindowState> => {\n return (await this.call('ui.openWindow', params)) as WindowState\n },\n update: async (params: WindowUpdateParams): Promise<WindowState> => {\n return (await this.call('ui.updateWindow', params)) as WindowState\n },\n focus: async (windowId?: string): Promise<void> => {\n await this.call('ui.focusWindow', windowId === undefined ? {} : { windowId })\n },\n close: async (windowId?: string): Promise<void> => {\n await this.call('ui.closeWindow', windowId === undefined ? {} : { windowId })\n },\n list: async (): Promise<WindowState[]> => {\n const r = (await this.call('ui.listWindows')) as WindowListResult\n return r.windows\n }\n }\n\n /**\n * Apply a stored resource group to the host's display (capability\n * `resourceGroups`). Resolves to `{ applied }` — the types the host made a\n * best-effort attempt to carry out; a type it did not act on is omitted.\n * Follow applies from any origin (including the user's own group picker) via\n * the `resourceGroup.applied` event (`ResourceGroupAppliedEvent`). Group CRUD\n * is not here: use the server's resources API (`/resources/groups`). A\n * plain-JS extension can call `client.call('resourceGroup.apply', { id })`\n * directly with no behavioural difference.\n */\n readonly resourceGroup = {\n apply: async (id: string): Promise<ResourceGroupApplyResult> => {\n return (await this.call('resourceGroup.apply', {\n id\n })) as ResourceGroupApplyResult\n }\n }\n\n /**\n * The host's chart viewport (capability `map`). Read the current\n * `{ center, zoom, bounds }`, drive it, and follow it by subscribing to the\n * `map.view` event (`MapViewEvent`), which carries the same shape and fires\n * once per settled pan/zoom. Seed with `getView` so a follower has a view\n * before the first change. As with every wrapper, a plain-JS extension can\n * call `client.call('map.getView' | 'map.center' | 'map.fitBounds', …)`\n * directly with no behavioural difference.\n */\n readonly map = {\n getView: async (): Promise<MapView> => {\n return (await this.call('map.getView')) as MapView\n },\n center: async (\n position: [number, number],\n zoom?: number\n ): Promise<void> => {\n await this.call('map.center', {\n position,\n ...(typeof zoom === 'number' ? { zoom } : {})\n })\n },\n /**\n * Fit the chart to `[west, south, east, north]`. A box crossing the\n * antimeridian may be given as `west > east` (the API's form) or unwrapped\n * (`east` past 180); the host fits it the short way round.\n */\n fitBounds: async (\n bounds: [number, number, number, number]\n ): Promise<void> => {\n await this.call('map.fitBounds', { bounds })\n }\n }\n\n close(): void {\n this.endpoint.close()\n }\n}\n\n/**\n * Connect to the host from inside an extension iframe. Sends `bus.ready`\n * (repeating until answered) and resolves once the host's `bus.handshake`\n * arrives.\n */\nexport function connectExtension(\n opts: ConnectOptions = {}\n): Promise<ExtensionClient> {\n // Default transport: postMessage to the embedding window. Origin checks\n // are relaxed to '*' on the extension side because the host page's origin\n // may legitimately differ from the extension asset origin (e.g. a host dev\n // server embedding extension assets served by the Signal K server); the\n // peer-source check in windowPort still applies, and the host side\n // enforces a strict origin for the extension's frame.\n const port =\n opts.port ??\n windowPort((globalThis as unknown as Window).parent as Window, {\n origin: '*'\n })\n const endpoint = new BusEndpoint({\n port,\n callTimeoutMs: opts.callTimeoutMs,\n onError: opts.onError\n })\n const readyParams: ReadyParams | undefined = opts.id ? { id: opts.id } : undefined\n return new Promise<ExtensionClient>((resolve, reject) => {\n let done = false\n const off = endpoint.onEvent([EVENT_HANDSHAKE], (_name, params) => {\n if (done) return\n done = true\n cleanup()\n resolve(new ExtensionClient(endpoint, params as Handshake))\n })\n const interval = setInterval(\n () => endpoint.notify(EVENT_READY, readyParams),\n opts.readyIntervalMs ?? 250\n )\n const timeout = setTimeout(() => {\n if (done) return\n done = true\n cleanup()\n endpoint.close()\n reject(\n new RpcError('Timed out waiting for host handshake', {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'HANDSHAKE_TIMEOUT'\n })\n )\n }, opts.timeoutMs ?? 10_000)\n const cleanup = () => {\n off()\n clearInterval(interval)\n clearTimeout(timeout)\n }\n endpoint.notify(EVENT_READY, readyParams)\n })\n}\n\nexport * from './protocol'\nexport * from './wildcard'\nexport * from './bounds'\nexport { BusEndpoint } from './endpoint'\nexport type { MethodHandler, MethodContext, EventHandler } from './endpoint'\nexport * from './port'\n", "/** Live depth layer: pure helpers turning depth-at-position samples (v2 Track\n * API `properties`) into a safe, drawable aggregate.\n *\n * The overriding rule is safety: a rendered mark must never display deeper than\n * the shallowest sample it covers. Every aggregate therefore takes the\n * MINIMUM depth \u2014 never a mean, which would let a shoal vanish into deeper\n * water \u2014 and the layer draws shallower marks over deeper ones. Samples at or\n * below a shoal threshold are returned individually so a critical minimum is\n * never smeared to a cell centre.\n *\n * Pure functions (no Angular, no map) so they can be unit tested directly.\n */\n\n/** A `[lon, lat]` position. */\nexport type Position = [number, number];\n\n/** A URL query string from the given params, skipping absent or empty ones. */\nfunction queryString(\n params: Record<string, string | number | undefined>,\n): string {\n return Object.entries(params)\n .filter(([, v]) => v !== undefined && v !== \"\")\n .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n}\n\n/** A recorded depth at a position, depth in metres (canonical Signal K). */\nexport interface DepthSample {\n lon: number;\n lat: number;\n depth: number;\n}\n\n/** One grid cell of the aggregate. */\nexport interface DepthCell {\n /** Grid indices (Web Mercator cell coordinates), for neighbour lookup. */\n ix: number;\n iy: number;\n /** Cell centre (mean of the samples in it), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth among the cell's samples, in metres. */\n depth: number;\n /** How many samples fell in the cell. */\n count: number;\n}\n\n/** One drawn circle: a group of adjacent cells at a similar depth, merged. */\nexport interface DepthCluster {\n /** Circle centre (weighted centroid), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth in the group, in metres. */\n depth: number;\n /** Samples the group covers. */\n count: number;\n /** Spatial radius of the group, in metres (Web Mercator). */\n radiusMetres: number;\n}\n\n/** Depth (metres) the shading runs to by default: at this depth and beyond the\n * layer draws nothing, so the chart shows through. */\nexport const DEFAULT_SHADE_LIMIT_M = 10;\n\n/** Default shoal threshold (metres): samples at or below this are marked\n * individually. Matches the S-57 renderer's default safety depth. */\nexport const DEFAULT_SAFETY_DEPTH_M = 3;\n\nconst EARTH_RADIUS = 6378137;\nconst MERCATOR_MAX_LAT = 85.05112878;\nconst RAD = Math.PI / 180;\n\n/** Web Mercator easting (metres) of a longitude. */\nexport function mercX(lon: number): number {\n return lon * RAD * EARTH_RADIUS;\n}\n\n/** Web Mercator northing (metres) of a latitude, clamped to the projection. */\nexport function mercY(lat: number): number {\n const c = Math.max(-MERCATOR_MAX_LAT, Math.min(MERCATOR_MAX_LAT, lat));\n return EARTH_RADIUS * Math.log(Math.tan(Math.PI / 4 + (c * RAD) / 2));\n}\n\n/** Longitude of a Web Mercator easting. */\nexport function lonOf(x: number): number {\n return x / (RAD * EARTH_RADIUS);\n}\n\n/** Latitude of a Web Mercator northing. */\nexport function latOf(y: number): number {\n return (2 * Math.atan(Math.exp(y / EARTH_RADIUS)) - Math.PI / 2) / RAD;\n}\n\n/** Depth tint, Navionics-like: a saturated azure at the surface, fading\n * through pale blue to white at the shade limit. Stops are `[t, r, g, b]` with\n * `t` the fraction from the surface (0) to the shade limit (1). */\nconst SHADE_RAMP: ReadonlyArray<readonly [number, number, number, number]> = [\n [0, 26, 132, 216],\n [0.3, 96, 178, 232],\n [0.55, 156, 209, 240],\n [0.78, 205, 232, 248],\n [1, 255, 255, 255],\n];\n\n/** Isobaths drawn between the surface and the shade limit. */\nexport const SHADE_CONTOUR_LEVELS = 6;\n\n/** RGB at fraction `t` (0 = surface, 1 = the shade limit), sampling\n * {@link SHADE_RAMP}. */\nexport function shadeColor(t: number): [number, number, number] {\n const c = Math.min(1, Math.max(0, Number.isFinite(t) ? t : 0));\n let lo = SHADE_RAMP[0];\n let hi = SHADE_RAMP[SHADE_RAMP.length - 1];\n for (let i = 0; i < SHADE_RAMP.length - 1; i++) {\n if (c >= SHADE_RAMP[i][0] && c <= SHADE_RAMP[i + 1][0]) {\n lo = SHADE_RAMP[i];\n hi = SHADE_RAMP[i + 1];\n break;\n }\n }\n const span = hi[0] - lo[0];\n const f = span > 0 ? (c - lo[0]) / span : 0;\n const lerp = (a: number, b: number) => Math.round(a + (b - a) * f);\n return [lerp(lo[1], hi[1]), lerp(lo[2], hi[2]), lerp(lo[3], hi[3])];\n}\n\n/** RGB for a depth: azure at the surface, white at `shadeLimit` and beyond. */\nexport function depthColor(\n depth: number,\n shadeLimit: number,\n): [number, number, number] {\n const limit = shadeLimit > 0 ? shadeLimit : DEFAULT_SHADE_LIMIT_M;\n return shadeColor(depth / limit);\n}\n\n/** {@link depthColor} as a CSS `rgb()`/`rgba()` string. */\nexport function depthCss(depth: number, shadeLimit: number, alpha = 1): string {\n const [r, g, b] = depthColor(depth, shadeLimit);\n return `rgba(${r},${g},${b},${alpha})`;\n}\n\n/** Aggregate samples into a Web Mercator grid, keeping the SHALLOWEST depth in\n * each cell. `maxDepthMetres` drops samples deeper than the display range\n * (they can never form a cell's minimum anyway); a sample that idles in the\n * grid alone still carries its real depth. Samples with a non-finite or\n * negative depth are ignored \u2014 a negative reading is not water. */\nexport function aggregateMinDepth(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n maxDepthMetres = Infinity,\n): DepthCell[] {\n if (!(cellSizeMetres > 0)) {\n return [];\n }\n const cells = new Map<\n string,\n {\n ix: number;\n iy: number;\n sx: number;\n sy: number;\n n: number;\n depth: number;\n }\n >();\n for (const s of samples) {\n if (\n !Number.isFinite(s.lon) ||\n !Number.isFinite(s.lat) ||\n !Number.isFinite(s.depth) ||\n s.depth < 0 ||\n s.depth > maxDepthMetres\n ) {\n continue;\n }\n const x = mercX(s.lon);\n const y = mercY(s.lat);\n const ix = Math.floor(x / cellSizeMetres);\n const iy = Math.floor(y / cellSizeMetres);\n const key = `${ix},${iy}`;\n const c = cells.get(key);\n if (c) {\n c.sx += x;\n c.sy += y;\n c.n++;\n if (s.depth < c.depth) {\n c.depth = s.depth;\n }\n } else {\n cells.set(key, { ix, iy, sx: x, sy: y, n: 1, depth: s.depth });\n }\n }\n const out: DepthCell[] = [];\n cells.forEach((c) => {\n out.push({\n ix: c.ix,\n iy: c.iy,\n lon: lonOf(c.sx / c.n),\n lat: latOf(c.sy / c.n),\n depth: c.depth,\n count: c.n,\n });\n });\n return out;\n}\n\n/** Cells ordered deepest first, so a shallow cell drawn later paints over a\n * deeper one that overlaps it \u2014 a shoal is never occluded. */\nexport function orderDeepToShallow<T extends { depth: number }>(\n cells: readonly T[],\n): T[] {\n return [...cells].sort((a, b) => b.depth - a.depth);\n}\n\n/** Samples at or below `safetyDepthMetres`, drawn individually on top of the\n * aggregate so a critical minimum keeps its exact position and depth. */\nexport function selectShoals(\n samples: readonly DepthSample[],\n safetyDepthMetres: number,\n): DepthSample[] {\n return samples.filter(\n (s) =>\n Number.isFinite(s.depth) && s.depth >= 0 && s.depth <= safetyDepthMetres,\n );\n}\n\n/** Share of the anchor's depth two samples may differ by and still merge.\n * Depth readings are noisy, so exact equality merges almost nothing. */\nexport const DEFAULT_MERGE_TOLERANCE = 0.15;\n\n/** How far a merged group may grow from its shallowest cell, in cells. Bounds\n * a circle's size so a uniform-depth passage does not become one giant blob. */\nexport const MERGE_MAX_RADIUS_CELLS = 2.5;\n\n/** Merge adjacent grid cells whose depths are within `tolerance` of the\n * group's shallowest cell, into one circle per group. The shallowest cell is\n * the anchor and comparisons are always against the anchor (not the previous\n * neighbour), so depths cannot chain up to merge a wide range; and a group is\n * not allowed to grow beyond `maxRadiusCells` from its anchor, so a long\n * uniform run becomes a row of modest circles rather than one huge one. */\nexport function mergeDepthCells(\n cells: readonly DepthCell[],\n cellSizeMetres: number,\n toleranceOrMaxRadius: number = DEFAULT_MERGE_TOLERANCE,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n if (cells.length === 0 || !(cellSizeMetres > 0)) {\n return [];\n }\n const tolerance = Math.max(0, toleranceOrMaxRadius);\n const maxRadius = Math.max(cellSizeMetres, maxRadiusCells * cellSizeMetres);\n const byKey = new Map<string, DepthCell>();\n cells.forEach((c) => byKey.set(`${c.ix},${c.iy}`, c));\n const visited = new Set<string>();\n const ordered = [...cells].sort((a, b) => a.depth - b.depth);\n const neighbours = [\n [1, 0],\n [-1, 0],\n [0, 1],\n [0, -1],\n [1, 1],\n [1, -1],\n [-1, 1],\n [-1, -1],\n ];\n const clusters: DepthCluster[] = [];\n\n for (const start of ordered) {\n if (visited.has(`${start.ix},${start.iy}`)) {\n continue;\n }\n const anchor = start.depth;\n const ax = mercX(start.lon);\n const ay = mercY(start.lat);\n const members: DepthCell[] = [];\n const queue: DepthCell[] = [start];\n visited.add(`${start.ix},${start.iy}`);\n\n while (queue.length) {\n const cell = queue.pop();\n if (!cell) {\n continue;\n }\n members.push(cell);\n for (const [dx, dy] of neighbours) {\n const key = `${cell.ix + dx},${cell.iy + dy}`;\n const nb = byKey.get(key);\n if (!nb || visited.has(key)) {\n continue;\n }\n // anchor-relative, not neighbour-to-neighbour, so depth cannot drift\n if (nb.depth - anchor > Math.max(tolerance * anchor, 0.1)) {\n continue;\n }\n if (Math.hypot(mercX(nb.lon) - ax, mercY(nb.lat) - ay) > maxRadius) {\n continue;\n }\n visited.add(key);\n queue.push(nb);\n }\n }\n\n let cx = 0;\n let cy = 0;\n let total = 0;\n let minDepth = Infinity;\n members.forEach((m) => {\n cx += mercX(m.lon) * m.count;\n cy += mercY(m.lat) * m.count;\n total += m.count;\n if (m.depth < minDepth) {\n minDepth = m.depth;\n }\n });\n cx /= total;\n cy /= total;\n let radius = 0;\n members.forEach((m) => {\n radius = Math.max(\n radius,\n Math.hypot(mercX(m.lon) - cx, mercY(m.lat) - cy),\n );\n });\n clusters.push({\n lon: lonOf(cx),\n lat: latOf(cy),\n depth: minDepth,\n count: total,\n radiusMetres: Math.min(maxRadius, radius + cellSizeMetres / 2),\n });\n }\n return clusters;\n}\n\n/** Aggregate, merge similar-depth neighbours and order deep \u2192 shallow, ready\n * to draw. */\nexport function depthClusters(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n tolerance: number = DEFAULT_MERGE_TOLERANCE,\n maxDepthMetres = Infinity,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n return orderDeepToShallow(\n mergeDepthCells(\n aggregateMinDepth(samples, cellSizeMetres, maxDepthMetres),\n cellSizeMetres,\n tolerance,\n maxRadiusCells,\n ),\n );\n}\n\n// ******** History API ********\n//\n// The History API supplies the depth series (position and depth together) over\n// a recent time window; the Track API is not used, as on servers whose track\n// provider does not co-record depth it costs seconds per query just to find a\n// time span. Each depth sample carries its own position.\n\n/** Aggregation requested for depth in the History API: the SHALLOWEST sample.\n * At native resolution (no `resolution`) each bucket is one recorded sample, so\n * this is an identity; it only matters if a provider buckets unasked, where it\n * keeps the shallowest value rather than an average. */\nexport const DEPTH_HISTORY_METHOD = \"min\";\n\n/** Query for the depth series with the position of each sample. An explicit\n * `resolution` (seconds, fractional allowed) is honoured by the provider; when\n * it is omitted most providers auto-downsample to a few hundred points, so the\n * caller should always pass the finest interval it wants. `context` defaults to\n * the server's own vessel. */\nexport function historyValuesQuery(opts: {\n depthPath: string;\n from: number;\n to: number;\n resolution?: number;\n context?: string;\n}): string {\n return queryString({\n context: opts.context,\n paths: `navigation.position,${opts.depthPath}:${DEPTH_HISTORY_METHOD},navigation.headingTrue:average`,\n from: new Date(opts.from).toISOString(),\n to: new Date(opts.to).toISOString(),\n resolution: opts.resolution,\n });\n}\n\n/** One depth sample from the History API, with its recorded position. */\nexport interface DepthHistoryRow {\n time: number;\n depth: number;\n position?: Position;\n /** True heading (radians) at the sample, to place it at the transducer. */\n heading?: number;\n}\n\n/** A `[lon, lat]` from either array or `{latitude, longitude}` form. */\nfunction toPosition(value: unknown): Position | undefined {\n if (Array.isArray(value) && value.length >= 2) {\n const [lon, lat] = value as number[];\n if (Number.isFinite(lon) && Number.isFinite(lat)) {\n return [lon, lat];\n }\n return undefined;\n }\n if (value && typeof value === \"object\") {\n const v = value as { latitude?: unknown; longitude?: unknown };\n if (typeof v.latitude === \"number\" && typeof v.longitude === \"number\") {\n return [v.longitude, v.latitude];\n }\n }\n return undefined;\n}\n\n/** Depth rows from a History API `values` response, matched to `depthPath`.\n * The `position` column is attached when the response carried one; rows with a\n * null, non-number or negative depth are skipped. */\nexport function parseDepthSeries(\n result: unknown,\n depthPath: string,\n): DepthHistoryRow[] {\n const values = (result as { values?: Array<{ path?: string }> })?.values;\n const data = (result as { data?: unknown[] })?.data;\n if (!Array.isArray(values) || !Array.isArray(data)) {\n return [];\n }\n const depthCol = values.findIndex((v) => v?.path === depthPath);\n if (depthCol < 0) {\n return [];\n }\n const posCol = values.findIndex((v) => v?.path === \"navigation.position\");\n const headingCol = values.findIndex(\n (v) => v?.path === \"navigation.headingTrue\",\n );\n const rows: DepthHistoryRow[] = [];\n for (const row of data) {\n if (!Array.isArray(row)) {\n continue;\n }\n const time = Date.parse(String(row[0]));\n const depth = row[depthCol + 1];\n if (\n !Number.isFinite(time) ||\n typeof depth !== \"number\" ||\n !Number.isFinite(depth) ||\n depth < 0\n ) {\n continue;\n }\n const rec: DepthHistoryRow = { time, depth };\n if (posCol >= 0) {\n const position = toPosition(row[posCol + 1]);\n if (position) {\n rec.position = position;\n }\n }\n if (headingCol >= 0) {\n const heading = row[headingCol + 1];\n if (typeof heading === \"number\" && Number.isFinite(heading)) {\n rec.heading = heading;\n }\n }\n rows.push(rec);\n }\n return rows;\n}\n", "/** The extension's user settings, shared by the config panel (which edits\n * them), the runtime (which applies them) and the render Worker (which draws\n * with them). Persisted in host state under the extension scope. */\n\nexport type DepthSource = \"history\" | \"tiles\";\n\n/** Overlay render quality. `auto` measures the worker's own frame cost and\n * scales to a budget; the rest pin a scale, so a weak GPU (a laptop iGPU) can\n * be dialled down and a fast one dialled up. */\nexport type DepthQuality = \"auto\" | \"high\" | \"balanced\" | \"low\";\n\nexport interface LiveDepthSettings {\n enabled: boolean;\n /** Where the soundings come from: the server's History API (the vessel's own\n * recent track) or the plugin's gridded bathymetry tiles. */\n source: DepthSource;\n /** Overlay render quality (see {@link DepthQuality}). */\n quality: DepthQuality;\n /** History look-back window, in hours. */\n historyWindowHours: number;\n /** History sample interval, in seconds. */\n historyResolutionS: number;\n /** Depth (metres) the shading runs to; deeper is left to the chart. */\n shadeLimitMetres: number;\n /** Depth (metres) between isobaths. */\n contourIntervalMetres: number;\n /** Grid cell size in screen pixels; also the base label size. */\n cellPx: number;\n /** Fractional depth difference (0.15 = 15%) within which adjacent cells\n * merge into one group. */\n mergeTolerance: number;\n /** Samples deeper than this (metres) are not drawn. */\n maxDepthMetres: number;\n /** Samples at or below this depth (metres) are marked individually. */\n safetyDepthMetres: number;\n}\n\nexport const DEFAULT_SETTINGS: LiveDepthSettings = {\n enabled: true,\n source: \"history\",\n quality: \"auto\",\n historyWindowHours: 1,\n historyResolutionS: 0.1,\n shadeLimitMetres: 10,\n contourIntervalMetres: 1,\n cellPx: 24,\n mergeTolerance: 0.15,\n maxDepthMetres: 100,\n safetyDepthMetres: 3,\n};\n\n/** The render scale (fraction of the host's requested frame size) each fixed\n * quality pins; `auto` is driven by the worker's own measurement instead. */\nexport const QUALITY_SCALE: Record<Exclude<DepthQuality, \"auto\">, number> = {\n high: 1,\n balanced: 0.6,\n low: 0.4,\n};\n\n/** The allowed values of the settings that are a closed set of strings. */\nconst ENUM_VALUES: Partial<Record<keyof LiveDepthSettings, readonly string[]>> =\n {\n source: [\"history\", \"tiles\"],\n quality: [\"auto\", \"high\", \"balanced\", \"low\"],\n };\n\n/** The settings keys, with the bounds the config panel clamps to. */\nexport const SETTINGS_BOUNDS: Partial<\n Record<keyof LiveDepthSettings, { min: number; max: number; step: number }>\n> = {\n historyWindowHours: { min: 0.1, max: 168, step: 0.1 },\n historyResolutionS: { min: 0.01, max: 60, step: 0.01 },\n shadeLimitMetres: { min: 1, max: 200, step: 1 },\n contourIntervalMetres: { min: 0.5, max: 50, step: 0.5 },\n cellPx: { min: 4, max: 128, step: 1 },\n mergeTolerance: { min: 0, max: 1, step: 0.01 },\n maxDepthMetres: { min: 1, max: 500, step: 1 },\n safetyDepthMetres: { min: 0, max: 50, step: 0.5 },\n};\n\n/** Merge stored state over the defaults, ignoring unknown/ill-typed keys. */\nexport function mergeSettings(\n stored: Record<string, unknown> | undefined,\n): LiveDepthSettings {\n const out = { ...DEFAULT_SETTINGS };\n if (!stored) {\n return out;\n }\n for (const key of Object.keys(\n DEFAULT_SETTINGS,\n ) as (keyof LiveDepthSettings)[]) {\n const value = stored[key];\n if (typeof value !== typeof DEFAULT_SETTINGS[key]) {\n continue;\n }\n const allowed = ENUM_VALUES[key];\n if (allowed && !allowed.includes(value as string)) {\n continue;\n }\n (out[key] as unknown) = value;\n }\n return out;\n}\n", "/**\n * Fixed-grid geometry for the bathymetry tile store.\n *\n * Shared by the LiveDepth server plugin and the chart client, so the two build\n * and read identical tiles. The byte codec uses `DataView`/`Uint8Array`, which\n * works both under Node and in the browser, against the `sparse-v1` wire\n * format.\n *\n * A Web-Mercator XYZ quadtree: level `z` splits the world into `2^z` tiles of\n * {@link TILE_SIZE} cells each, so a tile's cell grid is square on the ground.\n * The base level is chosen so a cell is a fraction of a metre; coarser levels\n * are the min-pooled parents (see {@link minPoolTile}), so a shoal is never lost\n * when the chart is zoomed out.\n *\n * A tile is stored sparsely \u2014 only cells that have a sounding \u2014 because a\n * vessel's track is a thin line through a tile and a dense 256x256 array would\n * be almost entirely empty. Each occupied cell is a 5-byte record:\n * `cx:u8, cy:u8, depthCm:u16le, count:u8`.\n */\n\nexport const TILE_SIZE = 256;\n/** Base level: cell \u2248 0.15 m at the equator (156543.03 / 2^20). */\nexport const BASE_ZOOM = 20;\n/** Coarsest level built and served (\u2248 2.4 km/cell at z4). Low enough that a\n * regional zoom-out is covered by a handful of tiles; the min-pool keeps a\n * shoal visible however far the chart is zoomed out. */\nexport const MIN_ZOOM = 4;\n/** Decoded value for a cell with no sounding. Never stored. */\nexport const NODATA = 0xffff;\n/** Bytes per occupied cell: cx(1) + cy(1) + depth(2) + count(1). */\nexport const RECORD_BYTES = 5;\n\nconst EARTH_RADIUS = 6378137;\nconst ORIGIN_SHIFT = Math.PI * EARTH_RADIUS; // 20037508.342789244\nconst WORLD_WIDTH = 2 * ORIGIN_SHIFT;\n\n/** One cell's aggregate: shallowest depth in centimetres and how many soundings. */\nexport type Cell = { depth: number; count: number };\n/** A tile's occupied cells, keyed by `cy * TILE_SIZE + cx`. */\nexport type TileCells = Map<number, Cell>;\n\nconst clamp = (v: number, lo: number, hi: number) =>\n v < lo ? lo : v > hi ? hi : v;\n\n/** Ground size of one cell, in metres, on the equator. */\nexport function cellSize(z: number): number {\n return WORLD_WIDTH / (TILE_SIZE * Math.pow(2, z));\n}\n\n/** Fractional tile coordinates of a lon/lat at level z (x east, y south). */\nfunction tileFraction(lon: number, lat: number, z: number) {\n const n = Math.pow(2, z);\n const latRad = (clamp(lat, -85.05112878, 85.05112878) * Math.PI) / 180;\n const x = ((lon + 180) / 360) * n;\n const y =\n ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * n;\n return { x, y, n };\n}\n\n/** The level-z tile and the cell within it that contains a lon/lat. */\nexport function lngLatToCell(lon: number, lat: number, z: number) {\n const { x: fx, y: fy, n } = tileFraction(lon, lat, z);\n const x = clamp(Math.floor(fx), 0, n - 1);\n const y = clamp(Math.floor(fy), 0, n - 1);\n const cx = clamp(Math.floor((fx - x) * TILE_SIZE), 0, TILE_SIZE - 1);\n const cy = clamp(Math.floor((fy - y) * TILE_SIZE), 0, TILE_SIZE - 1);\n return { x, y, cx, cy };\n}\n\n/** Quantise a depth in metres to the store's centimetre resolution. */\nexport function encodeDepth(metres: number): number {\n if (!Number.isFinite(metres)) return NODATA;\n return clamp(Math.round(metres * 100), 0, NODATA - 1);\n}\n\n/** Decode a stored depth in centimetres back to metres. */\nexport function decodeDepth(cm: number): number {\n return cm === NODATA ? NaN : cm / 100;\n}\n\n/** Merge a sounding into a cell, keeping the shallowest and counting samples. */\nexport function mergeCell(\n cells: TileCells,\n key: number,\n depthCm: number,\n count = 1,\n): void {\n const existing = cells.get(key);\n if (!existing) {\n cells.set(key, { depth: depthCm, count: Math.min(255, count) });\n return;\n }\n if (depthCm < existing.depth) existing.depth = depthCm;\n existing.count = Math.min(255, existing.count + count);\n}\n\n/** Encode a tile's cells to its sparse on-the-wire representation. */\nexport function encodeTile(cells: TileCells): Uint8Array {\n const bytes = new Uint8Array(cells.size * RECORD_BYTES);\n const view = new DataView(bytes.buffer);\n let offset = 0;\n for (const [key, { depth, count }] of cells) {\n view.setUint8(offset, key % TILE_SIZE);\n view.setUint8(offset + 1, Math.floor(key / TILE_SIZE));\n view.setUint16(offset + 2, depth, true);\n view.setUint8(offset + 4, count);\n offset += RECORD_BYTES;\n }\n return bytes;\n}\n\n/** Decode a sparse tile buffer back to its cells. */\nexport function decodeTile(bytes: Uint8Array): TileCells {\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const cells: TileCells = new Map();\n for (let o = 0; o + RECORD_BYTES <= view.byteLength; o += RECORD_BYTES) {\n const cx = view.getUint8(o);\n const cy = view.getUint8(o + 1);\n const depth = view.getUint16(o + 2, true);\n const count = view.getUint8(o + 4);\n cells.set(cy * TILE_SIZE + cx, { depth, count });\n }\n return cells;\n}\n\n/**\n * Build a parent tile's cells from its four child tiles (level z+1, ordered\n * `[southwest, southeast, northwest, northeast]` = `[(0,0),(1,0),(0,1),(1,1)]`\n * in child-tile offsets). A parent cell keeps the **minimum** depth of its four\n * children and the summed count, so zooming out never deepens a shoal.\n */\nexport function minPoolTile(children: (TileCells | undefined)[]): TileCells {\n const parent: TileCells = new Map();\n children.forEach((child, index) => {\n if (!child) return;\n const dx = index & 1;\n const dy = index >> 1;\n for (const [key, { depth, count }] of child) {\n const cx = key % TILE_SIZE;\n const cy = Math.floor(key / TILE_SIZE);\n const pcx = (dx * TILE_SIZE + cx) >> 1;\n const pcy = (dy * TILE_SIZE + cy) >> 1;\n mergeCell(parent, pcy * TILE_SIZE + pcx, depth, count);\n }\n });\n return parent;\n}\n", "/** Helpers for reading the LiveDepth bathymetry tiles.\n *\n * Glue around the grid: pick the tile level for a target ground resolution,\n * work out which tiles cover a viewport, and turn a decoded tile's cells back\n * into lon/lat depth soundings. Pure functions (no HTTP, no framework) so they\n * can be unit tested directly.\n */\n\nimport { Sounding } from \"./types.js\";\nimport { TransducerOffset } from \"./correction.js\";\nimport {\n BASE_ZOOM,\n MIN_ZOOM,\n TILE_SIZE,\n TileCells,\n cellSize,\n decodeDepth,\n decodeTile,\n} from \"./grid.js\";\n\n/** Grid spec served by `/plugins/<id>/bathymetry/meta`. */\nexport interface TileMeta {\n format: string;\n baseZoom: number;\n minZoom: number;\n tileSize: number;\n recordBytes: number;\n depthPath?: string;\n /** Offset from the position the server reports to the depth transducer, when\n * the plugin corrects for it; absent or null otherwise. A client applies the\n * same shift to its own soundings so they match the served tiles. */\n transducer?: TransducerOffset | null;\n /** Vessel dimensions, for the debug footprint overlay. */\n vessel?: { loa?: number; beam?: number };\n /** Temporary debug flags; see the plugin's debug config. */\n debug?: { vesselFootprint?: boolean };\n}\n\nconst RAD = Math.PI / 180;\n\n/** The finest tile level whose cells are no coarser than `cellMetres`, clamped\n * to the served range. Finer than needed wastes requests; coarser loses detail\n * the field could have shown. */\nexport function zoomForCellSize(\n cellMetres: number,\n minZoom = MIN_ZOOM,\n baseZoom = BASE_ZOOM,\n): number {\n if (!(cellMetres > 0)) return baseZoom;\n // Round up, so the chosen level's cells are no coarser than requested.\n const ideal = Math.ceil(\n Math.log2((2 * Math.PI * 6378137) / (TILE_SIZE * cellMetres)),\n );\n return Math.max(minZoom, Math.min(baseZoom, ideal));\n}\n\n/** Tile x for a longitude at level z; may fall outside `[0, 2^z)` for a view\n * panned a world away, and is wrapped by the caller. */\nfunction lonToTileX(lon: number, z: number): number {\n return ((lon + 180) / 360) * Math.pow(2, z);\n}\n\n/** Tile y for a latitude at level z (south = larger y). */\nfunction latToTileY(lat: number, z: number): number {\n const clamped = Math.max(-85.05112878, Math.min(85.05112878, lat));\n const latRad = clamped * RAD;\n return (\n ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) *\n Math.pow(2, z)\n );\n}\n\n/** The tiles (wrapped x) covering a `[west, south, east, north]` box at level\n * z. `west > east` is read the short way round the antimeridian. */\nexport function tileRangeForBbox(\n bbox: readonly number[],\n z: number,\n): { xs: number[]; ys: number[] } {\n const n = Math.pow(2, z);\n if (bbox.length !== 4) {\n return { xs: [], ys: [] };\n }\n const [west, south, east, north] = bbox;\n\n const xStart = Math.floor(lonToTileX(west, z));\n let xEnd = Math.floor(lonToTileX(east, z));\n if (xEnd < xStart) {\n xEnd += n; // wrapped past the antimeridian\n }\n const xCount = Math.min(n, xEnd - xStart + 1);\n const xs: number[] = [];\n for (let i = 0; i < xCount; i++) {\n xs.push((((xStart + i) % n) + n) % n);\n }\n\n const yStart = Math.max(0, Math.floor(latToTileY(north, z)));\n const yEnd = Math.min(n - 1, Math.floor(latToTileY(south, z)));\n const ys: number[] = [];\n for (let y = yStart; y <= yEnd; y++) {\n ys.push(y);\n }\n\n return { xs, ys };\n}\n\n/** Lon/lat of the centre of a cell within a tile. */\nexport function tileCellCentre(\n z: number,\n x: number,\n y: number,\n key: number,\n): [number, number] {\n const n = Math.pow(2, z);\n const cx = key % TILE_SIZE;\n const cy = Math.floor(key / TILE_SIZE);\n const fx = (x + (cx + 0.5) / TILE_SIZE) / n;\n const fy = (y + (cy + 0.5) / TILE_SIZE) / n;\n const lon = fx * 360 - 180;\n const lat = Math.atan(Math.sinh(Math.PI * (1 - 2 * fy))) / RAD;\n return [lon, lat];\n}\n\n/** Turn a tile's cells into lon/lat depth samples. */\nexport function cellsToSamples(\n z: number,\n x: number,\n y: number,\n cells: TileCells,\n): Sounding[] {\n const samples: Sounding[] = [];\n for (const [key, cell] of cells) {\n const depth = decodeDepth(cell.depth);\n if (!Number.isFinite(depth)) continue;\n const [lon, lat] = tileCellCentre(z, x, y, key);\n samples.push({ lon, lat, depth });\n }\n return samples;\n}\n\n/** Decode a served tile blob into lon/lat depth samples. */\nexport function decodeTileSamples(\n z: number,\n x: number,\n y: number,\n bytes: Uint8Array,\n): Sounding[] {\n return cellsToSamples(z, x, y, decodeTile(bytes));\n}\n\nexport { cellSize };\n", "/**\n * Vessel reference point \u2192 depth transducer correction.\n *\n * The recorded position is the reference point the server reports as\n * `navigation.position`: the GNSS antenna, or \u2014 when the server's\n * `gnssCorrection` is active \u2014 the Consistent Common Reference Point (CCRP,\n * midships on the centreline). A depth sounding is measured at the transducer,\n * which sits elsewhere on the hull, so its position must be shifted by the\n * body-frame offset from that reference to the transducer, rotated by the true\n * heading.\n *\n * The caller supplies the offset; this module neither knows nor guesses the\n * reference, so it stays clear of the server's GNSS correction.\n */\n\n/** Body-frame offset of the transducer from the position reference, in metres:\n * `forward` toward the bow, `starboard` to starboard. */\nexport interface TransducerOffset {\n forward: number;\n starboard: number;\n}\n\n/** A lon/lat position. */\nexport interface LonLat {\n lon: number;\n lat: number;\n}\n\nconst EARTH_RADIUS_M = 6378137;\nconst DEG_TO_RAD = Math.PI / 180;\nconst RAD_TO_DEG = 180 / Math.PI;\n\n/**\n * Shift `position` from the reference point to the transducer, rotating the\n * body-frame `offset` by `heading` (true, radians, clockwise from north).\n * Returns `position` unchanged when there is no heading or no offset.\n */\nexport function correctToTransducer(\n position: LonLat,\n heading: number | undefined,\n offset: TransducerOffset,\n): LonLat {\n if (\n heading === undefined ||\n !Number.isFinite(heading) ||\n (!offset.forward && !offset.starboard)\n ) {\n return position;\n }\n const cos = Math.cos(heading);\n const sin = Math.sin(heading);\n // +x body (toward bow) points along the heading; +y body (starboard) is 90\u00B0\n // clockwise from it.\n const north = offset.forward * cos - offset.starboard * sin;\n const east = offset.forward * sin + offset.starboard * cos;\n const lat = position.lat + (north / EARTH_RADIUS_M) * RAD_TO_DEG;\n const lon =\n position.lon +\n (east / (EARTH_RADIUS_M * Math.cos(position.lat * DEG_TO_RAD))) *\n RAD_TO_DEG;\n // wrap into [-180, 180] so a shift near the antimeridian stays in range\n const wrapped = ((((lon + 180) % 360) + 360) % 360) - 180;\n return { lon: wrapped, lat };\n}\n", "// The LiveDepth background runtime: a headless iframe the host loads while the\n// plugin is enabled. It owns the soundings, asks its render Worker for a frame\n// on each `overlay.request`, and answers with `overlay.frame`.\n//\n// Data has two sources, matching the plugin's two modes: the server's History\n// API (the vessel's own recent track) or the plugin's gridded bathymetry tiles\n// \u2014 both reached over same-origin REST, since the runtime is served by the same\n// server as the data. The own vessel's live position + depth arrive over the\n// bus (`signalk.subscribe`) and extend the tile source in place.\n\nimport { connectExtension } from \"signalk-plotterext-bus/extension\";\nimport type {\n OverlayRequestEvent,\n OverlayStateEvent,\n} from \"signalk-plotterext-bus/extension\";\nimport type { DepthSample } from \"./shared/live-depth.js\";\nimport { historyValuesQuery, parseDepthSeries } from \"./shared/live-depth.js\";\nimport {\n DEFAULT_SETTINGS,\n mergeSettings,\n type LiveDepthSettings,\n} from \"./shared/settings.js\";\nimport type { RenderRequest, RenderWorkerOut } from \"./shared/protocol.js\";\nimport {\n correctToTransducer,\n decodeTileSamples,\n tileRangeForBbox,\n zoomForCellSize,\n BASE_ZOOM,\n MIN_ZOOM,\n type TileMeta,\n type TransducerOffset,\n} from \"../tiles/core/index.js\";\n\nconst PLUGIN_ID = \"signalk-livedepth-plugin\";\nconst ASSET_BASE = `/plotterext/${PLUGIN_ID}`;\nconst BATHY_BASE = `${ASSET_BASE}/bathymetry`;\n\n/** Settle time before a moved view or a changed window is fetched. */\nconst DEBOUNCE_MS = 400;\n/** How often the history source is refreshed while the layer is visible. */\nconst POLL_MS = 15000;\n/** Most tile fetches a single view may need before the layer draws nothing. */\nconst MAX_TILE_FETCHES = 24;\n/** How long live soundings are batched before folding into the client store.\n * Kept short so the surface fills in under the bow rather than a couple of\n * seconds behind it. */\nconst LIVE_FLUSH_MS = 300;\n/** Depth path when the plugin advertises none. */\nconst DEFAULT_DEPTH_PATH = \"environment.depth.belowTransducer\";\n\nconst client = await connectExtension();\nconst worker = new Worker(new URL(\"./render-worker.js\", import.meta.url), {\n type: \"module\",\n});\n\nlet settings: LiveDepthSettings = { ...DEFAULT_SETTINGS };\nlet transducer: TransducerOffset | null = null;\nlet depthPath = DEFAULT_DEPTH_PATH;\n/** The vessel's dimensions and whether to draw the debug footprint, from the\n * plugin's meta. */\nlet vessel: { loa?: number; beam?: number } = {};\nlet showFootprint = false;\n\n/** The server-derived soundings (tiles or history); the worker folds the live\n * own-vessel soundings onto these. */\nlet baseSamples: DepthSample[] = [];\nlet lastReq: RenderRequest | null = null;\nlet busy = false;\nlet dirty = false;\n/** The request currently being rendered. Its frame carries this request's own\n * id and extent, even if a newer one arrives mid-render, so the host refuses a\n * stale frame as `superseded` rather than reading it as a size mismatch. */\nlet rendering: RenderRequest | null = null;\n/** Whether the frame being rendered is a live (data-driven) one, sent without a\n * request id and paced by the host's `liveInterval`. */\nlet renderingLive = false;\n/** The host's least interval between two live frames, in ms. */\nlet liveInterval = 200;\n/** When the last live frame was sent, to pace the next. */\nlet lastLiveAt = 0;\n/** The worker's last perf readout, for the frame-rate log. */\nlet perfScale = 0;\nlet perfFrameMs = 0;\nlet framesSeen = 0;\nlet framesSince = Date.now();\nlet timer: ReturnType<typeof setTimeout> | undefined;\n\n// Own-vessel soundings buffered since the last flush, sent to the worker to be\n// folded into the field.\nlet pendingLive: DepthSample[] = [];\nlet liveFlushTimer: ReturnType<typeof setInterval> | undefined;\n\n// ******** settings ********\n\nasync function loadSettings(): Promise<void> {\n try {\n const values = await client.state.get();\n settings = mergeSettings(values as Record<string, unknown>);\n } catch {\n settings = { ...DEFAULT_SETTINGS };\n }\n worker.postMessage({ type: \"settings\", settings });\n // The live extension belongs to the tile source only; start or stop it as the\n // source changes, so switching depth source takes effect.\n if (settings.source === \"tiles\") {\n startLive();\n } else {\n stopLive();\n }\n schedule();\n}\n\n// ******** data ********\n\nasync function loadMeta(): Promise<void> {\n try {\n const res = await fetch(`${BATHY_BASE}/meta`, {\n credentials: \"same-origin\",\n });\n if (!res.ok) return;\n const meta = (await res.json()) as TileMeta;\n transducer = meta.transducer ?? null;\n if (meta.depthPath) depthPath = meta.depthPath;\n vessel = meta.vessel ?? {};\n showFootprint = meta.debug?.vesselFootprint === true;\n publishVessel();\n } catch {\n // No plugin meta: the history source still works, uncorrected.\n }\n}\n\n/** Shift a sounding from the reported position to the transducer, when the\n * plugin's meta told us to correct. */\nfunction correct(\n lon: number,\n lat: number,\n heading: number | undefined,\n): { lon: number; lat: number } {\n return transducer\n ? correctToTransducer({ lon, lat }, heading, transducer)\n : { lon, lat };\n}\n\n/** Ask the host for the user's preferred depth unit, for the labels. */\nasync function loadUnits(): Promise<void> {\n try {\n const res = (await client.call(\"units.get\")) as {\n units?: { depth?: string };\n };\n if (res?.units?.depth) {\n worker.postMessage({ type: \"units\", depth: res.units.depth });\n }\n } catch {\n // No units capability: the labels stay in metres.\n }\n}\n\nasync function fetchHistory(): Promise<void> {\n const windowMs = settings.historyWindowHours * 3600000;\n const to = Date.now();\n const query = historyValuesQuery({\n depthPath,\n from: to - windowMs,\n to,\n resolution: Number(settings.historyResolutionS.toPrecision(3)),\n });\n const res = await fetch(`/signalk/v2/api/history/values?${query}`, {\n credentials: \"same-origin\",\n });\n if (!res.ok) return;\n const rows = parseDepthSeries(await res.json(), depthPath);\n const out: DepthSample[] = [];\n for (const row of rows) {\n if (!row.position) continue;\n const { lon, lat } = correct(row.position[0], row.position[1], row.heading);\n out.push({ lon, lat, depth: row.depth });\n }\n baseSamples = out;\n publishBase();\n}\n\nasync function fetchTiles(req: RenderRequest): Promise<void> {\n const cellMetres = Math.max(req.resolution, 0.01);\n let zoom = zoomForCellSize(cellMetres, MIN_ZOOM, BASE_ZOOM);\n while (zoom > MIN_ZOOM) {\n const { xs, ys } = tileRangeForBbox(req.bounds, zoom);\n if (xs.length * ys.length <= MAX_TILE_FETCHES) break;\n zoom--;\n }\n const { xs, ys } = tileRangeForBbox(req.bounds, zoom);\n const out: DepthSample[] = [];\n await Promise.all(\n xs.flatMap((x) =>\n ys.map(async (y) => {\n try {\n const res = await fetch(`${BATHY_BASE}/tiles/${zoom}/${x}/${y}`, {\n credentials: \"same-origin\",\n });\n if (!res.ok) return;\n const bytes = new Uint8Array(await res.arrayBuffer());\n out.push(...decodeTileSamples(zoom, x, y, bytes));\n } catch {\n // A missing tile is simply no data there.\n }\n }),\n ),\n );\n baseSamples = out;\n publishBase();\n}\n\nfunction schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => void refresh(), DEBOUNCE_MS);\n}\n\nasync function refresh(): Promise<void> {\n if (!settings.enabled) return;\n if (settings.source === \"history\") {\n await fetchHistory();\n } else if (lastReq) {\n await fetchTiles(lastReq);\n }\n}\n\n/** Hand the worker the server-derived soundings; it rebuilds the field around\n * them. */\nfunction publishBase(): void {\n worker.postMessage({ type: \"base\", samples: baseSamples });\n dirty = true;\n // A live frame for the last request, so the layer updates without a new one.\n if (lastReq) draw(true);\n}\n\n// ******** live extension (tiles source) ********\n\nlet liveUnsub: (() => Promise<void>) | undefined;\n\nfunction startLive(): void {\n if (settings.source !== \"tiles\" || liveUnsub) return;\n void client.signalk\n .subscribe(\n [\"navigation.position\", depthPath, \"navigation.headingTrue\"],\n (ev) => {\n if (ev.path === \"navigation.position\") {\n // The host relays the path's raw Signal K value, which is an object\n // (`{ latitude, longitude }`); a host that normalises positions\n // sends an array instead. Accept either, so a live sounding is never\n // silently dropped over the shape.\n const p = ev.value as\n | [number, number]\n | { longitude?: number; latitude?: number }\n | null\n | undefined;\n if (Array.isArray(p)) {\n livePosition = [p[0], p[1]];\n } else if (\n typeof p?.longitude === \"number\" &&\n typeof p?.latitude === \"number\"\n ) {\n livePosition = [p.longitude, p.latitude];\n }\n } else if (ev.path === depthPath && typeof ev.value === \"number\") {\n liveDepth = ev.value;\n } else if (\n ev.path === \"navigation.headingTrue\" &&\n typeof ev.value === \"number\"\n ) {\n liveHeading = ev.value;\n }\n publishVessel();\n sampleLive();\n },\n )\n .then((un) => {\n liveUnsub = un;\n })\n .catch(() => {});\n clearInterval(liveFlushTimer);\n liveFlushTimer = setInterval(flushLive, LIVE_FLUSH_MS);\n}\n\nfunction stopLive(): void {\n clearInterval(liveFlushTimer);\n liveFlushTimer = undefined;\n void liveUnsub?.();\n liveUnsub = undefined;\n}\n\nlet livePosition: [number, number] | null = null;\nlet liveDepth: number | null = null;\nlet liveHeading: number | undefined;\n\n/** Tell the worker the vessel's live position and heading, for the debug\n * footprint overlay. A no-op unless the plugin's debug flag is on. */\nfunction publishVessel(): void {\n if (!showFootprint) return;\n worker.postMessage({\n type: \"vessel\",\n position: livePosition,\n heading: liveHeading,\n loa: vessel.loa,\n beam: vessel.beam,\n });\n dirty = true;\n if (lastReq) draw(true);\n}\n\nfunction sampleLive(): void {\n if (!livePosition || liveDepth === null) return;\n const { lon, lat } = correct(livePosition[0], livePosition[1], liveHeading);\n pendingLive.push({ lon, lat, depth: liveDepth });\n}\n\nfunction flushLive(): void {\n if (pendingLive.length === 0) return;\n const live = pendingLive;\n pendingLive = [];\n // Fold the new soundings into the worker's field. No tile re-fetch, and the\n // worker patches only the cells they reach, so a flush is cheap.\n worker.postMessage({ type: \"live\", samples: live });\n dirty = true;\n if (lastReq) draw(true);\n}\n\n// ******** frame cycle ********\n\nfunction draw(live = false): void {\n if (busy || !lastReq || !settings.enabled) return;\n if (live && Date.now() - lastLiveAt < liveInterval) return;\n busy = true;\n rendering = lastReq;\n renderingLive = live;\n dirty = false;\n worker.postMessage({ type: \"render\", req: rendering });\n}\n\nworker.onmessage = (e: MessageEvent<RenderWorkerOut>) => {\n const msg = e.data;\n if (msg.type === \"perf\") {\n perfScale = msg.scale;\n perfFrameMs = msg.frameMs;\n return;\n }\n const req = rendering;\n const live = renderingLive;\n busy = false;\n rendering = null;\n if (msg.type !== \"frame\" || !req) {\n return;\n }\n if (live) lastLiveAt = Date.now();\n // The host owns the image once it is transferred, and closes a refused one\n // itself, so there is nothing to release here. A live frame carries no\n // request id, so the host paces it against `liveInterval`.\n void client.overlays\n .frame({\n overlayId: req.overlayId,\n requestId: live ? undefined : req.requestId,\n extent: req.extent,\n image: msg.image,\n })\n .catch(() => {});\n // A quick frame-rate readout, with the worker's current render scale, every\n // few seconds \u2014 handy when tuning quality or measuring a GPU's headroom.\n framesSeen++;\n const now = Date.now();\n if (now - framesSince >= 5000) {\n const fps = (framesSeen * 1000) / (now - framesSince);\n console.info(\n `[LiveDepth] ${fps.toFixed(1)} frames/s \u00B7 render scale ${perfScale.toFixed(2)} \u00B7 worker ${perfFrameMs.toFixed(1)} ms/frame`,\n );\n framesSeen = 0;\n framesSince = now;\n }\n // A newer request arrived, or the data changed, while this frame rendered.\n if (dirty || lastReq !== req) draw(live);\n};\n\n// ******** bus wiring ********\n\nawait client.subscribe([\"overlay.request\"], (_name, payload) => {\n const req = payload as OverlayRequestEvent;\n lastReq = req;\n if (typeof req.liveInterval === \"number\" && req.liveInterval > 0) {\n liveInterval = req.liveInterval;\n }\n // The tiles are view-dependent: a settled pan/zoom needs the tiles covering\n // its own bbox. Without this the layer keeps drawing the tile set fetched for\n // the previous view, which is why panning showed only partial tracks.\n if (settings.source === \"tiles\") schedule();\n draw();\n});\n\nawait client.subscribe([\"overlay.state\"], (_name, payload) => {\n const state = payload as OverlayStateEvent;\n if (!state.visible || state.suspended) {\n worker.postMessage({ type: \"release\" });\n stopLive();\n } else {\n startLive();\n schedule();\n }\n});\n\nawait client.subscribe([\"state.changed\"], () => void loadSettings());\n\nawait loadMeta();\nawait loadSettings();\nawait loadUnits();\nsetInterval(() => {\n if (settings.enabled && settings.source === \"history\") schedule();\n}, POLL_MS);\n"],
5
+ "mappings": "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",
6
+ "names": ["timer", "depthPath", "RAD", "xs", "ys"]
7
7
  }