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.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.Utils;
21  import com.irurueta.algebra.WrongSizeException;
22  import com.irurueta.navigation.LockedException;
23  import com.irurueta.navigation.NotReadyException;
24  import com.irurueta.navigation.frames.CoordinateTransformation;
25  import com.irurueta.navigation.frames.ECEFFrame;
26  import com.irurueta.navigation.frames.FrameType;
27  import com.irurueta.navigation.frames.NEDFrame;
28  import com.irurueta.navigation.frames.converters.ECEFtoNEDFrameConverter;
29  import com.irurueta.navigation.inertial.BodyMagneticFluxDensity;
30  import com.irurueta.navigation.inertial.calibration.CalibrationException;
31  import com.irurueta.navigation.inertial.calibration.FrameBodyMagneticFluxDensity;
32  import com.irurueta.navigation.inertial.calibration.MagneticFluxDensityTriad;
33  import com.irurueta.navigation.inertial.estimators.BodyMagneticFluxDensityEstimator;
34  import com.irurueta.navigation.inertial.wmm.NEDMagneticFluxDensity;
35  import com.irurueta.navigation.inertial.wmm.WMMEarthMagneticFluxDensityEstimator;
36  import com.irurueta.navigation.inertial.wmm.WorldMagneticModel;
37  import com.irurueta.units.MagneticFluxDensity;
38  import com.irurueta.units.MagneticFluxDensityConverter;
39  import com.irurueta.units.MagneticFluxDensityUnit;
40  
41  import java.io.IOException;
42  import java.util.Collection;
43  
44  /**
45   * Estimates magnetometer soft-iron cross couplings and scaling factors.
46   * <p>
47   * This calibrator uses a linear approach to find a minimum least squared error
48   * solution.
49   * <p>
50   * To use this calibrator at least 3 measurements at different known frames
51   * must be provided. In other words, magnetometer samples must be obtained at
52   * 3 different positions or orientations.
53   * Notice that frame velocities are ignored by this calibrator.
54   * <p>
55   * Measured magnetic flux density is assumed to follow the model shown below:
56   * <pre>
57   *     mBmeas = bm + (I + Mm) * mBtrue + w
58   * </pre>
59   * Where:
60   * - mBmeas is the measured magnetic flux density. This is a 3x1 vector.
61   * - bm is magnetometer hard-iron bias. Ideally, on a perfect magnetometer,
62   * this should be a 3x1 zero vector.
63   * - I is the 3x3 identity matrix.
64   * - Mm is the 3x3 soft-iron matrix containing cross-couplings and scaling
65   * factors. Ideally, on a perfect magnetometer, this should be a 3x3 zero
66   * matrix.
67   * - mBtrue is ground-truth magnetic flux density. This is a 3x1 vector.
68   * - w is measurement noise. This is a 3x1 vector.
69   */
70  @SuppressWarnings("DuplicatedCode")
71  public class KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator implements
72          KnownHardIronAndFrameMagnetometerCalibrator<FrameBodyMagneticFluxDensity,
73                  KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener>,
74          UnorderedFrameBodyMagneticFluxDensityMagnetometerCalibrator {
75  
76      /**
77       * Indicates whether by default a common z-axis is assumed for the accelerometer,
78       * gyroscope and magnetometer.
79       */
80      public static final boolean DEFAULT_USE_COMMON_Z_AXIS = false;
81  
82      /**
83       * Required minimum number of measurements.
84       */
85      public static final int MINIMUM_MEASUREMENTS = 3;
86  
87      /**
88       * Number of equations generated for each measurement.
89       */
90      private static final int EQUATIONS_PER_MEASUREMENT = 3;
91  
92      /**
93       * Number of unknowns when common z-axis is assumed for the accelerometer,
94       * gyroscope and magnetometer.
95       */
96      private static final int COMMON_Z_AXIS_UNKNOWNS = 6;
97  
98      /**
99       * Number of unknowns for the general case.
100      */
101     private static final int GENERAL_UNKNOWNS = 9;
102 
103     /**
104      * X-coordinate of known hard-iron bias.
105      * This is expressed in Teslas (T).
106      */
107     private double hardIronX;
108 
109     /**
110      * Y-coordinate of known hard-iron bias.
111      * This is expressed in Teslas (T).
112      */
113     private double hardIronY;
114 
115     /**
116      * Z-coordinate of known hard-iron bias.
117      * This is expressed in Teslas (T).
118      */
119     private double hardIronZ;
120 
121     /**
122      * Contains a collection of body magnetic flux density measurements taken
123      * at different frames (positions and orientations).
124      * If a single device magnetometer needs to be calibrated, typically all
125      * measurements are taken at the same position, with zero velocity and
126      * multiple orientations.
127      * However, if we just want to calibrate a given magnetometer model (e.g.
128      * obtain an average and less precise calibration for the magnetometer of
129      * a given phone model), we could take measurements collected throughout
130      * the planet at multiple positions while the phone remains static (e.g.
131      * while charging), hence each measurement position will change, velocity
132      * will remain zero and orientation will be typically constant at
133      * horizontal orientation while the phone remains on a
134      * flat surface.
135      */
136     private Collection<FrameBodyMagneticFluxDensity> measurements;
137 
138     /**
139      * This flag indicates whether z-axis is assumed to be common for accelerometer,
140      * gyroscope and magnetometer.
141      * When enabled, this eliminates 3 variables from Mm matrix.
142      */
143     private boolean commonAxisUsed = DEFAULT_USE_COMMON_Z_AXIS;
144 
145     /**
146      * Listener to handle events raised by this calibrator.
147      */
148     private KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener;
149 
150     /**
151      * Estimated magnetometer soft-iron matrix containing scale factors
152      * and cross coupling errors.
153      * This is the product of matrix Tm containing cross coupling errors and Km
154      * containing scaling factors.
155      * So tat:
156      * <pre>
157      *     Mm = [sx    mxy  mxz] = Tm*Km
158      *          [myx   sy   myz]
159      *          [mzx   mzy  sz ]
160      * </pre>
161      * Where:
162      * <pre>
163      *     Km = [sx 0   0 ]
164      *          [0  sy  0 ]
165      *          [0  0   sz]
166      * </pre>
167      * and
168      * <pre>
169      *     Tm = [1          -alphaXy    alphaXz ]
170      *          [alphaYx    1           -alphaYz]
171      *          [-alphaZx   alphaZy     1       ]
172      * </pre>
173      * Hence:
174      * <pre>
175      *     Mm = [sx    mxy  mxz] = Tm*Km =  [sx             -sy * alphaXy   sz * alphaXz ]
176      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
177      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
178      * </pre>
179      * This instance allows any 3x3 matrix however, typically alphaYx, alphaZx and alphaZy
180      * are considered to be zero if the accelerometer z-axis is assumed to be the same
181      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
182      * becomes upper diagonal:
183      * <pre>
184      *     Mm = [sx    mxy  mxz]
185      *          [0     sy   myz]
186      *          [0     0    sz ]
187      * </pre>
188      * Values of this matrix are unit-less.
189      */
190     private Matrix estimatedMm;
191 
192     /**
193      * Indicates whether calibrator is running.
194      */
195     private boolean running;
196 
197     /**
198      * Contains Earth's magnetic model.
199      */
200     private WorldMagneticModel magneticModel;
201 
202     /**
203      * Constructor.
204      */
205     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator() {
206     }
207 
208     /**
209      * Constructor.
210      *
211      * @param listener listener to handle events raised by this calibrator.
212      */
213     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
214             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
215         this.listener = listener;
216     }
217 
218     /**
219      * Constructor.
220      *
221      * @param measurements collection of body magnetic flux density measurements
222      *                     taken at different frames (positions and orientations).
223      */
224     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
225             final Collection<? extends FrameBodyMagneticFluxDensity> measurements) {
226         //noinspection unchecked
227         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
228     }
229 
230     /**
231      * Constructor.
232      *
233      * @param measurements collection of body magnetic flux density measurements
234      *                     taken at different frames (positions and orientations).
235      * @param listener     listener to handle events raised by this calibrator.
236      */
237     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
238             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
239             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
240         this(measurements);
241         this.listener = listener;
242     }
243 
244     /**
245      * Constructor.
246      *
247      * @param commonAxisUsed indicates whether z-axis is assumed to be common
248      *                       for the accelerometer, gyroscope and magnetometer.
249      */
250     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(final boolean commonAxisUsed) {
251         this.commonAxisUsed = commonAxisUsed;
252     }
253 
254     /**
255      * Constructor.
256      *
257      * @param commonAxisUsed indicates whether z-axis is assumed to be common
258      *                       for the accelerometer, gyroscope and magnetometer.
259      * @param listener       listener to handle events raised by this calibrator.
260      */
261     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
262             final boolean commonAxisUsed,
263             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
264         this(commonAxisUsed);
265         this.listener = listener;
266     }
267 
268     /**
269      * Constructor.
270      *
271      * @param measurements   collection of body magnetic flux density measurements
272      *                       taken at different frames (positions and orientations).
273      * @param commonAxisUsed indicates whether z-axis is assumed to be common
274      *                       for the accelerometer, gyroscope and magnetometer.
275      */
276     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
277             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed) {
278         this(measurements);
279         this.commonAxisUsed = commonAxisUsed;
280     }
281 
282     /**
283      * Constructor.
284      *
285      * @param measurements   collection of body magnetic flux density measurements
286      *                       taken at different frames (positions and orientations).
287      * @param commonAxisUsed indicates whether z-axis is assumed to be common
288      *                       for the accelerometer, gyroscope and magnetometer.
289      * @param listener       listener to handle events raised by this calibrator.
290      */
291     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
292             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
293             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
294         this(measurements, commonAxisUsed);
295         this.listener = listener;
296     }
297 
298     /**
299      * Constructor.
300      *
301      * @param magneticModel Earth's magnetic model. If null, a default model
302      *                      will be used instead.
303      */
304     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(final WorldMagneticModel magneticModel) {
305         this.magneticModel = magneticModel;
306     }
307 
308     /**
309      * Constructor.
310      *
311      * @param magneticModel Earth's magnetic model. If null, a default model
312      *                      will be used instead.
313      * @param listener      listener to handle events raised by this calibrator.
314      */
315     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
316             final WorldMagneticModel magneticModel,
317             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
318         this(listener);
319         this.magneticModel = magneticModel;
320     }
321 
322     /**
323      * Constructor.
324      *
325      * @param measurements  collection of body magnetic flux density measurements
326      *                      taken at different frames (positions and orientations).
327      * @param magneticModel Earth's magnetic model. If null, a default model
328      *                      will be used instead.
329      */
330     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
331             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
332             final WorldMagneticModel magneticModel) {
333         this(measurements);
334         this.magneticModel = magneticModel;
335     }
336 
337     /**
338      * Constructor.
339      *
340      * @param measurements  collection of body magnetic flux density measurements
341      *                      taken at different frames (positions and orientations).
342      * @param magneticModel Earth's magnetic model. If null, a default model
343      *                      will be used instead.
344      * @param listener      listener to handle events raised by this calibrator.
345      */
346     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
347             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
348             final WorldMagneticModel magneticModel,
349             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
350         this(measurements, listener);
351         this.magneticModel = magneticModel;
352     }
353 
354     /**
355      * Constructor.
356      *
357      * @param commonAxisUsed indicates whether z-axis is assumed to be common
358      *                       for the accelerometer, gyroscope and magnetometer.
359      * @param magneticModel  Earth's magnetic model. If null, a default model
360      *                       will be used instead.
361      */
362     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
363             final boolean commonAxisUsed, final WorldMagneticModel magneticModel) {
364         this(commonAxisUsed);
365         this.magneticModel = magneticModel;
366     }
367 
368     /**
369      * Constructor.
370      *
371      * @param commonAxisUsed indicates whether z-axis is assumed to be common
372      *                       for the accelerometer, gyroscope and magnetometer.
373      * @param magneticModel  Earth's magnetic model. If null, a default model
374      *                       will be used instead.
375      * @param listener       listener to handle events raised by this calibrator.
376      */
377     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
378             final boolean commonAxisUsed, final WorldMagneticModel magneticModel,
379             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
380         this(commonAxisUsed, listener);
381         this.magneticModel = magneticModel;
382     }
383 
384     /**
385      * Constructor.
386      *
387      * @param measurements   collection of body magnetic flux density measurements
388      *                       taken at different frames (positions and orientations).
389      * @param commonAxisUsed indicates whether z-axis is assumed to be common
390      *                       for the accelerometer, gyroscope and magnetometer.
391      * @param magneticModel  Earth's magnetic model. If null, a default model
392      *                       will be used instead.
393      */
394     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
395             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
396             final WorldMagneticModel magneticModel) {
397         this(measurements, commonAxisUsed);
398         this.magneticModel = magneticModel;
399     }
400 
401     /**
402      * Constructor.
403      *
404      * @param measurements   collection of body magnetic flux density measurements
405      *                       taken at different frames (positions and orientations).
406      * @param commonAxisUsed indicates whether z-axis is assumed to be common
407      *                       for the accelerometer, gyroscope and magnetometer.
408      * @param magneticModel  Earth's magnetic model. If null, a default model
409      *                       will be used instead.
410      * @param listener       listener to handle events raised by this calibrator.
411      */
412     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
413             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
414             final WorldMagneticModel magneticModel,
415             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
416         this(measurements, commonAxisUsed, listener);
417         this.magneticModel = magneticModel;
418     }
419 
420     /**
421      * Constructor.
422      *
423      * @param hardIronX x-coordinate of magnetometer hard-iron bias
424      *                  expressed in Teslas (T).
425      * @param hardIronY y-coordinate of magnetometer hard-iron bias
426      *                  expressed in Teslas (T).
427      * @param hardIronZ z-coordinate of magnetometer hard-iron bias
428      *                  expressed in Teslas (T).
429      */
430     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
431             final double hardIronX, final double hardIronY, final double hardIronZ) {
432         try {
433             setHardIronCoordinates(hardIronX, hardIronY, hardIronZ);
434         } catch (final LockedException ignore) {
435             // never happens
436         }
437     }
438 
439     /**
440      * Constructor.
441      *
442      * @param hardIronX x-coordinate of magnetometer hard-iron bias
443      *                  expressed in Teslas (T).
444      * @param hardIronY y-coordinate of magnetometer hard-iron bias
445      *                  expressed in Teslas (T).
446      * @param hardIronZ z-coordinate of magnetometer hard-iron bias
447      *                  expressed in Teslas (T).
448      * @param listener  listener to handle events raised by this calibrator.
449      */
450     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
451             final double hardIronX, final double hardIronY, final double hardIronZ,
452             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
453         this(hardIronX, hardIronY, hardIronZ);
454         this.listener = listener;
455     }
456 
457     /**
458      * Constructor.
459      *
460      * @param measurements collection of body magnetic flux density measurements
461      *                     taken at different frames (positions and orientations).
462      * @param hardIronX    x-coordinate of magnetometer hard-iron bias
463      *                     expressed in Teslas (T).
464      * @param hardIronY    y-coordinate of magnetometer hard-iron bias
465      *                     expressed in Teslas (T).
466      * @param hardIronZ    z-coordinate of magnetometer hard-iron bias
467      *                     expressed in Teslas (T).
468      */
469     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
470             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
471             final double hardIronX, final double hardIronY, final double hardIronZ) {
472         this(hardIronX, hardIronY, hardIronZ);
473         //noinspection unchecked
474         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
475     }
476 
477     /**
478      * Constructor.
479      *
480      * @param measurements collection of body magnetic flux density measurements
481      *                     taken at different frames (positions and orientations).
482      * @param hardIronX    x-coordinate of magnetometer hard-iron bias
483      *                     expressed in Teslas (T).
484      * @param hardIronY    y-coordinate of magnetometer hard-iron bias
485      *                     expressed in Teslas (T).
486      * @param hardIronZ    z-coordinate of magnetometer hard-iron bias
487      *                     expressed in Teslas (T).
488      * @param listener     listener to handle events raised by this calibrator.
489      */
490     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
491             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
492             final double hardIronX, final double hardIronY, final double hardIronZ,
493             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
494         this(measurements, hardIronX, hardIronY, hardIronZ);
495         this.listener = listener;
496     }
497 
498     /**
499      * Constructor.
500      *
501      * @param commonAxisUsed indicates whether z-axis is assumed to be common
502      *                       for the accelerometer, gyroscope and magnetometer.
503      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
504      *                       expressed in Teslas (T).
505      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
506      *                       expressed in Teslas (T).
507      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
508      *                       expressed in Teslas (T).
509      */
510     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
511             final boolean commonAxisUsed, final double hardIronX, final double hardIronY, final double hardIronZ) {
512         this(hardIronX, hardIronY, hardIronZ);
513         this.commonAxisUsed = commonAxisUsed;
514     }
515 
516     /**
517      * Constructor.
518      *
519      * @param commonAxisUsed indicates whether z-axis is assumed to be common
520      *                       for the accelerometer, gyroscope and magnetometer.
521      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
522      *                       expressed in Teslas (T).
523      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
524      *                       expressed in Teslas (T).
525      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
526      *                       expressed in Teslas (T).
527      * @param listener       listener to handle events raised by this calibrator.
528      */
529     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
530             final boolean commonAxisUsed, final double hardIronX, final double hardIronY, final double hardIronZ,
531             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
532         this(commonAxisUsed, hardIronX, hardIronY, hardIronZ);
533         this.listener = listener;
534     }
535 
536     /**
537      * Constructor.
538      *
539      * @param measurements   collection of body magnetic flux density measurements
540      *                       taken at different frames (positions and orientations).
541      * @param commonAxisUsed indicates whether z-axis is assumed to be common
542      *                       for the accelerometer, gyroscope and magnetometer.
543      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
544      *                       expressed in Teslas (T).
545      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
546      *                       expressed in Teslas (T).
547      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
548      *                       expressed in Teslas (T).
549      */
550     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
551             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
552             final double hardIronX, final double hardIronY, final double hardIronZ) {
553         this(commonAxisUsed, hardIronX, hardIronY, hardIronZ);
554         //noinspection unchecked
555         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
556     }
557 
558     /**
559      * Constructor.
560      *
561      * @param measurements   collection of body magnetic flux density measurements
562      *                       taken at different frames (positions and orientations).
563      * @param commonAxisUsed indicates whether z-axis is assumed to be common
564      *                       for the accelerometer, gyroscope and magnetometer.
565      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
566      *                       expressed in Teslas (T).
567      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
568      *                       expressed in Teslas (T).
569      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
570      *                       expressed in Teslas (T).
571      * @param listener       listener to handle events raised by this calibrator.
572      */
573     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
574             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
575             final boolean commonAxisUsed, final double hardIronX, final double hardIronY, final double hardIronZ,
576             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
577         this(measurements, commonAxisUsed, hardIronX, hardIronY, hardIronZ);
578         this.listener = listener;
579     }
580 
581     /**
582      * Constructor.
583      *
584      * @param magneticModel Earth's magnetic model. If null, a default model
585      *                      will be used instead.
586      * @param hardIronX     x-coordinate of magnetometer hard-iron bias
587      *                      expressed in Teslas (T).
588      * @param hardIronY     y-coordinate of magnetometer hard-iron bias
589      *                      expressed in Teslas (T).
590      * @param hardIronZ     z-coordinate of magnetometer hard-iron bias
591      *                      expressed in Teslas (T).
592      */
593     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
594             final WorldMagneticModel magneticModel,
595             final double hardIronX, final double hardIronY, final double hardIronZ) {
596         this(hardIronX, hardIronY, hardIronZ);
597         this.magneticModel = magneticModel;
598     }
599 
600     /**
601      * Constructor.
602      *
603      * @param magneticModel Earth's magnetic model. If null, a default model
604      *                      will be used instead.
605      * @param hardIronX     x-coordinate of magnetometer hard-iron bias
606      *                      expressed in Teslas (T).
607      * @param hardIronY     y-coordinate of magnetometer hard-iron bias
608      *                      expressed in Teslas (T).
609      * @param hardIronZ     z-coordinate of magnetometer hard-iron bias
610      *                      expressed in Teslas (T).
611      * @param listener      listener to handle events raised by this calibrator.
612      */
613     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
614             final WorldMagneticModel magneticModel,
615             final double hardIronX, final double hardIronY, final double hardIronZ,
616             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
617         this(magneticModel, hardIronX, hardIronY, hardIronZ);
618         this.listener = listener;
619     }
620 
621     /**
622      * Constructor.
623      *
624      * @param measurements  collection of body magnetic flux density measurements
625      *                      taken at different frames (positions and orientations).
626      * @param magneticModel Earth's magnetic model. If null, a default model
627      *                      will be used instead.
628      * @param hardIronX     x-coordinate of magnetometer hard-iron bias
629      *                      expressed in Teslas (T).
630      * @param hardIronY     y-coordinate of magnetometer hard-iron bias
631      *                      expressed in Teslas (T).
632      * @param hardIronZ     z-coordinate of magnetometer hard-iron bias
633      *                      expressed in Teslas (T).
634      */
635     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
636             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
637             final WorldMagneticModel magneticModel,
638             final double hardIronX, final double hardIronY, final double hardIronZ) {
639         this(hardIronX, hardIronY, hardIronZ);
640         //noinspection unchecked
641         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
642         this.magneticModel = magneticModel;
643     }
644 
645     /**
646      * Constructor.
647      *
648      * @param measurements  collection of body magnetic flux density measurements
649      *                      taken at different frames (positions and orientations).
650      * @param magneticModel Earth's magnetic model. If null, a default model
651      *                      will be used instead.
652      * @param hardIronX     x-coordinate of magnetometer hard-iron bias
653      *                      expressed in Teslas (T).
654      * @param hardIronY     y-coordinate of magnetometer hard-iron bias
655      *                      expressed in Teslas (T).
656      * @param hardIronZ     z-coordinate of magnetometer hard-iron bias
657      *                      expressed in Teslas (T).
658      * @param listener      listener to handle events raised by this calibrator.
659      */
660     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
661             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
662             final WorldMagneticModel magneticModel,
663             final double hardIronX, final double hardIronY, final double hardIronZ,
664             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
665         this(measurements, magneticModel, hardIronX, hardIronY, hardIronZ);
666         this.listener = listener;
667     }
668 
669     /**
670      * Constructor
671      *
672      * @param commonAxisUsed indicates whether z-axis is assumed to be common
673      *                       for the accelerometer, gyroscope and magnetometer.
674      * @param magneticModel  Earth's magnetic model. If null, a default model
675      *                       will be used instead.
676      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
677      *                       expressed in Teslas (T).
678      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
679      *                       expressed in Teslas (T).
680      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
681      *                       expressed in Teslas (T).
682      */
683     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
684             final boolean commonAxisUsed, final WorldMagneticModel magneticModel,
685             final double hardIronX, final double hardIronY, final double hardIronZ) {
686         this(magneticModel, hardIronX, hardIronY, hardIronZ);
687         this.commonAxisUsed = commonAxisUsed;
688     }
689 
690     /**
691      * Constructor.
692      *
693      * @param commonAxisUsed indicates whether z-axis is assumed to be common
694      *                       for the accelerometer, gyroscope and magnetometer.
695      * @param magneticModel  Earth's magnetic model. If null, a default model
696      *                       will be used instead.
697      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
698      *                       expressed in Teslas (T).
699      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
700      *                       expressed in Teslas (T).
701      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
702      *                       expressed in Teslas (T).
703      * @param listener       listener to handle events raised by this calibrator.
704      */
705     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
706             final boolean commonAxisUsed, final WorldMagneticModel magneticModel,
707             final double hardIronX, final double hardIronY, final double hardIronZ,
708             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
709         this(commonAxisUsed, magneticModel, hardIronX, hardIronY, hardIronZ);
710         this.listener = listener;
711     }
712 
713     /**
714      * Constructor.
715      *
716      * @param measurements   collection of body magnetic flux density measurements
717      *                       taken at different frames (positions and orientations).
718      * @param commonAxisUsed indicates whether z-axis is assumed to be common
719      *                       for the accelerometer, gyroscope and magnetometer.
720      * @param magneticModel  Earth's magnetic model. If null, a default model
721      *                       will be used instead.
722      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
723      *                       expressed in Teslas (T).
724      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
725      *                       expressed in Teslas (T).
726      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
727      *                       expressed in Teslas (T).
728      */
729     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
730             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
731             final WorldMagneticModel magneticModel,
732             final double hardIronX, final double hardIronY, final double hardIronZ) {
733         this(commonAxisUsed, magneticModel, hardIronX, hardIronY, hardIronZ);
734         //noinspection unchecked
735         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
736     }
737 
738     /**
739      * Constructor.
740      *
741      * @param measurements   collection of body magnetic flux density measurements
742      *                       taken at different frames (positions and orientations).
743      * @param commonAxisUsed indicates whether z-axis is assumed to be common
744      *                       for the accelerometer, gyroscope and magnetometer.
745      * @param magneticModel  Earth's magnetic model. If null, a default model
746      *                       will be used instead.
747      * @param hardIronX      x-coordinate of magnetometer hard-iron bias
748      *                       expressed in Teslas (T).
749      * @param hardIronY      y-coordinate of magnetometer hard-iron bias
750      *                       expressed in Teslas (T).
751      * @param hardIronZ      z-coordinate of magnetometer hard-iron bias
752      *                       expressed in Teslas (T).
753      * @param listener       listener to handle events raised by this calibrator.
754      */
755     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
756             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
757             final WorldMagneticModel magneticModel,
758             final double hardIronX, final double hardIronY, final double hardIronZ,
759             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
760         this(measurements, commonAxisUsed, magneticModel, hardIronX, hardIronY, hardIronZ);
761         this.listener = listener;
762     }
763 
764     /**
765      * Constructor.
766      *
767      * @param hardIron known hard-iron bias.
768      * @throws IllegalArgumentException if provided hard-iron array does not
769      *                                  have length 3.
770      */
771     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(final double[] hardIron) {
772         try {
773             setHardIron(hardIron);
774         } catch (final LockedException ignore) {
775             // never happens
776         }
777     }
778 
779     /**
780      * Constructor.
781      *
782      * @param hardIron known hard-iron bias.
783      * @param listener listener to handle events raised by this calibrator.
784      * @throws IllegalArgumentException if provided hard-iron array does not
785      *                                  have length 3.
786      */
787     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
788             final double[] hardIron,
789             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
790         this(hardIron);
791         this.listener = listener;
792     }
793 
794     /**
795      * Constructor.
796      *
797      * @param measurements collection of body magnetic flux density measurements
798      *                     taken at different frames (positions and orientations).
799      * @param hardIron     known hard-iron bias.
800      * @throws IllegalArgumentException if provided hard-iron array does not
801      *                                  have length 3.
802      */
803     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
804             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final double[] hardIron) {
805         this(hardIron);
806         //noinspection unchecked
807         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
808     }
809 
810     /**
811      * Constructor.
812      *
813      * @param measurements collection of body magnetic flux density measurements
814      *                     taken at different frames (positions and orientations).
815      * @param hardIron     known hard-iron bias.
816      * @param listener     listener to handle events raised by this calibrator.
817      * @throws IllegalArgumentException if provided hard-iron array does not
818      *                                  have length 3.
819      */
820     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
821             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final double[] hardIron,
822             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
823         this(measurements, hardIron);
824         this.listener = listener;
825     }
826 
827     /**
828      * Constructor.
829      *
830      * @param commonAxisUsed indicates whether z-axis is assumed to be common
831      *                       for the accelerometer, gyroscope and magnetometer.
832      * @param hardIron       known hard-iron bias.
833      * @throws IllegalArgumentException if provided hard-iron array does not
834      *                                  have length 3.
835      */
836     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
837             final boolean commonAxisUsed, final double[] hardIron) {
838         this(hardIron);
839         this.commonAxisUsed = commonAxisUsed;
840     }
841 
842     /**
843      * Constructor.
844      *
845      * @param commonAxisUsed indicates whether z-axis is assumed to be common
846      *                       for the accelerometer, gyroscope and magnetometer.
847      * @param hardIron       known hard-iron bias.
848      * @param listener       listener to handle events raised by this calibrator.
849      * @throws IllegalArgumentException if provided hard-iron array does not
850      *                                  have length 3.
851      */
852     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
853             final boolean commonAxisUsed, final double[] hardIron,
854             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
855         this(commonAxisUsed, hardIron);
856         this.listener = listener;
857     }
858 
859     /**
860      * Constructor.
861      *
862      * @param measurements   collection of body magnetic flux density measurements
863      *                       taken at different frames (positions and orientations).
864      * @param commonAxisUsed indicates whether z-axis is assumed to be common
865      *                       for the accelerometer, gyroscope and magnetometer.
866      * @param hardIron       known hard-iron bias.
867      * @throws IllegalArgumentException if provided hard-iron array does not
868      *                                  have length 3.
869      */
870     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
871             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
872             final double[] hardIron) {
873         this(commonAxisUsed, hardIron);
874         //noinspection unchecked
875         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
876     }
877 
878     /**
879      * Constructor.
880      *
881      * @param measurements   collection of body magnetic flux density measurements
882      *                       taken at different frames (positions and orientations).
883      * @param commonAxisUsed indicates whether z-axis is assumed to be common
884      *                       for the accelerometer, gyroscope and magnetometer.
885      * @param hardIron       known hard-iron bias.
886      * @param listener       listener to handle events raised by this calibrator.
887      * @throws IllegalArgumentException if provided hard-iron array does not
888      *                                  have length 3.
889      */
890     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
891             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
892             final boolean commonAxisUsed, final double[] hardIron,
893             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
894         this(measurements, commonAxisUsed, hardIron);
895         this.listener = listener;
896     }
897 
898     /**
899      * Constructor.
900      *
901      * @param magneticModel Earth's magnetic model. If null, a default model
902      *                      will be used instead.
903      * @param hardIron      known hard-iron bias.
904      * @throws IllegalArgumentException if provided hard-iron array does not
905      *                                  have length 3.
906      */
907     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
908             final WorldMagneticModel magneticModel, final double[] hardIron) {
909         this(hardIron);
910         this.magneticModel = magneticModel;
911     }
912 
913     /**
914      * Constructor.
915      *
916      * @param magneticModel Earth's magnetic model. If null, a default model
917      *                      will be used instead.
918      * @param hardIron      known hard-iron bias.
919      * @param listener      listener to handle events raised by this calibrator.
920      * @throws IllegalArgumentException if provided hard-iron array does not
921      *                                  have length 3.
922      */
923     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
924             final WorldMagneticModel magneticModel, final double[] hardIron,
925             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
926         this(magneticModel, hardIron);
927         this.listener = listener;
928     }
929 
930     /**
931      * Constructor.
932      *
933      * @param measurements  collection of body magnetic flux density measurements
934      *                      taken at different frames (positions and orientations).
935      * @param magneticModel Earth's magnetic model. If null, a default model
936      *                      will be used instead.
937      * @param hardIron      known hard-iron bias.
938      * @throws IllegalArgumentException if provided hard-iron array does not
939      *                                  have length 3.
940      */
941     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
942             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
943             final WorldMagneticModel magneticModel, final double[] hardIron) {
944         this(magneticModel, hardIron);
945         //noinspection unchecked
946         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
947     }
948 
949     /**
950      * Constructor.
951      *
952      * @param measurements  collection of body magnetic flux density measurements
953      *                      taken at different frames (positions and orientations).
954      * @param magneticModel Earth's magnetic model. If null, a default model
955      *                      will be used instead.
956      * @param hardIron      known hard-iron bias.
957      * @param listener      listener to handle events raised by this calibrator.
958      * @throws IllegalArgumentException if provided hard-iron array does not
959      *                                  have length 3.
960      */
961     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
962             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
963             final WorldMagneticModel magneticModel, final double[] hardIron,
964             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
965         this(measurements, magneticModel, hardIron);
966         this.listener = listener;
967     }
968 
969     /**
970      * Constructor.
971      *
972      * @param commonAxisUsed indicates whether z-axis is assumed to be common
973      *                       for the accelerometer, gyroscope and magnetometer.
974      * @param magneticModel  Earth's magnetic model. If null, a default model
975      *                       will be used instead.
976      * @param hardIron       known hard-iron bias.
977      * @throws IllegalArgumentException if provided hard-iron array does not
978      *                                  have length 3.
979      */
980     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
981             final boolean commonAxisUsed, final WorldMagneticModel magneticModel, final double[] hardIron) {
982         this(magneticModel, hardIron);
983         this.commonAxisUsed = commonAxisUsed;
984     }
985 
986     /**
987      * Constructor.
988      *
989      * @param commonAxisUsed indicates whether z-axis is assumed to be common
990      *                       for the accelerometer, gyroscope and magnetometer.
991      * @param magneticModel  Earth's magnetic model. If null, a default model
992      *                       will be used instead.
993      * @param hardIron       known hard-iron bias.
994      * @param listener       listener to handle events raised by this calibrator.
995      * @throws IllegalArgumentException if provided hard-iron array does not
996      *                                  have length 3.
997      */
998     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
999             final boolean commonAxisUsed, final WorldMagneticModel magneticModel, final double[] hardIron,
1000             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1001         this(commonAxisUsed, magneticModel, hardIron);
1002         this.listener = listener;
1003     }
1004 
1005     /**
1006      * Constructor.
1007      *
1008      * @param measurements   collection of body magnetic flux density measurements
1009      *                       taken at different frames (positions and orientations).
1010      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1011      *                       for the accelerometer, gyroscope and magnetometer.
1012      * @param magneticModel  Earth's magnetic model. If null, a default model
1013      *                       will be used instead.
1014      * @param hardIron       known hard-iron bias.
1015      * @throws IllegalArgumentException if provided hard-iron array does not
1016      *                                  have length 3.
1017      */
1018     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1019             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1020             final WorldMagneticModel magneticModel, final double[] hardIron) {
1021         this(commonAxisUsed, magneticModel, hardIron);
1022         //noinspection unchecked
1023         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
1024     }
1025 
1026     /**
1027      * Constructor.
1028      *
1029      * @param measurements   collection of body magnetic flux density measurements
1030      *                       taken at different frames (positions and orientations).
1031      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1032      *                       for the accelerometer, gyroscope and magnetometer.
1033      * @param magneticModel  Earth's magnetic model. If null, a default model
1034      *                       will be used instead.
1035      * @param hardIron       known hard-iron bias.
1036      * @param listener       listener to handle events raised by this calibrator.
1037      * @throws IllegalArgumentException if provided hard-iron array does not
1038      *                                  have length 3.
1039      */
1040     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1041             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1042             final WorldMagneticModel magneticModel, final double[] hardIron,
1043             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1044         this(measurements, commonAxisUsed, magneticModel, hardIron);
1045         this.listener = listener;
1046     }
1047 
1048     /**
1049      * Constructor.
1050      *
1051      * @param hardIron known hard-iron bias.
1052      * @throws IllegalArgumentException if provided hard-iron matrix is not
1053      *                                  3x1.
1054      */
1055     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(final Matrix hardIron) {
1056         try {
1057             setHardIron(hardIron);
1058         } catch (final LockedException ignore) {
1059             // never happens
1060         }
1061     }
1062 
1063     /**
1064      * Constructor.
1065      *
1066      * @param hardIron known hard-iron bias.
1067      * @param listener listener to handle events raised by this calibrator.
1068      * @throws IllegalArgumentException if provided hard-iron matrix is not
1069      *                                  3x1.
1070      */
1071     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1072             final Matrix hardIron,
1073             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1074         this(hardIron);
1075         this.listener = listener;
1076     }
1077 
1078     /**
1079      * Constructor.
1080      *
1081      * @param measurements collection of body magnetic flux density measurements
1082      *                     taken at different frames (positions and orientations).
1083      * @param hardIron     known hard-iron bias.
1084      * @throws IllegalArgumentException if provided hard-iron matrix is not
1085      *                                  3x1.
1086      */
1087     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1088             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final Matrix hardIron) {
1089         this(hardIron);
1090         //noinspection unchecked
1091         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
1092     }
1093 
1094     /**
1095      * Constructor.
1096      *
1097      * @param measurements collection of body magnetic flux density measurements
1098      *                     taken at different frames (positions and orientations).
1099      * @param hardIron     known hard-iron bias.
1100      * @param listener     listener to handle events raised by this calibrator.
1101      * @throws IllegalArgumentException if provided hard-iron matrix is not
1102      *                                  3x1.
1103      */
1104     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1105             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final Matrix hardIron,
1106             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1107         this(measurements, hardIron);
1108         this.listener = listener;
1109     }
1110 
1111     /**
1112      * Constructor.
1113      *
1114      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1115      *                       for the accelerometer, gyroscope and magnetometer.
1116      * @param hardIron       known hard-iron bias.
1117      * @throws IllegalArgumentException if provided hard-iron matrix is not
1118      *                                  3x1.
1119      */
1120     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1121             final boolean commonAxisUsed, final Matrix hardIron) {
1122         this(hardIron);
1123         this.commonAxisUsed = commonAxisUsed;
1124     }
1125 
1126     /**
1127      * Constructor.
1128      *
1129      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1130      *                       for the accelerometer, gyroscope and magnetometer.
1131      * @param hardIron       known hard-iron bias.
1132      * @param listener       listener to handle events raised by this calibrator.
1133      * @throws IllegalArgumentException if provided hard-iron matrix is not
1134      *                                  3x1.
1135      */
1136     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1137             final boolean commonAxisUsed, final Matrix hardIron,
1138             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1139         this(commonAxisUsed, hardIron);
1140         this.listener = listener;
1141     }
1142 
1143     /**
1144      * Constructor.
1145      *
1146      * @param measurements   collection of body magnetic flux density measurements
1147      *                       taken at different frames (positions and orientations).
1148      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1149      *                       for the accelerometer, gyroscope and magnetometer.
1150      * @param hardIron       known hard-iron bias.
1151      * @throws IllegalArgumentException if provided hard-iron matrix is not
1152      *                                  3x1.
1153      */
1154     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1155             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1156             final Matrix hardIron) {
1157         this(commonAxisUsed, hardIron);
1158         //noinspection unchecked
1159         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
1160     }
1161 
1162     /**
1163      * Constructor.
1164      *
1165      * @param measurements   collection of body magnetic flux density measurements
1166      *                       taken at different frames (positions and orientations).
1167      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1168      *                       for the accelerometer, gyroscope and magnetometer.
1169      * @param hardIron       known hard-iron bias.
1170      * @param listener       listener to handle events raised by this calibrator.
1171      * @throws IllegalArgumentException if provided hard-iron matrix is not
1172      *                                  3x1.
1173      */
1174     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1175             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1176             final Matrix hardIron,
1177             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1178         this(measurements, commonAxisUsed, hardIron);
1179         this.listener = listener;
1180     }
1181 
1182     /**
1183      * Constructor.
1184      *
1185      * @param magneticModel Earth's magnetic model. If null, a default model
1186      *                      will be used instead.
1187      * @param hardIron      known hard-iron bias.
1188      * @throws IllegalArgumentException if provided hard-iron matrix is not
1189      *                                  3x1.
1190      */
1191     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1192             final WorldMagneticModel magneticModel, final Matrix hardIron) {
1193         this(hardIron);
1194         this.magneticModel = magneticModel;
1195     }
1196 
1197     /**
1198      * Constructor.
1199      *
1200      * @param magneticModel Earth's magnetic model. If null, a default model
1201      *                      will be used instead.
1202      * @param hardIron      known hard-iron bias.
1203      * @param listener      listener to handle events raised by this calibrator.
1204      * @throws IllegalArgumentException if provided hard-iron matrix is not
1205      *                                  3x1.
1206      */
1207     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1208             final WorldMagneticModel magneticModel, final Matrix hardIron,
1209             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1210         this(magneticModel, hardIron);
1211         this.listener = listener;
1212     }
1213 
1214     /**
1215      * Constructor.
1216      *
1217      * @param measurements  collection of body magnetic flux density measurements
1218      *                      taken at different frames (positions and orientations).
1219      * @param magneticModel Earth's magnetic model. If null, a default model
1220      *                      will be used instead.
1221      * @param hardIron      known hard-iron bias.
1222      * @throws IllegalArgumentException if provided hard-iron matrix is not
1223      *                                  3x1.
1224      */
1225     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1226             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
1227             final WorldMagneticModel magneticModel, final Matrix hardIron) {
1228         this(magneticModel, hardIron);
1229         //noinspection unchecked
1230         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
1231     }
1232 
1233     /**
1234      * Constructor.
1235      *
1236      * @param measurements  collection of body magnetic flux density measurements
1237      *                      taken at different frames (positions and orientations).
1238      * @param magneticModel Earth's magnetic model. If null, a default model
1239      *                      will be used instead.
1240      * @param hardIron      known hard-iron bias.
1241      * @param listener      listener to handle events raised by this calibrator.
1242      * @throws IllegalArgumentException if provided hard-iron matrix is not
1243      *                                  3x1.
1244      */
1245     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1246             final Collection<? extends FrameBodyMagneticFluxDensity> measurements,
1247             final WorldMagneticModel magneticModel, final Matrix hardIron,
1248             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1249         this(measurements, magneticModel, hardIron);
1250         this.listener = listener;
1251     }
1252 
1253     /**
1254      * Constructor.
1255      *
1256      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1257      *                       for the accelerometer, gyroscope and magnetometer.
1258      * @param magneticModel  Earth's magnetic model. If null, a default model
1259      *                       will be used instead.
1260      * @param hardIron       known hard-iron bias.
1261      * @throws IllegalArgumentException if provided hard-iron matrix is not
1262      *                                  3x1.
1263      */
1264     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1265             final boolean commonAxisUsed, final WorldMagneticModel magneticModel, final Matrix hardIron) {
1266         this(magneticModel, hardIron);
1267         this.commonAxisUsed = commonAxisUsed;
1268     }
1269 
1270     /**
1271      * Constructor.
1272      *
1273      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1274      *                       for the accelerometer, gyroscope and magnetometer.
1275      * @param magneticModel  Earth's magnetic model. If null, a default model
1276      *                       will be used instead.
1277      * @param hardIron       known hard-iron bias.
1278      * @param listener       listener to handle events raised by this calibrator.
1279      * @throws IllegalArgumentException if provided hard-iron matrix is not
1280      *                                  3x1.
1281      */
1282     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1283             final boolean commonAxisUsed, final WorldMagneticModel magneticModel, final Matrix hardIron,
1284             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1285         this(commonAxisUsed, magneticModel, hardIron);
1286         this.listener = listener;
1287     }
1288 
1289     /**
1290      * Constructor.
1291      *
1292      * @param measurements   collection of body magnetic flux density measurements
1293      *                       taken at different frames (positions and orientations).
1294      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1295      *                       for the accelerometer, gyroscope and magnetometer.
1296      * @param magneticModel  Earth's magnetic model. If null, a default model
1297      *                       will be used instead.
1298      * @param hardIron       known hard-iron bias.
1299      * @throws IllegalArgumentException if provided hard-iron matrix is not
1300      *                                  3x1.
1301      */
1302     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1303             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1304             final WorldMagneticModel magneticModel, final Matrix hardIron) {
1305         this(commonAxisUsed, magneticModel, hardIron);
1306         //noinspection unchecked
1307         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
1308     }
1309 
1310     /**
1311      * Constructor.
1312      *
1313      * @param measurements   collection of body magnetic flux density measurements
1314      *                       taken at different frames (positions and orientations).
1315      * @param commonAxisUsed indicates whether z-axis is assumed to be common
1316      *                       for the accelerometer, gyroscope and magnetometer.
1317      * @param magneticModel  Earth's magnetic model. If null, a default model
1318      *                       will be used instead.
1319      * @param hardIron       known hard-iron bias.
1320      * @param listener       listener to handle events raised by this calibrator.
1321      * @throws IllegalArgumentException if provided hard-iron matrix is not
1322      *                                  3x1.
1323      */
1324     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibrator(
1325             final Collection<? extends FrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1326             final WorldMagneticModel magneticModel, final Matrix hardIron,
1327             final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener) {
1328         this(measurements, commonAxisUsed, magneticModel, hardIron);
1329         this.listener = listener;
1330     }
1331 
1332     /**
1333      * Gets x-coordinate of known magnetometer hard-iron bias.
1334      * This is expressed in Teslas (T).
1335      *
1336      * @return x-coordinate of known magnetometer hard-iron bias.
1337      */
1338     @Override
1339     public double getHardIronX() {
1340         return hardIronX;
1341     }
1342 
1343     /**
1344      * Sets x-coordinate of known magnetometer hard-iron bias.
1345      * This is expressed in Teslas (T).
1346      *
1347      * @param hardIronX x coordinate of magnetometer hard-iron.
1348      * @throws LockedException if calibrator is currently running.
1349      */
1350     @Override
1351     public void setHardIronX(final double hardIronX) throws LockedException {
1352         if (running) {
1353             throw new LockedException();
1354         }
1355         this.hardIronX = hardIronX;
1356     }
1357 
1358     /**
1359      * Gets y-coordinate of known magnetometer hard-iron bias.
1360      * This is expressed in Teslas (T).
1361      *
1362      * @return y-coordinate of known magnetometer hard-iron bias.
1363      */
1364     @Override
1365     public double getHardIronY() {
1366         return hardIronY;
1367     }
1368 
1369     /**
1370      * Sets y-coordinate of known magnetometer hard-iron bias.
1371      * This is expressed in Teslas (T).
1372      *
1373      * @param hardIronY y coordinate of magnetometer hard-iron.
1374      * @throws LockedException if calibrator is currently running.
1375      */
1376     @Override
1377     public void setHardIronY(final double hardIronY) throws LockedException {
1378         if (running) {
1379             throw new LockedException();
1380         }
1381         this.hardIronY = hardIronY;
1382     }
1383 
1384     /**
1385      * Gets z-coordinate of known magnetometer hard-iron bias.
1386      * This is expressed in Teslas (T).
1387      *
1388      * @return z-coordinate of known magnetometer hard-iron bias.
1389      */
1390     @Override
1391     public double getHardIronZ() {
1392         return hardIronZ;
1393     }
1394 
1395     /**
1396      * Sets z-coordinate of known magnetometer hard-iron bias.
1397      * This is expressed in Teslas (T).
1398      *
1399      * @param hardIronZ z coordinate of magnetometer hard-iron.
1400      * @throws LockedException if calibrator is currently running.
1401      */
1402     @Override
1403     public void setHardIronZ(final double hardIronZ) throws LockedException {
1404         if (running) {
1405             throw new LockedException();
1406         }
1407         this.hardIronZ = hardIronZ;
1408     }
1409 
1410     /**
1411      * Gets known x coordinate of magnetometer hard-iron.
1412      *
1413      * @return x coordinate of magnetometer hard-iron.
1414      */
1415     @Override
1416     public MagneticFluxDensity getHardIronXAsMagneticFluxDensity() {
1417         return new MagneticFluxDensity(hardIronX, MagneticFluxDensityUnit.TESLA);
1418     }
1419 
1420     /**
1421      * Gets known x coordinate of magnetometer hard-iron.
1422      *
1423      * @param result instance where result will be stored.
1424      */
1425     @Override
1426     public void getHardIronXAsMagneticFluxDensity(final MagneticFluxDensity result) {
1427         result.setValue(hardIronX);
1428         result.setUnit(MagneticFluxDensityUnit.TESLA);
1429     }
1430 
1431     /**
1432      * Sets known x-coordinate of magnetometer hard-iron.
1433      *
1434      * @param hardIronX known x-coordinate of magnetometer hard-iron.
1435      * @throws LockedException if calibrator is currently running.
1436      */
1437     @Override
1438     public void setHardIronX(final MagneticFluxDensity hardIronX) throws LockedException {
1439         if (running) {
1440             throw new LockedException();
1441         }
1442         this.hardIronX = convertMagneticFluxDensity(hardIronX);
1443     }
1444 
1445     /**
1446      * Gets known y coordinate of magnetometer hard-iron.
1447      *
1448      * @return y coordinate of magnetometer hard-iron.
1449      */
1450     @Override
1451     public MagneticFluxDensity getHardIronYAsMagneticFluxDensity() {
1452         return new MagneticFluxDensity(hardIronY, MagneticFluxDensityUnit.TESLA);
1453     }
1454 
1455     /**
1456      * Gets known y coordinate of magnetometer hard-iron.
1457      *
1458      * @param result instance where result will be stored.
1459      */
1460     @Override
1461     public void getHardIronYAsMagneticFluxDensity(final MagneticFluxDensity result) {
1462         result.setValue(hardIronY);
1463         result.setUnit(MagneticFluxDensityUnit.TESLA);
1464     }
1465 
1466     /**
1467      * Sets known y-coordinate of magnetometer hard-iron.
1468      *
1469      * @param hardIronY known y-coordinate of magnetometer hard-iron.
1470      * @throws LockedException if calibrator is currently running.
1471      */
1472     @Override
1473     public void setHardIronY(final MagneticFluxDensity hardIronY) throws LockedException {
1474         if (running) {
1475             throw new LockedException();
1476         }
1477         this.hardIronY = convertMagneticFluxDensity(hardIronY);
1478     }
1479 
1480     /**
1481      * Gets known z coordinate of magnetometer hard-iron.
1482      *
1483      * @return z coordinate of magnetometer hard-iron.
1484      */
1485     @Override
1486     public MagneticFluxDensity getHardIronZAsMagneticFluxDensity() {
1487         return new MagneticFluxDensity(hardIronZ, MagneticFluxDensityUnit.TESLA);
1488     }
1489 
1490     /**
1491      * Gets known z coordinate of magnetometer hard-iron.
1492      *
1493      * @param result instance where result will be stored.
1494      */
1495     @Override
1496     public void getHardIronZAsMagneticFluxDensity(final MagneticFluxDensity result) {
1497         result.setValue(hardIronZ);
1498         result.setUnit(MagneticFluxDensityUnit.TESLA);
1499     }
1500 
1501     /**
1502      * Sets known z-coordinate of magnetometer hard-iron.
1503      *
1504      * @param hardIronZ known z-coordinate of magnetometer hard-iron.
1505      * @throws LockedException if calibrator is currently running.
1506      */
1507     @Override
1508     public void setHardIronZ(final MagneticFluxDensity hardIronZ) throws LockedException {
1509         if (running) {
1510             throw new LockedException();
1511         }
1512         this.hardIronZ = convertMagneticFluxDensity(hardIronZ);
1513     }
1514 
1515     /**
1516      * Sets known hard-iron bias coordinates of magnetometer expressed
1517      * in Teslas (T).
1518      *
1519      * @param hardIronX x-coordinate of magnetometer hard-iron.
1520      * @param hardIronY y-coordinate of magnetometer hard-iron.
1521      * @param hardIronZ z-coordinate of magnetometer hard-iron.
1522      * @throws LockedException if calibrator is currently running.
1523      */
1524     @Override
1525     public void setHardIronCoordinates(
1526             final double hardIronX, final double hardIronY, final double hardIronZ) throws LockedException {
1527         if (running) {
1528             throw new LockedException();
1529         }
1530         this.hardIronX = hardIronX;
1531         this.hardIronY = hardIronY;
1532         this.hardIronZ = hardIronZ;
1533     }
1534 
1535     /**
1536      * Sets known hard-iron coordinates.
1537      *
1538      * @param hardIronX x-coordinate of magnetometer hard-iron.
1539      * @param hardIronY y-coordinate of magnetometer hard-iron.
1540      * @param hardIronZ z-coordinate of magnetometer hard-iron.
1541      * @throws LockedException if calibrator is currently running.
1542      */
1543     @Override
1544     public void setHardIronCoordinates(
1545             final MagneticFluxDensity hardIronX, final MagneticFluxDensity hardIronY,
1546             final MagneticFluxDensity hardIronZ) throws LockedException {
1547         if (running) {
1548             throw new LockedException();
1549         }
1550         this.hardIronX = convertMagneticFluxDensity(hardIronX);
1551         this.hardIronY = convertMagneticFluxDensity(hardIronY);
1552         this.hardIronZ = convertMagneticFluxDensity(hardIronZ);
1553     }
1554 
1555     /**
1556      * Gets known hard-iron.
1557      *
1558      * @return known hard-iron.
1559      */
1560     @Override
1561     public MagneticFluxDensityTriad getHardIronAsTriad() {
1562         return new MagneticFluxDensityTriad(MagneticFluxDensityUnit.TESLA, hardIronX, hardIronY, hardIronZ);
1563     }
1564 
1565     /**
1566      * Gets known hard-iron.
1567      *
1568      * @param result instance where result will be stored.
1569      */
1570     @Override
1571     public void getHardIronAsTriad(final MagneticFluxDensityTriad result) {
1572         result.setValueCoordinatesAndUnit(hardIronX, hardIronY, hardIronZ, MagneticFluxDensityUnit.TESLA);
1573     }
1574 
1575     /**
1576      * Sets known hard-iron.
1577      *
1578      * @param hardIron hard-iron to be set.
1579      * @throws LockedException if calibrator is currently running.
1580      */
1581     @Override
1582     public void setHardIron(final MagneticFluxDensityTriad hardIron) throws LockedException {
1583         if (running) {
1584             throw new LockedException();
1585         }
1586 
1587         hardIronX = convertMagneticFluxDensity(hardIron.getValueX(), hardIron.getUnit());
1588         hardIronY = convertMagneticFluxDensity(hardIron.getValueY(), hardIron.getUnit());
1589         hardIronZ = convertMagneticFluxDensity(hardIron.getValueZ(), hardIron.getUnit());
1590     }
1591 
1592     /**
1593      * Gets a collection of body magnetic flux density measurements taken at different
1594      * frames (positions, orientations and velocities).
1595      * If a single device IMU needs to be calibrated, typically all measurements are
1596      * taken at the same position, with zero velocity and multiple orientations.
1597      * However, if we just want to calibrate a given IMU model (e.g. obtain
1598      * an average and less precise calibration for the IMU of a given phone model),
1599      * we could take measurements collected throughout the planet at multiple positions
1600      * while the phone remains static (e.g. while charging), hence each measurement
1601      * position will change, velocity will remain zero and orientation will be
1602      * typically constant at horizontal orientation while the phone remains on a
1603      * flat surface.
1604      *
1605      * @return a collection of body magnetic flux density measurements taken at different
1606      * frames (positions, orientations and velocities).
1607      */
1608     @Override
1609     public Collection<FrameBodyMagneticFluxDensity> getMeasurements() {
1610         return measurements;
1611     }
1612 
1613     /**
1614      * Sets a collection of body magnetic flux density measurements taken at different
1615      * frames (positions, orientations and velocities).
1616      * If a single device IMU needs to be calibrated, typically all measurements are
1617      * taken at the same position, with zero velocity and multiple orientations.
1618      * However, if we just want to calibrate the a given IMU model (e.g. obtain
1619      * an average and less precise calibration for the IMU of a given phone model),
1620      * we could take measurements collected throughout the planet at multiple positions
1621      * while the phone remains static (e.g. while charging), hence each measurement
1622      * position will change, velocity will remain zero and orientation will be
1623      * typically constant at horizontal orientation while the phone remains on a
1624      * flat surface.
1625      *
1626      * @param measurements collection of body magnetic flux density measurements
1627      *                     taken at different frames (positions, orientations
1628      *                     and velocities).
1629      * @throws LockedException if estimator is currently running.
1630      */
1631     @Override
1632     public void setMeasurements(
1633             final Collection<? extends FrameBodyMagneticFluxDensity> measurements) throws LockedException {
1634         if (running) {
1635             throw new LockedException();
1636         }
1637         //noinspection unchecked
1638         this.measurements = (Collection<FrameBodyMagneticFluxDensity>) measurements;
1639     }
1640 
1641     /**
1642      * Indicates the type of measurement used by this calibrator.
1643      *
1644      * @return type of measurement used by this calibrator.
1645      */
1646     @Override
1647     public MagnetometerCalibratorMeasurementType getMeasurementType() {
1648         return MagnetometerCalibratorMeasurementType.FRAME_BODY_MAGNETIC_FLUX_DENSITY;
1649     }
1650 
1651     /**
1652      * Indicates whether this calibrator requires ordered measurements in a
1653      * list or not.
1654      *
1655      * @return true if measurements must be ordered, false otherwise.
1656      */
1657     @Override
1658     public boolean isOrderedMeasurementsRequired() {
1659         return false;
1660     }
1661 
1662     /**
1663      * Indicates whether this calibrator requires quality scores for each
1664      * measurement or not.
1665      *
1666      * @return true if quality scores are required, false otherwise.
1667      */
1668     @Override
1669     public boolean isQualityScoresRequired() {
1670         return false;
1671     }
1672 
1673     /**
1674      * Indicates whether z-axis is assumed to be common for accelerometer,
1675      * gyroscope and magnetometer.
1676      * When enabled, this eliminates 3 variables from Mm (soft-iron) matrix.
1677      *
1678      * @return true if z-axis is assumed to be common for accelerometer,
1679      * gyroscope and magnetometer, false otherwise.
1680      */
1681     @Override
1682     public boolean isCommonAxisUsed() {
1683         return commonAxisUsed;
1684     }
1685 
1686     /**
1687      * Specifies whether z-axis is assumed to be common for accelerometer and
1688      * gyroscope.
1689      * When enabled, this eliminates 3 variables from Mm matrix.
1690      *
1691      * @param commonAxisUsed true if z-axis is assumed to be common for
1692      *                       accelerometer, gyroscope and magnetometer, false
1693      *                       otherwise.
1694      * @throws LockedException if estimator is currently running.
1695      */
1696     @Override
1697     public void setCommonAxisUsed(final boolean commonAxisUsed) throws LockedException {
1698         if (running) {
1699             throw new LockedException();
1700         }
1701 
1702         this.commonAxisUsed = commonAxisUsed;
1703     }
1704 
1705     /**
1706      * Gets listener to handle events raised by this calibrator.
1707      *
1708      * @return listener to handle events raised by this calibrator.
1709      */
1710     @Override
1711     public KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener getListener() {
1712         return listener;
1713     }
1714 
1715     /**
1716      * Sets listener to handle events raised by this calibrator.
1717      *
1718      * @param listener listener to handle events raised by this calibrator.
1719      * @throws LockedException if estimator is currently running.
1720      */
1721     @Override
1722     public void setListener(final KnownHardIronAndFrameMagnetometerLinearLeastSquaresCalibratorListener listener)
1723             throws LockedException {
1724         if (running) {
1725             throw new LockedException();
1726         }
1727 
1728         this.listener = listener;
1729     }
1730 
1731     /**
1732      * Gets minimum number of required measurements.
1733      *
1734      * @return minimum number of required measurements.
1735      */
1736     @Override
1737     public int getMinimumRequiredMeasurements() {
1738         return MINIMUM_MEASUREMENTS;
1739     }
1740 
1741     /**
1742      * Indicates whether calibrator is ready to start the estimator.
1743      *
1744      * @return true if calibrator is ready, false otherwise.
1745      */
1746     @Override
1747     public boolean isReady() {
1748         return measurements != null && measurements.size() >= MINIMUM_MEASUREMENTS;
1749     }
1750 
1751     /**
1752      * Indicates whether calibrator is currently running or no.
1753      *
1754      * @return true if calibrator is running, false otherwise.
1755      */
1756     @Override
1757     public boolean isRunning() {
1758         return running;
1759     }
1760 
1761     /**
1762      * Gets Earth's magnetic model.
1763      *
1764      * @return Earth's magnetic model or null if not provided.
1765      */
1766     public WorldMagneticModel getMagneticModel() {
1767         return magneticModel;
1768     }
1769 
1770     /**
1771      * Sets Earth's magnetic model.
1772      * If not provided a default model will be loaded internally.
1773      *
1774      * @param magneticModel Earth's magnetic model to be set.
1775      * @throws LockedException if calibrator is currently running.
1776      */
1777     public void setMagneticModel(final WorldMagneticModel magneticModel) throws LockedException {
1778         if (running) {
1779             throw new LockedException();
1780         }
1781         this.magneticModel = magneticModel;
1782     }
1783 
1784     /**
1785      * Gets known hard-iron bias as an array.
1786      * Array values are expressed in Teslas (T).
1787      *
1788      * @return array containing coordinates of initial bias.
1789      */
1790     @Override
1791     public double[] getHardIron() {
1792         final var result = new double[BodyMagneticFluxDensity.COMPONENTS];
1793         getHardIron(result);
1794         return result;
1795     }
1796 
1797     /**
1798      * Gets known hard-iron bias as an array.
1799      * Array values are expressed in Teslas (T).
1800      *
1801      * @param result instance where result data will be copied to.
1802      * @throws IllegalArgumentException if provided array does not have
1803      *                                  length 3.
1804      */
1805     @Override
1806     public void getHardIron(final double[] result) {
1807         if (result.length != BodyMagneticFluxDensity.COMPONENTS) {
1808             throw new IllegalArgumentException();
1809         }
1810         result[0] = hardIronX;
1811         result[1] = hardIronY;
1812         result[2] = hardIronZ;
1813     }
1814 
1815     /**
1816      * Sets known hard-iron bias as an array.
1817      * Array values are expressed in Teslas (T).
1818      *
1819      * @param hardIron known hard-iron bias.
1820      * @throws LockedException          if calibrator is currently running.
1821      * @throws IllegalArgumentException if provided array does not have
1822      *                                  length 3.
1823      */
1824     @Override
1825     public void setHardIron(final double[] hardIron) throws LockedException {
1826         if (running) {
1827             throw new LockedException();
1828         }
1829 
1830         if (hardIron.length != BodyMagneticFluxDensity.COMPONENTS) {
1831             throw new IllegalArgumentException();
1832         }
1833         hardIronX = hardIron[0];
1834         hardIronY = hardIron[1];
1835         hardIronZ = hardIron[2];
1836     }
1837 
1838     /**
1839      * Gets known hard-iron bias as a column matrix.
1840      *
1841      * @return hard-iron bias as a column matrix.
1842      */
1843     @Override
1844     public Matrix getHardIronMatrix() {
1845         Matrix result;
1846         try {
1847             result = new Matrix(BodyMagneticFluxDensity.COMPONENTS, 1);
1848             getHardIronMatrix(result);
1849         } catch (final WrongSizeException ignore) {
1850             // never happens
1851             result = null;
1852         }
1853         return result;
1854     }
1855 
1856     /**
1857      * Gets known hard-iron bias as a column matrix.
1858      *
1859      * @param result instance where result data will be copied to.
1860      * @throws IllegalArgumentException if provided matrix is not 3x1.
1861      */
1862     @Override
1863     public void getHardIronMatrix(final Matrix result) {
1864         if (result.getRows() != BodyMagneticFluxDensity.COMPONENTS || result.getColumns() != 1) {
1865             throw new IllegalArgumentException();
1866         }
1867         result.setElementAtIndex(0, hardIronX);
1868         result.setElementAtIndex(1, hardIronY);
1869         result.setElementAtIndex(2, hardIronZ);
1870     }
1871 
1872     /**
1873      * Sets known hard-iron bias.
1874      *
1875      * @param hardIron magnetometer hard-iron bias to be set.
1876      * @throws LockedException          if calibrator is currently running.
1877      * @throws IllegalArgumentException if provided matrix is not 3x1.
1878      */
1879     @Override
1880     public void setHardIron(final Matrix hardIron) throws LockedException {
1881         if (running) {
1882             throw new LockedException();
1883         }
1884         if (hardIron.getRows() != BodyMagneticFluxDensity.COMPONENTS || hardIron.getColumns() != 1) {
1885             throw new IllegalArgumentException();
1886         }
1887 
1888         hardIronX = hardIron.getElementAtIndex(0);
1889         hardIronY = hardIron.getElementAtIndex(1);
1890         hardIronZ = hardIron.getElementAtIndex(2);
1891     }
1892 
1893     /**
1894      * Estimates magnetometer calibration parameters containing scale factors
1895      * and cross-coupling errors.
1896      *
1897      * @throws LockedException      if calibrator is currently running.
1898      * @throws NotReadyException    if calibrator is not ready.
1899      * @throws CalibrationException if calibration fails for numerical reasons.
1900      */
1901     @Override
1902     public void calibrate() throws LockedException, NotReadyException, CalibrationException {
1903         if (running) {
1904             throw new LockedException();
1905         }
1906 
1907         if (!isReady()) {
1908             throw new NotReadyException();
1909         }
1910 
1911         try {
1912             running = true;
1913 
1914             if (listener != null) {
1915                 listener.onCalibrateStart(this);
1916             }
1917 
1918             if (commonAxisUsed) {
1919                 calibrateCommonAxis();
1920             } else {
1921                 calibrateGeneral();
1922             }
1923 
1924             if (listener != null) {
1925                 listener.onCalibrateEnd(this);
1926             }
1927 
1928         } catch (final AlgebraException | IOException e) {
1929             throw new CalibrationException(e);
1930         } finally {
1931             running = false;
1932         }
1933     }
1934 
1935     /**
1936      * Gets estimated magnetometer soft-iron matrix containing scale factors
1937      * and cross coupling errors.
1938      * This is the product of matrix Tm containing cross coupling errors and Km
1939      * containing scaling factors.
1940      * So tat:
1941      * <pre>
1942      *     Mm = [sx    mxy  mxz] = Tm*Km
1943      *          [myx   sy   myz]
1944      *          [mzx   mzy  sz ]
1945      * </pre>
1946      * Where:
1947      * <pre>
1948      *     Km = [sx 0   0 ]
1949      *          [0  sy  0 ]
1950      *          [0  0   sz]
1951      * </pre>
1952      * and
1953      * <pre>
1954      *     Tm = [1          -alphaXy    alphaXz ]
1955      *          [alphaYx    1           -alphaYz]
1956      *          [-alphaZx   alphaZy     1       ]
1957      * </pre>
1958      * Hence:
1959      * <pre>
1960      *     Mm = [sx    mxy  mxz] = Tm*Km =  [sx             -sy * alphaXy   sz * alphaXz ]
1961      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
1962      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
1963      * </pre>
1964      * This instance allows any 3x3 matrix however, typically alphaYx, alphaZx and alphaZy
1965      * are considered to be zero if the accelerometer z-axis is assumed to be the same
1966      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
1967      * becomes upper diagonal:
1968      * <pre>
1969      *     Mm = [sx    mxy  mxz]
1970      *          [0     sy   myz]
1971      *          [0     0    sz ]
1972      * </pre>
1973      * Values of this matrix are unit-less.
1974      *
1975      * @return estimated magnetometer soft-iron scale factors and cross coupling errors,
1976      * or null if not available.
1977      */
1978     @Override
1979     public Matrix getEstimatedMm() {
1980         return estimatedMm;
1981     }
1982 
1983     /**
1984      * Gets estimated x-axis scale factor.
1985      *
1986      * @return estimated x-axis scale factor or null if not available.
1987      */
1988     @Override
1989     public Double getEstimatedSx() {
1990         return estimatedMm != null ? estimatedMm.getElementAt(0, 0) : null;
1991     }
1992 
1993     /**
1994      * Gets estimated y-axis scale factor.
1995      *
1996      * @return estimated y-axis scale factor or null if not available.
1997      */
1998     @Override
1999     public Double getEstimatedSy() {
2000         return estimatedMm != null ? estimatedMm.getElementAt(1, 1) : null;
2001     }
2002 
2003     /**
2004      * Gets estimated z-axis scale factor.
2005      *
2006      * @return estimated z-axis scale factor or null if not available.
2007      */
2008     @Override
2009     public Double getEstimatedSz() {
2010         return estimatedMm != null ? estimatedMm.getElementAt(2, 2) : null;
2011     }
2012 
2013     /**
2014      * Gets estimated x-y cross-coupling error.
2015      *
2016      * @return estimated x-y cross-coupling error or null if not available.
2017      */
2018     @Override
2019     public Double getEstimatedMxy() {
2020         return estimatedMm != null ? estimatedMm.getElementAt(0, 1) : null;
2021     }
2022 
2023     /**
2024      * Gets estimated x-z cross-coupling error.
2025      *
2026      * @return estimated x-z cross-coupling error or null if not available.
2027      */
2028     @Override
2029     public Double getEstimatedMxz() {
2030         return estimatedMm != null ? estimatedMm.getElementAt(0, 2) : null;
2031     }
2032 
2033     /**
2034      * Gets estimated y-x cross-coupling error.
2035      *
2036      * @return estimated y-x cross-coupling error or null if not available.
2037      */
2038     @Override
2039     public Double getEstimatedMyx() {
2040         return estimatedMm != null ? estimatedMm.getElementAt(1, 0) : null;
2041     }
2042 
2043     /**
2044      * Gets estimated y-z cross-coupling error.
2045      *
2046      * @return estimated y-z cross-coupling error or null if not available.
2047      */
2048     @Override
2049     public Double getEstimatedMyz() {
2050         return estimatedMm != null ? estimatedMm.getElementAt(1, 2) : null;
2051     }
2052 
2053     /**
2054      * Gets estimated z-x cross-coupling error.
2055      *
2056      * @return estimated z-x cross-coupling error or null if not available.
2057      */
2058     @Override
2059     public Double getEstimatedMzx() {
2060         return estimatedMm != null ? estimatedMm.getElementAt(2, 0) : null;
2061     }
2062 
2063     /**
2064      * Gets estimated z-y cross-coupling error.
2065      *
2066      * @return estimated z-y cross-coupling error or null if not available.
2067      */
2068     @Override
2069     public Double getEstimatedMzy() {
2070         return estimatedMm != null ? estimatedMm.getElementAt(2, 1) : null;
2071     }
2072 
2073     /**
2074      * Internal method to perform calibration when common z-axis is assumed
2075      * for the accelerometer, gyroscope and magnetometer.
2076      *
2077      * @throws AlgebraException if there are numerical errors.
2078      * @throws IOException      if world magnetic model cannot be loaded.
2079      */
2080     private void calibrateCommonAxis() throws AlgebraException, IOException {
2081         // The magnetometer model is:
2082         // mBmeas = bm + (I + Mm) * mBtrue + w
2083 
2084         // Ideally a least squares solution tries to minimize noise component, so:
2085         // mBmeas = bm + (I + Mm) * mBtrue
2086 
2087         // Hence:
2088         // [mBmeasx] = [bx] + ( [1  0   0] + [sx    mxy mxz])   [mBtruex]
2089         // [mBmeasy] = [by]     [0  1   0]   [myx   sy  myz]    [mBtruey]
2090         // [mBmeasz] = [bz]     [0  0   1]   [mzx   mzy sz ]    [mBtruez]
2091 
2092         // where myx = mzx = mzy = 0
2093 
2094         // Hence:
2095         // [mBmeasx] = [bx] + ( [1  0   0] + [sx    mxy mxz])   [mBtruex]
2096         // [mBmeasy] = [by]     [0  1   0]   [0     sy  myz]    [mBtruey]
2097         // [mBmeasz] = [bz]     [0  0   1]   [0     0   sz ]    [mBtruez]
2098 
2099         // [mBmeasx] = [bx] +   [1+sx   mxy     mxz ][mBtruex]
2100         // [mBmeasy]   [by]     [0      1+sy    myz ][mBtruey]
2101         // [mBmeasz]   [bz]     [0      0       1+sz][mBtruez]
2102 
2103         // mBmeasx = bx + (1+sx) * mBtruex + mxy * mBtruey + mxz * mBtruez
2104         // mBmeasy = by + (1+sy) * mBtruey + myz * mBtruez
2105         // mBmeasz = bz + (1+sz) * mBtruez
2106 
2107         // Where the unknowns are: sx, sy, sz, mxy mxz, myz
2108         // Reordering:
2109         // mBmeasx = bx + mBtruex + sx * mBtruex + mxy * mBtruey + mxz * mBtruez
2110         // mBmeasy = by + mBtruey + sy * mBtruey + myz * mBtruez
2111         // mBmeasz = bz + mBtruez + sz * mBtruez
2112 
2113         // mBmeasx - mBtruex - bx = sx * mBtruex + mxy * mBtruey + mxz * mBtruez
2114         // mBmeasy - mBtruey - by = sy * mBtruey + myz * mBtruez
2115         // mBmeasz - mBtruez - bz = sz * mBtruez
2116 
2117         // [mBtruex  0        0        mBtruey  mBtruez  0      ][sx ] = [mBmeasx - mBtruex - bx]
2118         // [0        mBtruey  0        0        0        mBtruez][sy ]   [mBmeasy - mBtruey - by]
2119         // [0        0        mBtruez  0        0        0      ][sz ]   [mBmeasz - mBtruez - bz]
2120         //                                                       [mxy]
2121         //                                                       [mxz]
2122         //                                                       [myz]
2123 
2124         final WMMEarthMagneticFluxDensityEstimator wmmEstimator;
2125         if (magneticModel != null) {
2126             wmmEstimator = new WMMEarthMagneticFluxDensityEstimator(magneticModel);
2127         } else {
2128             wmmEstimator = new WMMEarthMagneticFluxDensityEstimator();
2129         }
2130 
2131         final var expectedMagneticFluxDensity = new BodyMagneticFluxDensity();
2132         final var nedFrame = new NEDFrame();
2133         final var earthB = new NEDMagneticFluxDensity();
2134         final var cbn = new CoordinateTransformation(FrameType.BODY_FRAME, FrameType.LOCAL_NAVIGATION_FRAME);
2135         final var cnb = new CoordinateTransformation(FrameType.LOCAL_NAVIGATION_FRAME, FrameType.BODY_FRAME);
2136 
2137         final var rows = EQUATIONS_PER_MEASUREMENT * measurements.size();
2138         final var a = new Matrix(rows, COMMON_Z_AXIS_UNKNOWNS);
2139         final var b = new Matrix(rows, 1);
2140         var i = 0;
2141         for (final var measurement : measurements) {
2142             final var measuredMagneticFluxDensity = measurement.getMagneticFluxDensity();
2143 
2144             // estimate Earth magnetic flux density at frame position and
2145             // timestamp using WMM
2146             final var ecefFrame = measurement.getFrame();
2147             ECEFtoNEDFrameConverter.convertECEFtoNED(ecefFrame, nedFrame);
2148 
2149             final var year = measurement.getYear();
2150 
2151             final var latitude = nedFrame.getLatitude();
2152             final var longitude = nedFrame.getLongitude();
2153             final var height = nedFrame.getHeight();
2154 
2155             nedFrame.getCoordinateTransformation(cbn);
2156             cbn.inverse(cnb);
2157 
2158             wmmEstimator.estimate(latitude, longitude, height, year, earthB);
2159 
2160             // estimate expected body magnetic flux density taking into
2161             // account body attitude (inverse of frame orientation) and
2162             // estimated Earth magnetic flux density
2163             BodyMagneticFluxDensityEstimator.estimate(earthB, cnb, expectedMagneticFluxDensity);
2164 
2165             final var bMeasX = measuredMagneticFluxDensity.getBx();
2166             final var bMeasY = measuredMagneticFluxDensity.getBy();
2167             final var bMeasZ = measuredMagneticFluxDensity.getBz();
2168 
2169             final var bTrueX = expectedMagneticFluxDensity.getBx();
2170             final var bTrueY = expectedMagneticFluxDensity.getBy();
2171             final var bTrueZ = expectedMagneticFluxDensity.getBz();
2172 
2173             a.setElementAt(i, 0, bTrueX);
2174             a.setElementAt(i, 1, 0.0);
2175             a.setElementAt(i, 2, 0.0);
2176             a.setElementAt(i, 3, bTrueY);
2177             a.setElementAt(i, 4, bTrueZ);
2178             a.setElementAt(i, 5, 0.0);
2179 
2180             b.setElementAtIndex(i, bMeasX - bTrueX - hardIronX);
2181             i++;
2182 
2183             a.setElementAt(i, 0, 0.0);
2184             a.setElementAt(i, 1, bTrueY);
2185             a.setElementAt(i, 2, 0.0);
2186             a.setElementAt(i, 3, 0.0);
2187             a.setElementAt(i, 4, 0.0);
2188             a.setElementAt(i, 5, bTrueZ);
2189 
2190             b.setElementAtIndex(i, bMeasY - bTrueY - hardIronY);
2191             i++;
2192 
2193             a.setElementAt(i, 0, 0.0);
2194             a.setElementAt(i, 1, 0.0);
2195             a.setElementAt(i, 2, bTrueZ);
2196             a.setElementAt(i, 3, 0.0);
2197             a.setElementAt(i, 4, 0.0);
2198             a.setElementAt(i, 5, 0.0);
2199 
2200             b.setElementAtIndex(i, bMeasZ - bTrueZ - hardIronZ);
2201             i++;
2202         }
2203 
2204         final var unknowns = Utils.solve(a, b);
2205 
2206         final var sx = unknowns.getElementAtIndex(0);
2207         final var sy = unknowns.getElementAtIndex(1);
2208         final var sz = unknowns.getElementAtIndex(2);
2209         final var mxy = unknowns.getElementAtIndex(3);
2210         final var mxz = unknowns.getElementAtIndex(4);
2211         final var myz = unknowns.getElementAtIndex(5);
2212 
2213         fillMm(sx, sy, sz, mxy, mxz, 0.0, myz, 0.0, 0.0);
2214     }
2215 
2216     /**
2217      * Internal method to perform general calibration.
2218      *
2219      * @throws AlgebraException if there are numerical errors.
2220      * @throws IOException      if world magnetic model cannot be loaded.
2221      */
2222     private void calibrateGeneral() throws AlgebraException, IOException {
2223         // The magnetometer model is:
2224         // mBmeas = bm + (I + Mm) * mBtrue + w
2225 
2226         // Ideally a least squares solution tries to minimize noise component, so:
2227         // mBmeas = bm + (I + Mm) * mBtrue
2228 
2229         // Hence:
2230         // [mBmeasx] = [bx] + ( [1  0   0] + [sx    mxy mxz])   [mBtruex]
2231         // [mBmeasy] = [by]     [0  1   0]   [myx   sy  myz]    [mBtruey]
2232         // [mBmeasz] = [bz]     [0  0   1]   [mzx   mzy sz ]    [mBtruez]
2233 
2234         // [mBmeasx] = [bx] +   [1+sx   mxy     mxz ][mBtruex]
2235         // [mBmeasy]   [by]     [myx    1+sy    myz ][mBtruey]
2236         // [mBmeasz]   [bz]     [mzx    mzy     1+sz][mBtruez]
2237 
2238         // mBmeasx = bx + (1+sx) * mBtruex + mxy * mBtruey + mxz * mBtruez
2239         // mBmeasy = by + myx * mBtruex + (1+sy) * mBtruey + myz * mBtruez
2240         // mBmeasz = bz + mzx * mBtruex + mzy * mBtruey + (1+sz) * mBtruez
2241 
2242         // Where the unknowns are: bx, by, bz, sx, sy, sz, mxy mxz, myx, myz, mzx, mzy
2243         // Reordering:
2244         // mBmeasx = bx + mBtruex + sx * mBtruex + mxy * mBtruey + mxz * mBtruez
2245         // mBmeasy = by + myx * mBtruex + mBtruey + sy * mBtruey + myz * mBtruez
2246         // mBmeasz = bz + mzx * mBtruex + mzy * mBtruey + mBtruez + sz * mBtruez
2247 
2248         // mBmeasx - mBtruex - bx = sx * mBtruex + mxy * mBtruey + mxz * mBtruez
2249         // mBmeasy - mBtruey - by = myx * mBtruex + sy * mBtruey + myz * mBtruez
2250         // mBmeasz - mBtruez - bz = mzx * mBtruex + mzy * mBtruey + sz * mBtruez
2251 
2252         // [mBtruex  0        0        mBtruey  mBtruez  0        0        0        0      ][sx ] = [mBmeasx - mBtruex - bx]
2253         // [0        mBtruey  0        0        0        mBtruex  mBtruez  0        0      ][sy ]   [mBmeasy - mBtruey - by]
2254         // [0        0        mBtruez  0        0        0        0        mBtruex  mBtruey][sz ]   [mBmeasz - mBtruez - bz]
2255         //                                                                                  [mxy]
2256         //                                                                                  [mxz]
2257         //                                                                                  [myx]
2258         //                                                                                  [myz]
2259         //                                                                                  [mzx]
2260         //                                                                                  [mzy]
2261 
2262         final WMMEarthMagneticFluxDensityEstimator wmmEstimator;
2263         if (magneticModel != null) {
2264             wmmEstimator = new WMMEarthMagneticFluxDensityEstimator(magneticModel);
2265         } else {
2266             wmmEstimator = new WMMEarthMagneticFluxDensityEstimator();
2267         }
2268 
2269         final var expectedMagneticFluxDensity = new BodyMagneticFluxDensity();
2270         final var nedFrame = new NEDFrame();
2271         final var earthB = new NEDMagneticFluxDensity();
2272         final var cbn = new CoordinateTransformation(FrameType.BODY_FRAME, FrameType.LOCAL_NAVIGATION_FRAME);
2273         final var cnb = new CoordinateTransformation(FrameType.LOCAL_NAVIGATION_FRAME, FrameType.BODY_FRAME);
2274 
2275         final var rows = EQUATIONS_PER_MEASUREMENT * measurements.size();
2276         final var a = new Matrix(rows, GENERAL_UNKNOWNS);
2277         final var b = new Matrix(rows, 1);
2278         var i = 0;
2279         for (final var measurement : measurements) {
2280             final var measuredMagneticFluxDensity = measurement.getMagneticFluxDensity();
2281 
2282             // estimate Earth magnetic flux density at frame position and
2283             // timestamp using WMM
2284             final var ecefFrame = measurement.getFrame();
2285             ECEFtoNEDFrameConverter.convertECEFtoNED(ecefFrame, nedFrame);
2286 
2287             final var year = measurement.getYear();
2288 
2289             final var latitude = nedFrame.getLatitude();
2290             final var longitude = nedFrame.getLongitude();
2291             final var height = nedFrame.getHeight();
2292 
2293             nedFrame.getCoordinateTransformation(cbn);
2294             cbn.inverse(cnb);
2295 
2296             wmmEstimator.estimate(latitude, longitude, height, year, earthB);
2297 
2298             // estimate expected body magnetic flux density taking into
2299             // account body attitude (inverse of frame orientation) and
2300             // estimated Earth magnetic flux density
2301             BodyMagneticFluxDensityEstimator.estimate(earthB, cnb, expectedMagneticFluxDensity);
2302 
2303             final var bMeasX = measuredMagneticFluxDensity.getBx();
2304             final var bMeasY = measuredMagneticFluxDensity.getBy();
2305             final var bMeasZ = measuredMagneticFluxDensity.getBz();
2306 
2307             final var bTrueX = expectedMagneticFluxDensity.getBx();
2308             final var bTrueY = expectedMagneticFluxDensity.getBy();
2309             final var bTrueZ = expectedMagneticFluxDensity.getBz();
2310 
2311             a.setElementAt(i, 0, bTrueX);
2312             a.setElementAt(i, 1, 0.0);
2313             a.setElementAt(i, 2, 0.0);
2314             a.setElementAt(i, 3, bTrueY);
2315             a.setElementAt(i, 4, bTrueZ);
2316             a.setElementAt(i, 5, 0.0);
2317             a.setElementAt(i, 6, 0.0);
2318             a.setElementAt(i, 7, 0.0);
2319             a.setElementAt(i, 8, 0.0);
2320 
2321             b.setElementAtIndex(i, bMeasX - bTrueX - hardIronX);
2322             i++;
2323 
2324             a.setElementAt(i, 0, 0.0);
2325             a.setElementAt(i, 1, bTrueY);
2326             a.setElementAt(i, 2, 0.0);
2327             a.setElementAt(i, 3, 0.0);
2328             a.setElementAt(i, 4, 0.0);
2329             a.setElementAt(i, 5, bTrueX);
2330             a.setElementAt(i, 6, bTrueZ);
2331             a.setElementAt(i, 7, 0.0);
2332             a.setElementAt(i, 8, 0.0);
2333 
2334             b.setElementAtIndex(i, bMeasY - bTrueY - hardIronY);
2335             i++;
2336 
2337             a.setElementAt(i, 0, 0.0);
2338             a.setElementAt(i, 1, 0.0);
2339             a.setElementAt(i, 2, bTrueZ);
2340             a.setElementAt(i, 3, 0.0);
2341             a.setElementAt(i, 4, 0.0);
2342             a.setElementAt(i, 5, 0.0);
2343             a.setElementAt(i, 6, 0.0);
2344             a.setElementAt(i, 7, bTrueX);
2345             a.setElementAt(i, 8, bTrueY);
2346 
2347             b.setElementAtIndex(i, bMeasZ - bTrueZ - hardIronZ);
2348             i++;
2349         }
2350 
2351         final var unknowns = Utils.solve(a, b);
2352 
2353         final var sx = unknowns.getElementAtIndex(0);
2354         final var sy = unknowns.getElementAtIndex(1);
2355         final var sz = unknowns.getElementAtIndex(2);
2356         final var mxy = unknowns.getElementAtIndex(3);
2357         final var mxz = unknowns.getElementAtIndex(4);
2358         final var myx = unknowns.getElementAtIndex(5);
2359         final var myz = unknowns.getElementAtIndex(6);
2360         final var mzx = unknowns.getElementAtIndex(7);
2361         final var mzy = unknowns.getElementAtIndex(8);
2362 
2363         fillMm(sx, sy, sz, mxy, mxz, myx, myz, mzx, mzy);
2364     }
2365 
2366     /**
2367      * Fills scale factor and cross coupling error matrix with estimated values.
2368      *
2369      * @param sx  x scale factor
2370      * @param sy  y scale factor
2371      * @param sz  z scale factor
2372      * @param mxy x-y cross coupling
2373      * @param mxz x-z cross coupling
2374      * @param myx y-x cross coupling
2375      * @param myz y-z cross coupling
2376      * @param mzx z-x cross coupling
2377      * @param mzy z-y cross coupling
2378      * @throws WrongSizeException never happens.
2379      */
2380     private void fillMm(final double sx, final double sy, final double sz,
2381                         final double mxy, final double mxz, final double myx,
2382                         final double myz, final double mzx, final double mzy) throws WrongSizeException {
2383         if (estimatedMm == null) {
2384             estimatedMm = new Matrix(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
2385         }
2386 
2387         estimatedMm.setElementAt(0, 0, sx);
2388         estimatedMm.setElementAt(1, 0, myx);
2389         estimatedMm.setElementAt(2, 0, mzx);
2390 
2391         estimatedMm.setElementAt(0, 1, mxy);
2392         estimatedMm.setElementAt(1, 1, sy);
2393         estimatedMm.setElementAt(2, 1, mzy);
2394 
2395         estimatedMm.setElementAt(0, 2, mxz);
2396         estimatedMm.setElementAt(1, 2, myz);
2397         estimatedMm.setElementAt(2, 2, sz);
2398     }
2399 
2400     /**
2401      * Converts magnetic flux density value and unit to Teslas.
2402      *
2403      * @param value magnetic flux density value.
2404      * @param unit  unit of magnetic flux density value.
2405      * @return converted value.
2406      */
2407     private static double convertMagneticFluxDensity(final double value, final MagneticFluxDensityUnit unit) {
2408         return MagneticFluxDensityConverter.convert(value, unit, MagneticFluxDensityUnit.TESLA);
2409     }
2410 
2411     /**
2412      * Converts magnetic flux density instance to Teslas.
2413      *
2414      * @param magneticFluxDensity magnetic flux density instance to be converted.
2415      * @return converted value.
2416      */
2417     private static double convertMagneticFluxDensity(final MagneticFluxDensity magneticFluxDensity) {
2418         return convertMagneticFluxDensity(magneticFluxDensity.getValue().doubleValue(), magneticFluxDensity.getUnit());
2419     }
2420 }