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.Point3D;
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 3D 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 access point 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 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 RANSACRobustRssiRadioSourceEstimator3D<S extends RadioSource> extends RobustRssiRadioSourceEstimator3D<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 RANSACRobustRssiRadioSourceEstimator3D() {
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 RANSACRobustRssiRadioSourceEstimator3D(final List<? extends RssiReadingLocated<S, Point3D>> 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 RANSACRobustRssiRadioSourceEstimator3D(final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
144 final List<? extends RssiReadingLocated<S, Point3D>> readings,
145 final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
159 final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D 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 RANSACRobustRssiRadioSourceEstimator3D(final Point3D 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 RANSACRobustRssiRadioSourceEstimator3D(
181 final Point3D initialPosition, final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
196 final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
197 final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(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 RANSACRobustRssiRadioSourceEstimator3D(
223 final List<? extends RssiReadingLocated<S, Point3D>> 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 RANSACRobustRssiRadioSourceEstimator3D(
236 final Double initialTransmittedPowerdBm,
237 final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
253 final List<? extends RssiReadingLocated<S, Point3D>> readings, final Double initialTransmittedPowerdBm,
254 final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
271 final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D 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 RANSACRobustRssiRadioSourceEstimator3D(
286 final Point3D 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 RANSACRobustRssiRadioSourceEstimator3D(
301 final Point3D initialPosition, final Double initialTransmittedPowerdBm,
302 final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
320 final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
321 final Double initialTransmittedPowerdBm,
322 final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
340 final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D 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 RANSACRobustRssiRadioSourceEstimator3D(
356 final Point3D 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 RANSACRobustRssiRadioSourceEstimator3D(
373 final Point3D initialPosition, final Double initialTransmittedPowerdBm,
374 final double initialPathLossExponent, final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 RANSACRobustRssiRadioSourceEstimator3D(
393 final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
394 final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
395 final RobustRssiRadioSourceEstimatorListener<S, Point3D> 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 * Indicates whether inliers must be computed and kept.
433 *
434 * @return true if inliers must be computed and kept, false if inliers
435 * only need to be computed but not kept.
436 */
437 public boolean isComputeAndKeepInliersEnabled() {
438 return computeAndKeepInliers;
439 }
440
441 /**
442 * Specifies whether inliers must be computed and kept.
443 *
444 * @param computeAndKeepInliers true if inliers must be computed and kept,
445 * false if inliers only need to be computed but not kept.
446 * @throws LockedException if this solver is locked.
447 */
448 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
449 if (isLocked()) {
450 throw new LockedException();
451 }
452 this.computeAndKeepInliers = computeAndKeepInliers;
453 }
454
455 /**
456 * Indicates whether residuals must be computed and kept.
457 *
458 * @return true if residuals must be computed and kept, false if residuals
459 * only need to be computed but not kept.
460 */
461 public boolean isComputeAndKeepResidualsEnabled() {
462 return computeAndKeepResiduals;
463 }
464
465 /**
466 * Specifies whether residuals must be computed and kept.
467 *
468 * @param computeAndKeepResiduals true if residuals must be computed and kept,
469 * false if residuals only need to be computed but not kept.
470 * @throws LockedException if this solver is locked.
471 */
472 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
473 if (isLocked()) {
474 throw new LockedException();
475 }
476 this.computeAndKeepResiduals = computeAndKeepResiduals;
477 }
478
479 /**
480 * Robustly estimates position, transmitted power and path-loss exponent for a
481 * radio source.
482 *
483 * @throws LockedException if instance is busy during estimation.
484 * @throws NotReadyException if estimator is not ready.
485 * @throws RobustEstimatorException if estimation fails for any reason
486 * (i.e. numerical instability, no solution available, etc).
487 */
488 @Override
489 public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
490 if (isLocked()) {
491 throw new LockedException();
492 }
493 if (!isReady()) {
494 throw new NotReadyException();
495 }
496
497 final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Solution<Point3D>>() {
498 @Override
499 public double getThreshold() {
500 return threshold;
501 }
502
503 @Override
504 public int getTotalSamples() {
505 return readings.size();
506 }
507
508 @Override
509 public int getSubsetSize() {
510 return Math.max(preliminarySubsetSize, getMinReadings());
511 }
512
513 @Override
514 public void estimatePreliminarSolutions(
515 final int[] samplesIndices, final List<Solution<Point3D>> solutions) {
516 solvePreliminarySolutions(samplesIndices, solutions);
517 }
518
519 @Override
520 public double computeResidual(final Solution<Point3D> currentEstimation, final int i) {
521 return residual(currentEstimation, i);
522 }
523
524 @Override
525 public boolean isReady() {
526 return RANSACRobustRssiRadioSourceEstimator3D.this.isReady();
527 }
528
529 @Override
530 public void onEstimateStart(final RobustEstimator<Solution<Point3D>> estimator) {
531 // no action needed
532 }
533
534 @Override
535 public void onEstimateEnd(final RobustEstimator<Solution<Point3D>> estimator) {
536 // no action needed
537 }
538
539 @Override
540 public void onEstimateNextIteration(
541 final RobustEstimator<Solution<Point3D>> estimator, final int iteration) {
542 if (listener != null) {
543 listener.onEstimateNextIteration(RANSACRobustRssiRadioSourceEstimator3D.this, iteration);
544 }
545 }
546
547 @Override
548 public void onEstimateProgressChange(
549 final RobustEstimator<Solution<Point3D>> estimator, final float progress) {
550 if (listener != null) {
551 listener.onEstimateProgressChange(RANSACRobustRssiRadioSourceEstimator3D.this, progress);
552 }
553 }
554 });
555
556 try {
557 locked = true;
558
559 if (listener != null) {
560 listener.onEstimateStart(this);
561 }
562
563 inliersData = null;
564 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
565 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
566 innerEstimator.setConfidence(confidence);
567 innerEstimator.setMaxIterations(maxIterations);
568 innerEstimator.setProgressDelta(progressDelta);
569 final var result = innerEstimator.estimate();
570 inliersData = innerEstimator.getInliersData();
571 attemptRefine(result);
572
573 if (listener != null) {
574 listener.onEstimateEnd(this);
575 }
576
577 } catch (final com.irurueta.numerical.LockedException e) {
578 throw new LockedException(e);
579 } catch (final com.irurueta.numerical.NotReadyException e) {
580 throw new NotReadyException(e);
581 } finally {
582 locked = false;
583 }
584 }
585
586 /**
587 * Returns method being used for robust estimation.
588 *
589 * @return method being used for robust estimation.
590 */
591 @Override
592 public RobustEstimatorMethod getMethod() {
593 return RobustEstimatorMethod.RANSAC;
594 }
595 }