signalk-livedepth-plugin 0.3.0 → 0.3.2

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,52 @@ 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.2] - 2026-10-19
8
+
9
+ ### Added
10
+
11
+ - Depth from **other vessels**. The plugin now records any vessel on the server
12
+ that publishes a depth on the selected datum, so a tender or a second
13
+ sounder-equipped vessel (for example a dinghy) is folded into the bathymetry
14
+ automatically. AIS targets — which have a position but no depth — are ignored.
15
+ The transducer correction describes this vessel's hull, so it is applied to
16
+ this vessel's soundings only. `vessels.include` / `vessels.exclude` narrow the
17
+ set if needed.
18
+ - A **Render quality** setting: Auto, or High / Balanced / Low. It scales only
19
+ the shaded depth surface; soundings, circles and labels always render at full
20
+ resolution. Auto adapts the surface to the GPU, and a frame-rate readout is
21
+ logged to the console.
22
+ - A temporary `debug.vesselFootprint` option draws the vessel's LOA × beam on
23
+ the chart layer, for checking where a sounding lands relative to the hull.
24
+
25
+ ### Fixed
26
+
27
+ - A transducer forward of midships is placed ahead of the reference, not behind
28
+ it. The offset sign was inverted, which drew every sounding astern.
29
+ - The chart layer accepts the host's object-shaped `navigation.position`
30
+ (`{ latitude, longitude }`), so live soundings are no longer silently dropped.
31
+ - Panning or zooming re-fetches the tiles for the new view; previously the layer
32
+ kept drawing the previous view's tile set.
33
+ - A vessel that goes quiet keeps its subscription, so the deltas it replays on
34
+ reconnect are recorded rather than lost. A GPS-only vessel with no compass
35
+ falls back to course over ground for its heading.
36
+
37
+ ### Changed
38
+
39
+ - The chart layer renders at the display's pixel ratio instead of oversampling
40
+ at 2×, caches the per-frame cluster aggregation, and patches the depth field
41
+ incrementally on a live flush instead of rebuilding the grid. This cuts the
42
+ browser's CPU/GPU cost substantially, especially on a lower-powered GPU.
43
+
44
+ ## [0.3.1] - 2026-10-16
45
+
46
+ ### Fixed
47
+
48
+ - The chart layer no longer lags the vessel by 2–3 seconds. Live soundings were
49
+ batched for two seconds, and each batch re-fetched every tile for the view
50
+ over HTTP; they now fold into the frame every 300 ms without a network round
51
+ trip, so the surface fills in under the bow.
52
+
7
53
  ## [0.3.0] - 2026-10-15
8
54
 
9
55
  The LiveDepth chart layer, and the merge that brings everything into one
package/dist/config.d.ts CHANGED
@@ -13,10 +13,23 @@ export type Config = {
13
13
  /** Shift soundings from the vessel reference point the server reports to the
14
14
  * transducer. `off` leaves them where the server put them. */
15
15
  transducerCorrection: "off" | "replace";
16
+ /** Which vessels to record soundings from. By default every vessel that
17
+ * publishes a depth on the selected datum is recorded — a tender or a second
18
+ * sounder-equipped vessel is picked up automatically, while AIS targets
19
+ * (position but no depth) are ignored. `include`/`exclude` narrow that set. */
20
+ vessels?: {
21
+ include?: string[];
22
+ exclude?: string[];
23
+ };
16
24
  seed?: {
17
25
  run?: boolean;
18
26
  resolution?: number;
19
27
  };
28
+ /** Temporary diagnostics, removed once the geometry is verified. */
29
+ debug?: {
30
+ /** Draw the vessel's footprint (LOA × beam) in the chart layer. */
31
+ vesselFootprint?: boolean;
32
+ };
20
33
  };
21
34
  export declare const DepthPaths: readonly ["belowSurface", "belowTransducer", "belowKeel"];
