@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.
- package/app.js +26 -11
- package/index.js +98 -68
- 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:
|
|
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
|
-
|
|
988
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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
|
-
|
|
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) {
|