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