signalk-livedepth-plugin 0.2.1 → 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 +113 -0
- package/README.md +67 -41
- package/dist/collector.d.ts +1 -1
- package/dist/collector.d.ts.map +1 -1
- package/dist/config.d.ts +52 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -4
- package/dist/config.js.map +1 -1
- package/dist/plotterext/manifest.d.ts +24 -0
- package/dist/plotterext/manifest.d.ts.map +1 -0
- package/dist/plotterext/manifest.js +79 -0
- package/dist/plotterext/manifest.js.map +1 -0
- package/dist/plotterext/provider.d.ts +7 -0
- package/dist/plotterext/provider.d.ts.map +1 -0
- package/dist/plotterext/provider.js +61 -0
- package/dist/plotterext/provider.js.map +1 -0
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +40 -8
- package/dist/plugin.js.map +1 -1
- package/dist/streams/live.d.ts +16 -4
- package/dist/streams/live.d.ts.map +1 -1
- package/dist/streams/live.js +164 -43
- package/dist/streams/live.js.map +1 -1
- package/dist/tiles/api.d.ts +21 -11
- package/dist/tiles/api.d.ts.map +1 -1
- package/dist/tiles/api.js +36 -27
- package/dist/tiles/api.js.map +1 -1
- package/dist/tiles/core/accumulator.d.ts +37 -0
- package/dist/tiles/core/accumulator.d.ts.map +1 -0
- package/dist/tiles/core/accumulator.js +75 -0
- package/dist/tiles/core/accumulator.js.map +1 -0
- package/dist/tiles/core/correction.d.ts +32 -0
- package/dist/tiles/core/correction.d.ts.map +1 -0
- package/dist/tiles/core/correction.js +43 -0
- package/dist/tiles/core/correction.js.map +1 -0
- package/dist/tiles/core/grid.d.ts +64 -0
- package/dist/tiles/core/grid.d.ts.map +1 -0
- package/dist/tiles/core/grid.js +127 -0
- package/dist/tiles/core/grid.js.map +1 -0
- package/dist/tiles/core/index.d.ts +7 -0
- package/dist/tiles/core/index.d.ts.map +1 -0
- package/dist/tiles/core/index.js +7 -0
- package/dist/tiles/core/index.js.map +1 -0
- package/dist/tiles/core/store.d.ts +37 -0
- package/dist/tiles/core/store.d.ts.map +1 -0
- package/dist/tiles/core/store.js +86 -0
- package/dist/tiles/core/store.js.map +1 -0
- package/dist/tiles/core/tile-source.d.ts +50 -0
- package/dist/tiles/core/tile-source.d.ts.map +1 -0
- package/dist/tiles/core/tile-source.js +86 -0
- package/dist/tiles/core/tile-source.js.map +1 -0
- package/dist/tiles/core/types.d.ts +11 -0
- package/dist/tiles/core/types.d.ts.map +1 -0
- package/dist/tiles/core/types.js +2 -0
- package/dist/tiles/core/types.js.map +1 -0
- package/dist/tiles/index.d.ts +1 -1
- package/dist/tiles/index.d.ts.map +1 -1
- package/dist/tiles/index.js +1 -1
- package/dist/tiles/index.js.map +1 -1
- package/dist/tiles/seed.d.ts +1 -1
- package/dist/tiles/seed.d.ts.map +1 -1
- package/dist/tiles/seed.js +1 -1
- package/dist/tiles/seed.js.map +1 -1
- package/dist/tiles/store.d.ts +1 -1
- package/dist/tiles/store.d.ts.map +1 -1
- package/dist/tiles/store.js +1 -1
- package/dist/tiles/store.js.map +1 -1
- package/dist/tiles/writer.d.ts +1 -1
- package/dist/tiles/writer.d.ts.map +1 -1
- package/dist/tiles/writer.js +1 -1
- package/dist/tiles/writer.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +27 -6
- package/public/config.html +14 -0
- package/public/index.html +15 -0
- package/public/js/config.js +939 -0
- package/public/js/config.js.map +7 -0
- package/public/js/render-worker.js +866 -0
- package/public/js/render-worker.js.map +7 -0
- package/public/js/renderer.js +1159 -0
- package/public/js/renderer.js.map +7 -0
- package/public/renderer.html +14 -0
- package/public/styles.css +110 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { ASSET_BASE, PLUGIN_ID, buildManifest } from "./manifest.js";
|
|
5
|
+
let registered = false;
|
|
6
|
+
let mounted = false;
|
|
7
|
+
/** Offer the plotter extension: register the read-only `plotterExtensions`
|
|
8
|
+
* resource and mount the extension's pages. Safe to call on every start — the
|
|
9
|
+
* provider is registered and the assets mounted once; `isRunning` reflects the
|
|
10
|
+
* plugin's current state so the extension leaves when the plugin is disabled. */
|
|
11
|
+
export function registerPlotterExtension(app, isRunning) {
|
|
12
|
+
const a = app;
|
|
13
|
+
if (!registered) {
|
|
14
|
+
if (typeof a.registerResourceProvider === "function") {
|
|
15
|
+
a.registerResourceProvider({
|
|
16
|
+
type: "plotterExtensions",
|
|
17
|
+
methods: {
|
|
18
|
+
listResources: async () => isRunning() ? { [PLUGIN_ID]: buildManifest() } : {},
|
|
19
|
+
getResource: async (id) => {
|
|
20
|
+
if (!isRunning() || id !== PLUGIN_ID) {
|
|
21
|
+
throw new Error(`No such plotterExtensions resource: ${id}`);
|
|
22
|
+
}
|
|
23
|
+
return buildManifest();
|
|
24
|
+
},
|
|
25
|
+
setResource: async () => {
|
|
26
|
+
throw new Error(`${PLUGIN_ID} is a read-only provider`);
|
|
27
|
+
},
|
|
28
|
+
deleteResource: async () => {
|
|
29
|
+
throw new Error(`${PLUGIN_ID} is a read-only provider`);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
registered = true;
|
|
34
|
+
a.debug(`${PLUGIN_ID}: plotter extension offered`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
a.debug(`${PLUGIN_ID}: server has no resource provider registry`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
mountAssets(a);
|
|
41
|
+
}
|
|
42
|
+
/** Serve the extension's pages as a top-level static route. Express is provided
|
|
43
|
+
* by the server, so requiring it adds no runtime dependency of our own. */
|
|
44
|
+
function mountAssets(app) {
|
|
45
|
+
if (mounted || typeof app.use !== "function") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let serveStatic;
|
|
49
|
+
try {
|
|
50
|
+
serveStatic = createRequire(import.meta.url)("express").static;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
app.debug(`${PLUGIN_ID}: express unavailable; cannot serve ${ASSET_BASE}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
57
|
+
app.use(ASSET_BASE, serveStatic(join(here, "..", "..", "public")));
|
|
58
|
+
mounted = true;
|
|
59
|
+
app.debug(`${PLUGIN_ID}: extension assets served at ${ASSET_BASE}`);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/plotterext/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAQrE,IAAI,UAAU,GAAG,KAAK,CAAC;AACvB,IAAI,OAAO,GAAG,KAAK,CAAC;AAEpB;;;iFAGiF;AACjF,MAAM,UAAU,wBAAwB,CACtC,GAAc,EACd,SAAwB;IAExB,MAAM,CAAC,GAAG,GAAmB,CAAC;IAE9B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,IAAI,OAAO,CAAC,CAAC,wBAAwB,KAAK,UAAU,EAAE,CAAC;YACrD,CAAC,CAAC,wBAAwB,CAAC;gBACzB,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EAAE;oBACP,aAAa,EAAE,KAAK,IAAI,EAAE,CACxB,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;oBACrD,WAAW,EAAE,KAAK,EAAE,EAAU,EAAE,EAAE;wBAChC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;4BACrC,MAAM,IAAI,KAAK,CAAC,uCAAuC,EAAE,EAAE,CAAC,CAAC;wBAC/D,CAAC;wBACD,OAAO,aAAa,EAAE,CAAC;oBACzB,CAAC;oBACD,WAAW,EAAE,KAAK,IAAI,EAAE;wBACtB,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,0BAA0B,CAAC,CAAC;oBAC1D,CAAC;oBACD,cAAc,EAAE,KAAK,IAAI,EAAE;wBACzB,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,0BAA0B,CAAC,CAAC;oBAC1D,CAAC;iBACF;aACF,CAAC,CAAC;YACH,UAAU,GAAG,IAAI,CAAC;YAClB,CAAC,CAAC,KAAK,CAAC,GAAG,SAAS,6BAA6B,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,CAAC,CAAC,KAAK,CAAC,GAAG,SAAS,4CAA4C,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,WAAW,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED;2EAC2E;AAC3E,SAAS,WAAW,CAAC,GAAiB;IACpC,IAAI,OAAO,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAC7C,OAAO;IACT,CAAC;IACD,IAAI,WAAmD,CAAC;IACxD,IAAI,CAAC;QACH,WAAW,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,MAE5C,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,uCAAuC,UAAU,EAAE,CAAC,CAAC;QAC3E,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnE,OAAO,GAAG,IAAI,CAAC;IACf,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,gCAAgC,UAAU,EAAE,CAAC,CAAC;AACtE,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
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
@@ -3,7 +3,8 @@ import { schema } from "./config.js";
|
|
|
3
3
|
import { createTileDB, createTileStore } from "./tiles/store.js";
|
|
4
4
|
import { createTileWriter } from "./tiles/writer.js";
|
|
5
5
|
import { seedFromHistory } from "./tiles/seed.js";
|
|
6
|
-
import { registerBathymetryRoutes, } from "./tiles/api.js";
|
|
6
|
+
import { registerBathymetryRoutes, registerSeedRoutes, } from "./tiles/api.js";
|
|
7
|
+
import { registerPlotterExtension } from "./plotterext/provider.js";
|
|
7
8
|
import { createCollector } from "./collector.js";
|
|
8
9
|
/** The vessel's length overall, for locating the reference point the server
|
|
9
10
|
* reports (the CCRP is midships).
|
|
@@ -23,6 +24,15 @@ export function readLengthOverall(app) {
|
|
|
23
24
|
? overall
|
|
24
25
|
: undefined;
|
|
25
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
|
+
}
|
|
26
36
|
/** The server's GNSS antenna correction mode; `off` unless configured. */
|
|
27
37
|
function readGnssCorrection(app) {
|
|
28
38
|
// @ts-expect-error: app.config is not a public API
|
|
@@ -37,7 +47,7 @@ function readGnssCorrection(app) {
|
|
|
37
47
|
* position is the GNSS antenna, whose body position we do not know — so the
|
|
38
48
|
* offset is meaningless. We still apply what the user configured, but flag it.
|
|
39
49
|
*/
|
|
40
|
-
function resolveTransducerOffset(app, config) {
|
|
50
|
+
export function resolveTransducerOffset(app, config) {
|
|
41
51
|
if (config.transducerCorrection !== "replace") {
|
|
42
52
|
return undefined;
|
|
43
53
|
}
|
|
@@ -55,7 +65,9 @@ function resolveTransducerOffset(app, config) {
|
|
|
55
65
|
return undefined;
|
|
56
66
|
}
|
|
57
67
|
return {
|
|
58
|
-
|
|
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),
|
|
59
71
|
starboard: config.sounder?.x ?? 0,
|
|
60
72
|
};
|
|
61
73
|
}
|
|
@@ -67,6 +79,8 @@ export default function createPlugin(app) {
|
|
|
67
79
|
let config;
|
|
68
80
|
let transducer;
|
|
69
81
|
let seedStatus = { running: false };
|
|
82
|
+
let running = false;
|
|
83
|
+
let routesRegistered = false;
|
|
70
84
|
const runSeed = async (resolution) => {
|
|
71
85
|
if (seedStatus.running)
|
|
72
86
|
return;
|
|
@@ -101,7 +115,7 @@ export default function createPlugin(app) {
|
|
|
101
115
|
return {
|
|
102
116
|
id: "signalk-livedepth-plugin",
|
|
103
117
|
name: "LiveDepth",
|
|
104
|
-
description: "Serve a gridded bathymetry tile store built from
|
|
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.",
|
|
105
119
|
async start(startConfig, restart) {
|
|
106
120
|
config = startConfig;
|
|
107
121
|
app.debug("Starting signalk-livedepth-plugin");
|
|
@@ -116,6 +130,24 @@ export default function createPlugin(app) {
|
|
|
116
130
|
signal: abortController.signal,
|
|
117
131
|
});
|
|
118
132
|
app.setPluginStatus("Collecting depth into the bathymetry tile store");
|
|
133
|
+
running = true;
|
|
134
|
+
// Offer the chart layer to a host chartplotter, and serve the store the
|
|
135
|
+
// layer reads from a top-level, non-admin route.
|
|
136
|
+
registerPlotterExtension(app, () => running);
|
|
137
|
+
if (!routesRegistered) {
|
|
138
|
+
registerBathymetryRoutes(app, () => tileStore, () => ({
|
|
139
|
+
depthPath: config ? `environment.depth.${config.path}` : undefined,
|
|
140
|
+
transducer,
|
|
141
|
+
vessel: {
|
|
142
|
+
loa: readLengthOverall(app),
|
|
143
|
+
beam: readVesselBeam(app),
|
|
144
|
+
},
|
|
145
|
+
debug: {
|
|
146
|
+
vesselFootprint: config?.debug?.vesselFootprint === true,
|
|
147
|
+
},
|
|
148
|
+
}));
|
|
149
|
+
routesRegistered = true;
|
|
150
|
+
}
|
|
119
151
|
if (startConfig.seed?.run) {
|
|
120
152
|
// One-time rebuild. Clear the trigger when it finishes (or fails) so it
|
|
121
153
|
// doesn't fire again on the next start; an interrupted run resumes from
|
|
@@ -127,6 +159,7 @@ export default function createPlugin(app) {
|
|
|
127
159
|
}
|
|
128
160
|
},
|
|
129
161
|
stop() {
|
|
162
|
+
running = false;
|
|
130
163
|
abortController?.abort("Stopping signalk-livedepth-plugin");
|
|
131
164
|
tileStore?.close();
|
|
132
165
|
tileStore = undefined;
|
|
@@ -135,10 +168,9 @@ export default function createPlugin(app) {
|
|
|
135
168
|
return schema(app);
|
|
136
169
|
},
|
|
137
170
|
registerWithRouter(router) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}), seed);
|
|
171
|
+
// The one-time seed trigger stays admin-only, on the plugin's own router;
|
|
172
|
+
// the store the extension reads is served from the top-level route above.
|
|
173
|
+
registerSeedRoutes(router, seed);
|
|
142
174
|
},
|
|
143
175
|
};
|
|
144
176
|
}
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,MAAM,EAAU,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,wBAAwB,
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/streams/live.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { ServerAPI } from "@signalk/server-api";
|
|
2
2
|
import { PassThrough } from "stream";
|
|
3
|
-
import { TransducerOffset } from "
|
|
3
|
+
import { TransducerOffset } from "../tiles/core/index.js";
|
|
4
4
|
import { Config } from "../config.js";
|
|
5
5
|
/**
|
|
6
|
-
* Subscribe to
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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,
|
|
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"}
|
package/dist/streams/live.js
CHANGED
|
@@ -1,57 +1,178 @@
|
|
|
1
1
|
import { PassThrough } from "stream";
|
|
2
|
-
import { correctToTransducer } from "
|
|
2
|
+
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
13
|
-
|
|
14
|
-
const
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
}
|
package/dist/streams/live.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/streams/live.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAoB,MAAM,
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/streams/live.ts"],"names":[],"mappings":"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"}
|
package/dist/tiles/api.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { PluginRouter } from "@signalk/server-api";
|
|
2
|
-
import { TileStore } from "
|
|
1
|
+
import type { PluginRouter, ServerAPI } from "@signalk/server-api";
|
|
2
|
+
import { TileStore } from "./core/index.js";
|
|
3
3
|
import { SeedProgress } from "./seed.js";
|
|
4
4
|
/** Wire format of a `/bathymetry/tiles` response. */
|
|
5
5
|
export declare const BATHYMETRY_FORMAT = "sparse-v1";
|
|
6
|
+
/** Top-level, non-admin route the store is served from. */
|
|
7
|
+
export declare const BATHYMETRY_BASE = "/plotterext/signalk-livedepth-plugin/bathymetry";
|
|
6
8
|
/** Extra fields a client needs to mirror the store (depth path, offsets, …). */
|
|
7
9
|
export type BathymetryMeta = Record<string, unknown>;
|
|
8
10
|
/** Status of the one-time history seed, as served to the admin UI. */
|
|
@@ -21,16 +23,24 @@ export interface SeedHandle {
|
|
|
21
23
|
status(): SeedStatus;
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
|
-
* Serve the gridded store to
|
|
26
|
+
* Serve the gridded store to the extension runtime — and any other chart
|
|
27
|
+
* client.
|
|
25
28
|
*
|
|
26
|
-
* Mounted
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* Mounted at a top-level, non-admin path
|
|
30
|
+
* (`/plotterext/<id>/bathymetry/…`) so an extension iframe loaded for a
|
|
31
|
+
* read-only user can reach it: the provider guide warns that the `/plugins/*`
|
|
32
|
+
* path is admin-gated. A client seeds its view from these tiles, then keeps its
|
|
33
|
+
* own copy current from the live `navigation.position` + `environment.depth.*`
|
|
34
|
+
* stream the plugin consumes.
|
|
31
35
|
*
|
|
32
|
-
* `getStore`/`getMeta` are read per request because the
|
|
33
|
-
*
|
|
36
|
+
* `getStore`/`getMeta` are read per request because the routes are registered
|
|
37
|
+
* before the plugin's `start()` has built the store.
|
|
34
38
|
*/
|
|
35
|
-
export declare function registerBathymetryRoutes(
|
|
39
|
+
export declare function registerBathymetryRoutes(app: ServerAPI, getStore: () => TileStore | undefined, getMeta?: () => BathymetryMeta): void;
|
|
40
|
+
/**
|
|
41
|
+
* The one-time history seed trigger, on the plugin's own router
|
|
42
|
+
* (`/plugins/<id>/bathymetry/seed`). Registered without `access()`, so it
|
|
43
|
+
* requires admin — it mutates the store.
|
|
44
|
+
*/
|
|
45
|
+
export declare function registerSeedRoutes(router: PluginRouter, seed: SeedHandle): void;
|
|
36
46
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/tiles/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/tiles/api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/tiles/api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAKL,SAAS,EAEV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,qDAAqD;AACrD,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C,2DAA2D;AAC3D,eAAO,MAAM,eAAe,oDAA6B,CAAC;AAE1D,gFAAgF;AAChF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAErD,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACrB,MAAM,IAAI,UAAU,CAAC;CACtB;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,MAAM,SAAS,GAAG,SAAS,EACrC,OAAO,GAAE,MAAM,cAA2B,GACzC,IAAI,CAwDN;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,UAAU,GACf,IAAI,CAUN"}
|