signalk-polar-performance-plugin 0.0.12 → 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.12",
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,25 +213,27 @@ 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
- // Calculate beat angle
216
+ // Take beat angle
217
+ if (typeof polar[indexTWS]['Beat angle'] != 'undefined') {
202
218
  let beatLower = polar[indexTWS]['Beat angle']
203
219
  let beatUpper = polar[indexTWS+1]['Beat angle']
204
220
  performance.beatAngle = beatLower + ((beatUpper - beatLower) * twsGapRatio)
205
221
  }
206
222
  // Calculate optimum wind angle (B&G thing)
207
223
  if (options.optimumWindAngle == true) {
208
- performance.optimumWindAngle = (TWA - performance.beatAngle) * port
224
+ if (typeof performance.beatAngle != 'undefined') {
225
+ performance.optimumWindAngle = (TWA - performance.beatAngle) * port
226
+ }
209
227
  }
210
228
  // Calculate beat VMG
211
- if (options.beatVMG == true) {
229
+ if (typeof polar[indexTWS]['Beat VMG'] != 'undefined') {
212
230
  let VMGLower = polar[indexTWS]['Beat VMG']
213
231
  let VMGUpper = polar[indexTWS+1]['Beat VMG']
214
232
  performance.beatVMG = VMGLower + ((VMGUpper - VMGLower) * twsGapRatio)
215
233
  }
216
234
  } else {
217
235
  // app.debug('Downwind')
218
- if (options.beatAngle == true) {
236
+ if (typeof polar[indexTWS]['Run angle'] != 'undefined') {
219
237
  // Calculate run angle
220
238
  let runLower = polar[indexTWS]['Run angle']
221
239
  let runUpper = polar[indexTWS+1]['Run angle']
@@ -223,14 +241,16 @@ module.exports = function (app) {
223
241
  performance.runAngle = runLower + ((runUpper - runLower) * twsGapRatio)
224
242
  }
225
243
  if (options.optimumWindAngle == true) {
226
- // Calculate optimum wind angle
227
- performance.optimumWindAngle = (performance.runAngle - TWA) * port * -1
244
+ if (typeof performance.runAngle != 'undefined') {
245
+ // Calculate optimum wind angle
246
+ performance.optimumWindAngle = (performance.runAngle - TWA) * port * -1
247
+ }
228
248
  }
229
- if (options.beatVMG == true) {
230
- // Calculate run VMG
249
+ // Calculate run VMG
250
+ if (typeof polar[indexTWS]['Run VMG'] != 'undefined') {
231
251
  let VMGLower = polar[indexTWS]['Run VMG']
232
252
  let VMGUpper = polar[indexTWS+1]['Run VMG']
233
- //app.debug('VMGLower: %s VMGUpper: %s', VMGLower, VMGUpper)
253
+ //app.debug('VMGLower: %s VMGUpper: %s', VMGLower, VMGUpper)
234
254
  performance.runVMG = VMGLower + ((VMGUpper - VMGLower) * twsGapRatio)
235
255
  }
236
256
  }
@@ -336,10 +356,10 @@ module.exports = function (app) {
336
356
  app.debug('beat and run angles are included')
337
357
  } else if (row.includes('0')) {
338
358
  // Beat / Run angle
339
- let angle = Number(row[0])
359
+ let angle = degToRad(Number(row[0]))
340
360
  let angleName
341
361
  let VMGName
342
- if (angle < 90) {
362
+ if (angle < halfPi) {
343
363
  angleName = 'Beat angle'
344
364
  VMGName = 'Beat VMG'
345
365
  } else {
@@ -348,17 +368,18 @@ module.exports = function (app) {
348
368
  }
349
369
  for (let index = 0; index < row.length-1; index++) {
350
370
  if (row[index+1] != 0) {
351
- polar[index][angleName] = degToRad(angle)
352
- 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)
353
375
  if (typeof polar[index]['twa'] == 'undefined') {
354
376
  polar[index]['twa'] = []
355
377
  }
356
- let tbs = ktsToMs(Number(row[index+1]))
357
- let Obj = {"twa": degToRad(angle), "tbs": tbs}
378
+ let Obj = {"twa": angle, "tbs": tbs, "vmg": vmg}
358
379
  polar[index]['twa'] = polar[index]['twa'].concat(Obj)
359
380
  if (typeof polar[index]['Max speed'] == 'undefined' || tbs > polar[index]['Max speed']) {
360
381
  polar[index]['Max speed'] = tbs
361
- polar[index]['Max speed angle'] = degToRad(angle)
382
+ polar[index]['Max speed angle'] = angle
362
383
  }
363
384
  }
364
385
  }
@@ -370,9 +391,15 @@ module.exports = function (app) {
370
391
  polar[index].twa = []
371
392
  }
372
393
  let tbs = ktsToMs(Number(row[index+1]))
373
- let Obj = {"twa": angle, "tbs": tbs}
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))
374
397
  polar[index]['twa'] = polar[index]['twa'].concat(Obj)
375
- if (tbs > polar[index]['Max speed']) {
398
+ // See if we need to set/overwrite beat/run angle
399
+
400
+
401
+ // See if this a new Max speed
402
+ if (typeof polar[index]['Max speed'] == 'undefined' || tbs > polar[index]['Max speed']) {
376
403
  polar[index]['Max speed'] = tbs
377
404
  polar[index]['Max speed angle'] = angle
378
405
  }
@@ -388,34 +415,68 @@ module.exports = function (app) {
388
415
  tws.twa = twaArray
389
416
  })
390
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
+
391
452
  // And now fill in some missing ends to avoid doing expensive calculations in the main loop
392
453
  for (let index = 0; index < polar.length-1; index++) {
393
454
  // Sorted on angle, so first is lowest
455
+ let tws = polar[index].tws
394
456
  let twaArray = polar[index].twa
395
457
  let lowTBS = twaArray[0].tbs
396
458
  let lowTWA = twaArray[0].twa
397
459
 
398
- // 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))
399
461
 
400
462
  // Now put some extra values at the beginning
401
463
  var topArray = []
402
464
  for (let angle = 0; angle < lowTWA; angle = angle + degToRad(5)) {
403
465
  let tbs = (angle / lowTWA) * Math.pow(Math.cos((-1*lowTWA + angle)*3),3) * lowTBS
404
466
  let Obj = {'twa': angle, 'tbs': tbs}
405
- // app.debug('Adding Obj: %s', JSON.stringify(Obj))
406
467
  topArray.push(Obj)
407
- // app.debug('twaArray: %s', JSON.stringify(twaArray))
408
468
  }
409
- twaArray = topArray.concat(twaArray)
410
- // 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)
411
471
  }
412
472
 
413
473
  for (let index = 0; index < polar.length-1; index++) {
474
+ let tws = polar[index].tws
414
475
  let twaArray = polar[index].twa
415
476
  let highTBS = twaArray[twaArray.length-1].tbs
416
477
  let highTWA = twaArray[twaArray.length-1].twa
417
478
 
418
- // 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))
419
480
 
420
481
  // Now put some extra values at the beginning
421
482
  var tailArray = []
@@ -424,7 +485,8 @@ module.exports = function (app) {
424
485
  let Obj = {'twa': angle, 'tbs': tbs}
425
486
  tailArray.unshift(Obj)
426
487
  }
427
- twaArray = twaArray.concat(tailArray)
488
+ // app.debug('Adding values: %s', JSON.stringify(tailArray))
489
+ polar[index].twa = twaArray.concat(tailArray)
428
490
  }
429
491
 
430
492
  // app.debug(JSON.stringify(polar))