signalk-livedepth-plugin 0.3.17 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,47 @@ All notable changes to this project are documented here, newest first. The
4
4
  format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
5
  versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.3.19] - 2026-11-07
8
+
9
+ ### Added
10
+
11
+ - **Tide-corrected soundings.** With tide correction on, every depth is reduced
12
+ to a fixed chart datum (LAT or MLLW) using the predicted water level at that
13
+ sample's time and position, so soundings from different days and different
14
+ vessels share one grid. The Slackwater station database ships with the
15
+ plugin, so nothing is downloaded. The chart is told the datum in the plugin
16
+ metadata and stops folding its own live depth onto the corrected grid.
17
+
18
+ ### Changed
19
+
20
+ - **Depth is always read from a Signal K depth path.** The external
21
+ tide-compensated paths (`belowSurfaceTideCompensated`, `…LAT`, `…MLLW`) are
22
+ removed; tide compensation now happens inside the plugin. Point the Depth
23
+ setting at `environment.depth.belowSurface` and turn tide correction on.
24
+ - **The Depth selector is disabled while tide correction is on**, with a note
25
+ that changing the datum clears the grid because the stored cells are depths
26
+ in the old datum.
27
+
28
+ ### Fixed
29
+
30
+ - **A history rebuild no longer races the history provider.** The plugin waits
31
+ up to 30 s for the History API provider to register before falling back to
32
+ the HTTP endpoint, so a rebuild at server start no longer fails instantly.
33
+ - **Older pinned Slackwater versions are supported.** The tide predictor is
34
+ awaited, which works whether `nearestStation` returns a promise or the
35
+ predictor directly.
36
+
37
+ ## [0.3.18] - 2026-11-02
38
+
39
+ ### Changed
40
+
41
+ - **The layer now always draws the plugin's bathymetry grid.** The History API
42
+ is no longer offered as a depth source in the layer's settings; the source
43
+ selector and its history-only window/interval fields are gone. The separate
44
+ History import in the plugin's server settings is unchanged — that is what
45
+ seeds the grid. Settings saved with the old History source migrate
46
+ automatically.
47
+
7
48
  ## [0.3.17] - 2026-11-01
8
49
 
9
50
  ### Fixed
package/README.md CHANGED
@@ -19,6 +19,9 @@ already covered is there to look at the next time you're in the area.
19
19
  server has built up over time — extended live as you move.
20
20
  - **Rebuild from history.** Already have a track recorded on the server? A
21
21
  one-time rebuild fills the map in from it.
22
+ - **Comparable across days.** Optionally reduce every sounding to a fixed
23
+ vertical datum (LAT or MLLW) using the tide predicted at the moment and place
24
+ it was taken, so a depth recorded months ago still means the same thing.
22
25
 
23
26
  ## What you need
24
27
 
@@ -37,7 +40,6 @@ chart's. Open **Layer settings** on its row (or the LiveDepth toolbar button) to
37
40
  choose what it shows:
38
41
 
39
42
  - **Show the layer** — on or off.
40
- - **Depth source** — your own recent track, or the accumulated depth map.
41
43
  - **Shade limit** — the depth the shading runs to; deeper water is left to the
42
44
  chart.
43
45
  - **Isobath interval** — how far apart the depth-contour lines are.
@@ -51,7 +53,16 @@ Depth values are shown in your preferred units.
51
53
  ## Server settings
52
54
 
53
55
  - **Path** — which depth measurement to record (below surface, transducer or
54
- keel).
56
+ keel). Ignored when tide correction is on, which always uses the raw depth
57
+ below the surface.
58
+ - **Tide correction** — store soundings relative to a fixed vertical datum
59
+ instead of the moving surface. Choose chart datum, **LAT** or **MLLW**. LAT
60
+ and MLLW are fixed references at each location, so a corrected depth keeps its
61
+ meaning when you return; chart datum changes with the chart region. Each
62
+ sounding — live, rebuilt from history, or recorded by another vessel — is
63
+ reduced using the tide predicted at its own time and position. Changing the
64
+ datum clears the map, because the stored cells are depths in the old datum;
65
+ run **Rebuild from history** to repopulate it.
55
66
  - **Depth transducer** — where the sounder sits on the hull, so each reading is
56
67
  placed at it rather than at the position the server reports.
57
68
  - **Transducer position correction** — shift readings from the vessel's
