signalk-polar-performance-plugin 0.0.14 → 0.0.16
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 +1 -1
- package/package.json +2 -1
- package/plugin/index.js +95 -10
- package/public/index.html +45 -0
package/README.md
CHANGED
|
@@ -37,12 +37,12 @@ In the plugin configuration you can toggle the following options:
|
|
|
37
37
|
- Plugin option to use SOG as boat speed
|
|
38
38
|
- Wind angle for maximum speed at this wind speed
|
|
39
39
|
- Fill up the ends of the polar diagram
|
|
40
|
+
- Visualisation of the polar diagram
|
|
40
41
|
|
|
41
42
|
### To-do list
|
|
42
43
|
- Improved interpolation
|
|
43
44
|
- Make moment to do calculation smarter/configurable
|
|
44
45
|
- API to see JSON of polar
|
|
45
|
-
- Visualisation of the polar diagram
|
|
46
46
|
- Extrapolation of polar data
|
|
47
47
|
- Create polar from live data
|
|
48
48
|
-- Save polar info to file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-polar-performance-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "A plugin that calculates performance information based on a (CSV) polar diagram.",
|
|
5
5
|
"main": "plugin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/htool/signalk-polar-performance-plugin#readme",
|
|
19
19
|
"keywords": [
|
|
20
|
+
"signalk-webapp",
|
|
20
21
|
"signalk-node-server-plugin",
|
|
21
22
|
"signalk-category-instruments",
|
|
22
23
|
"signalk-category-chart-plotters"
|
package/plugin/index.js
CHANGED
|
@@ -28,6 +28,10 @@ module.exports = function (app) {
|
|
|
28
28
|
type: 'boolean',
|
|
29
29
|
title: 'Enable sending Target TWA (performance.targetAngle)'
|
|
30
30
|
},
|
|
31
|
+
tackTrue: {
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
title: 'Enable calculating Opposite Tack True (performance.tackTrue)'
|
|
34
|
+
},
|
|
31
35
|
optimumWindAngle: {
|
|
32
36
|
type: 'boolean',
|
|
33
37
|
title: 'Enable calculation of Optimum Wind Angle (difference between TWA and beat/run angle (depends on beat/run angle)'
|
|
@@ -62,9 +66,23 @@ module.exports = function (app) {
|
|
|
62
66
|
// Load polar table
|
|
63
67
|
var polar = csvToPolarObject(options.csvTable)
|
|
64
68
|
app.debug('polar: %s', JSON.stringify(polar))
|
|
69
|
+
|
|
70
|
+
plugin.registerWithRouter = function(router) {
|
|
71
|
+
// Will appear here; plugins/signalk-polar-performance-plugin/
|
|
72
|
+
app.debug("registerWithRouter")
|
|
73
|
+
router.get("/polar", (req, res) => {
|
|
74
|
+
res.contentType("application/json")
|
|
75
|
+
res.send(JSON.stringify(polar))
|
|
76
|
+
})
|
|
77
|
+
router.get("/chartData", (req, res) => {
|
|
78
|
+
res.contentType("application/json")
|
|
79
|
+
res.send(JSON.stringify(getChartData()))
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
65
83
|
|
|
66
84
|
// Global variables
|
|
67
|
-
var BSP, STW, TWA, TWS, port // Angles in rad, speed in m/s
|
|
85
|
+
var BSP, STW, TWA, targetTWA, TWS, HDG, port // Angles in rad, speed in m/s
|
|
68
86
|
var halfPi = Math.PI / 2
|
|
69
87
|
|
|
70
88
|
// Subscribe to paths
|
|
@@ -93,6 +111,12 @@ module.exports = function (app) {
|
|
|
93
111
|
policy: 'instant'
|
|
94
112
|
})
|
|
95
113
|
}
|
|
114
|
+
if (options.tackTrue == true) {
|
|
115
|
+
localSubscription.subscribe.push({
|
|
116
|
+
path: 'navigation.headingTrue',
|
|
117
|
+
policy: 'instant'
|
|
118
|
+
})
|
|
119
|
+
}
|
|
96
120
|
|
|
97
121
|
app.subscriptionmanager.subscribe(
|
|
98
122
|
localSubscription,
|
|
@@ -120,6 +144,9 @@ module.exports = function (app) {
|
|
|
120
144
|
SOG = delta.value
|
|
121
145
|
BSP = SOG
|
|
122
146
|
// app.debug('speedThroughWater (STW): %d', STW)
|
|
147
|
+
} else if (delta.path == 'navigation.headingTrue') {
|
|
148
|
+
HDG = delta.value
|
|
149
|
+
// app.debug('heading (HDG): %d', HDG)
|
|
123
150
|
} else if (delta.path == 'environment.wind.speedTrue') {
|
|
124
151
|
TWS = delta.value
|
|
125
152
|
// app.debug('environment.wind.speedTrue (TWS): %d', TWS)
|
|
@@ -186,6 +213,11 @@ module.exports = function (app) {
|
|
|
186
213
|
values.push({path: 'performance.maxSpeedAngle', value: roundDec(perfObj.maxSpeedAngle)})
|
|
187
214
|
metas.push({path: 'performance.maxSpeedAngle', value: {"units": "rad"}})
|
|
188
215
|
}
|
|
216
|
+
if (options.tackTrue == true) {
|
|
217
|
+
if (typeof perfObj.tackTrue != 'undefined') {
|
|
218
|
+
values.push({path: 'performance.tackTrue', value: roundDec(perfObj.tackTrue)})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
189
221
|
|
|
190
222
|
app.debug('sendUpdates: %s', JSON.stringify(values))
|
|
191
223
|
app.handleMessage(plugin.id, {
|
|
@@ -218,6 +250,7 @@ module.exports = function (app) {
|
|
|
218
250
|
let beatLower = polar[indexTWS]['Beat angle']
|
|
219
251
|
let beatUpper = polar[indexTWS+1]['Beat angle']
|
|
220
252
|
performance.beatAngle = beatLower + ((beatUpper - beatLower) * twsGapRatio)
|
|
253
|
+
targetTWA = performance.beatAngle
|
|
221
254
|
}
|
|
222
255
|
// Calculate optimum wind angle (B&G thing)
|
|
223
256
|
if (options.optimumWindAngle == true) {
|
|
@@ -239,6 +272,7 @@ module.exports = function (app) {
|
|
|
239
272
|
let runUpper = polar[indexTWS+1]['Run angle']
|
|
240
273
|
//app.debug('runLower: %s runUpper: %s', runLower, runUpper)
|
|
241
274
|
performance.runAngle = runLower + ((runUpper - runLower) * twsGapRatio)
|
|
275
|
+
targetTWA = performance.runAngle
|
|
242
276
|
}
|
|
243
277
|
if (options.optimumWindAngle == true) {
|
|
244
278
|
if (typeof performance.runAngle != 'undefined') {
|
|
@@ -254,6 +288,21 @@ module.exports = function (app) {
|
|
|
254
288
|
performance.runVMG = VMGLower + ((VMGUpper - VMGLower) * twsGapRatio)
|
|
255
289
|
}
|
|
256
290
|
}
|
|
291
|
+
// Calculate opposite Tack True
|
|
292
|
+
// app.debug('tackTrue: port: %d HDG: %d targetTWA: %d', port, radToDeg(HDG), radToDeg(targetTWA))
|
|
293
|
+
if (port) {
|
|
294
|
+
let tackTrue = HDG - targetTWA
|
|
295
|
+
if (tackTrue < 0) {
|
|
296
|
+
tackTrue = tackTrue + (2*Math.PI)
|
|
297
|
+
}
|
|
298
|
+
performance.tackTrue = tackTrue
|
|
299
|
+
} else {
|
|
300
|
+
let tackTrue = HDG + targetTWA
|
|
301
|
+
if (tackTrue > (2*Math.PI)) {
|
|
302
|
+
tackTrue = tackTrue - (2*Math.PI)
|
|
303
|
+
}
|
|
304
|
+
performance.tackTrue = tackTrue
|
|
305
|
+
}
|
|
257
306
|
// Calculate polar target boat speed
|
|
258
307
|
|
|
259
308
|
// Interpolate Define the 4 near data points
|
|
@@ -420,9 +469,9 @@ module.exports = function (app) {
|
|
|
420
469
|
for (let index = 0; index < polar.length-1; index++) {
|
|
421
470
|
let tws = polar[index].tws
|
|
422
471
|
let beatVMG = 0
|
|
423
|
-
let
|
|
472
|
+
let beatElement = 0
|
|
424
473
|
let runVMG = 0
|
|
425
|
-
let
|
|
474
|
+
let runElement = 0
|
|
426
475
|
|
|
427
476
|
let twaArray = polar[index].twa
|
|
428
477
|
for (let element = 0; element < twaArray.length-1; element++) {
|
|
@@ -439,12 +488,12 @@ module.exports = function (app) {
|
|
|
439
488
|
}
|
|
440
489
|
}
|
|
441
490
|
}
|
|
442
|
-
app.debug('beatVMG for %d is %d (angel %d)', tws, twaArray[
|
|
443
|
-
app.debug(' runVMG for %d is %d (angel %d)', tws, twaArray[
|
|
444
|
-
polar[index]['Beat angle'] = twaArray[
|
|
445
|
-
polar[index]['Beat VMG'] = twaArray[
|
|
446
|
-
polar[index]['Run angle'] = twaArray[
|
|
447
|
-
polar[index]['Run VMG'] = twaArray[
|
|
491
|
+
app.debug('beatVMG for %d is %d (angel %d)', tws, twaArray[beatElement].vmg, twaArray[beatElement].twa)
|
|
492
|
+
app.debug(' runVMG for %d is %d (angel %d)', tws, twaArray[runElement].vmg, twaArray[runElement].twa)
|
|
493
|
+
polar[index]['Beat angle'] = twaArray[beatElement].twa
|
|
494
|
+
polar[index]['Beat VMG'] = twaArray[beatElement].vmg
|
|
495
|
+
polar[index]['Run angle'] = twaArray[runElement].twa
|
|
496
|
+
polar[index]['Run VMG'] = twaArray[runElement].vmg
|
|
448
497
|
}
|
|
449
498
|
|
|
450
499
|
}
|
|
@@ -462,7 +511,8 @@ module.exports = function (app) {
|
|
|
462
511
|
// Now put some extra values at the beginning
|
|
463
512
|
var topArray = []
|
|
464
513
|
for (let angle = 0; angle < lowTWA; angle = angle + degToRad(5)) {
|
|
465
|
-
let tbs = (angle / lowTWA) * Math.pow(Math.cos((-1*lowTWA + angle)*
|
|
514
|
+
let tbs = (angle / lowTWA) * Math.pow(Math.cos((-1*lowTWA + angle)*2),2) * lowTBS
|
|
515
|
+
if (tbs < 0 ) { tbs = 0 }
|
|
466
516
|
let Obj = {'twa': angle, 'tbs': tbs}
|
|
467
517
|
topArray.push(Obj)
|
|
468
518
|
}
|
|
@@ -492,6 +542,41 @@ module.exports = function (app) {
|
|
|
492
542
|
// app.debug(JSON.stringify(polar))
|
|
493
543
|
return (polar)
|
|
494
544
|
}
|
|
545
|
+
|
|
546
|
+
function getChartData () {
|
|
547
|
+
const color = ["#0000FF","#3300FF","#6600FF","#9900FF","#CC00FF","#FF00FF","#0033FF","#3333FF","#6633FF","#9933FF","#CC33FF","#FF33FF","#0066FF","#3366FF","#6666FF","#9966FF","#CC66FF","#FF66FF"]
|
|
548
|
+
|
|
549
|
+
var data = {
|
|
550
|
+
labels: [],
|
|
551
|
+
datasets: []
|
|
552
|
+
}
|
|
553
|
+
for (let angle = 0; angle <= 180; angle += 5) {
|
|
554
|
+
data.labels.push(angle)
|
|
555
|
+
}
|
|
556
|
+
for (let index = 0; index < polar.length -1; index++) {
|
|
557
|
+
// Add x axis label TWS
|
|
558
|
+
let tws = msToKts(polar[index].tws).toFixed(0)
|
|
559
|
+
let twaArray = polar[index].twa
|
|
560
|
+
data.datasets[index] = {
|
|
561
|
+
data: [],
|
|
562
|
+
pointRadius: [],
|
|
563
|
+
borderColor: color[index],
|
|
564
|
+
fill: false,
|
|
565
|
+
parsing: false,
|
|
566
|
+
label: tws + ' kts'
|
|
567
|
+
}
|
|
568
|
+
for (let twaIndex = 0; twaIndex <= twaArray.length-1; twaIndex++) {
|
|
569
|
+
let twaObj = twaArray[twaIndex]
|
|
570
|
+
let radius = 2
|
|
571
|
+
if (twaObj.twa == polar[index]['Beat angle'] || twaObj.twa == polar[index]['Run angle']) {
|
|
572
|
+
radius = 5
|
|
573
|
+
}
|
|
574
|
+
data.datasets[index].data.push({x: Math.round(radToDeg(twaObj.twa)), y: Number(msToKts(twaObj.tbs).toFixed(2))})
|
|
575
|
+
data.datasets[index].pointRadius.push(radius)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return data
|
|
579
|
+
}
|
|
495
580
|
}
|
|
496
581
|
|
|
497
582
|
plugin.stop = function () {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
|
|
4
|
+
<script type='text/javascript' src='/jquery/dist/jquery.min.js'></script>
|
|
5
|
+
<title>Polar performance</title>
|
|
6
|
+
<body>
|
|
7
|
+
|
|
8
|
+
<canvas id="myChart" style="width:100%;max-width:1200px"></canvas>
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
$.getJSON("/plugins/signalk-polar-performance-plugin/chartData", function(json) {
|
|
12
|
+
showChart(json)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
function showChart (data) {
|
|
16
|
+
console.log(data)
|
|
17
|
+
|
|
18
|
+
new Chart("myChart", {
|
|
19
|
+
type: "scatter",
|
|
20
|
+
data: data,
|
|
21
|
+
options: {
|
|
22
|
+
legend: {display: true},
|
|
23
|
+
spanGaps: true,
|
|
24
|
+
scales: {
|
|
25
|
+
x: {
|
|
26
|
+
type: 'linear'
|
|
27
|
+
},
|
|
28
|
+
xAxes: [{
|
|
29
|
+
scaleLabel: {
|
|
30
|
+
display: true,
|
|
31
|
+
labelString: 'True wind angle (TWA)'
|
|
32
|
+
}
|
|
33
|
+
}],
|
|
34
|
+
yAxes: [{
|
|
35
|
+
scaleLabel: {
|
|
36
|
+
display: true,
|
|
37
|
+
labelString: 'Target boat speed (POL SPD)'
|
|
38
|
+
}
|
|
39
|
+
}],
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|