signalk-livedepth-plugin 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +53 -2
- package/dist/collector.d.ts +18 -0
- package/dist/collector.d.ts.map +1 -0
- package/dist/collector.js +23 -0
- package/dist/collector.js.map +1 -0
- package/dist/config.d.ts +83 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -0
- package/dist/fetch.d.ts +5 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +13 -0
- package/dist/fetch.js.map +1 -0
- package/dist/history.d.ts +24 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +76 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +137 -0
- package/dist/plugin.js.map +1 -0
- package/dist/streams/index.d.ts +3 -0
- package/dist/streams/index.d.ts.map +1 -0
- package/dist/streams/index.js +3 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/live.d.ts +11 -0
- package/dist/streams/live.d.ts.map +1 -0
- package/dist/streams/live.js +63 -0
- package/dist/streams/live.js.map +1 -0
- package/dist/streams/transforms.d.ts +14 -0
- package/dist/streams/transforms.d.ts.map +1 -0
- package/dist/streams/transforms.js +21 -0
- package/dist/streams/transforms.js.map +1 -0
- package/dist/tiles/accumulator.d.ts +34 -0
- package/dist/tiles/accumulator.d.ts.map +1 -0
- package/dist/tiles/accumulator.js +60 -0
- package/dist/tiles/accumulator.js.map +1 -0
- package/dist/tiles/api.d.ts +36 -0
- package/dist/tiles/api.d.ts.map +1 -0
- package/dist/tiles/api.js +75 -0
- package/dist/tiles/api.js.map +1 -0
- package/dist/tiles/grid.d.ts +59 -0
- package/dist/tiles/grid.d.ts.map +1 -0
- package/dist/tiles/grid.js +121 -0
- package/dist/tiles/grid.js.map +1 -0
- package/dist/tiles/index.d.ts +6 -0
- package/dist/tiles/index.d.ts.map +1 -0
- package/dist/tiles/index.js +6 -0
- package/dist/tiles/index.js.map +1 -0
- package/dist/tiles/seed.d.ts +47 -0
- package/dist/tiles/seed.d.ts.map +1 -0
- package/dist/tiles/seed.js +148 -0
- package/dist/tiles/seed.js.map +1 -0
- package/dist/tiles/store.d.ts +14 -0
- package/dist/tiles/store.d.ts.map +1 -0
- package/dist/tiles/store.js +117 -0
- package/dist/tiles/store.js.map +1 -0
- package/dist/tiles/writer.d.ts +15 -0
- package/dist/tiles/writer.d.ts.map +1 -0
- package/dist/tiles/writer.js +49 -0
- package/dist/tiles/writer.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/icon.svg +12 -0
- package/package.json +54 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nick Roberts
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
-
#
|
|
1
|
+
# signalk-livedepth-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Signal K server plugin that builds a **gridded bathymetry tile store** from the
|
|
4
|
+
vessel's own position and depth, and serves it to a chart client's LiveDepth
|
|
5
|
+
layer. It is the server side of Freeboard-SK's LiveDepth layer.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Subscribes to the vessel's own `environment.depth.*` and `navigation.position`,
|
|
10
|
+
and folds each sounding into a Web-Mercator XYZ tile store: 256-cell tiles,
|
|
11
|
+
base level `z20` (≈0.15 m per cell), min-pooled down to `z4` (≈2.4 km per
|
|
12
|
+
cell). A tile is stored sparsely — only the cells a sounding fell in — as a
|
|
13
|
+
5-byte record (`cx, cy, depthCm, count`) keeping the shallowest depth, so
|
|
14
|
+
zooming out never deepens a shoal. The tile algorithm itself lives in
|
|
15
|
+
[`signalk-livedepth-tiles`](https://www.npmjs.com/package/signalk-livedepth-tiles),
|
|
16
|
+
shared with the chart client so both build identical tiles.
|
|
17
|
+
- Serves the tiles over HTTP so a chart client can draw a depth surface for the
|
|
18
|
+
area without replaying the raw track or interpolating at query time.
|
|
19
|
+
- Can seed the store from the server's History API at the finest granularity it
|
|
20
|
+
offers — a one-time, resumable rebuild for a store that already has a track.
|
|
21
|
+
|
|
22
|
+
## Routes
|
|
23
|
+
|
|
24
|
+
Mounted by the server at `/plugins/signalk-livedepth-plugin`:
|
|
25
|
+
|
|
26
|
+
| Route | Returns |
|
|
27
|
+
| -------------------------------- | -------------------------------------------------------------------------------------- |
|
|
28
|
+
| `GET /bathymetry/meta` | grid spec (base/min zoom, tile size, record size) and the depth path/offsets to mirror |
|
|
29
|
+
| `GET /bathymetry/tiles/:z/:x/:y` | the sparse tile blob (`application/octet-stream`), or `404` |
|
|
30
|
+
| `GET /bathymetry/seed` | admin only: status of the one-time history rebuild |
|
|
31
|
+
| `POST /bathymetry/seed` | admin only: start the one-time history rebuild (`202`) |
|
|
32
|
+
|
|
33
|
+
A client seeds its view from the tiles, then keeps its own copy current from the
|
|
34
|
+
same Signal K stream.
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
- **Path** — the `environment.depth.*` path to record.
|
|
39
|
+
- **Depth Sounder / GPS Receiver** — the sensor offsets, and the source to pin
|
|
40
|
+
each path to (so soundings from a second GPS don't smear the track).
|
|
41
|
+
- **Rebuild from History** — a one-shot rebuild from the server's History API;
|
|
42
|
+
it fires once when saved and clears itself when it finishes.
|
|
43
|
+
|
|
44
|
+
## Development
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install
|
|
48
|
+
npm run build # tsc -b -> dist/
|
|
49
|
+
npm test # vitest
|
|
50
|
+
npm run format
|
|
51
|
+
npm run lint
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Node `>=22.5` (the store uses the built-in `node:sqlite`).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Writable } from "stream";
|
|
2
|
+
import { ServerAPI } from "@signalk/server-api";
|
|
3
|
+
import { TransducerOffset } from "signalk-livedepth-tiles";
|
|
4
|
+
import { Config } from "./config.js";
|
|
5
|
+
export interface CollectorOptions {
|
|
6
|
+
app: ServerAPI;
|
|
7
|
+
config: Config;
|
|
8
|
+
/** The tile writer fed the live soundings. */
|
|
9
|
+
writer: Writable;
|
|
10
|
+
/** Shift each sounding from the reported position to the transducer. */
|
|
11
|
+
offset?: TransducerOffset;
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Pair depth with position once and feed the soundings to the tile writer.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createCollector({ app, config, writer, offset, signal, }: CollectorOptions): import("stream-transform").Transformer;
|
|
18
|
+
//# sourceMappingURL=collector.d.ts.map
|
|
@@ -0,0 +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,yBAAyB,CAAC;AAC3D,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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { transform } from "stream-transform";
|
|
2
|
+
import { createLiveStream, toPrecision } from "./streams/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Pair depth with position once and feed the soundings to the tile writer.
|
|
5
|
+
*/
|
|
6
|
+
export function createCollector({ app, config, writer, offset, signal, }) {
|
|
7
|
+
const live = createLiveStream(app, config, offset);
|
|
8
|
+
const precise = live.pipe(transform(toPrecision()));
|
|
9
|
+
precise.pipe(writer);
|
|
10
|
+
const destroy = () => {
|
|
11
|
+
live.destroy();
|
|
12
|
+
precise.destroy();
|
|
13
|
+
writer.destroy();
|
|
14
|
+
};
|
|
15
|
+
if (signal.aborted) {
|
|
16
|
+
destroy();
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
signal.addEventListener("abort", destroy, { once: true });
|
|
20
|
+
}
|
|
21
|
+
return precise;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=collector.js.map
|
|
@@ -0,0 +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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ServerAPI } from "@signalk/server-api";
|
|
2
|
+
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. */
|
|
5
|
+
path: (typeof DepthPaths)[number];
|
|
6
|
+
/** The depth transducer's position on the hull: `y` from the bow, `x` from
|
|
7
|
+
* the centreline (+starboard). Used only to place a sounding at the
|
|
8
|
+
* transducer rather than at the position the server reports. */
|
|
9
|
+
sounder: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
};
|
|
13
|
+
/** Shift soundings from the vessel reference point the server reports to the
|
|
14
|
+
* transducer. `off` leaves them where the server put them. */
|
|
15
|
+
transducerCorrection: "off" | "replace";
|
|
16
|
+
seed?: {
|
|
17
|
+
run?: boolean;
|
|
18
|
+
resolution?: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare const DepthPaths: readonly ["belowSurface", "belowTransducer", "belowKeel"];
|
|
22
|
+
export declare function schema(app: ServerAPI): {
|
|
23
|
+
type: string;
|
|
24
|
+
description: string;
|
|
25
|
+
properties: {
|
|
26
|
+
path: {
|
|
27
|
+
type: string;
|
|
28
|
+
title: string;
|
|
29
|
+
description: string;
|
|
30
|
+
enum: readonly ["belowSurface", "belowTransducer", "belowKeel"];
|
|
31
|
+
enumNames: string[];
|
|
32
|
+
default: "belowKeel" | "belowSurface" | "belowTransducer" | undefined;
|
|
33
|
+
};
|
|
34
|
+
sounder: {
|
|
35
|
+
type: string;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
required: string[];
|
|
39
|
+
properties: {
|
|
40
|
+
y: {
|
|
41
|
+
type: string;
|
|
42
|
+
title: string;
|
|
43
|
+
minimum: number;
|
|
44
|
+
default: unknown;
|
|
45
|
+
};
|
|
46
|
+
x: {
|
|
47
|
+
type: string;
|
|
48
|
+
title: string;
|
|
49
|
+
description: string;
|
|
50
|
+
default: unknown;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
transducerCorrection: {
|
|
55
|
+
type: string;
|
|
56
|
+
title: string;
|
|
57
|
+
description: string;
|
|
58
|
+
enum: string[];
|
|
59
|
+
enumNames: string[];
|
|
60
|
+
default: string;
|
|
61
|
+
};
|
|
62
|
+
seed: {
|
|
63
|
+
type: string;
|
|
64
|
+
title: string;
|
|
65
|
+
description: string;
|
|
66
|
+
properties: {
|
|
67
|
+
run: {
|
|
68
|
+
type: string;
|
|
69
|
+
default: boolean;
|
|
70
|
+
title: string;
|
|
71
|
+
description: string;
|
|
72
|
+
};
|
|
73
|
+
resolution: {
|
|
74
|
+
type: string;
|
|
75
|
+
default: number;
|
|
76
|
+
title: string;
|
|
77
|
+
description: string;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +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;IAKjC,IAAI;IACJ,WAAW;IAEX,UAAU;QACR,IAAI;YACF,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,IAAI;YACJ,SAAS;YAKT,OAAO;;QAET,OAAO;YACL,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,QAAQ;YACR,UAAU;gBACR,CAAC;oBACC,IAAI;oBACJ,KAAK;oBACL,OAAO;oBACP,OAAO;;gBAET,CAAC;oBACC,IAAI;oBACJ,KAAK;oBACL,WAAW;oBACX,OAAO;;;;QAIb,oBAAoB;YAClB,IAAI;YACJ,KAAK;YACL,WAAW;YAEX,IAAI;YACJ,SAAS;YACT,OAAO;;QAET,IAAI;YACF,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"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const DepthPaths = [
|
|
2
|
+
"belowSurface",
|
|
3
|
+
"belowTransducer",
|
|
4
|
+
"belowKeel",
|
|
5
|
+
];
|
|
6
|
+
export function schema(app) {
|
|
7
|
+
const defaultPath = DepthPaths.find((path) => app.getSelfPath(`environment.depth.${path}.value`));
|
|
8
|
+
return {
|
|
9
|
+
type: "object",
|
|
10
|
+
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.",
|
|
11
|
+
properties: {
|
|
12
|
+
path: {
|
|
13
|
+
type: "string",
|
|
14
|
+
title: "Depth",
|
|
15
|
+
description: "Which depth datum to record. Signal K publishes each relative to its own reference, so no draft or transducer offset is needed here.",
|
|
16
|
+
enum: DepthPaths,
|
|
17
|
+
enumNames: [
|
|
18
|
+
"Depth below surface",
|
|
19
|
+
"Depth below transducer",
|
|
20
|
+
"Depth below keel",
|
|
21
|
+
],
|
|
22
|
+
default: defaultPath,
|
|
23
|
+
},
|
|
24
|
+
sounder: {
|
|
25
|
+
type: "object",
|
|
26
|
+
title: "Depth Transducer",
|
|
27
|
+
description: "Where the transducer sits on the hull, so a sounding is placed at it rather than at the position the server reports.",
|
|
28
|
+
required: ["x", "y"],
|
|
29
|
+
properties: {
|
|
30
|
+
y: {
|
|
31
|
+
type: "number",
|
|
32
|
+
title: "Distance of the transducer from the bow (meters)",
|
|
33
|
+
minimum: 0,
|
|
34
|
+
default: app.getSelfPath("sensors.depth.fromBow.value"),
|
|
35
|
+
},
|
|
36
|
+
x: {
|
|
37
|
+
type: "number",
|
|
38
|
+
title: "Distance of the transducer from the center (meters)",
|
|
39
|
+
description: "+ve to starboard, -ve to port",
|
|
40
|
+
default: app.getSelfPath("sensors.depth.fromCenter.value"),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
transducerCorrection: {
|
|
45
|
+
type: "string",
|
|
46
|
+
title: "Transducer position correction",
|
|
47
|
+
description: "Shift each sounding from the vessel reference point the server reports to the transducer. This is only meaningful when the server's own GNSS correction is on (Server → Settings → Vessel → GNSS correction): otherwise the position is the GNSS antenna, not the vessel reference point, and the offset is applied from the wrong place.",
|
|
48
|
+
enum: ["off", "replace"],
|
|
49
|
+
enumNames: ["Off", "Replace with the transducer's position"],
|
|
50
|
+
default: "off",
|
|
51
|
+
},
|
|
52
|
+
seed: {
|
|
53
|
+
type: "object",
|
|
54
|
+
title: "Rebuild from History",
|
|
55
|
+
description: "Rebuild the gridded bathymetry tiles from the server's History API at the finest granularity it offers. Slow — it reads the whole record — so run it once; the live stream keeps the tiles current afterwards, and an interrupted run resumes where it stopped.",
|
|
56
|
+
properties: {
|
|
57
|
+
run: {
|
|
58
|
+
type: "boolean",
|
|
59
|
+
default: false,
|
|
60
|
+
title: "Rebuild the bathymetry tiles from history now",
|
|
61
|
+
description: "Starts the rebuild when the configuration is saved. Resets itself when it finishes; leave off to skip.",
|
|
62
|
+
},
|
|
63
|
+
resolution: {
|
|
64
|
+
type: "number",
|
|
65
|
+
default: 0.1,
|
|
66
|
+
title: "History resolution (seconds)",
|
|
67
|
+
description: "Granularity to read. The provider floors this at its native cadence; 0.1 keeps nearly every fix.",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +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,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,OAAO,EAAE,WAAW;aACrB;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,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC,6BAA6B,CAAC;qBACxD;oBACD,CAAC,EAAE;wBACD,IAAI,EAAE,QAAQ;wBACd,KAAK,EAAE,qDAAqD;wBAC5D,WAAW,EAAE,+BAA+B;wBAC5C,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC,gCAAgC,CAAC;qBAC3D;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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
/** Earliest instant the history seed reaches back to. signalk-to-influxdb was
|
|
3
|
+
* first released on 2017-06-28 — the earliest recorded bathymetry. */
|
|
4
|
+
export declare const BATHY_EPOCH: Temporal.Instant;
|
|
5
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;sEACsE;AACtE,eAAO,MAAM,WAAW,kBAEvB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
/** Earliest instant the history seed reaches back to. signalk-to-influxdb was
|
|
3
|
+
* first released on 2017-06-28 — the earliest recorded bathymetry. */
|
|
4
|
+
export const BATHY_EPOCH = Temporal.Instant.from(process.env.BATHY_EPOCH ?? "2017-06-28T00:00:00Z");
|
|
5
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;sEACsE;AACtE,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAC9C,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,sBAAsB,CAClD,CAAC"}
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,OAAO,WAAW,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,GAAE,WAAgB,qBAQjE"}
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch wrapper that adds a User-Agent, used by the History API HTTP fallback.
|
|
3
|
+
*/
|
|
4
|
+
export default function (url, init = {}) {
|
|
5
|
+
return fetch(url, {
|
|
6
|
+
...init,
|
|
7
|
+
headers: {
|
|
8
|
+
...init.headers,
|
|
9
|
+
"User-Agent": "signalk-livedepth-plugin",
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,OAAO,WAAW,GAAiB,EAAE,IAAI,GAAgB,EAAE;IAChE,OAAO,KAAK,CAAC,GAAG,EAAE;QAChB,GAAG,IAAI;QACP,OAAO,EAAE;YACP,GAAG,IAAI,CAAC,OAAO;YACf,YAAY,EAAE,0BAA0B;SACzC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Path, ServerAPI } from "@signalk/server-api";
|
|
2
|
+
import { HistoryApi, ValuesResponse } from "@signalk/server-api/history";
|
|
3
|
+
export type HistoryAPIOptions = {
|
|
4
|
+
app: ServerAPI;
|
|
5
|
+
host?: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The History Provider API is new and may not be available. This uses a provider if available,
|
|
9
|
+
* and otherwise falls back to direct HTTP access if available.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getHistoryAPI({ app, port, host, }: HistoryAPIOptions): Promise<HistoryApi | undefined>;
|
|
12
|
+
/**
|
|
13
|
+
* Build a lookup from SignalK path to its column index in a history `DataRow`.
|
|
14
|
+
*
|
|
15
|
+
* `values[i]` describes data column `i + 1` (column 0 is always the timestamp),
|
|
16
|
+
* so the returned index can be used directly against a `DataRow`. The response's
|
|
17
|
+
* `values` metadata is authoritative — v2+ providers may reorder or drop columns
|
|
18
|
+
* — but providers that omit it fall back to the requested path order. When a
|
|
19
|
+
* path appears more than once the first matching column wins.
|
|
20
|
+
*/
|
|
21
|
+
export declare function columnsByPath(values: ValuesResponse["values"] | undefined, requested: readonly {
|
|
22
|
+
path: Path;
|
|
23
|
+
}[]): Map<string, number>;
|
|
24
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/history.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import { BATHY_EPOCH } from "./constants.js";
|
|
3
|
+
import fetch from "./fetch.js";
|
|
4
|
+
/**
|
|
5
|
+
* The History Provider API is new and may not be available. This uses a provider if available,
|
|
6
|
+
* and otherwise falls back to direct HTTP access if available.
|
|
7
|
+
*/
|
|
8
|
+
export async function getHistoryAPI({ app,
|
|
9
|
+
// @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();
|
|
15
|
+
app.debug("Using built-in history provider");
|
|
16
|
+
return api;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// History provider API not available or no provider configured.
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
await getContexts({
|
|
23
|
+
from: BATHY_EPOCH,
|
|
24
|
+
to: Temporal.Now.instant(),
|
|
25
|
+
});
|
|
26
|
+
app.debug("Using History API available at %s", host);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
app.debug("History API is not available");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
return { getValues, getContexts, getPaths };
|
|
33
|
+
async function get(path, params = {}) {
|
|
34
|
+
const url = new URL(path, new URL("/signalk/v2/api/history/", host));
|
|
35
|
+
url.search = new URLSearchParams(params).toString();
|
|
36
|
+
app.debug("Fetching %s", url.toString());
|
|
37
|
+
const res = await fetch(url);
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
return res.json();
|
|
42
|
+
}
|
|
43
|
+
function getValues({ pathSpecs, ...query }) {
|
|
44
|
+
return get("values", {
|
|
45
|
+
...query,
|
|
46
|
+
paths: pathSpecs
|
|
47
|
+
.map(({ path, aggregate }) => [path, aggregate].join(":"))
|
|
48
|
+
.join(","),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function getContexts(query) {
|
|
52
|
+
return get("contexts", query);
|
|
53
|
+
}
|
|
54
|
+
function getPaths(query) {
|
|
55
|
+
return get("paths", query);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build a lookup from SignalK path to its column index in a history `DataRow`.
|
|
60
|
+
*
|
|
61
|
+
* `values[i]` describes data column `i + 1` (column 0 is always the timestamp),
|
|
62
|
+
* so the returned index can be used directly against a `DataRow`. The response's
|
|
63
|
+
* `values` metadata is authoritative — v2+ providers may reorder or drop columns
|
|
64
|
+
* — but providers that omit it fall back to the requested path order. When a
|
|
65
|
+
* path appears more than once the first matching column wins.
|
|
66
|
+
*/
|
|
67
|
+
export function columnsByPath(values, requested) {
|
|
68
|
+
const source = values && values.length ? values : requested;
|
|
69
|
+
const columns = new Map();
|
|
70
|
+
source.forEach((column, i) => {
|
|
71
|
+
if (!columns.has(column.path))
|
|
72
|
+
columns.set(column.path, i + 1);
|
|
73
|
+
});
|
|
74
|
+
return columns;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import createPlugin from "./plugin.js";
|
|
2
|
+
export default createPlugin;
|
|
3
|
+
export * from "./constants.js";
|
|
4
|
+
export * from "./types.js";
|
|
5
|
+
export * from "./config.js";
|
|
6
|
+
export * from "./streams/index.js";
|
|
7
|
+
export * from "./tiles/index.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAC;eAExB,YAAY;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import createPlugin from "./plugin.js";
|
|
2
|
+
export default createPlugin;
|
|
3
|
+
export * from "./constants.js";
|
|
4
|
+
export * from "./types.js";
|
|
5
|
+
export * from "./config.js";
|
|
6
|
+
export * from "./streams/index.js";
|
|
7
|
+
export * from "./tiles/index.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAC;AAEvC,eAAe,YAAY,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +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;AAuEtE,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAwG3D"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { schema } from "./config.js";
|
|
3
|
+
import { createTileDB, createTileStore } from "./tiles/store.js";
|
|
4
|
+
import { createTileWriter } from "./tiles/writer.js";
|
|
5
|
+
import { seedFromHistory } from "./tiles/seed.js";
|
|
6
|
+
import { registerBathymetryRoutes, } from "./tiles/api.js";
|
|
7
|
+
import { createCollector } from "./collector.js";
|
|
8
|
+
/** The vessel's length overall, for locating the reference point the server
|
|
9
|
+
* reports (the CCRP is midships). Handles `getSelfPath` returning either the
|
|
10
|
+
* value or its node. */
|
|
11
|
+
function readLengthOverall(app) {
|
|
12
|
+
const node = app.getSelfPath("design.length.overall");
|
|
13
|
+
const value = typeof node === "number" ? node : node?.value;
|
|
14
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
15
|
+
? value
|
|
16
|
+
: undefined;
|
|
17
|
+
}
|
|
18
|
+
/** The server's GNSS antenna correction mode; `off` unless configured. */
|
|
19
|
+
function readGnssCorrection(app) {
|
|
20
|
+
// @ts-expect-error: app.config is not a public API
|
|
21
|
+
return app.config?.settings?.gnssCorrection ?? "off";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The body-frame offset from the reference point the server reports to the
|
|
25
|
+
* transducer, or undefined when correction is off (or can't be located).
|
|
26
|
+
*
|
|
27
|
+
* The server corrects `navigation.position` to the CCRP (midships on the
|
|
28
|
+
* centreline) only when its own `gnssCorrection` is on. When it is off the
|
|
29
|
+
* position is the GNSS antenna, whose body position we do not know — so the
|
|
30
|
+
* offset is meaningless. We still apply what the user configured, but flag it.
|
|
31
|
+
*/
|
|
32
|
+
function resolveTransducerOffset(app, config) {
|
|
33
|
+
if (config.transducerCorrection !== "replace") {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if (readGnssCorrection(app) === "off") {
|
|
37
|
+
app.setPluginError("Transducer correction is on but the server's GNSS correction is off, so " +
|
|
38
|
+
"the position is the GNSS antenna, not the vessel reference point the " +
|
|
39
|
+
"offset is measured from. Enable GNSS correction (Server → Settings → " +
|
|
40
|
+
"Vessel) or turn this off.");
|
|
41
|
+
}
|
|
42
|
+
const length = readLengthOverall(app);
|
|
43
|
+
if (length === undefined) {
|
|
44
|
+
app.setPluginError("Transducer correction needs the vessel's length overall " +
|
|
45
|
+
"(design.length.overall) to locate the reference point; soundings are " +
|
|
46
|
+
"left where the server reports them.");
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
forward: (config.sounder?.y ?? 0) - length / 2,
|
|
51
|
+
starboard: config.sounder?.x ?? 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export default function createPlugin(app) {
|
|
55
|
+
let abortController = undefined;
|
|
56
|
+
// The gridded store served to chart clients. Built in start(), read by the
|
|
57
|
+
// routes registered in registerWithRouter().
|
|
58
|
+
let tileStore;
|
|
59
|
+
let config;
|
|
60
|
+
let transducer;
|
|
61
|
+
let seedStatus = { running: false };
|
|
62
|
+
const runSeed = async (resolution) => {
|
|
63
|
+
if (seedStatus.running)
|
|
64
|
+
return;
|
|
65
|
+
const store = tileStore;
|
|
66
|
+
const cfg = config;
|
|
67
|
+
if (!store || !cfg)
|
|
68
|
+
return;
|
|
69
|
+
seedStatus = { running: true };
|
|
70
|
+
app.setPluginStatus("Rebuilding bathymetry tiles from history…");
|
|
71
|
+
try {
|
|
72
|
+
const progress = await seedFromHistory({
|
|
73
|
+
app,
|
|
74
|
+
store,
|
|
75
|
+
config: cfg,
|
|
76
|
+
resolution,
|
|
77
|
+
offset: transducer,
|
|
78
|
+
signal: abortController?.signal,
|
|
79
|
+
onProgress: (p) => {
|
|
80
|
+
seedStatus = { running: !p.done, progress: p };
|
|
81
|
+
app.setPluginStatus(`Rebuilding bathymetry tiles: ${p.points} points over ${p.chunks} windows`);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
seedStatus = { running: false, progress };
|
|
85
|
+
app.setPluginStatus(`Bathymetry rebuild complete: ${progress.points} points`);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
seedStatus = { running: false, error: String(err) };
|
|
89
|
+
app.setPluginError(`Bathymetry rebuild failed: ${err}`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const seed = { run: runSeed, status: () => seedStatus };
|
|
93
|
+
return {
|
|
94
|
+
id: "signalk-livedepth-plugin",
|
|
95
|
+
name: "LiveDepth",
|
|
96
|
+
description: "Serve a gridded bathymetry tile store built from the vessel's own position and depth, for a chart plotter's LiveDepth layer.",
|
|
97
|
+
async start(startConfig, restart) {
|
|
98
|
+
config = startConfig;
|
|
99
|
+
app.debug("Starting signalk-livedepth-plugin");
|
|
100
|
+
abortController = new AbortController();
|
|
101
|
+
tileStore = createTileStore(createTileDB(join(app.getDataDirPath(), `bathymetry-tiles.sqlite`)));
|
|
102
|
+
transducer = resolveTransducerOffset(app, config);
|
|
103
|
+
createCollector({
|
|
104
|
+
app,
|
|
105
|
+
config,
|
|
106
|
+
writer: createTileWriter(tileStore),
|
|
107
|
+
offset: transducer,
|
|
108
|
+
signal: abortController.signal,
|
|
109
|
+
});
|
|
110
|
+
app.setPluginStatus("Collecting depth into the bathymetry tile store");
|
|
111
|
+
if (startConfig.seed?.run) {
|
|
112
|
+
// One-time rebuild. Clear the trigger when it finishes (or fails) so it
|
|
113
|
+
// doesn't fire again on the next start; an interrupted run resumes from
|
|
114
|
+
// the stored cursor instead of repeating.
|
|
115
|
+
void runSeed(startConfig.seed.resolution).then(() => restart({
|
|
116
|
+
...startConfig,
|
|
117
|
+
seed: { ...startConfig.seed, run: false },
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
stop() {
|
|
122
|
+
abortController?.abort("Stopping signalk-livedepth-plugin");
|
|
123
|
+
tileStore?.close();
|
|
124
|
+
tileStore = undefined;
|
|
125
|
+
},
|
|
126
|
+
schema() {
|
|
127
|
+
return schema(app);
|
|
128
|
+
},
|
|
129
|
+
registerWithRouter(router) {
|
|
130
|
+
registerBathymetryRoutes(router, () => tileStore, () => ({
|
|
131
|
+
depthPath: config ? `environment.depth.${config.path}` : undefined,
|
|
132
|
+
transducer,
|
|
133
|
+
}), seed);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|