signalk-polar-performance-plugin 0.0.57 → 0.0.59
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/package.json +1 -1
- package/plugin/index.js +126 -45
package/package.json
CHANGED
package/plugin/index.js
CHANGED
|
@@ -243,89 +243,132 @@ module.exports = function (app) {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
if (BSP) {
|
|
246
|
-
addValue('performance.boatSpeedDamped', BSP, {
|
|
246
|
+
addValue('performance.boatSpeedDamped', BSP, {
|
|
247
|
+
units: 'm/s',
|
|
248
|
+
description: 'Boat speed after applying damping factor (see Polar Performance Plugin settings).'
|
|
249
|
+
})
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
if (TWA) {
|
|
250
253
|
addValue('environment.wind.angleTrueWaterDamped', TWA * port, {
|
|
251
|
-
units: 'rad'
|
|
254
|
+
units: 'rad',
|
|
255
|
+
description: 'True Wind Angle after applying damping factor, negative to port (see Polar Performance Plugin settings).'
|
|
252
256
|
})
|
|
253
257
|
}
|
|
254
258
|
|
|
255
259
|
if (typeof perfObj.beatAngle !== 'undefined') {
|
|
256
260
|
if (options.beatAngle === true) {
|
|
257
|
-
addValue('performance.beatAngle', perfObj.beatAngle * port
|
|
261
|
+
addValue('performance.beatAngle', perfObj.beatAngle * port, {
|
|
262
|
+
units: 'rad',
|
|
263
|
+
description: 'The optimal beat/upwind angle for current TWS, negative to port.'
|
|
264
|
+
})
|
|
258
265
|
}
|
|
259
266
|
if (options.targetTWA === true) {
|
|
260
|
-
addValue('performance.targetAngle', perfObj.beatAngle * port
|
|
267
|
+
addValue('performance.targetAngle', perfObj.beatAngle * port, {
|
|
268
|
+
units: 'rad',
|
|
269
|
+
description: 'The combined and automatic switching optimal beat or run angle for current TWS, negative to port.'
|
|
270
|
+
})
|
|
261
271
|
}
|
|
262
272
|
}
|
|
263
273
|
|
|
264
|
-
if (typeof perfObj.
|
|
265
|
-
|
|
274
|
+
if (typeof perfObj.runAngle !== 'undefined') {
|
|
275
|
+
if (options.beatAngle === true) {
|
|
276
|
+
addValue('performance.gybeAngle', perfObj.runAngle * port, {
|
|
277
|
+
units: 'rad',
|
|
278
|
+
description: 'The optimal run/downwind angle for current TWS, negative to port.'
|
|
279
|
+
})
|
|
280
|
+
}
|
|
266
281
|
if (options.targetTWA === true) {
|
|
267
|
-
addValue('performance.
|
|
268
|
-
units: '
|
|
282
|
+
addValue('performance.targetAngle', perfObj.runAngle * port, {
|
|
283
|
+
units: 'rad',
|
|
284
|
+
description: 'The combined and automatic switching optimal beat or run angle for current TWS, negative to port.'
|
|
269
285
|
})
|
|
270
286
|
}
|
|
271
287
|
}
|
|
272
288
|
|
|
273
|
-
if (typeof perfObj.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
if (typeof perfObj.beatVMG !== 'undefined') {
|
|
290
|
+
addValue('performance.beatAngleVelocityMadeGood', perfObj.beatVMG, {
|
|
291
|
+
units: 'm/s',
|
|
292
|
+
description: 'The beat/upwind Velocity Made Good for current boat speed and heading.'
|
|
293
|
+
})
|
|
277
294
|
if (options.targetTWA === true) {
|
|
278
|
-
addValue('performance.
|
|
295
|
+
addValue('performance.targetVelocityMadeGood', perfObj.beatVMG, {
|
|
296
|
+
units: 'm/s',
|
|
297
|
+
description: 'The combined and automatic switching beat or run Velocity Made Good for current boat speed and heading.'
|
|
298
|
+
})
|
|
279
299
|
}
|
|
280
300
|
}
|
|
281
301
|
|
|
282
302
|
if (typeof perfObj.runVMG !== 'undefined') {
|
|
283
|
-
addValue('performance.gybeAngleVelocityMadeGood', perfObj.runVMG
|
|
303
|
+
addValue('performance.gybeAngleVelocityMadeGood', perfObj.runVMG, {
|
|
304
|
+
units: 'm/s',
|
|
305
|
+
description: 'The run/downwind Velocity Made Good for current boat speed and heading.'
|
|
306
|
+
})
|
|
284
307
|
if (options.targetTWA === true) {
|
|
285
308
|
addValue('performance.targetVelocityMadeGood', perfObj.runVMG, {
|
|
286
|
-
units: 'm/s'
|
|
309
|
+
units: 'm/s',
|
|
310
|
+
description: 'The combined and automatic switching beat or run Velocity Made Good for current TWS and TWA.'
|
|
287
311
|
})
|
|
288
312
|
}
|
|
289
313
|
}
|
|
290
314
|
|
|
291
315
|
if (typeof perfObj.optimumWindAngle !== 'undefined') {
|
|
292
316
|
addValue('performance.optimumWindAngle', perfObj.optimumWindAngle, {
|
|
293
|
-
units: 'rad'
|
|
317
|
+
units: 'rad',
|
|
318
|
+
description: 'The optimum wind angle, negative to port (diff between TWA and environment.wind.directionTrue).'
|
|
294
319
|
})
|
|
295
320
|
}
|
|
296
321
|
|
|
297
322
|
if (typeof perfObj.targetSpeed !== 'undefined') {
|
|
298
|
-
addValue('performance.targetSpeed', perfObj.targetSpeed
|
|
323
|
+
addValue('performance.targetSpeed', perfObj.targetSpeed, {
|
|
324
|
+
units: 'm/s',
|
|
325
|
+
description: 'Target boat speed based on current TWA.'
|
|
326
|
+
}
|
|
327
|
+
)
|
|
299
328
|
}
|
|
300
329
|
|
|
301
330
|
if (typeof perfObj.polarSpeed !== 'undefined') {
|
|
302
|
-
addValue('performance.polarSpeed', perfObj.polarSpeed
|
|
303
|
-
|
|
331
|
+
addValue('performance.polarSpeed', perfObj.polarSpeed, {
|
|
332
|
+
units: 'm/s',
|
|
333
|
+
description: 'The polar chart boat speed as per the polar chart for current TWS and TWA.'
|
|
334
|
+
})
|
|
335
|
+
addValue('performance.polarSpeedRatio', perfObj.polarSpeedRatio, {
|
|
336
|
+
units: 'ratio',
|
|
337
|
+
description: 'The ratio between actual boat speed and polar chart boat speed for current TWS and TWA.'
|
|
338
|
+
})
|
|
304
339
|
if (typeof perfObj.velocityMadeGood !== 'undefined') {
|
|
305
|
-
addValue('performance.velocityMadeGood', perfObj.velocityMadeGood
|
|
306
|
-
|
|
307
|
-
'
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
340
|
+
addValue('performance.velocityMadeGood', perfObj.velocityMadeGood, {
|
|
341
|
+
units: 'm/s',
|
|
342
|
+
description: 'The actual Velocity Made Good based on current heading and boat speed.'
|
|
343
|
+
})
|
|
344
|
+
addValue('performance.polarVelocityMadeGood', perfObj.polarVelocityMadeGood, {
|
|
345
|
+
units: 'm/s',
|
|
346
|
+
description: 'The polar chart Velocity Made Good indicated in the polar for current TWS and TWA.'
|
|
347
|
+
})
|
|
348
|
+
addValue('performance.polarVelocityMadeGoodRatio', perfObj.polarVelocityMadeGoodRatio, {
|
|
349
|
+
units: 'ratio',
|
|
350
|
+
description: 'The ratio between actual Velocity Made Good and polar chart indicated Velocity Made Good for current TWS and TWA.'
|
|
351
|
+
})
|
|
316
352
|
}
|
|
317
353
|
}
|
|
318
354
|
|
|
319
355
|
if (typeof perfObj.maxSpeed !== 'undefined') {
|
|
320
|
-
addValue('performance.maxSpeed', perfObj.maxSpeed, {
|
|
356
|
+
addValue('performance.maxSpeed', perfObj.maxSpeed, {
|
|
357
|
+
units: 'm/s',
|
|
358
|
+
description: 'Maximum boat speed as per polar chart.'
|
|
359
|
+
})
|
|
321
360
|
addValue('performance.maxSpeedAngle', perfObj.maxSpeedAngle, {
|
|
322
|
-
units: 'rad'
|
|
361
|
+
units: 'rad',
|
|
362
|
+
description: 'The angle to achieve maximum boat speed (not the VMG), based on current TWS, negative to port.'
|
|
323
363
|
})
|
|
324
364
|
}
|
|
325
365
|
|
|
326
366
|
if (options.tackTrue === true) {
|
|
327
367
|
if (typeof perfObj.tackTrue !== 'undefined') {
|
|
328
|
-
addValue('performance.tackTrue', perfObj.tackTrue
|
|
368
|
+
addValue('performance.tackTrue', perfObj.tackTrue, {
|
|
369
|
+
units: 'rad',
|
|
370
|
+
description: 'The Opposite Tack\'s heading relative to True North, based on current TWS.'
|
|
371
|
+
})
|
|
329
372
|
}
|
|
330
373
|
}
|
|
331
374
|
|
|
@@ -429,7 +472,7 @@ module.exports = function (app) {
|
|
|
429
472
|
}
|
|
430
473
|
// Calculate opposite Tack True
|
|
431
474
|
// app.debug('tackTrue: port: %d HDG: %d targetTWA: %d', port, radToDeg(HDG), radToDeg(targetTWA))
|
|
432
|
-
if (port) {
|
|
475
|
+
if (port < 0) {
|
|
433
476
|
let tackTrue = HDG - targetTWA
|
|
434
477
|
if (tackTrue < 0) {
|
|
435
478
|
tackTrue = tackTrue + 2 * Math.PI
|
|
@@ -545,15 +588,39 @@ module.exports = function (app) {
|
|
|
545
588
|
var polar = {}
|
|
546
589
|
var beat = false
|
|
547
590
|
|
|
591
|
+
function upsertTwa (polarIndex, twaObj, angleDeg) {
|
|
592
|
+
const twaArray = polar[polarIndex].twa
|
|
593
|
+
const existingIndex = twaArray.findIndex(entry => entry.twa === twaObj.twa)
|
|
594
|
+
if (existingIndex >= 0) {
|
|
595
|
+
app.error(
|
|
596
|
+
'Duplicate TWA %s deg for TWS %s kts, keeping last value',
|
|
597
|
+
angleDeg,
|
|
598
|
+
msToKts(polar[polarIndex].tws).toFixed(1)
|
|
599
|
+
)
|
|
600
|
+
twaArray[existingIndex] = twaObj
|
|
601
|
+
} else {
|
|
602
|
+
twaArray.push(twaObj)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
548
606
|
// Create array of arrays from csv
|
|
549
607
|
csv.split('\n').forEach(row => {
|
|
550
|
-
|
|
608
|
+
const trimmedRow = row.trim()
|
|
609
|
+
if (!trimmedRow) {
|
|
610
|
+
return
|
|
611
|
+
}
|
|
612
|
+
const cells = trimmedRow.split(';').map(cell => cell.trim())
|
|
613
|
+
if (!cells[0]) {
|
|
614
|
+
return
|
|
615
|
+
}
|
|
616
|
+
csvArray.push(cells)
|
|
551
617
|
})
|
|
552
618
|
// app.debug('csvArray: %s', JSON.stringify(csvArray))
|
|
553
619
|
|
|
554
620
|
// Populate the polar object from CSV rows
|
|
555
621
|
csvArray.forEach(row => {
|
|
556
|
-
|
|
622
|
+
const rowKey = row[0].trim().toLowerCase()
|
|
623
|
+
if (rowKey == 'twa/tws') {
|
|
557
624
|
// The row with TWS columns
|
|
558
625
|
// Create empty TWS objects in polar object
|
|
559
626
|
app.debug('First row with TWS columns')
|
|
@@ -566,7 +633,8 @@ module.exports = function (app) {
|
|
|
566
633
|
} else if (row.filter(i => i === '0').length > 1) {
|
|
567
634
|
app.debug('beat and run angles are included')
|
|
568
635
|
// Beat / Run angle
|
|
569
|
-
let
|
|
636
|
+
let angleDeg = Number(row[0])
|
|
637
|
+
let angle = degToRad(angleDeg)
|
|
570
638
|
let angleName
|
|
571
639
|
let VMGName
|
|
572
640
|
app.debug('angle < halfPi %d < %d', angle, halfPi)
|
|
@@ -581,17 +649,23 @@ module.exports = function (app) {
|
|
|
581
649
|
}
|
|
582
650
|
|
|
583
651
|
for (let index = 0; index < row.length - 1; index++) {
|
|
584
|
-
if (row[index + 1]
|
|
652
|
+
if (!row[index + 1]) {
|
|
653
|
+
continue
|
|
654
|
+
}
|
|
655
|
+
const tbsCell = Number(row[index + 1])
|
|
656
|
+
if (Number.isNaN(tbsCell) || tbsCell === 0) {
|
|
657
|
+
continue
|
|
658
|
+
}
|
|
659
|
+
if (tbsCell != 0) {
|
|
585
660
|
polar[index][angleName] = angle
|
|
586
|
-
let tbs =
|
|
587
|
-
ktsToMs(Number(row[index + 1])) * (options.perfAdjust || 1)
|
|
661
|
+
let tbs = ktsToMs(tbsCell) * (options.perfAdjust || 1)
|
|
588
662
|
let vmg = tbs * Math.abs(Math.cos(angle))
|
|
589
663
|
polar[index][VMGName] = roundDec(vmg)
|
|
590
664
|
if (typeof polar[index]['twa'] == 'undefined') {
|
|
591
665
|
polar[index]['twa'] = []
|
|
592
666
|
}
|
|
593
667
|
let Obj = { twa: angle, tbs: tbs, vmg: vmg }
|
|
594
|
-
|
|
668
|
+
upsertTwa(index, Obj, angleDeg)
|
|
595
669
|
app.debug('Finding max speed')
|
|
596
670
|
if (
|
|
597
671
|
typeof polar[index]['Max speed'] == 'undefined' ||
|
|
@@ -605,17 +679,24 @@ module.exports = function (app) {
|
|
|
605
679
|
}
|
|
606
680
|
} else {
|
|
607
681
|
// Normal line
|
|
608
|
-
let
|
|
682
|
+
let angleDeg = Number(row[0])
|
|
683
|
+
let angle = degToRad(angleDeg) // CSV deg to internal rad
|
|
609
684
|
for (let index = 0; index < row.length - 1; index++) {
|
|
610
685
|
if (typeof polar[index].twa == 'undefined') {
|
|
611
686
|
polar[index].twa = []
|
|
612
687
|
}
|
|
613
|
-
|
|
614
|
-
|
|
688
|
+
if (!row[index + 1]) {
|
|
689
|
+
continue
|
|
690
|
+
}
|
|
691
|
+
const tbsCell = Number(row[index + 1])
|
|
692
|
+
if (Number.isNaN(tbsCell)) {
|
|
693
|
+
continue
|
|
694
|
+
}
|
|
695
|
+
let tbs = ktsToMs(tbsCell) * (options.perfAdjust || 1)
|
|
615
696
|
let vmg = tbs * Math.abs(Math.cos(angle))
|
|
616
697
|
let Obj = { twa: angle, tbs: tbs, vmg: vmg }
|
|
617
698
|
// app.debug('Adding Obj: %s', JSON.stringify(Obj))
|
|
618
|
-
|
|
699
|
+
upsertTwa(index, Obj, angleDeg)
|
|
619
700
|
// See if we need to set/overwrite beat/run angle
|
|
620
701
|
|
|
621
702
|
// See if this a new Max speed
|