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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/plugin/index.js +126 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-polar-performance-plugin",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "description": "A plugin that calculates performance information based on a (CSV) polar diagram.",
5
5
  "main": "plugin/index.js",
6
6
  "scripts": {
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, { units: 'm/s' })
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.beatVMG !== 'undefined') {
265
- addValue('performance.beatAngleVelocityMadeGood', perfObj.beatVMG)
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.targetVelocityMadeGood', perfObj.beatVMG, {
268
- units: 'm/s'
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.runAngle !== 'undefined') {
274
- if (options.beatAngle === true) {
275
- addValue('performance.gybeAngle', perfObj.runAngle * port)
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.targetAngle', perfObj.runAngle * port)
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
- addValue('performance.polarSpeedRatio', perfObj.polarSpeedRatio)
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
- addValue(
307
- 'performance.polarVelocityMadeGood',
308
- perfObj.polarVelocityMadeGood,
309
- { units: 'm/s' }
310
- )
311
- addValue(
312
- 'performance.polarVelocityMadeGoodRatio',
313
- perfObj.polarVelocityMadeGoodRatio,
314
- { units: 'ratio' }
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, { units: 'm/s' })
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
- csvArray.push(row.split(';'))
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
- if (row[0] == 'twa/tws') {
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 angle = degToRad(Number(row[0]))
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] != 0) {
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
- polar[index]['twa'] = polar[index]['twa'].concat(Obj)
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 angle = degToRad(Number(row[0])) // CSV deg to internal rad
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
- let tbs =
614
- ktsToMs(Number(row[index + 1])) * (options.perfAdjust || 1)
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
- polar[index]['twa'] = polar[index]['twa'].concat(Obj)
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