View Javadoc
1   /*
2    * Copyright (C) 2019 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.indoor.position;
17  
18  import com.irurueta.algebra.Matrix;
19  import com.irurueta.geometry.Point;
20  import com.irurueta.navigation.LockedException;
21  import com.irurueta.navigation.NotReadyException;
22  import com.irurueta.navigation.indoor.Fingerprint;
23  import com.irurueta.navigation.indoor.RadioSource;
24  import com.irurueta.navigation.indoor.RadioSourceLocated;
25  import com.irurueta.navigation.indoor.RangingAndRssiReading;
26  import com.irurueta.navigation.indoor.RangingFingerprint;
27  import com.irurueta.navigation.indoor.RangingReading;
28  import com.irurueta.navigation.indoor.Reading;
29  import com.irurueta.navigation.indoor.RssiFingerprint;
30  import com.irurueta.navigation.indoor.RssiReading;
31  import com.irurueta.numerical.robust.InliersData;
32  import com.irurueta.numerical.robust.RobustEstimatorException;
33  import com.irurueta.numerical.robust.RobustEstimatorMethod;
34  
35  import java.util.ArrayList;
36  import java.util.List;
37  
38  /**
39   * Base class for robust position estimation, using RSSI readings first to obtain
40   * an initial coarse position estimation, and then ranging readings to refine such
41   * estimation.
42   * <p>
43   * This implementation is like SequentialRobustRangingAndRssiPositionEstimator but
44   * allows mixing different kinds of readings (ranging, RSSI or ranging+RSSI).
45   *
46   * @param <P> a {@link Point} type.
47   */
48  @SuppressWarnings("DuplicatedCode")
49  public abstract class SequentialRobustMixedPositionEstimator<P extends Point<?>> {
50  
51      /**
52       * Default robust estimator method for robust position estimation using ranging
53       * data when no robust method is provided.
54       */
55      public static final RobustEstimatorMethod DEFAULT_RANGING_ROBUST_METHOD = RobustEstimatorMethod.PROMEDS;
56  
57      /**
58       * Default robust method for coarse robust position estimation using RSSI
59       * data when no robust method is provided.
60       */
61      public static final RobustEstimatorMethod DEFAULT_RSSI_ROBUST_METHOD = RobustEstimatorMethod.PROMEDS;
62  
63      /**
64       * Indicates that by default located radio source position covariance is taken
65       * into account (if available) to determine distance standard deviation for ranging
66       * measurements.
67       */
68      public static final boolean DEFAULT_USE_RANGING_RADIO_SOURCE_POSITION_COVARIANCE = true;
69  
70      /**
71       * Indicates that by default located radio source position covariance is taken
72       * into account (if available) to determine distance standard deviation for RSSI
73       * measurements.
74       */
75      public static final boolean DEFAULT_USE_RSSI_RADIO_SOURCE_POSITION_COVARIANCE = true;
76  
77      /**
78       * Indicates that by default readings are distributed evenly among radio sources
79       * taking into account quality scores of both radio sources and ranging readings.
80       */
81      public static final boolean DEFAULT_EVENLY_DISTRIBUTE_RANGING_READINGS = true;
82  
83      /**
84       * Indicates that by default readings are distributed evenly among radio sources
85       * taking into account quality scores of both radio sources and RSSI readings.
86       */
87      public static final boolean DEFAULT_EVENLY_DISTRIBUTE_RSSI_READINGS = true;
88  
89      /**
90       * Distance standard deviation assumed for provided distances as a fallback when
91       * none can be determined.
92       */
93      public static final double FALLBACK_DISTANCE_STANDARD_DEVIATION =
94              RobustPositionEstimator.FALLBACK_DISTANCE_STANDARD_DEVIATION;
95  
96      /**
97       * Default amount of progress variation before notifying a change in estimation progress.
98       * By default, this is set to 5%.
99       */
100     public static final float DEFAULT_PROGRESS_DELTA = 0.05f;
101 
102     /**
103      * Minimum allowed value for progress delta.
104      */
105     public static final float MIN_PROGRESS_DELTA = 0.0f;
106 
107     /**
108      * Maximum allowed value for progress delta.
109      */
110     public static final float MAX_PROGRESS_DELTA = 1.0f;
111 
112     /**
113      * Constant defining default confidence of the estimated result, which is
114      * 99%. This means that with a probability of 99% estimation will be
115      * accurate because chosen sub-samples will be inliers.
116      */
117     public static final double DEFAULT_CONFIDENCE = 0.99;
118 
119     /**
120      * Default maximum allowed number of iterations.
121      */
122     public static final int DEFAULT_MAX_ITERATIONS = 5000;
123 
124     /**
125      * Minimum allowed confidence value.
126      */
127     public static final double MIN_CONFIDENCE = 0.0;
128 
129     /**
130      * Maximum allowed confidence value.
131      */
132     public static final double MAX_CONFIDENCE = 1.0;
133 
134     /**
135      * Minimum allowed number of iterations.
136      */
137     public static final int MIN_ITERATIONS = 1;
138 
139     /**
140      * Indicates that result is refined by default using all found inliers.
141      */
142     public static final boolean DEFAULT_REFINE_RESULT = true;
143 
144     /**
145      * Indicates that covariance is kept by default after refining result.
146      */
147     public static final boolean DEFAULT_KEEP_COVARIANCE = true;
148 
149     /**
150      * Indicates that by default a linear solver is used for preliminary solution
151      * estimation using ranging measurements.
152      * The result obtained on each preliminary solution might be later refined.
153      */
154     public static final boolean DEFAULT_USE_RANGING_LINEAR_SOLVER = true;
155 
156     /**
157      * Indicates that by default a linear solver is used for preliminary solution
158      * estimation using RSSI measurements.
159      * The result obtained on each preliminary solution might be later refined.
160      */
161     public static final boolean DEFAULT_USE_RSSI_LINEAR_SOLVER = true;
162 
163     /**
164      * Indicates that by default an homogeneous linear solver is used either to
165      * estimate preliminary solutions or an initial solution for preliminary solutions
166      * that will be later refined on the ranging fine estimation.
167      */
168     public static final boolean DEFAULT_USE_RANGING_HOMOGENEOUS_LINEAR_SOLVER = false;
169 
170     /**
171      * Indicates that by default an homogeneous linear solver is used either to
172      * estimate preliminary solutions or an initial solution for preliminary solutions
173      * that will be later refined on the RSSI coarse estimation.
174      */
175     public static final boolean DEFAULT_USE_RSSI_HOMOGENEOUS_LINEAR_SOLVER = false;
176 
177     /**
178      * Indicates that by default preliminary ranging solutions are refined.
179      */
180     public static final boolean DEFAULT_REFINE_RANGING_PRELIMINARY_SOLUTIONS = true;
181 
182     /**
183      * Indicates that by default preliminary RSSI solutions are refined.
184      */
185     public static final boolean DEFAULT_REFINE_RSSI_PRELIMINARY_SOLUTIONS = true;
186 
187     /**
188      * Internal robust estimator for position estimation using ranging readings.
189      */
190     protected RobustRangingPositionEstimator<P> rangingEstimator;
191 
192     /**
193      * Internal robust estimator for coarse position estimation using RSSI readings.
194      */
195     protected RobustRssiPositionEstimator<P> rssiEstimator;
196 
197     /**
198      * Robust method used for robust position estimation using ranging data.
199      */
200     protected RobustEstimatorMethod rangingRobustMethod = DEFAULT_RANGING_ROBUST_METHOD;
201 
202     /**
203      * Robust method used for coarse robust position estimation using RSSI data.
204      */
205     protected RobustEstimatorMethod rssiRobustMethod = DEFAULT_RSSI_ROBUST_METHOD;
206 
207     /**
208      * Size of subsets to be checked during robust estimation.
209      */
210     protected int rangingPreliminarySubsetSize;
211 
212     /**
213      * Size of subsets to be checked during RSSI robust estimation.
214      */
215     protected int rssiPreliminarySubsetSize;
216 
217     /**
218      * Threshold to determine when samples are inliers or not used during ranging
219      * position estimation.
220      * If not defined, default threshold will be used.
221      */
222     protected Double rangingThreshold;
223 
224     /**
225      * Threshold to determine when samples are inliers or not used during RSSI
226      * position estimation.
227      * If not defined, default threshold will be used.
228      */
229     protected Double rssiThreshold;
230 
231     /**
232      * Indicates whether located radio source position covariance is taken into account
233      * (if available) to determine distance standard deviation for ranging measurements.
234      */
235     private boolean useRangingRadioSourcePositionCovariance = DEFAULT_USE_RANGING_RADIO_SOURCE_POSITION_COVARIANCE;
236 
237     /**
238      * Indicates whether located radio source position covariance is taken into account
239      * (if available) to determine distance standard deviation for RSSI measurements.
240      */
241     private boolean useRssiRadioSourcePositionCovariance = DEFAULT_USE_RSSI_RADIO_SOURCE_POSITION_COVARIANCE;
242 
243     /**
244      * Indicates whether ranging readings are evenly distributed among radio sources
245      * taking into account quality scores of both radio sources and ranging readings.
246      */
247     private boolean evenlyDistributeRangingReadings = DEFAULT_EVENLY_DISTRIBUTE_RANGING_READINGS;
248 
249     /**
250      * Indicates whether RSSI readings are evenly distributed among radio sources
251      * taking into account quality scores of both radio sources and RSSI readings.
252      */
253     private boolean evenlyDistributeRssiReadings = DEFAULT_EVENLY_DISTRIBUTE_RSSI_READINGS;
254 
255     /**
256      * Distance standard deviation fallback value to use when none can be determined
257      * from provided RSSI measurements.
258      */
259     private double rssiFallbackDistanceStandardDeviation = FALLBACK_DISTANCE_STANDARD_DEVIATION;
260 
261     /**
262      * Distance standard deviation fallback value to use when none can be determined
263      * from provided ranging measurements.
264      */
265     private double rangingFallbackDistanceStandardDeviation =
266             FALLBACK_DISTANCE_STANDARD_DEVIATION;
267 
268     /**
269      * Amount of progress variation before notifying a progress change during
270      * estimation.
271      */
272     private float progressDelta = DEFAULT_PROGRESS_DELTA;
273 
274     /**
275      * Amount of confidence expressed as a value between 0.0 and 1.0 (which is
276      * equivalent to 100%) for robust position estimation on ranging data. The amount
277      * of confidence indicates the probability that the estimated result is correct.
278      * Usually this value will be close to 1.0, but not exactly 1.0.
279      */
280     private double rangingConfidence = DEFAULT_CONFIDENCE;
281 
282     /**
283      * Amount of confidence expressed as a value between 0.0 and 1.0 (which is
284      * equivalent to 100%) for robust position estimation on RSSI data. The amount
285      * of confidence indicates the probability that the estimated result is correct.
286      * Usually this value will be close to 1.0, but not exactly 1.0.
287      */
288     private double rssiConfidence = DEFAULT_CONFIDENCE;
289 
290     /**
291      * Maximum allowed number of iterations for robust ranging position estimation.
292      * When the maximum number of iterations is exceeded, an approximate result
293      * might be available for retrieval.
294      */
295     private int rangingMaxIterations = DEFAULT_MAX_ITERATIONS;
296 
297     /**
298      * Maximum allowed number of iterations for robust RSSI position estimation.
299      * When the maximum number of iterations is exceeded, an approximate result
300      * might be available for retrieval.
301      */
302     private int rssiMaxIterations = DEFAULT_MAX_ITERATIONS;
303 
304     /**
305      * Indicates whether result is refined using all found inliers.
306      */
307     private boolean refineResult = DEFAULT_REFINE_RESULT;
308 
309     /**
310      * Indicates that covariance is kept after refining result.
311      */
312     private boolean keepCovariance = DEFAULT_KEEP_COVARIANCE;
313 
314     /**
315      * Indicates that a linear solver is used for preliminary solution estimation
316      * using ranging measurements.
317      * The result obtained on each preliminary solution might be later refined.
318      */
319     private boolean useRangingLinearSolver = DEFAULT_USE_RANGING_LINEAR_SOLVER;
320 
321     /**
322      * Indicates that a linear solver is used for preliminary solution estimation
323      * using RSSI measurements.
324      * The result obtained on each preliminary solution might be later refined.
325      */
326     private boolean useRssiLinearSolver = DEFAULT_USE_RSSI_LINEAR_SOLVER;
327 
328     /**
329      * Indicates whether an homogeneous linear solver is used either to estimate
330      * preliminary solutions or an initial solution for preliminary solutions that
331      * will be later refined on the ranging fine estimation.
332      */
333     private boolean useRangingHomogeneousLinearSolver = DEFAULT_USE_RANGING_HOMOGENEOUS_LINEAR_SOLVER;
334 
335     /**
336      * Indicates whether an homogeneous linear solver is used either to estimate
337      * preliminary solutions or an initial solution for preliminary solutions that
338      * will be later refined on the RSSI coarse estimation.
339      */
340     private boolean useRssiHomogeneousLinearSolver = DEFAULT_USE_RSSI_HOMOGENEOUS_LINEAR_SOLVER;
341 
342     /**
343      * Indicates whether preliminary ranging solutions are refined.
344      */
345     private boolean refineRangingPreliminarySolutions = DEFAULT_REFINE_RANGING_PRELIMINARY_SOLUTIONS;
346 
347     /**
348      * Indicates whether preliminary RSSI solutions are refined.
349      */
350     private boolean refineRssiPreliminarySolutions = DEFAULT_REFINE_RSSI_PRELIMINARY_SOLUTIONS;
351 
352     /**
353      * Listener in charge of handling events.
354      */
355     private SequentialRobustMixedPositionEstimatorListener<P> listener;
356 
357     /**
358      * Located radio sources used for lateration.
359      */
360     private List<? extends RadioSourceLocated<P>> sources;
361 
362     /**
363      * Fingerprint containing readings at an unknown location for provided located
364      * radio sources.
365      */
366     private Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint;
367 
368     /**
369      * Quality scores corresponding to each provided located radio source.
370      * The larger the score value the better the quality of the radio source.
371      */
372     private double[] sourceQualityScores;
373 
374     /**
375      * Quality scores corresponding to each reading with provided fingerprint.
376      * The larger the score value the better the quality of the reading.
377      */
378     private double[] fingerprintReadingsQualityScores;
379 
380     /**
381      * An initial position to start the estimation from. This can be useful if we only
382      * intend to refine a previously known estimation.
383      */
384     private P initialPosition;
385 
386     /**
387      * Indicates if this instance is locked because estimation is being executed.
388      */
389     private boolean locked;
390 
391     /**
392      * Indicates whether ranging estimation must be available or not using
393      * provided fingerprint readings.
394      */
395     private boolean rangingEstimatorAvailable;
396 
397     /**
398      * Indicates whether RSSI estimation must be available or not using
399      * provided fingerprint readings.
400      */
401     private boolean rssiEstimatorAvailable;
402 
403     /**
404      * Number of ranging readings found on provided fingerprint.
405      */
406     private int numRangingReadings;
407 
408     /**
409      * Number of RSSI readings found on provided fingerprint.
410      */
411     private int numRssiReadings;
412 
413     /**
414      * Constructor.
415      */
416     protected SequentialRobustMixedPositionEstimator() {
417     }
418 
419     /**
420      * Constructor.
421      *
422      * @param sources located radio sources used for lateration.
423      * @throws IllegalArgumentException if provided sources is null or the number of
424      *                                  provided sources is less than the required minimum.
425      */
426     protected SequentialRobustMixedPositionEstimator(final List<? extends RadioSourceLocated<P>> sources) {
427         internalSetSources(sources);
428     }
429 
430     /**
431      * Constructor.
432      *
433      * @param fingerprint fingerprint containing RSSI readings at an unknown location
434      *                    for provided located radio sources.
435      * @throws IllegalArgumentException if provided fingerprint is null.
436      */
437     protected SequentialRobustMixedPositionEstimator(
438             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint) {
439         internalSetFingerprint(fingerprint);
440     }
441 
442     /**
443      * Constructor.
444      *
445      * @param sources     located radio sources used for lateration.
446      * @param fingerprint fingerprint containing readings at an unknown location
447      *                    for provided located radio sources.
448      * @throws IllegalArgumentException if either provided sources or fingerprint is
449      *                                  null or the number of provided sources is less
450      *                                  than the required minimum.
451      */
452     protected SequentialRobustMixedPositionEstimator(
453             final List<? extends RadioSourceLocated<P>> sources,
454             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint) {
455         internalSetSources(sources);
456         internalSetFingerprint(fingerprint);
457     }
458 
459     /**
460      * Constructor.
461      *
462      * @param listener listener in charge of handling events.
463      */
464     protected SequentialRobustMixedPositionEstimator(final SequentialRobustMixedPositionEstimatorListener<P> listener) {
465         this.listener = listener;
466     }
467 
468     /**
469      * Constructor.
470      *
471      * @param sources  located radio sources used for lateration.
472      * @param listener listener in charge of handling events.
473      * @throws IllegalArgumentException if provided sources is null or the number of
474      *                                  provided sources is less than the required
475      *                                  minimum.
476      */
477     protected SequentialRobustMixedPositionEstimator(
478             final List<? extends RadioSourceLocated<P>> sources,
479             final SequentialRobustMixedPositionEstimatorListener<P> listener) {
480         this(sources);
481         this.listener = listener;
482     }
483 
484     /**
485      * Constructor.
486      *
487      * @param fingerprint fingerprint containing readings at an unknown location for
488      *                    provided located radio sources.
489      * @param listener    listener in charge of handling events.
490      * @throws IllegalArgumentException if provided fingerprint is null.
491      */
492     protected SequentialRobustMixedPositionEstimator(
493             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint,
494             final SequentialRobustMixedPositionEstimatorListener<P> listener) {
495         this(fingerprint);
496         this.listener = listener;
497     }
498 
499     /**
500      * Constructor.
501      *
502      * @param sources     located radio sources used for lateration.
503      * @param fingerprint fingerprint containing readings at an unknown location
504      *                    for provided located radio sources.
505      * @param listener    listener in charge of handling events.
506      * @throws IllegalArgumentException if either provided sources or fingerprint is
507      *                                  null or the number of provided sources is less
508      *                                  than the required minimum.
509      */
510     protected SequentialRobustMixedPositionEstimator(
511             final List<? extends RadioSourceLocated<P>> sources,
512             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint,
513             final SequentialRobustMixedPositionEstimatorListener<P> listener) {
514         this(sources, fingerprint);
515         this.listener = listener;
516     }
517 
518     /**
519      * Constructor.
520      *
521      * @param sourceQualityScores             quality scores corresponding to each
522      *                                        provided located radio source. The
523      *                                        larger the score value the better the
524      *                                        quality of the radio source.
525      * @param fingerprintReadingQualityScores quality scores corresponding to
526      *                                        readings within provided fingerprint.
527      *                                        The larger the score the better the
528      *                                        quality of the reading.
529      */
530     protected SequentialRobustMixedPositionEstimator(
531             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores) {
532         internalSetSourceQualityScores(sourceQualityScores);
533         internalSetFingerprintReadingsQualityScores(fingerprintReadingQualityScores);
534     }
535 
536     /**
537      * Constructor.
538      *
539      * @param sourceQualityScores             quality scores corresponding to each
540      *                                        provided located radio source. The
541      *                                        larger the score value the better the
542      *                                        quality of the radio source.
543      * @param fingerprintReadingQualityScores quality scores corresponding to readings
544      *                                        within provided fingerprint. The larger
545      *                                        the score the better the quality of the
546      *                                        reading.
547      * @param sources                         located radio sources used for
548      *                                        lateration.
549      * @throws IllegalArgumentException if provided sources is null or the number of
550      *                                  provided sources is less than the required minimum.
551      */
552     protected SequentialRobustMixedPositionEstimator(
553             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores,
554             final List<? extends RadioSourceLocated<P>> sources) {
555         this(sources);
556         internalSetSourceQualityScores(sourceQualityScores);
557         internalSetFingerprintReadingsQualityScores(fingerprintReadingQualityScores);
558     }
559 
560     /**
561      * Constructor.
562      *
563      * @param sourceQualityScores             quality scores corresponding to each
564      *                                        provided located radio source. The
565      *                                        larger the score value the better the
566      *                                        quality of the radio source.
567      * @param fingerprintReadingQualityScores quality scores corresponding to readings
568      *                                        within provided fingerprint. The larger
569      *                                        the score the better the quality of the
570      *                                        reading.
571      * @param fingerprint                     fingerprint containing readings at an
572      *                                        unknown location for provided located
573      *                                        radio sources.
574      * @throws IllegalArgumentException if provided fingerprint is null.
575      */
576     protected SequentialRobustMixedPositionEstimator(
577             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores,
578             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint) {
579         this(fingerprint);
580         internalSetSourceQualityScores(sourceQualityScores);
581         internalSetFingerprintReadingsQualityScores(fingerprintReadingQualityScores);
582     }
583 
584     /**
585      * Constructor.
586      *
587      * @param sourceQualityScores             quality scores corresponding to each
588      *                                        provided located radio source. The
589      *                                        larger the score value the better the
590      *                                        quality of the radio source.
591      * @param fingerprintReadingQualityScores quality scores corresponding to readings
592      *                                        within provided fingerprint. The larger
593      *                                        the score the better the quality of the
594      *                                        reading.
595      * @param sources                         located radio sources used for
596      *                                        lateration.
597      * @param fingerprint                     fingerprint containing readings at an
598      *                                        unknown location for provided located
599      *                                        radio sources.
600      * @throws IllegalArgumentException if either provided sources or fingerprint is null
601      *                                  or the number of provided sources is less than the required minimum.
602      */
603     protected SequentialRobustMixedPositionEstimator(
604             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores,
605             final List<? extends RadioSourceLocated<P>> sources,
606             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint) {
607         this(sources, fingerprint);
608         internalSetSourceQualityScores(sourceQualityScores);
609         internalSetFingerprintReadingsQualityScores(fingerprintReadingQualityScores);
610     }
611 
612     /**
613      * Constructor.
614      *
615      * @param sourceQualityScores             quality scores corresponding to each
616      *                                        provided located radio source. The
617      *                                        larger the score value the better the
618      *                                        quality of the radio source.
619      * @param fingerprintReadingQualityScores quality scores corresponding to readings
620      *                                        within provided fingerprint. The larger
621      *                                        the score the better the quality of the
622      *                                        reading.
623      * @param listener                        listener in charge of handling events.
624      */
625     protected SequentialRobustMixedPositionEstimator(
626             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores,
627             final SequentialRobustMixedPositionEstimatorListener<P> listener) {
628         this(sourceQualityScores, fingerprintReadingQualityScores);
629         this.listener = listener;
630     }
631 
632     /**
633      * Constructor.
634      *
635      * @param sourceQualityScores             quality scores corresponding to each
636      *                                        provided located radio source. The
637      *                                        larger the score value the better the
638      *                                        quality of the radio source.
639      * @param fingerprintReadingQualityScores quality scores corresponding to readings
640      *                                        within provided fingerprint. The larger
641      *                                        the score the better the quality of the
642      *                                        reading.
643      * @param sources                         located radio sources used for
644      *                                        lateration.
645      * @param listener                        listener in charge of handling events.
646      * @throws IllegalArgumentException if provided sources is null or the number of
647      *                                  provided sources is less than the required minimum.
648      */
649     protected SequentialRobustMixedPositionEstimator(
650             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores,
651             final List<? extends RadioSourceLocated<P>> sources,
652             final SequentialRobustMixedPositionEstimatorListener<P> listener) {
653         this(sourceQualityScores, fingerprintReadingQualityScores, sources);
654         this.listener = listener;
655     }
656 
657     /**
658      * Constructor.
659      *
660      * @param sourceQualityScores             quality scores corresponding to each
661      *                                        provided located radio source. The
662      *                                        larger the score value the better the
663      *                                        quality of the radio source.
664      * @param fingerprintReadingQualityScores quality scores corresponding to
665      *                                        readings within provided fingerprint.
666      *                                        The larger the score the better the
667      *                                        quality of the reading.
668      * @param fingerprint                     fingerprint containing readings at an
669      *                                        unknown location for provided located
670      *                                        radio sources.
671      * @param listener                        listener in charge of handling events.
672      * @throws IllegalArgumentException if provided fingerprint is null.
673      */
674     protected SequentialRobustMixedPositionEstimator(
675             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores,
676             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint,
677             final SequentialRobustMixedPositionEstimatorListener<P> listener) {
678         this(sourceQualityScores, fingerprintReadingQualityScores, fingerprint);
679         this.listener = listener;
680     }
681 
682     /**
683      * Constructor.
684      *
685      * @param sourceQualityScores             quality scores corresponding to each
686      *                                        provided located radio source. The
687      *                                        larger the score value the better the
688      *                                        quality of the radio source.
689      * @param fingerprintReadingQualityScores quality scores corresponding to readings
690      *                                        within provided fingerprint. The larger
691      *                                        the score the better the quality of the
692      *                                        reading.
693      * @param sources                         located radio sources used for
694      *                                        lateration.
695      * @param fingerprint                     fingerprint containing readings at an
696      *                                        unknown location for provided located radio
697      *                                        sources.
698      * @param listener                        listener in charge of handling events.
699      * @throws IllegalArgumentException if either provided sources or fingerprint is null
700      *                                  or the number of provided sources is less than the required minimum.
701      */
702     protected SequentialRobustMixedPositionEstimator(
703             final double[] sourceQualityScores, final double[] fingerprintReadingQualityScores,
704             final List<? extends RadioSourceLocated<P>> sources,
705             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint,
706             final SequentialRobustMixedPositionEstimatorListener<P> listener) {
707         this(sourceQualityScores, fingerprintReadingQualityScores, sources, fingerprint);
708         this.listener = listener;
709     }
710 
711     /**
712      * Gets robust method used for robust position estimation using ranging data.
713      *
714      * @return robust method used for robust position estimation using ranging data.
715      */
716     public RobustEstimatorMethod getRangingRobustMethod() {
717         return rangingRobustMethod;
718     }
719 
720     /**
721      * Sets robust method for robust position estimation using ranging data.
722      *
723      * @param rangingRobustMethod robust method used for robust position estimation
724      *                            using ranging data.
725      * @throws LockedException if this instance is locked.
726      */
727     public void setRangingRobustMethod(final RobustEstimatorMethod rangingRobustMethod) throws LockedException {
728         if (isLocked()) {
729             throw new LockedException();
730         }
731 
732         this.rangingRobustMethod = rangingRobustMethod;
733     }
734 
735     /**
736      * Gets robust method used for coarse robust position estimation using RSSI data.
737      *
738      * @return robust method used for coarse robust position estimation using RSSI
739      * data.
740      */
741     public RobustEstimatorMethod getRssiRobustMethod() {
742         return rssiRobustMethod;
743     }
744 
745     /**
746      * Sets robust method used for coarse robust position estimation using RSSI data.
747      *
748      * @param rssiRobustMethod robust method used for coarse robust position estimation
749      *                         using RSSI data.
750      * @throws LockedException if this instance is locked.
751      */
752     public void setRssiRobustMethod(final RobustEstimatorMethod rssiRobustMethod) throws LockedException {
753         if (isLocked()) {
754             throw new LockedException();
755         }
756 
757         this.rssiRobustMethod = rssiRobustMethod;
758     }
759 
760     /**
761      * Indicates whether located radio source position covariance is taken into account
762      * (if available) to determine distance standard deviation for ranging measurements.
763      *
764      * @return true to take into account radio source position covariance during ranging
765      * position estimation, false otherwise.
766      */
767     public boolean isRangingRadioSourcePositionCovarianceUsed() {
768         return useRangingRadioSourcePositionCovariance;
769     }
770 
771     /**
772      * Specifies whether located radio source position covariance is taken into account
773      * (if available) to determine distance standard deviation for ranging measurements.
774      *
775      * @param useRangingRadioSourcePositionCovariance true to take into account radio
776      *                                                source position covariance during
777      *                                                ranging position estimation,
778      *                                                false otherwise.
779      * @throws LockedException if this instance is locked.
780      */
781     public void setRangingRadioSourcePositionCovarianceUsed(
782             final boolean useRangingRadioSourcePositionCovariance) throws LockedException {
783         if (isLocked()) {
784             throw new LockedException();
785         }
786 
787         this.useRangingRadioSourcePositionCovariance =
788                 useRangingRadioSourcePositionCovariance;
789     }
790 
791     /**
792      * Indicates whether located radio source position covariance is taken into account
793      * (if available) to determine distance standard deviation for RSSI measurements.
794      *
795      * @return true to take into account radio source position covariance during RSSI
796      * position estimation, false otherwise.
797      */
798     public boolean isRssiRadioSourcePositionCovarianceUsed() {
799         return useRssiRadioSourcePositionCovariance;
800     }
801 
802     /**
803      * Specifies whether located radio source position covariance is taken into account
804      * (if available) to determine distance standard deviation for RSSI measurements.
805      *
806      * @param useRssiRadioSourcePositionCovariance true to take into account radio
807      *                                             source position covariance during
808      *                                             RSSI position estimation, false
809      *                                             otherwise.
810      * @throws LockedException if this instance is locked.
811      */
812     public void setRssiRadioSourcePositionCovarianceUsed(final boolean useRssiRadioSourcePositionCovariance)
813             throws LockedException {
814         if (isLocked()) {
815             throw new LockedException();
816         }
817 
818         this.useRssiRadioSourcePositionCovariance = useRssiRadioSourcePositionCovariance;
819     }
820 
821     /**
822      * Indicates whether ranging readings are evenly distributed among radio sources
823      * taking into account quality scores of both radio sources and ranging readings.
824      *
825      * @return true if ranging readings are evenly distributed among radio sources,
826      * false otherwise.
827      */
828     public boolean isRangingReadingsEvenlyDistributed() {
829         return evenlyDistributeRangingReadings;
830     }
831 
832     /**
833      * Specifies whether ranging readings are evenly distributed among radio sources
834      * taking into account quality scores of both radio sources and ranging readings.
835      *
836      * @param evenlyDistributeRangingReadings true if ranging readings are evenly
837      *                                        distributed among radio sources, false
838      *                                        otherwise.
839      * @throws LockedException if this instance is locked.
840      */
841     public void setRangingReadingsEvenlyDistributed(final boolean evenlyDistributeRangingReadings)
842             throws LockedException {
843         if (isLocked()) {
844             throw new LockedException();
845         }
846 
847         this.evenlyDistributeRangingReadings = evenlyDistributeRangingReadings;
848     }
849 
850     /**
851      * Gets distance standard deviation fallback value to use when none can be
852      * determined from provided RSSI measurements.
853      *
854      * @return distance standard deviation fallback value to use when none can be
855      * determined from provided RSSI measurements.
856      */
857     public double getRssiFallbackDistanceStandardDeviation() {
858         return rssiFallbackDistanceStandardDeviation;
859     }
860 
861     /**
862      * Sets distance standard deviation fallback value to use when none can be
863      * determined from provided RSSI measurements.
864      *
865      * @param rssiFallbackDistanceStandardDeviation distance standard deviation fallback
866      *                                              value to use when none can be
867      *                                              determined from provided RSSI
868      *                                              measurements.
869      * @throws LockedException if this instance is locked.
870      */
871     public void setRssiFallbackDistanceStandardDeviation(final double rssiFallbackDistanceStandardDeviation)
872             throws LockedException {
873         if (isLocked()) {
874             throw new LockedException();
875         }
876         this.rssiFallbackDistanceStandardDeviation = rssiFallbackDistanceStandardDeviation;
877     }
878 
879     /**
880      * Gets distance standard deviation fallback value to use when none can be
881      * determined from provided ranging measurements.
882      *
883      * @return distance standard deviation fallback value to use when none can be
884      * determined from provided ranging measurements.
885      */
886     public double getRangingFallbackDistanceStandardDeviation() {
887         return rangingFallbackDistanceStandardDeviation;
888     }
889 
890     /**
891      * Sets distance standard deviation fallback value to use when none can be
892      * determined from provided ranging measurements.
893      *
894      * @param rangingFallbackDistanceStandardDeviation distance standard deviation
895      *                                                 fallback value to use when none
896      *                                                 can be determined from provided
897      *                                                 ranging measurements.
898      * @throws LockedException if this instance is locked.
899      */
900     public void setRangingFallbackDistanceStandardDeviation(final double rangingFallbackDistanceStandardDeviation)
901             throws LockedException {
902         if (isLocked()) {
903             throw new LockedException();
904         }
905         this.rangingFallbackDistanceStandardDeviation = rangingFallbackDistanceStandardDeviation;
906     }
907 
908     /**
909      * Indicates whether RSSI readings are evenly distributed among radio sources
910      * taking into account quality scores of both radio sources and RSSI readings.
911      *
912      * @return true if RSSI readings are evenly distributed among radio sources,
913      * false otherwise.
914      */
915     public boolean isRssiReadingsEvenlyDistributed() {
916         return evenlyDistributeRssiReadings;
917     }
918 
919     /**
920      * Specifies whether RSSI readings are evenly distributed among radio sources
921      * taking into account quality scores of both radio sources and RSSI readings.
922      *
923      * @param evenlyDistributeRssiReadings true if RSSI readings are evenly distributed
924      *                                     among radio sources, false otherwise.
925      * @throws LockedException if this instance is locked.
926      */
927     public void setRssiReadingsEvenlyDistributed(final boolean evenlyDistributeRssiReadings) throws LockedException {
928         if (isLocked()) {
929             throw new LockedException();
930         }
931 
932         this.evenlyDistributeRssiReadings = evenlyDistributeRssiReadings;
933     }
934 
935     /**
936      * Gets amount of progress variation before notifying a progress change during
937      * estimation.
938      *
939      * @return amount of progress variation before notifying a progress change during
940      * estimation.
941      */
942     public float getProgressDelta() {
943         return progressDelta;
944     }
945 
946     /**
947      * Sets amount of progress variation before notifying a progress change during
948      * estimation.
949      *
950      * @param progressDelta amount of progress variation before notifying a progress
951      *                      change during estimation.
952      * @throws IllegalArgumentException if progress delta is less than zero or
953      *                                  greater than 1.
954      * @throws LockedException          if this instance is locked.
955      */
956     public void setProgressDelta(final float progressDelta) throws LockedException {
957         if (isLocked()) {
958             throw new LockedException();
959         }
960         if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) {
961             throw new IllegalArgumentException();
962         }
963         this.progressDelta = progressDelta;
964     }
965 
966     /**
967      * Returns amount of confidence expressed as a value between 0.0 and 1.0 (which
968      * is equivalent to 100%) for robust position estimation on ranging data. The
969      * amount of confidence indicates the probability that the estimated result is
970      * correct. Usually this value will be close to 1.0, but not exactly 1.0.
971      *
972      * @return amount of confidence for robust position estimation as a value between
973      * 0.0 and 1.0.
974      */
975     public double getRangingConfidence() {
976         return rangingConfidence;
977     }
978 
979     /**
980      * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which is
981      * equivalent to 100%) for robust position estimation on ranging data. The amount
982      * of confidence indicates the probability that the estimated result is correct.
983      * Usually this value will be close to 1.0, but not exactly 1.0.
984      *
985      * @param rangingConfidence confidence to be set for robust position estimation as
986      *                          a value between 0.0 and 1.0.
987      * @throws IllegalArgumentException if provided value is not between 0.0 and 1.0.
988      * @throws LockedException          if estimator is locked.
989      */
990     public void setRangingConfidence(final double rangingConfidence) throws LockedException {
991         if (isLocked()) {
992             throw new LockedException();
993         }
994         if (rangingConfidence < MIN_CONFIDENCE || rangingConfidence > MAX_CONFIDENCE) {
995             throw new IllegalArgumentException();
996         }
997         this.rangingConfidence = rangingConfidence;
998     }
999 
1000     /**
1001      * Returns amount of confidence expressed as a value between 0.0 and 1.0 (which is
1002      * equivalent to 100%) for robust position estimation on RSSI data. The amount of
1003      * confidence indicates the probability that the estimated result is correct.
1004      * Usually this value will be close to 1.0, but not exactly 1.0.
1005      *
1006      * @return amount of confidence for robust position estimation as a value between
1007      * 0.0 and 1.0.
1008      */
1009     public double getRssiConfidence() {
1010         return rssiConfidence;
1011     }
1012 
1013     /**
1014      * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which is
1015      * equivalent to 100%) for robust position estimation on RSSI data. The amount
1016      * of confidence indicates the probability that the estimated result is correct.
1017      * Usually this value will be close to 1.0, but not exactly 1.0.
1018      *
1019      * @param rssiConfidence amount of confidence for robust position estimation as
1020      *                       a value between 0.0 and 1.0.
1021      * @throws IllegalArgumentException if provided value is not between 0.0 and 1.0.
1022      * @throws LockedException          if estimator is locked.
1023      */
1024     public void setRssiConfidence(final double rssiConfidence) throws LockedException {
1025         if (isLocked()) {
1026             throw new LockedException();
1027         }
1028         if (rssiConfidence < MIN_CONFIDENCE || rssiConfidence > MAX_CONFIDENCE) {
1029             throw new IllegalArgumentException();
1030         }
1031         this.rssiConfidence = rssiConfidence;
1032     }
1033 
1034     /**
1035      * Gets maximum allowed number of iterations for robust ranging position estimation.
1036      * When the maximum number of iterations is exceeded, an approximate result might
1037      * be available for retrieval.
1038      *
1039      * @return maximum allowed number of iterations for position estimation.
1040      */
1041     public int getRangingMaxIterations() {
1042         return rangingMaxIterations;
1043     }
1044 
1045     /**
1046      * Sets maximum allowed number of iterations for robust ranging position
1047      * estimation.
1048      * When the maximum number of iterations is exceeded, an approximate result might
1049      * be available for retrieval.
1050      *
1051      * @param rangingMaxIterations maximum allowed number of iterations to be set for
1052      *                             position estimation.
1053      * @throws IllegalArgumentException if provided value is less than 1.
1054      * @throws LockedException          if estimator is locked.
1055      */
1056     public void setRangingMaxIterations(final int rangingMaxIterations) throws LockedException {
1057         if (isLocked()) {
1058             throw new LockedException();
1059         }
1060         if (rangingMaxIterations < MIN_ITERATIONS) {
1061             throw new IllegalArgumentException();
1062         }
1063         this.rangingMaxIterations = rangingMaxIterations;
1064     }
1065 
1066     /**
1067      * Gets maximum allowed number of iterations for robust RSSI position estimation.
1068      * When the maximum number of iterations is exceeded, an approximate result might
1069      * be available for retrieval.
1070      *
1071      * @return maximum allowed number of iterations for position estimation.
1072      */
1073     public int getRssiMaxIterations() {
1074         return rssiMaxIterations;
1075     }
1076 
1077     /**
1078      * Sets maximum allowed number of iterations for robust RSSI position estimation.
1079      * When the maximum number of iterations is exceeded, an approximate result might
1080      * be available for retrieval.
1081      *
1082      * @param rssiMaxIterations maximum allowed number of iterations to be set for
1083      *                          position estimation.
1084      * @throws IllegalArgumentException if provided value is less than 1.
1085      * @throws LockedException          if estimator is locked.
1086      */
1087     public void setRssiMaxIterations(final int rssiMaxIterations) throws LockedException {
1088         if (isLocked()) {
1089             throw new LockedException();
1090         }
1091         if (rssiMaxIterations < MIN_ITERATIONS) {
1092             throw new IllegalArgumentException();
1093         }
1094         this.rssiMaxIterations = rssiMaxIterations;
1095     }
1096 
1097     /**
1098      * Indicates whether result is refined using all found inliers.
1099      *
1100      * @return true if result is refined, false otherwise.
1101      */
1102     public boolean isResultRefined() {
1103         return refineResult;
1104     }
1105 
1106     /**
1107      * Specifies whether result is refined using all found inliers.
1108      *
1109      * @param refineResult true if result is refined, false otherwise.
1110      * @throws LockedException if this instance is locked.
1111      */
1112     public void setResultRefined(final boolean refineResult) throws LockedException {
1113         if (isLocked()) {
1114             throw new LockedException();
1115         }
1116 
1117         this.refineResult = refineResult;
1118     }
1119 
1120     /**
1121      * Indicates whether covariance must be kept after refining result.
1122      * This setting is only taken into account if result is refined.
1123      *
1124      * @return true if covariance must be kept after refining result, false otherwise.
1125      */
1126     public boolean isCovarianceKept() {
1127         return keepCovariance;
1128     }
1129 
1130     /**
1131      * Specifies whether covariance must be kept after refining result.
1132      * This setting is only taken into account if result is refined.
1133      *
1134      * @param keepCovariance true if covariance must be kept after refining result,
1135      *                       false otherwise.
1136      * @throws LockedException if estimator is locked.
1137      */
1138     public void setCovarianceKept(final boolean keepCovariance) throws LockedException {
1139         if (isLocked()) {
1140             throw new LockedException();
1141         }
1142         this.keepCovariance = keepCovariance;
1143     }
1144 
1145     /**
1146      * Indicates whether a linear solver is used for preliminary solution estimation
1147      * using ranging measurements.
1148      * The result obtained on each preliminary solution might be later refined.
1149      *
1150      * @return true if a linear solver is used for preliminary solution estimation on
1151      * ranging readings.
1152      */
1153     public boolean isRangingLinearSolverUsed() {
1154         return useRangingLinearSolver;
1155     }
1156 
1157     /**
1158      * Specifies whether a linear solver is used for preliminary solution estimation
1159      * using ranging measurements.
1160      * The result obtained on each preliminary solution might be later refined.
1161      *
1162      * @param useRangingLinearSolver true if a linear solver is used for preliminary
1163      *                               solution estimation on ranging readings.
1164      * @throws LockedException if estimator is locked.
1165      */
1166     public void setRangingLinearSolverUsed(final boolean useRangingLinearSolver) throws LockedException {
1167         if (isLocked()) {
1168             throw new LockedException();
1169         }
1170         this.useRangingLinearSolver = useRangingLinearSolver;
1171     }
1172 
1173     /**
1174      * Indicates whether a linear solver is used for preliminary solution estimation
1175      * using RSSI measurements.
1176      * The result obtained on each preliminary solution might be later refined.
1177      *
1178      * @return true if a linear solver is used for preliminary solution estimation on
1179      * RSSI readings.
1180      */
1181     public boolean isRssiLinearSolverUsed() {
1182         return useRssiLinearSolver;
1183     }
1184 
1185     /**
1186      * Specifies whether a linear solver is used for preliminary solution estimation
1187      * using RSSI measurements.
1188      * The result obtained on each preliminary solution might be later refined.
1189      *
1190      * @param useRssiLinearSolver true if a linear solver is used for preliminary
1191      *                            solution estimation on RSSI readings.
1192      * @throws LockedException if estimator is locked.
1193      */
1194     public void setRssiLinearSolverUsed(final boolean useRssiLinearSolver) throws LockedException {
1195         if (isLocked()) {
1196             throw new LockedException();
1197         }
1198         this.useRssiLinearSolver = useRssiLinearSolver;
1199     }
1200 
1201     /**
1202      * Indicates whether an homogeneous linear solver is used either to estimate
1203      * preliminary solutions or an initial solution for preliminary solutions that
1204      * will be later refined on the ranging fine estimation.
1205      *
1206      * @return true to use an homogeneous linear solver for preliminary solutions
1207      * during ranging fine position estimation.
1208      */
1209     public boolean isRangingHomogeneousLinearSolverUsed() {
1210         return useRangingHomogeneousLinearSolver;
1211     }
1212 
1213     /**
1214      * Specifies whether an homogeneous linear solver is used either to estimate
1215      * preliminary solutions or an initial solution for preliminary solutions that
1216      * will be later refined on the ranging fine estimation.
1217      *
1218      * @param useRangingHomogeneousLinearSolver true to use an homogeneous linear
1219      *                                          solver for preliminary solutions during
1220      *                                          ranging fine position estimation.
1221      * @throws LockedException if estimator is locked.
1222      */
1223     public void setRangingHomogeneousLinearSolverUsed(final boolean useRangingHomogeneousLinearSolver)
1224             throws LockedException {
1225         if (isLocked()) {
1226             throw new LockedException();
1227         }
1228         this.useRangingHomogeneousLinearSolver = useRangingHomogeneousLinearSolver;
1229     }
1230 
1231     /**
1232      * Indicates whether an homogeneous linear solver is used either to estimate
1233      * preliminary solutions or an initial solution for preliminary solutions that
1234      * will be later refined on the RSSI coarse estimation.
1235      *
1236      * @return true to use an homogeneous linear solver for preliminary solutions
1237      * during RSSI coarse position estimation.
1238      */
1239     public boolean isRssiHomogeneousLinearSolverUsed() {
1240         return useRssiHomogeneousLinearSolver;
1241     }
1242 
1243     /**
1244      * Specifies whether an homogeneous linear solver is used either to estimate
1245      * preliminary solutions or an initial solution for preliminary solutions that
1246      * will be later refined on the RSSI coarse estimation.
1247      *
1248      * @param useRssiHomogeneousLinearSolver true to use an homogeneous linear
1249      *                                       solver for preliminary solutions during
1250      *                                       RSSI fine position estimation.
1251      * @throws LockedException if estimator is locked.
1252      */
1253     public void setRssiHomogeneousLinearSolverUsed(final boolean useRssiHomogeneousLinearSolver)
1254             throws LockedException {
1255         if (isLocked()) {
1256             throw new LockedException();
1257         }
1258         this.useRssiHomogeneousLinearSolver = useRssiHomogeneousLinearSolver;
1259     }
1260 
1261     /**
1262      * Indicates whether preliminary ranging solutions are refined after an initial
1263      * linear solution is found.
1264      * If no initial preliminary solution is found using a linear solver, a non-linear
1265      * solver will be used regardless of this value using an average solution
1266      * as the initial value to be refined.
1267      *
1268      * @return true if preliminary ranging solutions must be refined after an initial
1269      * linear solution, false otherwise.
1270      */
1271     public boolean isRangingPreliminarySolutionRefined() {
1272         return refineRangingPreliminarySolutions;
1273     }
1274 
1275     /**
1276      * Specifies whether preliminary ranging solutions are refined after an initial
1277      * linear solution is found.
1278      * If no initial preliminary solution is found using a linear solver, a non-linear
1279      * solver will be used regardless of this value using an average solution
1280      * as the initial value to be refined.
1281      *
1282      * @param refineRangingPreliminarySolutions true if preliminary ranging solutions
1283      *                                          must be refined after an initial linear
1284      *                                          solution, false otherwise.
1285      * @throws LockedException if estimator is locked.
1286      */
1287     public void setRangingPreliminarySolutionRefined(final boolean refineRangingPreliminarySolutions)
1288             throws LockedException {
1289         if (isLocked()) {
1290             throw new LockedException();
1291         }
1292         this.refineRangingPreliminarySolutions = refineRangingPreliminarySolutions;
1293     }
1294 
1295     /**
1296      * Indicates whether preliminary RSSI solutions are refined after an initial
1297      * linear solution is found.
1298      * If no initial preliminary solution is found using a linear solver, a non-linear
1299      * solver will be used regardless of this value using an average solution
1300      * as the initial value to be refined.
1301      *
1302      * @return true if preliminary RSSI solutions must be refined after an initial
1303      * linear solution, false otherwise.
1304      */
1305     public boolean isRssiPreliminarySolutionRefined() {
1306         return refineRssiPreliminarySolutions;
1307     }
1308 
1309     /**
1310      * Specifies whether preliminary RSSI solutions are refined after an initial
1311      * linear solution is found.
1312      * If no initial preliminary solution is found using a linear solver, a non-linear
1313      * solver will be used regardless of this value using an average solution
1314      * as the initial value ot be refined.
1315      *
1316      * @param refineRssiPreliminarySolutions true if preliminary RSSI solutions must
1317      *                                       be refined after an initial linear
1318      *                                       solution, false otherwise.
1319      * @throws LockedException if estimator is locked.
1320      */
1321     public void setRssiPreliminarySolutionRefined(final boolean refineRssiPreliminarySolutions) throws LockedException {
1322         if (isLocked()) {
1323             throw new LockedException();
1324         }
1325         this.refineRssiPreliminarySolutions = refineRssiPreliminarySolutions;
1326     }
1327 
1328     /**
1329      * Gets size of subsets to be checked during ranging robust estimation.
1330      *
1331      * @return size of subsets to be checked during ranging robust estimation.
1332      */
1333     public int getRangingPreliminarySubsetSize() {
1334         return rangingPreliminarySubsetSize;
1335     }
1336 
1337     /**
1338      * Sets size of subsets to be checked during ranging robust estimation.
1339      *
1340      * @param rangingPreliminarySubsetSize size of subsets to be checked during
1341      *                                     ranging robust estimation.
1342      * @throws LockedException          if estimator is locked.
1343      * @throws IllegalArgumentException if provided value is less than {@link #getMinRequiredSources()}.
1344      */
1345     public void setRangingPreliminarySubsetSize(final int rangingPreliminarySubsetSize) throws LockedException {
1346         if (isLocked()) {
1347             throw new LockedException();
1348         }
1349         if (rangingPreliminarySubsetSize < getMinRequiredSources()) {
1350             throw new IllegalArgumentException();
1351         }
1352 
1353         this.rangingPreliminarySubsetSize = rangingPreliminarySubsetSize;
1354     }
1355 
1356     /**
1357      * Gets size of subsets to be checked during RSSI robust estimation.
1358      *
1359      * @return size of subsets to be checked during RSSI robust estimation.
1360      */
1361     public int getRssiPreliminarySubsetSize() {
1362         return rssiPreliminarySubsetSize;
1363     }
1364 
1365     /**
1366      * Sets size of subsets to be checked during RSSI robust estimation.
1367      *
1368      * @param rssiPreliminarySubsetSize size of subsets to be checked during
1369      *                                  RSSI robust estimation.
1370      * @throws LockedException          if estimator is locked.
1371      * @throws IllegalArgumentException if provided value is less than {@link #getMinRequiredSources()}.
1372      */
1373     public void setRssiPreliminarySubsetSize(final int rssiPreliminarySubsetSize) throws LockedException {
1374         if (isLocked()) {
1375             throw new LockedException();
1376         }
1377         if (rssiPreliminarySubsetSize < getMinRequiredSources()) {
1378             throw new IllegalArgumentException();
1379         }
1380 
1381         this.rssiPreliminarySubsetSize = rssiPreliminarySubsetSize;
1382     }
1383 
1384     /**
1385      * Gets threshold to determine when samples are inliers or not, used during robust
1386      * fine ranging position estimation.
1387      * If not defined, default threshold will be used.
1388      *
1389      * @return threshold for ranging estimation or null.
1390      */
1391     public Double getRangingThreshold() {
1392         return rangingThreshold;
1393     }
1394 
1395     /**
1396      * Sets threshold to determine when samples are inliers or not, used during robust
1397      * fine ranging position estimation.
1398      * If not defined, default threshold will be used.
1399      *
1400      * @param rangingThreshold threshold for ranging estimation or null.
1401      * @throws LockedException if estimator is locked.
1402      */
1403     public void setRangingThreshold(final Double rangingThreshold) throws LockedException {
1404         if (isLocked()) {
1405             throw new LockedException();
1406         }
1407         this.rangingThreshold = rangingThreshold;
1408     }
1409 
1410     /**
1411      * Gets threshold to determine when samples are inliers or not, used during robust
1412      * coarse RSSI position estimation.
1413      * If not defined, default threshold will be used.
1414      *
1415      * @return threshold for RSSI estimation or null.
1416      */
1417     public Double getRssiThreshold() {
1418         return rssiThreshold;
1419     }
1420 
1421     /**
1422      * Sets threshold to determine when samples are inliers or not, used during robust
1423      * coarse RSSI position estimation.
1424      * If not defined, default threshold will be used.
1425      *
1426      * @param rssiThreshold threshold for RSSI estimation or null.
1427      * @throws LockedException if estimator is locked.
1428      */
1429     public void setRssiThreshold(final Double rssiThreshold) throws LockedException {
1430         if (isLocked()) {
1431             throw new LockedException();
1432         }
1433         this.rssiThreshold = rssiThreshold;
1434     }
1435 
1436     /**
1437      * Gets located radio sources used for lateration.
1438      *
1439      * @return located radio sources used for lateration.
1440      */
1441     public List<RadioSourceLocated<P>> getSources() {
1442         //noinspection unchecked
1443         return (List<RadioSourceLocated<P>>) sources;
1444     }
1445 
1446     /**
1447      * Sets located radio sources used for lateration.
1448      *
1449      * @param sources located radio sources used for lateration.
1450      * @throws LockedException          if estimator is locked.
1451      * @throws IllegalArgumentException if provided value is null or the number of
1452      *                                  provided sources is less than the required
1453      *                                  minimum.
1454      */
1455     public void setSources(final List<? extends RadioSourceLocated<P>> sources) throws LockedException {
1456         if (isLocked()) {
1457             throw new LockedException();
1458         }
1459 
1460         internalSetSources(sources);
1461     }
1462 
1463     /**
1464      * Gets fingerprint containing readings at an unknown location for provided located
1465      * radio sources.
1466      *
1467      * @return fingerprint containing readings at an unknown location for provided
1468      * located radio sources.
1469      */
1470     public Fingerprint<RadioSource, Reading<RadioSource>> getFingerprint() {
1471         //noinspection unchecked
1472         return (Fingerprint<RadioSource, Reading<RadioSource>>) fingerprint;
1473     }
1474 
1475     /**
1476      * Sets fingerprint containing readings at an unknown location for provided
1477      * located radio sources.
1478      *
1479      * @param fingerprint fingerprint containing readings at an unknown location for
1480      *                    provided located radio sources.
1481      * @throws LockedException if estimator is locked.
1482      */
1483     public void setFingerprint(
1484             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint)
1485             throws LockedException {
1486         if (isLocked()) {
1487             throw new LockedException();
1488         }
1489 
1490         internalSetFingerprint(fingerprint);
1491     }
1492 
1493     /**
1494      * Returns quality scores corresponding to each radio source.
1495      * The larger the score value the better the quality of the radio source.
1496      *
1497      * @return quality scores corresponding to each radio source.
1498      */
1499     public double[] getSourceQualityScores() {
1500         return sourceQualityScores;
1501     }
1502 
1503     /**
1504      * Sets quality scores corresponding to each radio source.
1505      * The larger the score value the better the quality of the radio source.
1506      *
1507      * @param sourceQualityScores quality scores corresponding to each radio source.
1508      * @throws LockedException          if this instance is locked.
1509      * @throws IllegalArgumentException if provided quality scores length is smaller
1510      *                                  than minimum required samples.
1511      */
1512     public void setSourceQualityScores(final double[] sourceQualityScores) throws LockedException {
1513         if (isLocked()) {
1514             throw new LockedException();
1515         }
1516         internalSetSourceQualityScores(sourceQualityScores);
1517     }
1518 
1519     /**
1520      * Gets quality scores corresponding to each reading within provided fingerprint.
1521      * The larger the score value the better the quality of the reading.
1522      *
1523      * @return quality scores corresponding to each reading within provided fingerprint.
1524      */
1525     public double[] getFingerprintReadingsQualityScores() {
1526         return fingerprintReadingsQualityScores;
1527     }
1528 
1529     /**
1530      * Sets quality scores corresponding to each reading within provided fingerprint.
1531      * The larger the score value the better the quality of the reading.
1532      *
1533      * @param fingerprintReadingsQualityScores quality scores corresponding to each
1534      *                                         reading within provided fingerprint.
1535      * @throws LockedException          if this instance is locked.
1536      * @throws IllegalArgumentException if provided quality scores length is smaller
1537      *                                  than minimum required samples.
1538      */
1539     public void setFingerprintReadingsQualityScores(final double[] fingerprintReadingsQualityScores)
1540             throws LockedException {
1541         if (isLocked()) {
1542             throw new LockedException();
1543         }
1544         internalSetFingerprintReadingsQualityScores(fingerprintReadingsQualityScores);
1545     }
1546 
1547     /**
1548      * Gets listener to be notified of events raised by this instance.
1549      *
1550      * @return listener to be notified of events raised by this instance.
1551      */
1552     public SequentialRobustMixedPositionEstimatorListener<P> getListener() {
1553         return listener;
1554     }
1555 
1556     /**
1557      * Sets listener to be notified of events raised by this instance.
1558      *
1559      * @param listener listener to be notified of events raised by this instance.
1560      * @throws LockedException if estimator is locked.
1561      */
1562     public void setListener(final SequentialRobustMixedPositionEstimatorListener<P> listener) throws LockedException {
1563         if (isLocked()) {
1564             throw new LockedException();
1565         }
1566         this.listener = listener;
1567     }
1568 
1569     /**
1570      * Gets initial position to use as a starting point to find a new solution.
1571      * This is optional, but if provided, when no linear solvers are used, this is
1572      * taken into account. If linear solvers are used, this is ignored.
1573      *
1574      * @return an initial position.
1575      */
1576     public P getInitialPosition() {
1577         return initialPosition;
1578     }
1579 
1580     /**
1581      * Sets initial position to use as a starting point to find a new solution.
1582      * This is optional, but if provided, when no linear solvers are used, this is
1583      * taken into account. If linear solvers are used, this is ignored.
1584      *
1585      * @param initialPosition an initial position.
1586      * @throws LockedException if estimator is locked.
1587      */
1588     public void setInitialPosition(final P initialPosition) throws LockedException {
1589         if (isLocked()) {
1590             throw new LockedException();
1591         }
1592         this.initialPosition = initialPosition;
1593     }
1594 
1595     /**
1596      * Returns boolean indicating if estimator is locked because estimation is under
1597      * progress.
1598      *
1599      * @return true if estimator is locked, false otherwise.
1600      */
1601     public boolean isLocked() {
1602         return locked;
1603     }
1604 
1605     /**
1606      * Indicates whether this instance is ready to start the estimation.
1607      *
1608      * @return true if this instance is ready, false otherwise.
1609      */
1610     public boolean isReady() {
1611         checkFingerprint(fingerprint);
1612 
1613         final var numSources = sources != null ? sources.size() : 0;
1614         return numSources > getMinRequiredSources() && (rssiEstimatorAvailable || rangingEstimatorAvailable);
1615     }
1616 
1617     /**
1618      * Estimates position based on provided located radio sources and readings of such
1619      * sources at an unknown location.
1620      *
1621      * @return estimated position.
1622      * @throws LockedException          if estimator is locked.
1623      * @throws NotReadyException        if estimator is not ready.
1624      * @throws RobustEstimatorException if estimation fails for some other reason.
1625      */
1626     public P estimate() throws LockedException, NotReadyException, RobustEstimatorException {
1627         if (isLocked()) {
1628             throw new LockedException();
1629         }
1630 
1631         if (!isReady()) {
1632             throw new NotReadyException();
1633         }
1634 
1635         if (rssiEstimatorAvailable) {
1636             buildRssiEstimator();
1637             setupRssiEstimator();
1638 
1639             if (!rssiEstimator.isReady()) {
1640                 throw new NotReadyException();
1641             }
1642         }
1643 
1644         if (rangingEstimatorAvailable) {
1645             buildRangingEstimator();
1646             setupRangingEstimator();
1647 
1648             if (!rangingEstimator.isReady()) {
1649                 throw new NotReadyException();
1650             }
1651         }
1652 
1653         locked = true;
1654         if (listener != null) {
1655             listener.onEstimateStart(this);
1656         }
1657 
1658         var coarsePosition = initialPosition;
1659         if (rssiEstimator != null) {
1660             rssiEstimator.setInitialPosition(initialPosition);
1661 
1662             try {
1663                 // estimate coarse position using RSSI data
1664                 coarsePosition = rssiEstimator.estimate();
1665             } catch (final RobustEstimatorException e) {
1666                 coarsePosition = null;
1667             }
1668         }
1669 
1670         // use coarse position as initial position for ranging estimation
1671         if (rangingEstimator != null) {
1672             rangingEstimator.setInitialPosition(coarsePosition != null ? coarsePosition : initialPosition);
1673         }
1674 
1675         try {
1676             final var result = rangingEstimator != null ? rangingEstimator.estimate() : coarsePosition;
1677 
1678             if (listener != null) {
1679                 listener.onEstimateEnd(this);
1680             }
1681 
1682             return result;
1683         } finally {
1684             locked = false;
1685         }
1686     }
1687 
1688     /**
1689      * Gets data related to inliers found after estimation.
1690      *
1691      * @return data related to inliers found after estimation.
1692      */
1693     public InliersData getInliersData() {
1694         if (rangingEstimator != null) {
1695             return rangingEstimator.getInliersData();
1696         } else {
1697             return rssiEstimator != null ? rssiEstimator.getInliersData() : null;
1698         }
1699     }
1700 
1701     /**
1702      * Gets known positions of radio sources used internally to solve lateration.
1703      *
1704      * @return known positions used internally.
1705      */
1706     public P[] getPositions() {
1707         if (rangingEstimator != null) {
1708             return rangingEstimator.getPositions();
1709         } else {
1710             return rssiEstimator != null ? rssiEstimator.getPositions() : null;
1711         }
1712     }
1713 
1714     /**
1715      * Gets Euclidean distances from known located radio sources to the location of
1716      * provided readings in a fingerprint.
1717      * Distance values are used internally to solve lateration.
1718      *
1719      * @return Euclidean distances used internally.
1720      */
1721     public double[] getDistances() {
1722         if (rangingEstimator != null) {
1723             return rangingEstimator.getDistances();
1724         } else {
1725             return rssiEstimator != null ? rssiEstimator.getDistances() : null;
1726         }
1727     }
1728 
1729     /**
1730      * Gets standard deviation distances from known located radio sources to the
1731      * location of provided readings in a fingerprint.
1732      * Distance standard deviations are used internally to solve lateration.
1733      *
1734      * @return standard deviations used internally.
1735      */
1736     public double[] getDistanceStandardDeviations() {
1737         if (rangingEstimator != null) {
1738             return rangingEstimator.getDistanceStandardDeviations();
1739         } else {
1740             return rssiEstimator != null ? rssiEstimator.getDistanceStandardDeviations() : null;
1741         }
1742     }
1743 
1744     /**
1745      * Gets estimated covariance of estimated position if available.
1746      * This is only available when result has been refined and covariance is kept.
1747      *
1748      * @return estimated covariance or null.
1749      */
1750     public Matrix getCovariance() {
1751         if (rangingEstimator != null) {
1752             return rangingEstimator.getCovariance();
1753         } else {
1754             return rssiEstimator != null ? rssiEstimator.getCovariance() : null;
1755         }
1756     }
1757 
1758     /**
1759      * Gets estimated position.
1760      *
1761      * @return estimated position.
1762      */
1763     public P getEstimatedPosition() {
1764         if (rangingEstimator != null) {
1765             return rangingEstimator.getEstimatedPosition();
1766         } else {
1767             return rssiEstimator != null ? rssiEstimator.getEstimatedPosition() : null;
1768         }
1769     }
1770 
1771     /**
1772      * Gets number of dimensions of provided points.
1773      *
1774      * @return number of dimensions of provided points.
1775      */
1776     public abstract int getNumberOfDimensions();
1777 
1778     /**
1779      * Gets minimum required number of located radio sources to perform lateration.
1780      *
1781      * @return minimum required number of located radio sources to perform
1782      * lateration.
1783      */
1784     public abstract int getMinRequiredSources();
1785 
1786     /**
1787      * Builds ranging internal estimator.
1788      */
1789     protected abstract void buildRangingEstimator();
1790 
1791     /**
1792      * Builds RSSI internal estimator.
1793      */
1794     protected abstract void buildRssiEstimator();
1795 
1796     /**
1797      * Setup ranging internal estimator.
1798      *
1799      * @throws LockedException if estimator is locked.
1800      */
1801     protected void setupRangingEstimator() throws LockedException {
1802         if (fingerprint != null) {
1803             //builds separated ranging readings
1804             final var readings = fingerprint.getReadings();
1805 
1806             final var rangingReadings = new ArrayList<RangingReading<RadioSource>>();
1807 
1808             final var newFingerprintReadingsQualityScores = new double[numRangingReadings];
1809             var i = 0;
1810             var j = 0;
1811             for (final var reading : readings) {
1812                 if (reading instanceof RangingReading) {
1813                     //noinspection unchecked
1814                     rangingReadings.add(
1815                             (RangingReading<RadioSource>) reading);
1816                     newFingerprintReadingsQualityScores[i] = this.fingerprintReadingsQualityScores[j];
1817                     i++;
1818                 } else if (reading instanceof RangingAndRssiReading) {
1819                     //noinspection unchecked
1820                     rangingReadings.add(createRangingReading((RangingAndRssiReading<RadioSource>) reading));
1821                     newFingerprintReadingsQualityScores[i] = this.fingerprintReadingsQualityScores[j];
1822                     i++;
1823                 }
1824                 j++;
1825             }
1826 
1827             final var rangingFingerprint = new RangingFingerprint<>(rangingReadings);
1828 
1829             // set data and configuration on both internal estimators
1830             rangingEstimator.setSources(sources);
1831             rangingEstimator.setFingerprint(rangingFingerprint);
1832             rangingEstimator.setRadioSourcePositionCovarianceUsed(useRangingRadioSourcePositionCovariance);
1833             rangingEstimator.setEvenlyDistributeReadings(evenlyDistributeRangingReadings);
1834             rangingEstimator.setFallbackDistanceStandardDeviation(rangingFallbackDistanceStandardDeviation);
1835             rangingEstimator.setProgressDelta(2.0f * progressDelta);
1836             rangingEstimator.setConfidence(rangingConfidence);
1837             rangingEstimator.setMaxIterations(rangingMaxIterations);
1838             rangingEstimator.setCovarianceKept(keepCovariance);
1839             rangingEstimator.setInitialPosition(initialPosition);
1840             rangingEstimator.setLinearSolverUsed(useRangingLinearSolver);
1841             rangingEstimator.setHomogeneousLinearSolverUsed(useRangingHomogeneousLinearSolver);
1842             rangingEstimator.setPreliminarySolutionRefined(refineRangingPreliminarySolutions);
1843             rangingEstimator.setSourceQualityScores(sourceQualityScores);
1844             rangingEstimator.setFingerprintReadingsQualityScores(newFingerprintReadingsQualityScores);
1845             rangingEstimator.setListener(new RobustRangingPositionEstimatorListener<>() {
1846                 @Override
1847                 public void onEstimateStart(final RobustRangingPositionEstimator<P> estimator) {
1848                     // not used
1849                 }
1850 
1851                 @Override
1852                 public void onEstimateEnd(final RobustRangingPositionEstimator<P> estimator) {
1853                     // not used
1854                 }
1855 
1856                 @Override
1857                 public void onEstimateNextIteration(
1858                         final RobustRangingPositionEstimator<P> estimator, final int iteration) {
1859                     // not used
1860                 }
1861 
1862                 @Override
1863                 public void onEstimateProgressChange(
1864                         final RobustRangingPositionEstimator<P> estimator, final float progress) {
1865                     if (listener != null) {
1866                         final var p = rssiEstimatorAvailable ? 0.5f + 0.5f * progress : progress;
1867                         listener.onEstimateProgressChange(SequentialRobustMixedPositionEstimator.this, p);
1868                     }
1869                 }
1870             });
1871 
1872             rangingEstimator.setPreliminarySubsetSize(
1873                     Math.max(rangingPreliminarySubsetSize, rangingEstimator.getMinRequiredSources()));
1874         }
1875     }
1876 
1877     /**
1878      * Setup RSSI internal estimator.
1879      *
1880      * @throws LockedException if estimator is locked.
1881      */
1882     protected void setupRssiEstimator() throws LockedException {
1883         if (fingerprint != null) {
1884             // builds separated RSSI readings
1885             final var readings = fingerprint.getReadings();
1886 
1887             final var rssiReadings = new ArrayList<RssiReading<RadioSource>>();
1888 
1889             final var newFingerprintReadingsQualityScores = new double[numRssiReadings];
1890             var i = 0;
1891             var j = 0;
1892             for (final var reading : readings) {
1893                 if (reading instanceof RssiReading) {
1894                     //noinspection unchecked
1895                     rssiReadings.add((RssiReading<RadioSource>) reading);
1896                     newFingerprintReadingsQualityScores[i] = this.fingerprintReadingsQualityScores[j];
1897                     i++;
1898                 } else if (reading instanceof RangingAndRssiReading) {
1899                     //noinspection unchecked
1900                     rssiReadings.add(createRssiReading((RangingAndRssiReading<RadioSource>) reading));
1901                     newFingerprintReadingsQualityScores[i] = this.fingerprintReadingsQualityScores[j];
1902                     i++;
1903                 }
1904                 j++;
1905             }
1906 
1907             final var rssiFingerprint = new RssiFingerprint<>(rssiReadings);
1908 
1909             // set data and configuration on both internal estimators
1910             rssiEstimator.setSources(sources);
1911             rssiEstimator.setFingerprint(rssiFingerprint);
1912             rssiEstimator.setRadioSourcePositionCovarianceUsed(useRssiRadioSourcePositionCovariance);
1913             rssiEstimator.setEvenlyDistributeReadings(evenlyDistributeRssiReadings);
1914             rssiEstimator.setFallbackDistanceStandardDeviation(rssiFallbackDistanceStandardDeviation);
1915             rssiEstimator.setProgressDelta(2.0f * progressDelta);
1916             rssiEstimator.setConfidence(rssiConfidence);
1917             rssiEstimator.setMaxIterations(rssiMaxIterations);
1918             rssiEstimator.setResultRefined(refineResult);
1919             rssiEstimator.setCovarianceKept(keepCovariance);
1920             rssiEstimator.setInitialPosition(initialPosition);
1921             rssiEstimator.setLinearSolverUsed(useRssiLinearSolver);
1922             rssiEstimator.setHomogeneousLinearSolverUsed(useRssiHomogeneousLinearSolver);
1923             rssiEstimator.setPreliminarySolutionRefined(refineRssiPreliminarySolutions);
1924             rssiEstimator.setSourceQualityScores(sourceQualityScores);
1925             rssiEstimator.setFingerprintReadingsQualityScores(newFingerprintReadingsQualityScores);
1926             rssiEstimator.setListener(new RobustRssiPositionEstimatorListener<>() {
1927                 @Override
1928                 public void onEstimateStart(final RobustRssiPositionEstimator<P> estimator) {
1929                     // not used
1930                 }
1931 
1932                 @Override
1933                 public void onEstimateEnd(final RobustRssiPositionEstimator<P> estimator) {
1934                     // not used
1935                 }
1936 
1937                 @Override
1938                 public void onEstimateNextIteration(
1939                         final RobustRssiPositionEstimator<P> estimator, final int iteration) {
1940                     // not used
1941                 }
1942 
1943                 @Override
1944                 public void onEstimateProgressChange(
1945                         final RobustRssiPositionEstimator<P> estimator, final float progress) {
1946                     if (listener != null) {
1947                         final var p = rangingEstimatorAvailable ? 0.5f * progress : progress;
1948                         listener.onEstimateProgressChange(SequentialRobustMixedPositionEstimator.this, p);
1949                     }
1950                 }
1951             });
1952 
1953             rssiEstimator.setPreliminarySubsetSize(
1954                     Math.max(rssiPreliminarySubsetSize, rssiEstimator.getMinRequiredSources()));
1955         }
1956     }
1957 
1958     /**
1959      * Internally sets located radio sources used for lateration.
1960      *
1961      * @param sources located radio sources used for lateration.
1962      * @throws IllegalArgumentException if provided value is null or the number of
1963      *                                  provided sources is less than the required minimum.
1964      */
1965     private void internalSetSources(final List<? extends RadioSourceLocated<P>> sources) {
1966         if (sources == null) {
1967             throw new IllegalArgumentException();
1968         }
1969 
1970         if (sources.size() < getMinRequiredSources()) {
1971             throw new IllegalArgumentException();
1972         }
1973 
1974         this.sources = sources;
1975     }
1976 
1977     /**
1978      * Internally sets fingerprint containing readings at an unknown location for
1979      * provided located radio sources.
1980      *
1981      * @param fingerprint fingerprint containing readings at an unknown location for
1982      *                    provided located radio sources.
1983      * @throws IllegalArgumentException if provided value is null.
1984      */
1985     private void internalSetFingerprint(
1986             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint) {
1987         if (fingerprint == null) {
1988             throw new IllegalArgumentException();
1989         }
1990 
1991         this.fingerprint = fingerprint;
1992     }
1993 
1994     /**
1995      * Sets quality scores corresponding to each provided located radio source.
1996      * This method is used internally and does not check whether instance is
1997      * locked or not.
1998      *
1999      * @param sourceQualityScores quality scores to be set.
2000      * @throws IllegalArgumentException if provided quality scores length is
2001      *                                  smaller than 3 samples for 2D or 4 samples for 3D.
2002      */
2003     private void internalSetSourceQualityScores(final double[] sourceQualityScores) {
2004         if (sourceQualityScores == null ||
2005                 sourceQualityScores.length < getMinRequiredSources()) {
2006             throw new IllegalArgumentException();
2007         }
2008 
2009         this.sourceQualityScores = sourceQualityScores;
2010     }
2011 
2012     /**
2013      * Sets quality scores corresponding to each provided reading within provided
2014      * fingerprint.
2015      * This method is used internally and does not check whether instance is locked
2016      * or not.
2017      *
2018      * @param fingerprintReadingsQualityScores quality scores to be set.
2019      * @throws IllegalArgumentException if provided quality scores length is smaller
2020      *                                  than 3 samples for 2D or 4 samples for 3D.
2021      */
2022     private void internalSetFingerprintReadingsQualityScores(final double[] fingerprintReadingsQualityScores) {
2023         if (fingerprintReadingsQualityScores == null
2024                 || fingerprintReadingsQualityScores.length < getMinRequiredSources()) {
2025             throw new IllegalArgumentException();
2026         }
2027 
2028         this.fingerprintReadingsQualityScores = fingerprintReadingsQualityScores;
2029     }
2030 
2031     /**
2032      * Creates a ranging reading from a ranging and RSSI reading.
2033      *
2034      * @param reading input reading to convert from.
2035      * @return a ranging reading containing only the ranging data of input reading.
2036      */
2037     private RangingReading<RadioSource> createRangingReading(
2038             final RangingAndRssiReading<? extends RadioSource> reading) {
2039         return new RangingReading<>(reading.getSource(), reading.getDistance(), reading.getDistanceStandardDeviation(),
2040                 reading.getNumAttemptedMeasurements(), reading.getNumSuccessfulMeasurements());
2041     }
2042 
2043     /**
2044      * Creates an RSSI reading from a ranging and RSSI reading.
2045      *
2046      * @param reading input reading to convert from.
2047      * @return an RSSI reading containing only the RSSI data of input reading.
2048      */
2049     private RssiReading<RadioSource> createRssiReading(final RangingAndRssiReading<? extends RadioSource> reading) {
2050         return new RssiReading<>(reading.getSource(), reading.getRssi(), reading.getRssiStandardDeviation());
2051     }
2052 
2053     /**
2054      * Checks readings within provided fingerprint to determine the amount of available
2055      * ranging or RSSI readings.
2056      *
2057      * @param fingerprint fingerprint to be checked.
2058      */
2059     private void checkFingerprint(
2060             final Fingerprint<? extends RadioSource, ? extends Reading<? extends RadioSource>> fingerprint) {
2061         checkReadings(fingerprint != null ? fingerprint.getReadings() : null);
2062     }
2063 
2064     /**
2065      * Checks number of available ranging readings and number of available RSSI
2066      * readings. Also determines whether position must be estimated using ranging
2067      * data or RSSI data.
2068      *
2069      * @param readings readings to be checked.
2070      */
2071     private void checkReadings(final List<? extends Reading<?>> readings) {
2072         numRssiReadings = 0;
2073         numRangingReadings = 0;
2074         rangingEstimatorAvailable = rssiEstimatorAvailable = false;
2075 
2076         if (readings == null) {
2077             return;
2078         }
2079 
2080         for (final var reading : readings) {
2081             if (reading instanceof RangingReading) {
2082                 numRangingReadings++;
2083             } else if (reading instanceof RssiReading) {
2084                 numRssiReadings++;
2085             } else if (reading instanceof RangingAndRssiReading) {
2086                 numRangingReadings++;
2087                 numRssiReadings++;
2088             }
2089         }
2090 
2091         final var min = getMinRequiredSources();
2092         rangingEstimatorAvailable = numRangingReadings >= min;
2093         rssiEstimatorAvailable = numRssiReadings >= min;
2094     }
2095 }