@signalk/tracks-plugin 3.2.0 → 3.4.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/README.md +37 -15
- package/dist/asyncTrackStore.d.ts +5 -4
- package/dist/import.js +27 -6
- package/dist/importedTracks.d.ts +2 -4
- package/dist/index.html +4 -0
- package/dist/index.js +266 -69
- package/dist/index.js.map +1 -1
- package/dist/sqliteStore.d.ts +12 -3
- package/dist/store.d.ts +8 -3
- package/dist/trackApi.d.ts +2 -0
- package/dist/trackProvider.d.ts +62 -2
- package/dist/trackStoreProtocol.d.ts +1 -1
- package/dist/trackStoreWorker.js +40 -3
- package/dist/trackStoreWorker.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/docs/history-and-storage.md +6 -1
- package/package.json +1 -1
- package/public/import.js +27 -6
- package/public/index.html +4 -0
package/README.md
CHANGED
|
@@ -49,29 +49,51 @@ other track, filtered by vessel, time window and area, but as a feature of its o
|
|
|
49
49
|
it names a vessel the plugin also records. It is never merged with a history provider's data,
|
|
50
50
|
never removed by the retention settings, and never returned by the v1 routes, which answer
|
|
51
51
|
where vessels are now. Its own name and any other properties it was posted with come back as
|
|
52
|
-
posted.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
posted. It carries `imported`, the time it was stored, which a recorded track does not, so a
|
|
53
|
+
client drawing a vessel's recording can leave the import out of it. Imports stored by a version
|
|
54
|
+
before that time was kept are dated to the first start of a version that keeps it.
|
|
55
|
+
|
|
56
|
+
A timed track of the own vessel is the exception: it is the boat's own passage, logged before
|
|
57
|
+
the plugin was installed or by another plotter, so it is written into the own vessel's
|
|
58
|
+
recording instead of being kept as an import, and the answer is the recording's id,
|
|
59
|
+
`tracks:recorded:<context>`. By default it only fills what the recording is missing: its
|
|
60
|
+
points are used where the vessel has no position, from this plugin or a history provider, for
|
|
61
|
+
at least five minutes, and left out elsewhere, so nothing recorded is overwritten and two
|
|
62
|
+
sources are never interleaved minute by minute; while a history provider cannot be read, such an
|
|
63
|
+
import is refused with a 503 rather than drawn over what it holds. Asked to replace instead, it takes the place of
|
|
64
|
+
what was recorded over the same time: the positions this plugin stored there go to the recycle
|
|
65
|
+
bin first, one entry per segment, and a history provider's data over that time is hidden while
|
|
66
|
+
the entry is there. Restoring the entry brings both back. An untimed track of the own vessel
|
|
67
|
+
stays an import, since it cannot be placed in time.
|
|
68
|
+
|
|
69
|
+
A track with neither times (`coordTimes`) nor a vessel (`context`) is kept too. No time
|
|
70
|
+
window or vessel query matches it, but `GET /plugins/tracks/imports` lists it with every other
|
|
71
|
+
import, and `GET /signalk/v2/api/tracks/<id>` returns it.
|
|
57
72
|
|
|
58
73
|
### Importing a GPX file
|
|
59
74
|
|
|
60
75
|
The Tracks webapp imports a GPX file from the **Import** form, and a client can do the same
|
|
61
76
|
with `POST /plugins/tracks/imports`, the file as the request body with `Content-Type:
|
|
62
|
-
application/gpx+xml`. Each track in the file
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
the plugin's own GPX export does.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
application/gpx+xml`. Each track in the file becomes an imported track as above. Ticking
|
|
78
|
+
**Tracks of my own vessel** (`?self=true`) files them under the own vessel, which writes a
|
|
79
|
+
timed one into the recording; otherwise a track names a vessel only if the file says which, as
|
|
80
|
+
the plugin's own GPX export does. Ticking **Replace what my vessel recorded over the same time**
|
|
81
|
+
as well (`onOverlap=replace`; the default is `keep`) replaces the recording rather than filling
|
|
82
|
+
it.
|
|
83
|
+
|
|
84
|
+
In a timed track, points without a time are skipped, and the answer says how many
|
|
85
|
+
(`skippedPoints`), as it does for the points left out where the recording already has positions
|
|
86
|
+
(`overlappingPoints`). A track posted through the Track API's `POST /signalk/v2/api/tracks` is
|
|
87
|
+
always kept beside the recording this way, since that API has no such choice yet. The route
|
|
88
|
+
needs write access on a Signal K server that lets a plugin say so, and administrator rights on
|
|
89
|
+
an older one.
|
|
71
90
|
|
|
72
91
|
The webapp lists every imported track, however old and whether or not it has times, next to
|
|
73
92
|
the last 30 days of recorded ones, so a passage imported from years ago can be found there. It
|
|
74
|
-
can be deleted there on a Signal K server that deletes tracks by id.
|
|
93
|
+
can be deleted there on a Signal K server that deletes tracks by id.
|
|
94
|
+
|
|
95
|
+
`GET /plugins/tracks/imports` returns every imported track, and no recorded ones, each with
|
|
96
|
+
`imported`, the time it was stored.
|
|
75
97
|
|
|
76
98
|
The Tracks webapp exports an imported track as GPX from its row, as it was imported. A client
|
|
77
99
|
can download the same file from `GET /plugins/tracks/imports/<id>/track.gpx`, with the id less
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TrackStore } from './store.js';
|
|
2
2
|
import { SqliteStoreConfig } from './sqliteStore.js';
|
|
3
|
-
import { Debug, Context, ImportedTrack, ImportFilter, LatLngTuple, TimeWindow, TrackParams } from './types.js';
|
|
3
|
+
import { Debug, Context, ImportedTrack, ImportFilter, LatLngTuple, TimedPosition, TimeWindow, TrackParams } from './types.js';
|
|
4
4
|
import { TrackQuery } from './timeWindow.js';
|
|
5
5
|
interface Limits {
|
|
6
6
|
maxItems?: number;
|
|
@@ -58,7 +58,7 @@ export declare class AsyncTrackStore implements TrackStore {
|
|
|
58
58
|
initialTrack(context: Context, track: LatLngTuple[], timestamps?: number[]): void;
|
|
59
59
|
prune(maxAge: number, keep?: Context): void;
|
|
60
60
|
get(context: Context, window?: TimeWindow): Promise<LatLngTuple[]>;
|
|
61
|
-
getTimed(context: Context, window?: TimeWindow): Promise<
|
|
61
|
+
getTimed(context: Context, window?: TimeWindow): Promise<TimedPosition[]>;
|
|
62
62
|
getAllTracks(query?: TrackQuery): Promise<{
|
|
63
63
|
context: string;
|
|
64
64
|
track: LatLngTuple[];
|
|
@@ -70,6 +70,7 @@ export declare class AsyncTrackStore implements TrackStore {
|
|
|
70
70
|
id?: number;
|
|
71
71
|
known: boolean;
|
|
72
72
|
}>;
|
|
73
|
+
recordTrack(context: Context, segments: TimedPosition[][], deletedAt: number, keepEmpty: boolean): Promise<number[]>;
|
|
73
74
|
binImport(importId: string, span: {
|
|
74
75
|
from?: number;
|
|
75
76
|
to?: number;
|
|
@@ -78,8 +79,8 @@ export declare class AsyncTrackStore implements TrackStore {
|
|
|
78
79
|
purgeFromBin(id: number): Promise<boolean>;
|
|
79
80
|
binEntries(): Promise<import('./types.js').BinEntry[]>;
|
|
80
81
|
deletedSpans(): Promise<import('./types.js').RecordedSpan[]>;
|
|
81
|
-
getImport(id: string): Promise<
|
|
82
|
-
findImports(filter?: ImportFilter): Promise<
|
|
82
|
+
getImport(id: string): Promise<import('./types.js').StoredImport | undefined>;
|
|
83
|
+
findImports(filter?: ImportFilter): Promise<import('./types.js').StoredImport[]>;
|
|
83
84
|
close(): Promise<void>;
|
|
84
85
|
}
|
|
85
86
|
export {};
|
package/dist/import.js
CHANGED
|
@@ -10,12 +10,26 @@ import { refusal } from './bin.js'
|
|
|
10
10
|
/** Relative, because the server mounts this page under /@signalk/tracks-plugin. */
|
|
11
11
|
const IMPORT_URL = '../../plugins/tracks/imports'
|
|
12
12
|
|
|
13
|
+
const points = (count) => `${count} point${count === 1 ? '' : 's'}`
|
|
14
|
+
|
|
13
15
|
/** What an import came to, in words. */
|
|
14
|
-
export function imported({ ids, skippedPoints }) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
:
|
|
16
|
+
export function imported({ ids, skippedPoints, overlappingPoints = 0 }) {
|
|
17
|
+
return [
|
|
18
|
+
`Imported ${ids.length} track${ids.length === 1 ? '' : 's'}.`,
|
|
19
|
+
...(skippedPoints > 0 ? [`${points(skippedPoints)} without a time left out.`] : []),
|
|
20
|
+
...(overlappingPoints > 0 ? [`${points(overlappingPoints)} left out where your vessel already has a track.`] : []),
|
|
21
|
+
].join(' ')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The upload's query. Only the own vessel's tracks go into a recording, so
|
|
26
|
+
* replacing is offered for those alone; the route keeps by default.
|
|
27
|
+
*/
|
|
28
|
+
export function importQuery({ self, replace }) {
|
|
29
|
+
if (!self) {
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
return replace ? '?self=true&onOverlap=replace' : '?self=true'
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
async function messageOf(response) {
|
|
@@ -31,6 +45,13 @@ export function start() {
|
|
|
31
45
|
const form = document.getElementById('import-form')
|
|
32
46
|
const status = document.getElementById('import-status')
|
|
33
47
|
const button = form.querySelector('button[type="submit"]')
|
|
48
|
+
const { self, replace } = form.elements
|
|
49
|
+
// Only the own vessel has a recording to replace.
|
|
50
|
+
const allowReplace = () => {
|
|
51
|
+
replace.disabled = !self.checked
|
|
52
|
+
}
|
|
53
|
+
allowReplace()
|
|
54
|
+
self.addEventListener('change', allowReplace)
|
|
34
55
|
|
|
35
56
|
function show(message, state) {
|
|
36
57
|
// A failed upload can be tried again; a successful one reloads the page.
|
|
@@ -58,7 +79,7 @@ export function start() {
|
|
|
58
79
|
show('Choose a GPX file first.', 'error')
|
|
59
80
|
return
|
|
60
81
|
}
|
|
61
|
-
const query =
|
|
82
|
+
const query = importQuery({ self: self.checked, replace: replace.checked })
|
|
62
83
|
button.disabled = true
|
|
63
84
|
show('Importing…')
|
|
64
85
|
let response
|
package/dist/importedTracks.d.ts
CHANGED
|
@@ -14,9 +14,8 @@ export declare const isImportedId: (id: string) => boolean;
|
|
|
14
14
|
* thinning and window code assume time order, and a segment boundary is a gap
|
|
15
15
|
* in recording, so segments are never merged or reordered.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* so it could only ever be found again by an id the client would have to keep.
|
|
17
|
+
* A track with neither times nor a vessel answers no time window and no
|
|
18
|
+
* vessel query; the plugin's import list and its id are what find it again.
|
|
20
19
|
*/
|
|
21
20
|
export declare function toImportedTrack(track: TrackImport, id?: string): ImportedTrack;
|
|
22
21
|
/**
|
|
@@ -27,7 +26,6 @@ export declare function toImportedTrack(track: TrackImport, id?: string): Import
|
|
|
27
26
|
* becomes an untimed import; a file with some keeps only its timed points,
|
|
28
27
|
* because an import is timed throughout or not at all, and its times are what
|
|
29
28
|
* let a query find it. `context` names the vessel in place of the file's own.
|
|
30
|
-
* Refused, like any import, when it ends up with neither times nor a vessel.
|
|
31
29
|
*/
|
|
32
30
|
export declare function fromGpxTrack(gpx: GpxTrack, context?: string | undefined, id?: string): {
|
|
33
31
|
track: ImportedTrack;
|
package/dist/index.html
CHANGED
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
<form id="import-form">
|
|
32
32
|
<label>GPX file <input type="file" name="file" accept=".gpx,application/gpx+xml" required /></label>
|
|
33
33
|
<label><input type="checkbox" name="self" /> Tracks of my own vessel</label>
|
|
34
|
+
<label
|
|
35
|
+
><input type="checkbox" name="replace" /> Replace what my vessel recorded over the same time (it goes to the
|
|
36
|
+
recycle bin)</label
|
|
37
|
+
>
|
|
34
38
|
<button type="submit">Import</button>
|
|
35
39
|
</form>
|
|
36
40
|
<p id="import-status" role="status" hidden></p>
|