signalk-tides 1.2.3 → 1.3.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.
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.approximateTideHeightAt = approximateTideHeightAt;
4
+ /** Given a list of tide extremes, estimate the height at a specific time. */
5
+ function approximateTideHeightAt(extremes, time) {
6
+ const sorted = extremes.slice().sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
7
+ const prev = sorted.filter(h => new Date(h.time) <= time).at(-1);
8
+ const next = sorted.filter(h => new Date(h.time) >= time).at(0);
9
+ if (!prev)
10
+ throw new Error("Missing height data before " + time.toISOString());
11
+ if (!next)
12
+ throw new Error("Missing height data after " + time.toISOString());
13
+ const progress = (time.getTime() - new Date(prev.time).getTime()) /
14
+ (new Date(next.time).getTime() - new Date(prev.time).getTime());
15
+ const value = interpolate(prev.value, next.value, easeSine(progress));
16
+ return parseFloat(value.toFixed(3));
17
+ }
18
+ /** Interpolate between two values using the given progress (0-1). */
19
+ function interpolate(start, end, progress) {
20
+ return start + (end - start) * progress;
21
+ }
22
+ function easeSine(progress) {
23
+ // Map progress [0..1] to angle [0..π]
24
+ const angle = progress * Math.PI;
25
+ // Use sine to ease in/out
26
+ return (1 - Math.cos(angle)) / 2;
27
+ }
package/dist/index.js CHANGED
@@ -20,10 +20,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
20
20
  const noaa_js_1 = __importDefault(require("./sources/noaa.js"));
21
21
  const stormglass_js_1 = __importDefault(require("./sources/stormglass.js"));
22
22
  const worldtides_js_1 = __importDefault(require("./sources/worldtides.js"));
23
+ const calculations_js_1 = require("./calculations.js");
23
24
  module.exports = function (app) {
24
25
  // Interval to update tide data
25
26
  const defaultPeriod = 60; // 1 hour
26
27
  let unsubscribes = [];
28
+ let lastForecast = null;
27
29
  const sources = [
28
30
  (0, noaa_js_1.default)(app),
29
31
  (0, stormglass_js_1.default)(app),
@@ -52,7 +54,7 @@ module.exports = function (app) {
52
54
  period: {
53
55
  title: "Update frequency",
54
56
  type: "number",
55
- description: "How often to update tide data (minutes)",
57
+ description: "How often to update tide forecast (minutes)",
56
58
  default: 60,
57
59
  minimum: 1,
58
60
  },
@@ -104,18 +106,16 @@ module.exports = function (app) {
104
106
  // @ts-expect-error: TODO[TS]: fix Delta type upstream
105
107
  values.forEach(({ path }) => {
106
108
  if (path === "navigation.position") {
107
- performUpdate();
109
+ updateForecast();
108
110
  }
109
111
  });
110
112
  });
111
113
  });
112
- async function performUpdate() {
114
+ async function updateForecast(now = new Date()) {
113
115
  try {
114
- const { extremes } = await provider();
115
- // Use server date, or current date if not available
116
- const now = new Date(app.getSelfPath("navigation.datetime.value") ?? Date.now());
116
+ lastForecast = await provider();
117
117
  const nextTide = {};
118
- extremes.forEach(({ type, value, time }) => {
118
+ lastForecast.extremes.forEach(({ type, value, time }) => {
119
119
  // Get the first tide of this type after now
120
120
  if (!nextTide[type] && new Date(time) > now) {
121
121
  nextTide[type] = { time, value };
@@ -137,6 +137,7 @@ module.exports = function (app) {
137
137
  };
138
138
  app.debug("Sending delta: " + JSON.stringify(delta));
139
139
  app.handleMessage(plugin.id, delta);
140
+ updateHeightNow();
140
141
  app.setPluginStatus("Updated tide data");
141
142
  }
142
143
  catch (e) {
@@ -146,8 +147,25 @@ module.exports = function (app) {
146
147
  app.error(e);
147
148
  }
148
149
  }
150
+ async function updateHeightNow(now = new Date()) {
151
+ if (!lastForecast)
152
+ return;
153
+ // Combine heights and extremes to account for min/max being between height samples
154
+ const height = (0, calculations_js_1.approximateTideHeightAt)(lastForecast.extremes, now);
155
+ app.handleMessage(plugin.id, {
156
+ context: "vessels." + app.selfId,
157
+ updates: [
158
+ {
159
+ timestamp: now.toISOString(),
160
+ values: [{ path: "environment.tide.heightNow", value: height }]
161
+ }
162
+ ]
163
+ });
164
+ }
149
165
  // Perform initial update on startup after short delay to allow gnss position to be populated
150
- delay(4000).then(() => { performUpdate(); });
166
+ delay(4000).then(() => { updateForecast(); });
167
+ // Update heightNow every minute
168
+ setInterval(updateHeightNow, 60 * 1000);
151
169
  };
152
170
  function delay(time) {
153
171
  return new Promise((resolve) => setTimeout(resolve, time));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-tides",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Tidal predictions for the vessel's position from various online sources.",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
package/CONTRIBUTING.md DELETED
@@ -1 +0,0 @@
1
- # Contributing