View Javadoc
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 }