-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMER-traverse-converter.html
287 lines (243 loc) · 12.1 KB
/
MER-traverse-converter.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
<!--
Turns rover motion files (SVF and RVF) into JSON arrays.
Saves into JSON file.
To do: plot data with plotly, see traverse2d.html
-->
<!DOCTYPE html>
<html>
<head>
<title>MER RVF/SVF File Processor</title>
</head>
<body>
<h1>MER missions RVF/SVF File Processor</h1>
Processor for <a href="https://pds-geosciences.wustl.edu/mer/mer2-m-eng-6-rmc-ops-v1/mer2rm_0xxx/data/">Rover Vector Files and Site Vector Files</a><br>
See <a href="https://pds-geosciences.wustl.edu/mer/mer2-m-eng-6-rmc-ops-v1/mer2rm_0xxx/document/rmc_sis.htm">Rover Motion Counter (RMC) Master File SIS</a>
for details about format and content (PDF <a href="https://pds-geosciences.wustl.edu/mer/mer2-m-eng-6-rmc-ops-v1/mer2rm_0xxx/document/rmc_sis.pdf">here</a>)<br><br>
<div class="container">
<div class="controls">
<h2>File Input</h2>
<div class="file-input">
<label>Master SVF:</label><br>
<input type="file" id="masterFile" accept=".svf">
</div>
<div class="file-input">
<label>Secondary RVF Files:</label><br>
<input type="file" id="secondaryFiles" accept=".rvf" multiple>
</div>
<button id="saveButton" style="margin-top: 20px;" disabled>Save</button><br>
</div>
</div>
<script>
let masterData = null;
let secondaryData = [];
let siteOffsets = [];
let drives = {};
// Funzione per convertire quaternione in Euler angles (yaw, pitch, roll)
function quaternionToEuler(q) {
let yaw = Math.atan2(2*(q.s*q.v3 + q.v1*q.v2), 1 - 2*(q.v2*q.v2 + q.v3*q.v3));
let pitch = Math.asin(2*(q.s*q.v2 - q.v3*q.v1));
let roll = Math.atan2(2*(q.s*q.v1 + q.v2*q.v3), 1 - 2*(q.v1*q.v1 + q.v2*q.v2));
return { yaw, pitch, roll };
}
// Event listeners per i file
document.getElementById('masterFile').addEventListener('change', async (e) => {
const file = e.target.files[0];
const text = await file.text();
masterData = new DOMParser().parseFromString(text, 'text/xml');
updateButton();
processMasterFile();
});
// Event listener per i file secondari
document.getElementById('secondaryFiles').addEventListener('change', async (e) => {
secondaryData = [];
// Ordina i file in base al nome
const files = Array.from(e.target.files).sort((a, b) => a.name.localeCompare(b.name));
for (let file of files) {
const text = await file.text();
console.log(file.name); // Per verifica
secondaryData.push(new DOMParser().parseFromString(text, 'text/xml'));
}
updateButton();
processSecondaryFiles();
});
function updateButton() {
button = document.getElementById('saveButton');
saveButton.disabled = !(masterData && secondaryData.length > 0 );
}
function processMasterFile() {
siteOffsets = [];
solutionsSites = masterData.getElementsByTagName('solution');
console.log(solutionsSites);
let cumulativeOffset = { x: 0, y: 0, z: 0 };
for (let solutionSite of solutionsSites) {
console.log("solutionSite", solutionSite);
if (solutionSite.getAttribute('name') === 'SITE_FRAME') {
const index1 = parseInt(solutionSite.getAttribute('index1'));
//console.log(" site found", index1);
const offset = solutionSite.getElementsByTagName('offset')[0];
//console.log(" offset=", offset);
const relativeOffset = {
x: parseFloat(offset.getAttribute('x')),
y: parseFloat(offset.getAttribute('y')),
z: parseFloat(offset.getAttribute('z'))
};
//console.log(" relativeOffset=", relativeOffset);
cumulativeOffset = {
x: cumulativeOffset.x + relativeOffset.x,
y: cumulativeOffset.y + relativeOffset.y,
z: cumulativeOffset.z + relativeOffset.z
};
//console.log(" cumulativeOffset=", cumulativeOffset);
siteOffsets.push({
index1,
relativeOffset,
cumulativeOffset: {...cumulativeOffset}
});
} else {
// never here
}
}
}
function processSecondaryFiles() {
/*
* 1. Site - Declared by operations personnel (i.e. manually), this is a major coordinate frame from which all activities in a local region are referenced. When the Site is incremented. all other components are set to 0.
* 2. Drive - Incremented by the rover whenever it intentionally moves. When the Drive is incremented, all other components except for Site are set to 0.
* 3. IDD - Incremented by the rover whenever the IDD (Instrument Deployment Device) moves. Other components are NOT set to 0 when this changes.
* 4. PMA - Incremented by the rover whenever the PMA (Pancam Mast Assembly) moves. Other components are NOT set to 0 when this changes.
* 5. HGA - Incremented by the rover whenever the HGA (High Gain Antenna) moves. Other components are NOT set to 0 when this changes
*/
drives = {};
let lastSite = null;
let lastDrive = null;
// Per ogni documento XML secondario
for (let doc of secondaryData) {
solutionsDrives = doc.getElementsByTagName('solution');
// Per ogni solution nel documento
for (let solutionDrive of solutionsDrives) {
if ((solutionDrive.getAttribute('name') === 'ROVER_FRAME') && (solutionDrive.getAttribute('solution_id') === 'telemetry')) {
const site = parseInt(solutionDrive.getAttribute('index1'));
const drive = parseInt(solutionDrive.getAttribute('index2'));
const IDD = parseInt(solutionDrive.getAttribute('index3')); // arm
const PMA = parseInt(solutionDrive.getAttribute('index4')); // pancam
const HGA = parseInt(solutionDrive.getAttribute('index5')); // Haigh Gain Antenna
// Salta se site e drive sono uguali all'ultima soluzione processata
/*if (lastSite === site && lastDrive === drive && lastPMA === PMA) {
continue;
}*/
console.log(" site, drive, arm, pancam, antenna:", site, drive, IDD, PMA, HGA);
// Aggiorna gli ultimi site e drive processati
lastSite = site;
lastDrive = drive;
lastIDD = IDD;
lastPMA = PMA;
lastHGA = HGA;
// Ottieni offset e orientamento dalla solution
//console.log("OFFSET=",solutionDrive.getElementsByTagName('offset'));
const offset = solutionDrive.getElementsByTagName('offset')[0];
const orientation = solutionDrive.getElementsByTagName('orientation')[0];
// Crea oggetto quaternione dai dati
/*
* "The orientation element is a quaternion representing the rotation of the current frame with
* respect to the reference frame (reference = current * orientation). Note that per ground system
* convention, the scalar part of the quaternion is listed first; this differs from the flight system
* convention of putting the scalar part last." (s, v1, v2, v3) (w, x, y, z)
*/
const quaternion = {
s: parseFloat(orientation.getAttribute('s')),
v1: parseFloat(orientation.getAttribute('v1')),
v2: parseFloat(orientation.getAttribute('v2')),
v3: parseFloat(orientation.getAttribute('v3'))
};
// Cerca l'offset cumulativo corrispondente al site
let siteOffset = { x: 0, y: 0, z: 0 }; // valore di default
if (site > 0) {
siteOffset = siteOffsets[site-1].cumulativeOffset;
}
// Crea oggetto con l'offset del drive
const driveOffset = {
x: parseFloat(offset.getAttribute('x')),
y: parseFloat(offset.getAttribute('y')),
z: parseFloat(offset.getAttribute('z'))
};
// Calcola l'offset totale sommando siteOffset e driveOffset
const totalOffset = {
x: siteOffset.x + driveOffset.x,
y: siteOffset.y + driveOffset.y,
z: siteOffset.z + driveOffset.z
};
// Inizializza l'oggetto per il site se non esiste
if (!drives[site]) {
drives[site] = {};
}
// Memorizza tutti i dati per questa combinazione site/drive
drives[site][drive] = {
offset: driveOffset,
quaternion: quaternion,
euler: quaternionToEuler(quaternion), // w.r.t north
totalOffset: totalOffset
};
}
}
}
}
/**
* Calcola la differenza tra i campi euler e totalOffset di due elementi dell'array drives
* @param {Object} drives - Array bidimensionale con indici site e drive
* @param {string|number} site1 - Primo indice site
* @param {string|number} drive1 - Primo indice drive
* @param {string|number} site2 - Secondo indice site
* @param {string|number} drive2 - Secondo indice drive
* @returns {Object} Oggetto contenente le differenze calcolate
*/
function calculateDrivesDifferences(site1, drive1, site2, drive2) {
// Costante per convertire da radianti a gradi
const RAD_TO_DEG = 180 / Math.PI;
// Ottieni i due elementi dall'array
const element1 = drives[site1][drive1];
const element2 = drives[site2][drive2];
// Calcola le differenze per gli angoli di Euler e converti in gradi
const eulerDiffRad = {
yaw: (element1.euler.yaw - element2.euler.yaw),
pitch: (element1.euler.pitch - element2.euler.pitch),
roll: (element1.euler.roll - element2.euler.roll)
};
const eulerDiffDeg = {
yaw: (element1.euler.yaw - element2.euler.yaw) * RAD_TO_DEG,
pitch: (element1.euler.pitch - element2.euler.pitch) * RAD_TO_DEG,
roll: (element1.euler.roll - element2.euler.roll) * RAD_TO_DEG
};
// Calcola le differenze per totalOffset
const totalOffsetDiff = {
x: element1.totalOffset.x - element2.totalOffset.x,
y: element1.totalOffset.y - element2.totalOffset.y,
z: element1.totalOffset.z - element2.totalOffset.z
};
return {
eulerDiffRad,
eulerDiffDeg,
totalOffsetDiff
};
}
// Funzione per salvare i dati su un file JSON
function saveDataToFile(data, filename) {
// Converti i dati in una stringa JSON
const jsonData = "const drives = " + JSON.stringify(data, null, 2);
// Crea un blob con i dati
const blob = new Blob([jsonData], { type: "application/json" });
// Crea un URL per il download
const url = window.URL.createObjectURL(blob);
// Crea un elemento <a> per il download
const a = document.createElement("a");
a.href = url;
a.download = filename;
// Attiva il download
a.click();
// Libera la memoria
window.URL.revokeObjectURL(url);
}
document.getElementById("saveButton").addEventListener("click", () => {
saveDataToFile(drives, "drives.js");
});
</script>
</body>
</html>