22
35
  export declare function schema(app: ServerAPI): {
@@ -59,6 +72,29 @@ export declare function schema(app: ServerAPI): {
59
72
  enumNames: string[];
60
73
  default: string;
61
74
  };
75
+ vessels: {
76
+ type: string;
77
+ title: string;
78
+ description: string;
79
+ properties: {
80
+ include: {
81
+ type: string;
82
+ title: string;
83
+ description: string;
84
+ items: {
85
+ type: string;
86
+ };
87
+ };
88
+ exclude: {
89
+ type: string;
90
+ title: string;
91
+ description: string;
92
+ items: {
93
+ type: string;
94
+ };
95
+ };
96
+ };
97
+ };
62
98
  seed: {
63
99
  type: string;
64
100
  title: string;
@@ -78,6 +114,19 @@ export declare function schema(app: ServerAPI): {
78
114
  };
79
115
  };
80
116
  };
117
+ debug: {
118
+ type: string;
119
+ title: string;
120
+ description: string;
121
+ properties: {
122
+ vesselFootprint: {
123
+ type: string;
124
+ default: boolean;
125
+ title: string;
126
+ description: string;
127
+ };
128
+ };
129
+ };
81
130
  };
82
131
  };
83
132
  //# sourceMappingURL=config.d.ts.map
