1 /*
2 * Copyright (C) 2020 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.navigation.inertial.calibration.magnetometer;
17
18 import com.irurueta.algebra.Matrix;
19 import com.irurueta.navigation.LockedException;
20 import com.irurueta.navigation.frames.ECEFPosition;
21 import com.irurueta.navigation.frames.ECEFVelocity;
22 import com.irurueta.navigation.frames.NEDPosition;
23 import com.irurueta.navigation.frames.NEDVelocity;
24 import com.irurueta.navigation.frames.converters.ECEFtoNEDPositionVelocityConverter;
25 import com.irurueta.navigation.frames.converters.NEDtoECEFPositionVelocityConverter;
26 import com.irurueta.navigation.inertial.calibration.CalibrationException;
27 import com.irurueta.navigation.inertial.calibration.StandardDeviationBodyMagneticFluxDensity;
28 import com.irurueta.navigation.inertial.wmm.WMMEarthMagneticFluxDensityEstimator;
29 import com.irurueta.navigation.inertial.wmm.WorldMagneticModel;
30
31 import java.io.IOException;
32 import java.util.Collection;
33 import java.util.Date;
34 import java.util.GregorianCalendar;
35 import java.util.List;
36
37 /**
38 * Estimates magnetometer cross couplings and scaling factors.
39 * This calibrator uses Levenberg-Marquardt to find a minimum least squared
40 * error solution.
41 * <p>
42 * To use this calibrator at least 7 measurements taken at a single known
43 * position and instant must be taken at 7 different unknown orientations and
44 * zero velocity when common z-axis is assumed, otherwise at least 10
45 * measurements are required.
46 * <p>
47 * Measured magnetic flux density is assumed to follow the model shown below:
48 * <pre>
49 * mBmeas = bm + (I + Mm) * mBtrue + w
50 * </pre>
51 * Where:
52 * - mBmeas is the measured magnetic flux density. This is a 3x1 vector.
53 * - bm is magnetometer hard-iron bias. Ideally, on a perfect magnetometer,
54 * this should be a 3x1 zero vector.
55 * - I is the 3x3 identity matrix.
56 * - Mm is the 3x3 soft-iron matrix containing cross-couplings and scaling
57 * factors. Ideally, on a perfect magnetometer, this should be a 3x3 zero
58 * matrix.
59 * - mBtrue is ground-truth magnetic flux density. This is a 3x1 vector.
60 * - w is measurement noise. This is a 3x1 vector.
61 * Notice that this calibrator assumes that all measurements are taken in
62 * a short span of time where Earth magnetic field can be assumed to be
63 * constant at provided location and instant.
64 */
65 public class KnownHardIronPositionAndInstantMagnetometerCalibrator extends
66 BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator<
67 KnownHardIronPositionAndInstantMagnetometerCalibrator,
68 KnownHardIronPositionAndInstantMagnetometerCalibratorListener> {
69
70 /**
71 * Position where body magnetic flux density measurements have been
72 * taken.
73 */
74 private NEDPosition position;
75
76 /**
77 * Timestamp expressed as decimal year where magnetic flux density
78 * measurements have been measured.
79 */
80 private Double year = convertTime(System.currentTimeMillis());
81
82 /**
83 * Contains Earth's magnetic model.
84 */
85 private WorldMagneticModel magneticModel;
86
87 /**
88 * Constructor.
89 */
90 public KnownHardIronPositionAndInstantMagnetometerCalibrator() {
91 super();
92 }
93
94 /**
95 * Constructor.
96 *
97 * @param listener listener to handle events raised by this calibrator.
98 */
99 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
100 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
101 super(listener);
102 }
103
104 /**
105 * Constructor.
106 *
107 * @param measurements collection of body magnetic flux density
108 * measurements with standard deviation of
109 * magnetometer measurements taken at the same
110 * position with zero velocity and unknown different
111 * orientations.
112 */
113 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
114 final Collection<StandardDeviationBodyMagneticFluxDensity> measurements) {
115 super(measurements);
116 }
117
118 /**
119 * Constructor.
120 *
121 * @param commonAxisUsed indicates whether z-axis is assumed to be common
122 * for the accelerometer, gyroscope and magnetometer.
123 */
124 public KnownHardIronPositionAndInstantMagnetometerCalibrator(final boolean commonAxisUsed) {
125 super(commonAxisUsed);
126 }
127
128 /**
129 * Constructor.
130 *
131 * @param magneticModel Earth's magnetic model. If null, a default model
132 * will be used instead.
133 */
134 public KnownHardIronPositionAndInstantMagnetometerCalibrator(final WorldMagneticModel magneticModel) {
135 super();
136 this.magneticModel = magneticModel;
137 }
138
139 /**
140 * Constructor.
141 *
142 * @param hardIron known hard-iron.
143 * @throws IllegalArgumentException if provided hard-iron array does
144 * not have length 3.
145 */
146 public KnownHardIronPositionAndInstantMagnetometerCalibrator(final double[] hardIron) {
147 super(hardIron);
148 }
149
150 /**
151 * Constructor.
152 *
153 * @param hardIron known hard-iron.
154 * @throws IllegalArgumentException if provided hard-iron matrix is not
155 * 3x1.
156 */
157 public KnownHardIronPositionAndInstantMagnetometerCalibrator(final Matrix hardIron) {
158 super(hardIron);
159 }
160
161 /**
162 * Constructor.
163 *
164 * @param hardIron known hard-iron.
165 * @param initialMm initial soft-iron matrix containing scale factors
166 * and cross coupling errors.
167 * @throws IllegalArgumentException if provided hard-iron matrix is not
168 * 3x1 or if soft-iron matrix is not
169 * 3x3.
170 */
171 public KnownHardIronPositionAndInstantMagnetometerCalibrator(final Matrix hardIron, final Matrix initialMm) {
172 super(hardIron, initialMm);
173 }
174
175 /**
176 * Constructor.
177 *
178 * @param position position where body magnetic flux density measurements
179 * have been taken.
180 */
181 public KnownHardIronPositionAndInstantMagnetometerCalibrator(final NEDPosition position) {
182 super();
183 this.position = position;
184 }
185
186 /**
187 * Constructor.
188 *
189 * @param position position where body magnetic flux density measurements
190 * have been taken.
191 * @param measurements collection of body magnetic flux density
192 * measurements with standard deviation of
193 * magnetometer measurements taken at the same
194 * position with zero velocity and unknown different
195 * orientations.
196 */
197 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
198 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements) {
199 super(measurements);
200 this.position = position;
201 }
202
203 /**
204 * Constructor.
205 *
206 * @param position position where body magnetic flux density measurements
207 * have been taken.
208 * @param measurements collection of body magnetic flux density
209 * measurements with standard deviation of
210 * magnetometer measurements taken at the same
211 * position with zero velocity and unknown different
212 * orientations.
213 * @param listener listener to handle events raised by this calibrator.
214 */
215 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
216 final NEDPosition position, final Collection<StandardDeviationBodyMagneticFluxDensity> measurements,
217 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
218 super(measurements, listener);
219 this.position = position;
220 }
221
222 /**
223 * Constructor.
224 *
225 * @param position position where body magnetic flux density measurements
226 * have been taken.
227 * @param measurements collection of body magnetic flux density
228 * measurements with standard deviation of
229 * magnetometer measurements taken at the same
230 * position with zero velocity and unknown different
231 * orientations.
232 * @param commonAxisUsed indicates whether z-axis is assumed to be common
233 * for the accelerometer, gyroscope and magnetometer.
234 */
235 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
236 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
237 final boolean commonAxisUsed) {
238 super(measurements, commonAxisUsed);
239 this.position = position;
240 }
241
242 /**
243 * Constructor.
244 *
245 * @param position position where body magnetic flux density measurements
246 * have been taken.
247 * @param measurements collection of body magnetic flux density
248 * measurements with standard deviation of
249 * magnetometer measurements taken at the same
250 * position with zero velocity and unknown different
251 * orientations.
252 * @param commonAxisUsed indicates whether z-axis is assumed to be common
253 * for the accelerometer, gyroscope and magnetometer.
254 * @param listener listener to handle events raised by this calibrator.
255 */
256 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
257 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
258 final boolean commonAxisUsed,
259 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
260 super(measurements, commonAxisUsed, listener);
261 this.position = position;
262 }
263
264 /**
265 * Constructor.
266 *
267 * @param position position where body magnetic flux density measurements
268 * have been taken.
269 * @param measurements collection of body magnetic flux density
270 * measurements with standard deviation of
271 * magnetometer measurements taken at the same
272 * position with zero velocity and unknown different
273 * orientations.
274 * @param hardIron known hard-iron.
275 * @throws IllegalArgumentException if provided hard-iron array does
276 * not have length 3.
277 */
278 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
279 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
280 final double[] hardIron) {
281 super(measurements, hardIron);
282 this.position = position;
283 }
284
285 /**
286 * Constructor.
287 *
288 * @param position position where body magnetic flux density measurements
289 * have been taken.
290 * @param measurements collection of body magnetic flux density
291 * measurements with standard deviation of
292 * magnetometer measurements taken at the same
293 * position with zero velocity and unknown different
294 * orientations.
295 * @param hardIron known hard-iron.
296 * @param listener listener to handle events raised by this calibrator.
297 * @throws IllegalArgumentException if provided hard-iron array does
298 * not have length 3.
299 */
300 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
301 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
302 final double[] hardIron, final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
303 super(measurements, hardIron, listener);
304 this.position = position;
305 }
306
307 /**
308 * @param position position where body magnetic flux density measurements
309 * have been taken.
310 * @param measurements collection of body magnetic flux density
311 * measurements with standard deviation of
312 * magnetometer measurements taken at the same
313 * position with zero velocity and unknown different
314 * orientations.
315 * @param commonAxisUsed indicates whether z-axis is assumed to be common
316 * for the accelerometer, gyroscope and magnetometer.
317 * @param hardIron known hard-iron.
318 * @throws IllegalArgumentException if provided hard-iron array does
319 * not have length 3.
320 */
321 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
322 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
323 final boolean commonAxisUsed, final double[] hardIron) {
324 super(measurements, commonAxisUsed, hardIron);
325 this.position = position;
326 }
327
328 /**
329 * Constructor.
330 *
331 * @param position position where body magnetic flux density measurements
332 * have been taken.
333 * @param measurements collection of body magnetic flux density
334 * measurements with standard deviation of
335 * magnetometer measurements taken at the same
336 * position with zero velocity and unknown different
337 * orientations.
338 * @param commonAxisUsed indicates whether z-axis is assumed to be common
339 * for the accelerometer, gyroscope and magnetometer.
340 * @param hardIron known hard-iron.
341 * @param listener listener to handle events raised by this calibrator.
342 * @throws IllegalArgumentException if provided hard-iron array does
343 * not have length 3.
344 */
345 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
346 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
347 final boolean commonAxisUsed, final double[] hardIron,
348 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
349 super(measurements, commonAxisUsed, hardIron, listener);
350 this.position = position;
351 }
352
353 /**
354 * Constructor.
355 *
356 * @param position position where body magnetic flux density measurements
357 * have been taken.
358 * @param measurements collection of body magnetic flux density
359 * measurements with standard deviation of
360 * magnetometer measurements taken at the same
361 * position with zero velocity and unknown different
362 * orientations.
363 * @param hardIron known hard-iron.
364 * @throws IllegalArgumentException if provided hard-iron matrix is not
365 * 3x1.
366 */
367 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
368 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
369 final Matrix hardIron) {
370 super(measurements, hardIron);
371 this.position = position;
372 }
373
374 /**
375 * Constructor.
376 *
377 * @param position position where body magnetic flux density measurements
378 * have been taken.
379 * @param measurements collection of body magnetic flux density
380 * measurements with standard deviation of
381 * magnetometer measurements taken at the same
382 * position with zero velocity and unknown different
383 * orientations.
384 * @param hardIron known hard-iron.
385 * @param listener listener to handle events raised by this calibrator.
386 * @throws IllegalArgumentException if provided hard-iron matrix is not
387 * 3x1.
388 */
389 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
390 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
391 final Matrix hardIron, final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
392 super(measurements, hardIron, listener);
393 this.position = position;
394 }
395
396 /**
397 * Constructor.
398 *
399 * @param position position where body magnetic flux density measurements
400 * have been taken.
401 * @param measurements collection of body magnetic flux density
402 * measurements with standard deviation of
403 * magnetometer measurements taken at the same
404 * position with zero velocity and unknown different
405 * orientations.
406 * @param commonAxisUsed indicates whether z-axis is assumed to be common
407 * for the accelerometer, gyroscope and magnetometer.
408 * @param hardIron known hard-iron.
409 * @throws IllegalArgumentException if provided hard-iron matrix is not
410 * 3x1.
411 */
412 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
413 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
414 final boolean commonAxisUsed, final Matrix hardIron) {
415 super(measurements, commonAxisUsed, hardIron);
416 this.position = position;
417 }
418
419 /**
420 * Constructor.
421 *
422 * @param position position where body magnetic flux density measurements
423 * have been taken.
424 * @param measurements collection of body magnetic flux density
425 * measurements with standard deviation of
426 * magnetometer measurements taken at the same
427 * position with zero velocity and unknown different
428 * orientations.
429 * @param commonAxisUsed indicates whether z-axis is assumed to be common
430 * for the accelerometer, gyroscope and magnetometer.
431 * @param hardIron known hard-iron.
432 * @param listener listener to handle events raised by this calibrator.
433 * @throws IllegalArgumentException if provided hard-iron matrix is not
434 * 3x1.
435 */
436 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
437 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
438 final boolean commonAxisUsed, final Matrix hardIron,
439 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
440 super(measurements, commonAxisUsed, hardIron, listener);
441 this.position = position;
442 }
443
444 /**
445 * Constructor.
446 *
447 * @param position position where body magnetic flux density measurements
448 * have been taken.
449 * @param measurements collection of body magnetic flux density
450 * measurements with standard deviation of
451 * magnetometer measurements taken at the same
452 * position with zero velocity and unknown different
453 * orientations.
454 * @param hardIron known hard-iron.
455 * @param initialMm initial soft-iron matrix containing scale factors
456 * and cross coupling errors.
457 * @throws IllegalArgumentException if provided hard-iron matrix is not
458 * 3x1 or if soft-iron matrix is not
459 * 3x3.
460 */
461 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
462 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
463 final Matrix hardIron, final Matrix initialMm) {
464 super(measurements, hardIron, initialMm);
465 this.position = position;
466 }
467
468 /**
469 * Constructor.
470 *
471 * @param position position where body magnetic flux density measurements
472 * have been taken.
473 * @param measurements collection of body magnetic flux density
474 * measurements with standard deviation of
475 * magnetometer measurements taken at the same
476 * position with zero velocity and unknown different
477 * orientations.
478 * @param hardIron known hard-iron.
479 * @param initialMm initial soft-iron matrix containing scale factors
480 * and cross coupling errors.
481 * @param listener listener to handle events raised by this calibrator.
482 * @throws IllegalArgumentException if provided hard-iron matrix is not
483 * 3x1 or if soft-iron matrix is not
484 * 3x3.
485 */
486 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
487 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
488 final Matrix hardIron, final Matrix initialMm,
489 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
490 super(measurements, hardIron, initialMm, listener);
491 this.position = position;
492 }
493
494 /**
495 * Constructor.
496 *
497 * @param position position where body magnetic flux density measurements
498 * have been taken.
499 * @param measurements collection of body magnetic flux density
500 * measurements with standard deviation of
501 * magnetometer measurements taken at the same
502 * position with zero velocity and unknown different
503 * orientations.
504 * @param commonAxisUsed indicates whether z-axis is assumed to be common
505 * for the accelerometer, gyroscope and magnetometer.
506 * @param hardIron known hard-iron.
507 * @param initialMm initial soft-iron matrix containing scale factors
508 * and cross coupling errors.
509 * @throws IllegalArgumentException if provided hard-iron matrix is not
510 * 3x1 or if soft-iron matrix is not
511 * 3x3.
512 */
513 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
514 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
515 final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm) {
516 super(measurements, commonAxisUsed, hardIron, initialMm);
517 this.position = position;
518 }
519
520 /**
521 * Constructor.
522 *
523 * @param position position where body magnetic flux density measurements
524 * have been taken.
525 * @param measurements collection of body magnetic flux density
526 * measurements with standard deviation of
527 * magnetometer measurements taken at the same
528 * position with zero velocity and unknown different
529 * orientations.
530 * @param commonAxisUsed indicates whether z-axis is assumed to be common
531 * for the accelerometer, gyroscope and magnetometer.
532 * @param hardIron known hard-iron.
533 * @param initialMm initial soft-iron matrix containing scale factors
534 * and cross coupling errors.
535 * @param listener listener to handle events raised by this calibrator.
536 * @throws IllegalArgumentException if provided hard-iron matrix is not
537 * 3x1 or if soft-iron matrix is not
538 * 3x3.
539 */
540 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
541 final NEDPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
542 final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm,
543 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
544 super(measurements, commonAxisUsed, hardIron, initialMm, listener);
545 this.position = position;
546 }
547
548 /**
549 * Constructor.
550 *
551 * @param position position where body magnetic flux density measurements
552 * have been taken.
553 */
554 public KnownHardIronPositionAndInstantMagnetometerCalibrator(final ECEFPosition position) {
555 this(convertPosition(position));
556 }
557
558 /**
559 * Constructor.
560 *
561 * @param position position where body magnetic flux density measurements
562 * have been taken.
563 * @param measurements collection of body magnetic flux density
564 * measurements with standard deviation of
565 * magnetometer measurements taken at the same
566 * position with zero velocity and unknown different
567 * orientations.
568 */
569 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
570 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements) {
571 this(convertPosition(position), measurements);
572 }
573
574 /**
575 * Constructor.
576 *
577 * @param position position where body magnetic flux density measurements
578 * have been taken.
579 * @param measurements collection of body magnetic flux density
580 * measurements with standard deviation of
581 * magnetometer measurements taken at the same
582 * position with zero velocity and unknown different
583 * orientations.
584 * @param listener listener to handle events raised by this calibrator.
585 */
586 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
587 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
588 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
589 this(convertPosition(position), measurements, listener);
590 }
591
592 /**
593 * Constructor.
594 *
595 * @param position position where body magnetic flux density measurements
596 * have been taken.
597 * @param measurements collection of body magnetic flux density
598 * measurements with standard deviation of
599 * magnetometer measurements taken at the same
600 * position with zero velocity and unknown different
601 * orientations.
602 * @param commonAxisUsed indicates whether z-axis is assumed to be common
603 * for the accelerometer, gyroscope and magnetometer.
604 */
605 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
606 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
607 final boolean commonAxisUsed) {
608 this(convertPosition(position), measurements, commonAxisUsed);
609 }
610
611 /**
612 * Constructor.
613 *
614 * @param position position where body magnetic flux density measurements
615 * have been taken.
616 * @param measurements collection of body magnetic flux density
617 * measurements with standard deviation of
618 * magnetometer measurements taken at the same
619 * position with zero velocity and unknown different
620 * orientations.
621 * @param commonAxisUsed indicates whether z-axis is assumed to be common
622 * for the accelerometer, gyroscope and magnetometer.
623 * @param listener listener to handle events raised by this calibrator.
624 */
625 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
626 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
627 final boolean commonAxisUsed,
628 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
629 this(convertPosition(position), measurements, commonAxisUsed, listener);
630 }
631
632 /**
633 * Constructor.
634 *
635 * @param position position where body magnetic flux density measurements
636 * have been taken.
637 * @param measurements collection of body magnetic flux density
638 * measurements with standard deviation of
639 * magnetometer measurements taken at the same
640 * position with zero velocity and unknown different
641 * orientations.
642 * @param hardIron known hard-iron.
643 * @throws IllegalArgumentException if provided hard-iron array does
644 * not have length 3.
645 */
646 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
647 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
648 final double[] hardIron) {
649 this(convertPosition(position), measurements, hardIron);
650 }
651
652 /**
653 * Constructor.
654 *
655 * @param position position where body magnetic flux density measurements
656 * have been taken.
657 * @param measurements collection of body magnetic flux density
658 * measurements with standard deviation of
659 * magnetometer measurements taken at the same
660 * position with zero velocity and unknown different
661 * orientations.
662 * @param hardIron known hard-iron.
663 * @param listener listener to handle events raised by this calibrator.
664 * @throws IllegalArgumentException if provided hard-iron array does
665 * not have length 3.
666 */
667 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
668 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
669 final double[] hardIron, final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
670 this(convertPosition(position), measurements, hardIron, listener);
671 }
672
673 /**
674 * @param position position where body magnetic flux density measurements
675 * have been taken.
676 * @param measurements collection of body magnetic flux density
677 * measurements with standard deviation of
678 * magnetometer measurements taken at the same
679 * position with zero velocity and unknown different
680 * orientations.
681 * @param commonAxisUsed indicates whether z-axis is assumed to be common
682 * for the accelerometer, gyroscope and magnetometer.
683 * @param hardIron known hard-iron.
684 * @throws IllegalArgumentException if provided hard-iron array does
685 * not have length 3.
686 */
687 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
688 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
689 final boolean commonAxisUsed, final double[] hardIron) {
690 this(convertPosition(position), measurements, commonAxisUsed, hardIron);
691 }
692
693 /**
694 * Constructor.
695 *
696 * @param position position where body magnetic flux density measurements
697 * have been taken.
698 * @param measurements collection of body magnetic flux density
699 * measurements with standard deviation of
700 * magnetometer measurements taken at the same
701 * position with zero velocity and unknown different
702 * orientations.
703 * @param commonAxisUsed indicates whether z-axis is assumed to be common
704 * for the accelerometer, gyroscope and magnetometer.
705 * @param hardIron known hard-iron.
706 * @param listener listener to handle events raised by this calibrator.
707 * @throws IllegalArgumentException if provided hard-iron array does
708 * not have length 3.
709 */
710 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
711 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
712 final boolean commonAxisUsed, final double[] hardIron,
713 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
714 this(convertPosition(position), measurements, commonAxisUsed, hardIron, listener);
715 }
716
717 /**
718 * Constructor.
719 *
720 * @param position position where body magnetic flux density measurements
721 * have been taken.
722 * @param measurements collection of body magnetic flux density
723 * measurements with standard deviation of
724 * magnetometer measurements taken at the same
725 * position with zero velocity and unknown different
726 * orientations.
727 * @param hardIron known hard-iron.
728 * @throws IllegalArgumentException if provided hard-iron matrix is not
729 * 3x1.
730 */
731 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
732 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
733 final Matrix hardIron) {
734 this(convertPosition(position), measurements, hardIron);
735 }
736
737 /**
738 * Constructor.
739 *
740 * @param position position where body magnetic flux density measurements
741 * have been taken.
742 * @param measurements collection of body magnetic flux density
743 * measurements with standard deviation of
744 * magnetometer measurements taken at the same
745 * position with zero velocity and unknown different
746 * orientations.
747 * @param hardIron known hard-iron.
748 * @param listener listener to handle events raised by this calibrator.
749 * @throws IllegalArgumentException if provided hard-iron matrix is not
750 * 3x1.
751 */
752 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
753 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
754 final Matrix hardIron, final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
755 this(convertPosition(position), measurements, hardIron, listener);
756 }
757
758 /**
759 * Constructor.
760 *
761 * @param position position where body magnetic flux density measurements
762 * have been taken.
763 * @param measurements collection of body magnetic flux density
764 * measurements with standard deviation of
765 * magnetometer measurements taken at the same
766 * position with zero velocity and unknown different
767 * orientations.
768 * @param commonAxisUsed indicates whether z-axis is assumed to be common
769 * for the accelerometer, gyroscope and magnetometer.
770 * @param hardIron known hard-iron.
771 * @throws IllegalArgumentException if provided hard-iron matrix is not
772 * 3x1.
773 */
774 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
775 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
776 final boolean commonAxisUsed, final Matrix hardIron) {
777 this(convertPosition(position), measurements, commonAxisUsed, hardIron);
778 }
779
780 /**
781 * Constructor.
782 *
783 * @param position position where body magnetic flux density measurements
784 * have been taken.
785 * @param measurements collection of body magnetic flux density
786 * measurements with standard deviation of
787 * magnetometer measurements taken at the same
788 * position with zero velocity and unknown different
789 * orientations.
790 * @param commonAxisUsed indicates whether z-axis is assumed to be common
791 * for the accelerometer, gyroscope and magnetometer.
792 * @param hardIron known hard-iron.
793 * @param listener listener to handle events raised by this calibrator.
794 * @throws IllegalArgumentException if provided hard-iron matrix is not
795 * 3x1.
796 */
797 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
798 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
799 final boolean commonAxisUsed, final Matrix hardIron,
800 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
801 this(convertPosition(position), measurements, commonAxisUsed, hardIron, listener);
802 }
803
804 /**
805 * Constructor.
806 *
807 * @param position position where body magnetic flux density measurements
808 * have been taken.
809 * @param measurements collection of body magnetic flux density
810 * measurements with standard deviation of
811 * magnetometer measurements taken at the same
812 * position with zero velocity and unknown different
813 * orientations.
814 * @param hardIron known hard-iron.
815 * @param initialMm initial soft-iron matrix containing scale factors
816 * and cross coupling errors.
817 * @throws IllegalArgumentException if provided hard-iron matrix is not
818 * 3x1 or if soft-iron matrix is not
819 * 3x3.
820 */
821 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
822 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
823 final Matrix hardIron, final Matrix initialMm) {
824 this(convertPosition(position), measurements, hardIron, initialMm);
825 }
826
827 /**
828 * Constructor.
829 *
830 * @param position position where body magnetic flux density measurements
831 * have been taken.
832 * @param measurements collection of body magnetic flux density
833 * measurements with standard deviation of
834 * magnetometer measurements taken at the same
835 * position with zero velocity and unknown different
836 * orientations.
837 * @param hardIron known hard-iron.
838 * @param initialMm initial soft-iron matrix containing scale factors
839 * and cross coupling errors.
840 * @param listener listener to handle events raised by this calibrator.
841 * @throws IllegalArgumentException if provided hard-iron matrix is not
842 * 3x1 or if soft-iron matrix is not
843 * 3x3.
844 */
845 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
846 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
847 final Matrix hardIron, final Matrix initialMm,
848 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
849 this(convertPosition(position), measurements, hardIron, initialMm, listener);
850 }
851
852 /**
853 * Constructor.
854 *
855 * @param position position where body magnetic flux density measurements
856 * have been taken.
857 * @param measurements collection of body magnetic flux density
858 * measurements with standard deviation of
859 * magnetometer measurements taken at the same
860 * position with zero velocity and unknown different
861 * orientations.
862 * @param commonAxisUsed indicates whether z-axis is assumed to be common
863 * for the accelerometer, gyroscope and magnetometer.
864 * @param hardIron known hard-iron.
865 * @param initialMm initial soft-iron matrix containing scale factors
866 * and cross coupling errors.
867 * @throws IllegalArgumentException if provided hard-iron matrix is not
868 * 3x1 or if soft-iron matrix is not
869 * 3x3.
870 */
871 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
872 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
873 final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm) {
874 this(convertPosition(position), measurements, commonAxisUsed, hardIron, initialMm);
875 }
876
877 /**
878 * Constructor.
879 *
880 * @param position position where body magnetic flux density measurements
881 * have been taken.
882 * @param measurements collection of body magnetic flux density
883 * measurements with standard deviation of
884 * magnetometer measurements taken at the same
885 * position with zero velocity and unknown different
886 * orientations.
887 * @param commonAxisUsed indicates whether z-axis is assumed to be common
888 * for the accelerometer, gyroscope and magnetometer.
889 * @param hardIron known hard-iron.
890 * @param initialMm initial soft-iron matrix containing scale factors
891 * and cross coupling errors.
892 * @param listener listener to handle events raised by this calibrator.
893 * @throws IllegalArgumentException if provided hard-iron matrix is not
894 * 3x1 or if soft-iron matrix is not
895 * 3x3.
896 */
897 public KnownHardIronPositionAndInstantMagnetometerCalibrator(
898 final ECEFPosition position, final List<StandardDeviationBodyMagneticFluxDensity> measurements,
899 final boolean commonAxisUsed, final Matrix hardIron, final Matrix initialMm,
900 final KnownHardIronPositionAndInstantMagnetometerCalibratorListener listener) {
901 this(convertPosition(position), measurements, commonAxisUsed, hardIron, initialMm, listener);
902 }
903
904 /**
905 * Gets position where body magnetic flux density measurements have been
906 * taken.
907 *
908 * @return position where body magnetic flux density measurements have
909 * been taken.
910 */
911 public NEDPosition getNedPosition() {
912 return position;
913 }
914
915 /**
916 * Sets position where body magnetic flux density measurements have been
917 * taken.
918 *
919 * @param position position where body magnetic flux density measurements
920 * have been taken.
921 * @throws LockedException if calibrator is currently running.
922 */
923 public void setPosition(final NEDPosition position) throws LockedException {
924 if (isRunning()) {
925 throw new LockedException();
926 }
927
928 this.position = position;
929 }
930
931 /**
932 * Gets position where body magnetic flux density measurements have been
933 * taken expressed in ECEF coordinates.
934 *
935 * @return position where body magnetic flux density measurements have
936 * been taken or null if not available.
937 */
938 public ECEFPosition getEcefPosition() {
939 if (position != null) {
940 final var result = new ECEFPosition();
941 getEcefPosition(result);
942 return result;
943 } else {
944 return null;
945 }
946 }
947
948 /**
949 * Gets position where body magnetic flux density measurements have been
950 * taken expressed in ECEF coordinates.
951 *
952 * @param result instance where result will be stored.
953 * @return true if ECEF position could be computed, false otherwise.
954 */
955 public boolean getEcefPosition(final ECEFPosition result) {
956 if (position != null) {
957 final var velocity = new ECEFVelocity();
958 NEDtoECEFPositionVelocityConverter.convertNEDtoECEF(
959 position.getLatitude(), position.getLongitude(), position.getHeight(),
960 0.0, 0.0, 0.0, result, velocity);
961 return true;
962 } else {
963 return false;
964 }
965 }
966
967 /**
968 * Sets position where body magnetic flux density measurements have been
969 * taken expressed in ECEF coordinates.
970 *
971 * @param position position where body magnetic flux density have been
972 * taken.
973 * @throws LockedException if calibrator is currently running.
974 */
975 public void setPosition(final ECEFPosition position) throws LockedException {
976 if (isRunning()) {
977 throw new LockedException();
978 }
979
980 this.position = convertPosition(position);
981 }
982
983 /**
984 * Gets timestamp expressed as decimal year where magnetic flux density
985 * measurements have been measured.
986 *
987 * @return timestamp expressed as decimal year or null if not defined.
988 */
989 public Double getYear() {
990 return year;
991 }
992
993 /**
994 * Sets timestamp expressed as decimal year where magnetic flux density
995 * measurements have been measured.
996 *
997 * @param year timestamp expressed as decimal year.
998 * @throws LockedException if calibrator is currently running.
999 */
1000 public void setYear(final Double year) throws LockedException {
1001 if (isRunning()) {
1002 throw new LockedException();
1003 }
1004 this.year = year;
1005 }
1006
1007 /**
1008 * Sets timestamp when magnetic flux density measurements have been
1009 * measured.
1010 *
1011 * @param timestampMillis a timestamp expressed in milliseconds since
1012 * epoch time (January 1st, 1970 at midnight).
1013 * @throws LockedException if calibrator is currently running.
1014 */
1015 public void setTime(final Long timestampMillis) throws LockedException {
1016 if (isRunning()) {
1017 throw new LockedException();
1018 }
1019 year = convertTime(timestampMillis);
1020 }
1021
1022 /**
1023 * Sets timestamp when magnetic flux density measurements have been
1024 * measured.
1025 *
1026 * @param date a date instance containing a timestamp.
1027 * @throws LockedException if calibrator is currently running.
1028 */
1029 public void setTime(final Date date) throws LockedException {
1030 if (isRunning()) {
1031 throw new LockedException();
1032 }
1033 year = convertTime(date);
1034 }
1035
1036 /**
1037 * Sets timestamp when magnetic flux density measurements have been
1038 * measured.
1039 *
1040 * @param calendar a calendar instance containing a timestamp.
1041 * @throws LockedException if calibrator is currently running.
1042 */
1043 public void setTime(final GregorianCalendar calendar) throws LockedException {
1044 if (isRunning()) {
1045 throw new LockedException();
1046 }
1047 year = convertTime(calendar);
1048 }
1049
1050 /**
1051 * Indicates whether calibrator is ready to start.
1052 *
1053 * @return true if calibrator is ready, false otherwise.
1054 */
1055 @Override
1056 public boolean isReady() {
1057 return super.isReady() && position != null && year != null;
1058 }
1059
1060 /**
1061 * Gets Earth's magnetic model.
1062 *
1063 * @return Earth's magnetic model or null if not provided.
1064 */
1065 public WorldMagneticModel getMagneticModel() {
1066 return magneticModel;
1067 }
1068
1069 /**
1070 * Sets Earth's magnetic model.
1071 *
1072 * @param magneticModel Earth's magnetic model to be set.
1073 * @throws LockedException if calibrator is currently running.
1074 */
1075 public void setMagneticModel(final WorldMagneticModel magneticModel) throws LockedException {
1076 if (isRunning()) {
1077 throw new LockedException();
1078 }
1079 this.magneticModel = magneticModel;
1080 }
1081
1082 /**
1083 * Called before calibration occurs.
1084 * This can be overridden by subclasses.
1085 *
1086 * @throws CalibrationException if anything fails.
1087 */
1088 @Override
1089 protected void onBeforeCalibrate() throws CalibrationException {
1090 computeGroundTruthMagneticFluxDensityNorm();
1091 }
1092
1093 /**
1094 * Computes expected ground truth magnetic flux density norm based on World Magnetic Model.
1095 * Notice that actual magnetic field measured by devices might differ from this value, since it might be attenuated
1096 * or other devices might interfere.
1097 *
1098 * @throws CalibrationException if world magnetic model cannot be loaded.
1099 */
1100 private void computeGroundTruthMagneticFluxDensityNorm() throws CalibrationException {
1101 final WMMEarthMagneticFluxDensityEstimator wmmEstimator;
1102 if (magneticModel != null) {
1103 wmmEstimator = new WMMEarthMagneticFluxDensityEstimator(magneticModel);
1104 } else {
1105 try {
1106 wmmEstimator = new WMMEarthMagneticFluxDensityEstimator();
1107 } catch (final IOException e) {
1108 throw new CalibrationException(e);
1109 }
1110 }
1111
1112 final var pos = getNedPosition();
1113 final var earthB = wmmEstimator.estimate(pos, year);
1114 groundTruthMagneticFluxDensityNorm = earthB.getNorm();
1115 }
1116
1117 /**
1118 * Converts a time instance expressed in milliseconds since epoch time
1119 * (January 1st, 1970 at midnight) to a decimal year.
1120 *
1121 * @param timestampMillis milliseconds value to be converted.
1122 * @return converted value expressed in decimal years.
1123 */
1124 private static Double convertTime(final Long timestampMillis) {
1125 if (timestampMillis == null) {
1126 return null;
1127 }
1128
1129 final var calendar = new GregorianCalendar();
1130 calendar.setTimeInMillis(timestampMillis);
1131 return convertTime(calendar);
1132 }
1133
1134 /**
1135 * Converts a time instant contained ina date object to a
1136 * decimal year.
1137 *
1138 * @param date a time instance to be converted.
1139 * @return converted value expressed in decimal years.
1140 */
1141 private static Double convertTime(final Date date) {
1142 if (date == null) {
1143 return null;
1144 }
1145
1146 final var calendar = new GregorianCalendar();
1147 calendar.setTime(date);
1148 return convertTime(calendar);
1149 }
1150
1151 /**
1152 * Converts a time instant contained in a gregorian calendar to a
1153 * decimal year.
1154 *
1155 * @param calendar calendar containing a specific instant to be
1156 * converted.
1157 * @return converted value expressed in decimal years.
1158 */
1159 private static Double convertTime(final GregorianCalendar calendar) {
1160 if (calendar == null) {
1161 return null;
1162 }
1163
1164 return WMMEarthMagneticFluxDensityEstimator.convertTime(calendar);
1165 }
1166
1167 /**
1168 * Converts provided ECEF position to position expressed in NED
1169 * coordinates.
1170 *
1171 * @param position ECEF position to be converted.
1172 * @return converted position expressed in NED coordinates.
1173 */
1174 private static NEDPosition convertPosition(final ECEFPosition position) {
1175 final var velocity = new NEDVelocity();
1176 final var result = new NEDPosition();
1177 ECEFtoNEDPositionVelocityConverter.convertECEFtoNED(
1178 position.getX(), position.getY(), position.getZ(),
1179 0.0, 0.0, 0.0, result, velocity);
1180 return result;
1181 }
1182 }