signalk-tides 1.0.0 → 1.1.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 +15 -1
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/sources/noaa.js +7 -9
- package/src/sources/stormglass.js +70 -0
- package/src/sources/worldtides.js +6 -5
package/README.md
CHANGED
|
@@ -19,10 +19,21 @@ It publishes the following [tide data](https://signalk.org/specification/1.7.0/d
|
|
|
19
19
|
* `environment.tide.heightLow`
|
|
20
20
|
* `environment.tide.timeLow`
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
### Tides resource
|
|
23
|
+
|
|
24
|
+
It also registers a `tides` resource, which returns tide predictions for yesterday and the next 6 days for the vessel's position.
|
|
23
25
|
|
|
24
26
|
```
|
|
25
27
|
$ curl http://localhost:3000/signalk/v2/api/resources/tides
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
##### Request Parameters
|
|
31
|
+
|
|
32
|
+
* `date` (optional) - the date for which to get the tide predictions in the format `YYYY-MM-DD`. Default: yesterday's date.
|
|
33
|
+
|
|
34
|
+
##### Response
|
|
35
|
+
|
|
36
|
+
```json
|
|
26
37
|
{
|
|
27
38
|
"id" : "9710441",
|
|
28
39
|
"name" : "Nurse Channel",
|
|
@@ -95,10 +106,13 @@ $ curl http://localhost:3000/signalk/v2/api/resources/tides
|
|
|
95
106
|
}
|
|
96
107
|
```
|
|
97
108
|
|
|
109
|
+
|
|
110
|
+
|
|
98
111
|
## Sources
|
|
99
112
|
|
|
100
113
|
- [NOAA](https://tidesandcurrents.noaa.gov/web_services_info.html) (US only)
|
|
101
114
|
- [WorldTides API](https://www.worldtides.info/) (requires an API key)
|
|
115
|
+
- [StormGlass.io](https://stormglass.io/) (requires an API key)
|
|
102
116
|
|
|
103
117
|
## License
|
|
104
118
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
const noaa = require('./sources/noaa');
|
|
18
|
+
const stormglass = require('./sources/stormglass');
|
|
17
19
|
const worldtides = require('./sources/worldtides');
|
|
18
|
-
const noaa = require('./sources/noaa')
|
|
19
20
|
|
|
20
21
|
module.exports = function(app) {
|
|
21
22
|
// Interval to update tide data
|
|
@@ -24,6 +25,7 @@ module.exports = function(app) {
|
|
|
24
25
|
|
|
25
26
|
const sources = [
|
|
26
27
|
noaa(app),
|
|
28
|
+
stormglass(app),
|
|
27
29
|
worldtides(app),
|
|
28
30
|
];
|
|
29
31
|
|
package/src/sources/noaa.js
CHANGED
|
@@ -63,21 +63,18 @@ class NoaaProvider {
|
|
|
63
63
|
this.stations = stations;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
async listResources(
|
|
66
|
+
async listResources({ date = moment().subtract(1, "days") } = {}) {
|
|
67
67
|
const position = this.app.getSelfPath("navigation.position.value");
|
|
68
68
|
if (!position) throw new Error("no position");
|
|
69
69
|
|
|
70
70
|
const station = this.stations.closestTo(position);
|
|
71
71
|
|
|
72
|
-
// Use server date, or current date if not available
|
|
73
|
-
const now = new Date(this.app.getSelfPath("navigation.datetime.value") ?? Date.now());
|
|
74
|
-
|
|
75
72
|
const endpoint = new URL(dataGetterUrl);
|
|
76
73
|
endpoint.search = new URLSearchParams({
|
|
77
74
|
product: "predictions",
|
|
78
75
|
application: "signalk.org/node-server",
|
|
79
|
-
begin_date: moment(
|
|
80
|
-
end_date: moment(
|
|
76
|
+
begin_date: moment(date).format("YYYYMMDD"),
|
|
77
|
+
end_date: moment(date).add(7, "days").format("YYYYMMDD"),
|
|
81
78
|
datum,
|
|
82
79
|
station: station.reference_id,
|
|
83
80
|
time_zone: "gmt",
|
|
@@ -90,7 +87,8 @@ class NoaaProvider {
|
|
|
90
87
|
|
|
91
88
|
try {
|
|
92
89
|
const res = await fetch(endpoint);
|
|
93
|
-
if (!res.ok)
|
|
90
|
+
if (!res.ok)
|
|
91
|
+
throw new Error("Failed to fetch NOAA tides: " + res.statusText);
|
|
94
92
|
const body = await res.json();
|
|
95
93
|
this.app.debug("NOAA response: \n" + JSON.stringify(body, null, 2));
|
|
96
94
|
|
|
@@ -103,13 +101,13 @@ class NoaaProvider {
|
|
|
103
101
|
latitude: station.lat,
|
|
104
102
|
longitude: station.lng,
|
|
105
103
|
},
|
|
106
|
-
extremes: body.predictions.map(({t, v, type}) => {
|
|
104
|
+
extremes: body.predictions.map(({ t, v, type }) => {
|
|
107
105
|
return {
|
|
108
106
|
type: type === "H" ? "High" : "Low",
|
|
109
107
|
value: Number(v),
|
|
110
108
|
time: new Date(`${t}Z`).toISOString(),
|
|
111
109
|
};
|
|
112
|
-
})
|
|
110
|
+
}),
|
|
113
111
|
};
|
|
114
112
|
|
|
115
113
|
return tides;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const moment = require('moment');
|
|
2
|
+
|
|
3
|
+
module.exports = function (app, plugin) {
|
|
4
|
+
return {
|
|
5
|
+
id: 'stormglass',
|
|
6
|
+
title: 'StormGlass.io',
|
|
7
|
+
properties: {
|
|
8
|
+
stormglassApiKey: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
title: 'StormGlass.io API key'
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
start(options) {
|
|
15
|
+
app.debug("Using StormGlass.io API");
|
|
16
|
+
return {
|
|
17
|
+
async listResources({ date = moment().subtract(1, "days") } = {}) {
|
|
18
|
+
const endPoint = new URL("https://api.stormglass.io/v2/tide/extremes/point");
|
|
19
|
+
|
|
20
|
+
const position = app.getSelfPath("navigation.position.value");
|
|
21
|
+
if (!position) throw new Error("no position");
|
|
22
|
+
|
|
23
|
+
endPoint.search = new URLSearchParams({
|
|
24
|
+
start: moment(date).format("YYYY-MM-DD"),
|
|
25
|
+
end: moment(date).add(7, "days").format("YYYY-MM-DD"),
|
|
26
|
+
// datum: "CD",
|
|
27
|
+
lat: position.latitude,
|
|
28
|
+
lng: position.longitude,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
app.debug("Fetching StormGlass.io", endPoint.toString());
|
|
32
|
+
|
|
33
|
+
const res = await fetch(endPoint, {
|
|
34
|
+
headers: { Authorization: options.stormglassApiKey },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if(!res.ok) throw new Error('Failed to fetch StormGlass.io: ' + res.statusText);
|
|
38
|
+
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
app.debug(JSON.stringify(data, null, 2));
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
name: data.meta.station.name,
|
|
44
|
+
position: {
|
|
45
|
+
latitude: data.meta.station.lat,
|
|
46
|
+
longitude: data.meta.station.lng,
|
|
47
|
+
},
|
|
48
|
+
extremes: data.data.map(({ type, time, height}) => {
|
|
49
|
+
return {
|
|
50
|
+
type: type === "high" ? "High" : "Low",
|
|
51
|
+
value: height,
|
|
52
|
+
time: new Date(time).toISOString(),
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
getResource: (id, property) => {
|
|
58
|
+
throw new Error("Not implemented");
|
|
59
|
+
},
|
|
60
|
+
setResource: (id, value) => {
|
|
61
|
+
throw new Error("Not implemented");
|
|
62
|
+
},
|
|
63
|
+
deleteResource: (id) => {
|
|
64
|
+
throw new Error("Not implemented");
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const moment = require('moment');
|
|
2
|
+
|
|
1
3
|
module.exports = function (app, plugin) {
|
|
2
4
|
return {
|
|
3
5
|
id: 'WorldTides.info',
|
|
@@ -12,15 +14,14 @@ module.exports = function (app, plugin) {
|
|
|
12
14
|
start(options) {
|
|
13
15
|
app.debug("Using WorldTides API");
|
|
14
16
|
return {
|
|
15
|
-
async listResources() {
|
|
16
|
-
const endPoint = new URL("https://www.worldtides.info/api");
|
|
17
|
+
async listResources({ date = moment().subtract(1, "days") } = {}) {
|
|
18
|
+
const endPoint = new URL("https://www.worldtides.info/api/v3");
|
|
17
19
|
|
|
18
20
|
const position = app.getSelfPath("navigation.position.value");
|
|
19
|
-
|
|
20
21
|
if (!position) throw new Error("no position");
|
|
21
22
|
|
|
22
23
|
endPoint.search = new URLSearchParams({
|
|
23
|
-
date: "
|
|
24
|
+
date: moment(date).format("YYYY-MM-DD"),
|
|
24
25
|
datum: "CD",
|
|
25
26
|
days: 7,
|
|
26
27
|
extremes: true,
|
|
@@ -31,11 +32,11 @@ module.exports = function (app, plugin) {
|
|
|
31
32
|
|
|
32
33
|
app.debug("Fetching worldtides", endPoint.toString());
|
|
33
34
|
|
|
34
|
-
// return fetch(endPoint).then(async (res) => {
|
|
35
35
|
const res = await fetch(endPoint);
|
|
36
36
|
if(!res.ok) throw new Error('Failed to fetch worldtides: ' + res.statusText);
|
|
37
37
|
|
|
38
38
|
const data = await res.json();
|
|
39
|
+
app.debug(JSON.stringify(data, null, 2));
|
|
39
40
|
|
|
40
41
|
if (data.status != 200) throw new Error("worldtides data: " + data.error ? data.error : "none");
|
|
41
42
|
|