@@ -62,9 +73,15 @@ Depth values are shown in your preferred units.
62
73
  ## How it works
63
74
 
64
75
  The plugin runs on your Signal K server and turns each sounding into a
65
- Web-Mercator depth map, keeping the shallowest reading for every spot. A
66
- chartplotter that supports plotter extensions draws it as a map layer, rendering
67
- in the browser. The plugin offers the layer to the chartplotter automatically —
76
+ Web-Mercator depth map, keeping the shallowest reading for every spot. With tide
77
+ correction on it predicts the water level from the station database bundled with
78
+ the plugin (Slackwater) — offline, for any past or future instant — and
79
+ subtracts it before the reading reaches the map; a reading whose tide cannot be
80
+ predicted is skipped rather than stored in the wrong datum. Because the browser
81
+ cannot correct its own live readings, the layer then draws the corrected server
82
+ tiles (refreshed as new soundings arrive) instead of adding the raw live depth on
83
+ top. A chartplotter that supports plotter extensions draws it as a map layer,
84
+ rendering in the browser. The plugin offers the layer to the chartplotter automatically —
68
85
  there's nothing to configure on the chart side.
69
86
 
70
87
  ## Development
@@ -2,6 +2,7 @@ import { Writable } from "stream";
2
2
  import { ServerAPI } from "@signalk/server-api";
3
3
  import { TransducerOffset } from "./tiles/core/index.js";
4
4
  import { Config } from "./config.js";
