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