@sailingrotevista/rotevista-dash 7.0.16 → 7.0.18
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/app.js +40 -16
- package/index.js +98 -68
- package/package.json +1 -1
- package/style.css +9 -0
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:
|
|
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 },
|
|
@@ -744,11 +744,20 @@ function startDisplayLoop() {
|
|
|
744
744
|
}
|
|
745
745
|
|
|
746
746
|
if (lastAvgUIUpdate % 3 === 0) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
747
|
+
const baseThreshold = CONFIG.averaging.stabilityThreshold || 0.95;
|
|
748
|
+
const breakout = CONFIG.averaging.stabilityBreakout || 15;
|
|
749
|
+
|
|
750
|
+
// CALCOLO DIFFERENZIALE: Tolleranza del vento proporzionale alla sensibilità di base.
|
|
751
|
+
// Rilassiamo la soglia del vento di 0.13 rispetto alla prua (es. se la prua richiede 0.95, il vento richiede 0.82)
|
|
752
|
+
const windThreshold = Math.max(0.60, baseThreshold - 0.13);
|
|
753
|
+
|
|
754
|
+
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averaging.longWindow * 2, false, now, baseThreshold, breakout);
|
|
755
|
+
let cObj = getCircularAverageFromBuffer(store.longBuf.cog, CONFIG.averaging.longWindow, false, now, baseThreshold, breakout);
|
|
756
|
+
|
|
757
|
+
// Applichiamo la soglia differenziale ottimizzata per i tre dati legati al vento
|
|
758
|
+
let awObj = getCircularAverageFromBuffer(store.longBuf.awa, CONFIG.averaging.longWindow, true, now, windThreshold, breakout);
|
|
759
|
+
let twObj = getCircularAverageFromBuffer(store.longBuf.twa, CONFIG.averaging.longWindow, true, now, windThreshold, breakout);
|
|
760
|
+
let twdObj = getCircularAverageFromBuffer(store.longBuf.twd, CONFIG.averaging.longWindow, false, now, windThreshold, breakout);
|
|
752
761
|
|
|
753
762
|
// --- GESTIONE DINAMICA ETICHETTA TACK/GYBE CON ISTERESI DI 10 GRADI ---
|
|
754
763
|
if (ui.tackLabel) {
|
|
@@ -964,11 +973,11 @@ function manageHistory(type, value) {
|
|
|
964
973
|
const bucketReady = (now - store.lastUpdates[type] > bucketIntervalMs) || store.histories[type].length === 0;
|
|
965
974
|
if (!bucketReady) return;
|
|
966
975
|
|
|
967
|
-
// --- 5. AGGREGAZIONE SEMANTICA ---
|
|
976
|
+
// --- 5. AGGREGAZIONE SEMANTICA (Ottimizzata a zero-allocazioni di array intermedi) ---
|
|
968
977
|
let finalValue = value;
|
|
969
978
|
|
|
970
979
|
if (tempBuf.length > 0) {
|
|
971
|
-
// A. VENTO -> SUSTAINED PEAK (EMA Time-Aware)
|
|
980
|
+
// A. VENTO -> SUSTAINED PEAK (EMA Time-Aware) (0 allocazioni)
|
|
972
981
|
if (type === 'tws' || type === 'aws') {
|
|
973
982
|
const tauMs = 2500;
|
|
974
983
|
let ema = tempBuf[0].val;
|
|
@@ -982,17 +991,32 @@ function manageHistory(type, value) {
|
|
|
982
991
|
}
|
|
983
992
|
finalValue = maxSustained;
|
|
984
993
|
}
|
|
985
|
-
// B. PROFONDITÀ -> MINIMO
|
|
994
|
+
// B. PROFONDITÀ -> MINIMO (In-place loop, 0 allocazioni)
|
|
986
995
|
else if (type === 'depth') {
|
|
987
|
-
|
|
988
|
-
|
|
996
|
+
let minVal = Infinity;
|
|
997
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
998
|
+
const v = tempBuf[i].val;
|
|
999
|
+
if (isFinite(v) && v < minVal) {
|
|
1000
|
+
minVal = v;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (minVal !== Infinity) {
|
|
1004
|
+
finalValue = minVal;
|
|
1005
|
+
}
|
|
989
1006
|
}
|
|
990
|
-
// C. VELOCITÀ -> MEDIA
|
|
1007
|
+
// C. VELOCITÀ -> MEDIA (In-place loop, 0 allocazioni)
|
|
991
1008
|
else {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1009
|
+
let sum = 0;
|
|
1010
|
+
let count = 0;
|
|
1011
|
+
for (let i = 0; i < tempBuf.length; i++) {
|
|
1012
|
+
const v = tempBuf[i].val;
|
|
1013
|
+
if (isFinite(v)) {
|
|
1014
|
+
sum += v;
|
|
1015
|
+
count++;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (count > 0) {
|
|
1019
|
+
finalValue = sum / count;
|
|
996
1020
|
}
|
|
997
1021
|
}
|
|
998
1022
|
}
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
package/style.css
CHANGED
|
@@ -601,3 +601,12 @@ body.night-mode {
|
|
|
601
601
|
.night-mode #wind-radar circle[stroke="#b0bec5"] {
|
|
602
602
|
stroke: #440000 !important;
|
|
603
603
|
}
|
|
604
|
+
|
|
605
|
+
/* ==========================================================================
|
|
606
|
+
13. SMORZAMENTO FLUIDO AGHI A 60FPS (DAMPING HARDWARE)
|
|
607
|
+
========================================================================== */
|
|
608
|
+
|
|
609
|
+
/* Applichiamo la transizione fluida a tutti i puntatori mantenendo i perni nativi dell'SVG */
|
|
610
|
+
#awa-pointer, #twa-pointer, #track-pointer, #twd-arrow, #twd-boat-wrap {
|
|
611
|
+
transition: transform 0.9s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
612
|
+
}
|