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 }