signalk-polar-performance-plugin 0.0.13 → 0.0.14

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 CHANGED
@@ -17,8 +17,11 @@ The polar diagram can be configured through CSV notation as used on [ORC sailboa
17
17
 
18
18
  ### Plugin options
19
19
  In the plugin configuration you can toggle the following options:
20
- - Enable calculation of beat/upwind and run/gybe/downwind angle
21
- - Enable calculation of beat/upwind and run/gybe/downwind VMG
20
+ - Enable calculation/sending of beat/upwind and run/gybe/downwind angle
21
+ - Enable calculation/sending of beat/upwind and run/gybe/downwind VMG
22
+ - Enable sending Target TWA
23
+ - Enable calculation of Optimum Wind Angle (difference between TWA and beat/run angle (depends on beat/run angle)
24
+ - Enable sending of maximum speed angle and boat speed for a given TWS
22
25
  - Enable calculation of Optimum Wind Angle (difference between TWA and beat/run angle (depends on beat/run angle)
23
26
 
24
27
  ## Calculated performance data
@@ -40,6 +43,7 @@ In the plugin configuration you can toggle the following options:
40
43
  - Make moment to do calculation smarter/configurable
41
44
  - API to see JSON of polar
42
45
  - Visualisation of the polar diagram
46
+ - Extrapolation of polar data
43
47
  - Create polar from live data
44
48
  -- Save polar info to file
45
49
  -- Save new record speed for angle in polar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-polar-performance-plugin",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
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
@@ -18,11 +18,15 @@ module.exports = function (app) {
18
18
  properties: {
19
19
  beatAngle: {
20
20
  type: 'boolean',
21
- title: 'Enable calculation of beat/upwind and run/gybe/downwind angle'
21
+ title: 'Enable calculation/sending of beat/upwind and run/gybe/downwind angle'
22
22
  },
23
23
  beatVMG: {
24
24
  type: 'boolean',
25
- title: 'Enable calculation of beat/upwind and run/gybe/downwind VMG'
25
+ title: 'Enable calculation/sending of beat/upwind and run/gybe/downwind VMG'
26
+ },
27
+ targetTWA: {
28
+ type: 'boolean',
29
+ title: 'Enable sending Target TWA (performance.targetAngle)'
26
30
  },
27
31
  optimumWindAngle: {
28
32
  type: 'boolean',
@@ -136,22 +140,34 @@ module.exports = function (app) {
136
140
  let values = []
137
141
  let metas = []
138
142
  if (typeof perfObj.beatAngle != 'undefined') {
139
- values.push({path: 'performance.beatAngle', value: roundDec(perfObj.beatAngle)})
140
- values.push({path: 'performance.targetAngle', value: roundDec(perfObj.beatAngle)})
143
+ if (options.beatAngle == true) {
144
+ values.push({path: 'performance.beatAngle', value: roundDec(perfObj.beatAngle)})
145
+ }
146
+ if (options.targetTWA == true) {
147
+ values.push({path: 'performance.targetAngle', value: roundDec(perfObj.beatAngle)})
148
+ }
141
149
  }
142
150
  if (typeof perfObj.beatVMG != 'undefined') {
143
151
  values.push({path: 'performance.beatAngleVelocityMadeGood', value: roundDec(perfObj.beatVMG)})
144
- values.push({path: 'performance.targetVelocityMadeGood', value: roundDec(perfObj.beatVMG)})
145
- metas.push({path: 'performance.targetVelocityMadeGood', value: {"units": "rad"}})
152
+ if (options.targetTWA == true) {
153
+ values.push({path: 'performance.targetVelocityMadeGood', value: roundDec(perfObj.beatVMG)})
154
+ metas.push({path: 'performance.targetVelocityMadeGood', value: {"units": "rad"}})
155
+ }
146
156
  }
147
157
  if (typeof perfObj.runAngle != 'undefined') {
148
- values.push({path: 'performance.gybeAngle', value: roundDec(perfObj.runAngle)})
149
- values.push({path: 'performance.targetAngle', value: roundDec(perfObj.runAngle)})
158
+ if (options.beatAngle == true) {
159
+ values.push({path: 'performance.gybeAngle', value: roundDec(perfObj.runAngle)})
160
+ }
161
+ if (options.targetTWA == true) {
162
+ values.push({path: 'performance.targetAngle', value: roundDec(perfObj.runAngle)})
163
+ }
150
164
  }
151
165
  if (typeof perfObj.runVMG != 'undefined') {
152
166
  values.push({path: 'performance.gybeAngleVelocityMadeGood', value: roundDec(perfObj.runVMG)})
153
- values.push({path: 'performance.targetVelocityMadeGood', value: roundDec(perfObj.runVMG)})
154
- metas.push({path: 'performance.targetVelocityMadeGood', value: {"units": "rad"}})
167
+ if (options.targetTWA == true) {
168
+ values.push({path: 'performance.targetVelocityMadeGood', value: roundDec(perfObj.runVMG)})
169
+ metas.push({path: 'performance.targetVelocityMadeGood', value: {"units": "rad"}})
170
+ }
155
171
  }
156
172
  if (typeof perfObj.optimumWindAngle != 'undefined') {
157
173
  values.push({path: 'performance.optimumWindAngle', value: roundDec(perfObj.optimumWindAngle)})
@@ -197,13 +213,11 @@ module.exports = function (app) {
197
213
  // Calculate beat/run angle
198
214
  if (TWA < halfPi) {
199
215
  // app.debug('Upwind')
200
- if (options.beatAngle == true) {
201
- // Take beat angle
202
- if (typeof polar[indexTWS]['Beat angle'] != 'undefined') {
203
- let beatLower = polar[indexTWS]['Beat angle']
204
- let beatUpper = polar[indexTWS+1]['Beat angle']
205
- performance.beatAngle = beatLower + ((beatUpper - beatLower) * twsGapRatio)
206
- }
216
+ // Take beat angle
217
+ if (typeof polar[indexTWS]['Beat angle'] != 'undefined') {
218
+ let beatLower = polar[indexTWS]['Beat angle']
219
+ let beatUpper = polar[indexTWS+1]['Beat angle']
220
+ performance.beatAngle = beatLower + ((beatUpper - beatLower) * twsGapRatio)
207
221
  }
208
222
  // Calculate optimum wind angle (B&G thing)
209
223
  if (options.optimumWindAngle == true) {
@@ -212,23 +226,19 @@ module.exports = function (app) {
212
226
  }
213
227
  }
214
228
  // Calculate beat VMG
215
- if (options.beatVMG == true) {
216
- if (typeof polar[indexTWS]['Beat VMG'] != 'undefined') {
217
- let VMGLower = polar[indexTWS]['Beat VMG']
218
- let VMGUpper = polar[indexTWS+1]['Beat VMG']
219
- performance.beatVMG = VMGLower + ((VMGUpper - VMGLower) * twsGapRatio)
220
- }
229
+ if (typeof polar[indexTWS]['Beat VMG'] != 'undefined') {
230
+ let VMGLower = polar[indexTWS]['Beat VMG']
231
+ let VMGUpper = polar[indexTWS+1]['Beat VMG']
232
+ performance.beatVMG = VMGLower + ((VMGUpper - VMGLower) * twsGapRatio)
221
233
  }
222
234
  } else {
223
235
  // app.debug('Downwind')
224
- if (options.beatAngle == true) {
225
- if (typeof polar[indexTWS]['Run angle'] != 'undefined') {
226
- // Calculate run angle
227
- let runLower = polar[indexTWS]['Run angle']
228
- let runUpper = polar[indexTWS+1]['Run angle']
229
- //app.debug('runLower: %s runUpper: %s', runLower, runUpper)
230
- performance.runAngle = runLower + ((runUpper - runLower) * twsGapRatio)
231
- }
236
+ if (typeof polar[indexTWS]['Run angle'] != 'undefined') {
237
+ // Calculate run angle
238
+ let runLower = polar[indexTWS]['Run angle']
239
+ let runUpper = polar[indexTWS+1]['Run angle']
240
+ //app.debug('runLower: %s runUpper: %s', runLower, runUpper)
241
+ performance.runAngle = runLower + ((runUpper - runLower) * twsGapRatio)
232
242
  }
233
243
  if (options.optimumWindAngle == true) {
234
244
  if (typeof performance.runAngle != 'undefined') {
@@ -236,14 +246,12 @@ module.exports = function (app) {
236
246
  performance.optimumWindAngle = (performance.runAngle - TWA) * port * -1
237
247
  }
238
248
  }
239
- if (options.beatVMG == true) {
240
- // Calculate run VMG
241
- if (typeof polar[indexTWS]['Run VMG'] != 'undefined') {
242
- let VMGLower = polar[indexTWS]['Run VMG']
243
- let VMGUpper = polar[indexTWS+1]['Run VMG']
244
- //app.debug('VMGLower: %s VMGUpper: %s', VMGLower, VMGUpper)
245
- performance.runVMG = VMGLower + ((VMGUpper - VMGLower) * twsGapRatio)
246
- }
249
+ // Calculate run VMG
250
+ if (typeof polar[indexTWS]['Run VMG'] != 'undefined') {
251
+ let VMGLower = polar[indexTWS]['Run VMG']
252
+ let VMGUpper = polar[indexTWS+1]['Run VMG']
253
+ //app.debug('VMGLower: %s VMGUpper: %s', VMGLower, VMGUpper)
254
+ performance.runVMG = VMGLower + ((VMGUpper - VMGLower) * twsGapRatio)
247
255
  }
248
256
  }
249
257
  // Calculate polar target boat speed
@@ -348,10 +356,10 @@ module.exports = function (app) {
348
356
  app.debug('beat and run angles are included')
349
357
  } else if (row.includes('0')) {
350
358
  // Beat / Run angle
351
- let angle = Number(row[0])
359
+ let angle = degToRad(Number(row[0]))
352
360
  let angleName
353
361
  let VMGName
354
- if (angle < 90) {
362
+ if (angle < halfPi) {
355
363
  angleName = 'Beat angle'
356
364
  VMGName = 'Beat VMG'
357
365
  } else {
@@ -360,17 +368,18 @@ module.exports = function (app) {
360
368
  }
361
369
  for (let index = 0; index < row.length-1; index++) {
362
370
  if (row[index+1] != 0) {
363
- polar[index][angleName] = degToRad(angle)
364
- polar[index][VMGName] = roundDec(Number((row[index+1]) * Math.abs(Math.cos(angle))))
371
+ polar[index][angleName] = angle
372
+ let tbs = ktsToMs(Number(row[index+1]))
373
+ let vmg = tbs * Math.abs(Math.cos(angle))
374
+ polar[index][VMGName] = roundDec(vmg)
365
375
  if (typeof polar[index]['twa'] == 'undefined') {
366
376
  polar[index]['twa'] = []
367
377
  }
368
- let tbs = ktsToMs(Number(row[index+1]))
369
- let Obj = {"twa": degToRad(angle), "tbs": tbs}
378
+ let Obj = {"twa": angle, "tbs": tbs, "vmg": vmg}
370
379
  polar[index]['twa'] = polar[index]['twa'].concat(Obj)
371
380
  if (typeof polar[index]['Max speed'] == 'undefined' || tbs > polar[index]['Max speed']) {
372
381
  polar[index]['Max speed'] = tbs
373
- polar[index]['Max speed angle'] = degToRad(angle)
382
+ polar[index]['Max speed angle'] = angle
374
383
  }
375
384
  }
376
385
  }
@@ -382,9 +391,13 @@ module.exports = function (app) {
382
391
  polar[index].twa = []
383
392
  }
384
393
  let tbs = ktsToMs(Number(row[index+1]))
385
- let Obj = {"twa": angle, "tbs": tbs}
386
- app.debug('Adding Obj: %s', JSON.stringify(Obj))
394
+ let vmg = tbs * Math.abs(Math.cos(angle))
395
+ let Obj = {"twa": angle, "tbs": tbs, "vmg": vmg}
396
+ // app.debug('Adding Obj: %s', JSON.stringify(Obj))
387
397
  polar[index]['twa'] = polar[index]['twa'].concat(Obj)
398
+ // See if we need to set/overwrite beat/run angle
399
+
400
+
388
401
  // See if this a new Max speed
389
402
  if (typeof polar[index]['Max speed'] == 'undefined' || tbs > polar[index]['Max speed']) {
390
403
  polar[index]['Max speed'] = tbs
@@ -402,34 +415,68 @@ module.exports = function (app) {
402
415
  tws.twa = twaArray
403
416
  })
404
417
 
418
+ // Find the beat/run angle if not set yet.
419
+ if (typeof polar[0].beatAngle == 'undefined') {
420
+ for (let index = 0; index < polar.length-1; index++) {
421
+ let tws = polar[index].tws
422
+ let beatVMG = 0
423
+ let beatVMGelement = 0
424
+ let runVMG = 0
425
+ let runVMGelement = 0
426
+
427
+ let twaArray = polar[index].twa
428
+ for (let element = 0; element < twaArray.length-1; element++) {
429
+ let Obj = twaArray[element]
430
+ if (Obj.twa < halfPi) {
431
+ // Beat
432
+ if (Obj.vmg > beatVMG) {
433
+ beatElement = element
434
+ }
435
+ } else {
436
+ // Run
437
+ if (Obj.vmg > runVMG) {
438
+ runElement = element
439
+ }
440
+ }
441
+ }
442
+ app.debug('beatVMG for %d is %d (angel %d)', tws, twaArray[beatVMGelement].vmg, twaArray[beatVMGelement].twa)
443
+ app.debug(' runVMG for %d is %d (angel %d)', tws, twaArray[runVMGelement].vmg, twaArray[runVMGelement].twa)
444
+ polar[index]['Beat angle'] = twaArray[beatVMGelement].twa
445
+ polar[index]['Beat VMG'] = twaArray[beatVMGelement].vmg
446
+ polar[index]['Run angle'] = twaArray[runVMGelement].twa
447
+ polar[index]['Run VMG'] = twaArray[runVMGelement].vmg
448
+ }
449
+
450
+ }
451
+
405
452
  // And now fill in some missing ends to avoid doing expensive calculations in the main loop
406
453
  for (let index = 0; index < polar.length-1; index++) {
407
454
  // Sorted on angle, so first is lowest
455
+ let tws = polar[index].tws
408
456
  let twaArray = polar[index].twa
409
457
  let lowTBS = twaArray[0].tbs
410
458
  let lowTWA = twaArray[0].twa
411
459
 
412
- // app.debug('Padding polar from 0 to first given angle (%d deg, %d kts) to twaArray %s' , radToDeg(lowTWA), msToKts(lowTBS), JSON.stringify(twaArray))
460
+ app.debug('Padding polar for %s from 0 to first given angle (%d deg, %d kts)', msToKts(tws).toFixed(0), radToDeg(lowTWA), msToKts(lowTBS))
413
461
 
414
462
  // Now put some extra values at the beginning
415
463
  var topArray = []
416
464
  for (let angle = 0; angle < lowTWA; angle = angle + degToRad(5)) {
417
465
  let tbs = (angle / lowTWA) * Math.pow(Math.cos((-1*lowTWA + angle)*3),3) * lowTBS
418
466
  let Obj = {'twa': angle, 'tbs': tbs}
419
- // app.debug('Adding Obj: %s', JSON.stringify(Obj))
420
467
  topArray.push(Obj)
421
- // app.debug('twaArray: %s', JSON.stringify(twaArray))
422
468
  }
423
- twaArray = topArray.concat(twaArray)
424
- // app.debug('twaArray: %s', JSON.stringify(polar[index].twa))
469
+ // app.debug('Adding values: %s', JSON.stringify(topArray))
470
+ polar[index].twa = topArray.concat(twaArray)
425
471
  }
426
472
 
427
473
  for (let index = 0; index < polar.length-1; index++) {
474
+ let tws = polar[index].tws
428
475
  let twaArray = polar[index].twa
429
476
  let highTBS = twaArray[twaArray.length-1].tbs
430
477
  let highTWA = twaArray[twaArray.length-1].twa
431
478
 
432
- // app.debug('Padding polar from highTWA to last given angle (%d, %d kts) to twaArray %s' , highTWA, msToKts(highTBS), JSON.stringify(twaArray))
479
+ app.debug('Padding polar for %s from highTWA to last given angle (%d, %d kts)', msToKts(tws).toFixed(0), highTWA, msToKts(highTBS))
433
480
 
434
481
  // Now put some extra values at the beginning
435
482
  var tailArray = []
@@ -438,7 +485,8 @@ module.exports = function (app) {
438
485
  let Obj = {'twa': angle, 'tbs': tbs}
439
486
  tailArray.unshift(Obj)
440
487
  }
441
- twaArray = twaArray.concat(tailArray)
488
+ // app.debug('Adding values: %s', JSON.stringify(tailArray))
489
+ polar[index].twa = twaArray.concat(tailArray)
442
490
  }
443
491
 
444
492
  // app.debug(JSON.stringify(polar))