@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.
Files changed (3) hide show
  1. package/index.js +21 -19
  2. package/package.json +1 -1
  3. 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
- function freeze30mSlot(slotTimestamp) {
563
- const startTime = slotTimestamp;
564
- const endTime = slotTimestamp + 1800000; // 30 minuti in millisecondi
562
+ function freeze30mSlot(slotTimestamp) {
563
+ const startTime = slotTimestamp;
564
+ const endTime = slotTimestamp + 1800000; // 30 minuti in millisecondi
565
565
 
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);
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
- if (twdPoints.length === 0) return; // Se non ci sono dati, salta lo slot
570
+ if (twdPoints.length === 0) return; // Se non ci sono dati, salta lo slot
571
571
 
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;
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
- // 3. Estrae tutti gli estremi angolari catturati minuto per minuto
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
- // 7. Salva l'arco compresso e pulito nello store dedicato
615
- windRadarSlots.push({
616
- timestamp: startTime,
617
- twdMin: finalMin,
618
- twdMax: finalMax,
619
- twsPeak: maxTws
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
- // Pruning: manteniamo in RAM solo le ultime 6 ore (12 slot)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "6.2.7",
3
+ "version": "6.2.8",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/radar.html CHANGED
@@ -417,21 +417,22 @@
417
417
  }
418
418
 
419
419
  // --- 4. MOTORE DI CALCOLO DELL'ANELLO 1 (Presente Mobile) ---
420
- function calculateActive30mRing() {
421
- const now = Date.now();
422
- const start30m = now - 1800000;
420
+ function calculateActive30mRing() {
421
+ const now = Date.now();
422
+ const start30m = now - 1800000;
423
423
 
424
- const twdRecent = store.twdMinuteBuffer.filter(p => p.time >= start30m);
425
- const twsRecent = store.twsMinuteBuffer.filter(p => p.time >= start30m);
424
+ const twdRecent = store.twdMinuteBuffer.filter(p => p.time >= start30m);
425
+ const twsRecent = store.twsMinuteBuffer.filter(p => p.time >= start30m);
426
426
 
427
- if (twdRecent.length === 0) return null;
427
+ if (twdRecent.length === 0) return null;
428
428
 
429
- const twsVals = twsRecent.map(p => p.val).filter(v => isFinite(v));
430
- const maxTws = twsVals.length > 0 ? Math.max(...twsVals) : 0;
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
- if (maxTws < CALM_THRESHOLD_KTS) {
433
- return { twsPeak: maxTws, twdMin: 0, twdMax: 360, isCalm: true };
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
- const finalMaxDeg = Math.round(radToDeg((finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2)));
463
-
464
- return {
465
- twdMin: finalMinDeg,
466
- twdMax: finalMaxDeg,
467
- twsPeak: maxTws,
468
- isCalm: false
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
- for (let i = 1; i <= 6; i++) {
646
- const targetTimestamp = current30mSlot - (i * 1800000);
647
- const matchedSlot = windRadarSlots.find(s => s.timestamp === targetTimestamp);
648
-
649
- if (matchedSlot) {
650
- radarDataList.push({
651
- twdMin: Math.round(radToDeg(matchedSlot.twdMin)),
652
- twdMax: Math.round(radToDeg(matchedSlot.twdMax)),
653
- twsPeak: matchedSlot.twsPeak,
654
- isCalm: matchedSlot.twsPeak < CALM_THRESHOLD_KTS
655
- });
656
- } else {
657
- radarDataList.push(null);
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
- const grad = getChordAlignedGradient(gradId, radius, data.twsPeak, data.twdMin, data.twdMax);
689
- let strokeColor = '';
690
-
691
- if (grad.type === 'gradient') {
692
- defsContainer.innerHTML += grad.xml;
693
- strokeColor = grad.url;
694
- } else {
695
- strokeColor = grad.color;
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
- const pathData = describeArc(200, 200, radius, data.twdMin, data.twdMax);
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) {