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.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.geometry.Point;
21  import com.irurueta.navigation.LockedException;
22  import com.irurueta.navigation.NotReadyException;
23  import com.irurueta.navigation.indoor.RadioSource;
24  import com.irurueta.navigation.indoor.RangingAndRssiReadingLocated;
25  import com.irurueta.navigation.indoor.RangingReadingLocated;
26  import com.irurueta.navigation.indoor.ReadingLocated;
27  import com.irurueta.navigation.indoor.RssiReadingLocated;
28  import com.irurueta.navigation.indoor.Utils;
29  
30  import java.util.ArrayList;
31  import java.util.List;
32  
33  /**
34   * This is an abstract class to robustly estimate position, transmitted power and
35   * path loss exponent of a radio source (e.g. Wi-Fi access point or bluetooth
36   * beacon) assuming that the ranging data is available to obtain position with greater
37   * accuracy and that the radio source emits isotropically following the
38   * expression below:
39   * Pr = Pt*Gt*Gr*lambda^2 / (4*pi*d)^2,
40   * where Pr is the received power (expressed in mW),
41   * Gt is the Gain of the transmission antenna
42   * Gr is the Gain of the receiver antenna
43   * d is the distance between emitter and receiver
44   * and lambda is the wavelength and is equal to: lambda = c / f,
45   * where c is the speed of light
46   * and f is the carrier frequency of the radio signal.
47   * Because usually information about the antenna of the radio source cannot be
48   * retrieved (because many measurements are made on unknown devices where
49   * physical access is not possible), this implementation will estimate the
50   * equivalent transmitted power as: Pte = Pt * Gt * Gr.
51   * If Readings contain RSSI standard deviations, those values will be used,
52   * otherwise it will be assumed an RSSI standard deviation of 1 dB.
53   * <p>
54   * This implementation is like RangingAndRssiRadioSourceEstimator but allows mixing
55   * different kinds of located radio source readings (ranging, RSSI and ranging+RSSI).
56   *
57   * @param <S> a {@link RadioSource} type.
58   * @param <P> a {@link Point} type.
59   */
60  public abstract class MixedRadioSourceEstimator<S extends RadioSource, P extends Point<P>>
61          extends RadioSourceEstimator<P, ReadingLocated<P>, MixedRadioSourceEstimatorListener<S, P>> {
62  
63      /**
64       * Speed of light expressed in meters per second (m/s).
65       */
66      public static final double SPEED_OF_LIGHT = 299792458.0;
67  
68      /**
69       * Default exponent typically used on free space for path loss propagation in
70       * terms of distance. This value is used for free space environments.
71       */
72      public static final double DEFAULT_PATH_LOSS_EXPONENT = 2.0;
73  
74      /**
75       * Indicates whether radio source transmitted power estimation is enabled or not by
76       * default. Typically, this data is required for Wi-Fi Access points, but it is already
77       * provided for Beacons (and hence its estimation is not needed).
78       */
79      public static final boolean DEFAULT_TRANSMITTED_POWER_ESTIMATION_ENABLED = true;
80  
81      /**
82       * Indicates whether path loss estimation is enabled or not by default.
83       */
84      public static final boolean DEFAULT_PATHLOSS_ESTIMATION_ENABLED = false;
85  
86      /**
87       * Indicates that by default position covariances of readings must be taken into account to increase
88       * the amount of standard deviation of each ranging measure by the amount of position standard deviation
89       * assuming that both measures are statistically independent.
90       */
91      public static final boolean DEFAULT_USE_READING_POSITION_COVARIANCES = true;
92  
93      /**
94       * RSSI radio source estimator.
95       */
96      protected RssiRadioSourceEstimator<S, P> rssiInnerEstimator;
97  
98      /**
99       * Ranging radio source estimator.
100      */
101     protected RangingRadioSourceEstimator<S, P> rangingInnerEstimator;
102 
103     /**
104      * Indicates whether transmitted power estimation is enabled or not.
105      */
106     protected boolean transmittedPowerEstimationEnabled = DEFAULT_TRANSMITTED_POWER_ESTIMATION_ENABLED;
107 
108     /**
109      * Indicates whether path loss estimation is enabled or not.
110      */
111     protected boolean pathLossEstimationEnabled = DEFAULT_PATHLOSS_ESTIMATION_ENABLED;
112 
113     /**
114      * Estimated transmitted power expressed in dBm's or null if not available.
115      */
116     private Double estimatedTransmittedPowerdBm;
117 
118     /**
119      * Estimated exponent typically used on free space for path loss propagation in
120      * terms of distance.
121      * On different environments path loss exponent might have different values:
122      * - Free space: 2.0
123      * - Urban Area: 2.7 to 3.5
124      * - Suburban Area: 3 to 5
125      * - Indoor (line-of-sight): 1.6 to 1.8
126      * If path loss exponent estimation is not enabled, this value will always be equal to
127      * {@link #DEFAULT_PATH_LOSS_EXPONENT}
128      */
129     private double estimatedPathLossExponent = DEFAULT_PATH_LOSS_EXPONENT;
130 
131     /**
132      * Variance of estimated transmitted power.
133      * This value will only be available when transmitted power
134      * estimation is enabled.
135      */
136     private Double estimatedTransmittedPowerVariance;
137 
138     /**
139      * Variance of estimated path loss exponent.
140      * This value will only be available when path-loss
141      * exponent estimation is enabled.
142      */
143     private Double estimatedPathLossExponentVariance;
144 
145     /**
146      * Initial transmitted power to start the estimation of radio source
147      * transmitted power.
148      * If not defined, average value of received power readings will be used.
149      */
150     private Double initialTransmittedPowerdBm;
151 
152     /**
153      * Initial position to start the estimation of radio source position.
154      * If not defined, centroid of provided readings will be used.
155      */
156     private P initialPosition;
157 
158     /**
159      * Initial exponent typically used on free space for path loss propagation in
160      * terms of distance.
161      * On different environments path loss exponent might have different values:
162      * - Free space: 2.0
163      * - Urban Area: 2.7 to 3.5
164      * - Suburban Area: 3 to 5
165      * - Indoor (line-of-sight): 1.6 to 1.8
166      * <p>
167      * If path loss exponent estimation is enabled, estimation will start at this
168      * value and will converge to the most appropriate value.
169      * If path loss exponent estimation is disabled, this value will be assumed
170      * to be exact and the estimated path loss exponent will be equal to this
171      * value.
172      */
173     private double initialPathLossExponent = DEFAULT_PATH_LOSS_EXPONENT;
174 
175     /**
176      * Indicates whether position covariances of readings must be taken into account to increase
177      * the amount of standard deviation of each ranging measure by the amount of position standard deviation
178      * assuming that both measures are statistically independent.
179      */
180     private boolean useReadingPositionCovariances = DEFAULT_USE_READING_POSITION_COVARIANCES;
181 
182     /**
183      * Number of ranging readings available among all readings.
184      */
185     private int numRangingReadings;
186 
187     /**
188      * Number of RSSI readings available among all readings.
189      */
190     private int numRssiReadings;
191 
192     /**
193      * Indicates whether position is estimated using RSSI data.
194      * If enough ranging readings are available, this is false and position is estimated using ranging readings,
195      * otherwise this is true and position is estimated using RSSI data in a less reliable way.
196      */
197     private boolean rssiPositionEnabled;
198 
199     /**
200      * Indicates whether an homogeneous linear solver is used to estimate an initial
201      * position for the internal ranging radio source estimator.
202      */
203     private boolean useHomogeneousRangingLinearSolver =
204             RangingRadioSourceEstimator.DEFAULT_USE_HOMOGENEOUS_LINEAR_SOLVER;
205 
206     /**
207      * Constructor.
208      */
209     protected MixedRadioSourceEstimator() {
210         super();
211     }
212 
213     /**
214      * Constructor.
215      * Sets radio signal readings belonging to the same radio source.
216      *
217      * @param readings radio signal readings belonging to the same
218      *                 radio sources.
219      * @throws IllegalArgumentException if readings are not valid.
220      */
221     protected MixedRadioSourceEstimator(final List<? extends ReadingLocated<P>> readings) {
222         super(readings);
223     }
224 
225     /**
226      * Constructor.
227      *
228      * @param listener listener in charge of attending events raised by this instance.
229      */
230     protected MixedRadioSourceEstimator(final MixedRadioSourceEstimatorListener<S, P> listener) {
231         super(listener);
232     }
233 
234     /**
235      * Constructor.
236      * Sets radio signal readings belonging to the same radio source.
237      *
238      * @param readings radio signal readings belonging to the same radio source.
239      * @param listener listener in charge of attending events raised by this instance.
240      * @throws IllegalArgumentException if readings are not valid.
241      */
242     protected MixedRadioSourceEstimator(
243             final List<? extends ReadingLocated<P>> readings, final MixedRadioSourceEstimatorListener<S, P> listener) {
244         super(readings, listener);
245     }
246 
247     /**
248      * Constructor.
249      *
250      * @param initialPosition initial position to start the estimation of radio
251      *                        source position.
252      */
253     protected MixedRadioSourceEstimator(final P initialPosition) {
254         this.initialPosition = initialPosition;
255     }
256 
257     /**
258      * Constructor.
259      * Sets radio signal readings belonging to the same radio source.
260      *
261      * @param readings        radio signal readings belonging to the same radio source.
262      * @param initialPosition initial position to start the estimation of radio
263      *                        source position.
264      * @throws IllegalArgumentException if readings are not valid.
265      */
266     protected MixedRadioSourceEstimator(
267             final List<? extends ReadingLocated<P>> readings, final P initialPosition) {
268         super(readings);
269         this.initialPosition = initialPosition;
270     }
271 
272     /**
273      * Constructor.
274      *
275      * @param initialPosition initial position to start the estimation of radio
276      *                        source position.
277      * @param listener        listener in charge of attending events raised by this instance.
278      */
279     protected MixedRadioSourceEstimator(
280             final P initialPosition, final MixedRadioSourceEstimatorListener<S, P> listener) {
281         super(listener);
282         this.initialPosition = initialPosition;
283     }
284 
285     /**
286      * Constructor.
287      * Sets radio signal readings belonging to the same radio source.
288      *
289      * @param readings        radio signal readings belonging to the same radio source.
290      * @param initialPosition initial position to start the estimation of radio
291      *                        source position.
292      * @param listener        listener in charge of attending events raised by this instance.
293      * @throws IllegalArgumentException if readings are not valid.
294      */
295     protected MixedRadioSourceEstimator(
296             final List<? extends ReadingLocated<P>> readings, final P initialPosition,
297             final MixedRadioSourceEstimatorListener<S, P> listener) {
298         super(readings, listener);
299         this.initialPosition = initialPosition;
300     }
301 
302     /**
303      * Constructor.
304      *
305      * @param initialTransmittedPowerDbm initial transmitted power to start the
306      *                                   estimation of radio source transmitted power
307      *                                   (expressed in dBm's).
308      */
309     protected MixedRadioSourceEstimator(final Double initialTransmittedPowerDbm) {
310         initialTransmittedPowerdBm = initialTransmittedPowerDbm;
311     }
312 
313     /**
314      * Constructor.
315      * Sets radio signal readings belonging to the same radio source.
316      *
317      * @param readings                   radio signal readings belonging to the same radio source.
318      * @param initialTransmittedPowerdBm initial transmitted power to start the
319      *                                   estimation of radio source transmitted power
320      *                                   (expressed in dBm's).
321      * @throws IllegalArgumentException if readings are not valid.
322      */
323     protected MixedRadioSourceEstimator(
324             final List<? extends ReadingLocated<P>> readings, final Double initialTransmittedPowerdBm) {
325         super(readings);
326         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
327     }
328 
329     /**
330      * Constructor.
331      *
332      * @param initialTransmittedPowerdBm initial transmitted power to start the
333      *                                   estimation of radio source transmitted power
334      *                                   (expressed in dBm's).
335      * @param listener                   listener in charge of attending events raised by this instance.
336      */
337     protected MixedRadioSourceEstimator(
338             final Double initialTransmittedPowerdBm, final MixedRadioSourceEstimatorListener<S, P> listener) {
339         super(listener);
340         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
341     }
342 
343     /**
344      * Constructor.
345      * Sets radio signal readings belonging to the same radio source.
346      *
347      * @param readings                   radio signal readings belonging to the same radio source.
348      * @param initialTransmittedPowerdBm initial transmitted power to start the
349      *                                   estimation of radio source transmitted power
350      *                                   (expressed in dBm's).
351      * @param listener                   listener in charge of attending events raised by this instance.
352      * @throws IllegalArgumentException if readings are not valid.
353      */
354     protected MixedRadioSourceEstimator(
355             final List<? extends ReadingLocated<P>> readings, final Double initialTransmittedPowerdBm,
356             final MixedRadioSourceEstimatorListener<S, P> listener) {
357         super(readings, listener);
358         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
359     }
360 
361     /**
362      * Constructor.
363      * Sets radio signal readings belonging to the same radio source.
364      *
365      * @param readings                   radio signal readings belonging to the same radio source.
366      * @param initialPosition            initial position to start the estimation of radio
367      *                                   source position.
368      * @param initialTransmittedPowerdBm initial transmitted power to start the
369      *                                   estimation of radio source transmitted power
370      *                                   (expressed in dBm's).
371      * @throws IllegalArgumentException if readings are not valid.
372      */
373     protected MixedRadioSourceEstimator(
374             final List<? extends ReadingLocated<P>> readings, final P initialPosition,
375             Double initialTransmittedPowerdBm) {
376         super(readings);
377         this.initialPosition = initialPosition;
378         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
379     }
380 
381     /**
382      * Constructor.
383      *
384      * @param initialPosition            initial position to start the estimation of radio
385      *                                   source position.
386      * @param initialTransmittedPowerdBm initial transmitted power to start the
387      *                                   estimation of radio source transmitted power
388      *                                   (expressed in dBm's).
389      */
390     protected MixedRadioSourceEstimator(final P initialPosition, final Double initialTransmittedPowerdBm) {
391         this.initialPosition = initialPosition;
392         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
393     }
394 
395     /**
396      * Constructor.
397      *
398      * @param initialPosition            initial position to start the estimation of radio
399      *                                   source position.
400      * @param initialTransmittedPowerdBm initial transmitted power to start the
401      *                                   estimation of radio source transmitted power
402      *                                   (expressed in dBm's).
403      * @param listener                   listener in charge of attending events raised by this instance.
404      */
405     protected MixedRadioSourceEstimator(
406             final P initialPosition, final Double initialTransmittedPowerdBm,
407             final MixedRadioSourceEstimatorListener<S, P> listener) {
408         super(listener);
409         this.initialPosition = initialPosition;
410         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
411     }
412 
413     /**
414      * Constructor.
415      * Sets radio signal readings belonging to the same radio source.
416      *
417      * @param readings                   radio signal readings belonging to the same radio source.
418      * @param initialPosition            initial position to start the estimation of radio
419      *                                   source position.
420      * @param initialTransmittedPowerdBm initial transmitted power to start the
421      *                                   estimation of radio source transmitted power
422      *                                   (expressed in dBm's).
423      * @param listener                   listener in charge of attending events raised by this instance.
424      * @throws IllegalArgumentException if readings are not valid.
425      */
426     protected MixedRadioSourceEstimator(
427             final List<? extends ReadingLocated<P>> readings, final P initialPosition,
428             final Double initialTransmittedPowerdBm, final MixedRadioSourceEstimatorListener<S, P> listener) {
429         super(readings, listener);
430         this.initialPosition = initialPosition;
431         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
432     }
433 
434     /**
435      * Constructor.
436      * Sets radio signal readings belonging to the same radio source.
437      *
438      * @param readings                   radio signal readings belonging to the same radio source.
439      * @param initialPosition            initial position to start the estimation of radio
440      *                                   source position.
441      * @param initialTransmittedPowerdBm initial transmitted power to start the
442      *                                   estimation of radio source transmitted power
443      *                                   (expressed in dBm's).
444      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
445      * @throws IllegalArgumentException if readings are not valid.
446      */
447     protected MixedRadioSourceEstimator(
448             final List<? extends ReadingLocated<P>> readings, final P initialPosition,
449             final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
450         this(readings, initialPosition, initialTransmittedPowerdBm);
451         this.initialPathLossExponent = initialPathLossExponent;
452     }
453 
454     /**
455      * Constructor.
456      *
457      * @param initialPosition            initial position to start the estimation of radio
458      *                                   source position.
459      * @param initialTransmittedPowerdBm initial transmitted power to start the
460      *                                   estimation of radio source transmitted power
461      *                                   (expressed in dBm's).
462      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
463      */
464     protected MixedRadioSourceEstimator(
465             final P initialPosition, final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
466         this(initialPosition, initialTransmittedPowerdBm);
467         this.initialPathLossExponent = initialPathLossExponent;
468     }
469 
470     /**
471      * Constructor.
472      *
473      * @param initialPosition            initial position to start the estimation of radio
474      *                                   source position.
475      * @param initialTransmittedPowerdBm initial transmitted power to start the
476      *                                   estimation of radio source transmitted power
477      *                                   (expressed in dBm's).
478      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
479      * @param listener                   listener in charge of attending events raised by this instance.
480      */
481     protected MixedRadioSourceEstimator(
482             final P initialPosition, final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
483             final MixedRadioSourceEstimatorListener<S, P> listener) {
484         this(initialPosition, initialTransmittedPowerdBm, listener);
485         this.initialPathLossExponent = initialPathLossExponent;
486     }
487 
488     /**
489      * Constructor.
490      * Sets radio signal readings belonging to the same radio source.
491      *
492      * @param readings                   radio signal readings belonging to the same radio source.
493      * @param initialPosition            initial position to start the estimation of radio
494      *                                   source position.
495      * @param initialTransmittedPowerdBm initial transmitted power to start the
496      *                                   estimation of radio source transmitted power
497      *                                   (expressed in dBm's).
498      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
499      * @param listener                   listener in charge of attending events raised by this instance.
500      * @throws IllegalArgumentException if readings are not valid.
501      */
502     protected MixedRadioSourceEstimator(
503             final List<? extends ReadingLocated<P>> readings,
504             final P initialPosition, final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
505             final MixedRadioSourceEstimatorListener<S, P> listener) {
506         this(readings, initialPosition, initialTransmittedPowerdBm, listener);
507         this.initialPathLossExponent = initialPathLossExponent;
508     }
509 
510     /**
511      * Gets initial transmitted power to start the estimation of radio source
512      * transmitted power (expressed in dBm's).
513      * If not defined, average value of received power readings will be used.
514      * <p>
515      * If transmitted power estimation is enabled, estimation will start at this
516      * value and will be converted to the most appropriate value.
517      * If transmitted power estimation is disabled, this value will be assumed to be
518      * exact and the estimated transmitted power will be equal to this value
519      * (converted to dBm's).
520      *
521      * @return initial transmitted power to start the estimation of radio source
522      * transmitted power.
523      */
524     public Double getInitialTransmittedPowerdBm() {
525         return initialTransmittedPowerdBm;
526     }
527 
528     /**
529      * Sets initial transmitted power to start the estimation of radio source
530      * transmitted power (expressed in dBm's).
531      * If not defined, average value of received power readings will be used.
532      * <p>
533      * If transmitted power estimation is enabled, estimation will start at this
534      * value and will be converted to the most appropriate value.
535      * If transmitted power estimation is disabled, this value will be assumed to be
536      * exact and the estimated transmitted power will be equal to this value
537      * (converted to dBm's).
538      *
539      * @param initialTransmittedPowerdBm initial transmitted power to start the
540      *                                   estimation of radio source transmitted
541      *                                   power.
542      * @throws LockedException if estimator is locked.
543      */
544     public void setInitialTransmittedPowerdBm(final Double initialTransmittedPowerdBm) throws LockedException {
545         if (isLocked()) {
546             throw new LockedException();
547         }
548         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
549     }
550 
551     /**
552      * Gets initial transmitted power to start the estimation of radio source
553      * transmitted power (expressed in mW).
554      * If not defined, average value of received power readings will be used.
555      * <p>
556      * If transmitted power estimation is enabled, estimation will start at this
557      * value and will be converted to the most appropriate value.
558      * If transmitted power estimation is disabled, this value will be assumed to be
559      * exact and the estimated transmitted power will be equal to this value
560      * (converted to dBm's).
561      *
562      * @return initial transmitted power to start the estimation of radio source
563      * transmitted power.
564      */
565     public Double getInitialTransmittedPower() {
566         return initialTransmittedPowerdBm != null ? Utils.dBmToPower(initialTransmittedPowerdBm) : null;
567     }
568 
569     /**
570      * Sets initial transmitted power to start the estimation of radio source
571      * transmitted power (expressed in mW).
572      * If not defined, average value of received power readings will be used.
573      * <p>
574      * If transmitted power estimation is enabled, estimation will start at this
575      * value and will be converted to the most appropriate value.
576      * If transmitted power estimation is disabled, this value will be assumed to be
577      * exact and the estimated transmitted power will be equal to this value
578      * (converted to dBm's).
579      *
580      * @param initialTransmittedPower initial transmitted power to start the
581      *                                estimation of radio source transmitted power.
582      * @throws LockedException          if estimator is locked.
583      * @throws IllegalArgumentException if provided value is negative.
584      */
585     public void setInitialTransmittedPower(final Double initialTransmittedPower) throws LockedException {
586         if (isLocked()) {
587             throw new LockedException();
588         }
589         if (initialTransmittedPower != null) {
590             if (initialTransmittedPower < 0.0) {
591                 throw new IllegalArgumentException();
592             }
593             initialTransmittedPowerdBm = Utils.powerTodBm(initialTransmittedPower);
594         } else {
595             initialTransmittedPowerdBm = null;
596         }
597     }
598 
599     /**
600      * Indicates whether transmitted power estimation is enabled or not.
601      *
602      * @return true if transmitted power estimation is enabled, false otherwise.
603      */
604     public boolean isTransmittedPowerEstimationEnabled() {
605         return transmittedPowerEstimationEnabled;
606     }
607 
608     /**
609      * Specifies whether transmitted power estimation is enabled or not.
610      *
611      * @param transmittedPowerEstimationEnabled true if transmitted power estimation is enabled,
612      *                                          false otherwise.
613      * @throws LockedException if estimator is locked.
614      */
615     public void setTransmittedPowerEstimationEnabled(final boolean transmittedPowerEstimationEnabled)
616             throws LockedException {
617         if (isLocked()) {
618             throw new LockedException();
619         }
620         this.transmittedPowerEstimationEnabled = transmittedPowerEstimationEnabled;
621     }
622 
623     /**
624      * Gets initial position to start the estimation of radio source position.
625      * If not defined, centroid of provided readings will be used.
626      * <p>
627      * If position estimation is enabled, estimation will start at this value
628      * and will converge to the most appropriate value.
629      * If position estimation is disabled, this value will be assumed to
630      * be exact and the estimated position will be equal to this value.
631      *
632      * @return initial position to start the estimation of radio source position.
633      */
634     public P getInitialPosition() {
635         return initialPosition;
636     }
637 
638     /**
639      * Sets initial position to start the estimation of radio source position.
640      * If not defined, centroid of provided fingerprints will be used.
641      * <p>
642      * If position estimation is enabled, estimation will start at this value
643      * and will converge to the most appropriate value.
644      * If position estimation is disabled, this value will be assumed to
645      * be exact and the estimated position will be equal to this value.
646      *
647      * @param initialPosition initial position to start the estimation of radio
648      *                        source position.
649      * @throws LockedException if estimator is locked.
650      */
651     public void setInitialPosition(final P initialPosition) throws LockedException {
652         if (isLocked()) {
653             throw new LockedException();
654         }
655         this.initialPosition = initialPosition;
656     }
657 
658     /**
659      * Gets initial exponent typically used on free space for path loss propagation
660      * in terms of distance.
661      * On different environments path loss exponent might have different value:
662      * - Free space: 2.0
663      * - Urban Area: 2.7 to 3.5
664      * - Suburban Area: 3 to 5
665      * - Indoor (line-of-sight): 1.6 to 1.8
666      * <p>
667      * If path loss exponent estimation is enabled, estimation will start at this
668      * value and will converge to the most appropriate value.
669      * If path loss exponent estimation is disabled, this value will be assumed
670      * to be exact and the estimated path loss exponent will be equal to this
671      * value.
672      *
673      * @return initial path loss exponent.
674      */
675     public double getInitialPathLossExponent() {
676         return initialPathLossExponent;
677     }
678 
679     /**
680      * Sets initial exponent typically used on free space for path loss propagation
681      * in terms of distance.
682      * On different environments path loss exponent might have different value:
683      * - Free space: 2.0
684      * - Urban Area: 2.7 to 3.5
685      * - Suburban Area: 3 to 5
686      * - Indoor (line-of-sight): 1.6 to 1.8
687      * <p>
688      * If path loss exponent estimation is enabled, estimation will start at this
689      * value and will converge to the most appropriate value.
690      * If path loss exponent estimation is disabled, this value will be assumed
691      * to be exact and the estimated path loss exponent will be equal to this
692      * value.
693      *
694      * @param initialPathLossExponent initial path loss exponent.
695      * @throws LockedException if estimator is locked.
696      */
697     public void setInitialPathLossExponent(final double initialPathLossExponent) throws LockedException {
698         if (isLocked()) {
699             throw new LockedException();
700         }
701         this.initialPathLossExponent = initialPathLossExponent;
702     }
703 
704     /**
705      * Indicates whether path loss estimation is enabled or not.
706      *
707      * @return true if path loss estimation is enabled, false otherwise.
708      */
709     public boolean isPathLossEstimationEnabled() {
710         return pathLossEstimationEnabled;
711     }
712 
713     /**
714      * Specifies whether path loss estimation is enabled or not.
715      *
716      * @param pathLossEstimationEnabled true if path loss estimation is enabled,
717      *                                  false otherwise.
718      * @throws LockedException if estimator is locked.
719      */
720     public void setPathLossEstimationEnabled(final boolean pathLossEstimationEnabled) throws LockedException {
721         if (isLocked()) {
722             throw new LockedException();
723         }
724         this.pathLossEstimationEnabled = pathLossEstimationEnabled;
725     }
726 
727     /**
728      * Indicates whether position covariances of readings must be taken into account to increase
729      * the amount of standard deviation of each ranging measure by the amount of position standard
730      * deviation assuming that both measures are statistically independent.
731      *
732      * @return true to take into account reading position covariances, false otherwise.
733      */
734     public boolean getUseReadingPositionCovariance() {
735         return useReadingPositionCovariances;
736     }
737 
738     /**
739      * Specifies whether position covariances of readings must be taken into account to increase
740      * the amount of standard deviation of each ranging measure by the amount of position standard
741      * deviation assuming that both measures are statistically independent.
742      *
743      * @param useReadingPositionCovariances true to take into account reading position covariances, false
744      *                                      otherwise.
745      * @throws LockedException if estimator is locked.
746      */
747     public void setUseReadingPositionCovariances(final boolean useReadingPositionCovariances) throws LockedException {
748         if (isLocked()) {
749             throw new LockedException();
750         }
751         this.useReadingPositionCovariances = useReadingPositionCovariances;
752     }
753 
754     /**
755      * Indicates whether an homogeneous linear solver is used to estimate an initial
756      * position for the internal ranging radio source estimator.
757      *
758      * @return true if homogeneous linear solver is used, false if an inhomogeneous linear
759      * one is used instead.
760      */
761     public boolean isHomogeneousRangingLinearSolverUsed() {
762         return useHomogeneousRangingLinearSolver;
763     }
764 
765     /**
766      * Specifies whether an homogeneous linear solver is used to estimate an initial
767      * position for the internal ranging radio source estimator.
768      *
769      * @param useHomogeneousLinearSolver true if homogeneous linear solver is used, false
770      *                                   if an inhomogeneous linear one is used instead.
771      * @throws LockedException if estimator is locked.
772      */
773     public void setHomogeneousRangingLinearSolverUsed(final boolean useHomogeneousLinearSolver) throws LockedException {
774         if (isLocked()) {
775             throw new LockedException();
776         }
777 
778         useHomogeneousRangingLinearSolver = useHomogeneousLinearSolver;
779     }
780 
781     /**
782      * Gets minimum required number of ranging or ranging+rssi readings
783      * required to start estimation.
784      *
785      * @return minimum required number of ranging or ranging+rssi readings.
786      */
787     public int getMinRangingReadings() {
788         return getNumberOfDimensions() + 1;
789     }
790 
791     /**
792      * Gets minimum required number of rssi or ranging+rssi readings
793      * required to start estimation.
794      *
795      * @return minimum required number of rssi or ranging+rssi readings.
796      */
797     public int getMinRssiReadings() {
798         return getMinReadings();
799     }
800 
801     /**
802      * Gets minimum required number of readings to estimate
803      * power, position and path-loss exponent.
804      * This value depends on the number of parameters to
805      * be estimated, but for position only, this is 3
806      * readings.
807      *
808      * @return minimum required number of readings.
809      * @throws IllegalStateException if inner RSSI estimator is busy.
810      */
811     @Override
812     public int getMinReadings() {
813         createInnerEstimatorsIfNeeded();
814 
815         var result = getNumberOfDimensions();
816         if (rssiInnerEstimator != null && (transmittedPowerEstimationEnabled || pathLossEstimationEnabled)) {
817             try {
818                 rssiInnerEstimator.setPositionEstimationEnabled(rssiPositionEnabled);
819                 rssiInnerEstimator.setTransmittedPowerEstimationEnabled(transmittedPowerEstimationEnabled);
820                 rssiInnerEstimator.setPathLossEstimationEnabled(pathLossEstimationEnabled);
821             } catch (final LockedException e) {
822                 throw new IllegalStateException(e);
823             }
824 
825             result += rssiInnerEstimator.getMinReadings();
826         } else {
827             result++;
828         }
829 
830         return result;
831     }
832 
833     /**
834      * Gets estimated radio source position.
835      *
836      * @return estimated radio source position.
837      */
838     public P getEstimatedPosition() {
839         return rssiPositionEnabled ? rssiInnerEstimator.getEstimatedPosition()
840                 : rangingInnerEstimator.getEstimatedPosition();
841     }
842 
843     /**
844      * Indicates whether readings are valid or not.
845      * Readings are considered valid when there are enough readings.
846      *
847      * @param readings readings to be validated.
848      * @return true if readings are valid, false otherwise.
849      */
850     @Override
851     public boolean areValidReadings(final List<? extends ReadingLocated<P>> readings) {
852         if (readings == null) {
853             return false;
854         }
855 
856         checkReadings(readings);
857 
858         // if enough ranging data is available, we check validity both for ranging and RSSI readings
859         return ((!rssiPositionEnabled && numRangingReadings >= getMinRangingReadings()
860                 && numRssiReadings >= getMinRssiReadings())
861                 // if not enough ranging data is available, we check validity only for RSSI readings
862                 || (rssiPositionEnabled && numRssiReadings >= getMinRssiReadings())
863                 // if only position is enabled, then only check for ranging readings
864                 || (!transmittedPowerEstimationEnabled && !pathLossEstimationEnabled
865                 && numRangingReadings >= getMinRangingReadings()))
866                 // in both upper cases enough general readings must be available
867                 && super.areValidReadings(readings);
868     }
869 
870     /**
871      * Indicates whether this instance is ready to start the estimation.
872      *
873      * @return true if this instance is ready, false otherwise.
874      */
875     @Override
876     public boolean isReady() {
877         return areValidReadings(readings);
878     }
879 
880     /**
881      * Estimate position, transmitted power and path loss exponent.
882      *
883      * @throws RadioSourceEstimationException if estimation fails.
884      * @throws NotReadyException              if estimator is not ready.
885      * @throws LockedException                if estimator is locked.
886      */
887     @SuppressWarnings("DuplicatedCode")
888     @Override
889     public void estimate() throws RadioSourceEstimationException, NotReadyException, LockedException {
890         if (isLocked()) {
891             throw new LockedException();
892         }
893         if (!isReady()) {
894             throw new NotReadyException();
895         }
896 
897         try {
898             locked = true;
899 
900             if (listener != null) {
901                 listener.onEstimateStart(this);
902             }
903 
904             createInnerEstimatorsIfNeeded();
905 
906             final var rangingReadings = new ArrayList<RangingReadingLocated<S, P>>();
907             final var rssiReadings = new ArrayList<RssiReadingLocated<S, P>>();
908             for (final var reading : readings) {
909                 if (reading instanceof RangingReadingLocated) {
910                     rangingReadings.add((RangingReadingLocated<S, P>) reading);
911 
912                 } else if (reading instanceof RssiReadingLocated) {
913                     rssiReadings.add((RssiReadingLocated<S, P>) reading);
914 
915                 } else if (reading instanceof RangingAndRssiReadingLocated) {
916                     rangingReadings.add(createRangingReading((RangingAndRssiReadingLocated<S, P>) reading));
917                     rssiReadings.add(createRssiReading((RangingAndRssiReadingLocated<S, P>) reading));
918                 }
919             }
920 
921             // estimate position using ranging data, if possible
922             P estimatedPosition = null;
923             if (!rssiPositionEnabled) {
924                 rangingInnerEstimator.setUseReadingPositionCovariances(useReadingPositionCovariances);
925                 rangingInnerEstimator.setHomogeneousLinearSolverUsed(useHomogeneousRangingLinearSolver);
926                 rangingInnerEstimator.setReadings(rangingReadings);
927                 rangingInnerEstimator.setInitialPosition(initialPosition);
928 
929                 rangingInnerEstimator.estimate();
930 
931                 estimatedPositionCoordinates = rangingInnerEstimator.getEstimatedPositionCoordinates();
932                 estimatedPositionCovariance = rangingInnerEstimator.getEstimatedPositionCovariance();
933                 estimatedPosition = rangingInnerEstimator.getEstimatedPosition();
934             }
935 
936             // estimate transmitted power and/or path-loss if enabled
937             if (transmittedPowerEstimationEnabled || pathLossEstimationEnabled || rssiPositionEnabled) {
938                 rssiInnerEstimator.setPositionEstimationEnabled(rssiPositionEnabled);
939                 rssiInnerEstimator.setInitialPosition(estimatedPosition);
940 
941                 rssiInnerEstimator.setTransmittedPowerEstimationEnabled(transmittedPowerEstimationEnabled);
942                 rssiInnerEstimator.setInitialTransmittedPowerdBm(initialTransmittedPowerdBm);
943 
944                 rssiInnerEstimator.setPathLossEstimationEnabled(pathLossEstimationEnabled);
945                 rssiInnerEstimator.setInitialPathLossExponent(initialPathLossExponent);
946 
947                 rssiInnerEstimator.setReadings(rssiReadings);
948 
949                 rssiInnerEstimator.estimate();
950 
951                 if (rssiPositionEnabled) {
952                     estimatedPositionCoordinates = rssiInnerEstimator.getEstimatedPositionCoordinates();
953                     estimatedPositionCovariance = rssiInnerEstimator.getEstimatedPositionCovariance();
954                 }
955 
956                 if (transmittedPowerEstimationEnabled) {
957                     // transmitted power estimation enabled
958                     estimatedTransmittedPowerdBm = rssiInnerEstimator.getEstimatedTransmittedPowerdBm();
959                     estimatedTransmittedPowerVariance = rssiInnerEstimator.getEstimatedTransmittedPowerVariance();
960                 } else {
961                     // transmitted power estimation disabled
962                     estimatedTransmittedPowerdBm = initialTransmittedPowerdBm;
963                     estimatedTransmittedPowerVariance = null;
964                 }
965 
966                 if (pathLossEstimationEnabled) {
967                     // path-loss exponent estimation enabled
968                     estimatedPathLossExponent = rssiInnerEstimator.getEstimatedPathLossExponent();
969                     estimatedPathLossExponentVariance = rssiInnerEstimator.getEstimatedPathLossExponentVariance();
970                 } else {
971                     // path-loss exponent estimation disabled
972                     estimatedPathLossExponent = initialPathLossExponent;
973                     estimatedPathLossExponentVariance = null;
974                 }
975 
976                 // build covariance matrix
977                 if (rssiPositionEnabled) {
978                     // if only RSSI estimation is done, we use directly the available estimated covariance
979                     estimatedCovariance = rssiInnerEstimator.getEstimatedCovariance();
980 
981                 } else {
982                     // if both ranging and RSSI data is used, we build covariance matrix by setting
983                     // position covariance estimated by ranging estimator into top-left corner, and then
984                     // adding covariance terms related to path-loss exponent and transmitted power
985                     final var rssiCov = rssiInnerEstimator.getEstimatedCovariance();
986                     if (estimatedPositionCovariance != null && rssiCov != null) {
987                         final var dims = getNumberOfDimensions();
988                         var n = dims;
989                         if (transmittedPowerEstimationEnabled) {
990                             n++;
991                         }
992                         if (pathLossEstimationEnabled) {
993                             n++;
994                         }
995 
996                         final var dimsMinus1 = dims - 1;
997                         final var nMinus1 = n - 1;
998                         estimatedCovariance = new Matrix(n, n);
999                         estimatedCovariance.setSubmatrix(0, 0, dimsMinus1, dimsMinus1,
1000                                 estimatedPositionCovariance);
1001                         estimatedCovariance.setSubmatrix(dims, dims, nMinus1, nMinus1, rssiCov);
1002                     } else {
1003                         estimatedCovariance = null;
1004                     }
1005                 }
1006 
1007             } else {
1008                 estimatedCovariance = estimatedPositionCovariance;
1009                 estimatedTransmittedPowerdBm = initialTransmittedPowerdBm;
1010                 estimatedTransmittedPowerVariance = null;
1011 
1012                 estimatedPathLossExponent = initialPathLossExponent;
1013                 estimatedPathLossExponentVariance = null;
1014             }
1015 
1016             if (listener != null) {
1017                 listener.onEstimateEnd(this);
1018             }
1019 
1020         } catch (final AlgebraException e) {
1021             throw new RadioSourceEstimationException(e);
1022         } finally {
1023             locked = false;
1024         }
1025     }
1026 
1027     /**
1028      * Gets estimated transmitted power expressed in milli watts (mW) or null if
1029      * not available.
1030      *
1031      * @return estimated transmitted power expressed in milli watts or null.
1032      */
1033     public Double getEstimatedTransmittedPower() {
1034         return estimatedTransmittedPowerdBm != null ? Utils.dBmToPower(estimatedTransmittedPowerdBm) : null;
1035     }
1036 
1037     /**
1038      * Gets estimated transmitted power expressed in dBm's or null if not available.
1039      *
1040      * @return estimated transmitted power expressed in dBm's or null.
1041      */
1042     public Double getEstimatedTransmittedPowerdBm() {
1043         return estimatedTransmittedPowerdBm;
1044     }
1045 
1046     /**
1047      * Gets estimated exponent typically used on free space for path loss propagation in
1048      * terms of distance.
1049      * On different environments path loss exponent might have different values:
1050      * - Free space: 2.0
1051      * - Urban Area: 2.7 to 3.5
1052      * - Suburban Area: 3 to 5
1053      * - Indoor (line-of-sight): 1.6 to 1.8
1054      * If path loss exponent estimation is not enabled, this value will always be equal to
1055      * {@link #DEFAULT_PATH_LOSS_EXPONENT}
1056      *
1057      * @return estimated path loss exponent.
1058      */
1059     public double getEstimatedPathLossExponent() {
1060         return estimatedPathLossExponent;
1061     }
1062 
1063     /**
1064      * Gets estimated transmitted power variance.
1065      * This value will only be available when transmitted power
1066      * estimation is enabled.
1067      *
1068      * @return estimated transmitted power variance or null.
1069      */
1070     public Double getEstimatedTransmittedPowerVariance() {
1071         return estimatedTransmittedPowerVariance;
1072     }
1073 
1074     /**
1075      * Gets estimated path loss exponent variance.
1076      * This value will only be available when path-loss
1077      * exponent estimation is enabled.
1078      *
1079      * @return estimated path loss exponent variance or null.
1080      */
1081     public Double getEstimatedPathLossExponentVariance() {
1082         return estimatedPathLossExponentVariance;
1083     }
1084 
1085     /**
1086      * Creates inner estimators if needed.
1087      */
1088     protected abstract void createInnerEstimatorsIfNeeded();
1089 
1090     /**
1091      * Creates a ranging reading from a ranging and RSSI reading.
1092      *
1093      * @param reading input reading to convert from.
1094      * @return a ranging reading containing only the ranging data of input reading.
1095      */
1096     private RangingReadingLocated<S, P> createRangingReading(final RangingAndRssiReadingLocated<S, P> reading) {
1097         return new RangingReadingLocated<>(reading.getSource(), reading.getDistance(), reading.getPosition(),
1098                 reading.getDistanceStandardDeviation(), reading.getPositionCovariance());
1099     }
1100 
1101     /**
1102      * Creates an RSSI reading from a ranging and RSSI reading.
1103      *
1104      * @param reading input reading to convert from.
1105      * @return an RSSI reading containing only the RSSI data of input reading.
1106      */
1107     private RssiReadingLocated<S, P> createRssiReading(final RangingAndRssiReadingLocated<S, P> reading) {
1108         return new RssiReadingLocated<>(reading.getSource(), reading.getRssi(), reading.getPosition(),
1109                 reading.getRssiStandardDeviation(), reading.getPositionCovariance());
1110     }
1111 
1112     /**
1113      * Checks number of available ranging readings and number of available RSSI readings. Also determines
1114      * whether position must be estimated using ranging data or RSSI data.
1115      *
1116      * @param readings readings to be checked.
1117      */
1118     private void checkReadings(final List<? extends ReadingLocated<P>> readings) {
1119         numRangingReadings = numRssiReadings = 0;
1120 
1121         if (readings == null) {
1122             return;
1123         }
1124 
1125         for (final var reading : readings) {
1126             if (reading instanceof RangingReadingLocated) {
1127                 numRangingReadings++;
1128 
1129             } else if (reading instanceof RssiReadingLocated) {
1130                 numRssiReadings++;
1131 
1132             } else if (reading instanceof RangingAndRssiReadingLocated) {
1133                 numRangingReadings++;
1134                 numRssiReadings++;
1135             }
1136         }
1137 
1138         rssiPositionEnabled = numRangingReadings < getMinRangingReadings();
1139     }
1140 }