speedandcurrent 1.4.2 → 1.5.1
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/LICENSE +1 -1
- package/README.md +179 -78
- package/correctionTable.js +149 -6
- package/index.js +363 -430
- package/leakyExtremes.js +98 -0
- package/package.json +12 -7
- package/public/TableRenderer.js +135 -83
- package/public/app.js +214 -55
- package/public/index.html +20 -5
- package/public/main.css +2 -2
- package/CHANGELOG.md +0 -28
- package/correctionModel.png +0 -0
- package/currentModel.png +0 -0
- package/models.md +0 -26
- package/public/surfaceplot.html +0 -78
package/public/app.js
CHANGED
|
@@ -12,40 +12,55 @@ let vSpeed = 1;
|
|
|
12
12
|
let dSpeed = 1;
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
const LS_KEYS = {
|
|
16
|
+
speed: 'sc_speed_unit',
|
|
17
|
+
angle: 'sc_angle_unit',
|
|
18
|
+
color: 'sc_color_mode',
|
|
19
|
+
metrics: 'sc_table_metrics'
|
|
20
|
+
};
|
|
21
|
+
|
|
15
22
|
export function handleSpeedUnitChange(value) {
|
|
16
23
|
if (value == "knots") {
|
|
17
24
|
vSpeed = 1.943844;
|
|
18
25
|
dSpeed = 1;
|
|
26
|
+
localStorage.setItem(LS_KEYS.speed, 'knots');
|
|
19
27
|
return;
|
|
20
28
|
}
|
|
21
29
|
if (value == "kmh") {
|
|
22
30
|
vSpeed = 3.6;
|
|
23
31
|
dSpeed = 1;
|
|
32
|
+
localStorage.setItem(LS_KEYS.speed, 'kmh');
|
|
24
33
|
return;
|
|
25
34
|
}
|
|
26
35
|
vSpeed = 1;
|
|
27
36
|
dSpeed = 1;
|
|
37
|
+
localStorage.setItem(LS_KEYS.speed, 'ms');
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
export function handleAngleUnitChange(value) {
|
|
31
41
|
if (value == "degrees") {
|
|
32
42
|
vAngle = 180 / Math.PI;
|
|
33
43
|
dAngle = 0;
|
|
44
|
+
localStorage.setItem(LS_KEYS.angle, 'degrees');
|
|
34
45
|
return;
|
|
35
46
|
}
|
|
36
47
|
vAngle = 1;
|
|
37
48
|
dAngle = 2;
|
|
49
|
+
localStorage.setItem(LS_KEYS.angle, 'radians');
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
|
|
41
53
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
// Removed table style switch; rendering is driven by metrics selection.
|
|
55
|
+
|
|
56
|
+
export function handleColorModeChange(value) {
|
|
57
|
+
if (tableRenderer && tableRenderer.setColorMode) {
|
|
58
|
+
tableRenderer.setColorMode(value);
|
|
59
|
+
localStorage.setItem(LS_KEYS.color, value);
|
|
60
|
+
// re-render immediately using last data if available
|
|
61
|
+
// fetchAndUpdateData will repaint next tick; do a quick repaint now if cached
|
|
62
|
+
// (Simplest approach: trigger fetch)
|
|
63
|
+
fetchAndUpdateData();
|
|
49
64
|
}
|
|
50
65
|
}
|
|
51
66
|
|
|
@@ -59,42 +74,83 @@ function cSpeed(value) {
|
|
|
59
74
|
return value.toFixed(dSpeed);
|
|
60
75
|
}
|
|
61
76
|
|
|
77
|
+
let selectedMetrics = {
|
|
78
|
+
correction: true,
|
|
79
|
+
factor: true,
|
|
80
|
+
leeway: true,
|
|
81
|
+
trace: false,
|
|
82
|
+
N: true,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function buildCellContent(parts) {
|
|
86
|
+
// parts is array of strings; filter falsy and join
|
|
87
|
+
return parts.filter(Boolean).join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
62
90
|
function cartesian(correction, speed, heel) {
|
|
63
91
|
if (correction.N == 0) return null;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
92
|
+
const bits = [];
|
|
93
|
+
if (selectedMetrics.correction) {
|
|
94
|
+
bits.push(` <div><strong>X:</strong> ${cSpeed(correction.x)}</div>`);
|
|
95
|
+
bits.push(` <div><strong>Y:</strong> ${cSpeed(correction.y)}</div>`);
|
|
96
|
+
}
|
|
97
|
+
if (selectedMetrics.trace && Number.isFinite(correction.trace)) {
|
|
98
|
+
bits.push(` <div><strong>trace:</strong> ${Number(correction.trace).toPrecision(2)}</div>`);
|
|
99
|
+
}
|
|
100
|
+
if (selectedMetrics.N) {
|
|
101
|
+
bits.push(` <div><strong>N:</strong> ${correction.N}</div>`);
|
|
102
|
+
}
|
|
103
|
+
return buildCellContent(bits);
|
|
68
104
|
}
|
|
69
105
|
|
|
70
106
|
function polar(correction, speed, heel) {
|
|
71
107
|
if (speed == 0 || correction.N == 0) return null;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const factor = (correction.x + speed) / speed;
|
|
92
|
-
return factor.toFixed(2);
|
|
108
|
+
const bits = [];
|
|
109
|
+
// Prefer server-provided values when available
|
|
110
|
+
const factor = Number.isFinite(correction.factor) ? correction.factor : (speed > 0 ? (correction.x + speed) / speed : 0);
|
|
111
|
+
const leewayRad = Number.isFinite(correction.leeway) ? correction.leeway : Math.atan2(correction.y, speed + correction.x);
|
|
112
|
+
const leewayDisplay = leewayRad; // signed value; color uses |leeway|
|
|
113
|
+
|
|
114
|
+
if (selectedMetrics.factor) {
|
|
115
|
+
bits.push(` <div><strong>factor:</strong> ${factor.toFixed(2)}</div>`);
|
|
116
|
+
}
|
|
117
|
+
if (selectedMetrics.leeway) {
|
|
118
|
+
bits.push(` <div><strong>leeway:</strong> ${cAngle(leewayDisplay)}</div>`);
|
|
119
|
+
}
|
|
120
|
+
if (selectedMetrics.trace && Number.isFinite(correction.trace)) {
|
|
121
|
+
bits.push(` <div><strong>trace:</strong> ${Number(correction.trace).toPrecision(2)}</div>`);
|
|
122
|
+
}
|
|
123
|
+
if (selectedMetrics.N) {
|
|
124
|
+
bits.push(` <div><strong>N:</strong> ${correction.N}</div>`);
|
|
125
|
+
}
|
|
126
|
+
return buildCellContent(bits);
|
|
93
127
|
}
|
|
94
128
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
129
|
+
function unifiedCellRenderer(correction, speed, heel) {
|
|
130
|
+
// Combines polar-oriented metrics and cartesian correction based on selections
|
|
131
|
+
if (correction.N == 0) return null;
|
|
132
|
+
const bits = [];
|
|
133
|
+
// Polar-ish metrics
|
|
134
|
+
if (selectedMetrics.factor && (speed !== 0)) {
|
|
135
|
+
const factor = Number.isFinite(correction.factor) ? correction.factor : (speed > 0 ? (correction.x + speed) / speed : 0);
|
|
136
|
+
bits.push(` <div><strong>factor:</strong> ${factor.toFixed(2)}</div>`);
|
|
137
|
+
}
|
|
138
|
+
if (selectedMetrics.leeway && (speed !== 0)) {
|
|
139
|
+
const leewayRad = Number.isFinite(correction.leeway) ? correction.leeway : Math.atan2(correction.y, speed + correction.x);
|
|
140
|
+
bits.push(` <div><strong>leeway:</strong> ${cAngle(leewayRad)}</div>`);
|
|
141
|
+
}
|
|
142
|
+
// Cartesian correction (always available)
|
|
143
|
+
if (selectedMetrics.correction) {
|
|
144
|
+
bits.push(` <div><strong>X:</strong> ${cSpeed(correction.x)}</div>`);
|
|
145
|
+
bits.push(` <div><strong>Y:</strong> ${cSpeed(correction.y)}</div>`);
|
|
146
|
+
}
|
|
147
|
+
if (selectedMetrics.trace && Number.isFinite(correction.trace)) {
|
|
148
|
+
bits.push(` <div><strong>trace:</strong> ${Number(correction.trace).toPrecision(2)}</div>`);
|
|
149
|
+
}
|
|
150
|
+
if (selectedMetrics.N) {
|
|
151
|
+
bits.push(` <div><strong>N:</strong> ${correction.N}</div>`);
|
|
152
|
+
}
|
|
153
|
+
return buildCellContent(bits);
|
|
98
154
|
}
|
|
99
155
|
|
|
100
156
|
|
|
@@ -104,26 +160,54 @@ async function getFromServer(endpoint) {
|
|
|
104
160
|
const response = await fetch(`${API_BASE_URL}/${endpoint}`, {
|
|
105
161
|
method: 'GET',
|
|
106
162
|
headers: {
|
|
107
|
-
'
|
|
108
|
-
}
|
|
163
|
+
'Accept': 'application/json'
|
|
164
|
+
},
|
|
165
|
+
credentials: 'same-origin' // include cookies for auth
|
|
109
166
|
});
|
|
110
167
|
|
|
168
|
+
const msgEl = document.getElementById("message");
|
|
169
|
+
const contentType = response.headers.get('content-type') || '';
|
|
170
|
+
|
|
171
|
+
// Auth errors: show friendly message and pause updates
|
|
172
|
+
if (response.status === 401 || response.status === 403) {
|
|
173
|
+
if (msgEl) {
|
|
174
|
+
msgEl.innerHTML = 'Not signed in. Please <a href="/">sign in</a> to the Signal K server, then reload this page.';
|
|
175
|
+
}
|
|
176
|
+
if (updateTimer) {
|
|
177
|
+
clearInterval(updateTimer);
|
|
178
|
+
updatesPaused = true;
|
|
179
|
+
const toggleButton = document.getElementById('toggle-updates');
|
|
180
|
+
if (toggleButton) toggleButton.textContent = "Resume";
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Service not running or other HTTP errors
|
|
111
186
|
if (!response.ok) {
|
|
112
187
|
if (response.status === 503) {
|
|
113
|
-
|
|
188
|
+
if (msgEl) msgEl.textContent = "Plugin is not running";
|
|
114
189
|
} else {
|
|
115
|
-
|
|
190
|
+
if (msgEl) msgEl.textContent = `Failed to fetch data (${response.status} ${response.statusText}).`;
|
|
116
191
|
}
|
|
117
|
-
|
|
118
|
-
else {
|
|
119
|
-
document.getElementById("message").innerHTML = "";
|
|
192
|
+
return null;
|
|
120
193
|
}
|
|
121
194
|
|
|
122
|
-
|
|
123
|
-
|
|
195
|
+
// Only parse JSON when content-type matches
|
|
196
|
+
if (contentType.includes('application/json')) {
|
|
197
|
+
const data = await response.json();
|
|
198
|
+
if (msgEl) msgEl.textContent = "";
|
|
199
|
+
return data;
|
|
200
|
+
} else {
|
|
201
|
+
// Likely an HTML login page or plain text error
|
|
202
|
+
if (msgEl) {
|
|
203
|
+
msgEl.innerHTML = 'Unexpected server response. If you are not signed in, please <a href="/">sign in</a> to the Signal K server, then reload.';
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
124
207
|
} catch (error) {
|
|
125
208
|
console.error("Failed to fetch data from server:", error);
|
|
126
|
-
document.getElementById("message")
|
|
209
|
+
const msgEl = document.getElementById("message");
|
|
210
|
+
if (msgEl) msgEl.textContent = `Network error: ${error && error.message ? error.message : String(error)}`;
|
|
127
211
|
return null;
|
|
128
212
|
}
|
|
129
213
|
}
|
|
@@ -160,7 +244,7 @@ function updatePolar(data) {
|
|
|
160
244
|
const table = document.createElement('table');
|
|
161
245
|
table.classList.add('polar');
|
|
162
246
|
const headerRow = document.createElement('tr');
|
|
163
|
-
headerRow.innerHTML = '<th>Label</th><th>Speed</th><th>Angle</th>';
|
|
247
|
+
headerRow.innerHTML = '<th>Label</th><th>Speed</th><th>Angle</th><th>Trace</th>';
|
|
164
248
|
table.appendChild(headerRow);
|
|
165
249
|
|
|
166
250
|
data.polars.forEach(polar => {
|
|
@@ -168,7 +252,10 @@ function updatePolar(data) {
|
|
|
168
252
|
if (polar.id) {
|
|
169
253
|
row.id = polar.id;
|
|
170
254
|
}
|
|
171
|
-
|
|
255
|
+
if (polar.displayAttributes.unstable) {
|
|
256
|
+
row.classList.add('unstable');
|
|
257
|
+
}
|
|
258
|
+
row.innerHTML = `<td>${polar.displayAttributes.label}</td><td>${cSpeed(polar.magnitude)}</td><td>${cAngle(polar.angle)}</td><td>${(polar.trace !== undefined && polar.trace !== null ? Number(polar.trace).toPrecision(2) : '')}</td>`;
|
|
172
259
|
table.appendChild(row);
|
|
173
260
|
});
|
|
174
261
|
|
|
@@ -182,7 +269,7 @@ function updateDelta(data) {
|
|
|
182
269
|
const table = document.createElement('table');
|
|
183
270
|
table.classList.add('delta');
|
|
184
271
|
const headerRow = document.createElement('tr');
|
|
185
|
-
headerRow.innerHTML = '<th>Label</th><th>Value</th>';
|
|
272
|
+
headerRow.innerHTML = '<th>Label</th><th>Value</th><th>Variance</th>';
|
|
186
273
|
table.appendChild(headerRow);
|
|
187
274
|
|
|
188
275
|
data.deltas.forEach(delta => {
|
|
@@ -190,7 +277,10 @@ function updateDelta(data) {
|
|
|
190
277
|
if (delta.id) {
|
|
191
278
|
row.id = delta.id;
|
|
192
279
|
}
|
|
193
|
-
|
|
280
|
+
if (delta.displayAttributes.unstable) {
|
|
281
|
+
row.classList.add('unstable');
|
|
282
|
+
}
|
|
283
|
+
row.innerHTML = `<td>${delta.displayAttributes.label}</td><td>${cAngle(delta.value)}</td><td>${(delta.variance !== undefined && delta.variance !== null ? Number(delta.variance).toPrecision(2) : '')}</td>`;
|
|
194
284
|
table.appendChild(row);
|
|
195
285
|
});
|
|
196
286
|
|
|
@@ -213,6 +303,9 @@ function updateAttitude(data) {
|
|
|
213
303
|
if (attitude.id) {
|
|
214
304
|
row.id = attitude.id;
|
|
215
305
|
}
|
|
306
|
+
if (attitude.displayAttributes.unstable) {
|
|
307
|
+
row.classList.add('unstable');
|
|
308
|
+
}
|
|
216
309
|
row.innerHTML = `<td>${attitude.displayAttributes.label}</td><td>${cAngle(attitude.value.roll)}</td><td>${cAngle(attitude.value.pitch)}</td><td>${cAngle(attitude.value.yaw)}</td>`;
|
|
217
310
|
table.appendChild(row);
|
|
218
311
|
});
|
|
@@ -223,7 +316,6 @@ function updateTable(data) {
|
|
|
223
316
|
const tableContainer = document.getElementById('table-container');
|
|
224
317
|
tableContainer.innerHTML = '';
|
|
225
318
|
data.tables.forEach(table => {
|
|
226
|
-
|
|
227
319
|
const tableElement = tableRenderer.render(table);
|
|
228
320
|
tableContainer.appendChild(tableElement);
|
|
229
321
|
});
|
|
@@ -268,21 +360,88 @@ export function toggleUpdates() {
|
|
|
268
360
|
|
|
269
361
|
//document.getElementById('toggle-updates').addEventListener('click', toggleUpdates);
|
|
270
362
|
|
|
271
|
-
handleSpeedUnitChange("knots");
|
|
272
|
-
handleAngleUnitChange("degrees");
|
|
273
|
-
|
|
274
363
|
const tableRenderer = new TableRenderer();
|
|
275
|
-
handleTableStyleChange("cartesian");
|
|
276
364
|
tableRenderer.setColumnHeaderFormat(cAngle);
|
|
277
365
|
tableRenderer.setRowHeaderFormat(cSpeed);
|
|
366
|
+
// Default cell format is unified and driven by metrics; set to polar-like by default
|
|
367
|
+
tableRenderer.setCellFormat(unifiedCellRenderer);
|
|
368
|
+
|
|
369
|
+
function initSettingsFromStorage() {
|
|
370
|
+
try {
|
|
371
|
+
const speedUnit = localStorage.getItem(LS_KEYS.speed) || 'knots';
|
|
372
|
+
const angleUnit = localStorage.getItem(LS_KEYS.angle) || 'degrees';
|
|
373
|
+
const colorMode = localStorage.getItem(LS_KEYS.color) || 'none';
|
|
374
|
+
const metricsRaw = localStorage.getItem(LS_KEYS.metrics);
|
|
375
|
+
|
|
376
|
+
const speedEl = document.getElementById('speed-unit');
|
|
377
|
+
const angleEl = document.getElementById('angle-unit');
|
|
378
|
+
const colorEl = document.getElementById('color-mode');
|
|
379
|
+
const metricsEls = {
|
|
380
|
+
correction: document.getElementById('show-correction'),
|
|
381
|
+
factor: document.getElementById('show-factor'),
|
|
382
|
+
leeway: document.getElementById('show-leeway'),
|
|
383
|
+
trace: document.getElementById('show-trace'),
|
|
384
|
+
N: document.getElementById('show-N'),
|
|
385
|
+
};
|
|
386
|
+
if (speedEl) speedEl.value = speedUnit;
|
|
387
|
+
if (angleEl) angleEl.value = angleUnit;
|
|
388
|
+
if (colorEl) colorEl.value = colorMode;
|
|
389
|
+
if (metricsRaw) {
|
|
390
|
+
try {
|
|
391
|
+
const parsed = JSON.parse(metricsRaw);
|
|
392
|
+
selectedMetrics = { ...selectedMetrics, ...parsed };
|
|
393
|
+
} catch {}
|
|
394
|
+
}
|
|
395
|
+
// Initialize checkboxes to match selectedMetrics
|
|
396
|
+
if (metricsEls.correction) metricsEls.correction.checked = !!selectedMetrics.correction;
|
|
397
|
+
if (metricsEls.factor) metricsEls.factor.checked = !!selectedMetrics.factor;
|
|
398
|
+
if (metricsEls.leeway) metricsEls.leeway.checked = !!selectedMetrics.leeway;
|
|
399
|
+
if (metricsEls.trace) metricsEls.trace.checked = !!selectedMetrics.trace;
|
|
400
|
+
if (metricsEls.N) metricsEls.N.checked = !!selectedMetrics.N;
|
|
401
|
+
|
|
402
|
+
// Apply handlers (order: units, then renderer-dependent)
|
|
403
|
+
handleSpeedUnitChange(speedUnit);
|
|
404
|
+
handleAngleUnitChange(angleUnit);
|
|
405
|
+
handleColorModeChange(colorMode);
|
|
406
|
+
} catch (e) {
|
|
407
|
+
// Fallback to defaults on any error
|
|
408
|
+
handleSpeedUnitChange('knots');
|
|
409
|
+
handleAngleUnitChange('degrees');
|
|
410
|
+
handleColorModeChange('none');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function handleMetricsChange() {
|
|
415
|
+
const metricsEls = {
|
|
416
|
+
correction: document.getElementById('show-correction'),
|
|
417
|
+
factor: document.getElementById('show-factor'),
|
|
418
|
+
leeway: document.getElementById('show-leeway'),
|
|
419
|
+
trace: document.getElementById('show-trace'),
|
|
420
|
+
N: document.getElementById('show-N'),
|
|
421
|
+
};
|
|
422
|
+
selectedMetrics = {
|
|
423
|
+
correction: metricsEls.correction ? !!metricsEls.correction.checked : selectedMetrics.correction,
|
|
424
|
+
factor: metricsEls.factor ? !!metricsEls.factor.checked : selectedMetrics.factor,
|
|
425
|
+
leeway: metricsEls.leeway ? !!metricsEls.leeway.checked : selectedMetrics.leeway,
|
|
426
|
+
trace: metricsEls.trace ? !!metricsEls.trace.checked : selectedMetrics.trace,
|
|
427
|
+
N: metricsEls.N ? !!metricsEls.N.checked : selectedMetrics.N,
|
|
428
|
+
};
|
|
429
|
+
try {
|
|
430
|
+
localStorage.setItem(LS_KEYS.metrics, JSON.stringify(selectedMetrics));
|
|
431
|
+
} catch {}
|
|
432
|
+
// Re-render with current data (trigger fetch or reuse last render by forcing a refresh)
|
|
433
|
+
fetchAndUpdateData();
|
|
434
|
+
}
|
|
278
435
|
|
|
279
436
|
|
|
280
437
|
// Attach functions to the window object to make them globally accessible
|
|
281
438
|
window.handleSpeedUnitChange = handleSpeedUnitChange;
|
|
282
439
|
window.handleAngleUnitChange = handleAngleUnitChange;
|
|
283
|
-
window.
|
|
440
|
+
window.handleColorModeChange = handleColorModeChange;
|
|
441
|
+
window.handleMetricsChange = handleMetricsChange;
|
|
284
442
|
window.toggleUpdates = toggleUpdates;
|
|
285
443
|
|
|
286
444
|
|
|
287
445
|
// Initial fetch and start updates
|
|
446
|
+
initSettingsFromStorage();
|
|
288
447
|
startUpdates();
|
package/public/index.html
CHANGED
|
@@ -30,16 +30,31 @@
|
|
|
30
30
|
</select>
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
<!-- Metrics Selection -->
|
|
34
|
+
<div class="menu-item" id="metrics-group">
|
|
35
|
+
<span>Show:</span>
|
|
36
|
+
<label><input type="checkbox" id="show-correction" onchange="handleMetricsChange()"> correction</label>
|
|
37
|
+
<label><input type="checkbox" id="show-factor" onchange="handleMetricsChange()"> factor</label>
|
|
38
|
+
<label><input type="checkbox" id="show-leeway" onchange="handleMetricsChange()"> leeway</label>
|
|
39
|
+
<label><input type="checkbox" id="show-trace" onchange="handleMetricsChange()"> trace</label>
|
|
40
|
+
<label><input type="checkbox" id="show-N" onchange="handleMetricsChange()"> N</label>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Color Mode Selection -->
|
|
34
44
|
<div class="menu-item dropdown">
|
|
35
|
-
<label for="
|
|
36
|
-
<select id="
|
|
37
|
-
<option value="
|
|
38
|
-
<option value="
|
|
45
|
+
<label for="color-mode">Table coloring:</label>
|
|
46
|
+
<select id="color-mode" onchange="handleColorModeChange(this.value)">
|
|
47
|
+
<option value="none" selected>none</option>
|
|
48
|
+
<option value="weight">weight</option>
|
|
49
|
+
<option value="leeway">leeway</option>
|
|
50
|
+
<option value="factor">factor</option>
|
|
51
|
+
<option value="trace">trace</option>
|
|
39
52
|
</select>
|
|
40
53
|
</div>
|
|
41
54
|
|
|
42
55
|
|
|
56
|
+
|
|
57
|
+
|
|
43
58
|
<!-- Pause Button -->
|
|
44
59
|
<button id="toggle-updates" class="menu-item" onclick="toggleUpdates()">Pause</button>
|
|
45
60
|
|
package/public/main.css
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
color: black;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
|
|
31
32
|
/* --- Instance IDs for Specific Colors --- */
|
|
32
33
|
#heading {
|
|
33
34
|
color: var(--heading-color);
|
|
@@ -86,8 +87,7 @@ th, td {
|
|
|
86
87
|
font-size: 11px;
|
|
87
88
|
}
|
|
88
89
|
.selectedCell {
|
|
89
|
-
color:
|
|
90
|
-
background-color: #1976d2;
|
|
90
|
+
color: rgb(17, 204, 0);
|
|
91
91
|
}
|
|
92
92
|
.emptyCell {
|
|
93
93
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
|
|
7
|
-
## [1.0.1] - 2025-1-27
|
|
8
|
-
### Added
|
|
9
|
-
- basic functionality to estimate speed, leeway and current using Kalman filters
|
|
10
|
-
- basic functionality for a webapp that shows what is going on
|
|
11
|
-
|
|
12
|
-
## [1.1.0] - 2025-8-20
|
|
13
|
-
### Changed
|
|
14
|
-
- Path names for current. It now is self.environment.current.setTrue and self.environment.current.drift
|
|
15
|
-
### Fixed
|
|
16
|
-
- Case error in filename that prevented the webapp from displaying the correction table
|
|
17
|
-
- Corrected speed through water is now calculated when the correction table is fixed
|
|
18
|
-
|
|
19
|
-
## [1.3.0] - ntb
|
|
20
|
-
### Changed
|
|
21
|
-
- Simplified code
|
|
22
|
-
- Less CPU usage
|
|
23
|
-
### Added
|
|
24
|
-
- Option to set the source for input paths
|
|
25
|
-
- Smart heartbeat. Calculations will run right after an update of the path that has the lowest update frequency
|
|
26
|
-
- Oversampling for quick update inputs to get better variance
|
|
27
|
-
### Fixed
|
|
28
|
-
- Angles are now reported in the appropriate range, 0 to 2 * PI or -PI to PI, depending on path
|
package/correctionModel.png
DELETED
|
Binary file
|
package/currentModel.png
DELETED
|
Binary file
|
package/models.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
## Correction model
|
|
2
|
-
### raw
|
|
3
|
-
\vec{correction} = \mathbb{R}(-heading) \cdot \vec{groundSpeed} - \vec{boatSpeed} - \mathbb{R}(-heading) \cdot \vec{waterSpeed}
|
|
4
|
-
|
|
5
|
-
### link
|
|
6
|
-
https://latex.codecogs.com/svg.image?\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}
|
|
7
|
-
|
|
8
|
-
### html
|
|
9
|
-
<img src="https://latex.codecogs.com/png.image?\dpi{110}\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}" title="\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}" />
|
|
10
|
-
|
|
11
|
-
### image
|
|
12
|
-
|
|
13
|
-

|
|
14
|
-
|
|
15
|
-
## current model
|
|
16
|
-
### raw
|
|
17
|
-
\vec{waterSpeed} = \vec{groundSpeed} - \mathbb{R}(heading) \cdot(\vec{boatSpeed}+\vec{correction})
|
|
18
|
-
### link
|
|
19
|
-
https://latex.codecogs.com/svg.image?\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}+\vec{correction})
|
|
20
|
-
### html
|
|
21
|
-
<img src="https://latex.codecogs.com/svg.image?\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}+\vec{correction})" title="\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}+\vec{correction})" />
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
### image
|
|
25
|
-
|
|
26
|
-

|
package/public/surfaceplot.html
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Surface Plot</title>
|
|
8
|
-
<!-- Correct Plotly.js CDN -->
|
|
9
|
-
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
|
10
|
-
</head>
|
|
11
|
-
|
|
12
|
-
<body>
|
|
13
|
-
<div id="surfacePlotX" style="width: 1024px; height: 800px;"></div>
|
|
14
|
-
<div id="surfacePlotY" style="width: 1024px; height: 800px;"></div>
|
|
15
|
-
|
|
16
|
-
<script>
|
|
17
|
-
// Data for the correction table
|
|
18
|
-
const x = [0, 5, 10, 15, 20]; // Speed axis
|
|
19
|
-
const y = [0, 10, 20, 30, 40]; // Heel axis
|
|
20
|
-
|
|
21
|
-
// Correction values for the X surface
|
|
22
|
-
const zX = [
|
|
23
|
-
[0, 1, 2, 3, 4],
|
|
24
|
-
[1, 2, 3, 4, 5],
|
|
25
|
-
[2, 3, 4, 5, 6],
|
|
26
|
-
[3, 4, 5, 6, 7],
|
|
27
|
-
[4, 5, 6, 7, 8],
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
// Correction values for the Y surface
|
|
31
|
-
const zY = [
|
|
32
|
-
[0, -1, -2, -3, -4],
|
|
33
|
-
[-1, -2, -3, -4, -5],
|
|
34
|
-
[-2, -3, -4, -5, -6],
|
|
35
|
-
[-3, -4, -5, -6, -7],
|
|
36
|
-
[-4, -5, -6, -7, -8],
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
// Surface plot for X correction
|
|
40
|
-
const dataX = {
|
|
41
|
-
x: x,
|
|
42
|
-
y: y,
|
|
43
|
-
z: zX,
|
|
44
|
-
type: 'surface',
|
|
45
|
-
colorscale: 'Viridis',
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Surface plot for Y correction
|
|
49
|
-
const dataY = {
|
|
50
|
-
x: x,
|
|
51
|
-
y: y,
|
|
52
|
-
z: zY,
|
|
53
|
-
type: 'surface',
|
|
54
|
-
colorscale: 'Cividis',
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Render plots
|
|
58
|
-
Plotly.newPlot('surfacePlotX', [dataX], {
|
|
59
|
-
title: 'X Correction Surface',
|
|
60
|
-
scene: {
|
|
61
|
-
xaxis: { title: 'Speed' },
|
|
62
|
-
yaxis: { title: 'Heel' },
|
|
63
|
-
zaxis: { title: 'X Correction' },
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
Plotly.newPlot('surfacePlotY', [dataY], {
|
|
68
|
-
title: 'Y Correction Surface',
|
|
69
|
-
scene: {
|
|
70
|
-
xaxis: { title: 'Speed' },
|
|
71
|
-
yaxis: { title: 'Heel' },
|
|
72
|
-
zaxis: { title: 'Y Correction' },
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
</script>
|
|
76
|
-
</body>
|
|
77
|
-
|
|
78
|
-
</html>
|