@signalk/tracks-plugin 3.0.0-beta.1 → 3.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -28
- package/dist/e2e.test-utils.d.ts +13 -3
- package/dist/gpx.d.ts +59 -0
- package/dist/harness.test-utils.d.ts +76 -1
- package/dist/index.d.ts +28 -29
- package/dist/index.html +30 -0
- package/dist/index.js +1046 -403
- package/dist/index.js.map +1 -1
- package/dist/simplify.d.ts +46 -0
- package/dist/simplify.test.d.ts +1 -0
- package/dist/sqliteStore.d.ts +9 -5
- package/dist/stateGate.d.ts +0 -4
- package/dist/store.d.ts +28 -9
- package/dist/style.css +49 -0
- package/dist/trackApi.d.ts +9 -11
- package/dist/trackProvider.d.ts +24 -0
- package/dist/tracks.d.ts +7 -4
- package/dist/tracks.js +192 -0
- package/dist/utils.d.ts +69 -0
- package/dist/webapp.e2e.test.d.ts +1 -0
- package/dist/webapp.test.d.ts +1 -0
- package/package.json +6 -2
- package/public/index.html +30 -0
- package/public/style.css +49 -0
- package/public/tracks.js +192 -0
- /package/dist/{bootstrap.test.d.ts → gpx.test.d.ts} +0 -0
- /package/dist/{source.test.d.ts → historyRow.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -2,21 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Signal K server plugin that accumulates vessel positions into tracks and implements the track API.
|
|
4
4
|
|
|
5
|
-
Positions are
|
|
6
|
-
|
|
5
|
+
Positions are recorded to a SQLite file in the plugin's data directory, at a configured time
|
|
6
|
+
resolution, so tracks survive a restart with no other plugin required. The plugin is enabled on
|
|
7
|
+
install and needs no configuration to start recording.
|
|
7
8
|
|
|
8
|
-
| Setting
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
9
|
+
| Setting | Behaviour |
|
|
10
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
11
|
+
| Track resolution | Minimum spacing between recorded positions. |
|
|
12
|
+
| Days of the own vessel's track to keep | Trims only the own vessel. 0 keeps everything; it is never dropped for idling. |
|
|
13
|
+
| Days to keep another vessel after its last fix | Drops a vessel that has gone quiet. 0 keeps every vessel indefinitely. |
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
build up from live data. `sqlite` also takes a retention setting, in days, for how long to keep
|
|
16
|
-
positions.
|
|
17
|
-
|
|
18
|
-
This replaces the earlier `bootstrapFromHistory` checkbox. Existing configurations keep working:
|
|
19
|
-
the setting is still read, with `false` mapping to `memory` and anything else to `history`.
|
|
15
|
+
The plugin's configuration page shows the current defaults, and is the authority on them.
|
|
20
16
|
|
|
21
17
|
The package also exports a client side `TrackAccumulator` class that manages the track for a single
|
|
22
18
|
vessel, exposing the result as `Observable<LatLngTuple[]>`.
|
|
@@ -28,9 +24,13 @@ With a history provider installed — [signalk-questdb](https://www.npmjs.com/pa
|
|
|
28
24
|
both it and the plugin's own store: the provider is the finer record for as long as its retention
|
|
29
25
|
reaches, and the store is what remains of everything older or of any period the provider missed.
|
|
30
26
|
|
|
31
|
-
Nothing needs configuring for this, and the plugin works
|
|
32
|
-
|
|
33
|
-
practice.
|
|
27
|
+
Nothing needs configuring for this, and the plugin works fully with no provider installed. Its own
|
|
28
|
+
store is a SQLite file in the plugin's data directory, so tracks survive a restart on their own.
|
|
29
|
+
See [docs/history-and-storage.md](docs/history-and-storage.md) for what that means in practice.
|
|
30
|
+
|
|
31
|
+
The own vessel is kept indefinitely by default. Other vessels are kept for 30 days after their last
|
|
32
|
+
fix — a harbour puts hundreds of AIS targets past a receiver in a day, and keeping every one of them
|
|
33
|
+
forever is rarely what anybody wants. Both are settings.
|
|
34
34
|
|
|
35
35
|
## Glitch filtering
|
|
36
36
|
|
|
@@ -45,17 +45,18 @@ vessel, and glitches usually miss it by orders of magnitude rather than by a lit
|
|
|
45
45
|
speed rather than distance, so a track resuming after a long gap — a passage with the plugin
|
|
46
46
|
stopped, or an AIS target reappearing — is not filtered. Set it to 0 to record everything.
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
the
|
|
48
|
+
Positions a history provider supplies are not filtered here — they are that provider's record, and
|
|
49
|
+
it is the one that decides what to keep.
|
|
50
50
|
|
|
51
51
|
## Pausing while not under way
|
|
52
52
|
|
|
53
53
|
A boat on a mooring for the winter emits a position every second and travels nowhere. Setting
|
|
54
54
|
**Pause recording while navigation.state is one of** stops those months costing any rows.
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
[signalk-autostate](https://github.com/meri-imperiumi/signalk-autostate) or by hand
|
|
58
|
-
that reports no state is always recorded, so
|
|
56
|
+
On by default for `moored`, `not-under-way` and `aground`. It needs `navigation.state` to be set —
|
|
57
|
+
by [signalk-autostate](https://github.com/meri-imperiumi/signalk-autostate) or by hand — and a
|
|
58
|
+
vessel that reports no state is always recorded, so an install with no state source behaves as
|
|
59
|
+
though this were off. Clear the list to record regardless of state.
|
|
59
60
|
|
|
60
61
|
Two limits are deliberate. **`anchored` is offered but rarely wanted**: an anchor alarm watches
|
|
61
62
|
exactly the track a vessel makes while swinging on its rode, so pausing there would break it.
|
|
@@ -132,7 +133,8 @@ so thinning never shortens the track._
|
|
|
132
133
|
],
|
|
133
134
|
"times": [["2026-08-14T09:00:00.000Z", "2026-08-14T09:01:00.000Z"]],
|
|
134
135
|
"context": "vessels.urn:mrn:imo:mmsi:123456789",
|
|
135
|
-
"isSelf": true
|
|
136
|
+
"isSelf": true,
|
|
137
|
+
"name": "Own Ship"
|
|
136
138
|
}
|
|
137
139
|
```
|
|
138
140
|
|
|
@@ -145,16 +147,21 @@ _`context` is the fully qualified context the track belongs to, and `isSelf` say
|
|
|
145
147
|
the own vessel. Asking for `self` resolves the alias, so the response tells you which vessel
|
|
146
148
|
`self` actually is._
|
|
147
149
|
|
|
150
|
+
_`name` is a display label, the way a chart plotter shows one: `Own Ship`, or `AIS <shipname>`
|
|
151
|
+
falling back to `AIS <mmsi>` and finally the raw context. It is for putting in a list — the v2
|
|
152
|
+
Track API's `contextName` carries the undecorated vessel name instead, and is absent for a
|
|
153
|
+
vessel that has not sent one._
|
|
154
|
+
|
|
148
155
|
---
|
|
149
156
|
|
|
150
157
|
**Retrieve tracks for all vessels:**
|
|
151
158
|
|
|
152
159
|
`/signalk/v1/api/tracks`
|
|
153
160
|
|
|
154
|
-
|
|
161
|
+
_Every vessel the plugin holds a track for. Add `?radius=` to narrow it to vessels near your own._
|
|
155
162
|
|
|
156
163
|
_Each entry carries `isSelf`, so the own vessel can be told from an AIS target without
|
|
157
|
-
string-matching the context against the server's self
|
|
164
|
+
string-matching the context against the server's self-identity, and `name` for display._
|
|
158
165
|
|
|
159
166
|
_`?times` works here too, adding a `times` array to every vessel's entry. Note that asking for
|
|
160
167
|
times also segments each track on the gap threshold, so `coordinates` and `times` line up;
|
|
@@ -166,7 +173,7 @@ without `times` each vessel keeps its single unsegmented line._
|
|
|
166
173
|
|
|
167
174
|
`/signalk/v1/api/tracks?radius=50000`
|
|
168
175
|
|
|
169
|
-
|
|
176
|
+
_Distance from the own vessel's current position, matched against each track's **last** position — "which vessels are near me now". The v2 Track API asks a different question and matches any position in the window._
|
|
170
177
|
|
|
171
178
|
---
|
|
172
179
|
|
|
@@ -203,10 +210,10 @@ npm run format # prettier --write
|
|
|
203
210
|
Signal K server against it and feeds positions as deltas — so it covers plugin loading, route
|
|
204
211
|
mounting and the delta path, none of which the unit suite can. It needs a built server checkout;
|
|
205
212
|
set `SIGNALK_SERVER_DIR` if yours is not at `~/dev/xxx_signalk-server`. A second tier installs a
|
|
206
|
-
real history provider (signalk-questdb) into that server and exercises the
|
|
207
|
-
|
|
213
|
+
real history provider (signalk-questdb) into that server and exercises the reconciliation through
|
|
214
|
+
it; it skips itself if no QuestDB is reachable at `QUESTDB_URL`. Neither tier runs in CI.
|
|
208
215
|
|
|
209
216
|
The package is ESM only and targets Node >= 22.5.0, the release that added `node:sqlite`. ESM alone
|
|
210
217
|
would only need 20.19, the first release in which the Signal K server's `require()`-based plugin
|
|
211
|
-
loader can load an ES module, but
|
|
218
|
+
loader can load an ES module, but recording to SQLite raises the floor. The server itself
|
|
212
219
|
requires Node >= 22, so this rules out nothing that could have run the plugin anyway.
|
package/dist/e2e.test-utils.d.ts
CHANGED
|
@@ -30,11 +30,21 @@ export interface E2EOptions {
|
|
|
30
30
|
* Other plugins to install and enable alongside this one, as
|
|
31
31
|
* `{ 'npm-package-name': pluginConfiguration }`.
|
|
32
32
|
*
|
|
33
|
-
* Used to stand up a real history provider so the
|
|
34
|
-
* through `getHistoryApi()` — the interface this plugin actually
|
|
35
|
-
* rather than against a provider's private storage.
|
|
33
|
+
* Used to stand up a real history provider so the reconciliation can be
|
|
34
|
+
* exercised through `getHistoryApi()` — the interface this plugin actually
|
|
35
|
+
* depends on — rather than against a provider's private storage.
|
|
36
36
|
*/
|
|
37
37
|
plugins?: Record<string, Record<string, unknown>>;
|
|
38
|
+
/**
|
|
39
|
+
* Plugin id to pin as the server's default history provider.
|
|
40
|
+
*
|
|
41
|
+
* The server takes the *first* provider that registers as the default, so a
|
|
42
|
+
* second one installed in the server checkout — KIP registers as a history
|
|
43
|
+
* provider, and a dev checkout usually has it — silently wins and answers
|
|
44
|
+
* every unqualified history query with nothing. Naming the one the test
|
|
45
|
+
* installed makes the run independent of whatever else is lying around.
|
|
46
|
+
*/
|
|
47
|
+
historyProvider?: string;
|
|
38
48
|
/** Port to boot on, when a test needs its own server. */
|
|
39
49
|
port?: number;
|
|
40
50
|
}
|
package/dist/gpx.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { TimedPosition } from './types.js';
|
|
2
|
+
/** Identity of the vessel a track belongs to, as far as it is known. */
|
|
3
|
+
export interface GpxTrackIdentity {
|
|
4
|
+
/** Display label, e.g. `Own Ship` or `AIS Ariadne`. Becomes `<name>`. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Fully qualified Signal K context, when there is one. */
|
|
7
|
+
context?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface GpxTrack extends GpxTrackIdentity {
|
|
10
|
+
/** Segments as `segment()` produces them: a recording gap starts a new one. */
|
|
11
|
+
segments: TimedPosition[][];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Serialise tracks as a GPX 1.1 document.
|
|
15
|
+
*
|
|
16
|
+
* Several tracks go in one file, each as its own `<trk>`. GPX allows it, and
|
|
17
|
+
* it is what makes "select three trips, export" produce one file rather than
|
|
18
|
+
* three — TimeZero writes one file per vessel, which is a choice rather than a
|
|
19
|
+
* constraint of the format.
|
|
20
|
+
*
|
|
21
|
+
* The Signal K context travels in `<extensions>`. TimeZero puts an opaque
|
|
22
|
+
* GUID there and drops the MMSI entirely, so its exports cannot round-trip an
|
|
23
|
+
* identity; carrying the context means ours can, while staying readable by
|
|
24
|
+
* anything else, since `<extensions>` is ignorable by specification.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toGpx(tracks: GpxTrack[], now?: Date): string;
|
|
27
|
+
/**
|
|
28
|
+
* Parse a GPX document into tracks.
|
|
29
|
+
*
|
|
30
|
+
* Structure follows GPX's own hierarchy: the root is `<gpx>`, tracks are its
|
|
31
|
+
* children, segments are a track's, points are a segment's. Anything else —
|
|
32
|
+
* a `<trkseg>` inside an `<extensions>` payload, a `<trk>` below the root — is
|
|
33
|
+
* not track data, and a namespace check alone cannot tell the difference,
|
|
34
|
+
* since a payload declaring no namespace inherits GPX's own.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately forgiving about structure and strict about values: elements it
|
|
37
|
+
* does not recognise are ignored rather than rejected, but a coordinate that is
|
|
38
|
+
* not an `xsd:decimal` in range, or a time that is not an `xsd:dateTime`, is
|
|
39
|
+
* refused. One NaN reaching the store stretches every bounding box that track
|
|
40
|
+
* appears in.
|
|
41
|
+
*
|
|
42
|
+
* **Points are sorted by time within each segment.** TimeZero paginates its
|
|
43
|
+
* export in blocks of 150 and the block seams overlap: the last point of a
|
|
44
|
+
* block can be a few milliseconds newer than the first point of the next. Out
|
|
45
|
+
* of 5,550 points in a real export, 16 pairs are out of order, every one on a
|
|
46
|
+
* 150-point boundary. Anything downstream that assumes ordering — time-window
|
|
47
|
+
* queries, the history reconciliation — would quietly misbehave on such a file.
|
|
48
|
+
*
|
|
49
|
+
* Sorting stops at the segment boundary, deliberately: a segment is a stretch
|
|
50
|
+
* of continuous recording, and reordering points across a gap would merge two
|
|
51
|
+
* passages into one line. So `segments.flat()` is not globally sorted for a
|
|
52
|
+
* file whose segments interleave in time, and a caller that needs that must
|
|
53
|
+
* sort for itself.
|
|
54
|
+
*
|
|
55
|
+
* A document the parser rejects yields no tracks rather than throwing: this
|
|
56
|
+
* reads files the plugin did not write, and an import that fails should say so
|
|
57
|
+
* by returning nothing rather than by taking the caller down.
|
|
58
|
+
*/
|
|
59
|
+
export declare function fromGpx(xml: string): GpxTrack[];
|
|
@@ -26,7 +26,7 @@ export interface TestHarness {
|
|
|
26
26
|
statuses: string[];
|
|
27
27
|
/**
|
|
28
28
|
* Install a track with explicit timestamps, bypassing the throttled bus. This
|
|
29
|
-
*
|
|
29
|
+
* bypasses the throttle, which is what makes a back-dated track testable.
|
|
30
30
|
*/
|
|
31
31
|
seedTrack: (context: string, positions: LatLngTuple[], timestamps: number[]) => void;
|
|
32
32
|
/** Self position as reported by `getSelfPath`, used for radius filtering. */
|
|
@@ -34,6 +34,20 @@ export interface TestHarness {
|
|
|
34
34
|
/** The own vessel's navigation.state, as reported by `getSelfPath`. */
|
|
35
35
|
setSelfState: (state: string | undefined) => void;
|
|
36
36
|
stop: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Start the same plugin instance again, with the config it was built with.
|
|
39
|
+
*
|
|
40
|
+
* A fresh harness would not do: what a restart has to show is the state the
|
|
41
|
+
* instance drops on `stop()`, which a new instance never had.
|
|
42
|
+
*/
|
|
43
|
+
restart: () => void;
|
|
44
|
+
/**
|
|
45
|
+
* How many positions the plugin's bus subscription has accepted.
|
|
46
|
+
*
|
|
47
|
+
* Observable after stop(), when the store itself is closed and unreadable —
|
|
48
|
+
* which is how a test can still tell that the subscription was torn down.
|
|
49
|
+
*/
|
|
50
|
+
emitted: () => number;
|
|
37
51
|
errors: unknown[][];
|
|
38
52
|
/**
|
|
39
53
|
* The v2 Track API provider the plugin registered, or undefined when the
|
|
@@ -50,10 +64,71 @@ export interface HarnessOptions {
|
|
|
50
64
|
selfPosition?: LatLngTuple;
|
|
51
65
|
/** Initial navigation.state for the own vessel. */
|
|
52
66
|
selfState?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Data-model entries other vessels' paths resolve to, keyed by full path
|
|
69
|
+
* (e.g. `vessels.urn:...:244813000.name`). Omit to stand in for an older
|
|
70
|
+
* server with no `getPath`, where track naming falls back to the MMSI.
|
|
71
|
+
*/
|
|
72
|
+
paths?: Record<string, unknown>;
|
|
53
73
|
/**
|
|
54
74
|
* Stand in for a server without the v2 Track API, to check the plugin still
|
|
55
75
|
* starts when `registerTrackApiProvider` is absent.
|
|
56
76
|
*/
|
|
57
77
|
withoutTrackApi?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* A history provider for the plugin to reconcile against.
|
|
80
|
+
*
|
|
81
|
+
* `contexts` is what `getContexts` lists, and `rows` what `getValues`
|
|
82
|
+
* returns — separately, because the difference between them is the whole
|
|
83
|
+
* point of the existence probe: a provider can know a vessel and still have
|
|
84
|
+
* no rows inside the asked window.
|
|
85
|
+
*
|
|
86
|
+
* `contextsSince` back-dates those contexts: a real provider filters its
|
|
87
|
+
* context list by the asked range (questdb builds a `WHERE` from it), so a
|
|
88
|
+
* stub that answers regardless of range cannot show what the probe misses.
|
|
89
|
+
* `getContextsRejects` and `getContextsHangs` cover the other two ways a
|
|
90
|
+
* provider can fail to answer.
|
|
91
|
+
*/
|
|
92
|
+
history?: {
|
|
93
|
+
/** Read on every `getContexts` call, so a getter can count the probes. */
|
|
94
|
+
contexts?: string[];
|
|
95
|
+
contextsSince?: number;
|
|
96
|
+
rows?: unknown[];
|
|
97
|
+
withoutGetContexts?: boolean;
|
|
98
|
+
getContextsRejects?: boolean;
|
|
99
|
+
getContextsHangs?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Hold every `getContexts` call open until the returned release is run.
|
|
102
|
+
*
|
|
103
|
+
* A provider that answers immediately cannot distinguish sharing an
|
|
104
|
+
* in-flight query from reusing a cached result: the first caller would
|
|
105
|
+
* have filled the cache before the rest arrived. Deferring the answer
|
|
106
|
+
* parks every caller inside the probe at once, so only sharing can
|
|
107
|
+
* collapse them.
|
|
108
|
+
*/
|
|
109
|
+
deferContexts?: {
|
|
110
|
+
release: () => void;
|
|
111
|
+
wait: Promise<void>;
|
|
112
|
+
};
|
|
113
|
+
/** Fail the windowed read, leaving the provider unable to answer at all. */
|
|
114
|
+
getValuesRejects?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Reject `getHistoryApi` itself, as the server does when no provider is
|
|
117
|
+
* registered — the default install, and not an outage.
|
|
118
|
+
*/
|
|
119
|
+
noProvider?: boolean;
|
|
120
|
+
/** Never resolve `getHistoryApi`: a registered provider that wedges. */
|
|
121
|
+
providerHangs?: boolean;
|
|
122
|
+
};
|
|
58
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* A promise held open until a test releases it.
|
|
126
|
+
*
|
|
127
|
+
* Lets a test observe that every request has reached the provider before any
|
|
128
|
+
* answer exists, which a timed wait can only assume.
|
|
129
|
+
*/
|
|
130
|
+
export declare function deferred(): {
|
|
131
|
+
release: () => void;
|
|
132
|
+
wait: Promise<void>;
|
|
133
|
+
};
|
|
59
134
|
export declare function createHarness(options?: HarnessOptions): TestHarness;
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,19 @@ interface HistoryValuesQuery {
|
|
|
29
29
|
}
|
|
30
30
|
interface HistoryApi {
|
|
31
31
|
getValues(query: HistoryValuesQuery): Promise<HistoryValuesResponse>;
|
|
32
|
+
/**
|
|
33
|
+
* Contexts the provider holds data for, within a time range.
|
|
34
|
+
*
|
|
35
|
+
* Optional here though the upstream interface requires it: a provider
|
|
36
|
+
* registered against an older server-api may not implement it, and a missing
|
|
37
|
+
* method must degrade rather than throw.
|
|
38
|
+
*/
|
|
39
|
+
getContexts?(query: HistoryContextsQuery): Promise<unknown[]>;
|
|
40
|
+
}
|
|
41
|
+
/** `ContextsRequest` upstream: a time range, with Instants rather than strings. */
|
|
42
|
+
interface HistoryContextsQuery {
|
|
43
|
+
from: Temporal.Instant;
|
|
44
|
+
to: Temporal.Instant;
|
|
32
45
|
}
|
|
33
46
|
interface HistoryValuesResponse {
|
|
34
47
|
context: string;
|
|
@@ -49,6 +62,14 @@ interface App {
|
|
|
49
62
|
};
|
|
50
63
|
};
|
|
51
64
|
getSelfPath: (path: string) => unknown;
|
|
65
|
+
/**
|
|
66
|
+
* Read any path in the full data model, including other vessels'.
|
|
67
|
+
*
|
|
68
|
+
* `getSelfPath` only reaches the own vessel, so naming an AIS track needs
|
|
69
|
+
* this. Optional: absent on older servers, where tracks fall back to the
|
|
70
|
+
* MMSI parsed out of the context.
|
|
71
|
+
*/
|
|
72
|
+
getPath?: (path: string) => unknown;
|
|
52
73
|
selfContext: string;
|
|
53
74
|
/** Shown against the plugin in the server's dashboard. Absent on older servers. */
|
|
54
75
|
setPluginStatus?: (msg: string) => void;
|
|
@@ -74,6 +95,8 @@ interface App {
|
|
|
74
95
|
}
|
|
75
96
|
interface Plugin {
|
|
76
97
|
start: (c: TracksPluginConfig) => void;
|
|
98
|
+
/** Enable on install, without waiting for a visit to the plugin config page. */
|
|
99
|
+
enabledByDefault: boolean;
|
|
77
100
|
stop: () => void;
|
|
78
101
|
signalKApiRoutes: (r: Router) => Router;
|
|
79
102
|
id: string;
|
|
@@ -89,28 +112,12 @@ interface Plugin {
|
|
|
89
112
|
*/
|
|
90
113
|
getTracks: () => TrackStore | undefined;
|
|
91
114
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Where tracks come from when the server starts.
|
|
94
|
-
*
|
|
95
|
-
* - `memory`: start empty, accumulate from the position bus only.
|
|
96
|
-
* - `history`: refill from a History provider at startup, then accumulate.
|
|
97
|
-
* - `sqlite`: persist to disk, so a restart resumes from what was recorded.
|
|
98
|
-
*
|
|
99
|
-
* One setting rather than a boolean per source: they are alternatives, and as
|
|
100
|
-
* independent booleans a user could enable two and have the same positions
|
|
101
|
-
* loaded twice.
|
|
102
|
-
*/
|
|
103
|
-
export type TrackSource = 'memory' | 'history' | 'sqlite';
|
|
104
115
|
interface TracksPluginConfig {
|
|
105
116
|
resolution?: number;
|
|
106
|
-
|
|
107
|
-
maxAge?: number;
|
|
108
|
-
maxRadius?: number;
|
|
109
|
-
source?: TrackSource;
|
|
110
|
-
/** @deprecated superseded by `source`; still honoured for existing configs. */
|
|
111
|
-
bootstrapFromHistory?: boolean;
|
|
112
|
-
/** Days of history to keep when `source` is `sqlite`. 0 keeps everything. */
|
|
117
|
+
/** Days of the own vessel's track to keep. 0 keeps everything. */
|
|
113
118
|
retentionDays?: number;
|
|
119
|
+
/** Days another vessel is kept after its last fix. 0 keeps every vessel. */
|
|
120
|
+
aisRetentionDays?: number;
|
|
114
121
|
/** Minutes without a fix that start a new track segment. 0 disables. */
|
|
115
122
|
segmentGapMinutes?: number;
|
|
116
123
|
/** Speed above which a position is treated as a glitch. 0 disables. */
|
|
@@ -118,17 +125,9 @@ interface TracksPluginConfig {
|
|
|
118
125
|
/** navigation.state values that pause recording of the own vessel. */
|
|
119
126
|
pauseWhenState?: string[];
|
|
120
127
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Resolve the effective source, honouring the setting this replaced.
|
|
123
|
-
*
|
|
124
|
-
* `bootstrapFromHistory` shipped as a boolean defaulting to true, so an
|
|
125
|
-
* installed plugin has it saved in its config either way. Reading `source`
|
|
126
|
-
* first and only then falling back keeps those installs working: an explicit
|
|
127
|
-
* `false` still means "do not touch the History API", and everything else
|
|
128
|
-
* lands on the old default of `history`.
|
|
129
|
-
*/
|
|
130
|
-
export declare const resolveSource: (config: TracksPluginConfig) => TrackSource;
|
|
131
128
|
export default function ThePlugin(app: App): Plugin;
|
|
129
|
+
export { fromGpx, toGpx } from './gpx.js';
|
|
130
|
+
export type { GpxTrack, GpxTrackIdentity } from './gpx.js';
|
|
132
131
|
export { Tracks, TrackAccumulator } from './tracks.js';
|
|
133
132
|
export type { TracksConfig } from './tracks.js';
|
|
134
133
|
export type { TrackStore } from './store.js';
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Tracks</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header>
|
|
11
|
+
<h1>Tracks</h1>
|
|
12
|
+
<p id="status" role="status">Loading…</p>
|
|
13
|
+
</header>
|
|
14
|
+
<main>
|
|
15
|
+
<table id="tracks" hidden>
|
|
16
|
+
<thead>
|
|
17
|
+
<tr>
|
|
18
|
+
<th scope="col">Vessel</th>
|
|
19
|
+
<th scope="col">From</th>
|
|
20
|
+
<th scope="col">To</th>
|
|
21
|
+
<th scope="col" class="num">Points</th>
|
|
22
|
+
<th scope="col">Export</th>
|
|
23
|
+
</tr>
|
|
24
|
+
</thead>
|
|
25
|
+
<tbody></tbody>
|
|
26
|
+
</table>
|
|
27
|
+
</main>
|
|
28
|
+
<script type="module" src="./tracks.js"></script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|