View Javadoc
1   /*
2    * Copyright (C) 2018 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.radiosource;
17  
18  import com.irurueta.geometry.Point2D;
19  import com.irurueta.navigation.LockedException;
20  import com.irurueta.navigation.NotReadyException;
21  import com.irurueta.navigation.indoor.RadioSource;
22  import com.irurueta.navigation.indoor.RssiReadingLocated;
23  import com.irurueta.numerical.robust.RANSACRobustEstimator;
24  import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
25  import com.irurueta.numerical.robust.RobustEstimator;
26  import com.irurueta.numerical.robust.RobustEstimatorException;
27  import com.irurueta.numerical.robust.RobustEstimatorMethod;
28  
29  import java.util.List;
30  
31  /**
32   * Robustly estimate 2D position, transmitted power and path-loss exponent of a radio source
33   * (e.g. Wi-Fi access point or bluetooth beacon), by discarding outliers using RANSAC
34   * algorithm and assuming that the radio source emits isotropically following the
35   * expression below:
36   * Pr = Pt*Gt*Gr*lambda^2 / (4*pi*d)^2,
37   * where Pr is the received power (expressed in mW),
38   * Gt is the Gain of the transmission antenna
39   * Gr is the Gain of the receiver antenna
40   * d is the distance between emitter and receiver
41   * and lambda is the wavelength and is equal to: lambda = c / f,
42   * where c is the speed of light
43   * and f is the carrier frequency of the radio signal.
44   * Because usually information about the antenna of the radio source cannot be
45   * retrieved (because many measurements are made on unknown devices where
46   * physical access is not possible), this implementation will estimate the
47   * equivalent transmitted power as: Pte = Pt * Gt * Gr.
48   * If RssiReadings contain RSSI standard deviations, those values will be used,
49   * otherwise it will be assumed an RSSI standard deviation of 1 dB.
50   * Implementations of this class should be able to detect and discard outliers in
51   * order to find the best solution.
52   * <p>
53   * IMPORTANT: When using this class estimation can be done using a
54   * combination of radio source position, transmitted power and path loss
55   * exponent. However enabling all three estimations usually achieves
56   * inaccurate results. When using this class, estimation must be of at least
57   * one parameter (position, transmitted power or path loss exponent) when
58   * initial values are provided for the other two, and at most it should consist
59   * of two parameters (either position and transmitted power, position and
60   * path loss exponent or transmitted power and path loss exponent), providing an
61   * initial value for the remaining parameter.
62   *
63   * @param <S> a {@link RadioSource} type.
64   */
65  @SuppressWarnings("Duplicates")
66  public class RANSACRobustRssiRadioSourceEstimator2D<S extends RadioSource> extends RobustRssiRadioSourceEstimator2D<S> {
67  
68      /**
69       * Constant defining default threshold on received power (RSSI) expressed in
70       * dBm's.
71       */
72      public static final double DEFAULT_THRESHOLD = 0.1;
73  
74      /**
75       * Minimum value that can be set as threshold.
76       * Threshold must be strictly greater than 0.0.s
77       */
78      public static final double MIN_THRESHOLD = 0.0;
79  
80      /**
81       * Indicates that by default inliers will only be computed but not kept.
82       */
83      public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
84  
85      /**
86       * Indicates that by default residuals will only be computed but not kept.
87       */
88      public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
89  
90      /**
91       * Threshold to determine whether samples are inliers or not when testing possible solutions.
92       * The threshold refers to the amount of error on received power (RSSI) expressed
93       * in dBm's between received value that should have been received on estimated
94       * iso-tropical model and actual measured value.
95       */
96      private double threshold = DEFAULT_THRESHOLD;
97  
98      /**
99       * Indicates whether inliers must be computed and kept.
100      */
101     private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
102 
103     /**
104      * Indicates whether residuals must be computed and kept.
105      */
106     private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
107 
108     /**
109      * Constructor.
110      */
111     public RANSACRobustRssiRadioSourceEstimator2D() {
112         super();
113     }
114 
115     /**
116      * Constructor.
117      * Sets signal readings belonging to the same radio source.
118      *
119      * @param readings signal readings belonging to the same radio source.
120      * @throws IllegalArgumentException if readings are not valid.
121      */
122     public RANSACRobustRssiRadioSourceEstimator2D(final List<? extends RssiReadingLocated<S, Point2D>> readings) {
123         super(readings);
124     }
125 
126     /**
127      * Constructor.
128      *
129      * @param listener listener in charge of attending events raised by this instance.
130      */
131     public RANSACRobustRssiRadioSourceEstimator2D(final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
132         super(listener);
133     }
134 
135     /**
136      * Constructor.
137      * Sets signal readings belonging to the same radio source.
138      *
139      * @param readings signal readings belonging to the same radio source.
140      * @param listener listener in charge of attending events raised by this instance.
141      * @throws IllegalArgumentException if readings are not valid.
142      */
143     public RANSACRobustRssiRadioSourceEstimator2D(
144             final List<? extends RssiReadingLocated<S, Point2D>> readings,
145             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
146         super(readings, listener);
147     }
148 
149     /**
150      * Constructor.
151      * Sets signal readings belonging to the same radio source.
152      *
153      * @param readings        signal readings belonging to the same radio source.
154      * @param initialPosition initial position to start the estimation of radio
155      *                        source position.
156      * @throws IllegalArgumentException if readings are not valid.
157      */
158     public RANSACRobustRssiRadioSourceEstimator2D(
159             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition) {
160         super(readings, initialPosition);
161     }
162 
163     /**
164      * Constructor.
165      *
166      * @param initialPosition initial position to start the estimation of radio
167      *                        source position.
168      */
169     public RANSACRobustRssiRadioSourceEstimator2D(final Point2D initialPosition) {
170         super(initialPosition);
171     }
172 
173     /**
174      * Constructor.
175      *
176      * @param initialPosition initial position to start the estimation of radio
177      *                        source position.
178      * @param listener        listener in charge of attending events raised by this instance.
179      */
180     public RANSACRobustRssiRadioSourceEstimator2D(
181             final Point2D initialPosition, final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
182         super(initialPosition, listener);
183     }
184 
185     /**
186      * Constructor.
187      * Sets signal readings belonging to the same radio source.
188      *
189      * @param readings        signal readings belonging to the same radio source.
190      * @param initialPosition initial position to start the estimation of radio
191      *                        source position.
192      * @param listener        listener in charge of attending events raised by this instance.
193      * @throws IllegalArgumentException if readings are not valid.
194      */
195     public RANSACRobustRssiRadioSourceEstimator2D(
196             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
197             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
198         super(readings, initialPosition, listener);
199     }
200 
201     /**
202      * Constructor.
203      *
204      * @param initialTransmittedPowerdBm initial transmitted power to start the
205      *                                   estimation of radio source transmitted power
206      *                                   (expressed in dBm's)
207      */
208     public RANSACRobustRssiRadioSourceEstimator2D(final Double initialTransmittedPowerdBm) {
209         super(initialTransmittedPowerdBm);
210     }
211 
212     /**
213      * Constructor.
214      * Sets signal readings belonging to the same radio source.
215      *
216      * @param readings                   signal readings belonging to the same radio source.
217      * @param initialTransmittedPowerdBm initial transmitted power to start the
218      *                                   estimation of radio source transmitted power
219      *                                   (expressed in dBm's)
220      * @throws IllegalArgumentException if readings are not valid.
221      */
222     public RANSACRobustRssiRadioSourceEstimator2D(
223             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Double initialTransmittedPowerdBm) {
224         super(readings, initialTransmittedPowerdBm);
225     }
226 
227     /**
228      * Constructor.
229      *
230      * @param initialTransmittedPowerdBm initial transmitted power to start the
231      *                                   estimation of radio source transmitted power
232      *                                   (expressed in dBm's)
233      * @param listener                   listener in charge of attending events raised by this instance.
234      */
235     public RANSACRobustRssiRadioSourceEstimator2D(
236             final Double initialTransmittedPowerdBm,
237             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
238         super(initialTransmittedPowerdBm, listener);
239     }
240 
241     /**
242      * Constructor.
243      * Sets signal readings belonging to the same radio source.
244      *
245      * @param readings                   signal readings belonging to the same radio source.
246      * @param initialTransmittedPowerdBm initial transmitted power to start the
247      *                                   estimation of radio source transmitted power
248      *                                   (expressed in dBm's)
249      * @param listener                   listener in charge of attending events raised by this instance.
250      * @throws IllegalArgumentException if readings are not valid.
251      */
252     public RANSACRobustRssiRadioSourceEstimator2D(
253             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Double initialTransmittedPowerdBm,
254             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
255         super(readings, initialTransmittedPowerdBm, listener);
256     }
257 
258     /**
259      * Constructor.
260      * Sets signal readings belonging to the same radio source.
261      *
262      * @param readings                   signal readings belonging to the same radio source.
263      * @param initialPosition            initial position to start the estimation of radio
264      *                                   source position.
265      * @param initialTransmittedPowerdBm initial transmitted power to start the
266      *                                   estimation of radio source transmitted power
267      *                                   (expressed in dBm's).
268      * @throws IllegalArgumentException if readings are not valid.
269      */
270     public RANSACRobustRssiRadioSourceEstimator2D(
271             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
272             final Double initialTransmittedPowerdBm) {
273         super(readings, initialPosition, initialTransmittedPowerdBm);
274     }
275 
276     /**
277      * Constructor.
278      *
279      * @param initialPosition            initial position to start the estimation of radio
280      *                                   source position.
281      * @param initialTransmittedPowerdBm initial transmitted power to start the
282      *                                   estimation of radio source transmitted power
283      *                                   (expressed in dBm's).
284      */
285     public RANSACRobustRssiRadioSourceEstimator2D(
286             final Point2D initialPosition, final Double initialTransmittedPowerdBm) {
287         super(initialPosition, initialTransmittedPowerdBm);
288     }
289 
290     /**
291      * Constructor.
292      *
293      * @param initialPosition            initial position to start the estimation of radio
294      *                                   source position.
295      * @param initialTransmittedPowerdBm initial transmitted power to start the
296      *                                   estimation of radio source transmitted power
297      *                                   (expressed in dBm's).
298      * @param listener                   in charge of attending events raised by this instance.
299      */
300     public RANSACRobustRssiRadioSourceEstimator2D(
301             final Point2D initialPosition, final Double initialTransmittedPowerdBm,
302             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
303         super(initialPosition, initialTransmittedPowerdBm, listener);
304     }
305 
306     /**
307      * Constructor.
308      * Sets signal readings belonging to the same radio source.
309      *
310      * @param readings                   signal readings belonging to the same radio source.
311      * @param initialPosition            initial position to start the estimation of radio
312      *                                   source position.
313      * @param initialTransmittedPowerdBm initial transmitted power to start the
314      *                                   estimation of radio source transmitted power
315      *                                   (expressed in dBm's).
316      * @param listener                   listener in charge of attending events raised by this instance.
317      * @throws IllegalArgumentException if readings are not valid.
318      */
319     public RANSACRobustRssiRadioSourceEstimator2D(
320             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
321             final Double initialTransmittedPowerdBm,
322             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
323         super(readings, initialPosition, initialTransmittedPowerdBm, listener);
324     }
325 
326     /**
327      * Constructor.
328      * Sets signal readings belonging to the same radio source.
329      *
330      * @param readings                   signal readings belonging to the same radio source.
331      * @param initialPosition            initial position to start the estimation of radio
332      *                                   source position.
333      * @param initialTransmittedPowerdBm initial transmitted power to start the
334      *                                   estimation of radio source transmitted power
335      *                                   (expressed in dBm's).
336      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
337      * @throws IllegalArgumentException if readings are not valid.
338      */
339     public RANSACRobustRssiRadioSourceEstimator2D(
340             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
341             final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
342         super(readings, initialPosition, initialTransmittedPowerdBm, initialPathLossExponent);
343     }
344 
345     /**
346      * Constructor.
347      *
348      * @param initialPosition            initial position to start the estimation of radio
349      *                                   source position.
350      * @param initialTransmittedPowerdBm initial transmitted power to start the
351      *                                   estimation of radio source transmitted power
352      *                                   (expressed in dBm's).
353      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
354      */
355     public RANSACRobustRssiRadioSourceEstimator2D(
356             final Point2D initialPosition, final Double initialTransmittedPowerdBm,
357             final double initialPathLossExponent) {
358         super(initialPosition, initialTransmittedPowerdBm, initialPathLossExponent);
359     }
360 
361     /**
362      * Constructor.
363      *
364      * @param initialPosition            initial position to start the estimation of radio
365      *                                   source position.
366      * @param initialTransmittedPowerdBm initial transmitted power to start the
367      *                                   estimation of radio source transmitted power
368      *                                   (expressed in dBm's).
369      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
370      * @param listener                   listener in charge of attending events raised by this instance.
371      */
372     public RANSACRobustRssiRadioSourceEstimator2D(
373             final Point2D initialPosition, final Double initialTransmittedPowerdBm,
374             final double initialPathLossExponent, final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
375         super(initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
376     }
377 
378     /**
379      * Constructor.
380      * Sets signal readings belonging to the same radio source.
381      *
382      * @param readings                   signal readings belonging to the same radio source.
383      * @param initialPosition            initial position to start the estimation of radio
384      *                                   source position.
385      * @param initialTransmittedPowerdBm initial transmitted power to start the
386      *                                   estimation of radio source transmitted power
387      *                                   (expressed in dBm's).
388      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
389      * @param listener                   listener in charge of attending events raised by this instance.
390      * @throws IllegalArgumentException if readings are not valid.
391      */
392     public RANSACRobustRssiRadioSourceEstimator2D(
393             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
394             final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
395             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
396         super(readings, initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
397     }
398 
399     /**
400      * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
401      * The threshold refers to the amount of error on received power (RSSI) expressed
402      * in dBm's between received value that should have been received on estimated
403      * iso-tropical model and actual measured value.
404      *
405      * @return threshold to determine whether samples are inliers or not.
406      */
407     public double getThreshold() {
408         return threshold;
409     }
410 
411     /**
412      * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
413      * The threshold refers to the amount of error on received power (RSSI) expressed
414      * in dBm's between received value that should have been received on estimated
415      * iso-tropical model and actual measured value.
416      *
417      * @param threshold threshold to determine whether samples are inliers or not.
418      * @throws IllegalArgumentException if provided value is equal or less than zero.
419      * @throws LockedException          if this estimator is locked.
420      */
421     public void setThreshold(final double threshold) throws LockedException {
422         if (isLocked()) {
423             throw new LockedException();
424         }
425         if (threshold <= MIN_THRESHOLD) {
426             throw new IllegalArgumentException();
427         }
428         this.threshold = threshold;
429     }
430 
431 
432     /**
433      * Indicates whether inliers must be computed and kept.
434      *
435      * @return true if inliers must be computed and kept, false if inliers
436      * only need to be computed but not kept.
437      */
438     public boolean isComputeAndKeepInliersEnabled() {
439         return computeAndKeepInliers;
440     }
441 
442     /**
443      * Specifies whether inliers must be computed and kept.
444      *
445      * @param computeAndKeepInliers true if inliers must be computed and kept,
446      *                              false if inliers only need to be computed but not kept.
447      * @throws LockedException if this solver is locked.
448      */
449     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
450         if (isLocked()) {
451             throw new LockedException();
452         }
453         this.computeAndKeepInliers = computeAndKeepInliers;
454     }
455 
456     /**
457      * Indicates whether residuals must be computed and kept.
458      *
459      * @return true if residuals must be computed and kept, false if residuals
460      * only need to be computed but not kept.
461      */
462     public boolean isComputeAndKeepResidualsEnabled() {
463         return computeAndKeepResiduals;
464     }
465 
466     /**
467      * Specifies whether residuals must be computed and kept.
468      *
469      * @param computeAndKeepResiduals true if residuals must be computed and kept,
470      *                                false if residuals only need to be computed but not kept.
471      * @throws LockedException if this solver is locked.
472      */
473     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
474         if (isLocked()) {
475             throw new LockedException();
476         }
477         this.computeAndKeepResiduals = computeAndKeepResiduals;
478     }
479 
480     /**
481      * Robustly estimates position, transmitted power and path-loss exponent for a
482      * radio source.
483      *
484      * @throws LockedException          if instance is busy during estimation.
485      * @throws NotReadyException        if estimator is not ready.
486      * @throws RobustEstimatorException if estimation fails for any reason
487      *                                  (i.e. numerical instability, no solution available, etc).
488      */
489     @Override
490     public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
491         if (isLocked()) {
492             throw new LockedException();
493         }
494         if (!isReady()) {
495             throw new NotReadyException();
496         }
497 
498         final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Solution<Point2D>>() {
499             @Override
500             public double getThreshold() {
501                 return threshold;
502             }
503 
504             @Override
505             public int getTotalSamples() {
506                 return readings.size();
507             }
508 
509             @Override
510             public int getSubsetSize() {
511                 return Math.max(preliminarySubsetSize, getMinReadings());
512             }
513 
514             @Override
515             public void estimatePreliminarSolutions(
516                     final int[] samplesIndices, final List<Solution<Point2D>> solutions) {
517                 solvePreliminarySolutions(samplesIndices, solutions);
518             }
519 
520             @Override
521             public double computeResidual(final Solution<Point2D> currentEstimation, final int i) {
522                 return residual(currentEstimation, i);
523             }
524 
525             @Override
526             public boolean isReady() {
527                 return RANSACRobustRssiRadioSourceEstimator2D.this.isReady();
528             }
529 
530             @Override
531             public void onEstimateStart(final RobustEstimator<Solution<Point2D>> estimator) {
532                 // no action needed
533             }
534 
535             @Override
536             public void onEstimateEnd(final RobustEstimator<Solution<Point2D>> estimator) {
537                 // no action needed
538             }
539 
540             @Override
541             public void onEstimateNextIteration(
542                     final RobustEstimator<Solution<Point2D>> estimator, final int iteration) {
543                 if (listener != null) {
544                     listener.onEstimateNextIteration(
545                             RANSACRobustRssiRadioSourceEstimator2D.this, iteration);
546                 }
547             }
548 
549             @Override
550             public void onEstimateProgressChange(
551                     final RobustEstimator<Solution<Point2D>> estimator, final float progress) {
552                 if (listener != null) {
553                     listener.onEstimateProgressChange(
554                             RANSACRobustRssiRadioSourceEstimator2D.this, progress);
555                 }
556             }
557         });
558 
559         try {
560             locked = true;
561 
562             if (listener != null) {
563                 listener.onEstimateStart(this);
564             }
565 
566             inliersData = null;
567             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
568             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
569             innerEstimator.setConfidence(confidence);
570             innerEstimator.setMaxIterations(maxIterations);
571             innerEstimator.setProgressDelta(progressDelta);
572             final var result = innerEstimator.estimate();
573             inliersData = innerEstimator.getInliersData();
574             attemptRefine(result);
575 
576             if (listener != null) {
577                 listener.onEstimateEnd(this);
578             }
579 
580         } catch (final com.irurueta.numerical.LockedException e) {
581             throw new LockedException(e);
582         } catch (final com.irurueta.numerical.NotReadyException e) {
583             throw new NotReadyException(e);
584         } finally {
585             locked = false;
586         }
587     }
588 
589     /**
590      * Returns method being used for robust estimation.
591      *
592      * @return method being used for robust estimation.
593      */
594     @Override
595     public RobustEstimatorMethod getMethod() {
596         return RobustEstimatorMethod.RANSAC;
597     }
598 }