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