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