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 }