@@ -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,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,OAAO,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,UAAU,YACrB,cAAc,EACd,iBAAiB,EACjB,WAAW,CACH,CAAC;AAEX,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAiD7B,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,IAAI;YACJ,SAAS;YACT,OAAO;;;YAGP,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;;;;;EAOtB"}
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,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,CACH,CAAC;AAEX,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAiD7B,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,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"}
package/dist/config.js CHANGED
@@ -12,7 +12,7 @@ export function schema(app) {
12
12
  const fromCenter = app.getSelfPath("sensors.depth.fromCenter.value");
13
13
  return {
14
14
  type: "object",
15
- description: "Build a gridded bathymetry tile store from the vessel's own position and depth, and serve it to a chart client's LiveDepth layer.",
15
+ description: "Build a gridded bathymetry tile store from vessel soundings — your own and any other depth-equipped vessel on the server — and serve it to a chart client's LiveDepth layer.",
16
16
  properties: {
17
17
  path: {
18
18
  type: "string",
@@ -54,6 +54,25 @@ export function schema(app) {
54
54
  enumNames: ["Off", "Replace with the transducer's position"],
55
55
  default: "off",
56
56
  },
57
+ vessels: {
58
+ type: "object",
59
+ title: "Vessels",
60
+ description: "Which vessel contexts to record. By default every vessel that publishes a depth on the selected datum is recorded — a tender or a second depth-equipped vessel is picked up automatically, while AIS targets (position but no depth) are ignored. Use these lists to narrow that set.",
61
+ properties: {
62
+ include: {
63
+ type: "array",
64
+ title: "Only these vessels",
65
+ description: "Record only these Signal K contexts, e.g. vessels.urn:mrn:signalk:uuid:…. Empty records every vessel that publishes a depth.",
66
+ items: { type: "string" },
67
+ },
68
+ exclude: {
69
+ type: "array",
70
+ title: "Never these vessels",
71
+ description: "Contexts to ignore even when they publish a depth.",
72
+ items: { type: "string" },
73
+ },
74
+ },
75
+ },
57
76
  seed: {
58
77
  type: "object",
59
78
  title: "Rebuild from History",
@@ -73,6 +92,19 @@ export function schema(app) {
73
92
  },
74
93
  },
75
94
  },
95
+ debug: {
96
+ type: "object",
97
+ title: "Debug",
98
+ description: "Temporary diagnostics, not for general use — they will be removed.",
99
+ properties: {
100
+ vesselFootprint: {
101
+ type: "boolean",
102
+ default: false,
103
+ title: "Draw the vessel footprint",
104
+ description: "Draw the vessel's footprint (design.length.overall × design.beam) on the chart layer, centred on navigation.position and rotated to the heading. Useful for checking where soundings land relative to the hull.",
105
+ },
106
+ },
107
+ },
76
108
  },
77
109
  };
78
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAsBA,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,cAAc;IACd,iBAAiB;IACjB,WAAW;CACH,CAAC;AAEX,MAAM,UAAU,MAAM,CAAC,GAAc;IACnC,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3C,GAAG,CAAC,WAAW,CAAC,qBAAqB,IAAI,QAAQ,CAAC,CACnD,CAAC;IACF,8EAA8E;IAC9E,6EAA6E;IAC7E,8CAA8C;IAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC,6BAA6B,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAC;IACrE,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,WAAW,EACT,mIAAmI;QACrI,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,OAAO;gBACd,WAAW,EACT,sIAAsI;gBACxI,IAAI,EAAE,UAAU;gBAChB,SAAS,EAAE;oBACT,qBAAqB;oBACrB,wBAAwB;oBACxB,kBAAkB;iBACnB;gBACD,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;aAC/D;YACD,OAAO,EAAE;gBACP,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,kBAAkB;gBACzB,WAAW,EACT,sHAAsH;gBACxH,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;gBACpB,UAAU,EAAE;oBACV,CAAC,EAAE;wBACD,IAAI,EAAE,QAAQ;wBACd,KAAK,EAAE,kDAAkD;wBACzD,OAAO,EAAE,CAAC;wBACV,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;qBACvD;oBACD,CAAC,EAAE;wBACD,IAAI,EAAE,QAAQ;wBACd,KAAK,EAAE,qDAAqD;wBAC5D,WAAW,EAAE,+BAA+B;wBAC5C,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;qBAC7D;iBACF;aACF;YACD,oBAAoB,EAAE;gBACpB,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,gCAAgC;gBACvC,WAAW,EACT,2UAA2U;gBAC7U,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC;gBACxB,SAAS,EAAE,CAAC,KAAK,EAAE,wCAAwC,CAAC;gBAC5D,OAAO,EAAE,KAAK;aACf;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,sBAAsB;gBAC7B,WAAW,EACT,iQAAiQ;gBACnQ,UAAU,EAAE;oBACV,GAAG,EAAE;wBACH,IAAI,EAAE,SAAS;wBACf,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,+CAA+C;wBACtD,WAAW,EACT,wGAAwG;qBAC3G;oBACD,UAAU,EAAE;wBACV,IAAI,EAAE,QAAQ;wBACd,OAAO,EAAE,GAAG;wBACZ,KAAK,EAAE,8BAA8B;wBACrC,WAAW,EACT,kGAAkG;qBACrG;iBACF;aACF;SACF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/plotterext/manifest.ts"],"names":[],"mappings":"AAIA,0EAA0E;AAC1E,eAAO,MAAM,SAAS,6BAA6B,CAAC;AACpD,iFAAiF;AACjF,eAAO,MAAM,UAAU,yCAA6B,CAAC;AAErD,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAeD;;;;uEAIuE;AACvE,wBAAgB,aAAa,CAAC,OAAO,SAAkB,GAAG,kBAAkB,CAsD3E"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/plotterext/manifest.ts"],"names":[],"mappings":"AAIA,0EAA0E;AAC1E,eAAO,MAAM,SAAS,6BAA6B,CAAC;AACpD,iFAAiF;AACjF,eAAO,MAAM,UAAU,yCAA6B,CAAC;AAErD,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAeD;;;;uEAIuE;AACvE,wBAAgB,aAAa,CAAC,OAAO,SAAkB,GAAG,kBAAkB,CAuD3E"}
@@ -68,9 +68,10 @@ export function buildManifest(version = pluginVersion()) {
68
68
  // layer's settings sit with the layer, not only behind the toolbar
69
69
  // button.
70
70
  configPanel: "livedepth-settings",
71
- // Isobaths and labels want the pixels; the host still clamps to its own
72
- // memory budget.
73
- preferredPixelRatio: 2,
71
+ // No `preferredPixelRatio`: the host then uses the display's own device
72
+ // pixel ratio, which is native and sharp. Asking for 2 oversampled on a
73
+ // 1×/1.33× display, and that cost is paid per frame in the transfer and
74
+ // composite — the render-quality setting scales further if needed.
74
75
  },
75
76
  ],
