@sailingrotevista/rotevista-dash 7.0.22 → 7.0.24
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 +139 -52
- package/charts.js +49 -13
- package/index.js +16 -8
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -216,16 +216,25 @@ function checkDepthAlarm(m) {
|
|
|
216
216
|
function computeTrueWind() {
|
|
217
217
|
const aws = store.raw["environment.wind.speedApparent"], awa = store.raw["environment.wind.angleApparent"];
|
|
218
218
|
const stw = store.raw["navigation.speedThroughWater"], sog = store.raw["navigation.speedOverGround"] || 0;
|
|
219
|
-
const hdg = store.raw["navigation.headingTrue"] || 0
|
|
219
|
+
const hdg = store.raw["navigation.headingTrue"] || 0; // Il COG qui non serve più per il calcolo stabile
|
|
220
220
|
if (aws === undefined || awa === undefined) return;
|
|
221
221
|
|
|
222
|
-
// Usiamo il tempo esatto di arrivo del pacchetto del vento per la coerenza dei buffer
|
|
223
222
|
const now = store.timestamps["environment.wind.speedApparent"] || Date.now();
|
|
224
223
|
|
|
225
|
-
//
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
// RILEVAMENTO "LOG BLOCCATO" (Fouled Paddlewheel)
|
|
225
|
+
const hasFreshStw = store.timestamps["navigation.speedThroughWater"] && (now - store.timestamps["navigation.speedThroughWater"] < 15000);
|
|
226
|
+
let speedRef = 0;
|
|
227
|
+
|
|
228
|
+
if (hasFreshStw && stw !== undefined) {
|
|
229
|
+
// Se la barca naviga a GPS (> 1.5 nodi) ma l'elichetta legge quasi zero (< 0.5 nodi), il log è sporco/bloccato.
|
|
230
|
+
if (sog > 0.77 && stw < 0.25) {
|
|
231
|
+
speedRef = sog; // Forza il backup su SOG ignorando lo "0" fittizio del log
|
|
232
|
+
} else {
|
|
233
|
+
speedRef = stw;
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
speedRef = sog;
|
|
237
|
+
}
|
|
229
238
|
|
|
230
239
|
// ==========================================================================
|
|
231
240
|
// 1. GESTIONE TWS (NATIVO vs FALLBACK)
|
|
@@ -234,47 +243,51 @@ function computeTrueWind() {
|
|
|
234
243
|
let tws_water = 0;
|
|
235
244
|
|
|
236
245
|
if (hasNativeTws) {
|
|
237
|
-
// Se la barca invia il TWS nativo, usiamo direttamente quello
|
|
238
246
|
tws_water = store.raw["environment.wind.speedTrue"] ? msToKts(store.raw["environment.wind.speedTrue"]) : 0;
|
|
239
247
|
} else {
|
|
240
|
-
// Altrimenti eseguiamo il calcolo vettoriale tattico utilizzando la velocità di riferimento (STW o SOG)
|
|
241
248
|
tws_water = Math.sqrt(aws * aws + speedRef * speedRef - 2 * aws * speedRef * Math.cos(awa));
|
|
242
249
|
store.raw["environment.wind.speedTrue"] = tws_water;
|
|
243
250
|
}
|
|
244
251
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
+
let twa = 0;
|
|
253
|
+
// Verifica se riceviamo un TWA nativo valido ed efficiente negli ultimi 5 secondi
|
|
254
|
+
const hasNativeTwa = store.timestamps["environment.wind.angleTrueWater"] && (Date.now() - store.timestamps["environment.wind.angleTrueWater"] < 5000);
|
|
255
|
+
|
|
256
|
+
if (hasNativeTwa && store.raw["environment.wind.angleTrueWater"] !== undefined) {
|
|
257
|
+
// "Native First": diamo la precedenza assoluta al valore calcolato dalla centralina
|
|
258
|
+
twa = store.raw["environment.wind.angleTrueWater"];
|
|
259
|
+
} else {
|
|
260
|
+
// "Fallback Second": se manca il dato nativo, eseguiamo il calcolo vettoriale a mano
|
|
261
|
+
if (tws_water > 0.05) {
|
|
262
|
+
twa = Math.atan2(aws * Math.sin(awa), aws * Math.cos(awa) - speedRef);
|
|
263
|
+
store.raw["environment.wind.angleTrueWater"] = twa;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Salviamo e allineiamo i buffer delle medie se abbiamo un angolo valido (nativo o calcolato)
|
|
268
|
+
if (tws_water > 0.05 || hasNativeTwa) {
|
|
252
269
|
safePush(store.smoothBuf.twa, twa, now);
|
|
253
270
|
safePush(store.longBuf.twa, twa, now);
|
|
254
|
-
|
|
255
|
-
|
|
271
|
+
|
|
272
|
+
// Salviamo l'angolo apparente solo se il relativo sensore fisico è effettivamente attivo
|
|
273
|
+
if (awa !== undefined) {
|
|
274
|
+
safePush(store.smoothBuf.awa, awa, now);
|
|
275
|
+
safePush(store.longBuf.awa, awa, now);
|
|
276
|
+
}
|
|
256
277
|
}
|
|
257
278
|
|
|
258
279
|
// ==========================================================================
|
|
259
|
-
// 2. GESTIONE TWD (NATIVO vs FALLBACK)
|
|
280
|
+
// 2. GESTIONE TWD (NATIVO vs FALLBACK MATEMATICO STABILE)
|
|
260
281
|
// ==========================================================================
|
|
261
282
|
const hasNativeTwd = store.timestamps["environment.wind.directionTrue"] && (Date.now() - store.timestamps["environment.wind.directionTrue"] < 5000);
|
|
262
283
|
|
|
263
|
-
if (hasNativeTwd) {
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const tws_ground = Math.sqrt(tw_ground_x * tw_ground_x + tw_ground_y * tw_ground_y);
|
|
271
|
-
|
|
272
|
-
if (tws_ground > 0.05) {
|
|
273
|
-
let twd = (hdg + Math.atan2(tw_ground_y, tw_ground_x) + 2 * Math.PI) % (2 * Math.PI);
|
|
274
|
-
store.raw["environment.wind.directionTrue"] = twd;
|
|
275
|
-
safePush(store.smoothBuf.twd, twd, now);
|
|
276
|
-
safePush(store.longBuf.twd, twd, now); // Alimenta la bussola meteo strategica!
|
|
277
|
-
}
|
|
284
|
+
if (!hasNativeTwd && tws_water > 0.05) {
|
|
285
|
+
// Calcolo TWD stabile e immune dal rollio: Prua + TWA
|
|
286
|
+
// Elimina i sobbalzi legati al brandeggio del COG e dello scarroccio fasullo
|
|
287
|
+
let twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
|
|
288
|
+
store.raw["environment.wind.directionTrue"] = twd;
|
|
289
|
+
safePush(store.smoothBuf.twd, twd, now);
|
|
290
|
+
safePush(store.longBuf.twd, twd, now);
|
|
278
291
|
}
|
|
279
292
|
}
|
|
280
293
|
|
|
@@ -387,6 +400,12 @@ function processIncomingData(path, val, source, timeMs) {
|
|
|
387
400
|
let speedVal = (val && typeof val === 'object' && val.val !== undefined) ? val.val : val;
|
|
388
401
|
store.raw["environment.wind.speedTrue"] = speedVal;
|
|
389
402
|
}
|
|
403
|
+
|
|
404
|
+
// INTERCETTAZIONE TWA NATIVO (Angolo Vento Reale pronto all'uso)
|
|
405
|
+
if (path === "environment.wind.angleTrueWater") {
|
|
406
|
+
let angleVal = (val && typeof val === 'object' && val.val !== undefined) ? val.val : val;
|
|
407
|
+
store.raw["environment.wind.angleTrueWater"] = angleVal;
|
|
408
|
+
}
|
|
390
409
|
|
|
391
410
|
// --- GESTIONE PRUA VERA / MAGNETICA CON AUTODIVIAZIONE ---
|
|
392
411
|
if (path === "navigation.headingTrue") {
|
|
@@ -665,7 +684,16 @@ function startDisplayLoop() {
|
|
|
665
684
|
|
|
666
685
|
// --- LOGICA SOG / VMG ---
|
|
667
686
|
if (store.raw["navigation.speedOverGround"] !== undefined) {
|
|
668
|
-
|
|
687
|
+
// RILEVAMENTO "LOG BLOCCATO" PER IL CALCOLO VMG
|
|
688
|
+
const hasFreshStw = store.timestamps["navigation.speedThroughWater"] && (now - store.timestamps["navigation.speedThroughWater"] < 15000);
|
|
689
|
+
let speedRefKts = sogKts; // Fallback predefinito su GPS
|
|
690
|
+
|
|
691
|
+
if (hasFreshStw) {
|
|
692
|
+
// Se viaggiamo a più di 1.5 nodi (GPS) ma il log segna meno di 0.5 nodi, usa il SOG come backup
|
|
693
|
+
speedRefKts = (sogKts > 1.5 && stwKts < 0.5) ? sogKts : stwKts;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const vmgVal = Math.abs(speedRefKts * Math.cos(store.raw["environment.wind.angleTrueWater"] || 0));
|
|
669
697
|
manageHistory('vmg', vmgVal);
|
|
670
698
|
manageHistory('sog', sogKts);
|
|
671
699
|
|
|
@@ -679,13 +707,22 @@ function startDisplayLoop() {
|
|
|
679
707
|
if (labelSogVmg) labelSogVmg.textContent = 'SOG';
|
|
680
708
|
|
|
681
709
|
if (isNavigating) {
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
710
|
+
const hasFreshStw = store.timestamps["navigation.speedThroughWater"] && (now - store.timestamps["navigation.speedThroughWater"] < 15000);
|
|
711
|
+
// Rileviamo se il log è plausibilmente sporco o bloccato (SOG in movimento, STW a zero)
|
|
712
|
+
const isFouledLog = (sogKts > 1.5 && stwKts < 0.5);
|
|
713
|
+
|
|
714
|
+
// Se non abbiamo un log affidabile, è impossibile calcolare la corrente. Evitiamo falsi colori tattici.
|
|
715
|
+
if (!hasFreshStw || isFouledLog) {
|
|
716
|
+
ui.sog.style.removeProperty('color');
|
|
717
|
+
} else {
|
|
718
|
+
const lastSog = store.histories.sog.length > 0 ? store.histories.sog[store.histories.sog.length - 1].val : sogKts;
|
|
719
|
+
const lastStw = store.histories.stw.length > 0 ? store.histories.stw[store.histories.stw.length - 1].val : stwKts;
|
|
720
|
+
const drift = lastSog - lastStw;
|
|
721
|
+
|
|
722
|
+
if (drift < -0.3) ui.sog.style.setProperty('color', '#ff3b30', 'important'); // Contro
|
|
723
|
+
else if (drift > 0.3) ui.sog.style.setProperty('color', '#00C851', 'important'); // Favore
|
|
724
|
+
else ui.sog.style.setProperty('color', '#ffbb33', 'important'); // Neutro SOG
|
|
725
|
+
}
|
|
689
726
|
} else {
|
|
690
727
|
ui.sog.style.removeProperty('color');
|
|
691
728
|
}
|
|
@@ -902,23 +939,71 @@ async function fetchServerHistory() {
|
|
|
902
939
|
const data = await response.json();
|
|
903
940
|
|
|
904
941
|
if (data && typeof data === 'object') {
|
|
942
|
+
// 1. RILEVAMENTO DEL DELTA DI COERENZA TEMPORALE
|
|
943
|
+
// Troviamo il timestamp più recente in assoluto presente nei dati storici del server
|
|
944
|
+
let maxServerTime = 0;
|
|
945
|
+
for (let key in data) {
|
|
946
|
+
if (store.histories[key] !== undefined && Array.isArray(data[key]) && data[key].length > 0) {
|
|
947
|
+
const lastPoint = data[key][data[key].length - 1];
|
|
948
|
+
if (lastPoint && lastPoint.time > maxServerTime) {
|
|
949
|
+
maxServerTime = lastPoint.time;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Calcoliamo lo sfasamento in millisecondi rispetto all'orologio del client (MacBook/Tablet)
|
|
955
|
+
const nowMac = Date.now();
|
|
956
|
+
const timeDelta = (maxServerTime > 0) ? (nowMac - maxServerTime) : 0;
|
|
957
|
+
|
|
958
|
+
if (Math.abs(timeDelta) > 500) {
|
|
959
|
+
console.log(`⏱️ [Clock Sync] Rilevato scostamento di ${(timeDelta/1000).toFixed(1)}s rispetto a einstein. Calibrazione attiva.`);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// 2. TRASLAZIONE DEI DATI STORICI
|
|
963
|
+
// Spostiamo nel tempo tutti i punti passati per incollarli millimetricamente al "now" del client
|
|
905
964
|
for (let key in data) {
|
|
906
965
|
if (store.histories[key] !== undefined) {
|
|
907
|
-
|
|
966
|
+
if (Array.isArray(data[key])) {
|
|
967
|
+
store.histories[key] = data[key].map(p => ({
|
|
968
|
+
val: p.val,
|
|
969
|
+
time: p.time + timeDelta, // Sposta il punto nel tempo per allinearlo al Mac
|
|
970
|
+
min: p.min !== undefined ? p.min : undefined,
|
|
971
|
+
max: p.max !== undefined ? p.max : undefined
|
|
972
|
+
}));
|
|
973
|
+
} else {
|
|
974
|
+
store.histories[key] = data[key];
|
|
975
|
+
}
|
|
908
976
|
}
|
|
909
977
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
if (data.
|
|
913
|
-
|
|
914
|
-
|
|
978
|
+
|
|
979
|
+
// Sincronizza i dati specifici del radar storici mantendo i timestamp calendarizzati assoluti (0% sfasamento dei perni)
|
|
980
|
+
if (data.windRadarSlots) {
|
|
981
|
+
store.windRadarSlots = data.windRadarSlots; // Mantiene gli orari spaccati al minuto per l'uguaglianza del radar
|
|
982
|
+
}
|
|
983
|
+
if (data.futureForecast) {
|
|
984
|
+
store.futureForecast = {
|
|
985
|
+
...data.futureForecast,
|
|
986
|
+
timestamp: data.futureForecast.timestamp + timeDelta
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
if (data.twd) {
|
|
990
|
+
store.twdMinuteBuffer = data.twd.map(p => ({
|
|
991
|
+
...p,
|
|
992
|
+
time: p.time + timeDelta
|
|
993
|
+
}));
|
|
994
|
+
}
|
|
995
|
+
if (data.tws) {
|
|
996
|
+
store.twsMinuteBuffer = data.tws.map(p => ({
|
|
997
|
+
...p,
|
|
998
|
+
time: p.time + timeDelta
|
|
999
|
+
}));
|
|
1000
|
+
}
|
|
915
1001
|
|
|
916
1002
|
// --- SILLABAZIONE STRATEGICA DELLA BUSSOLA METEO (TWD) ---
|
|
917
|
-
// Se il server ci invia lo storico del TWD, lo inseriamo calcolando i seni e coseni per i vettori
|
|
918
1003
|
if (data.twd && data.twd.length > 0) {
|
|
919
1004
|
store.longBuf.twd = data.twd.map(p => ({
|
|
920
1005
|
val: p.val,
|
|
921
|
-
time: p.time,
|
|
1006
|
+
time: p.time + timeDelta, // Allinea il tempo strategico del radar
|
|
922
1007
|
sin: Math.sin(p.val),
|
|
923
1008
|
cos: Math.cos(p.val)
|
|
924
1009
|
}));
|
|
@@ -928,7 +1013,7 @@ async function fetchServerHistory() {
|
|
|
928
1013
|
}
|
|
929
1014
|
} catch (err) {
|
|
930
1015
|
console.warn("⚠️ Impossibile caricare lo storico dal server. Utilizzo dati vuoti/simulati.");
|
|
931
|
-
throw err;
|
|
1016
|
+
throw err;
|
|
932
1017
|
}
|
|
933
1018
|
}
|
|
934
1019
|
|
|
@@ -1176,6 +1261,7 @@ function connect() {
|
|
|
1176
1261
|
{ path: "environment.wind.speedApparent", minPeriod: 333 }, // AWS a 3 Hz
|
|
1177
1262
|
{ path: "environment.wind.angleApparent", minPeriod: 333 }, // AWA a 3 Hz
|
|
1178
1263
|
{ path: "environment.wind.speedTrue", minPeriod: 333 }, // TWS (Nativo) a 3 Hz
|
|
1264
|
+
{ path: "environment.wind.angleTrueWater", minPeriod: 333 }, // TWA (Nativo) a 3 Hz (AGGIUNTO)
|
|
1179
1265
|
{ path: "environment.wind.directionTrue", minPeriod: 333 } // TWD (Nativo) a 3 Hz
|
|
1180
1266
|
]
|
|
1181
1267
|
};
|
|
@@ -1331,11 +1417,12 @@ async function init() {
|
|
|
1331
1417
|
startDisplayLoop();
|
|
1332
1418
|
connect(); // Si collegherà in tempo reale al WebSocket reale della barca
|
|
1333
1419
|
|
|
1334
|
-
// 5. POLL LENTO (15 secondi): aggiorna i dati radar in background
|
|
1420
|
+
// 5. POLL LENTO (15 secondi): aggiorna i dati radar in background SOLO se lo strumento attivo è il radar,
|
|
1421
|
+
// evitando di sovraccaricare la rete ed eliminando il salto visivo di reset dei grafici a 1Hz
|
|
1335
1422
|
setInterval(async () => {
|
|
1336
1423
|
try {
|
|
1337
|
-
await fetchServerHistory();
|
|
1338
1424
|
if (activeInstrument === 'radar') {
|
|
1425
|
+
await fetchServerHistory();
|
|
1339
1426
|
renderRadar();
|
|
1340
1427
|
}
|
|
1341
1428
|
} catch (err) {
|
package/charts.js
CHANGED
|
@@ -19,7 +19,9 @@ function calculateScale(type, data, mode) {
|
|
|
19
19
|
|
|
20
20
|
if (!store.herculesScales) store.herculesScales = {};
|
|
21
21
|
if (!store.herculesScales[type]) {
|
|
22
|
-
|
|
22
|
+
// Inizializzazione a NULL dello stato di partenza per consentire un allineamento istantaneo
|
|
23
|
+
// e perfettamente centrato al primo doppio clic
|
|
24
|
+
store.herculesScales[type] = { min: null, max: null };
|
|
23
25
|
}
|
|
24
26
|
let currentScale = store.herculesScales[type];
|
|
25
27
|
|
|
@@ -65,9 +67,10 @@ function calculateScale(type, data, mode) {
|
|
|
65
67
|
targetMax = targetMin + 4;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
currentScale.
|
|
70
|
+
// Gestione dell'allineamento iniziale o dello scostamento dei limiti della scala
|
|
71
|
+
if (currentScale.min === null || currentVal < currentScale.min || currentVal > currentScale.max) {
|
|
72
|
+
currentScale.min = currentScale.min === null ? targetMin : Math.min(currentScale.min, targetMin);
|
|
73
|
+
currentScale.max = currentScale.max === null ? targetMax : Math.max(currentScale.max, targetMax);
|
|
71
74
|
} else {
|
|
72
75
|
const allStableInTarget = recentVals.every(val => val >= targetMin && val <= targetMax);
|
|
73
76
|
if (allStableInTarget) {
|
|
@@ -90,8 +93,25 @@ function calculateScale(type, data, mode) {
|
|
|
90
93
|
const oneThirdCount = Math.max(1, Math.floor(data.length / 3));
|
|
91
94
|
const recentThirdData = data.slice(-oneThirdCount);
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
let localMin = Math.min(...recentThirdData);
|
|
97
|
+
let localMax = Math.max(...recentThirdData);
|
|
98
|
+
|
|
99
|
+
// SMART MOTORING DETECTION (Specifica per i grafici di velocità: STW, SOG, VMG)
|
|
100
|
+
const isSpeedType = (type === 'stw' || type === 'sog' || type === 'vmg');
|
|
101
|
+
if (isSpeedType && data.length > oneThirdCount) {
|
|
102
|
+
const olderData = data.slice(0, data.length - oneThirdCount);
|
|
103
|
+
const olderMin = Math.min(...olderData);
|
|
104
|
+
const olderMax = Math.max(...olderData);
|
|
105
|
+
const olderRange = olderMax - olderMin; // Variazione nello storico passato
|
|
106
|
+
|
|
107
|
+
// Se lo storico passato non era piatto (range > 0.2 nodi) oppure eravamo fermi/all'ancora (< 1.5 nodi),
|
|
108
|
+
// significa che il passato è rilevante per il trend. Includiamo il passato nel calcolo della scala Y.
|
|
109
|
+
// Se invece era piatto (motore), lo ignoriamo per zoomare subito sulla vela attuale.
|
|
110
|
+
if (olderRange > 0.2 || olderMin < 0.77) {
|
|
111
|
+
localMin = Math.min(localMin, olderMin);
|
|
112
|
+
localMax = Math.max(localMax, olderMax);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
95
115
|
|
|
96
116
|
let targetMin = Math.max(0, Math.floor(localMin / roundStep) * roundStep);
|
|
97
117
|
let targetMax = Math.ceil(localMax / roundStep) * roundStep;
|
|
@@ -100,9 +120,10 @@ function calculateScale(type, data, mode) {
|
|
|
100
120
|
targetMax = targetMin + roundStep;
|
|
101
121
|
}
|
|
102
122
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
currentScale.
|
|
123
|
+
// Gestione dell'allineamento iniziale o dello scostamento dei limiti della scala
|
|
124
|
+
if (currentScale.min === null || currentVal < currentScale.min || currentVal > currentScale.max) {
|
|
125
|
+
currentScale.min = currentScale.min === null ? targetMin : Math.min(currentScale.min, targetMin);
|
|
126
|
+
currentScale.max = currentScale.max === null ? targetMax : Math.max(currentScale.max, targetMax);
|
|
106
127
|
} else {
|
|
107
128
|
const allStableInTarget = recentThirdData.every(val => val >= targetMin && val <= targetMax);
|
|
108
129
|
if (allStableInTarget) {
|
|
@@ -142,15 +163,25 @@ function refreshGraph(t) {
|
|
|
142
163
|
|
|
143
164
|
if (!rawData || rawData.length < 2) return;
|
|
144
165
|
|
|
145
|
-
|
|
166
|
+
// ALINEAMENTO STRUTTURALE: Filtriamo lo storico mantenendo solo la finestra temporale visibile
|
|
167
|
+
// prima di calcolare la scala, allineando millimetricamente la scala Y con i pixel disegnati a schermo.
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
const visibleMinutes = CONFIG.graphs.historyMinutes * (isNavigating ? 1 : 2);
|
|
170
|
+
const viewportMs = visibleMinutes * 60000;
|
|
171
|
+
const viewportStart = now - viewportMs;
|
|
172
|
+
|
|
173
|
+
const visibleData = rawData.filter(p => p.time >= viewportStart);
|
|
174
|
+
if (visibleData.length < 2) return; // Abortisce se non ci sono abbastanza punti visibili
|
|
175
|
+
|
|
176
|
+
const values = visibleData.map(p => p.val); // Estrae i valori solo dei punti visibili
|
|
146
177
|
const mode = graphModes[boxType];
|
|
147
|
-
const cfg = calculateScale(boxType, values, mode);
|
|
178
|
+
const cfg = calculateScale(boxType, values, mode); // Calcola la scala Y esatta per la viewBox corrente
|
|
148
179
|
|
|
149
180
|
const box = document.querySelector(`.box-${boxType}`);
|
|
150
181
|
if (box) box.classList.toggle('box-hercules', mode === 'hercules');
|
|
151
182
|
|
|
152
183
|
updateScaleLabels(boxType, cfg.min, cfg.max);
|
|
153
|
-
drawGraph(
|
|
184
|
+
drawGraph(visibleData, boxType + '-graph', cfg.min, cfg.max, t === 'tws', mode === 'hercules');
|
|
154
185
|
}
|
|
155
186
|
|
|
156
187
|
// Genera fisicamente le curve e le aree SVG
|
|
@@ -256,7 +287,12 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
|
256
287
|
gradientStops += `<stop offset="${offset1}%" stop-color="${props.color}" stop-opacity="${props.opacity}" />`;
|
|
257
288
|
gradientStops += `<stop offset="${offset2}%" stop-color="${props.color}" stop-opacity="${props.opacity}" />`;
|
|
258
289
|
|
|
259
|
-
|
|
290
|
+
// CALCOLO ELASTICO DELL'INTERRUZIONE: Tollera buchi storici fino a 90 secondi (es. i 30s di einstein)
|
|
291
|
+
// per permettere di disegnare linee solide e continue anche se mostrate in una timeline corta e veloce
|
|
292
|
+
const maxAllowedGap = Math.max(expectedInterval * 2.5, 90000);
|
|
293
|
+
const isHistoryGap = deltaTime > maxAllowedGap;
|
|
294
|
+
|
|
295
|
+
if (isHistoryGap) {
|
|
260
296
|
if (started) {
|
|
261
297
|
areaPath += `L ${x1} ${h} `;
|
|
262
298
|
started = false;
|
package/index.js
CHANGED
|
@@ -277,32 +277,40 @@ module.exports = function (app) {
|
|
|
277
277
|
const stw = raw["navigation.speedThroughWater"];
|
|
278
278
|
const sog = raw["navigation.speedOverGround"] || 0;
|
|
279
279
|
const hdg = raw["navigation.headingTrue"];
|
|
280
|
-
const cog = raw["navigation.courseOverGroundTrue"] || 0;
|
|
281
280
|
|
|
282
281
|
if (aws !== undefined && awa !== undefined) {
|
|
283
282
|
const awsKts = aws * 1.94384;
|
|
284
283
|
|
|
285
|
-
//
|
|
284
|
+
// RILEVAMENTO "LOG BLOCCATO" SUL SERVER
|
|
286
285
|
const hasStw = lastPathProcessTimes["navigation.speedThroughWater"] && (now - lastPathProcessTimes["navigation.speedThroughWater"] < 15000);
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
let speedRef = 0;
|
|
287
|
+
|
|
288
|
+
if (hasStw && stw !== undefined) {
|
|
289
|
+
if (sog > 0.77 && stw < 0.25) {
|
|
290
|
+
speedRef = sog; // Log sporco: forza il SOG
|
|
291
|
+
} else {
|
|
292
|
+
speedRef = stw;
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
speedRef = sog;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const speedKtsRef = speedRef * 1.94384;
|
|
289
299
|
|
|
290
300
|
const tw_water_x = awsKts * Math.cos(awa) - speedKtsRef;
|
|
291
301
|
const tw_water_y = awsKts * Math.sin(awa);
|
|
292
302
|
|
|
293
|
-
//
|
|
303
|
+
// Calcolo TWS
|
|
294
304
|
if (now - lastNativeTwsTime > 5000) {
|
|
295
305
|
const tws = Math.sqrt(tw_water_x * tw_water_x + tw_water_y * tw_water_y);
|
|
296
306
|
manageHistory('tws', tws);
|
|
297
307
|
}
|
|
298
308
|
|
|
299
309
|
const twa = Math.atan2(tw_water_y, tw_water_x);
|
|
300
|
-
|
|
301
|
-
// La VMG viene calcolata usando la velocità di riferimento (STW reale o SOG di fallback)
|
|
302
310
|
const vmg = Math.abs(speedKtsRef * Math.cos(twa));
|
|
303
311
|
manageHistory('vmg', vmg);
|
|
304
312
|
|
|
305
|
-
//
|
|
313
|
+
// Calcolo TWD stabile (Prua + TWA)
|
|
306
314
|
if (hdg !== undefined && (now - lastNativeTwdTime > 5000)) {
|
|
307
315
|
const twd = (hdg + twa + 2 * Math.PI) % (2 * Math.PI);
|
|
308
316
|
manageHistory('twd', twd);
|