5
+ import { TideCorrection } from "./tide.js";
5
6
  export interface CollectorOptions {
6
7
  app: ServerAPI;
7
8
  config: Config;
@@ -9,10 +10,17 @@ export interface CollectorOptions {
9
10
  writer: Writable;
10
11
  /** Shift each sounding from the reported position to the transducer. */
11
12
  offset?: TransducerOffset;
13
+ /** Reduce each sounding to a fixed datum, using that sounding's own time and
14
+ * position. */
15
+ tide?: TideCorrection;
12
16
  signal: AbortSignal;
13
17
  }
14
18
  /**
15
19
  * Pair depth with position once and feed the soundings to the tile writer.
20
+ *
21
+ * With tide correction on, the raw depth below the surface is recorded (not the
22
+ * configured depth path, which may already be compensated) and each sounding is
23
+ * reduced to the chosen datum before it reaches the store.
16
24
  */
17
- export declare function createCollector({ app, config, writer, offset, signal, }: CollectorOptions): import("stream-transform").Transformer;
25
+ export declare function createCollector({ app, config, writer, offset, tide, signal, }: CollectorOptions): import("stream-transform").Transformer;
18
26
  //# sourceMappingURL=collector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,EAAE,QAAQ,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,GAAG,EACH,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,gBAAgB,0CAkBlB"}
1
+ {"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,cAAc,EAAsB,MAAM,WAAW,CAAC;AAG/D,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,EAAE,QAAQ,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;mBACe;IACf,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,GAAG,EACH,MAAM,EACN,MAAM,EACN,MAAM,EACN,IAAI,EACJ,MAAM,GACP,EAAE,gBAAgB,0CAyBlB"}
package/dist/collector.js CHANGED
@@ -1,15 +1,22 @@
1
1
  import { transform } from "stream-transform";
2
2
  import { createLiveStream, toPrecision } from "./streams/index.js";
3
+ import { recordingDepthPath } from "./tide.js";
3
4
  /**
4
5
  * Pair depth with position once and feed the soundings to the tile writer.
6
+ *
7
+ * With tide correction on, the raw depth below the surface is recorded (not the
8
+ * configured depth path, which may already be compensated) and each sounding is
9
+ * reduced to the chosen datum before it reaches the store.
5
10
  */
6
- export function createCollector({ app, config, writer, offset, signal, }) {
7
- const live = createLiveStream(app, config, offset);
11
+ export function createCollector({ app, config, writer, offset, tide, signal, }) {
12
+ const live = createLiveStream(app, config, offset, recordingDepthPath(config, tide !== undefined));
8
13
  const precise = live.pipe(transform(toPrecision()));
9
- precise.pipe(writer);
14
+ const output = tide ? precise.pipe(transform(tideTransform(tide))) : precise;
15
+ output.pipe(writer);
10
16
  const destroy = () => {
11
17
  live.destroy();
12
18
  precise.destroy();
19
+ output.destroy();
13
20
  writer.destroy();
14
21
  };
15
22
  if (signal.aborted) {
@@ -18,6 +25,18 @@ export function createCollector({ app, config, writer, offset, signal, }) {
18
25
  else {
19
26
  signal.addEventListener("abort", destroy, { once: true });
20
27
  }
21
- return precise;
28
+ return output;
29
+ }
30
+ /**
31
+ * Async stream handler that corrects each sounding. A sounding is dropped when
32
+ * the tide is unavailable, so a datum grid never mixes in a raw depth.
33
+ */
34
+ function tideTransform(tide) {
35
+ return (data, callback) => {
36
+ tide
37
+ .correct(data.longitude, data.latitude, data.timestamp, data.depth)
38
+ .then((depth) => callback(null, depth === undefined ? undefined : { ...data, depth }))
39
+ .catch((error) => callback(error));
40
+ };
22
41
  }
23
42
  //# sourceMappingURL=collector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collector.js","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAK7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAYnE;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,GAAG,EACH,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GACW;IACjB,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAErB,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC;IAEF,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"collector.js","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAK7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAkB,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAgB/D;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,GAAG,EACH,MAAM,EACN,MAAM,EACN,MAAM,EACN,IAAI,EACJ,MAAM,GACW;IACjB,MAAM,IAAI,GAAG,gBAAgB,CAC3B,GAAG,EACH,MAAM,EACN,MAAM,EACN,kBAAkB,CAAC,MAAM,EAAE,IAAI,KAAK,SAAS,CAAC,CAC/C,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7E,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC;IAEF,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,IAAoB;IACzC,OAAO,CACL,IAAoB,EACpB,QAAiE,EACjE,EAAE;QACF,IAAI;aACD,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;aAClE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACd,QAAQ,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,CACrE;aACA,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAc,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC;AACJ,CAAC"}
package/dist/config.d.ts CHANGED
@@ -1,8 +1,17 @@
1
1
  import { ServerAPI } from "@signalk/server-api";
2
+ import { TideDatum } from "./tide.js";
2
3
  export type Config = {
3
- /** Which depth datum to record. Signal K already publishes each relative to
4
- * its own reference, so the value is stored as-is. */
4
+ /** Which depth measurement to record when tide correction is off. Signal K
5
+ * already publishes each relative to its own reference, so it is stored
6
+ * as-is. With tide correction on, the raw depth below the surface is used
7
+ * and reduced to {@link Config.tide.datum} instead. */
5
8
  path: (typeof DepthPaths)[number];
9
+ /** Reduce each sounding to a fixed vertical datum, using the predicted tide
10
+ * at that sounding's own time and position. */
11
+ tide?: {
12
+ enabled?: boolean;
13
+ datum?: TideDatum;
14
+ };
6
15
  /** The depth transducer's position on the hull: `y` from the bow, `x` from
7
16
  * the centreline (+starboard). Used only to place a sounding at the
8
17
  * transducer rather than at the position the server reports. */
@@ -40,7 +49,13 @@ export type Config = {
40
49
  vesselFootprint?: boolean;
41
50
  };
42
51
  };
43
- export declare const DepthPaths: readonly ["belowSurface", "belowTransducer", "belowKeel", "belowSurfaceTideCompensated", "belowSurfaceTideCompensatedLAT", "belowSurfaceTideCompensatedMLLW"];
52
+ export declare const DepthPaths: readonly ["belowSurface", "belowTransducer", "belowKeel"];
53
+ /**
54
+ * The form overrides for the saved config. The Depth selector has no effect
55
+ * while tide correction is on (the raw below-surface depth is used), so the
56
+ * admin UI should show it disabled rather than as a live control.
57
+ */
58
+ export declare function formUiSchema(config?: Pick<Config, "tide">): Record<string, object>;
44
59
  export declare function schema(app: ServerAPI): {
45
60
  type: string;
46
61
  description: string;
@@ -49,9 +64,30 @@ export declare function schema(app: ServerAPI): {
49
64
  type: string;
50
65
  title: string;
51
66
  description: string;
52
- enum: readonly ["belowSurface", "belowTransducer", "belowKeel", "belowSurfaceTideCompensated", "belowSurfaceTideCompensatedLAT", "belowSurfaceTideCompensatedMLLW"];
67
+ enum: readonly ["belowSurface", "belowTransducer", "belowKeel"];
53
68
  enumNames: string[];
54
- default?: "belowKeel" | "belowSurface" | "belowSurfaceTideCompensated" | "belowSurfaceTideCompensatedLAT" | "belowSurfaceTideCompensatedMLLW" | "belowTransducer" | undefined;
69
+ default?: "belowKeel" | "belowSurface" | "belowTransducer" | undefined;
70
+ };
71
+ tide: {
72
+ type: string;
73
+ title: string;
74
+ description: string;
75
+ properties: {
76
+ enabled: {
77
+ type: string;
78
+ default: boolean;
79
+ title: string;
80
+ description: string;
81
+ };
82
+ datum: {
83
+ type: string;
84
+ title: string;
85
+ enum: readonly TideDatum[];
86
+ enumNames: string[];
87
+ default: string;
88
+ description: string;
89
+ };
90
+ };
55
91
  };
56
92
  sounder: {
57
93
  type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,MAAM,MAAM,MAAM,GAAG;IACnB;0DACsD;IACtD,IAAI,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IAClC;;oEAEgE;IAChE,OAAO,EAAE;QACP,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;IACF;kEAC8D;IAC9D,oBAAoB,EAAE,KAAK,GAAG,SAAS,CAAC;IACxC;;;mFAG+E;IAC/E,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF;;;gEAG4D;IAC5D,cAAc,CAAC,EAAE;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,OAAO,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,oEAAoE;IACpE,KAAK,CAAC,EAAE;QACN,mEAAmE;QACnE,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,UAAU,YACrB,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,6BAA6B,EAC7B,gCAAgC,EAChC,iCAAiC,CACzB,CAAC;AAEX,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAoD7B,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,IAAI;YACJ,SAAS;YACT,OAAO;;;YAGP,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,UAAU;gBACR,OAAO;oBACL,IAAI;oBACJ,KAAK;oBACL,WAAW;oBAEX,KAAK;wBAAI,IAAI;;;gBAEf,OAAO;oBACL,IAAI;oBACJ,KAAK;oBACL,WAAW;oBACX,KAAK;wBAAI,IAAI;;;;;;YAKjB,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,UAAU;gBACR,OAAO;oBACL,IAAI;oBACJ,OAAO;oBACP,KAAK;oBACL,WAAW;;gBAGb,OAAO;oBACL,IAAI;oBACJ,OAAO;oBACP,OAAO;oBACP,KAAK;oBACL,WAAW;;gBAGb,MAAM;oBACJ,IAAI;oBACJ,OAAO;oBACP,OAAO;oBACP,KAAK;oBACL,WAAW;;;;;YAMf,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,UAAU;gBACR,GAAG;oBACD,IAAI;oBACJ,OAAO;oBACP,KAAK;oBACL,WAAW;;gBAGb,UAAU;oBACR,IAAI;oBACJ,OAAO;oBACP,KAAK;oBACL,WAAW;;;;;YAMf,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,UAAU;gBACR,eAAe;oBACb,IAAI;oBACJ,OAAO;oBACP,KAAK;oBACL,WAAW;;;;;EAOtB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -1,11 +1,17 @@
1
+ import { TIDE_DATUMS } from "./tide.js";
1
2
  export const DepthPaths = [
2
3
  "belowSurface",
3
4
  "belowTransducer",
4
5
  "belowKeel",
5
- "belowSurfaceTideCompensated",
6
- "belowSurfaceTideCompensatedLAT",
7
- "belowSurfaceTideCompensatedMLLW",
8
6
  ];
7
+ /**
8
+ * The form overrides for the saved config. The Depth selector has no effect
9
+ * while tide correction is on (the raw below-surface depth is used), so the
10
+ * admin UI should show it disabled rather than as a live control.
11
+ */
12
+ export function formUiSchema(config) {
13
+ return config?.tide?.enabled ? { path: { "ui:disabled": true } } : {};
14
+ }
9
15
  export function schema(app) {
10
16
  const defaultPath = DepthPaths.find((path) => app.getSelfPath(`environment.depth.${path}.value`));
11
17
  // A `default` is attached only when present: `default: undefined` survives in
@@ -20,18 +26,40 @@ export function schema(app) {
20
26
  path: {
21
27
  type: "string",
22
28
  title: "Depth",
23
- description: "Which depth datum to record. Signal K publishes each relative to its own reference, so no draft or transducer offset is needed here.",
29
+ description: "Which depth measurement to record. Signal K publishes each relative to its own reference, so no draft or transducer offset is needed here. Disabled while tide correction is on: the raw depth below the surface is used then and reduced to the chosen datum.",
24
30
  enum: DepthPaths,
25
31
  enumNames: [
26
32
  "Depth below surface",
27
33
  "Depth below transducer",
28
34
  "Depth below keel",
29
- "Depth below surface (tide compensated)",
30
- "Depth below surface (tide compensated, LAT)",
31
- "Depth below surface (tide compensated, MLLW)",
32
35
  ],
33
36
  ...(defaultPath === undefined ? {} : { default: defaultPath }),
34
37
  },
38
+ tide: {
39
+ type: "object",
40
+ title: "Tide correction",
41
+ description: "Store soundings relative to a fixed vertical datum by subtracting the predicted tide at each sounding's own time and position. A depth below the surface rises and falls with the tide; a depth below a datum does not, so it still means the same thing when you return months later. When this is on, depth is always calculated using 'Depth below surface'. Changing the datum clears the depth map, because the stored cells are depths in the old datum.",
42
+ properties: {
43
+ enabled: {
44
+ type: "boolean",
45
+ default: false,
46
+ title: "Correct samples for tides?",
47
+ description: "Off records the selected Depth as measured. On records the depth below the surface reduced to the datum below.",
48
+ },
49
+ datum: {
50
+ type: "string",
51
+ title: "Datum",
52
+ enum: TIDE_DATUMS,
53
+ enumNames: [
54
+ "Chart datum / local datum",
55
+ "Lowest Astronomical Tide (LAT)",
56
+ "Mean Lower Low Water (MLLW)",
57
+ ],
58
+ default: "LAT",
59
+ description: "LAT and MLLW are fixed references at each location, so a corrected sounding keeps its meaning wherever and whenever you return. Chart datum is set per chart region, so a map corrected to it cannot be compared across regions; LAT and MLLW are the predictable choices.",
60
+ },
61
+ },
62
+ },
35
63
  sounder: {
36
64
  type: "object",
37
65
  title: "Depth Transducer",
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/history.d.ts CHANGED
@@ -3,12 +3,16 @@ import { HistoryApi, ValuesResponse } from "@signalk/server-api/history";
3
3
  export type HistoryAPIOptions = {
4
4
  app: ServerAPI;
5
5
  host?: string;
6
+ /** How long to wait for a provider to register, in milliseconds. */
7
+ waitMs?: number;
8
+ /** Delay between provider checks, in milliseconds. */
9
+ pollMs?: number;
6
10
  };
7
11
  /**
8
12
  * The History Provider API is new and may not be available. This uses a provider if available,
9
13
  * and otherwise falls back to direct HTTP access if available.
10
14
  */
11
- export declare function getHistoryAPI({ app, port, host, }: HistoryAPIOptions): Promise<HistoryApi | undefined>;
15
+ export declare function getHistoryAPI({ app, port, host, waitMs, pollMs, }: HistoryAPIOptions): Promise<HistoryApi | undefined>;
12
16
  /**
13
17
  * Build a lookup from SignalK path to its column index in a history `DataRow`.
14
18
  *
@@ -1 +1 @@
1
- {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAGL,UAAU,EAIV,cAAc,EACf,MAAM,6BAA6B,CAAC;AAKrC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,SAAS,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAMF;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAClC,GAAG,EAEH,IAAqE,EACrE,IAA8D,GAC/D,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA2DrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,SAAS,EAC5C,SAAS,EAAE,SAAS;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,EAAE,GACnC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrB"}
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAGL,UAAU,EAIV,cAAc,EACf,MAAM,6BAA6B,CAAC;AAKrC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,SAAS,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AASF;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAClC,GAAG,EAEH,IAAqE,EACrE,IAA8D,EAC9D,MAAiC,EACjC,MAAiC,GAClC,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAwErD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,SAAS,EAC5C,SAAS,EAAE,SAAS;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,EAAE,GACnC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrB"}
package/dist/history.js CHANGED
@@ -1,23 +1,24 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { BATHY_EPOCH } from "./constants.js";
3
3
  import fetch from "./fetch.js";
4
+ const DEFAULT_PROVIDER_WAIT_MS = 30_000;
5
+ const DEFAULT_PROVIDER_POLL_MS = 500;
4
6
  /**
5
7
  * The History Provider API is new and may not be available. This uses a provider if available,
6
8
  * and otherwise falls back to direct HTTP access if available.
7
9
  */
8
10
  export async function getHistoryAPI({ app,
9
11
  // @ts-expect-error: app.config is not a public API
10
- port = Number(process.env.PORT) || app.config?.settings?.port || 3000, host = process.env.SIGNALK_HOST ?? `http://localhost:${port}/`, }) {
11
- try {
12
- // Try to get the built-in history provider API first. It either returns the API or throws
13
- // an error if no provider is configured. It will also throw if the API is not available.
14
- const api = await app.getHistoryApi();
12
+ port = Number(process.env.PORT) || app.config?.settings?.port || 3000, host = process.env.SIGNALK_HOST ?? `http://localhost:${port}/`, waitMs = DEFAULT_PROVIDER_WAIT_MS, pollMs = DEFAULT_PROVIDER_POLL_MS, }) {
13
+ // Plugins start in an unspecified order, so this plugin can reach the History
14
+ // API before the provider plugin has registered. Wait briefly for it instead
15
+ // of falling through to the HTTP path, which may not be reachable from the
16
+ // server's own network namespace.
17
+ const api = await waitForProvider();
18
+ if (api) {
15
19
  app.debug("Using built-in history provider");
16
20
  return api;
17
21
  }
18
- catch {
19
- // History provider API not available or no provider configured.
20
- }
21
22
  try {
22
23
  await getContexts({
23
24
  from: BATHY_EPOCH,
@@ -30,6 +31,21 @@ port = Number(process.env.PORT) || app.config?.settings?.port || 3000, host = pr
30
31
  return;
31
32
  }
32
33
  return { getValues, getContexts, getPaths };
34
+ async function waitForProvider() {
35
+ if (typeof app.getHistoryApi !== "function")
36
+ return undefined;
37
+ const deadline = Date.now() + waitMs;
38
+ for (;;) {
39
+ try {
40
+ return await app.getHistoryApi();
41
+ }
42
+ catch {
43
+ if (Date.now() >= deadline)
44
+ return undefined;
45
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
46
+ }
47
+ }
48
+ }
33
49
  async function get(path, params = {}) {
34
50
  const url = new URL(path, new URL("/signalk/v2/api/history/", host));
35
51
  url.search = new URLSearchParams(params).toString();
@@ -1 +1 @@
1
- {"version":3,"file":"history.js","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,MAAM,YAAY,CAAC;AAW/B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAClC,GAAG;AACH,mDAAmD;AACnD,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,IAAI,IAAI,EACrE,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,oBAAoB,IAAI,GAAG,GAC5C;IAClB,IAAI,CAAC;QACH,0FAA0F;QAC1F,yFAAyF;QACzF,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,aAAc,EAAE,CAAC;QACvC,GAAG,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAC7C,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,gEAAgE;IAClE,CAAC;IAED,IAAI,CAAC;QACH,MAAM,WAAW,CAAC;YAChB,IAAI,EAAE,WAAW;YACjB,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE;SAC3B,CAAC,CAAC;QACH,GAAG,CAAC,KAAK,CAAC,mCAAmC,EAAE,IAAI,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;IAE5C,KAAK,UAAU,GAAG,CAAC,IAAY,EAAE,MAAM,GAA2B,EAAE;QAClE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC,CAAC;QACrE,GAAG,CAAC,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QAEpD,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEzC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,mBAAmB,GAAG,KAAK,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAC1D,CAAC;QACJ,CAAC;QAED,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,SAAS,SAAS,CAAC,EACjB,SAAS,EACT,GAAG,KAAK,EACM;QACd,OAAO,GAAG,CAAC,QAAQ,EAAE;YACnB,GAAG,KAAK;YACR,KAAK,EAAE,SAAS;iBACb,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;iBACzD,IAAI,CAAC,GAAG,CAAC;SACb,CAAC,CAAC;IACL,CAAC;IAED,SAAS,WAAW,CAAC,KAAuB;QAC1C,OAAO,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,SAAS,QAAQ,CAAC,KAAoB;QACpC,OAAO,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC3B,MAA4C,EAC5C,SAAoC;IAEpC,MAAM,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAgB,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,gBAAgB,EAAa,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAU,MAAM,EAAE,MAAM,aAAa,CAAC;AAa7C;;;;;;;mEAOmE;AACnE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAWpE;AAED;sEACsE;AACtE,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAOjE;AAQD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,GACb,gBAAgB,GAAG,SAAS,CA2B9B;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CA4H3D"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAgB,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,gBAAgB,EAAa,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAwB,MAAM,EAAE,MAAM,aAAa,CAAC;AAuB3D;;;;;;;mEAOmE;AACnE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAWpE;AAED;sEACsE;AACtE,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAOjE;AAQD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,GACb,gBAAgB,GAAG,SAAS,CA2B9B;AAOD,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAqM3D"}
package/dist/plugin.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { join } from "path";
2
- import { schema } from "./config.js";
2
+ import { schema, formUiSchema } from "./config.js";
3
3
  import { createTileDB, createTileStore } from "./tiles/store.js";
4
4
  import { createTileWriter } from "./tiles/writer.js";
5
- import { seedFromHistory } from "./tiles/seed.js";
5
+ import { SEED_CURSOR_KEY, seedFromHistory } from "./tiles/seed.js";
6
+ import { TIDE_MODE_KEY, createSlackwaterTideProvider, createTideCorrection, datumLabel, shouldResetGrid, tideMode, } from "./tide.js";
6
7
  import { registerBathymetryRoutes, registerSeedRoutes, } from "./tiles/api.js";
7
8
  import { registerPlotterExtension } from "./plotterext/provider.js";
8
9
  import { createCollector } from "./collector.js";
@@ -71,6 +72,10 @@ export function resolveTransducerOffset(app, config) {
71
72
  starboard: config.sounder?.x ?? 0,
72
73
  };
73
74
  }
75
+ /** A parenthetical for rows dropped because the tide was unavailable. */
76
+ function skipNote(skipped) {
77
+ return skipped > 0 ? ` (${skipped} skipped: no tide prediction)` : "";
78
+ }
74
79
  export default function createPlugin(app) {
75
80
  let abortController = undefined;
76
81
  // The gridded store served to chart clients. Built in start(), read by the
@@ -78,6 +83,9 @@ export default function createPlugin(app) {
78
83
  let tileStore;
79
84
  let config;
80
85
  let transducer;
86
+ let tide;
87
+ /** The datum the store is in; also reported to chart clients. */
88
+ let tideState = "off";
81
89
  let seedStatus = { running: false };
82
90
  let running = false;
83
91
  let routesRegistered = false;
@@ -88,6 +96,14 @@ export default function createPlugin(app) {
88
96
  const cfg = config;
89
97
  if (!store || !cfg)
90
98
  return;
99
+ // The store's datum is fixed once it holds cells: fold history with the
100
+ // same correction it was built with, and refuse rather than write raw rows
101
+ // into a datum grid.
102
+ if (tideState !== "off" && !tide) {
103
+ seedStatus = { running: false, error: "tide model unavailable" };
104
+ app.setPluginError("Cannot rebuild from history: the tide model is unavailable, so raw depths would be stored in the wrong datum.");
105
+ return;
106
+ }
91
107
  seedStatus = { running: true };
92
108
  app.setPluginStatus("Rebuilding bathymetry tiles from history…");
93
109
  try {
@@ -97,14 +113,15 @@ export default function createPlugin(app) {
97
113
  config: cfg,
98
114
  resolution,
99
115
  offset: transducer,
116
+ tide,
100
117
  signal: abortController?.signal,
101
118
  onProgress: (p) => {
102
119
  seedStatus = { running: !p.done, progress: p };
103
- app.setPluginStatus(`Rebuilding bathymetry tiles: ${p.points} points over ${p.chunks} windows`);
120
+ app.setPluginStatus(`Rebuilding bathymetry tiles: ${p.points} points over ${p.chunks} windows${skipNote(p.skipped)}`);
104
121
  },
105
122
  });
106
123
  seedStatus = { running: false, progress };
107
- app.setPluginStatus(`Bathymetry rebuild complete: ${progress.points} points`);
124
+ app.setPluginStatus(`Bathymetry rebuild complete: ${progress.points} points${skipNote(progress.skipped)}`);
108
125
  }
109
126
  catch (err) {
110
127
  seedStatus = { running: false, error: String(err) };
@@ -122,21 +139,63 @@ export default function createPlugin(app) {
122
139
  abortController = new AbortController();
123
140
  tileStore = createTileStore(createTileDB(join(app.getDataDirPath(), `bathymetry-tiles.sqlite`)));
124
141
  transducer = resolveTransducerOffset(app, config);
125
- createCollector({
126
- app,
127
- config,
128
- writer: createTileWriter(tileStore),
129
- offset: transducer,
130
- signal: abortController.signal,
131
- });
132
- app.setPluginStatus("Collecting depth into the bathymetry tile store");
142
+ // Build the tide source before the collector starts, so the first
143
+ // sounding already goes through it. The station database loads here, once.
144
+ const configured = tideMode(config);
145
+ tideState = configured;
146
+ tide = undefined;
147
+ let collecting = true;
148
+ if (configured !== "off") {
149
+ try {
150
+ tide = createTideCorrection(await createSlackwaterTideProvider(), configured);
151
+ }
152
+ catch (err) {
153
+ // A transient model failure must not clear or relabel an existing
154
+ // grid, nor write raw depths into a datum grid. Keep the stored
155
+ // datum, record nothing, and surface the error.
156
+ collecting = false;
157
+ const stored = tileStore.getMeta(TIDE_MODE_KEY);
158
+ tideState = stored === undefined ? "off" : stored;
159
+ app.setPluginError(`Tide correction is on but the tide model could not be loaded ` +
160
+ `(${err}); recording is paused and the existing grid is kept.`);
161
+ }
162
+ }
163
+ if (collecting) {
164
+ // The cells hold depths, not raw soundings, so a grid built in another
165
+ // datum cannot be corrected in place. Clearing is the only way to avoid
166
+ // silently mixing datums; the old seed cursor goes with it.
167
+ const reset = shouldResetGrid(tileStore.getMeta(TIDE_MODE_KEY), tideState);
168
+ if (reset) {
169
+ tileStore.clear();
170
+ tileStore.setMeta(SEED_CURSOR_KEY, "");
171
+ }
172
+ tileStore.setMeta(TIDE_MODE_KEY, tideState);
173
+ createCollector({
174
+ app,
175
+ config,
176
+ writer: createTileWriter(tileStore),
177
+ offset: transducer,
178
+ tide,
179
+ signal: abortController.signal,
180
+ });
181
+ app.setPluginStatus(tideState === "off"
182
+ ? "Collecting depth into the bathymetry tile store"
183
+ : `Collecting depth into the bathymetry tile store, relative to ${datumLabel(tideState)}`);
184
+ if (reset) {
185
+ app.setPluginStatus("The tide datum changed, so the bathymetry grid was cleared. " +
186
+ "Rebuild from history to repopulate it.");
187
+ }
188
+ }
133
189
  running = true;
134
190
  // Offer the chart layer to a host chartplotter, and serve the store the
135
191
  // layer reads from a top-level, non-admin route.
136
192
  registerPlotterExtension(app, () => running);
137
193
  if (!routesRegistered) {
138
194
  registerBathymetryRoutes(app, () => tileStore, () => ({
139
- depthPath: config ? `environment.depth.${config.path}` : undefined,
195
+ depthPath: config
196
+ ? `environment.depth.${tideState === "off" ? config.path : "belowSurface"}`
197
+ : undefined,
198
+ tide: { mode: tideState },
140
199
  transducer,
141
200
  vessel: {
142
201
  loa: readLengthOverall(app),
@@ -148,7 +207,7 @@ export default function createPlugin(app) {
148
207
  }));
149
208
  routesRegistered = true;
150
209
  }
151
- if (startConfig.seed?.run) {
210
+ if (collecting && startConfig.seed?.run) {
152
211
  // One-time rebuild. Clear the trigger when it finishes (or fails) so it
153
212
  // doesn't fire again on the next start; an interrupted run resumes from
154
213
  // the stored cursor instead of repeating.
@@ -167,6 +226,9 @@ export default function createPlugin(app) {
167
226
  schema() {
168
227
  return schema(app);
169
228
  },
229
+ uiSchema() {
230
+ return formUiSchema(config);
231
+ },
170
232
  registerWithRouter(router) {
171
233
  // The one-time seed trigger stays admin-only, on the plugin's own router;
172
234
  // the store the extension reads is served from the top-level route above.