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.fingerprint;
17  
18  import com.irurueta.geometry.Point;
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.RssiFingerprint;
23  import com.irurueta.navigation.indoor.RssiFingerprintLocated;
24  import com.irurueta.navigation.indoor.RssiReading;
25  
26  import java.util.List;
27  
28  /**
29   * Base class for position estimators based on located fingerprints containing only
30   * RSSI readings without any prior knowledge of radio sources.
31   * These kind of estimators can be used to determine the position of a given device by
32   * getting RSSI readings at an unknown location of different radio sources and comparing
33   * those readings with other located ones.
34   *
35   * @param <P> a {@link Point} type.
36   * @param <L> a {@link BaseFingerprintEstimatorListener} type.
37   */
38  public abstract class BaseFingerprintPositionEstimator<P extends Point<?>,
39          L extends BaseFingerprintEstimatorListener<?>> {
40  
41      /**
42       * Default minimum number of nearest fingerprints to search.
43       */
44      public static final int DEFAULT_MIN_NEAREST_FINGERPRINTS = 1;
45  
46      /**
47       * Default maximum number of nearest fingerprints to search (no limit).
48       */
49      public static final int DEFAULT_MAX_NEAREST_FINGERPRINTS = -1;
50  
51      /**
52       * Default exponent typically used on free space for path loss propagation in
53       * terms of distance. This value is used for free space environments.
54       */
55      public static final double DEFAULT_PATH_LOSS_EXPONENT = 2.0;
56  
57      /**
58       * Located fingerprints containing RSSI readings.
59       */
60      protected List<? extends RssiFingerprintLocated<? extends RadioSource,
61              ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints;
62  
63      /**
64       * Fingerprint containing readings at an unknown location.
65       * Readings need to belong to the same radio sources as those given at located
66       * fingerprints
67       */
68      protected RssiFingerprint<? extends RadioSource, ? extends RssiReading<? extends RadioSource>> fingerprint;
69  
70      /**
71       * Minimum number of nearest fingerprints to search.
72       */
73      protected int minNearestFingerprints = DEFAULT_MIN_NEAREST_FINGERPRINTS;
74  
75      /**
76       * Maximum number of nearest fingerprints to search or -1 if there is no limit and
77       * all provided fingerprints are used.
78       */
79      protected int maxNearestFingerprints = DEFAULT_MAX_NEAREST_FINGERPRINTS;
80  
81      /**
82       * Path loss exponent to be used by default.
83       * This is typically used on free space for path loss propagation in
84       * terms of distance.
85       * On different environments path loss exponent might have different values:
86       * - Free space: 2.0
87       * - Urban Area: 2.7 to 3.5
88       * - Suburban Area: 3 to 5
89       * - Indoor (line-of-sight): 1.6 to 1.8
90       */
91      protected double pathLossExponent = DEFAULT_PATH_LOSS_EXPONENT;
92  
93      /**
94       * Listener to be notified of events raised by this instance.
95       */
96      protected L listener;
97  
98      /**
99       * Estimated inhomogeneous position coordinates.
100      */
101     protected double[] estimatedPositionCoordinates;
102 
103     /**
104      * Nearest located fingerprints based on their RSSI readings respect to provided fingerprint.
105      * These are the fingerprints that are probably located close to the unknown location to be estimated,
106      * however their location is approximate due to errors on RSSI readings.
107      */
108     protected List<RssiFingerprintLocated<RadioSource, RssiReading<RadioSource>, P>> nearestFingerprints;
109 
110     /**
111      * Indicates if this instance is locked.
112      */
113     protected boolean locked;
114 
115     /**
116      * Constructor.
117      */
118     protected BaseFingerprintPositionEstimator() {
119     }
120 
121     /**
122      * Constructor.
123      *
124      * @param listener listener in charge of handling events.
125      */
126     protected BaseFingerprintPositionEstimator(final L listener) {
127         this.listener = listener;
128     }
129 
130     /**
131      * Constructor.
132      *
133      * @param locatedFingerprints located fingerprints containing RSSI readings.
134      * @param fingerprint         fingerprint containing readings at an unknown location
135      *                            for provided located fingerprints.
136      * @throws IllegalArgumentException if provided non located fingerprint is null,
137      *                                  located fingerprints value is null or there are not enough fingerprints or
138      *                                  readings within provided fingerprints (for 2D position estimation at least 2
139      *                                  located total readings are required among all fingerprints, for example 2
140      *                                  readings are required in a single fingerprint, or at least 2 fingerprints at
141      *                                  different locations containing a single reading are required. For 3D position
142      *                                  estimation 3 located total readings are required among all fingerprints).
143      */
144     protected BaseFingerprintPositionEstimator(
145             final List<? extends RssiFingerprintLocated<? extends RadioSource,
146                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints,
147             final RssiFingerprint<? extends RadioSource,
148                     ? extends RssiReading<? extends RadioSource>> fingerprint) {
149         internalSetLocatedFingerprints(locatedFingerprints);
150         internalSetFingerprint(fingerprint);
151     }
152 
153     /**
154      * Constructor.
155      *
156      * @param locatedFingerprints located fingerprints containing RSSI readings.
157      * @param fingerprint         fingerprint containing readings at an unknown location
158      *                            for provided located fingerprints.
159      * @param listener            listener in charge of handling events.
160      * @throws IllegalArgumentException if provided non located fingerprint is null,
161      *                                  located fingerprints value is null or there are not enough fingerprints or
162      *                                  readings within provided fingerprints (for 2D position estimation at least 2
163      *                                  located total readings are required among all fingerprints, for example 2
164      *                                  readings are required in a single fingerprint, or at least 2 fingerprints at
165      *                                  different locations containing a single reading are required. For 3D position
166      *                                  estimation 3 located total readings are required among all fingerprints).
167      */
168     protected BaseFingerprintPositionEstimator(
169             final List<? extends RssiFingerprintLocated<? extends RadioSource,
170                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints,
171             final RssiFingerprint<? extends RadioSource,
172                     ? extends RssiReading<? extends RadioSource>> fingerprint,
173             final L listener) {
174         this(listener);
175         internalSetLocatedFingerprints(locatedFingerprints);
176         internalSetFingerprint(fingerprint);
177     }
178 
179     /**
180      * Gets located fingerprints containing RSSI readings.
181      *
182      * @return located fingerprints containing RSSI readings.
183      */
184     public List<RssiFingerprintLocated<RadioSource, RssiReading<RadioSource>, P>> getLocatedFingerprints() {
185         //noinspection unchecked
186         return (List<RssiFingerprintLocated<RadioSource, RssiReading<RadioSource>,P>>) locatedFingerprints;
187     }
188 
189     /**
190      * Sets located fingerprints containing RSSI readings.
191      *
192      * @param locatedFingerprints located fingerprints containing RSSI readings.
193      * @throws LockedException          if estimator is locked.
194      * @throws IllegalArgumentException if provided value is null or there are not enough
195      *                                  fingerprints or readings within provided fingerprints (for 2D position estimation at
196      *                                  least 2 readings are required in a single fingerprint, or at least 2 fingerprints
197      *                                  at different locations containing a single reading are required. For 3D position
198      *                                  estimation 3 reading in a single fingerprint, or 3 fingerprints containing a single
199      *                                  reading or any combination resulting in at least 3 readings at different locations
200      *                                  are required).
201      */
202     public void setLocatedFingerprints(
203             final List<? extends RssiFingerprintLocated<? extends RadioSource,
204                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints) throws LockedException {
205         if (isLocked()) {
206             throw new LockedException();
207         }
208 
209         internalSetLocatedFingerprints(locatedFingerprints);
210     }
211 
212     /**
213      * Gets fingerprint containing readings at an unknown location for provided located
214      * fingerprints.
215      *
216      * @return fingerprint containing readings at an unknown location for provided located
217      * fingerprints.
218      */
219     public RssiFingerprint<RadioSource, RssiReading<RadioSource>> getFingerprint() {
220         //noinspection unchecked
221         return (RssiFingerprint<RadioSource, RssiReading<RadioSource>>) fingerprint;
222     }
223 
224     /**
225      * Sets fingerprint containing readings at an unknown location for provided located fingerprints.
226      *
227      * @param fingerprint fingerprint containing readings at an unknown location for provided located fingerprints.
228      * @throws LockedException          if estimator is locked.
229      * @throws IllegalArgumentException if provided value is null.
230      */
231     public void setFingerprint(
232             final RssiFingerprint<? extends RadioSource, ? extends RssiReading<? extends RadioSource>> fingerprint)
233             throws LockedException {
234         if (isLocked()) {
235             throw new LockedException();
236         }
237 
238         internalSetFingerprint(fingerprint);
239     }
240 
241     /**
242      * Get minimum number of nearest fingerprints to search.
243      *
244      * @return minimum number of nearest fingerprints.
245      */
246     public int getMinNearestFingerprints() {
247         return minNearestFingerprints;
248     }
249 
250     /**
251      * Gets maximum number of nearest fingerprints to search.
252      *
253      * @return maximum number of nearest fingerprints.
254      */
255     public int getMaxNearestFingerprints() {
256         return maxNearestFingerprints;
257     }
258 
259     /**
260      * Sets minimum and maximum number of nearest fingerprints to search.
261      *
262      * @param minNearestFingerprints minimum number of nearest fingerprints.
263      * @param maxNearestFingerprints maximum number of nearest fingerprints.
264      * @throws LockedException          if estimator is locked.
265      * @throws IllegalArgumentException if minimum value is larger than maximum value (as
266      *                                  long as it has a limit defined), or if minimum value is less than 1.
267      */
268     public void setMinMaxNearestFingerprints(
269             final int minNearestFingerprints, final int maxNearestFingerprints) throws LockedException {
270         if (isLocked()) {
271             throw new LockedException();
272         }
273 
274         internalSetMinMaxNearestFingerprints(minNearestFingerprints, maxNearestFingerprints);
275     }
276 
277     /**
278      * Gets path loss exponent to be used by default.
279      * This is typically used on free space for path loss propagation in
280      * terms of distance.
281      * On different environments path loss exponent might have different values:
282      * - Free space: 2.0
283      * - Urban Area: 2.7 to 3.5
284      * - Suburban Area: 3 to 5
285      * - Indoor (line-of-sight): 1.6 to 1.8
286      *
287      * @return path loss exponent to be used by default.
288      */
289     public double getPathLossExponent() {
290         return pathLossExponent;
291     }
292 
293     /**
294      * Sets path loss exponent to be used by default.
295      * This is typically used on free space for path loss propagation in
296      * terms of distance.
297      * On different environments path loss exponent might have different values:
298      * - Free space: 2.0
299      * - Urban Area: 2.7 to 3.5
300      * - Suburban Area: 3 to 5
301      * - Indoor (line-of-sight): 1.6 to 1.8
302      *
303      * @param pathLossExponent path loss exponent to be used by default.
304      * @throws LockedException if estimator is locked.
305      */
306     public void setPathLossExponent(final double pathLossExponent) throws LockedException {
307         if (isLocked()) {
308             throw new LockedException();
309         }
310         this.pathLossExponent = pathLossExponent;
311     }
312 
313     /**
314      * Gets listener to be notified of events raised by this instance.
315      *
316      * @return listener to be notified of events raised by this instance.
317      */
318     public L getListener() {
319         return listener;
320     }
321 
322     /**
323      * Sets listener to be notified of events raised by this instance.
324      *
325      * @param listener listener to be notified of events raised by this instance.
326      * @throws LockedException if estimator is locked.
327      */
328     public void setListener(final L listener) throws LockedException {
329         if (isLocked()) {
330             throw new LockedException();
331         }
332         this.listener = listener;
333     }
334 
335     /**
336      * Gets estimated inhomogeneous position coordinates.
337      *
338      * @return estimated inhomogeneous position coordinates.
339      */
340     public double[] getEstimatedPositionCoordinates() {
341         return estimatedPositionCoordinates;
342     }
343 
344     /**
345      * Gets estimated position and stores result into provided instance.
346      *
347      * @param estimatedPosition instance where estimated position will be stored.
348      */
349     public void getEstimatedPosition(final P estimatedPosition) {
350         if (estimatedPositionCoordinates != null) {
351             for (var i = 0; i < estimatedPositionCoordinates.length; i++) {
352                 estimatedPosition.setInhomogeneousCoordinate(i, estimatedPositionCoordinates[i]);
353             }
354         }
355     }
356 
357     /**
358      * Gets nearest found located fingerprints based on their RSSI readings respect to provided fingerprint.
359      * These are the fingerprints that are probably located close to the unknown location to be estimated,
360      * however their location is approximate due to errors on RSSI readings.
361      *
362      * @return nearest located fingerprints based on their RSSI readings or null if estimation has not been done yet.
363      */
364     public List<RssiFingerprintLocated<RadioSource, RssiReading<RadioSource>, P>> getNearestFingerprints() {
365         return nearestFingerprints;
366     }
367 
368     /**
369      * Returns boolean indicating whether this estimator is locked because an estimation
370      * is already in progress.
371      *
372      * @return true if estimator is locked, false otherwise.
373      */
374     public boolean isLocked() {
375         return locked;
376     }
377 
378     /**
379      * Gets number of dimensions of points.
380      *
381      * @return number of dimensions of points.
382      */
383     public abstract int getNumberOfDimensions();
384 
385     /**
386      * Indicates whether estimator is ready to find a solution.
387      *
388      * @return true if estimator is ready, false otherwise.
389      */
390     public abstract boolean isReady();
391 
392     /**
393      * Starts estimation based on provided located radio sources and readings of such radio sources at
394      * an unknown location.
395      *
396      * @throws LockedException                if estimator is locked.
397      * @throws NotReadyException              if estimator is not ready.
398      * @throws FingerprintEstimationException if estimation fails for some other reason.
399      */
400     public abstract void estimate() throws LockedException, NotReadyException, FingerprintEstimationException;
401 
402     /**
403      * Gets estimated position.
404      *
405      * @return estimated position.
406      */
407     public abstract P getEstimatedPosition();
408 
409     /**
410      * Gets total number of readings contained within provided fingerprints.
411      *
412      * @param locatedFingerprints fingerprints to be checked.
413      * @return total number of readings contained within provided fingerprints.
414      */
415     int totalReadings(
416             final List<? extends RssiFingerprintLocated<? extends RadioSource,
417                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints) {
418         if (locatedFingerprints == null) {
419             return 0;
420         }
421 
422         var totalReadings = 0;
423         for (final var fingerprint : locatedFingerprints) {
424             final var readings = fingerprint.getReadings();
425             if (readings != null) {
426                 totalReadings += readings.size();
427             }
428         }
429         return totalReadings;
430     }
431 
432     /**
433      * Internally sets located fingerprints containing RSSI readings.
434      *
435      * @param locatedFingerprints located fingerprints containing RSSI readings.
436      * @throws IllegalArgumentException if provided value is null or there are not enough
437      *                                  fingerprints or readings within provided fingerprints (for 2D position estimation at
438      *                                  least 2 located total readings are required among all fingerprints, for example
439      *                                  2 readings are required in a single fingerprint, or at least 2 fingerprints
440      *                                  at different locations containing a single reading are required. For 3D position
441      *                                  estimation 3 located total readings are required among all fingerprints).
442      */
443     private void internalSetLocatedFingerprints(
444             final List<? extends RssiFingerprintLocated<? extends RadioSource,
445                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints) {
446 
447         if (totalReadings(locatedFingerprints) < getNumberOfDimensions()) {
448             throw new IllegalArgumentException();
449         }
450 
451         this.locatedFingerprints = locatedFingerprints;
452     }
453 
454     /**
455      * Internally sets fingerprint containing readings at an unknown location for provided located fingerprints.
456      *
457      * @param fingerprint fingerprint containing readings at an unknown location for provided located fingerprints.
458      * @throws IllegalArgumentException if provided value is null.
459      */
460     private void internalSetFingerprint(
461             final RssiFingerprint<? extends RadioSource, ? extends RssiReading<? extends RadioSource>> fingerprint) {
462         if (fingerprint == null) {
463             throw new IllegalArgumentException();
464         }
465 
466         this.fingerprint = fingerprint;
467     }
468 
469     /**
470      * Sets minimum and maximum number of nearest fingerprints to search.
471      *
472      * @param minNearestFingerprints minimum number of nearest fingerprints.
473      * @param maxNearestFingerprints maximum number of nearest fingerprints.
474      * @throws IllegalArgumentException if minimum value is larger than maximum value (as
475      *                                  long as it has a limit defined), or if minimum value is less than 1.
476      */
477     @SuppressWarnings("Duplicates")
478     private void internalSetMinMaxNearestFingerprints(
479             final int minNearestFingerprints, final int maxNearestFingerprints) {
480         if (minNearestFingerprints < 1 || (maxNearestFingerprints >= 0
481                 && minNearestFingerprints > maxNearestFingerprints)) {
482             throw new IllegalArgumentException();
483         }
484 
485         this.minNearestFingerprints = minNearestFingerprints;
486         this.maxNearestFingerprints = maxNearestFingerprints;
487     }
488 }