signalk-tides 1.3.0 → 1.5.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/.github/dependabot.yml +12 -0
- package/.github/workflows/ci.yml +39 -0
- package/README.md +15 -4
- package/dist/cache.js +25 -0
- package/dist/calculations.js +1 -4
- package/dist/index.js +71 -78
- package/dist/sources/index.js +7 -0
- package/dist/sources/neaps.js +39 -0
- package/dist/sources/noaa.js +38 -38
- package/dist/sources/stormglass.js +8 -18
- package/dist/sources/worldtides.js +8 -17
- package/dist/types.js +1 -2
- package/package.json +11 -10
- package/public/assets/index-ClNCI7kN.js +9 -0
- package/public/assets/index-D8qkCL0i.css +1 -0
- package/public/index.html +2 -2
- package/tsconfig.node.json +2 -2
- package/public/assets/index-b9CEyV9Z.css +0 -1
- package/public/assets/index-yst_zb1r.js +0 -49
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "npm"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
open-pull-requests-limit: 100
|
|
8
|
+
- package-ecosystem: "github-actions"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
12
|
+
open-pull-requests-limit: 100
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push: {}
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
lint:
|
|
8
|
+
name: Lint
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout repository
|
|
12
|
+
uses: actions/checkout@v6
|
|
13
|
+
|
|
14
|
+
- name: Setup Node.js
|
|
15
|
+
uses: actions/setup-node@v6
|
|
16
|
+
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: npm install
|
|
19
|
+
|
|
20
|
+
- name: Run linter
|
|
21
|
+
run: npm run lint
|
|
22
|
+
|
|
23
|
+
build:
|
|
24
|
+
name: Build
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
env:
|
|
27
|
+
CI: true
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout repository
|
|
30
|
+
uses: actions/checkout@v6
|
|
31
|
+
|
|
32
|
+
- name: Setup Node.js
|
|
33
|
+
uses: actions/setup-node@v6
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: npm install
|
|
37
|
+
|
|
38
|
+
- name: Run build
|
|
39
|
+
run: npm run build
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# signalk-tides
|
|
2
2
|
|
|
3
|
-
A SignalK plugin that provides tidal predictions for the vessel's position from various
|
|
3
|
+
A SignalK plugin that provides tidal predictions for the vessel's position from various [sources](#sources).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -18,6 +18,8 @@ It publishes the following [tide data](https://signalk.org/specification/1.7.0/d
|
|
|
18
18
|
* `environment.tide.timeHigh`
|
|
19
19
|
* `environment.tide.heightLow`
|
|
20
20
|
* `environment.tide.timeLow`
|
|
21
|
+
* `environment.tide.heightNow`
|
|
22
|
+
* `environment.tide.stationName`
|
|
21
23
|
|
|
22
24
|
### Tides resource
|
|
23
25
|
|
|
@@ -204,9 +206,18 @@ $ curl http://localhost:3000/signalk/v2/api/resources/tides
|
|
|
204
206
|
|
|
205
207
|
## Sources
|
|
206
208
|
|
|
207
|
-
- [
|
|
208
|
-
-
|
|
209
|
-
-
|
|
209
|
+
- [Neaps](https://github.com/neaps/neaps)
|
|
210
|
+
- Offline, open source, runs locally
|
|
211
|
+
- Coverage: Regional — United States and territories (currently)
|
|
212
|
+
- [NOAA](https://tidesandcurrents.noaa.gov/web_services_info.html)
|
|
213
|
+
- Online (NOAA web services)
|
|
214
|
+
- Regional — United States and territories
|
|
215
|
+
- [WorldTides API](https://www.worldtides.info/)
|
|
216
|
+
- Online (requires API key, subject to API quotas)
|
|
217
|
+
- Coverage: Global - station and model coverage varies by location
|
|
218
|
+
- [StormGlass.io](https://stormglass.io/)
|
|
219
|
+
- Online (requires API key)
|
|
220
|
+
- Coverage: Global - aggregated marine data; tide availability varies
|
|
210
221
|
|
|
211
222
|
## License
|
|
212
223
|
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { writeFile, readFile, mkdir } from "fs/promises";
|
|
3
|
+
export default class FileCache {
|
|
4
|
+
path;
|
|
5
|
+
constructor(path) {
|
|
6
|
+
this.path = path;
|
|
7
|
+
}
|
|
8
|
+
async get(key) {
|
|
9
|
+
try {
|
|
10
|
+
const content = await readFile(this.getKey(key), "utf-8");
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async set(key, value) {
|
|
18
|
+
// Ensure directory exists
|
|
19
|
+
await mkdir(this.path, { recursive: true });
|
|
20
|
+
return writeFile(this.getKey(key), JSON.stringify(value));
|
|
21
|
+
}
|
|
22
|
+
getKey(key) {
|
|
23
|
+
return join(this.path, `${key}.json`);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/calculations.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.approximateTideHeightAt = approximateTideHeightAt;
|
|
4
1
|
/** Given a list of tide extremes, estimate the height at a specific time. */
|
|
5
|
-
function approximateTideHeightAt(extremes, time) {
|
|
2
|
+
export function approximateTideHeightAt(extremes, time) {
|
|
6
3
|
const sorted = extremes.slice().sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
|
|
7
4
|
const prev = sorted.filter(h => new Date(h.time) <= time).at(-1);
|
|
8
5
|
const next = sorted.filter(h => new Date(h.time) >= time).at(0);
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/*
|
|
3
2
|
* Copyright 2017 Scott Bender <scott@scottbender.net> and Joachim Bakke
|
|
4
3
|
* Copyright 2025 Brandon Keepers <brandon@opensoul.org>
|
|
@@ -14,27 +13,17 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const stormglass_js_1 = __importDefault(require("./sources/stormglass.js"));
|
|
22
|
-
const worldtides_js_1 = __importDefault(require("./sources/worldtides.js"));
|
|
23
|
-
const calculations_js_1 = require("./calculations.js");
|
|
24
|
-
module.exports = function (app) {
|
|
16
|
+
import { approximateTideHeightAt } from "./calculations.js";
|
|
17
|
+
import FileCache from "./cache.js";
|
|
18
|
+
import createSources from "./sources/index.js";
|
|
19
|
+
export default function (app) {
|
|
25
20
|
// Interval to update tide data
|
|
26
21
|
const defaultPeriod = 60; // 1 hour
|
|
27
22
|
let unsubscribes = [];
|
|
28
|
-
|
|
29
|
-
const sources = [
|
|
30
|
-
(0, noaa_js_1.default)(app),
|
|
31
|
-
(0, stormglass_js_1.default)(app),
|
|
32
|
-
(0, worldtides_js_1.default)(app),
|
|
33
|
-
];
|
|
23
|
+
const sources = createSources(app);
|
|
34
24
|
const plugin = {
|
|
35
25
|
id: "tides",
|
|
36
26
|
name: "Tides",
|
|
37
|
-
// @ts-expect-error: TODO[TS]: fix Plugin type upstream
|
|
38
27
|
description: "Tidal predictions for the vessel's position from various online sources.",
|
|
39
28
|
schema: () => ({
|
|
40
29
|
title: "Tides API",
|
|
@@ -43,9 +32,9 @@ module.exports = function (app) {
|
|
|
43
32
|
source: {
|
|
44
33
|
title: "Data source",
|
|
45
34
|
type: "string",
|
|
46
|
-
|
|
35
|
+
anyOf: sources.map(({ id, title }) => ({
|
|
47
36
|
const: id,
|
|
48
|
-
title
|
|
37
|
+
title,
|
|
49
38
|
})),
|
|
50
39
|
default: sources[0].id,
|
|
51
40
|
},
|
|
@@ -58,17 +47,21 @@ module.exports = function (app) {
|
|
|
58
47
|
default: 60,
|
|
59
48
|
minimum: 1,
|
|
60
49
|
},
|
|
61
|
-
}
|
|
50
|
+
},
|
|
62
51
|
}),
|
|
52
|
+
start,
|
|
63
53
|
stop() {
|
|
64
54
|
unsubscribes.forEach((f) => f());
|
|
65
55
|
unsubscribes = [];
|
|
66
|
-
}
|
|
56
|
+
},
|
|
67
57
|
};
|
|
68
|
-
|
|
58
|
+
async function start(props) {
|
|
69
59
|
app.debug("Starting tides-api: " + JSON.stringify(props));
|
|
60
|
+
let lastForecast = null;
|
|
61
|
+
let lastPosition = null;
|
|
62
|
+
const cache = new FileCache(app.getDataDirPath());
|
|
70
63
|
// Use the selected source, or the first one if not specified
|
|
71
|
-
const source = sources.find(source => source.id === props.source) || sources[0];
|
|
64
|
+
const source = sources.find((source) => source.id === props.source) || sources[0];
|
|
72
65
|
// Load the selected source
|
|
73
66
|
const provider = await source.start(props);
|
|
74
67
|
// Register the source as a resource provider
|
|
@@ -76,7 +69,9 @@ module.exports = function (app) {
|
|
|
76
69
|
type: "tides",
|
|
77
70
|
methods: {
|
|
78
71
|
async listResources(query) {
|
|
79
|
-
|
|
72
|
+
if (!lastPosition)
|
|
73
|
+
throw new Error("No position available");
|
|
74
|
+
return provider({ position: lastPosition, ...query });
|
|
80
75
|
},
|
|
81
76
|
getResource() {
|
|
82
77
|
throw new Error("Not implemented");
|
|
@@ -86,11 +81,11 @@ module.exports = function (app) {
|
|
|
86
81
|
},
|
|
87
82
|
deleteResource() {
|
|
88
83
|
throw new Error("Not implemented");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
91
86
|
});
|
|
92
87
|
app.subscriptionmanager.subscribe({
|
|
93
|
-
context: "vessels." + app.selfId,
|
|
88
|
+
context: ("vessels." + app.selfId),
|
|
94
89
|
subscribe: [
|
|
95
90
|
{
|
|
96
91
|
path: "navigation.position",
|
|
@@ -100,45 +95,24 @@ module.exports = function (app) {
|
|
|
100
95
|
],
|
|
101
96
|
}, unsubscribes, (subscriptionError) => {
|
|
102
97
|
app.error("Error:" + subscriptionError);
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
}, updatePosition);
|
|
99
|
+
async function updatePosition() {
|
|
100
|
+
lastPosition =
|
|
101
|
+
app.getSelfPath("navigation.position.value") || (await cache.get("position")) || null;
|
|
102
|
+
if (lastPosition) {
|
|
103
|
+
await cache.set("position", lastPosition);
|
|
104
|
+
await updateForecast();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function updateForecast() {
|
|
108
|
+
if (!lastPosition) {
|
|
109
|
+
app.debug("No position available, cannot fetch tide data");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
115
112
|
try {
|
|
116
|
-
lastForecast = await provider();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Get the first tide of this type after now
|
|
120
|
-
if (!nextTide[type] && new Date(time) > now) {
|
|
121
|
-
nextTide[type] = { time, value };
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
const delta = {
|
|
125
|
-
context: "vessels." + app.selfId,
|
|
126
|
-
updates: [
|
|
127
|
-
{
|
|
128
|
-
timestamp: now.toISOString(),
|
|
129
|
-
values: Object.entries(nextTide).flatMap(([type, { time, value }]) => {
|
|
130
|
-
return [
|
|
131
|
-
{ path: `environment.tide.height${type}`, value: value },
|
|
132
|
-
{ path: `environment.tide.time${type}`, value: time },
|
|
133
|
-
];
|
|
134
|
-
}),
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
};
|
|
138
|
-
app.debug("Sending delta: " + JSON.stringify(delta));
|
|
139
|
-
app.handleMessage(plugin.id, delta);
|
|
140
|
-
updateHeightNow();
|
|
141
|
-
app.setPluginStatus("Updated tide data");
|
|
113
|
+
lastForecast = await provider({ position: lastPosition });
|
|
114
|
+
app.setPluginStatus("Updated tide forecast from " + source.title);
|
|
115
|
+
updateTides();
|
|
142
116
|
}
|
|
143
117
|
catch (e) {
|
|
144
118
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -147,28 +121,47 @@ module.exports = function (app) {
|
|
|
147
121
|
app.error(e);
|
|
148
122
|
}
|
|
149
123
|
}
|
|
150
|
-
async function
|
|
124
|
+
async function updateTides(now = new Date()) {
|
|
151
125
|
if (!lastForecast)
|
|
152
126
|
return;
|
|
153
|
-
//
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
127
|
+
// Get the next two upcoming extremes
|
|
128
|
+
const nextTides = lastForecast.extremes
|
|
129
|
+
.filter(({ time }) => new Date(time) >= now)
|
|
130
|
+
.slice(0, 2);
|
|
131
|
+
const delta = {
|
|
132
|
+
context: ("vessels." + app.selfId),
|
|
157
133
|
updates: [
|
|
158
134
|
{
|
|
159
135
|
timestamp: now.toISOString(),
|
|
160
|
-
values: [
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
136
|
+
values: [
|
|
137
|
+
{
|
|
138
|
+
path: "environment.tide.stationName",
|
|
139
|
+
value: lastForecast.station.name,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
path: "environment.tide.heightNow",
|
|
143
|
+
value: approximateTideHeightAt(lastForecast.extremes, now),
|
|
144
|
+
},
|
|
145
|
+
...nextTides.flatMap(({ type, time, value }) => {
|
|
146
|
+
return [
|
|
147
|
+
{ path: `environment.tide.height${type}`, value },
|
|
148
|
+
{ path: `environment.tide.time${type}`, value: time },
|
|
149
|
+
];
|
|
150
|
+
}),
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
app.debug("Sending delta: " + JSON.stringify(delta));
|
|
156
|
+
app.handleMessage(plugin.id, delta);
|
|
164
157
|
}
|
|
165
158
|
// Perform initial update on startup after short delay to allow gnss position to be populated
|
|
166
|
-
delay(4000).then(
|
|
167
|
-
// Update
|
|
168
|
-
setInterval(
|
|
169
|
-
}
|
|
159
|
+
delay(4000).then(updatePosition);
|
|
160
|
+
// Update every minute
|
|
161
|
+
setInterval(updateTides, 60 * 1000);
|
|
162
|
+
}
|
|
170
163
|
function delay(time) {
|
|
171
164
|
return new Promise((resolve) => setTimeout(resolve, time));
|
|
172
165
|
}
|
|
173
166
|
return plugin;
|
|
174
|
-
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getExtremesPrediction } from "neaps";
|
|
2
|
+
import moment from 'moment';
|
|
3
|
+
export default function (app) {
|
|
4
|
+
return {
|
|
5
|
+
id: 'neaps',
|
|
6
|
+
title: 'Neaps',
|
|
7
|
+
properties: {},
|
|
8
|
+
start() {
|
|
9
|
+
app.debug("Using Neaps");
|
|
10
|
+
return async ({ position, date = moment().subtract(1, "days").toISOString() }) => {
|
|
11
|
+
const { station, extremes } = getExtremesPrediction({
|
|
12
|
+
...position,
|
|
13
|
+
start: new Date(date),
|
|
14
|
+
end: moment(date).add(7, "days").toDate(),
|
|
15
|
+
labels: {
|
|
16
|
+
high: "High",
|
|
17
|
+
low: "Low"
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
station: {
|
|
22
|
+
name: station.name,
|
|
23
|
+
position: {
|
|
24
|
+
latitude: station.latitude,
|
|
25
|
+
longitude: station.longitude,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
extremes: extremes.map(({ time, level, label }) => {
|
|
29
|
+
return {
|
|
30
|
+
type: label,
|
|
31
|
+
value: level,
|
|
32
|
+
time: time.toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
package/dist/sources/noaa.js
CHANGED
|
@@ -1,33 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.default = default_1;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
-
const geolib_1 = require("geolib");
|
|
10
|
-
const moment_1 = __importDefault(require("moment"));
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { unlink } from "fs/promises";
|
|
3
|
+
import { getDistance } from "geolib";
|
|
4
|
+
import moment from "moment";
|
|
5
|
+
import FileCache from "../cache.js";
|
|
11
6
|
const stationsUrl = `https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations.json?type=tidepredictions`;
|
|
12
|
-
const dataGetterUrl =
|
|
13
|
-
const datum =
|
|
14
|
-
function
|
|
7
|
+
const dataGetterUrl = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter";
|
|
8
|
+
const datum = "MLLW";
|
|
9
|
+
export default function (app) {
|
|
15
10
|
return {
|
|
16
|
-
id:
|
|
17
|
-
title:
|
|
11
|
+
id: "noaa",
|
|
12
|
+
title: "NOAA (US only)",
|
|
18
13
|
async start() {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
const cache = new FileCache(path.join(app.getDataDirPath(), "noaa"));
|
|
15
|
+
const stations = await StationList.load(cache, app);
|
|
16
|
+
// Remove old cache file
|
|
17
|
+
// @ts-expect-error: configPath exists, just not part of the types
|
|
18
|
+
unlink(path.join(app.config.configPath, "noaastations.json")).catch(() => {
|
|
19
|
+
/* ignore */
|
|
20
|
+
});
|
|
21
|
+
return async (params) => {
|
|
22
|
+
const { position, date = moment().subtract(1, "days") } = params;
|
|
24
23
|
const station = stations.closestTo(position);
|
|
25
24
|
const endpoint = new URL(dataGetterUrl);
|
|
26
25
|
endpoint.search = new URLSearchParams({
|
|
27
26
|
product: "predictions",
|
|
28
27
|
application: "signalk.org/node-server",
|
|
29
|
-
begin_date: (
|
|
30
|
-
end_date: (
|
|
28
|
+
begin_date: moment(date).format("YYYYMMDD"),
|
|
29
|
+
end_date: moment(date).add(7, "days").format("YYYYMMDD"),
|
|
31
30
|
datum,
|
|
32
31
|
station: station.id,
|
|
33
32
|
time_zone: "gmt",
|
|
@@ -40,7 +39,7 @@ function default_1(app) {
|
|
|
40
39
|
const res = await fetch(endpoint.toString());
|
|
41
40
|
if (!res.ok)
|
|
42
41
|
throw new Error("Failed to fetch NOAA tides: " + res.statusText);
|
|
43
|
-
const body = await res.json();
|
|
42
|
+
const body = (await res.json());
|
|
44
43
|
app.debug("NOAA response: \n" + JSON.stringify(body, null, 2));
|
|
45
44
|
if (body.error)
|
|
46
45
|
throw new Error(body.error.message);
|
|
@@ -66,26 +65,22 @@ function default_1(app) {
|
|
|
66
65
|
throw err;
|
|
67
66
|
}
|
|
68
67
|
};
|
|
69
|
-
}
|
|
68
|
+
},
|
|
70
69
|
};
|
|
71
70
|
}
|
|
72
|
-
;
|
|
73
71
|
class StationList extends Map {
|
|
74
|
-
static async load(app) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
data = JSON.parse(await promises_1.default.readFile(filename, 'utf-8'));
|
|
79
|
-
app.debug("NOAA: Loaded cached tide stations from " + filename);
|
|
72
|
+
static async load(cache, app) {
|
|
73
|
+
let data = (await cache.get("stations"));
|
|
74
|
+
if (data) {
|
|
75
|
+
app.debug("NOAA: Loaded cached tide stations");
|
|
80
76
|
}
|
|
81
|
-
|
|
82
|
-
app.debug(
|
|
83
|
-
app.debug('NOAA: Downloading tide stations');
|
|
77
|
+
else {
|
|
78
|
+
app.debug("NOAA: Downloading tide stations");
|
|
84
79
|
const res = await fetch(stationsUrl);
|
|
85
80
|
if (!res.ok)
|
|
86
81
|
throw new Error(`Failed to download stations: ${res.statusText}`);
|
|
87
|
-
data = await res.json();
|
|
88
|
-
await
|
|
82
|
+
data = (await res.json());
|
|
83
|
+
await cache.set("stations", data);
|
|
89
84
|
}
|
|
90
85
|
return new this(data.stations);
|
|
91
86
|
}
|
|
@@ -98,8 +93,13 @@ class StationList extends Map {
|
|
|
98
93
|
near(position, limit = 10) {
|
|
99
94
|
const stationsWithDistances = Array.from(this.values()).map((station) => ({
|
|
100
95
|
...station,
|
|
101
|
-
distance:
|
|
96
|
+
distance: getDistance(position, {
|
|
97
|
+
latitude: station.lat,
|
|
98
|
+
longitude: station.lng,
|
|
99
|
+
}),
|
|
102
100
|
}));
|
|
103
|
-
return stationsWithDistances
|
|
101
|
+
return stationsWithDistances
|
|
102
|
+
.sort((a, b) => a.distance - b.distance)
|
|
103
|
+
.slice(0, limit);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.default = default_1;
|
|
7
|
-
const moment_1 = __importDefault(require("moment"));
|
|
8
|
-
function default_1(app) {
|
|
1
|
+
import moment from 'moment';
|
|
2
|
+
export default function (app) {
|
|
9
3
|
return {
|
|
10
4
|
id: 'stormglass',
|
|
11
5
|
title: 'StormGlass.io',
|
|
@@ -17,18 +11,14 @@ function default_1(app) {
|
|
|
17
11
|
},
|
|
18
12
|
start({ stormglassApiKey } = {}) {
|
|
19
13
|
app.debug("Using StormGlass.io API");
|
|
20
|
-
return async (
|
|
21
|
-
const { date = (0, moment_1.default)().subtract(1, "days").toISOString() } = params;
|
|
14
|
+
return async ({ position, date = moment().subtract(1, "days").toISOString() }) => {
|
|
22
15
|
const endPoint = new URL("https://api.stormglass.io/v2/tide/extremes/point");
|
|
23
|
-
const position = app.getSelfPath("navigation.position.value");
|
|
24
|
-
if (!position)
|
|
25
|
-
throw new Error("no position");
|
|
26
16
|
endPoint.search = new URLSearchParams({
|
|
27
|
-
start: (
|
|
28
|
-
end: (
|
|
17
|
+
start: moment(date).format("YYYY-MM-DD"),
|
|
18
|
+
end: moment(date).add(7, "days").format("YYYY-MM-DD"),
|
|
29
19
|
// datum: "CD",
|
|
30
|
-
lat: position.latitude,
|
|
31
|
-
lng: position.longitude
|
|
20
|
+
lat: position.latitude.toString(),
|
|
21
|
+
lng: position.longitude.toString()
|
|
32
22
|
}).toString();
|
|
33
23
|
app.debug("Fetching StormGlass.io: " + endPoint.toString());
|
|
34
24
|
const res = await fetch(endPoint, {
|
|
@@ -40,7 +30,7 @@ function default_1(app) {
|
|
|
40
30
|
app.debug(JSON.stringify(data, null, 2));
|
|
41
31
|
return {
|
|
42
32
|
station: {
|
|
43
|
-
name: data.meta.station.name
|
|
33
|
+
name: `${data.meta.station.name} (${data.meta.station.source})`,
|
|
44
34
|
position: {
|
|
45
35
|
latitude: data.meta.station.lat,
|
|
46
36
|
longitude: data.meta.station.lng,
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.default = default_1;
|
|
7
|
-
const moment_1 = __importDefault(require("moment"));
|
|
8
|
-
function default_1(app) {
|
|
1
|
+
import moment from 'moment';
|
|
2
|
+
export default function (app) {
|
|
9
3
|
return {
|
|
10
4
|
id: 'WorldTides.info',
|
|
11
5
|
title: 'worldtides.info',
|
|
@@ -17,20 +11,17 @@ function default_1(app) {
|
|
|
17
11
|
},
|
|
18
12
|
start({ worldtidesApiKey = '' } = {}) {
|
|
19
13
|
app.debug("Using WorldTides API");
|
|
20
|
-
return async (params
|
|
21
|
-
const { date = (
|
|
14
|
+
return async (params) => {
|
|
15
|
+
const { position, date = moment().subtract(1, "days") } = params;
|
|
22
16
|
const endPoint = new URL("https://www.worldtides.info/api/v3");
|
|
23
|
-
const position = app.getSelfPath("navigation.position.value");
|
|
24
|
-
if (!position)
|
|
25
|
-
throw new Error("no position");
|
|
26
17
|
endPoint.search = new URLSearchParams({
|
|
27
|
-
date: (
|
|
18
|
+
date: moment(date).format("YYYY-MM-DD"),
|
|
28
19
|
datum: "CD",
|
|
29
20
|
days: "7",
|
|
30
21
|
extremes: "true",
|
|
31
22
|
key: worldtidesApiKey,
|
|
32
|
-
lat: position.latitude,
|
|
33
|
-
lon: position.longitude,
|
|
23
|
+
lat: position.latitude.toString(),
|
|
24
|
+
lon: position.longitude.toString(),
|
|
34
25
|
}).toString();
|
|
35
26
|
app.debug("Fetching worldtides: " + endPoint.toString());
|
|
36
27
|
const res = await fetch(endPoint);
|
|
@@ -42,7 +33,7 @@ function default_1(app) {
|
|
|
42
33
|
throw new Error("worldtides data: " + data.error ? data.error : "none");
|
|
43
34
|
return {
|
|
44
35
|
station: {
|
|
45
|
-
name:
|
|
36
|
+
name: `${data.station} (${data.atlas})`,
|
|
46
37
|
position: {
|
|
47
38
|
latitude: data.responseLat,
|
|
48
39
|
longitude: data.responseLon,
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|