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 }