@sailingrotevista/rotevista-dash 7.0.1 → 7.0.2
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 +95 -13
- package/charts.js +1 -0
- package/gauge.js +1 -0
- package/index.html +139 -98
- package/index.js +8 -4
- package/package.json +1 -1
- package/style.css +37 -0
- package/utils.js +1 -0
- package/weather-radar.js +281 -0
- /package/{test.html → debug_signalk_connection.html} +0 -0
- /package/{radaar debug.html → debug_weather_radar.html} +0 -0
- /package/{radar.html → sample_radar.html} +0 -0
package/app.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Gestisce: Medie Vettoriali, Deviazione Standard, Trend Strategico dinamico,
|
|
8
8
|
* Memoria UI persistente, Modalità Hercules, Focus Split Screen e
|
|
9
9
|
* Rendering Grafico basato sul Tempo Reale (Timeline e Gap Handling).
|
|
10
|
+
*file app.js
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
// ==========================================================================
|
|
@@ -43,6 +44,7 @@ const DASH_VERSION = "6.0"; // Major Update: Server-Side History RAM Logging (Pr
|
|
|
43
44
|
let simulationMode = false;
|
|
44
45
|
let displayModeSog = 'SOG';
|
|
45
46
|
let displayModeTws = 'TWS';
|
|
47
|
+
let activeInstrument = 'gauge'; // Modalità di default all'avvio: 'gauge' (analogico) o 'radar' (storico)
|
|
46
48
|
let socket, renderInterval, simInterval;
|
|
47
49
|
let lastAvgUIUpdate = 0; // Chirurgico: Rimosse audioCtx e lastAlarmTime poiché sono già dichiarate in utils.js
|
|
48
50
|
|
|
@@ -663,13 +665,15 @@ function startDisplayLoop() {
|
|
|
663
665
|
}
|
|
664
666
|
}
|
|
665
667
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
668
|
+
// --- AGGIORNAMENTO DELLA BUSSOLA CENTRALE (BATTERY SAVER A 1Hz) ---
|
|
669
|
+
if (activeInstrument === 'gauge') {
|
|
670
|
+
updateCentralGauge(store, ui, now, isNavigating, sogKts, stwKts, rawAws, awsVal);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// --- SLOW TIER (Salvataggio stato ogni 10 secondi) ---
|
|
674
|
+
if (lastAvgUIUpdate++ % 10 === 0) {
|
|
675
|
+
saveDashboardState();
|
|
676
|
+
}
|
|
673
677
|
|
|
674
678
|
if (lastAvgUIUpdate % 3 === 0) {
|
|
675
679
|
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averaging.longWindow * 2, false);
|
|
@@ -801,7 +805,7 @@ async function watchConfigChanges() {
|
|
|
801
805
|
}
|
|
802
806
|
|
|
803
807
|
/**
|
|
804
|
-
* Recupera lo storico dei grafici pre-popolato dal server Signal K (Pro v6.0)
|
|
808
|
+
* Recupera lo storico dei grafici e dei radar pre-popolato dal server Signal K (Pro v6.0)
|
|
805
809
|
*/
|
|
806
810
|
async function fetchServerHistory() {
|
|
807
811
|
try {
|
|
@@ -815,6 +819,12 @@ async function fetchServerHistory() {
|
|
|
815
819
|
store.histories[key] = data[key];
|
|
816
820
|
}
|
|
817
821
|
}
|
|
822
|
+
// Sincronizza i dati specifici del radar storici, previsionali e i buffer minuto per minuto
|
|
823
|
+
if (data.windRadarSlots) store.windRadarSlots = data.windRadarSlots;
|
|
824
|
+
if (data.futureForecast) store.futureForecast = data.futureForecast;
|
|
825
|
+
if (data.twd) store.twdMinuteBuffer = data.twd;
|
|
826
|
+
if (data.tws) store.twsMinuteBuffer = data.tws;
|
|
827
|
+
|
|
818
828
|
// --- SILLABAZIONE STRATEGICA DELLA BUSSOLA METEO (TWD) ---
|
|
819
829
|
// Se il server ci invia lo storico del TWD, lo inseriamo calcolando i seni e coseni per i vettori
|
|
820
830
|
if (data.twd && data.twd.length > 0) {
|
|
@@ -825,7 +835,7 @@ async function fetchServerHistory() {
|
|
|
825
835
|
cos: Math.cos(p.val)
|
|
826
836
|
}));
|
|
827
837
|
console.log(`📈 Memoria strategica TWD sincronizzata dal server (${data.twd.length} punti).`);
|
|
828
|
-
|
|
838
|
+
}
|
|
829
839
|
console.log("📈 Storico dei grafici pre-popolato caricato dal server.");
|
|
830
840
|
}
|
|
831
841
|
} catch (err) {
|
|
@@ -1100,29 +1110,101 @@ window.addEventListener('contextmenu', e => e.preventDefault(), true);
|
|
|
1100
1110
|
|
|
1101
1111
|
async function init() {
|
|
1102
1112
|
loadDashboardState();
|
|
1103
|
-
initCompassTicks(); // Genera i ticks sul quadrante usando il modulo gauge.js
|
|
1104
1113
|
|
|
1114
|
+
// Disegna la grafica statica delle tacche di calibrazione di entrambi gli strumenti
|
|
1115
|
+
initCompassTicks(); // Tacche del Wind Gauge analogico (gauge.js)
|
|
1116
|
+
initRadarTicks(); // Tacche del Wind Radar storico (weather-radar.js)
|
|
1117
|
+
|
|
1118
|
+
// 1. COMANDO TATTICO: Gestore Box TWD (Pressione prolungata -> Radar | Tocco rapido in modalità Radar -> Torna a Gauge)
|
|
1119
|
+
const twdBox = document.querySelector('.box-twd');
|
|
1120
|
+
if (twdBox) {
|
|
1121
|
+
let twdPressTimer = null;
|
|
1122
|
+
let longPressTriggered = false; // Flag di controllo della pressione prolungata
|
|
1123
|
+
|
|
1124
|
+
twdBox.addEventListener('pointerdown', (e) => {
|
|
1125
|
+
longPressTriggered = false;
|
|
1126
|
+
if (activeInstrument === 'gauge') {
|
|
1127
|
+
twdPressTimer = setTimeout(() => {
|
|
1128
|
+
activeInstrument = 'radar';
|
|
1129
|
+
document.getElementById('wind-gauge').style.display = 'none';
|
|
1130
|
+
document.getElementById('wind-radar').style.display = 'block';
|
|
1131
|
+
renderRadar(); // Disegna immediatamente il radar all'attivazione
|
|
1132
|
+
twdPressTimer = null;
|
|
1133
|
+
longPressTriggered = true; // Segnala che la transizione al radar è avvenuta con successo
|
|
1134
|
+
}, 1000);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
twdBox.addEventListener('pointerup', () => {
|
|
1138
|
+
if (activeInstrument === 'gauge') {
|
|
1139
|
+
if (twdPressTimer) {
|
|
1140
|
+
clearTimeout(twdPressTimer);
|
|
1141
|
+
twdPressTimer = null;
|
|
1142
|
+
}
|
|
1143
|
+
} else if (activeInstrument === 'radar') {
|
|
1144
|
+
if (longPressTriggered) {
|
|
1145
|
+
// Se l'evento di rilascio appartiene al tocco prolungato che ha appena attivato il radar, lo ignoriamo
|
|
1146
|
+
longPressTriggered = false;
|
|
1147
|
+
} else {
|
|
1148
|
+
// Altrimenti è un tocco rapido indipendente: torna alla bussola analogica
|
|
1149
|
+
activeInstrument = 'gauge';
|
|
1150
|
+
document.getElementById('wind-radar').style.display = 'none';
|
|
1151
|
+
document.getElementById('wind-gauge').style.display = 'block';
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
twdBox.addEventListener('pointerleave', () => {
|
|
1156
|
+
if (twdPressTimer) {
|
|
1157
|
+
clearTimeout(twdPressTimer);
|
|
1158
|
+
twdPressTimer = null;
|
|
1159
|
+
}
|
|
1160
|
+
longPressTriggered = false;
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// 2. COMANDO TATTICO: Click in qualsiasi punto del radar per tornare all'analogico
|
|
1165
|
+
const windRadarSvg = document.getElementById('wind-radar');
|
|
1166
|
+
if (windRadarSvg) {
|
|
1167
|
+
windRadarSvg.addEventListener('pointerup', () => {
|
|
1168
|
+
if (activeInstrument === 'radar') {
|
|
1169
|
+
activeInstrument = 'gauge';
|
|
1170
|
+
windRadarSvg.style.display = 'none';
|
|
1171
|
+
document.getElementById('wind-gauge').style.display = 'block';
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1105
1176
|
// Rileviamo se siamo sul Mac tramite file:// (Ambiente di sviluppo locale)
|
|
1106
1177
|
const isLocalFile = (window.location.protocol === 'file:');
|
|
1107
1178
|
|
|
1108
|
-
//
|
|
1179
|
+
// 3. CARICAMENTO STORICO GRAFICI E RADAR REALI DAL CERBO GX
|
|
1109
1180
|
try {
|
|
1110
1181
|
await fetchServerHistory();
|
|
1111
1182
|
} catch (err) {
|
|
1112
1183
|
console.warn("⚠️ Impossibile caricare lo storico reale dal server.");
|
|
1113
1184
|
}
|
|
1114
1185
|
|
|
1115
|
-
//
|
|
1186
|
+
// 4. CARICAMENTO CONFIGURAZIONI REALI (Bypassato su Mac per preservare i tuoi test!)
|
|
1116
1187
|
if (!isLocalFile) {
|
|
1117
1188
|
await fetchServerConfig();
|
|
1118
1189
|
} else {
|
|
1119
|
-
// Mantiene la CONFIG locale di app.js per farti fare le prove delle scale sul Mac
|
|
1120
1190
|
console.log("🎮 Esecuzione locale file://: utilizzo delle calibrazioni di CONFIG locali di debug.");
|
|
1121
1191
|
}
|
|
1122
1192
|
|
|
1123
1193
|
startDisplayLoop();
|
|
1124
1194
|
connect(); // Si collegherà in tempo reale al WebSocket reale della barca
|
|
1125
1195
|
|
|
1196
|
+
// 5. POLL LENTO (15 secondi): aggiorna i dati radar in background e, se attivo, li ridisegna
|
|
1197
|
+
setInterval(async () => {
|
|
1198
|
+
try {
|
|
1199
|
+
await fetchServerHistory();
|
|
1200
|
+
if (activeInstrument === 'radar') {
|
|
1201
|
+
renderRadar();
|
|
1202
|
+
}
|
|
1203
|
+
} catch (err) {
|
|
1204
|
+
console.warn("⚠️ Errore aggiornamento periodico storico:", err);
|
|
1205
|
+
}
|
|
1206
|
+
}, 15000);
|
|
1207
|
+
|
|
1126
1208
|
// Controlla le modifiche di configurazione sul Cerbo solo se non siamo sul Mac via file://
|
|
1127
1209
|
if (!isLocalFile) {
|
|
1128
1210
|
setInterval(watchConfigChanges, 10000);
|
package/charts.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* ==========================================================================
|
|
6
6
|
* Gestisce l'adattamento delle scale dei grafici, l'arrotondamento dei limiti
|
|
7
7
|
* (Snap a griglia) e la generazione dinamica delle curve SVG.
|
|
8
|
+
* file charts.js
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
// --- 1. MOTORE DI CALCOLO DELLE SCALE (Snap a griglia & Protezione Profondità) ---
|
package/gauge.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* ==========================================================================
|
|
5
5
|
* Gestisce l'aggiornamento grafico dei puntatori analogici (AWA, TWA),
|
|
6
6
|
* dello scarroccio (Leeway), della rotta (Track) e dei trend della bussola.
|
|
7
|
+
* file gauge,js
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
// 1. VARIABILI DI STATO DELLE ROTAZIONI (Estratte da app.js)
|
package/index.html
CHANGED
|
@@ -63,104 +63,144 @@
|
|
|
63
63
|
</div>
|
|
64
64
|
|
|
65
65
|
<!-- BUSSOLA CENTRALE -->
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
66
|
+
<div class="box-gauge">
|
|
67
|
+
<!-- STATO CONNESSIONE -->
|
|
68
|
+
<div id="status" class="offline">OFFLINE</div>
|
|
69
|
+
|
|
70
|
+
<!-- BUSSOLA 1: ANALOGICA STANDARD (Visibile all'avvio) -->
|
|
71
|
+
<svg id="wind-gauge" viewBox="35 38 330 395" preserveAspectRatio="xMidYMid meet">
|
|
72
|
+
<defs>
|
|
73
|
+
<clipPath id="boat-clip"><circle cx="200" cy="200" r="50" /></clipPath>
|
|
74
|
+
<linearGradient id="axiom-grad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
75
|
+
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
|
76
|
+
<stop offset="100%" style="stop-color:#888888;stop-opacity:1" />
|
|
77
|
+
</linearGradient>
|
|
78
|
+
<linearGradient id="leeway-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
79
|
+
<stop offset="0%" style="stop-color:#ff0000;stop-opacity:1" />
|
|
80
|
+
<stop offset="25%" style="stop-color:#ff8800;stop-opacity:1" />
|
|
81
|
+
<stop offset="50%" style="stop-color:#00ff00;stop-opacity:1" />
|
|
82
|
+
<stop offset="75%" style="stop-color:#ff8800;stop-opacity:1" />
|
|
83
|
+
<stop offset="100%" style="stop-color:#ff0000;stop-opacity:1" />
|
|
84
|
+
</linearGradient>
|
|
85
|
+
<linearGradient id="leeway-night-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
86
|
+
<stop offset="0%" style="stop-color:#ff0000;stop-opacity:1" />
|
|
87
|
+
<stop offset="50%" style="stop-color:#330000;stop-opacity:1" />
|
|
88
|
+
<stop offset="100%" style="stop-color:#ff0000;stop-opacity:1" />
|
|
89
|
+
</linearGradient>
|
|
90
|
+
<clipPath id="leeway-clip">
|
|
91
|
+
<rect id="leeway-mask-rect" x="125" y="0" width="0" height="12" rx="2" />
|
|
92
|
+
</clipPath>
|
|
93
|
+
<filter id="center-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
94
|
+
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#aaaaaa" flood-opacity="0.5" />
|
|
95
|
+
</filter>
|
|
96
|
+
</defs>
|
|
97
|
+
|
|
98
|
+
<circle cx="200" cy="200" r="160" fill="#050505" />
|
|
99
|
+
<circle cx="200" cy="200" r="125" fill="#121212" />
|
|
100
|
+
|
|
101
|
+
<path d="M 82.0 101.0 A 154 154 0 0 1 142.3 57.2" fill="none" stroke="#ff0000" stroke-width="12" />
|
|
102
|
+
<path d="M 257.7 57.2 A 154 154 0 0 1 318.0 101.0" fill="none" stroke="#00ff00" stroke-width="12" />
|
|
103
|
+
<path d="M 265.1 339.6 A 154 154 0 0 1 134.9 339.6" fill="none" stroke="#ff8800" stroke-width="12" />
|
|
104
|
+
|
|
105
|
+
<g id="ticks"></g>
|
|
106
|
+
|
|
107
|
+
<g id="tick-labels" fill="#bbb" text-anchor="middle" dominant-baseline="middle" font-family="Arial" font-weight="bold">
|
|
108
|
+
<text font-size="16" transform="translate(200, 74)">0</text>
|
|
109
|
+
<text font-size="16" transform="translate(326, 200) rotate(90)">90</text>
|
|
110
|
+
<text font-size="16" transform="translate(74, 200) rotate(-90)">90</text>
|
|
111
|
+
<text font-size="16" transform="translate(200, 326) rotate(180)">180</text>
|
|
112
|
+
<text font-size="11" transform="translate(262.5, 91.7) rotate(30)">30</text>
|
|
113
|
+
<text font-size="11" transform="translate(308.3, 137.5) rotate(60)">60</text>
|
|
114
|
+
<text font-size="11" transform="translate(308.3, 262.5) rotate(120)">120</text>
|
|
115
|
+
<text font-size="11" transform="translate(262.5, 308.3) rotate(150)">150</text>
|
|
116
|
+
<text font-size="11" transform="translate(137.5, 91.7) rotate(-30)">30</text>
|
|
117
|
+
<text font-size="11" transform="translate(91.7, 137.5) rotate(-60)">60</text>
|
|
118
|
+
<text font-size="11" transform="translate(91.7, 262.5) rotate(-120)">120</text>
|
|
119
|
+
<text font-size="11" transform="translate(137.5, 308.3) rotate(-150)">150</text>
|
|
120
|
+
</g>
|
|
121
|
+
|
|
122
|
+
<g id="track-pointer" transform="rotate(0, 200, 200)">
|
|
123
|
+
<path d="M200,42 L194,58 L206,58 Z" fill="#007aff" stroke="#fff" stroke-width="0.5" />
|
|
124
|
+
</g>
|
|
125
|
+
|
|
126
|
+
<circle id="fullscreen-hotspot" cx="200" cy="200" r="55" fill="#181818" stroke="#333" stroke-width="1" filter="url(#center-glow)" cursor="pointer" />
|
|
127
|
+
|
|
128
|
+
<path id="boat-icon" d="M200,150 Q165,185 170,250 Q165,190 200,173 Q235,190 230,250 Q235,185 200,150 Z"
|
|
129
|
+
fill="url(#axiom-grad)" transform="translate(0, 5)" clip-path="url(#boat-clip)" style="pointer-events: none;" />
|
|
130
|
+
|
|
131
|
+
<g id="aws-display-group" transform="translate(200, 265)">
|
|
132
|
+
<text x="0" y="0" fill="#777" font-size="10" font-weight="bold" text-anchor="middle" text-transform="uppercase">Apparent Wind kts</text>
|
|
133
|
+
<text id="aws-val-svg" x="0" y="42" fill="#fff" font-size="52" font-weight="bold" text-anchor="middle">0.0</text>
|
|
134
|
+
</g>
|
|
135
|
+
|
|
136
|
+
<g id="awa-pointer" transform="rotate(0, 200, 200)" opacity="0.9">
|
|
137
|
+
<path d="M 200,70 L 213,95 L 200,145 L 187,95 Z" fill="#ff8c00" stroke="#000" stroke-width="1" />
|
|
138
|
+
<text x="200" y="90" fill="#000" font-size="10" font-weight="900" text-anchor="middle" font-family="Arial Black">A</text>
|
|
139
|
+
</g>
|
|
140
|
+
|
|
141
|
+
<g id="twa-pointer" transform="rotate(0, 200, 200)" opacity="0.9">
|
|
142
|
+
<path d="M 200,92 A 8,8 0 0 1 208,100 C 208,108 200,128 200,128 C 200,128 192,108 192,100 A 8,8 0 0 1 200,92 Z"
|
|
143
|
+
fill="#ffff00" stroke="#000" stroke-width="0.8" />
|
|
144
|
+
<text x="200" y="106" fill="#000" font-size="9" font-weight="900" text-anchor="middle" font-family="Arial Black">T</text>
|
|
145
|
+
<circle id="trend-gauge-cw" cx="215" cy="110" r="4" fill="#bbb" />
|
|
146
|
+
<circle id="trend-gauge-ccw" cx="185" cy="110" r="4" fill="#bbb" />
|
|
147
|
+
</g>
|
|
148
|
+
|
|
149
|
+
<g transform="translate(75, 395)">
|
|
150
|
+
<text x="125" y="-12" id="leeway-val" fill="#aaa" font-size="11" text-anchor="middle" font-weight="bold">LEEWAY: 0.0°</text>
|
|
151
|
+
<rect x="0" y="0" width="250" height="12" fill="#222" rx="3" />
|
|
152
|
+
<rect x="0" y="0" width="250" height="12" fill="url(#leeway-grad)" clip-path="url(#leeway-clip)" rx="3" />
|
|
153
|
+
<g stroke="#555" stroke-width="1">
|
|
154
|
+
<line x1="0" y1="-2" x2="0" y2="14" /><line x1="62.5" y1="2" x2="62.5" y2="10" />
|
|
155
|
+
<line x1="125" y1="-2" x2="125" y2="14" /><line x1="187.5" y1="2" x2="187.5" y2="10" />
|
|
156
|
+
<line x1="250" y1="-2" x2="250" y2="14" />
|
|
157
|
+
</g>
|
|
158
|
+
<g fill="#555" font-size="8" text-anchor="middle" font-weight="bold">
|
|
159
|
+
<text x="0" y="24">-20°</text><text x="62.5" y="24">-10</text>
|
|
160
|
+
<text x="125" y="24">0°</text><text x="187.5" y="24">10</text>
|
|
161
|
+
<text x="250" y="24">20°</text>
|
|
162
|
+
</g>
|
|
163
|
+
</g>
|
|
164
|
+
</svg>
|
|
165
|
+
|
|
166
|
+
<!-- BUSSOLA 2: RADAR HISTORICAL (Nascosto all'avvio) -->
|
|
167
|
+
<svg id="wind-radar" viewBox="35 38 330 395" preserveAspectRatio="xMidYMid meet" style="display: none;">
|
|
168
|
+
<defs id="radar-gradients">
|
|
169
|
+
<clipPath id="radar-boat-clip"><circle cx="200" cy="200" r="50" /></clipPath>
|
|
170
|
+
<filter id="radar-center-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
171
|
+
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#aaaaaa" flood-opacity="0.5" />
|
|
172
|
+
</filter>
|
|
173
|
+
</defs>
|
|
174
|
+
|
|
175
|
+
<circle cx="200" cy="200" r="160" fill="rgb(252, 252, 252)" />
|
|
176
|
+
<circle cx="200" cy="200" r="125" fill="rgb(240, 240, 240)" />
|
|
177
|
+
|
|
178
|
+
<g id="radar-ticks"></g>
|
|
179
|
+
|
|
180
|
+
<g id="radar-tick-labels" fill="#000000" text-anchor="middle" dominant-baseline="middle" font-family="Arial" font-weight="bold">
|
|
181
|
+
<text font-size="16" transform="translate(200, 74)">N</text>
|
|
182
|
+
<text font-size="16" transform="translate(326, 200) rotate(90)">E</text>
|
|
183
|
+
<text font-size="16" transform="translate(74, 200) rotate(-90)">W</text>
|
|
184
|
+
<text font-size="16" transform="translate(200, 326) rotate(180)">S</text>
|
|
185
|
+
|
|
186
|
+
<text font-size="11" transform="translate(262.5, 91.7) rotate(30)">30</text>
|
|
187
|
+
<text font-size="11" transform="translate(308.3, 137.5) rotate(60)">60</text>
|
|
188
|
+
<text font-size="11" transform="translate(308.3, 262.5) rotate(120)">120</text>
|
|
189
|
+
<text font-size="11" transform="translate(262.5, 308.3) rotate(150)">150</text>
|
|
190
|
+
<text font-size="11" transform="translate(137.5, 91.7) rotate(-30)">330</text>
|
|
191
|
+
<text font-size="11" transform="translate(91.7, 137.5) rotate(-60)">300</text>
|
|
192
|
+
<text font-size="11" transform="translate(91.7, 262.5) rotate(-120)">240</text>
|
|
193
|
+
<text font-size="11" transform="translate(137.5, 308.3) rotate(-150)">210</text>
|
|
194
|
+
</g>
|
|
195
|
+
|
|
196
|
+
<circle id="ora-orbit" cx="200" cy="200" r="67.2" fill="none" stroke="#cfd8dc" stroke-width="1.2" stroke-dasharray="4, 4" />
|
|
197
|
+
<g id="radar-rings"></g>
|
|
198
|
+
|
|
199
|
+
<circle id="radar-hotspot" cx="200" cy="200" r="55" fill="rgb(238, 238, 238)" stroke="#e0e0e0" stroke-width="1" filter="url(#radar-center-glow)" cursor="pointer" />
|
|
200
|
+
<path id="radar-boat-icon" d="M200,150 Q165,185 170,250 Q165,190 200,173 Q235,190 230,250 Q235,185 200,150 Z"
|
|
201
|
+
fill="#000000" transform="translate(0, 5)" clip-path="url(#radar-boat-clip)" style="pointer-events: none;" />
|
|
202
|
+
</svg>
|
|
203
|
+
</div>
|
|
164
204
|
|
|
165
205
|
<!-- COLONNA DESTRA -->
|
|
166
206
|
<!-- NOTA: Etichetta a SX e Unità a DX nel codice, il CSS usa row-reverse per specchiarli sul bordo -->
|
|
@@ -218,6 +258,7 @@
|
|
|
218
258
|
<script src="utils.js"></script>
|
|
219
259
|
<script src="charts.js"></script>
|
|
220
260
|
<script src="gauge.js"></script>
|
|
261
|
+
<script src="weather-radar.js"></script>
|
|
221
262
|
<script src="app.js?v=6.0"></script>
|
|
222
263
|
</body>
|
|
223
264
|
</html>
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* ==========================================================================
|
|
5
5
|
* Definisce l'interfaccia di configurazione in Signal K Admin e crea
|
|
6
6
|
* gli endpoint pubblici per la Dashboard, mantenendo lo storico in RAM.
|
|
7
|
+
* file index.js
|
|
7
8
|
*/
|
|
8
9
|
const https = require('https'); // Importazione del modulo HTTPS nativo di Node.js
|
|
9
10
|
|
|
@@ -172,20 +173,23 @@ module.exports = function (app) {
|
|
|
172
173
|
raw[path] = val; // BUG RISOLTO: Acquisizione dell'AWA mancante inserita!
|
|
173
174
|
}
|
|
174
175
|
else if (path === 'environment.wind.speedTrue') {
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
lastNativeTwsTime = now; // Rilevato TWS nativo della centralina!
|
|
177
|
+
const cleanVal = (val && typeof val === 'object' && val.value !== undefined) ? val.value : val;
|
|
178
|
+
manageHistory('tws', cleanVal * 1.94384);
|
|
177
179
|
}
|
|
178
180
|
// --- DECODIFICA PRUA MAGNETICA SERVER-SIDE ---
|
|
179
181
|
else if (path === 'navigation.headingMagnetic') {
|
|
180
182
|
const hasTrueHdg = raw['navigation.headingTrue'] !== undefined;
|
|
181
183
|
if (!hasTrueHdg) {
|
|
182
184
|
const variation = raw['navigation.magneticVariation'] || 0;
|
|
183
|
-
|
|
185
|
+
const cleanVal = (val && typeof val === 'object' && val.value !== undefined) ? val.value : val;
|
|
186
|
+
raw['navigation.headingTrue'] = (cleanVal + variation + 2 * Math.PI) % (2 * Math.PI);
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
else if (path === 'environment.wind.directionTrue') {
|
|
187
190
|
lastNativeTwdTime = now; // Rilevato TWD nativo della centralina!
|
|
188
|
-
|
|
191
|
+
const cleanVal = (val && typeof val === 'object' && val.value !== undefined) ? val.value : val;
|
|
192
|
+
manageHistory('twd', cleanVal);
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
// 2. Calcolo combinato di FALLBACK (Si attiva solo se la centralina non invia TWS/TWD nativi)
|
package/package.json
CHANGED
package/style.css
CHANGED
|
@@ -520,3 +520,40 @@ body.night-mode {
|
|
|
520
520
|
.night-mode .graph-wrapper polygon[style*="#0088cc"] {
|
|
521
521
|
fill: #33aadd !important;
|
|
522
522
|
}
|
|
523
|
+
|
|
524
|
+
/* ==========================================================================
|
|
525
|
+
12. INTEGRAZIONE DUAL-SVG COMPASS & RADAR
|
|
526
|
+
========================================================================== */
|
|
527
|
+
#wind-gauge, #wind-radar {
|
|
528
|
+
width: 100%;
|
|
529
|
+
height: 100%;
|
|
530
|
+
max-height: 100%;
|
|
531
|
+
object-fit: contain;
|
|
532
|
+
transition: opacity 0.2s ease-in-out;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/* --- OVERRIDE NIGHT MODE PER IL WIND RADAR --- */
|
|
536
|
+
.night-mode #wind-radar circle {
|
|
537
|
+
fill: #000000 !important;
|
|
538
|
+
stroke: #220000 !important;
|
|
539
|
+
}
|
|
540
|
+
.night-mode #wind-radar circle:nth-of-type(2),
|
|
541
|
+
.night-mode #radar-hotspot {
|
|
542
|
+
fill: #050000 !important;
|
|
543
|
+
stroke: #330000 !important;
|
|
544
|
+
}
|
|
545
|
+
.night-mode #radar-tick-labels {
|
|
546
|
+
fill: #800000 !important;
|
|
547
|
+
}
|
|
548
|
+
.night-mode #radar-boat-icon {
|
|
549
|
+
fill: #220000 !important;
|
|
550
|
+
stroke: #ff0000 !important;
|
|
551
|
+
stroke-width: 0.8px !important;
|
|
552
|
+
opacity: 1 !important;
|
|
553
|
+
}
|
|
554
|
+
.night-mode #ora-orbit {
|
|
555
|
+
stroke: #330000 !important;
|
|
556
|
+
}
|
|
557
|
+
.night-mode #wind-radar circle[stroke="#b0bec5"] {
|
|
558
|
+
stroke: #440000 !important;
|
|
559
|
+
}
|
package/utils.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Sailing Dashboard Pro - Math, Conversions & Audio Utilities
|
|
4
4
|
* ==========================================================================
|
|
5
5
|
* Raccoglie le funzioni pure di calcolo vettoriale e sintesi sonora.
|
|
6
|
+
*file utils.js
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
// --- 1. CONVERSIONI STANDARD ---
|
package/weather-radar.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ==========================================================================
|
|
3
|
+
* Signal K Wind Radar - Vector Core and Compass Rendering Engine (Modular v6.0)
|
|
4
|
+
* ==========================================================================
|
|
5
|
+
* Autore: Sailing Rotevista
|
|
6
|
+
* Libreria grafica pura per il disegno della bussola radar storica (TWD).
|
|
7
|
+
* Condivide le risorse, lo stato e i flussi di dati generati da app.js.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// --- 1. PARAMETRI DI CALCOLO INTERNI ---
|
|
11
|
+
const CALM_THRESHOLD_KTS = 1.5;
|
|
12
|
+
const PRESSURE_FILTER_RATIO = 0.40;
|
|
13
|
+
const ringRadii = [59.0, 67.2, 75.4, 83.6, 91.8, 100.0, 108.2, 116.4];
|
|
14
|
+
const ARC_STROKE_WIDTH = 5.0;
|
|
15
|
+
const BORDER_STROKE_WIDTH = ARC_STROKE_WIDTH + 2; // 7px
|
|
16
|
+
|
|
17
|
+
// --- 2. FUNZIONI GEOMETRICHE DI SUPPORTO ---
|
|
18
|
+
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
|
19
|
+
const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
|
|
20
|
+
return {
|
|
21
|
+
x: centerX + (radius * Math.cos(angleInRadians)),
|
|
22
|
+
y: centerY + (radius * Math.sin(angleInRadians))
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function describeArc(centerX, centerY, radius, startAngle, endAngle) {
|
|
27
|
+
const start = polarToCartesian(centerX, centerY, radius, endAngle);
|
|
28
|
+
const end = polarToCartesian(centerX, centerY, radius, startAngle);
|
|
29
|
+
|
|
30
|
+
let arcSweep = endAngle - startAngle;
|
|
31
|
+
if (arcSweep < 0) arcSweep += 360;
|
|
32
|
+
|
|
33
|
+
const largeArcFlag = arcSweep <= 180 ? "0" : "1";
|
|
34
|
+
|
|
35
|
+
return [
|
|
36
|
+
"M", start.x, start.y,
|
|
37
|
+
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
|
|
38
|
+
].join(" ");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Genera dinamicamente le tacche dei gradi bussola per l'SVG del radar
|
|
42
|
+
function initRadarTicks() {
|
|
43
|
+
const c = document.getElementById('radar-ticks');
|
|
44
|
+
if (c) {
|
|
45
|
+
c.innerHTML = "";
|
|
46
|
+
for (let i = 0; i < 360; i += 10) {
|
|
47
|
+
const l = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
48
|
+
const m = i % 30 === 0;
|
|
49
|
+
l.setAttribute("x1", "200"); l.setAttribute("y1", "40"); l.setAttribute("x2", "200"); l.setAttribute("y2", (m ? 60 : 50));
|
|
50
|
+
l.setAttribute("stroke", m ? "#000" : "#bbb"); l.setAttribute("stroke-width", m ? "2" : "1");
|
|
51
|
+
l.setAttribute("transform", `rotate(${i}, 200, 200)`);
|
|
52
|
+
c.appendChild(l);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- 3. MOTORE DI CALCOLO DELL'ANELLO 1 (Presente Mobile) ---
|
|
58
|
+
function calculateActive30mRing() {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
const start30m = now - 1800000;
|
|
61
|
+
|
|
62
|
+
const twdRecent = (store.twdMinuteBuffer || []).filter(p => p.time >= start30m);
|
|
63
|
+
const twsRecent = (store.twsMinuteBuffer || []).filter(p => p.time >= start30m);
|
|
64
|
+
|
|
65
|
+
if (twdRecent.length === 0) return null;
|
|
66
|
+
|
|
67
|
+
const twsVals = twsRecent.map(p => p.val).filter(v => isFinite(v));
|
|
68
|
+
const maxTws = twsVals.length > 0 ? Math.max(...twsVals) : 0;
|
|
69
|
+
const minTws = twsVals.length > 0 ? Math.min(...twsVals) : 0;
|
|
70
|
+
|
|
71
|
+
if (maxTws < CALM_THRESHOLD_KTS) {
|
|
72
|
+
return { twsPeak: maxTws, twsMin: minTws, twdMin: 0, twdMax: 360, isCalm: true };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let allAngles = [];
|
|
76
|
+
twdRecent.forEach(p => {
|
|
77
|
+
allAngles.push(p.val);
|
|
78
|
+
allAngles.push(p.min);
|
|
79
|
+
allAngles.push(p.max);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let sumSin = 0; let sumCos = 0;
|
|
83
|
+
allAngles.forEach(a => { sumSin += Math.sin(a); sumCos += Math.cos(a); });
|
|
84
|
+
const avgAngle = Math.atan2(sumSin, sumCos);
|
|
85
|
+
const finalAvg = (avgAngle + Math.PI * 2) % (Math.PI * 2);
|
|
86
|
+
|
|
87
|
+
let diffs = allAngles.map(a => {
|
|
88
|
+
let diff = a - finalAvg;
|
|
89
|
+
return Math.atan2(Math.sin(diff), Math.cos(diff));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
diffs.sort((a, b) => a - b);
|
|
93
|
+
const trimCount = Math.floor(diffs.length * 0.05);
|
|
94
|
+
const activeDiffs = diffs.slice(trimCount, diffs.length - trimCount);
|
|
95
|
+
const finalDiffs = activeDiffs.length > 0 ? activeDiffs : diffs;
|
|
96
|
+
|
|
97
|
+
const minDiff = Math.min(...finalDiffs);
|
|
98
|
+
const maxDiff = Math.max(...finalDiffs);
|
|
99
|
+
|
|
100
|
+
const finalMinDeg = Math.round(radToDeg((finalAvg + minDiff + Math.PI * 2) % (Math.PI * 2)));
|
|
101
|
+
const finalMaxDeg = Math.round(radToDeg((finalAvg + maxDiff + Math.PI * 2) % (Math.PI * 2)));
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
twdMin: finalMinDeg,
|
|
105
|
+
twdMax: finalMaxDeg,
|
|
106
|
+
twsPeak: maxTws,
|
|
107
|
+
twsMin: minTws,
|
|
108
|
+
isCalm: false
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- 4. MOTORE GRAFICO DI DISEGNO DEL RADAR ---
|
|
113
|
+
function renderRadar() {
|
|
114
|
+
const ringsContainer = document.getElementById('radar-rings');
|
|
115
|
+
const defsContainer = document.getElementById('radar-gradients');
|
|
116
|
+
|
|
117
|
+
if (!ringsContainer || !defsContainer) return; // Protezione se l'SVG del radar non è presente nel DOM
|
|
118
|
+
|
|
119
|
+
defsContainer.innerHTML = `
|
|
120
|
+
<clipPath id="radar-boat-clip"><circle cx="200" cy="200" r="50" /></clipPath>
|
|
121
|
+
<filter id="radar-center-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
122
|
+
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#aaaaaa" flood-opacity="0.5" />
|
|
123
|
+
</filter>
|
|
124
|
+
`;
|
|
125
|
+
ringsContainer.innerHTML = '';
|
|
126
|
+
|
|
127
|
+
const oraOrbit = document.getElementById('ora-orbit');
|
|
128
|
+
if (oraOrbit) oraOrbit.setAttribute("r", ringRadii[1]);
|
|
129
|
+
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const current30mSlot = Math.floor(now / 1800000) * 1800000;
|
|
132
|
+
|
|
133
|
+
const radarDataList = [];
|
|
134
|
+
|
|
135
|
+
// 1. ANELLO 0: Previsione Futura Open-Meteo
|
|
136
|
+
if (store.futureForecast) {
|
|
137
|
+
const twdDeg = Math.round(radToDeg(store.futureForecast.twd));
|
|
138
|
+
radarDataList.push({
|
|
139
|
+
twdMin: (twdDeg - 20 + 360) % 360,
|
|
140
|
+
twdMax: (twdDeg + 20 + 360) % 360,
|
|
141
|
+
twsPeak: store.futureForecast.tws,
|
|
142
|
+
isFuture: true
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
radarDataList.push(null);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 2. ANELLO 1: Presente Mobile (Real-Time Client-Side)
|
|
149
|
+
const activeRing = calculateActive30mRing();
|
|
150
|
+
radarDataList.push(activeRing);
|
|
151
|
+
|
|
152
|
+
// 3. ANELLI 2-7: Storico consolidato dal server
|
|
153
|
+
const slots = store.windRadarSlots || [];
|
|
154
|
+
for (let i = 1; i <= 6; i++) {
|
|
155
|
+
const targetTimestamp = current30mSlot - (i * 1800000);
|
|
156
|
+
const matchedSlot = slots.find(s => s.timestamp === targetTimestamp);
|
|
157
|
+
|
|
158
|
+
if (matchedSlot) {
|
|
159
|
+
radarDataList.push({
|
|
160
|
+
twdMin: Math.round(radToDeg(matchedSlot.twdMin)),
|
|
161
|
+
twdMax: Math.round(radToDeg(matchedSlot.twdMax)),
|
|
162
|
+
twsPeak: matchedSlot.twsPeak,
|
|
163
|
+
twsMin: matchedSlot.twsMin !== undefined ? matchedSlot.twsMin : matchedSlot.twsPeak,
|
|
164
|
+
isCalm: matchedSlot.twsPeak < CALM_THRESHOLD_KTS
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
radarDataList.push(null);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Disegno degli archi
|
|
172
|
+
radarDataList.forEach((data, index) => {
|
|
173
|
+
if (!data) return;
|
|
174
|
+
|
|
175
|
+
const radius = ringRadii[index];
|
|
176
|
+
const gradId = `chord-gradient-${index}`;
|
|
177
|
+
const opacityValue = 1;
|
|
178
|
+
|
|
179
|
+
if (data.isCalm || data.twsPeak < CALM_THRESHOLD_KTS) {
|
|
180
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
181
|
+
circle.setAttribute("cx", "200");
|
|
182
|
+
circle.setAttribute("cy", "200");
|
|
183
|
+
circle.setAttribute("r", radius);
|
|
184
|
+
circle.setAttribute("fill", "none");
|
|
185
|
+
circle.setAttribute("stroke", document.body.classList.contains('night-mode') ? "#440000" : "#b0bec5");
|
|
186
|
+
circle.setAttribute("stroke-width", "1.2");
|
|
187
|
+
circle.setAttribute("stroke-dasharray", "4, 4");
|
|
188
|
+
ringsContainer.appendChild(circle);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let strokeColor = '';
|
|
193
|
+
|
|
194
|
+
const getColorForSpeed = (tws) => {
|
|
195
|
+
const R1 = CONFIG.graphs.reef1 || 15;
|
|
196
|
+
const R2 = CONFIG.graphs.reef2 || 20;
|
|
197
|
+
const R3 = R2 + (R2 - R1);
|
|
198
|
+
if (tws < R1 * 0.4) return '#ffffff';
|
|
199
|
+
if (tws < R1 * 0.75) return '#00C851';
|
|
200
|
+
if (tws < R1) return '#ff9800';
|
|
201
|
+
if (tws < R2) return '#ffaa00';
|
|
202
|
+
if (tws < R2 + (R3 - R2) * 0.5) return '#ff3b30';
|
|
203
|
+
return '#9c27b0';
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const baseTws = data.isFuture && store.futureForecast ? store.futureForecast.tws : (data.twsMin !== undefined ? data.twsMin : data.twsPeak);
|
|
207
|
+
const peakTws = data.isFuture && store.futureForecast ? store.futureForecast.gust : data.twsPeak;
|
|
208
|
+
|
|
209
|
+
const baseColor = getColorForSpeed(baseTws);
|
|
210
|
+
const peakColor = getColorForSpeed(peakTws);
|
|
211
|
+
|
|
212
|
+
if (baseColor !== peakColor) {
|
|
213
|
+
const startPt = polarToCartesian(200, 200, radius, data.twdMax);
|
|
214
|
+
const endPt = polarToCartesian(200, 200, radius, data.twdMin);
|
|
215
|
+
|
|
216
|
+
const xml = `
|
|
217
|
+
<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">
|
|
218
|
+
<stop offset="0%" stop-color="${baseColor}" />
|
|
219
|
+
<stop offset="50%" stop-color="${peakColor}" />
|
|
220
|
+
<stop offset="100%" stop-color="${baseColor}" />
|
|
221
|
+
</linearGradient>
|
|
222
|
+
`;
|
|
223
|
+
defsContainer.innerHTML += xml;
|
|
224
|
+
strokeColor = `url(#${gradId})`;
|
|
225
|
+
} else {
|
|
226
|
+
strokeColor = baseColor;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const pathData = describeArc(200, 200, radius, data.twdMin, data.twdMax);
|
|
230
|
+
|
|
231
|
+
if (!data.isFuture) {
|
|
232
|
+
const borderPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
233
|
+
borderPath.setAttribute("d", pathData);
|
|
234
|
+
borderPath.setAttribute("fill", "none");
|
|
235
|
+
borderPath.setAttribute("stroke", "#000000");
|
|
236
|
+
borderPath.setAttribute("stroke-width", BORDER_STROKE_WIDTH);
|
|
237
|
+
borderPath.setAttribute("stroke-linecap", "round");
|
|
238
|
+
borderPath.setAttribute("opacity", opacityValue);
|
|
239
|
+
ringsContainer.appendChild(borderPath);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const mainPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
243
|
+
mainPath.setAttribute("d", pathData);
|
|
244
|
+
mainPath.setAttribute("fill", "none");
|
|
245
|
+
mainPath.setAttribute("stroke", strokeColor);
|
|
246
|
+
mainPath.setAttribute("stroke-width", ARC_STROKE_WIDTH);
|
|
247
|
+
mainPath.setAttribute("stroke-linecap", "round");
|
|
248
|
+
mainPath.setAttribute("opacity", data.isFuture ? "0.5" : opacityValue);
|
|
249
|
+
mainPath.id = data.isFuture ? "" : (index === 1 ? "active-present-arc" : "");
|
|
250
|
+
ringsContainer.appendChild(mainPath);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Disegno del LED lampeggiante del meteo-trend
|
|
254
|
+
if (activeRing && !activeRing.isCalm) {
|
|
255
|
+
const twdNow = getCircularAverageFromBuffer(store.longBuf.twd, 60000, false);
|
|
256
|
+
const strategicWindowMs = (isNavigating ? 15 : 60) * 60000;
|
|
257
|
+
const twdRef = getCircularAverageFromBuffer(store.longBuf.twd, strategicWindowMs, false);
|
|
258
|
+
|
|
259
|
+
if (twdNow && twdRef) {
|
|
260
|
+
let deltaMeteo = radToDeg((twdNow.val - twdRef.val + Math.PI * 3) % (Math.PI * 2) - Math.PI);
|
|
261
|
+
|
|
262
|
+
if (Math.abs(deltaMeteo) > 6.0) {
|
|
263
|
+
const isSouth = store.raw["navigation.position"] && store.raw["navigation.position"].latitude < 0;
|
|
264
|
+
let meteoColor = (!isSouth) ? (deltaMeteo < 0 ? "#00C851" : "#ff3b30") : (deltaMeteo > 0 ? "#00C851" : "#ff3b30");
|
|
265
|
+
|
|
266
|
+
const radiusAnello1 = ringRadii[1];
|
|
267
|
+
const angleTarget = deltaMeteo > 0 ? activeRing.twdMax : activeRing.twdMin;
|
|
268
|
+
const pt = polarToCartesian(200, 200, radiusAnello1, angleTarget);
|
|
269
|
+
|
|
270
|
+
const led = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
271
|
+
led.setAttribute("cx", pt.x.toFixed(1));
|
|
272
|
+
led.setAttribute("cy", pt.y.toFixed(1));
|
|
273
|
+
led.setAttribute("r", "5.5");
|
|
274
|
+
led.setAttribute("fill", meteoColor);
|
|
275
|
+
led.setAttribute("class", "is-trending");
|
|
276
|
+
led.setAttribute("filter", "url(#radar-center-glow)");
|
|
277
|
+
ringsContainer.appendChild(led);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|