76
77
  };
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/plotterext/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,SAAS,GAAG,0BAA0B,CAAC;AACpD,iFAAiF;AACjF,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,SAAS,EAAE,CAAC;AAgBrD,4DAA4D;AAC5D,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACrC,CAAC;QAC1B,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;uEAIuE;AACvE,MAAM,UAAU,aAAa,CAAC,OAAO,GAAG,aAAa,EAAE;IACrD,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,WAAW,EACT,wFAAwF;QAC1F,OAAO;QACP,UAAU,EAAE,GAAG;QACf,QAAQ,EAAE,CAAC,mBAAmB,EAAE,gBAAgB,EAAE,gBAAgB,CAAC;QACnE,QAAQ,EAAE,CAAC,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC;QACnE,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,yBAAyB;gBAC7B,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,oBAAoB,EAAE;aAC7D;SACF;QACD,MAAM,EAAE;YACN;gBACE,EAAE,EAAE,oBAAoB;gBACxB,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,QAAQ;gBACd,GAAG,EAAE,GAAG,UAAU,cAAc;gBAChC,SAAS,EAAE,WAAW;aACvB;SACF;QACD,UAAU,EAAE;YACV;gBACE,EAAE,EAAE,UAAU;gBACd,IAAI,EAAE,QAAQ;gBACd,GAAG,EAAE,GAAG,UAAU,gBAAgB;aACnC;SACF;QACD,QAAQ,EAAE;YACR;gBACE,EAAE,EAAE,OAAO;gBACX,KAAK,EAAE,YAAY;gBACnB,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,UAAU;gBACnB,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;gBACV,oEAAoE;gBACpE,mEAAmE;gBACnE,UAAU;gBACV,WAAW,EAAE,oBAAoB;gBACjC,wEAAwE;gBACxE,iBAAiB;gBACjB,mBAAmB,EAAE,CAAC;aACvB;SACF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/plotterext/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,SAAS,GAAG,0BAA0B,CAAC;AACpD,iFAAiF;AACjF,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,SAAS,EAAE,CAAC;AAgBrD,4DAA4D;AAC5D,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACrC,CAAC;QAC1B,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;uEAIuE;AACvE,MAAM,UAAU,aAAa,CAAC,OAAO,GAAG,aAAa,EAAE;IACrD,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,WAAW,EACT,wFAAwF;QAC1F,OAAO;QACP,UAAU,EAAE,GAAG;QACf,QAAQ,EAAE,CAAC,mBAAmB,EAAE,gBAAgB,EAAE,gBAAgB,CAAC;QACnE,QAAQ,EAAE,CAAC,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC;QACnE,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,yBAAyB;gBAC7B,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,oBAAoB,EAAE;aAC7D;SACF;QACD,MAAM,EAAE;YACN;gBACE,EAAE,EAAE,oBAAoB;gBACxB,KAAK,EAAE,WAAW;gBAClB,IAAI,EAAE,QAAQ;gBACd,GAAG,EAAE,GAAG,UAAU,cAAc;gBAChC,SAAS,EAAE,WAAW;aACvB;SACF;QACD,UAAU,EAAE;YACV;gBACE,EAAE,EAAE,UAAU;gBACd,IAAI,EAAE,QAAQ;gBACd,GAAG,EAAE,GAAG,UAAU,gBAAgB;aACnC;SACF;QACD,QAAQ,EAAE;YACR;gBACE,EAAE,EAAE,OAAO;gBACX,KAAK,EAAE,YAAY;gBACnB,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,UAAU;gBACnB,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;gBACV,oEAAoE;gBACpE,mEAAmE;gBACnE,UAAU;gBACV,WAAW,EAAE,oBAAoB;gBACjC,wEAAwE;gBACxE,wEAAwE;gBACxE,wEAAwE;gBACxE,mEAAmE;aACpE;SACF;KACF,CAAC;AACJ,CAAC"}
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ServerAPI, Plugin } from "@signalk/server-api";
2
+ import { TransducerOffset } from "./tiles/core/index.js";
3
+ import { Config } from "./config.js";
2
4
  /** The vessel's length overall, for locating the reference point the server
3
5
  * reports (the CCRP is midships).
4
6
  *
@@ -8,5 +10,18 @@ import { ServerAPI, Plugin } from "@signalk/server-api";
8
10
  * take `.value.overall` (matching how the server's own GNSS corrector reads it).
9
11
  * Handles `getSelfPath` returning either the value or its node. */
