@sailingrotevista/rotevista-dash 7.0.16 → 7.0.17

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 (3) hide show
  1. package/app.js +26 -11
  2. package/index.js +98 -68
  3. package/package.json +1 -1
package/app.js CHANGED
@@ -23,7 +23,7 @@ let CONFIG = {
23
23
  minSpeed: 0.5,
24
24
  stabilityBreakout: 15
25
25
  },
26
- graphs: { reef1: 10, reef2: 15, historyMinutes: 10, samples: 60 },
26
+ graphs: { reef1: 10, reef2: 15, historyMinutes: 5, samples: 60 },
27
27
  scales: {
28
28
  stw: { stdMax: 4, hercSpan: 2, step: 1 },
29
29
  sog: { stdMax: 4, hercSpan: 2, step: 1 },
@@ -964,11 +964,11 @@ function manageHistory(type, value) {
964
964
  const bucketReady = (now - store.lastUpdates[type] > bucketIntervalMs) || store.histories[type].length === 0;
965
965
  if (!bucketReady) return;
966
966
 
967
- // --- 5. AGGREGAZIONE SEMANTICA ---
967
+ // --- 5. AGGREGAZIONE SEMANTICA (Ottimizzata a zero-allocazioni di array intermedi) ---
968
968
  let finalValue = value;
969
969
 
970
970
  if (tempBuf.length > 0) {
971
- // A. VENTO -> SUSTAINED PEAK (EMA Time-Aware)
971
+ // A. VENTO -> SUSTAINED PEAK (EMA Time-Aware) (0 allocazioni)
972
972
  if (type === 'tws' || type === 'aws') {
973
973
  const tauMs = 2500;
974
974
  let ema = tempBuf[0].val;
@@ -982,17 +982,32 @@ function manageHistory(type, value) {
982
982
  }
983
983
  finalValue = maxSustained;
984
984
  }
985
- // B. PROFONDITÀ -> MINIMO
985
+ // B. PROFONDITÀ -> MINIMO (In-place loop, 0 allocazioni)
986
986
  else if (type === 'depth') {
987
- const vals = tempBuf.map(p => p.val).filter(v => isFinite(v));
988
- if (vals.length > 0) finalValue = Math.min(...vals);
987
+ let minVal = Infinity;
988
+ for (let i = 0; i < tempBuf.length; i++) {
989
+ const v = tempBuf[i].val;
990
+ if (isFinite(v) && v < minVal) {
991
+ minVal = v;
992
+ }
993
+ }
994
+ if (minVal !== Infinity) {
995
+ finalValue = minVal;
996
+ }
989
997
  }
990
- // C. VELOCITÀ -> MEDIA
998
+ // C. VELOCITÀ -> MEDIA (In-place loop, 0 allocazioni)
991
999
  else {
992
- const vals = tempBuf.map(p => p.val).filter(v => isFinite(v));
993
- if (vals.length > 0) {
994
- const sum = vals.reduce((a, b) => a + b, 0);
995
- finalValue = sum / tempBuf.length;
1000
+ let sum = 0;
1001
+ let count = 0;
1002
+ for (let i = 0; i < tempBuf.length; i++) {
1003
+ const v = tempBuf[i].val;
1004
+ if (isFinite(v)) {
1005
+ sum += v;
1006
+ count++;
1007
+ }
1008
+ }
1009
+ if (count > 0) {
1010
+ finalValue = sum / count;
996
1011
  }
997
1012
  }
998
1013
  }
package/index.js CHANGED
@@ -351,81 +351,111 @@ module.exports = function (app) {
351
351
  let isTwdType = (type === 'twd');
352
352
 
353
353
  if (tempBuf.length > 0) {
354
- // ==========================================================================
355
- // CASO PARTICOLARE: DIREZIONE VENTO (TWD) -> MEDIA PESATA E LIMITI MIN/MAX
356
- // ==========================================================================
357
- if (isTwdType) {
358
- // 1. Identifica il vento massimo del minuto (in m/s)
359
- const twsVals = tempBuf.map(p => p.tws || 0);
360
- const maxTwsInMinute = Math.max(...twsVals, 0);
361
-
362
- // 2. Calcola la soglia dinamica di pressione: max tra 40% del picco e soglia di calma piatta (in m/s)
363
- const calmThresholdMs = CALM_THRESHOLD_KTS / 1.94384;
364
- const pressureThreshold = Math.max(maxTwsInMinute * PRESSURE_FILTER_RATIO, calmThresholdMs);
365
-
366
- // 3. Esclude le direzioni registrate quando il vento era debole (sotto la soglia di pressione)
367
- const activePoints = tempBuf.filter(p => (p.tws || 0) >= pressureThreshold);
368
- const pointsToUse = activePoints.length > 0 ? activePoints : tempBuf; // Fallback se tutto è calma piatta
369
-
370
- // 4. Media Vettoriale Pesata (TWS * sin, TWS * cos)
371
- let sumSin = 0;
372
- let sumCos = 0;
373
- let totalWeight = 0;
374
- pointsToUse.forEach(p => {
375
- const weight = Math.max(p.tws || 0.1, 0.05); // Evita pesi a zero
376
- sumSin += weight * Math.sin(p.val);
377
- sumCos += weight * Math.cos(p.val);
378
- totalWeight += weight;
379
- });
380
- const avgAngle = Math.atan2(sumSin, sumCos);
381
- const finalAvg = (avgAngle + Math.PI * 2) % (Math.PI * 2);
382
-
383
- // 5. Calcolo di Min e Max angolare del minuto (rispetto alla media per gestire l'oltrepasso di 0/360°)
384
- let diffs = pointsToUse.map(p => {
385
- let diff = p.val - finalAvg;
386
- return Math.atan2(Math.sin(diff), Math.cos(diff)); // Srotolamento tra -PI e +PI
387
- });
388
-
389
- const minDiff = Math.min(...diffs);
390
- const maxDiff = Math.max(...diffs);
391
-
392
- const finalMin = (finalAvg + minDiff + Math.PI * 2) % (Math.PI * 2);
393
- const finalMax = (finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2);
394
-
395
- finalValue = {
396
- val: finalAvg,
397
- min: finalMin,
398
- max: finalMax
399
- };
354
+ // ==========================================================================
355
+ // CASO PARTICOLARE: DIREZIONE VENTO (TWD) -> MEDIA PESATA E LIMITI MIN/MAX
356
+ // ==========================================================================
357
+ if (isTwdType) {
358
+ // 1. Identifica il vento massimo del minuto (in m/s) - Loop in-place (0 allocazioni)
359
+ let maxTwsInMinute = 0;
360
+ for (let i = 0; i < tempBuf.length; i++) {
361
+ const tws = tempBuf[i].tws || 0;
362
+ if (tws > maxTwsInMinute) maxTwsInMinute = tws;
400
363
  }
401
- // A. VENTO VELOCITÀ -> SUSTAINED PEAK (EMA Time-Aware)
402
- else if (type === 'tws' || type === 'aws') {
403
- const tauMs = 2500;
404
- let ema = tempBuf[0].val;
405
- let maxSustained = ema;
406
-
407
- for (let i = 1; i < tempBuf.length; i++) {
408
- const dt = Math.max(1, tempBuf[i].time - tempBuf[i-1].time);
409
- const alpha = 1 - Math.exp(-dt / tauMs);
410
- ema = (tempBuf[i].val * alpha) + (ema * (1 - alpha));
411
- if (isFinite(ema) && ema > maxSustained) maxSustained = ema;
364
+
365
+ // 2. Calcola la soglia dinamica di pressione
366
+ const calmThresholdMs = CALM_THRESHOLD_KTS / 1.94384;
367
+ const pressureThreshold = Math.max(maxTwsInMinute * PRESSURE_FILTER_RATIO, calmThresholdMs);
368
+
369
+ // 3. Determina in un unico passaggio se ci sono punti attivi sopra la soglia (0 allocazioni)
370
+ let hasActivePoints = false;
371
+ for (let i = 0; i < tempBuf.length; i++) {
372
+ if ((tempBuf[i].tws || 0) >= pressureThreshold) {
373
+ hasActivePoints = true;
374
+ break;
412
375
  }
413
- finalValue = maxSustained;
414
376
  }
415
- // B. PROFONDITÀ -> MINIMO
416
- else if (type === 'depth') {
417
- const vals = tempBuf.map(p => p.val).filter(v => isFinite(v));
418
- if (vals.length > 0) finalValue = Math.min(...vals);
377
+
378
+ // 4. Media Vettoriale Pesata (0 allocazioni di array)
379
+ let sumSin = 0;
380
+ let sumCos = 0;
381
+ let totalWeight = 0;
382
+ for (let i = 0; i < tempBuf.length; i++) {
383
+ const p = tempBuf[i];
384
+ if (hasActivePoints && (p.tws || 0) < pressureThreshold) continue;
385
+
386
+ const weight = Math.max(p.tws || 0.1, 0.05);
387
+ sumSin += weight * Math.sin(p.val);
388
+ sumCos += weight * Math.cos(p.val);
389
+ totalWeight += weight;
419
390
  }
420
- // C. VELOCITÀ BARCA / ALTRO -> MEDIA
421
- else {
422
- const vals = tempBuf.map(p => p.val).filter(v => isFinite(v));
423
- if (vals.length > 0) {
424
- const sum = vals.reduce((a, b) => a + b, 0);
425
- finalValue = sum / tempBuf.length;
391
+ const avgAngle = Math.atan2(sumSin, sumCos);
392
+ const finalAvg = (avgAngle + Math.PI * 2) % (Math.PI * 2);
393
+
394
+ // 5. Calcolo di Min e Max angolare del minuto (0 allocazioni di array)
395
+ let minDiff = 0;
396
+ let maxDiff = 0;
397
+ let firstDiff = true;
398
+ for (let i = 0; i < tempBuf.length; i++) {
399
+ const p = tempBuf[i];
400
+ if (hasActivePoints && (p.tws || 0) < pressureThreshold) continue;
401
+
402
+ const diff = Math.atan2(Math.sin(p.val - finalAvg), Math.cos(p.val - finalAvg));
403
+ if (firstDiff) {
404
+ minDiff = diff;
405
+ maxDiff = diff;
406
+ firstDiff = false;
407
+ } else {
408
+ if (diff < minDiff) minDiff = diff;
409
+ if (diff > maxDiff) maxDiff = diff;
426
410
  }
427
411
  }
412
+
413
+ const finalMin = (finalAvg + minDiff + Math.PI * 2) % (Math.PI * 2);
414
+ const finalMax = (finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2);
415
+
416
+ finalValue = {
417
+ val: finalAvg,
418
+ min: finalMin,
419
+ max: finalMax
420
+ };
428
421
  }
422
+ // A. VENTO VELOCITÀ -> SUSTAINED PEAK (EMA Time-Aware) (Inalterato per minimizzare le modifiche)
423
+ else if (type === 'tws' || type === 'aws') {
424
+ const tauMs = 2500;
425
+ let ema = tempBuf[0].val;
426
+ let maxSustained = ema;
427
+
428
+ for (let i = 1; i < tempBuf.length; i++) {
429
+ const dt = Math.max(1, tempBuf[i].time - tempBuf[i-1].time);
430
+ const alpha = 1 - Math.exp(-dt / tauMs);
431
+ ema = (tempBuf[i].val * alpha) + (ema * (1 - alpha));
432
+ if (isFinite(ema) && ema > maxSustained) maxSustained = ema;
433
+ }
434
+ finalValue = maxSustained;
435
+ }
436
+ // B. PROFONDITÀ -> MINIMO (Ottimizzato a 0 allocazioni)
437
+ else if (type === 'depth') {
438
+ let minVal = Infinity;
439
+ for (let i = 0; i < tempBuf.length; i++) {
440
+ const v = tempBuf[i].val;
441
+ if (isFinite(v) && v < minVal) minVal = v;
442
+ }
443
+ if (minVal !== Infinity) finalValue = minVal;
444
+ }
445
+ // C. VELOCITÀ BARCA / ALTRO -> MEDIA (Ottimizzato a 0 allocazioni)
446
+ else {
447
+ let sum = 0;
448
+ let count = 0;
449
+ for (let i = 0; i < tempBuf.length; i++) {
450
+ const v = tempBuf[i].val;
451
+ if (isFinite(v)) {
452
+ sum += v;
453
+ count++;
454
+ }
455
+ }
456
+ if (count > 0) finalValue = sum / count;
457
+ }
458
+ }
429
459
 
430
460
  // Validazione e clamping di sicurezza (non si applica all'oggetto TWD)
431
461
  if (!isTwdType) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "7.0.16",
3
+ "version": "7.0.17",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {