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.Reading;
26  import com.irurueta.navigation.lateration.NonLinearLeastSquaresLaterationSolver;
27  import com.irurueta.navigation.lateration.RobustLaterationSolver;
28  import com.irurueta.navigation.lateration.RobustLaterationSolverListener;
29  import com.irurueta.numerical.robust.InliersData;
30  import com.irurueta.numerical.robust.RobustEstimatorException;
31  import com.irurueta.numerical.robust.RobustEstimatorMethod;
32  
33  import java.util.ArrayList;
34  import java.util.List;
35  
36  /**
37   * Base class for robust position estimators using located radio sources and their
38   * readings at unknown locations.
39   * These kind of estimators can be used to robustly determine the position of a given
40   * device by getting readings at an unknown location of different radio sources whose
41   * locations are known.
42   * Implementations of this class should be able to detect and discard outliers in order
43   * to find the best solution.
44   *
45   * @param <P> a {@link Point} type.
46   * @param <R> a {@link Reading} type.
47   * @param <L> a {@link RobustPositionEstimatorListener} type.
48   */
49  public abstract class RobustPositionEstimator<P extends Point<?>,
50          R extends Reading<? extends RadioSource>,
51          L extends RobustPositionEstimatorListener<? extends RobustPositionEstimator<?, ?, ?>>> {
52  
53      /**
54       * Default robust estimator method when none is provided.
55       */
56      public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.PROMEDS;
57  
58      /**
59       * Indicates that by default located radio source position covariance is taken
60       * into account (if available) to determine distance standard deviation.
61       */
62      public static final boolean DEFAULT_USE_RADIO_SOURCE_POSITION_COVARIANCE = true;
63  
64      /**
65       * Indicates that by default readings are distributed evenly among radio sources
66       * taking into account quality scores of both radio sources and readings.
67       */
68      public static final boolean DEFAULT_EVENLY_DISTRIBUTE_READINGS = true;
69  
70      /**
71       * Distance standard deviation assumed for provided distances as a fallback when
72       * none can be determined.
73       */
74      public static final double FALLBACK_DISTANCE_STANDARD_DEVIATION =
75              NonLinearLeastSquaresLaterationSolver.DEFAULT_DISTANCE_STANDARD_DEVIATION;
76  
77      /**
78       * Located radio sources  used for lateration.
79       */
80      protected List<? extends RadioSourceLocated<P>> sources;
81  
82      /**
83       * Fingerprint containing readings at an unknown location for provided located
84       * radio sources.
85       */
86      protected Fingerprint<? extends RadioSource, ? extends R> fingerprint;
87  
88      /**
89       * Indicates whether located radio source position covariances must be taken into
90       * account (if available) to determine distance standard deviation.
91       */
92      private boolean useRadioSourcePositionCovariance = DEFAULT_USE_RADIO_SOURCE_POSITION_COVARIANCE;
93  
94      /**
95       * Indicates whether readings are evenly distributed among radio sources
96       * taking into account quality scores of both radio sources and readings.
97       */
98      private boolean evenlyDistributeReadings = DEFAULT_EVENLY_DISTRIBUTE_READINGS;
99  
100     /**
101      * Distance standard deviation fallback value to use when none can be determined
102      * from provided radio sources and fingerprint readings.
103      */
104     private double fallbackDistanceStandardDeviation = FALLBACK_DISTANCE_STANDARD_DEVIATION;
105 
106     /**
107      * Listener to be notified of events raised by this instance.
108      */
109     protected L listener;
110 
111     /**
112      * A robust lateration solver to solve position.
113      */
114     protected RobustLaterationSolver<P> laterationSolver;
115 
116     /**
117      * Listener for the robust lateration solver.
118      */
119     protected RobustLaterationSolverListener<P> trilaterationSolverListener;
120 
121     /**
122      * Size of subsets to be checked during robust estimation.
123      */
124     protected int preliminarySubsetSize;
125 
126     /**
127      * Constructor.
128      */
129     protected RobustPositionEstimator() {
130     }
131 
132     /**
133      * Constructor.
134      *
135      * @param listener listener in charge of handling events.
136      */
137     protected RobustPositionEstimator(final L listener) {
138         this.listener = listener;
139     }
140 
141     /**
142      * Gets located radio sources used for lateration.
143      *
144      * @return located radio sources used for lateration.
145      */
146     public List<RadioSourceLocated<P>> getSources() {
147         //noinspection unchecked
148         return (List<RadioSourceLocated<P>>) sources;
149     }
150 
151     /**
152      * Sets located radio sources used for lateration.
153      *
154      * @param sources located radio sources used for lateration.
155      * @throws LockedException          if estimator is locked.
156      * @throws IllegalArgumentException if provided value is null or the number of
157      *                                  provided sources is less than the required
158      *                                  minimum.
159      */
160     public void setSources(final List<? extends RadioSourceLocated<P>> sources) throws LockedException {
161         if (isLocked()) {
162             throw new LockedException();
163         }
164 
165         internalSetSources(sources);
166     }
167 
168     /**
169      * Gets fingerprint containing readings at an unknown location for provided located
170      * radio sources.
171      *
172      * @return fingerprint containing readings at an unknown location for provided
173      * located radio sources.
174      */
175     public Fingerprint<RadioSource, Reading<RadioSource>> getFingerprint() {
176         //noinspection unchecked
177         return (Fingerprint<RadioSource, Reading<RadioSource>>) fingerprint;
178     }
179 
180     /**
181      * Sets fingerprint containing readings at an unknown location for provided located
182      * radio sources.
183      *
184      * @param fingerprint fingerprint containing readings at an unknown location for
185      *                    provided located radio sources.
186      * @throws LockedException if estimator is locked.
187      */
188     public void setFingerprint(
189             final Fingerprint<? extends RadioSource, ? extends R> fingerprint) throws LockedException {
190         if (isLocked()) {
191             throw new LockedException();
192         }
193 
194         internalSetFingerprint(fingerprint);
195     }
196 
197     /**
198      * Gets listener to be notified of events raised by this instance.
199      *
200      * @return listener to be notified of events raised by this instance.
201      */
202     public L getListener() {
203         return listener;
204     }
205 
206     /**
207      * Sets listener to be notified of events raised by this instance.
208      *
209      * @param listener listener to be notified of events raised by this instance.
210      * @throws LockedException if estimator is locked.
211      */
212     public void setListener(final L listener) throws LockedException {
213         if (isLocked()) {
214             throw new LockedException();
215         }
216         this.listener = listener;
217     }
218 
219     /**
220      * Indicates whether located radio source position covariance must be taken into
221      * account (if available) to determine distance standard deviation.
222      *
223      * @return true to take radio source position covariance into account, false
224      * otherwise.
225      */
226     public boolean isRadioSourcePositionCovarianceUsed() {
227         return useRadioSourcePositionCovariance;
228     }
229 
230     /**
231      * Specifies whether located radio source position covariance must be taken into
232      * account (if available) to determine distance standard deviation.
233      *
234      * @param useRadioSourcePositionCovariance true to take radio source position
235      *                                         covariance into account, false otherwise.
236      * @throws LockedException if estimator is locked.
237      */
238     public void setRadioSourcePositionCovarianceUsed(
239             final boolean useRadioSourcePositionCovariance) throws LockedException {
240         if (isLocked()) {
241             throw new LockedException();
242         }
243         this.useRadioSourcePositionCovariance = useRadioSourcePositionCovariance;
244 
245         buildPositionsDistancesDistanceStandardDeviationsAndQualityScores();
246     }
247 
248     /**
249      * Indicates whether readings are evenly distributed among radio sources taking
250      * into account quality scores of both radio sources and readings.
251      *
252      * @return true if readings are evenly distributed, false otherwise.
253      */
254     public boolean getEvenlyDistributeReadings() {
255         return evenlyDistributeReadings;
256     }
257 
258     /**
259      * Specifies whether readings are evenly distributed among radio sources taking
260      * into account quality scores of both radio sources and readings.
261      *
262      * @param evenlyDistributeReadings true if readings are evenly distributed, false
263      *                                 otherwise.
264      * @throws LockedException if estimator is locked.
265      */
266     public void setEvenlyDistributeReadings(final boolean evenlyDistributeReadings) throws LockedException {
267         if (isLocked()) {
268             throw new LockedException();
269         }
270         this.evenlyDistributeReadings = evenlyDistributeReadings;
271 
272         buildPositionsDistancesDistanceStandardDeviationsAndQualityScores();
273     }
274 
275     /**
276      * Gets distance standard deviation fallback value to use when none can be
277      * determined from provided radio sources and fingerprint readings.
278      *
279      * @return distance standard deviation to use as fallback.
280      */
281     public double getFallbackDistanceStandardDeviation() {
282         return fallbackDistanceStandardDeviation;
283     }
284 
285     /**
286      * Sets distance standard deviation fallback value to use when none can be
287      * determined from provided radio sources and fingerprint readings.
288      *
289      * @param fallbackDistanceStandardDeviation distance standard deviation to use
290      *                                          as fallback.
291      * @throws LockedException if estimator is locked.
292      */
293     public void setFallbackDistanceStandardDeviation(final double fallbackDistanceStandardDeviation)
294             throws LockedException {
295         if (isLocked()) {
296             throw new LockedException();
297         }
298         this.fallbackDistanceStandardDeviation = fallbackDistanceStandardDeviation;
299 
300         buildPositionsDistancesDistanceStandardDeviationsAndQualityScores();
301     }
302 
303     /**
304      * Returns boolean indicating if estimator is locked because estimation is
305      * under progress.
306      *
307      * @return true if estimator is locked, false otherwise.
308      */
309     public boolean isLocked() {
310         return laterationSolver.isLocked();
311     }
312 
313     /**
314      * Returns amount of progress variation before notifying a progress change during
315      * estimation.
316      *
317      * @return amount of progress variation before notifying a progress change during
318      * estimation.
319      */
320     public float getProgressDelta() {
321         return laterationSolver.getProgressDelta();
322     }
323 
324     /**
325      * Sets amount of progress variation before notifying a progress change during
326      * estimation.
327      *
328      * @param progressDelta amount of progress variation before notifying a progress
329      *                      change during estimation.
330      * @throws LockedException          if this instance is locked.
331      * @throws IllegalArgumentException if progress delta is less than zero or greater
332      *                                  than 1.
333      */
334     public void setProgressDelta(final float progressDelta) throws LockedException {
335         laterationSolver.setProgressDelta(progressDelta);
336     }
337 
338     /**
339      * Returns amount of confidence expressed as a value between 0.0 and 1.0 (which is
340      * equivalent to 100%). The amount of confidence indicates the probability that the
341      * estimated result is correct. Usually this value will be close to 1.0, but not
342      * exactly 1.0.
343      *
344      * @return amount of confidence as a value between 0.0 and 1.0.
345      */
346     public double getConfidence() {
347         return laterationSolver.getConfidence();
348     }
349 
350     /**
351      * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which is
352      * equivalent to 100%). The amount of confidence indicates the probability that the
353      * estimated result is correct. Usually this value will be close to 1.0, but not
354      * exactly 1.0.
355      *
356      * @param confidence confidence to be set as a value between 0.0 and 1.0.
357      * @throws LockedException          if this instance is locked.
358      * @throws IllegalArgumentException if provided value is not between 0.0 and 1.0.
359      */
360     public void setConfidence(final double confidence) throws LockedException {
361         laterationSolver.setConfidence(confidence);
362     }
363 
364     /**
365      * Returns maximum allowed number of iterations. If maximum allowed number of
366      * iterations is achieved without converging to a result when calling solve(),
367      * a RobustEstimatorException will be raised.
368      *
369      * @return maximum allowed number of iterations.
370      */
371     public int getMaxIterations() {
372         return laterationSolver.getMaxIterations();
373     }
374 
375     /**
376      * Sets maximum allowed number of iterations. When the maximum number of iterations
377      * is exceeded, result will not be available, however an approximate result will be
378      * available for retrieval.
379      *
380      * @param maxIterations maximum allowed number of iterations to be set.
381      * @throws LockedException          if this instance is locked.
382      * @throws IllegalArgumentException if provided value is less than 1.
383      */
384     public void setMaxIterations(final int maxIterations) throws LockedException {
385         laterationSolver.setMaxIterations(maxIterations);
386     }
387 
388     /**
389      * Indicates whether result must be refined using a non-linear estimator over found
390      * inliers.
391      *
392      * @return true to refine result, false to simply use result found by robust
393      * estimator without further refining.
394      */
395     public boolean isResultRefined() {
396         return laterationSolver.isResultRefined();
397     }
398 
399     /**
400      * Specifies whether result must be refined using a non-linear estimator over found
401      * inliers.
402      *
403      * @param refineResult true to refine result, false to simply use result found by
404      *                     robust estimator without further refining.
405      * @throws LockedException if this instance is locked.
406      */
407     public void setResultRefined(final boolean refineResult) throws LockedException {
408         laterationSolver.setResultRefined(refineResult);
409     }
410 
411     /**
412      * Indicates whether covariance must be kept after refining result.
413      * This setting is only taken into account if result is refined.
414      *
415      * @return true if covariance must be kept after refining result, false otherwise.
416      */
417     public boolean isCovarianceKept() {
418         return laterationSolver.isCovarianceKept();
419     }
420 
421     /**
422      * Specifies whether covariance must be kept after refining result.
423      * This setting is only taken into account if result is refined.
424      *
425      * @param keepCovariance true if covariance must be kept after refining result,
426      *                       false otherwise.
427      * @throws LockedException if this instance is locked.
428      */
429     public void setCovarianceKept(final boolean keepCovariance) throws LockedException {
430         laterationSolver.setCovarianceKept(keepCovariance);
431     }
432 
433     /**
434      * Gets initial position to use as a starting point to find a new solution.
435      * This is optional, but if provided, when no linear solvers are used, this is
436      * taken into account. If linear solvers are used, this is ignored.
437      *
438      * @return an initial position.
439      */
440     public P getInitialPosition() {
441         return laterationSolver.getInitialPosition();
442     }
443 
444     /**
445      * Sets initial position to use as a starting point to find a new solution.
446      * This is optional, but if provided, when no linear solvers are used, this is
447      * taken into account. If linear solvers are used, this is ignored.
448      *
449      * @param initialPosition an initial position.
450      * @throws LockedException if this instance is locked.
451      */
452     public void setInitialPosition(final P initialPosition) throws LockedException {
453         laterationSolver.setInitialPosition(initialPosition);
454     }
455 
456     /**
457      * Indicates whether a linear solver is used or not (either homogeneous or
458      * inhomogeneous) for preliminary solutions.
459      *
460      * @return true if a linear solver is used, false otherwise.
461      */
462     public boolean isLinearSolverUsed() {
463         return laterationSolver.isLinearSolverUsed();
464     }
465 
466     /**
467      * Specifies whether a linear solver is used or not (either homogeneous or
468      * inhomogeneous) for preliminary solutions.
469      *
470      * @param linearSolverUsed true if a linear solver is used, false otherwise.
471      * @throws LockedException if this instance is locked.
472      */
473     public void setLinearSolverUsed(final boolean linearSolverUsed) throws LockedException {
474         laterationSolver.setLinearSolverUsed(linearSolverUsed);
475     }
476 
477     /**
478      * Indicates whether an homogeneous linear solver is used either to estimate
479      * preliminary solutions or an initial solution for preliminary solutions that will
480      * be later refined.
481      *
482      * @return true if homogeneous linear solver is used, false otherwise.
483      */
484     public boolean isHomogeneousLinearSolverUsed() {
485         return laterationSolver.isHomogeneousLinearSolverUsed();
486     }
487 
488     /**
489      * Specifies whether an homogeneous linear solver is used either to estimate
490      * preliminary solutions or an initial solution for preliminary solutions that will
491      * be later refined.
492      *
493      * @param useHomogeneousLinearSolver true if homogeneous linear solver is used,
494      *                                   false otherwise.
495      * @throws LockedException if estimator is locked.
496      */
497     public void setHomogeneousLinearSolverUsed(final boolean useHomogeneousLinearSolver) throws LockedException {
498         laterationSolver.setHomogeneousLinearSolverUsed(useHomogeneousLinearSolver);
499     }
500 
501     /**
502      * Indicates whether preliminary solutions must be refined after an initial linear
503      * solution is found.
504      * If no initial solution is found using a linear solver, a non-linear solver will
505      * be used regardless of this value using an average solution as the initial value
506      * to be refined.
507      *
508      * @return true if preliminary solutions must be refined after an initial linear
509      * solution, false otherwise.
510      */
511     public boolean isPreliminarySolutionRefined() {
512         return laterationSolver.isPreliminarySolutionRefined();
513     }
514 
515     /**
516      * Specifies whether preliminary solutions must be refined after an initial linear
517      * solution is found.
518      * If no initial solution is found using a linear solver, a non-linear solver will
519      * be used regardless of this value using an average solution as the initial value
520      * to be refined.
521      *
522      * @param preliminarySolutionRefined true if preliminary solutions must be refined
523      *                                   after an initial linear solution, false
524      *                                   otherwise.
525      * @throws LockedException if estimator is locked.
526      */
527     public void setPreliminarySolutionRefined(final boolean preliminarySolutionRefined) throws LockedException {
528         laterationSolver.setPreliminarySolutionRefined(preliminarySolutionRefined);
529     }
530 
531     /**
532      * Gets data related to inliers found after estimation.
533      * Inlier data is related to the internal positions and distances used for
534      * solving lateration.
535      *
536      * @return data related to inliers found after estimation.
537      */
538     public InliersData getInliersData() {
539         return laterationSolver.getInliersData();
540     }
541 
542     /**
543      * Gets known positions of radio sources used internally to solve lateration.
544      *
545      * @return known positions used internally.
546      */
547     public P[] getPositions() {
548         return laterationSolver.getPositions();
549     }
550 
551     /**
552      * Gets Euclidean distances from known located radio sources to the location of
553      * provided readings in a fingerprint.
554      * Distance values are used internally to solve lateration.
555      *
556      * @return Euclidean distances used internally.
557      */
558     public double[] getDistances() {
559         return laterationSolver.getDistances();
560     }
561 
562     /**
563      * Gets standard deviation distances from known located radio sources to the
564      * location of provided readings in a fingerprint.
565      * Distance standard deviations are used internally to solve lateration.
566      *
567      * @return standard deviations used internally.
568      */
569     public double[] getDistanceStandardDeviations() {
570         return laterationSolver.getDistanceStandardDeviations();
571     }
572 
573     /**
574      * Indicates whether estimator is ready to find a solution.
575      *
576      * @return true if estimator is ready, false otherwise.
577      */
578     public boolean isReady() {
579         return laterationSolver.isReady();
580     }
581 
582     /**
583      * Returns quality scores corresponding to each radio source.
584      * The larger the score value the better the quality of the sample.
585      * This implementation always returns null.
586      * Subclasses using quality scores must implement proper behavior.
587      *
588      * @return quality scores corresponding to each radio source.
589      */
590     public double[] getSourceQualityScores() {
591         return null;
592     }
593 
594     /**
595      * Sets quality scores corresponding to each radio source.
596      * The larger the score value the better the quality of the radio source.
597      * This implementation makes no action.
598      * Subclasses using quality scores must implement proper behavior.
599      *
600      * @param sourceQualityScores quality scores corresponding to each radio source.
601      * @throws LockedException          if this instance is locked.
602      * @throws IllegalArgumentException if provided quality scores length is smaller
603      *                                  than minimum required samples.
604      */
605     public void setSourceQualityScores(final double[] sourceQualityScores) throws LockedException {
606     }
607 
608     /**
609      * Gets quality scores corresponding to each reading within provided fingerprint.
610      * The larger the score value the better the quality of the reading.
611      * This implementation always returns null.
612      * Subclasses using quality scores must implement proper behavior.
613      *
614      * @return quality scores corresponding to each reading within provided
615      * fingerprint.
616      */
617     public double[] getFingerprintReadingsQualityScores() {
618         return null;
619     }
620 
621     /**
622      * Sets quality scores corresponding to each reading within provided fingerprint.
623      * The larger the score value the better the quality of the reading.
624      * This implementation makes no action.
625      * Subclasses using quality scores must implement proper behavior.
626      *
627      * @param fingerprintReadingsQualityScores quality scores corresponding to each
628      *                                         reading within provided fingerprint.
629      * @throws LockedException          if this instance is locked.
630      * @throws IllegalArgumentException if provided quality scores length is smaller
631      *                                  than minimum required samples.
632      */
633     public void setFingerprintReadingsQualityScores(final double[] fingerprintReadingsQualityScores)
634             throws LockedException {
635     }
636 
637     /**
638      * Gets size of subsets to be checked during robust estimation.
639      * This has to be at least {@link #getMinRequiredSources()}.
640      *
641      * @return size of subsets to be checked during robust estimation.
642      */
643     public int getPreliminarySubsetSize() {
644         return preliminarySubsetSize;
645     }
646 
647     /**
648      * Sets size of subsets to be checked during robust estimation.
649      * This has to be at least {@link #getMinRequiredSources()}.
650      *
651      * @param preliminarySubsetSize size of subsets to be checked during robust estimation.
652      * @throws LockedException          if instance is busy solving the lateration problem.
653      * @throws IllegalArgumentException if provided value is less than {@link #getMinRequiredSources()}.
654      */
655     public void setPreliminarySubsetSize(final int preliminarySubsetSize) throws LockedException {
656         if (isLocked()) {
657             throw new LockedException();
658         }
659         if (preliminarySubsetSize < getMinRequiredSources()) {
660             throw new IllegalArgumentException();
661         }
662 
663         this.preliminarySubsetSize = preliminarySubsetSize;
664 
665         buildPositionsDistancesDistanceStandardDeviationsAndQualityScores();
666     }
667 
668     /**
669      * Gets estimated covariance of estimated position if available.
670      * This is only available when result has been refined and covariance is kept.
671      *
672      * @return estimated covariance or null.
673      */
674     public Matrix getCovariance() {
675         return laterationSolver.getCovariance();
676     }
677 
678     /**
679      * Gets estimated position.
680      *
681      * @return estimated position.
682      */
683     public P getEstimatedPosition() {
684         return laterationSolver.getEstimatedPosition();
685     }
686 
687     /**
688      * Gets number of dimensions of provided points.
689      *
690      * @return number of dimensions of provided points.
691      */
692     public int getNumberOfDimensions() {
693         return laterationSolver.getNumberOfDimensions();
694     }
695 
696     /**
697      * Estimates position based on provided located radio sources and readings of such
698      * sources at an unknown location.
699      *
700      * @return estimated position.
701      * @throws LockedException          if estimator is locked.
702      * @throws NotReadyException        if estimator is not ready.
703      * @throws RobustEstimatorException if estimation fails for some other reason.
704      */
705     public P estimate() throws LockedException, NotReadyException, RobustEstimatorException {
706         laterationSolver.setPreliminarySubsetSize(preliminarySubsetSize);
707         return laterationSolver.solve();
708     }
709 
710     /**
711      * Gets minimum required number of located radio sources to perform lateration.
712      *
713      * @return minimum required number of located radio sources to perform
714      * lateration.
715      */
716     public abstract int getMinRequiredSources();
717 
718     /**
719      * Returns method being used for robust estimation.
720      *
721      * @return method being used for robust estimation.
722      */
723     public abstract RobustEstimatorMethod getMethod();
724 
725     /**
726      * Internally sets located radio sources used for lateration.
727      *
728      * @param sources located radio sources used for lateration.
729      * @throws IllegalArgumentException if provided value is null or the number of
730      *                                  provided sources is less than the required minimum.
731      */
732     @SuppressWarnings("Duplicates")
733     protected void internalSetSources(final List<? extends RadioSourceLocated<P>> sources) {
734         if (sources == null) {
735             throw new IllegalArgumentException();
736         }
737 
738         if (sources.size() < getMinRequiredSources()) {
739             throw new IllegalArgumentException();
740         }
741 
742         this.sources = sources;
743 
744         buildPositionsDistancesDistanceStandardDeviationsAndQualityScores();
745     }
746 
747     /**
748      * Internally sets fingerprint containing readings at an unknown location for
749      * provided located radio sources.
750      *
751      * @param fingerprint fingerprint containing readings at an unknown location for
752      *                    provided located radio sources.
753      * @throws IllegalArgumentException if provided value is null.
754      */
755     protected void internalSetFingerprint(final Fingerprint<? extends RadioSource, ? extends R> fingerprint) {
756         if (fingerprint == null) {
757             throw new IllegalArgumentException();
758         }
759 
760         this.fingerprint = fingerprint;
761 
762         buildPositionsDistancesDistanceStandardDeviationsAndQualityScores();
763     }
764 
765     /**
766      * Sets positions, distances and standard deviations of distances on internal
767      * lateration solver.
768      *
769      * @param positions                  positions to be set.
770      * @param distances                  distances to be set.
771      * @param distanceStandardDeviations standard deviations of distances to be set.
772      * @param distanceQualityScores      distance quality scores or null if not required.
773      */
774     protected abstract void setPositionsDistancesDistanceStandardDeviationsAndQualityScores(
775             final List<P> positions, List<Double> distances, final List<Double> distanceStandardDeviations,
776             final List<Double> distanceQualityScores);
777 
778     /**
779      * Builds positions, distances, standard deviation of distances and quality scores
780      * for the internal lateration solver.
781      */
782     @SuppressWarnings("Duplicates")
783     protected void buildPositionsDistancesDistanceStandardDeviationsAndQualityScores() {
784         if (laterationSolver == null) {
785             return;
786         }
787 
788         final var min = getPreliminarySubsetSize();
789         if (sources == null || fingerprint == null || sources.size() < min || fingerprint.getReadings() == null
790                 || fingerprint.getReadings().size() < min) {
791             return;
792         }
793 
794         final var positions = new ArrayList<P>();
795         final var distances = new ArrayList<Double>();
796         final var distanceStandardDeviations = new ArrayList<Double>();
797 
798         var sourceQualityScores = getSourceQualityScores();
799         var fingerprintReadingsQualityScores = getFingerprintReadingsQualityScores();
800 
801         if (evenlyDistributeReadings) {
802             // distribute evenly by modifying the relative values of quality scores
803             if (sourceQualityScores == null) {
804                 sourceQualityScores = new double[sources.size()];
805             }
806             if (fingerprintReadingsQualityScores == null) {
807                 fingerprintReadingsQualityScores = new double[fingerprint.getReadings().size()];
808             }
809 
810             final var sorter = new ReadingSorter<P, R>(sources, fingerprint, sourceQualityScores,
811                     fingerprintReadingsQualityScores);
812             sorter.sort();
813 
814             final var sortedSources = sorter.getSortedSourcesAndReadings();
815 
816             var j = 0;
817             var k = 0;
818             boolean finished;
819             do {
820                 var i = 0;
821                 finished = true;
822                 for (final var sortedSource : sortedSources) {
823                     sourceQualityScores[sortedSource.position] = i;
824                     i--;
825 
826                     final var sortedReadings = sortedSource.readingsWithQualityScores;
827                     if (k < sortedReadings.size()) {
828                         finished = false;
829                         ReadingSorter.ReadingWithQualityScore<R> sortedReading = sortedReadings.get(k);
830 
831                         fingerprintReadingsQualityScores[sortedReading.position] = j;
832                         j--;
833                     }
834                 }
835                 k++;
836             } while (!finished);
837         }
838 
839         List<Double> distanceQualityScores = null;
840         if (sourceQualityScores != null || fingerprintReadingsQualityScores != null) {
841             distanceQualityScores = new ArrayList<>();
842         }
843         PositionEstimatorHelper.buildPositionsDistancesDistanceStandardDeviationsAndQualityScores(
844                 sources, fingerprint, sourceQualityScores, fingerprintReadingsQualityScores,
845                 isRadioSourcePositionCovarianceUsed(), getFallbackDistanceStandardDeviation(), positions, distances,
846                 distanceStandardDeviations, distanceQualityScores);
847 
848         setPositionsDistancesDistanceStandardDeviationsAndQualityScores(positions, distances,
849                 distanceStandardDeviations, distanceQualityScores);
850     }
851 }