10
12
  export declare function readLengthOverall(app: ServerAPI): number | undefined;
13
+ /** The vessel's beam, for the debug footprint overlay. `design.beam` is a leaf
14
+ * on most servers but read defensively in case it is a value node. */
15
+ export declare function readVesselBeam(app: ServerAPI): number | undefined;
16
+ /**
17
+ * The body-frame offset from the reference point the server reports to the
18
+ * transducer, or undefined when correction is off (or can't be located).
19
+ *
20
+ * The server corrects `navigation.position` to the CCRP (midships on the
21
+ * centreline) only when its own `gnssCorrection` is on. When it is off the
22
+ * position is the GNSS antenna, whose body position we do not know — so the
23
+ * offset is meaningless. We still apply what the user configured, but flag it.
24
+ */
25
+ export declare function resolveTransducerOffset(app: ServerAPI, config: Config): TransducerOffset | undefined;
11
26
  export default function createPlugin(app: ServerAPI): Plugin;
12
27
  //# sourceMappingURL=plugin.d.ts.map
@@ -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;AAgBtE;;;;;;;mEAOmE;AACnE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAWpE;AA+CD,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAqH3D"}
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"}
package/dist/plugin.js CHANGED
@@ -24,6 +24,15 @@ export function readLengthOverall(app) {
24
24
  ? overall
25
25
  : undefined;
26
26
  }
27
+ /** The vessel's beam, for the debug footprint overlay. `design.beam` is a leaf
28
+ * on most servers but read defensively in case it is a value node. */
29
+ export function readVesselBeam(app) {
30
+ const node = app.getSelfPath("design.beam");
31
+ const value = typeof node === "number" ? node : node?.value;
32
+ return typeof value === "number" && Number.isFinite(value) && value > 0
33
+ ? value
34
+ : undefined;
35
+ }
27
36
  /** The server's GNSS antenna correction mode; `off` unless configured. */
