speedandcurrent 1.0.0
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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/correctionModel.png +0 -0
- package/correctionTable.js +179 -0
- package/currentModel.png +0 -0
- package/index.js +592 -0
- package/models.md +26 -0
- package/package.json +34 -0
- package/public/TableRenderer.js +186 -0
- package/public/app.js +278 -0
- package/public/icon.png +0 -0
- package/public/index.html +107 -0
- package/public/style.css +184 -0
- package/public/surfaceplot.html +78 -0
- package/public/vectors.css +59 -0
- package/public/vectors.html +30 -0
- package/public/vectors.js +165 -0
package/public/style.css
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-family: Arial, sans-serif;
|
|
3
|
+
display: flex;
|
|
4
|
+
|
|
5
|
+
position: relative;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
margin: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
section {
|
|
12
|
+
width: 100%;
|
|
13
|
+
padding: 10px;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
svg {
|
|
19
|
+
border: 1px solid #ccc;
|
|
20
|
+
width: 50%;
|
|
21
|
+
height: auto;
|
|
22
|
+
aspect-ratio: 1 / 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.label {
|
|
26
|
+
font-size: 12px;
|
|
27
|
+
text-anchor: middle;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
table {
|
|
31
|
+
border-collapse: collapse;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* .correctionTable {
|
|
35
|
+
|
|
36
|
+
} */
|
|
37
|
+
|
|
38
|
+
th,
|
|
39
|
+
td {
|
|
40
|
+
border: 1px solid black;
|
|
41
|
+
padding: 5px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
th {
|
|
45
|
+
background-color: #a2a2a2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* tr:nth-child(even) {
|
|
49
|
+
background-color: #f2f2f2;
|
|
50
|
+
} */
|
|
51
|
+
|
|
52
|
+
.TablecolumnHeader {
|
|
53
|
+
text-align: center;
|
|
54
|
+
background-color: #a2a2a2;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.TableRowHeader {
|
|
58
|
+
text-align: right;
|
|
59
|
+
background-color: #a2a2a2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.TableCell {
|
|
63
|
+
width: 60px;
|
|
64
|
+
/* Adjust the size as needed */
|
|
65
|
+
height: 60px;
|
|
66
|
+
/* Same as width to make it square */
|
|
67
|
+
text-align: center;
|
|
68
|
+
vertical-align: middle;
|
|
69
|
+
font-size: 11px;
|
|
70
|
+
background-color:white;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.selectedCell {
|
|
74
|
+
background-color: #ff0000;
|
|
75
|
+
color: #fff;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.emptyCell {
|
|
79
|
+
background-color: #f2f2f2;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Fixed menu bar */
|
|
83
|
+
.menu-bar {
|
|
84
|
+
position: fixed;
|
|
85
|
+
top: 0;
|
|
86
|
+
left: 0;
|
|
87
|
+
width: 100%;
|
|
88
|
+
background: #6a6a6a;
|
|
89
|
+
color: black;
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
padding: 10px 20px;
|
|
93
|
+
gap: 20px;
|
|
94
|
+
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.2);
|
|
95
|
+
z-index: 1000;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Menu items */
|
|
99
|
+
.menu-item {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 5px;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Labels */
|
|
107
|
+
.menu-item label {
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
color: black;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Dropdowns styled as text with dropdown arrow */
|
|
113
|
+
.menu-item select {
|
|
114
|
+
background: transparent;
|
|
115
|
+
border: none;
|
|
116
|
+
border: 1px solid #888;
|
|
117
|
+
color: black;
|
|
118
|
+
font-size: 14px;
|
|
119
|
+
appearance: none;
|
|
120
|
+
padding-right: 15px;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Custom dropdown styling */
|
|
125
|
+
.menu-item select::-ms-expand {
|
|
126
|
+
display: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
/* Pause Button */
|
|
131
|
+
#toggle-updates {
|
|
132
|
+
background: transparent;
|
|
133
|
+
border: 1px solid #888;
|
|
134
|
+
padding: 5px 15px;
|
|
135
|
+
border-radius: 4px;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#toggle-updates:hover {
|
|
140
|
+
background: #555;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Push content below fixed menu */
|
|
144
|
+
.content {
|
|
145
|
+
margin-top: 60px;
|
|
146
|
+
: 20px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.boatSpeed {
|
|
150
|
+
background-color: lightseagreen;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.correction {
|
|
154
|
+
background-color: red;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.correctedSpeed {
|
|
158
|
+
background-color: seagreen;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.residual {
|
|
162
|
+
background-color: rgb(250, 140, 158);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.current {
|
|
166
|
+
background-color: rgb(52, 52, 255);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
.groundSpeed {
|
|
171
|
+
background-color: brown;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.surfacePlot {
|
|
175
|
+
width: 100%;
|
|
176
|
+
|
|
177
|
+
height: 100%;
|
|
178
|
+
border: 1px solid black;
|
|
179
|
+
/* Keep the border for recognition */
|
|
180
|
+
padding: 0;
|
|
181
|
+
margin: 0;
|
|
182
|
+
position: relative;
|
|
183
|
+
/* Ensure it can be positioned correctly */
|
|
184
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-family: Arial, sans-serif;
|
|
3
|
+
display: flex;
|
|
4
|
+
position: relative;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
margin: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
svg {
|
|
12
|
+
width: 100vmin;
|
|
13
|
+
height: 100vmin;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
line {
|
|
19
|
+
stroke-width: 1;
|
|
20
|
+
stroke: green;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#hull {
|
|
24
|
+
stroke: purple;
|
|
25
|
+
stroke-width: 1;
|
|
26
|
+
stroke-linecap: round;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#mast {
|
|
30
|
+
stroke: purple;
|
|
31
|
+
stroke-width: 1;
|
|
32
|
+
stroke-linecap: round;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#boatSpeed {
|
|
36
|
+
stroke: lightseagreen;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#correction {
|
|
40
|
+
stroke: red;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#correctedSpeed {
|
|
45
|
+
stroke: seagreen;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#residual {
|
|
49
|
+
stroke: rgb(250, 140, 158);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#current {
|
|
53
|
+
stroke: rgb(52, 52, 255) ;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
#groundSpeed {
|
|
58
|
+
stroke: brown;
|
|
59
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Wind vectors</title>
|
|
7
|
+
<link rel="stylesheet" href="vectors.css">
|
|
8
|
+
</head>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
<div id="message"></div>
|
|
12
|
+
|
|
13
|
+
<svg height="400" width="400" xmlns="http://www.w3.org/2000/svg" viewbox="-100 -100 200 200" id="canvas">
|
|
14
|
+
|
|
15
|
+
<!-- Draw the paths -->
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
<path id="hull" d="M -45 10 Q 15 25 30 0 Q 15 -25 -45 -10 z" stroke="black" fill="none" stroke-width="1"
|
|
19
|
+
transform="rotate(-90), scale(1, 1)" />
|
|
20
|
+
|
|
21
|
+
<line id="mast" x1="0" y1="0" x2="1" y2="8" stroke="brown" stroke-width="2" transform="rotate(-90)" />
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Sorry, your browser does not support inline SVG.
|
|
25
|
+
</svg>
|
|
26
|
+
|
|
27
|
+
</body>
|
|
28
|
+
<script src="vectors.js"></script>
|
|
29
|
+
|
|
30
|
+
</html>
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const API_BASE_URL = "/plugins/speedandcurrent";
|
|
2
|
+
let previous = 0;
|
|
3
|
+
let lastTime = new Date();
|
|
4
|
+
|
|
5
|
+
function getHeading(data) {
|
|
6
|
+
let heading = 0;
|
|
7
|
+
data.deltas.forEach(delta => {
|
|
8
|
+
if (delta.id == "heading") {
|
|
9
|
+
heading = delta.value * 180 / Math.PI;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
return heading;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getOffset(att, height) {
|
|
16
|
+
let offset = { x: 0, y: 0 };
|
|
17
|
+
offset.x = Math.sin(att.pitch) * height * 7.5;
|
|
18
|
+
offset.y = Math.sin(att.roll) * height * 7.5;
|
|
19
|
+
return offset;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getAtt(data) {
|
|
23
|
+
let att = { pitch: 0, roll: 0 };
|
|
24
|
+
data.deltas.forEach(delta => {
|
|
25
|
+
if (delta.id == "attitude") {
|
|
26
|
+
att.pitch = (delta.value.pitch);
|
|
27
|
+
att.roll = (delta.value.roll);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return att;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getLargest(data) {
|
|
34
|
+
let largest = 1;
|
|
35
|
+
data.polars.forEach(polar => {
|
|
36
|
+
if (polar.id != "sensorSpeed")
|
|
37
|
+
largest = Math.max(largest, polar.speed);
|
|
38
|
+
});
|
|
39
|
+
return largest;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function drawBoat(canvas, data, heading, offset, att) {
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const hull = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
46
|
+
hull.setAttribute("d", "M -45 8 Q 15 20 30 0 Q 15 -20 -45 -8 z");
|
|
47
|
+
hull.setAttribute("fill", "none");
|
|
48
|
+
hull.setAttribute("stroke", "black");
|
|
49
|
+
hull.setAttribute("stroke-width", "1");
|
|
50
|
+
//hull.setAttribute("transform", `scale(${1 - Math.abs(Math.sin(att.pitch))}, ${1 - Math.abs(Math.sin(att.roll))})`);
|
|
51
|
+
hull.setAttribute("id", "hull");
|
|
52
|
+
|
|
53
|
+
// const mast = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
54
|
+
// mast.setAttribute("x1", 0);
|
|
55
|
+
// mast.setAttribute("y1", 0);
|
|
56
|
+
// mast.setAttribute("x2", offset.x);
|
|
57
|
+
// mast.setAttribute("y2", offset.y);
|
|
58
|
+
// mast.setAttribute("id", "mast");
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
const boat = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
62
|
+
boat.setAttribute("transform", `rotate(${heading - 90})`);
|
|
63
|
+
boat.appendChild(hull);
|
|
64
|
+
// boat.appendChild(mast);
|
|
65
|
+
canvas.appendChild(boat);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
function drawVectors(canvas, data, heading, scale) {
|
|
70
|
+
data.polars.forEach(polar => {
|
|
71
|
+
const vector = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
72
|
+
vector.setAttribute("x1", 0 + (polar.plane == "ref_mast" ? offset.x : 0));
|
|
73
|
+
vector.setAttribute("y1", 0 + (polar.plane == "ref_mast" ? offset.y : 0));
|
|
74
|
+
vector.setAttribute("x2", polar.speed * Math.cos(polar.angle) * scale + (polar.plane == "ref_mast" ? offset.x : 0));
|
|
75
|
+
vector.setAttribute("y2", polar.speed * Math.sin(polar.angle) * scale + (polar.plane == "ref_mast" ? offset.y : 0));
|
|
76
|
+
vector.setAttribute("id", polar.id);
|
|
77
|
+
s = scale * 5 / 1.94384 - 1;
|
|
78
|
+
if (s>50) {
|
|
79
|
+
s = s / 5;
|
|
80
|
+
dash = `1 1 ${s - 1} 1 ${s} 1 ${s} 1 ${s} 1 ${s - 1} 1 `;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
dash = `1 1 ${s - 1} 1 ${s} 1 `;
|
|
84
|
+
}
|
|
85
|
+
vector.setAttribute("stroke-dasharray", dash);
|
|
86
|
+
vector.setAttribute("stroke-dashoffset", `1`);
|
|
87
|
+
if (polar.plane == "ref_ground")
|
|
88
|
+
vector.setAttribute("transform", `rotate(${-90})`);
|
|
89
|
+
else
|
|
90
|
+
vector.setAttribute("transform", `rotate(${heading - 90})`);
|
|
91
|
+
canvas.appendChild(vector);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async function fetchVectorData() {
|
|
97
|
+
const data = await getFromServer('getVectors');
|
|
98
|
+
if (data) {
|
|
99
|
+
const canvas = document.getElementById("canvas");
|
|
100
|
+
canvas.innerHTML = "";
|
|
101
|
+
|
|
102
|
+
heading = getHeading(data);
|
|
103
|
+
largest = getLargest(data);
|
|
104
|
+
att = getAtt(data);
|
|
105
|
+
offset = getOffset(att, 15);
|
|
106
|
+
if (previous == 0) {
|
|
107
|
+
previous = largest;
|
|
108
|
+
lastTime = new Date();
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
time = new Date();
|
|
112
|
+
deltaT = (time - lastTime);
|
|
113
|
+
if (largest > previous)
|
|
114
|
+
tc = 1;
|
|
115
|
+
else
|
|
116
|
+
tc = 4;
|
|
117
|
+
const alpha = 1 - Math.exp(-deltaT / tc);
|
|
118
|
+
previous = previous + alpha * (largest - previous);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
scale = 100 / previous;
|
|
122
|
+
|
|
123
|
+
drawBoat(canvas, data, heading, offset, att);
|
|
124
|
+
drawVectors(canvas, data, heading, scale, offset);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function getFromServer(endpoint) {
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch(`${API_BASE_URL}/${endpoint}`, {
|
|
130
|
+
method: 'GET',
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': 'application/json'
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
if (response.status === 503) {
|
|
138
|
+
throw new Error("Plugin is not running");
|
|
139
|
+
} else {
|
|
140
|
+
throw new Error(`Error fetching data: ${response.statusText}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
document.getElementById("message").innerHTML = "";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const data = await response.json();
|
|
148
|
+
return data;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
handleError(error);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function handleError(error) {
|
|
157
|
+
document.getElementById("message").innerHTML = `
|
|
158
|
+
<div>
|
|
159
|
+
<p>${error}</p>
|
|
160
|
+
</div>
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
setInterval(fetchVectorData, 200);
|