@sailingrotevista/rotevista-dash 6.2.7 → 6.2.8
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/index.js +21 -19
- package/package.json +1 -1
- package/radar.html +79 -45
package/index.js
CHANGED
|
@@ -559,21 +559,22 @@ module.exports = function (app) {
|
|
|
559
559
|
* Raccoglie i 30 record da 1 minuto, estrae i picchi, unisce gli angoli
|
|
560
560
|
* e applica la scrematura del percentile al 5% prima di salvare lo slot.
|
|
561
561
|
*/
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
562
|
+
function freeze30mSlot(slotTimestamp) {
|
|
563
|
+
const startTime = slotTimestamp;
|
|
564
|
+
const endTime = slotTimestamp + 1800000; // 30 minuti in millisecondi
|
|
565
565
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
566
|
+
// 1. Estrae i record storici del TWD e del TWS che ricadono in quella mezz'ora
|
|
567
|
+
const twdPoints = histories['twd'].filter(p => p.time >= startTime && p.time < endTime);
|
|
568
|
+
const twsPoints = histories['tws'].filter(p => p.time >= startTime && p.time < endTime);
|
|
569
569
|
|
|
570
|
-
|
|
570
|
+
if (twdPoints.length === 0) return; // Se non ci sono dati, salta lo slot
|
|
571
571
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
572
|
+
// 2. Calcola il vento massimo sostenuto (in nodi) registrato nel periodo
|
|
573
|
+
const twsVals = twsPoints.map(p => p.val).filter(v => isFinite(v));
|
|
574
|
+
const maxTws = twsVals.length > 0 ? Math.max(...twsVals) : 0;
|
|
575
|
+
const minTws = twsVals.length > 0 ? Math.min(...twsVals) : 0; // Chirurgico: Calcoliamo il vento minimo del periodo
|
|
575
576
|
|
|
576
|
-
|
|
577
|
+
// 3. Estrae tutti gli estremi angolari catturati minuto per minuto
|
|
577
578
|
let allAngles = [];
|
|
578
579
|
twdPoints.forEach(p => {
|
|
579
580
|
allAngles.push(p.val);
|
|
@@ -611,15 +612,16 @@ module.exports = function (app) {
|
|
|
611
612
|
const finalMin = (finalAvg + minDiff + Math.PI * 2) % (Math.PI * 2);
|
|
612
613
|
const finalMax = (finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2);
|
|
613
614
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
615
|
+
// 7. Salva l'arco compresso e pulito nello store dedicato
|
|
616
|
+
windRadarSlots.push({
|
|
617
|
+
timestamp: startTime,
|
|
618
|
+
twdMin: finalMin,
|
|
619
|
+
twdMax: finalMax,
|
|
620
|
+
twsPeak: maxTws,
|
|
621
|
+
twsMin: minTws // Chirurgico: Salviamo il vento minimo per poter tracciare la variabilità (Gust Factor)
|
|
622
|
+
});
|
|
621
623
|
|
|
622
|
-
|
|
624
|
+
// Pruning: manteniamo in RAM solo le ultime 6 ore (12 slot)
|
|
623
625
|
while (windRadarSlots.length > 12) {
|
|
624
626
|
windRadarSlots.shift();
|
|
625
627
|
}
|
package/package.json
CHANGED
package/radar.html
CHANGED
|
@@ -417,21 +417,22 @@
|
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
// --- 4. MOTORE DI CALCOLO DELL'ANELLO 1 (Presente Mobile) ---
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
420
|
+
function calculateActive30mRing() {
|
|
421
|
+
const now = Date.now();
|
|
422
|
+
const start30m = now - 1800000;
|
|
423
423
|
|
|
424
|
-
|
|
425
|
-
|
|
424
|
+
const twdRecent = store.twdMinuteBuffer.filter(p => p.time >= start30m);
|
|
425
|
+
const twsRecent = store.twsMinuteBuffer.filter(p => p.time >= start30m);
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
if (twdRecent.length === 0) return null;
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
const twsVals = twsRecent.map(p => p.val).filter(v => isFinite(v));
|
|
430
|
+
const maxTws = twsVals.length > 0 ? Math.max(...twsVals) : 0;
|
|
431
|
+
const minTws = twsVals.length > 0 ? Math.min(...twsVals) : 0; // Chirurgico: Troviamo il vento minimo reale degli ultimi 30 minuti
|
|
431
432
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
433
|
+
if (maxTws < CALM_THRESHOLD_KTS) {
|
|
434
|
+
return { twsPeak: maxTws, twsMin: minTws, twdMin: 0, twdMax: 360, isCalm: true };
|
|
435
|
+
}
|
|
435
436
|
|
|
436
437
|
let allAngles = [];
|
|
437
438
|
twdRecent.forEach(p => {
|
|
@@ -459,15 +460,16 @@
|
|
|
459
460
|
const maxDiff = Math.max(...finalDiffs);
|
|
460
461
|
|
|
461
462
|
const finalMinDeg = Math.round(radToDeg((finalAvg + minDiff + Math.PI * 2) % (Math.PI * 2)));
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
463
|
+
const finalMaxDeg = Math.round(radToDeg((finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2)));
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
twdMin: finalMinDeg,
|
|
467
|
+
twdMax: finalMaxDeg,
|
|
468
|
+
twsPeak: maxTws,
|
|
469
|
+
twsMin: minTws, // Chirurgico: Ritorniamo anche il valore minimo per abilitare la sfumatura sull'Anello 1
|
|
470
|
+
isCalm: false
|
|
471
|
+
};
|
|
472
|
+
}
|
|
471
473
|
|
|
472
474
|
// --- 5. MOTORE GRAFICO DI DISEGNO ---
|
|
473
475
|
function drawCompassTicks() {
|
|
@@ -642,21 +644,23 @@
|
|
|
642
644
|
radarDataList.push(activeRing);
|
|
643
645
|
|
|
644
646
|
// 3. ANELLI da 2 a 7 (I 6 slot storici da 30 min passati salvati dal server, pari a 3 ore)
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
647
|
+
for (let i = 1; i <= 6; i++) {
|
|
648
|
+
const targetTimestamp = current30mSlot - (i * 1800000);
|
|
649
|
+
const matchedSlot = windRadarSlots.find(s => s.timestamp === targetTimestamp);
|
|
650
|
+
|
|
651
|
+
if (matchedSlot) {
|
|
652
|
+
radarDataList.push({
|
|
653
|
+
twdMin: Math.round(radToDeg(matchedSlot.twdMin)),
|
|
654
|
+
twdMax: Math.round(radToDeg(matchedSlot.twdMax)),
|
|
655
|
+
twsPeak: matchedSlot.twsPeak,
|
|
656
|
+
// Chirurgico: Estraiamo il minimo storico con protezione di compatibilità se assente
|
|
657
|
+
twsMin: matchedSlot.twsMin !== undefined ? matchedSlot.twsMin : matchedSlot.twsPeak,
|
|
658
|
+
isCalm: matchedSlot.twsPeak < CALM_THRESHOLD_KTS
|
|
659
|
+
});
|
|
660
|
+
} else {
|
|
661
|
+
radarDataList.push(null);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
660
664
|
|
|
661
665
|
document.getElementById('debug-rings-count').innerText = radarDataList.filter(p => p !== null).length + "/8";
|
|
662
666
|
|
|
@@ -685,17 +689,47 @@
|
|
|
685
689
|
}
|
|
686
690
|
|
|
687
691
|
// Caso B: Arco Direzionale
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
692
|
+
let strokeColor = '';
|
|
693
|
+
|
|
694
|
+
// Chirurgico: Definita la funzione di colorazione universale basata sulle soglie dei Reef di bordo
|
|
695
|
+
const getColorForSpeed = (tws) => {
|
|
696
|
+
const R1 = REEF1; const R2 = REEF2; const R3 = REEF3;
|
|
697
|
+
if (tws < R1 * 0.4) return '#ffffff'; // Bianco (Calma)
|
|
698
|
+
if (tws < R1 * 0.75) return '#00C851'; // Verde (Regolare)
|
|
699
|
+
if (tws < R1) return '#ff9800'; // Arancio (1° Reef Alert)
|
|
700
|
+
if (tws < R2) return '#ffaa00'; // Dorato (Transizione)
|
|
701
|
+
if (tws < R2 + (R3 - R2) * 0.5) return '#ff3b30'; // Rosso (2° Reef Alert)
|
|
702
|
+
return '#9c27b0'; // Viola (Storm / Temporale)
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
// Chirurgico: Estraiamo i limiti minimi e massimi dell'arco corrente
|
|
706
|
+
// Se è la previsione, usiamo il vento medio e le raffiche di Open-Meteo, altrimenti i minimi e i picchi in RAM
|
|
707
|
+
const baseTws = data.isFuture && futureForecast ? futureForecast.tws : (data.twsMin !== undefined ? data.twsMin : data.twsPeak);
|
|
708
|
+
const peakTws = data.isFuture && futureForecast ? futureForecast.gust : data.twsPeak;
|
|
709
|
+
|
|
710
|
+
const baseColor = getColorForSpeed(baseTws);
|
|
711
|
+
const peakColor = getColorForSpeed(peakTws);
|
|
712
|
+
|
|
713
|
+
if (baseColor !== peakColor) {
|
|
714
|
+
// Se c'è variabilità d'intensità (raffica), disegnamo il gradiente termico dinamico lungo l'arco (Min -> Max -> Min)
|
|
715
|
+
const startPt = polarToCartesian(200, 200, radius, data.twdMax);
|
|
716
|
+
const endPt = polarToCartesian(200, 200, radius, data.twdMin);
|
|
717
|
+
|
|
718
|
+
const xml = `
|
|
719
|
+
<linearGradient id="${gradId}" x1="${startPt.x.toFixed(1)}" y1="${startPt.y.toFixed(1)}" x2="${endPt.x.toFixed(1)}" y2="${endPt.y.toFixed(1)}" gradientUnits="userSpaceOnUse">
|
|
720
|
+
<stop offset="0%" stop-color="${baseColor}" />
|
|
721
|
+
<stop offset="50%" stop-color="${peakColor}" />
|
|
722
|
+
<stop offset="100%" stop-color="${baseColor}" />
|
|
723
|
+
</linearGradient>
|
|
724
|
+
`;
|
|
725
|
+
defsContainer.innerHTML += xml;
|
|
726
|
+
strokeColor = `url(#${gradId})`;
|
|
727
|
+
} else {
|
|
728
|
+
// Se il vento è costante (base e picco ricadono nella stessa fascia), usiamo il colore solido puro
|
|
729
|
+
strokeColor = baseColor;
|
|
730
|
+
}
|
|
697
731
|
|
|
698
|
-
|
|
732
|
+
const pathData = describeArc(200, 200, radius, data.twdMin, data.twdMax);
|
|
699
733
|
|
|
700
734
|
// Chirurgico: Se è l'anello futuro (isFuture), non disegniamo il bordo nero per evitare artefatti "a doppia bolla"
|
|
701
735
|
if (!data.isFuture) {
|