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 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
- It also registers a `tides` resource, which returns tide predictions for the next 7 days for the vessel's position:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-tides",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Tidal predictions for the vessel's position from various online sources.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
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
 
@@ -63,21 +63,18 @@ class NoaaProvider {
63
63
  this.stations = stations;
64
64
  }
65
65
 
66
- async listResources(params) {
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(now).subtract(1, "day").format("YYYYMMDD"),
80
- end_date: moment(now).add(1, "day").format("YYYYMMDD"),
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) throw new Error("Failed to fetch NOAA tides: " + res.statusText);
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: "today",
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