@signalk/freeboard-sk 2.6.0-rc2 → 2.6.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/CHANGELOG.md +3 -1
- package/README.md +22 -0
- package/package.json +1 -1
- package/plugin/anchor/anchor-api.js +118 -0
- package/plugin/lib/noaa.js +191 -0
- package/plugin/lib/openweather.js +166 -0
- package/plugin/lib/types.js +0 -0
- package/plugin/pypilot.js +312 -0
- package/plugin/weather.js +609 -0
- package/public/assets/help/img/settings_charts.png +0 -0
- package/public/assets/help/index.html +30 -3
- package/public/index.html +1 -1
- package/public/{main.f16a8a2fbadd9ec7.js → main.8e02670b90762557.js} +1 -1
- package/public/{runtime.dc0de0ded4e16b3c.js → runtime.c7f9b6cf7269498e.js} +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
### v2.6.0
|
|
4
4
|
|
|
5
|
-
- **Added**: Ability to show/hide toolbar buttons on both sides of the screen.
|
|
5
|
+
- **Added**: Ability to show / hide toolbar buttons on both sides of the screen.
|
|
6
6
|
- **Added**: Route builder. 1st release allows creating a route from waypoints.
|
|
7
|
+
- **Added**: Support for displaying S57 vector charts converted using [s57-tiler](https://github.com/wdantuma/s57-tiler).
|
|
8
|
+
- **Updated**: Resources created outside of Freeboard UI (plugins, etc.) are now selected for display on the map. Previously these had to be manually selected from the relevant list.
|
|
7
9
|
- **Fixed**: Wind speed values in vessel popover showing 0 rather than - when no value is present.
|
|
8
10
|
|
|
9
11
|
### v2.5.0
|
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ Charts are sourced from the `/resources/charts` path on the Signal K server and
|
|
|
32
32
|
|
|
33
33
|
- Image tiles _(XYZ)_
|
|
34
34
|
- Vector Tiles _(MVT / PBF)_
|
|
35
|
+
- [S57 ENC's converted to vector tiles](#S57-charts) _(MVT / PBF)_
|
|
35
36
|
- WMS _(Web Map Server)_
|
|
36
37
|
- WMTS _(Web Map Tile Server)_
|
|
37
38
|
- PMTiles _(ProtoMap files)_
|
|
@@ -116,6 +117,27 @@ _Note: The `Signal K Instrument Panel` app will be displayed if no user selectio
|
|
|
116
117
|
|
|
117
118
|
---
|
|
118
119
|
|
|
120
|
+
### S57 Charts
|
|
121
|
+
|
|
122
|
+
Freeboard-SK is able to display S57 ENC charts that have been converted to vector tiles with [s57-tiler](https://github.com/wdantuma/s57-tiler). _(See the [README](https://github.com/wdantuma/s57-tiler) for instructions how to create the vectortiles from downloaded S57 ENC's.)_
|
|
123
|
+
|
|
124
|
+
See [Open CPN chart sources](https://opencpn.org/OpenCPN/info/chartsource.html) for a list of locations to source charts.
|
|
125
|
+
|
|
126
|
+
_Note: Only unencrypted ENC's are supported (no S63 support)._
|
|
127
|
+
|
|
128
|
+
**_Requires: @signalk/charts-plugin_**
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+

|
|
132
|
+
|
|
133
|
+
Rendering of the Shallow, safety and deep depths and can be configured in the settings dialog
|
|
134
|
+
|
|
135
|
+

|
|
136
|
+
|
|
137
|
+
_Note: This functionality is not a replacement for official navigational charts_
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
119
141
|
### Experiments:
|
|
120
142
|
|
|
121
143
|
Features that are not ready for "prime time" are made available as experiments.
|
package/package.json
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initAnchorApi = void 0;
|
|
4
|
+
const fetch_1 = require("../lib/fetch");
|
|
5
|
+
let server;
|
|
6
|
+
let hostPath;
|
|
7
|
+
const apiBasePath = '/signalk/v2/api/vessels/self/navigation/anchor';
|
|
8
|
+
const anchorPlugin = {
|
|
9
|
+
has: false,
|
|
10
|
+
enabled: false,
|
|
11
|
+
version: ''
|
|
12
|
+
};
|
|
13
|
+
let pluginPath;
|
|
14
|
+
const msgPluginNotFound = 'signalk-anchor-alarm is not installed!';
|
|
15
|
+
const initAnchorApi = async (app) => {
|
|
16
|
+
server = app;
|
|
17
|
+
server.debug(`** initAnchorApi() **`);
|
|
18
|
+
// detect signalk-anchor-alarm plugin
|
|
19
|
+
anchorPlugin.has = true;
|
|
20
|
+
try {
|
|
21
|
+
let port = 3000;
|
|
22
|
+
if (typeof server.config?.getExternalPort === 'function') {
|
|
23
|
+
server.debug('*** getExternalPort()', server.config.getExternalPort());
|
|
24
|
+
port = server.config.getExternalPort();
|
|
25
|
+
}
|
|
26
|
+
hostPath = `${server.config.ssl ? 'https' : 'http'}://localhost:${port}`;
|
|
27
|
+
// temp patch for detection issue
|
|
28
|
+
pluginPath = `/plugins/anchoralarm`;
|
|
29
|
+
anchorPlugin.enabled = true;
|
|
30
|
+
/*
|
|
31
|
+
const url = `${hostPath}/plugins`;
|
|
32
|
+
const r: Array<{ id: string }> = await fetch(url);
|
|
33
|
+
r.forEach(
|
|
34
|
+
(plugin: { id: string; version: string; data: { enabled: boolean } }) => {
|
|
35
|
+
if (plugin.id === 'anchoralarm') {
|
|
36
|
+
pluginPath = `/plugins/${plugin.id}`;
|
|
37
|
+
anchorPlugin.has = true;
|
|
38
|
+
anchorPlugin.version = plugin.version;
|
|
39
|
+
anchorPlugin.enabled = plugin.data.enabled;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
);*/
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
anchorPlugin.has = false;
|
|
46
|
+
}
|
|
47
|
+
server.debug('*** Anchor Alarm Plugin detected:', anchorPlugin.has);
|
|
48
|
+
server.debug('*** Anchor Alarm Plugin enabled:', anchorPlugin.enabled);
|
|
49
|
+
server.debug('*** Anchor Alarm Plugin API Path', `${hostPath}${pluginPath}`);
|
|
50
|
+
if (anchorPlugin.has) {
|
|
51
|
+
initApiEndpoints();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
exports.initAnchorApi = initAnchorApi;
|
|
55
|
+
const initApiEndpoints = () => {
|
|
56
|
+
server.debug(`** Registering Anchor API endpoint(s) **`);
|
|
57
|
+
server.post(`${apiBasePath}/drop`, async (req, res) => {
|
|
58
|
+
server.debug(`** POST ${apiBasePath}/drop`);
|
|
59
|
+
if (!anchorPlugin.has) {
|
|
60
|
+
res.status(400).json({
|
|
61
|
+
state: 'COMPLETED',
|
|
62
|
+
statusCode: 400,
|
|
63
|
+
message: msgPluginNotFound
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const r = await (0, fetch_1.post)(`${hostPath}${pluginPath}/dropAnchor`, '{}');
|
|
69
|
+
res.status(r.statusCode).json(r);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
// fix plugin returned 401 error code when no position is available
|
|
73
|
+
if (e.statusCode === 401 && e.message.indexOf('no position') !== -1) {
|
|
74
|
+
e.statusCode = 400;
|
|
75
|
+
}
|
|
76
|
+
res.status(e.statusCode).json(e);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
server.post(`${apiBasePath}/raise`, async (req, res) => {
|
|
80
|
+
server.debug(`** POST ${apiBasePath}/raise`);
|
|
81
|
+
if (!anchorPlugin.has) {
|
|
82
|
+
res.status(400).json({
|
|
83
|
+
state: 'COMPLETED',
|
|
84
|
+
statusCode: 400,
|
|
85
|
+
message: msgPluginNotFound
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const r = await (0, fetch_1.post)(`${hostPath}${pluginPath}/raiseAnchor`, '{}');
|
|
91
|
+
res.status(r.statusCode).json(r);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
res.status(e.statusCode).json(e);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
server.post(`${apiBasePath}/radius`, async (req, res) => {
|
|
98
|
+
server.debug(`** POST ${apiBasePath}/radius`);
|
|
99
|
+
if (!anchorPlugin.has) {
|
|
100
|
+
res.status(400).json({
|
|
101
|
+
state: 'COMPLETED',
|
|
102
|
+
statusCode: 400,
|
|
103
|
+
message: msgPluginNotFound
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const val = req.body.value && typeof req.body.value === 'number'
|
|
109
|
+
? { radius: req.body.value }
|
|
110
|
+
: {};
|
|
111
|
+
const r = await (0, fetch_1.post)(`${hostPath}${pluginPath}/setRadius`, JSON.stringify(val));
|
|
112
|
+
res.status(r.statusCode).json(r);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
res.status(e.statusCode).json(e);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// NOAA
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.NOAA = void 0;
|
|
5
|
+
const fetch_1 = require("./fetch");
|
|
6
|
+
const weather_1 = require("../weather");
|
|
7
|
+
var CARDINAL_POINTS;
|
|
8
|
+
(function (CARDINAL_POINTS) {
|
|
9
|
+
CARDINAL_POINTS[CARDINAL_POINTS["N"] = 0] = "N";
|
|
10
|
+
CARDINAL_POINTS[CARDINAL_POINTS["NNE"] = 22.5] = "NNE";
|
|
11
|
+
CARDINAL_POINTS[CARDINAL_POINTS["NE"] = 45] = "NE";
|
|
12
|
+
CARDINAL_POINTS[CARDINAL_POINTS["ENE"] = 67.5] = "ENE";
|
|
13
|
+
CARDINAL_POINTS[CARDINAL_POINTS["E"] = 90] = "E";
|
|
14
|
+
CARDINAL_POINTS[CARDINAL_POINTS["ESE"] = 112.5] = "ESE";
|
|
15
|
+
CARDINAL_POINTS[CARDINAL_POINTS["SE"] = 135] = "SE";
|
|
16
|
+
CARDINAL_POINTS[CARDINAL_POINTS["SSE"] = 157.5] = "SSE";
|
|
17
|
+
CARDINAL_POINTS[CARDINAL_POINTS["S"] = 180] = "S";
|
|
18
|
+
CARDINAL_POINTS[CARDINAL_POINTS["SSW"] = 202.5] = "SSW";
|
|
19
|
+
CARDINAL_POINTS[CARDINAL_POINTS["SW"] = 225] = "SW";
|
|
20
|
+
CARDINAL_POINTS[CARDINAL_POINTS["WSW"] = 247.5] = "WSW";
|
|
21
|
+
CARDINAL_POINTS[CARDINAL_POINTS["W"] = 270] = "W";
|
|
22
|
+
CARDINAL_POINTS[CARDINAL_POINTS["WNW"] = 292.5] = "WNW";
|
|
23
|
+
CARDINAL_POINTS[CARDINAL_POINTS["NW"] = 315] = "NW";
|
|
24
|
+
CARDINAL_POINTS[CARDINAL_POINTS["NNW"] = 337.5] = "NNW";
|
|
25
|
+
})(CARDINAL_POINTS || (CARDINAL_POINTS = {}));
|
|
26
|
+
class NOAA {
|
|
27
|
+
settings;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.settings = config;
|
|
30
|
+
}
|
|
31
|
+
getUrl(position) {
|
|
32
|
+
const api = 'https://api.weather.gov/points';
|
|
33
|
+
if (!this.settings.apiKey || !position) {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return `${api}/${position.latitude.toFixed(4)},${position.longitude.toFixed(4)}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
fetchData = async (position) => {
|
|
41
|
+
const url = this.getUrl(position);
|
|
42
|
+
//console.log(`url`, url)
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
const response = await (0, fetch_1.fetch)(url);
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
let forecasts = [];
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
let observations = [];
|
|
49
|
+
// observations
|
|
50
|
+
if (response?.properties?.observationStations) {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
const stations = await (0, fetch_1.fetch)(response.properties.observationStations);
|
|
53
|
+
observations = await (0, fetch_1.fetch)(`${stations.features[0].id}/observations/latest`);
|
|
54
|
+
//console.log(`observations`, observations)
|
|
55
|
+
}
|
|
56
|
+
// forecasts
|
|
57
|
+
if (response?.properties?.forecastHourly) {
|
|
58
|
+
forecasts = await (0, fetch_1.fetch)(response.properties.forecastHourly);
|
|
59
|
+
//console.log(`forecasts`, forecasts)
|
|
60
|
+
}
|
|
61
|
+
// warnings
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
const warnings = await (0, fetch_1.fetch)(`https://api.weather.gov/alerts/active?point=${position.latitude.toFixed(4)},${position.longitude.toFixed(4)}`);
|
|
64
|
+
//console.log(`warnings`, warnings)
|
|
65
|
+
return this.parseResponse({
|
|
66
|
+
position: position,
|
|
67
|
+
forecasts: forecasts?.properties?.periods ?? [],
|
|
68
|
+
observations: observations?.properties ?? null,
|
|
69
|
+
warnings: warnings?.features ?? []
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
parseResponse = (wData) => {
|
|
73
|
+
const res = {};
|
|
74
|
+
res[weather_1.defaultStationId] = {
|
|
75
|
+
name: 'Weather data relative to supplied position.',
|
|
76
|
+
position: {
|
|
77
|
+
latitude: wData.latitude,
|
|
78
|
+
longitude: wData.longitude
|
|
79
|
+
},
|
|
80
|
+
observations: this.parseNoaaObservations(wData.observations),
|
|
81
|
+
forecasts: this.parseNoaaForecasts(wData.forecasts),
|
|
82
|
+
warnings: this.parseNoaaWarnings(wData.warnings)
|
|
83
|
+
};
|
|
84
|
+
return res;
|
|
85
|
+
};
|
|
86
|
+
parseNoaaObservations(wData) {
|
|
87
|
+
const data = [];
|
|
88
|
+
let obs;
|
|
89
|
+
let v;
|
|
90
|
+
if (wData) {
|
|
91
|
+
obs = {
|
|
92
|
+
timestamp: wData.timestamp ?? null,
|
|
93
|
+
description: wData.textDescription ?? null
|
|
94
|
+
};
|
|
95
|
+
v = wData.visibility.value
|
|
96
|
+
? wData.visibility.unitCode === 'wmoUnit:m'
|
|
97
|
+
? wData.visibility.value
|
|
98
|
+
: wData.visibility.value
|
|
99
|
+
: null;
|
|
100
|
+
obs.outside.horizontalVisibility = v;
|
|
101
|
+
v = wData.temperature.value
|
|
102
|
+
? wData.temperature.unitCode === 'wmoUnit:degC'
|
|
103
|
+
? wData.temperature.value + 273.15
|
|
104
|
+
: wData.temperature.value
|
|
105
|
+
: null;
|
|
106
|
+
obs.outside.temperature = v;
|
|
107
|
+
v = wData.dewpoint.value
|
|
108
|
+
? wData.dewpoint.unitCode === 'wmoUnit:degC'
|
|
109
|
+
? wData.dewpoint.value + 273.15
|
|
110
|
+
: wData.dewpoint.value
|
|
111
|
+
: null;
|
|
112
|
+
obs.outside.dewPointTemperature = v;
|
|
113
|
+
v = wData.seaLevelPressure.value
|
|
114
|
+
? wData.seaLevelPressure.unitCode === 'wmoUnit:Pa'
|
|
115
|
+
? wData.seaLevelPressure.value
|
|
116
|
+
: wData.seaLevelPressure.value
|
|
117
|
+
: null;
|
|
118
|
+
obs.outside.pressure = v;
|
|
119
|
+
v = wData.relativeHumidity.value
|
|
120
|
+
? wData.relativeHumidity.unitCode === 'wmoUnit:percent'
|
|
121
|
+
? wData.relativeHumidity.value / 100
|
|
122
|
+
: wData.relativeHumidity.value
|
|
123
|
+
: null;
|
|
124
|
+
obs.outside.relativeHumidity = v;
|
|
125
|
+
v = wData.windSpeed.value
|
|
126
|
+
? wData.windSpeed.unitCode === 'wmoUnit:km_h-1'
|
|
127
|
+
? wData.windSpeed.value / 3.6
|
|
128
|
+
: wData.windSpeed.value
|
|
129
|
+
: null;
|
|
130
|
+
obs.wind.speedTrue = v;
|
|
131
|
+
v = wData.windDirection.value
|
|
132
|
+
? wData.windDirection.unitCode === 'wmoUnit:degree_(angle)'
|
|
133
|
+
? wData.windDirection.value * (Math.PI / 180)
|
|
134
|
+
: wData.windDirection.value
|
|
135
|
+
: null;
|
|
136
|
+
obs.wind.directionTrue = v;
|
|
137
|
+
v = wData.windGust.value
|
|
138
|
+
? wData.windGust.unitCode === 'wmoUnit:km_h-1'
|
|
139
|
+
? wData.windGust.value / 3.6
|
|
140
|
+
: wData.windGust.value
|
|
141
|
+
: null;
|
|
142
|
+
obs.wind.gust = v;
|
|
143
|
+
data.push(obs);
|
|
144
|
+
}
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
parseNoaaForecasts(forecasts) {
|
|
148
|
+
const data = [];
|
|
149
|
+
if (forecasts && Array.isArray(forecasts)) {
|
|
150
|
+
forecasts.forEach((f) => {
|
|
151
|
+
const forecast = {
|
|
152
|
+
timestamp: f.startTime ?? null,
|
|
153
|
+
description: f.shortForecast ?? null
|
|
154
|
+
};
|
|
155
|
+
forecast.outside.temperature =
|
|
156
|
+
typeof f.temperature !== 'undefined'
|
|
157
|
+
? ((f.temperature - 32) * 5) / 9 + 273.15
|
|
158
|
+
: null;
|
|
159
|
+
forecast.wind.speedTrue =
|
|
160
|
+
parseInt(f.windSpeed.split(' ')[0]) / 2.237 ?? null;
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
const wd = f.windDirection
|
|
163
|
+
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
|
+
CARDINAL_POINTS[f.windDirection]
|
|
165
|
+
: 0;
|
|
166
|
+
forecast.wind.directionTrue = f.windDirection
|
|
167
|
+
? (Math.PI / 180) * wd
|
|
168
|
+
: null;
|
|
169
|
+
data.push(forecast);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return data;
|
|
173
|
+
}
|
|
174
|
+
parseNoaaWarnings(alerts) {
|
|
175
|
+
const data = [];
|
|
176
|
+
if (alerts && Array.isArray(alerts)) {
|
|
177
|
+
alerts.forEach((alert) => {
|
|
178
|
+
const warn = {
|
|
179
|
+
startTime: alert?.properties?.effective ?? null,
|
|
180
|
+
endTime: alert?.properties?.ends ?? null,
|
|
181
|
+
details: alert?.properties?.description ?? null,
|
|
182
|
+
source: alert?.properties?.senderName ?? null,
|
|
183
|
+
type: alert?.properties?.messageType ?? null
|
|
184
|
+
};
|
|
185
|
+
data.push(warn);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return data;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
exports.NOAA = NOAA;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// OpenWeather
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.OpenWeather = void 0;
|
|
5
|
+
const fetch_1 = require("./fetch");
|
|
6
|
+
const weather_1 = require("../weather");
|
|
7
|
+
class OpenWeather {
|
|
8
|
+
settings;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.settings = config;
|
|
11
|
+
}
|
|
12
|
+
getUrl(position) {
|
|
13
|
+
const api = 'https://api.openweathermap.org/data/2.5/onecall';
|
|
14
|
+
if (!this.settings.apiKey || !position) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return `${api}?lat=${position.latitude}&lon=${position.longitude}&exclude=minutely,daily&appid=${this.settings.apiKey}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
fetchData = async (position) => {
|
|
22
|
+
const url = this.getUrl(position);
|
|
23
|
+
const response = await (0, fetch_1.fetch)(url);
|
|
24
|
+
return this.parseResponse(response);
|
|
25
|
+
};
|
|
26
|
+
parseResponse = (owData) => {
|
|
27
|
+
const res = {};
|
|
28
|
+
res[weather_1.defaultStationId] = {
|
|
29
|
+
id: weather_1.defaultStationId,
|
|
30
|
+
name: 'Weather data relative to supplied position.',
|
|
31
|
+
position: {
|
|
32
|
+
latitude: owData.lat,
|
|
33
|
+
longitude: owData.lon
|
|
34
|
+
},
|
|
35
|
+
observations: this.parseOWObservations(owData),
|
|
36
|
+
forecasts: this.parseOWForecasts(owData),
|
|
37
|
+
warnings: this.parseOWWarnings(owData)
|
|
38
|
+
};
|
|
39
|
+
return res;
|
|
40
|
+
};
|
|
41
|
+
parseOWObservations(owData) {
|
|
42
|
+
//server.debug(JSON.stringify(weatherData.current))
|
|
43
|
+
const data = [];
|
|
44
|
+
let obs;
|
|
45
|
+
if (owData && owData.current) {
|
|
46
|
+
const current = owData.current;
|
|
47
|
+
obs = {
|
|
48
|
+
date: current.dt
|
|
49
|
+
? new Date(current.dt * 1000).toISOString()
|
|
50
|
+
: new Date().toISOString(),
|
|
51
|
+
description: current.weather[0].description ?? '',
|
|
52
|
+
sun: {
|
|
53
|
+
sunrise: new Date(current.sunrise * 1000).toISOString() ?? null,
|
|
54
|
+
sunset: new Date(current.sunset * 1000).toISOString() ?? null
|
|
55
|
+
},
|
|
56
|
+
horizontalVisibility: current.visibility ?? null,
|
|
57
|
+
outside: {
|
|
58
|
+
uvIndex: current.uvi ?? null,
|
|
59
|
+
cloudCover: current.clouds ?? null,
|
|
60
|
+
temperature: current.temp ?? null,
|
|
61
|
+
feelsLikeTemperature: current.feels_like ?? null,
|
|
62
|
+
dewPointTemperature: current.dew_point ?? null,
|
|
63
|
+
pressure: current.pressure ? current.pressure * 100 : null,
|
|
64
|
+
absoluteHumidity: current.humidity ?? null,
|
|
65
|
+
precipitationType: current.rain && typeof current.rain['1h'] !== 'undefined'
|
|
66
|
+
? 'rain'
|
|
67
|
+
: current.snow && typeof current.snow['1h'] !== 'undefined'
|
|
68
|
+
? 'snow'
|
|
69
|
+
: null,
|
|
70
|
+
precipitationVolume: current.rain && typeof current.rain['1h'] !== 'undefined'
|
|
71
|
+
? current.rain['1h']
|
|
72
|
+
: current.snow && typeof current.snow['1h'] !== 'undefined'
|
|
73
|
+
? current.snow['1h']
|
|
74
|
+
: null
|
|
75
|
+
},
|
|
76
|
+
water: {},
|
|
77
|
+
wind: {
|
|
78
|
+
speedTrue: current.wind_speed ?? null,
|
|
79
|
+
directionTrue: typeof current.wind_deg !== 'undefined'
|
|
80
|
+
? (Math.PI / 180) * current.wind_deg
|
|
81
|
+
: null
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
data.push(obs);
|
|
85
|
+
}
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
parseOWForecasts(owData, period = 'hourly') {
|
|
89
|
+
//server.debug(JSON.stringify(owData[period]))
|
|
90
|
+
const data = [];
|
|
91
|
+
if (owData && owData[period] && Array.isArray(owData[period])) {
|
|
92
|
+
const forecasts = owData[period];
|
|
93
|
+
forecasts.forEach((f) => {
|
|
94
|
+
const forecast = {
|
|
95
|
+
date: f.dt
|
|
96
|
+
? new Date(f.dt * 1000).toISOString()
|
|
97
|
+
: new Date().toISOString(),
|
|
98
|
+
description: f.weather[0].description ?? '',
|
|
99
|
+
sun: {},
|
|
100
|
+
outside: {},
|
|
101
|
+
water: {},
|
|
102
|
+
wind: {}
|
|
103
|
+
};
|
|
104
|
+
if (period === 'daily') {
|
|
105
|
+
forecast.sun.sunrise =
|
|
106
|
+
new Date(f.sunrise * 1000).toISOString() ?? null;
|
|
107
|
+
forecast.sun.sunset = new Date(f.sunset * 1000).toISOString() ?? null;
|
|
108
|
+
forecast.outside.minTemperature = f.temp.min ?? null;
|
|
109
|
+
forecast.outside.maxTemperature = f.temp.max ?? null;
|
|
110
|
+
forecast.outside.feelsLikeTemperature = f.feels_like.day ?? null;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
|
+
forecast.outside.feelsLikeTemperature = f.feels_like ?? null;
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
forecast.outside.temperature = f.temp ?? null;
|
|
117
|
+
}
|
|
118
|
+
forecast.outside.dewPointTemperature = f.dew_point ?? null;
|
|
119
|
+
forecast.outside.uvIndex = f.uvi ?? null;
|
|
120
|
+
forecast.outside.cloudCover = f.clouds ?? null;
|
|
121
|
+
forecast.outside.pressure =
|
|
122
|
+
typeof f.pressure !== 'undefined' ? f.pressure * 100 : null;
|
|
123
|
+
forecast.outside.absoluteHumidity = f.humidity ?? null;
|
|
124
|
+
forecast.wind.speedTrue = f.wind_speed ?? null;
|
|
125
|
+
forecast.wind.directionTrue =
|
|
126
|
+
typeof f.wind_deg !== 'undefined'
|
|
127
|
+
? (Math.PI / 180) * f.wind_deg
|
|
128
|
+
: null;
|
|
129
|
+
forecast.wind.gust = f.wind_gust ?? null;
|
|
130
|
+
if (f.rain && typeof f.rain['1h'] !== 'undefined') {
|
|
131
|
+
forecast.outside.precipitationType = 'rain';
|
|
132
|
+
forecast.outside.precipitationVolume = f.rain['1h'] ?? null;
|
|
133
|
+
}
|
|
134
|
+
else if (f.snow && typeof f.snow['1h'] !== 'undefined') {
|
|
135
|
+
forecast.outside.precipitationType = 'snow';
|
|
136
|
+
forecast.outside.precipitationVolume = f.snow['1h'] ?? null;
|
|
137
|
+
}
|
|
138
|
+
data.push(forecast);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return data;
|
|
142
|
+
}
|
|
143
|
+
parseOWWarnings(owData) {
|
|
144
|
+
//server.debug(JSON.stringify(weatherData.alerts))
|
|
145
|
+
const data = [];
|
|
146
|
+
if (owData && owData.alerts) {
|
|
147
|
+
const alerts = owData.alerts;
|
|
148
|
+
alerts.forEach((alert) => {
|
|
149
|
+
const warn = {
|
|
150
|
+
startTime: alert.start
|
|
151
|
+
? new Date(alert.start * 1000).toISOString()
|
|
152
|
+
: null,
|
|
153
|
+
endTime: alert.end
|
|
154
|
+
? new Date(alert.start * 1000).toISOString()
|
|
155
|
+
: null,
|
|
156
|
+
details: alert.description ?? null,
|
|
157
|
+
source: alert.sender_name ?? null,
|
|
158
|
+
type: alert.event ?? null
|
|
159
|
+
};
|
|
160
|
+
data.push(warn);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return data;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.OpenWeather = OpenWeather;
|
|
File without changes
|