28
37
  function readGnssCorrection(app) {
29
38
  // @ts-expect-error: app.config is not a public API
@@ -38,7 +47,7 @@ function readGnssCorrection(app) {
38
47
  * position is the GNSS antenna, whose body position we do not know — so the
39
48
  * offset is meaningless. We still apply what the user configured, but flag it.
40
49
  */
41
- function resolveTransducerOffset(app, config) {
50
+ export function resolveTransducerOffset(app, config) {
42
51
  if (config.transducerCorrection !== "replace") {
43
52
  return undefined;
44
53
  }
@@ -56,7 +65,9 @@ function resolveTransducerOffset(app, config) {
56
65
  return undefined;
57
66
  }
58
67
  return {
59
- forward: (config.sounder?.y ?? 0) - length / 2,
68
+ // `sounder.y` is the distance from the bow, so a transducer forward of
69
+ // midships sits at a smaller `y` and is a positive shift toward the bow.
70
+ forward: length / 2 - (config.sounder?.y ?? 0),
60
71
  starboard: config.sounder?.x ?? 0,
61
72
  };
62
73
  }
@@ -104,7 +115,7 @@ export default function createPlugin(app) {
104
115
  return {
105
116
  id: "signalk-livedepth-plugin",
106
117
  name: "LiveDepth",
107
- description: "Serve a gridded bathymetry tile store built from the vessel's own position and depth, for a chart plotter's LiveDepth layer.",
118
+ description: "Serve a gridded bathymetry tile store built from vessel soundings — your own and any other depth-equipped vessel on the server — for a chart plotter's LiveDepth layer.",
108
119
  async start(startConfig, restart) {
109
120
  config = startConfig;
110
121
  app.debug("Starting signalk-livedepth-plugin");
@@ -127,6 +138,13 @@ export default function createPlugin(app) {
127
138
  registerBathymetryRoutes(app, () => tileStore, () => ({
128
139
  depthPath: config ? `environment.depth.${config.path}` : undefined,
129
140
  transducer,
141
+ vessel: {
142
+ loa: readLengthOverall(app),
143
+ beam: readVesselBeam(app),
144
+ },
145
+ debug: {
146
+ vesselFootprint: config?.debug?.vesselFootprint === true,
147
+ },
130
148
  }));
131
149
  routesRegistered = true;
132
150
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -3,9 +3,21 @@ import { PassThrough } from "stream";
3
3
  import { TransducerOffset } from "../tiles/core/index.js";
4
4
  import { Config } from "../config.js";
5
5
  /**
6
- * Subscribe to the vessel's own depth and pair each sounding with the last
7
- * position fix, emitting `BathymetryData` objects. When `offset` is given, the
8
- * position is shifted from the vessel reference point to the transducer.
6
+ * Subscribe to every vessel that publishes a depth on the configured datum,
7
+ * pair each sounding with that vessel's own position fix, and emit
8
+ * `BathymetryData`. Vessels are discovered from the data model: one that
9
+ * publishes a depth is recorded, an AIS target (position but no depth) is not,
10
+ * so a tender or a second sounder-equipped vessel is picked up automatically.
11
+ *
12
+ * The vessel's position and heading are subscribed alongside the depth, so a
13
+ * sounding is paired with the fix it was recorded at — including a replayed
14
+ * (back-dated) sounding from a vessel that was offline, which the data model's
15
+ * current position would place wrongly. The model is the fallback before the
16
+ * stream has carried a fix. A vessel with no compass has only course over
17
+ * ground, so that is the heading fallback.
18
+ *
19
+ * The transducer `offset` describes this vessel's hull, so it is applied only
20
+ * to this vessel's own soundings.
9
21
  */
10
22
  export declare function createLiveStream(app: ServerAPI, config: Config, offset?: TransducerOffset): PassThrough;
11
23
  //# sourceMappingURL=live.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/streams/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAuB,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,gBAAgB,eAyE1B"}
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/streams/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAuB,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA8CtC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,gBAAgB,eAmK1B"}
@@ -3,55 +3,176 @@ import { correctToTransducer } from "../tiles/core/index.js";
3
3
  import { Temporal } from "@js-temporal/polyfill";
4
4
  /** Maximum age of last position fix for a depth to be saved */
5
5
  const ttl = 2000;
6
+ /** How often to look for vessels that have started (or stopped) publishing. */
7
+ const scanIntervalMs = 15000;
8
+ /** A vessel's own fix and heading, subscribed alongside its depth so a
9
+ * sounding is paired with the position it was recorded at. */
10
+ const POSITION = "navigation.position";
11
+ const HEADING = "navigation.headingTrue";
12
+ /** Course over ground — the heading fallback for a vessel with no compass. */
13
+ const COURSE = "navigation.courseOverGroundTrue";
14
+ /** The `[lon, lat]` of a position in either raw (`{ longitude, latitude }`) or
15
+ * data-model node (`{ value: { … }, timestamp }`) form. */
16
+ function positionOf(value) {
17
+ const v = value?.value ?? value;
18
+ if (!v || typeof v !== "object")
19
+ return undefined;
20
+ const { longitude, latitude } = v;
21
+ if (typeof longitude === "number" && typeof latitude === "number") {
22
+ return { longitude, latitude };
23
+ }
24
+ return undefined;
25
+ }
6
26
  /**
7
- * Subscribe to the vessel's own depth and pair each sounding with the last
8
- * position fix, emitting `BathymetryData` objects. When `offset` is given, the
9
- * position is shifted from the vessel reference point to the transducer.
27
+ * Subscribe to every vessel that publishes a depth on the configured datum,
28
+ * pair each sounding with that vessel's own position fix, and emit
29
+ * `BathymetryData`. Vessels are discovered from the data model: one that
30
+ * publishes a depth is recorded, an AIS target (position but no depth) is not,
31
+ * so a tender or a second sounder-equipped vessel is picked up automatically.
32
+ *
33
+ * The vessel's position and heading are subscribed alongside the depth, so a
34
+ * sounding is paired with the fix it was recorded at — including a replayed
35
+ * (back-dated) sounding from a vessel that was offline, which the data model's
36
+ * current position would place wrongly. The model is the fallback before the
37
+ * stream has carried a fix. A vessel with no compass has only course over
38
+ * ground, so that is the heading fallback.
39
+ *
40
+ * The transducer `offset` describes this vessel's hull, so it is applied only
41
+ * to this vessel's own soundings.
10
42
  */
11
43
  export function createLiveStream(app, config, offset) {
12
- const path = `environment.depth.${config.path}`;
13
- app.debug(`Collecting depth data from environment.depth.${path}`);
14
- const unsubscribes = [];
44
+ const depthPath = `environment.depth.${config.path}`;
45
+ const include = new Set(config.vessels?.include ?? []);
46
+ const exclude = new Set(config.vessels?.exclude ?? []);
47
+ const selfUrn = app.getSelfPath("uuid");
48
+ const selfContext = selfUrn ? `vessels.${selfUrn}` : "vessels.self";
49
+ app.debug(`Collecting ${depthPath} from every vessel that publishes it`);
15
50
  const stream = new PassThrough({ objectMode: true });
16
- stream.on("close", () => {
17
- unsubscribes.forEach((f) => f());
18
- });
19
- app.subscriptionmanager.subscribe({
20
- context: "vessels.self",
21
- subscribe: [{ path, policy: "instant" }],
22
- }, unsubscribes, (error) => app.error(error), (delta) => {
51
+ const unsubscribes = new Map();
52
+ let scanTimer;
53
+ const isSelf = (context) => context === "vessels.self" || context === selfContext;
54
+ const wanted = (context) => !exclude.has(context) && (include.size === 0 || include.has(context));
55
+ const readVessel = (context, path) => isSelf(context) ? app.getSelfPath(path) : app.getPath(`${context}.${path}`);
56
+ // The last fix seen per vessel on the stream, so a sounding is paired with
57
+ // the position it was recorded at — and a replayed (back-dated) sounding uses
58
+ // the replayed position, not the vessel's current one.
59
+ const fixes = new Map();
60
+ const headings = new Map();
61
+ // Course over ground, the heading fallback for a vessel with no compass.
62
+ const courses = new Map();
63
+ const collect = (context, delta) => {
23
64
  delta.updates.forEach((update) => {
24
- const timestamp = Temporal.Instant.from(update.timestamp ?? Temporal.Now.instant());
25
- const position = app.getSelfPath("navigation.position");
26
- let heading = app.getSelfPath("navigation.headingTrue");
27
- if ("values" in update) {
28
- update.values.forEach(({ value }) => {
29
- if (!value)
30
- return;
31
- if (!position)
32
- return app.debug("No position data, ignoring depth data");
33
- if (isStale(position, timestamp, ttl))
34
- return app.debug("Stale position data, ignoring depth data");
35
- if (heading && isStale(heading, timestamp, ttl)) {
36
- app.debug("Stale heading data, discarding");
37
- heading = undefined;
38
- }
39
- const { lon, lat } = offset
40
- ? correctToTransducer({
41
- lon: position.value.longitude,
42
- lat: position.value.latitude,
43
- }, heading?.value, offset)
44
- : { lon: position.value.longitude, lat: position.value.latitude };
45
- stream.push({
46
- longitude: lon,
47
- latitude: lat,
48
- depth: value,
49
- timestamp,
50
- heading: heading?.value,
51
- });
52
- });
65
+ if (!("values" in update))
66
+ return;
67
+ const ts = update.timestamp ?? Temporal.Now.instant().toString();
68
+ const timestamp = Temporal.Instant.from(ts);
69
+ // Absorb the update's own position/heading first, so a depth sounding in
70
+ // the same update pairs with the fix it was recorded at.
71
+ update.values.forEach(({ path, value }) => {
72
+ if (path === POSITION) {
73
+ const p = positionOf(value);
74
+ if (p)
75
+ fixes.set(context, { ...p, timestamp: ts });
76
+ }
77
+ else if (path === HEADING && typeof value === "number") {
78
+ headings.set(context, { value, timestamp: ts });
79
+ }
80
+ else if (path === COURSE && typeof value === "number") {
81
+ courses.set(context, { value, timestamp: ts });
82
+ }
83
+ });
84
+ // Pair soundings with the last fix from the stream, or the data model
85
+ // before the stream has carried one (a vessel discovered but silent so
86
+ // far).
87
+ let fix = fixes.get(context);
88
+ if (!fix) {
89
+ const node = readVessel(context, POSITION);
90
+ const p = positionOf(node);
91
+ if (p)
92
+ fix = { ...p, timestamp: node?.timestamp ?? ts };
53
93
  }
94
+ // A vessel with no compass has only course over ground; use it as the
95
+ // heading fallback (it is the direction of travel, so meaningless while
96
+ // stopped, but there is nothing else).
97
+ let heading = headings.get(context) ?? courses.get(context);
98
+ if (!heading) {
99
+ heading =
100
+ readVessel(context, HEADING) ??
101
+ readVessel(context, COURSE);
102
+ }
103
+ update.values.forEach(({ path, value }) => {
104
+ // The subscription carries position and heading too; only a depth value
105
+ // is a sounding.
106
+ if (path !== depthPath)
107
+ return;
108
+ if (!value)
109
+ return;
110
+ if (!fix)
111
+ return app.debug(`No position for ${context}, ignoring depth data`);
112
+ if (isStale(fix, timestamp, ttl))
113
+ return app.debug(`Stale position for ${context}, ignoring depth data`);
114
+ if (heading && isStale(heading, timestamp, ttl)) {
115
+ app.debug("Stale heading data, discarding");
116
+ heading = undefined;
117
+ }
118
+ const vesselOffset = isSelf(context) ? offset : undefined;
119
+ const { lon, lat } = vesselOffset
120
+ ? correctToTransducer({ lon: fix.longitude, lat: fix.latitude }, heading?.value, vesselOffset)
121
+ : { lon: fix.longitude, lat: fix.latitude };
122
+ stream.push({
123
+ longitude: lon,
124
+ latitude: lat,
125
+ depth: value,
126
+ timestamp,
127
+ heading: heading?.value,
128
+ context,
129
+ });
130
+ });
54
131
  });
132
+ };
133
+ const subscribe = (context) => {
134
+ const unsubs = [];
135
+ app.subscriptionmanager.subscribe({
136
+ context: context,
137
+ subscribe: [
138
+ { path: depthPath, policy: "instant" },
139
+ { path: POSITION, policy: "instant" },
140
+ { path: HEADING, policy: "instant" },
141
+ { path: COURSE, policy: "instant" },
142
+ ],
143
+ }, unsubs, (error) => app.error(error), (delta) => collect(context, delta));
144
+ unsubscribes.set(context, () => unsubs.forEach((f) => f()));
145
+ app.debug(`Recording ${depthPath} from ${context}`);
146
+ };
147
+ const scan = () => {
148
+ const vessels = app.getPath("vessels");
149
+ if (!vessels)
150
+ return;
151
+ for (const [key, node] of Object.entries(vessels)) {
152
+ const context = key === "self" ? "vessels.self" : `vessels.${key}`;
153
+ if (unsubscribes.has(context) || !wanted(context))
154
+ continue;
155
+ // A depth on the chosen datum is what makes a vessel worth recording;
156
+ // AIS targets have a position but never a depth.
157
+ if (!node?.environment?.depth?.[config.path])
158
+ continue;
159
+ if (!node?.navigation?.position)
160
+ continue;
161
+ subscribe(context);
162
+ }
163
+ // A vessel is never unsubscribed for going quiet. It may be offline and
164
+ // replay its buffered deltas when it reconnects, and the data model entry
165
+ // is pruned while it is away — dropping the subscription then is exactly
166
+ // what loses the replay.
167
+ };
168
+ scan();
169
+ scanTimer = setInterval(scan, scanIntervalMs);
170
+ scanTimer.unref?.();
171
+ stream.on("close", () => {
172
+ if (scanTimer)
173
+ clearInterval(scanTimer);
174
+ unsubscribes.forEach((unsubscribe) => unsubscribe());
175
+ unsubscribes.clear();
55
176
  });
56
177
  return stream;
57
178
  }