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.position;
17  
18  import com.irurueta.geometry.Point;
19  import com.irurueta.navigation.indoor.Fingerprint;
20  import com.irurueta.navigation.indoor.RadioSource;
21  import com.irurueta.navigation.indoor.RadioSourceLocated;
22  import com.irurueta.navigation.indoor.Reading;
23  import com.irurueta.navigation.indoor.ReadingType;
24  
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Comparator;
28  import java.util.HashMap;
29  import java.util.List;
30  
31  /**
32   * Evenly sorts readings of a fingerprint among different radio sources taking into
33   * account their respective quality scores as well.
34   *
35   * @param <P> a {@link Point} type.
36   * @param <R> a {@link Reading} type.
37   */
38  public class ReadingSorter<P extends Point<?>, R extends Reading<? extends RadioSource>> {
39  
40      /**
41       * Sources to be taken into account within readings.
42       */
43      private final List<? extends RadioSourceLocated<P>> sources;
44  
45      /**
46       * Fingerprint containing readings of different sources.
47       */
48      private final Fingerprint<? extends RadioSource, ? extends R> fingerprint;
49  
50      /**
51       * Quality scores associated to each radio source.
52       */
53      private final double[] sourceQualityScores;
54  
55      /**
56       * Quality scores associated to each reading within the fingerprint.
57       */
58      private final double[] fingerprintReadingsQualityScores;
59  
60      /**
61       * Contains sorted sources and readings.
62       */
63      private List<RadioSourceSourceWithQualityScore<P, R>> sortedSourcesAndReadings;
64  
65      /**
66       * Constructor.
67       *
68       * @param sources                          sources to take into account within
69       *                                         readings.
70       * @param fingerprint                      fingerprint containing readings of
71       *                                         different sources.
72       * @param sourceQualityScores              quality scores associated to each radio
73       *                                         source.
74       * @param fingerprintReadingsQualityScores quality scores associated to each reading
75       *                                         within the fingerprint.
76       * @throws IllegalArgumentException if number of source quality scores is not equal
77       *                                  to the number of sources, or if number of
78       *                                  fingerprint reading quality scores is not equal
79       *                                  to the number of readings within fingerprint.
80       */
81      ReadingSorter(final List<? extends RadioSourceLocated<P>> sources,
82                    final Fingerprint<? extends RadioSource, ? extends R> fingerprint,
83                    final double[] sourceQualityScores, final double[] fingerprintReadingsQualityScores) {
84          if (sources.size() != sourceQualityScores.length) {
85              throw new IllegalArgumentException();
86          }
87  
88          if (fingerprint.getReadings().size() != fingerprintReadingsQualityScores.length) {
89              throw new IllegalArgumentException();
90          }
91  
92          this.sources = sources;
93          this.fingerprint = fingerprint;
94          this.sourceQualityScores = sourceQualityScores;
95          this.fingerprintReadingsQualityScores = fingerprintReadingsQualityScores;
96      }
97  
98      /**
99       * Gets sources to be taken into account within readings.
100      *
101      * @return sources to be taken into account within readings.
102      */
103     public List<RadioSourceLocated<P>> getSources() {
104         //noinspection unchecked
105         return (List<RadioSourceLocated<P>>) sources;
106     }
107 
108     /**
109      * Gets fingerprint containing readings of different sources.
110      *
111      * @return fingerprint containing readings of different sources.
112      */
113     public Fingerprint<RadioSource, Reading<RadioSource>> getFingerprint() {
114         //noinspection unchecked
115         return (Fingerprint<RadioSource, Reading<RadioSource>>) fingerprint;
116     }
117 
118     /**
119      * Gets quality scores associated to each radio source.
120      *
121      * @return quality scores associated to each radio source.
122      */
123     double[] getSourceQualityScores() {
124         return sourceQualityScores;
125     }
126 
127     /**
128      * Gets quality scores associated to each reading within the fingerprint.
129      *
130      * @return quality scores associated to each reading within the fingerprint.
131      */
132     double[] getFingerprintReadingsQualityScores() {
133         return fingerprintReadingsQualityScores;
134     }
135 
136     /**
137      * Sorts readings with quality scores.
138      */
139     void sort() {
140 
141         final var sourcesMap = new HashMap<RadioSourceLocated<P>, RadioSourceSourceWithQualityScore<P, R>>();
142 
143         // build sources
144         final var sourcesWithQualityScores = new ArrayList<RadioSourceSourceWithQualityScore<P, R>>();
145         var sourcePosition = 0;
146         for (final var source : sources) {
147             final var sourceWithQualityScore = new RadioSourceSourceWithQualityScore<P, R>();
148             sourceWithQualityScore.source = source;
149             sourceWithQualityScore.qualityScore = sourceQualityScores[sourcePosition];
150             sourceWithQualityScore.position = sourcePosition;
151             sourceWithQualityScore.readingsWithQualityScores = new ArrayList<>();
152 
153             sourcesWithQualityScores.add(sourceWithQualityScore);
154 
155             sourcesMap.put(source, sourceWithQualityScore);
156 
157             sourcePosition++;
158         }
159 
160         // build readings
161         var readingPosition = 0;
162         for (@SuppressWarnings("unchecked") final var reading : fingerprint.getReadings()) {
163             if (!(reading.getSource() instanceof RadioSourceLocated)) {
164                 continue;
165             }
166 
167             //noinspection unchecked
168             final var radioSourceLocated = (RadioSourceLocated<P>) reading.getSource();
169             final var sourceWithQualityScore = sourcesMap.get(radioSourceLocated);
170 
171             if (sourceWithQualityScore == null) {
172                 continue;
173             }
174 
175             final var readingsWithQualityScores = sourceWithQualityScore.readingsWithQualityScores;
176 
177             final var readingWithQualityScore = new ReadingWithQualityScore<R>();
178             readingWithQualityScore.reading = reading;
179             readingWithQualityScore.qualityScore = fingerprintReadingsQualityScores[readingPosition];
180             readingWithQualityScore.position = readingPosition;
181 
182             readingsWithQualityScores.add(readingWithQualityScore);
183 
184             readingPosition++;
185         }
186 
187         // sort all readings within sources
188         for (final var sourceWithQualityScore : sourcesWithQualityScores) {
189             // sort all readings for this source from highest to lowest quality
190 
191             // noinspection unchecked
192             final ReadingWithQualityScore<R>[] readingsWithQualityScoresArray =
193                     new ReadingWithQualityScore[sourceWithQualityScore.readingsWithQualityScores.size()];
194             sourceWithQualityScore.readingsWithQualityScores.toArray(readingsWithQualityScoresArray);
195             Arrays.sort(readingsWithQualityScoresArray, new ReadingComparator<>());
196 
197             sourceWithQualityScore.readingsWithQualityScores = Arrays.asList(readingsWithQualityScoresArray);
198         }
199 
200         // sort all sources from highest to lowest quality
201 
202         // noinspection unchecked
203         final RadioSourceSourceWithQualityScore<P, R>[] sourcesWithQualityScoresArray =
204                 new RadioSourceSourceWithQualityScore[sourcesWithQualityScores.size()];
205         sourcesWithQualityScores.toArray(sourcesWithQualityScoresArray);
206         Arrays.sort(sourcesWithQualityScoresArray, new RadioSourceComparator<>());
207 
208         sortedSourcesAndReadings = Arrays.asList(sourcesWithQualityScoresArray);
209     }
210 
211     /**
212      * Gets sorted sources and readings.
213      *
214      * @return sorted sources and readings.
215      */
216     List<RadioSourceSourceWithQualityScore<P, R>> getSortedSourcesAndReadings() {
217         return sortedSourcesAndReadings;
218     }
219 
220     /**
221      * Contains a reading with its associated quality score.
222      *
223      * @param <R> a {@link Reading} type.
224      */
225     static class ReadingWithQualityScore<R extends Reading<? extends RadioSource>> {
226         /**
227          * A reading.
228          */
229         R reading;
230 
231         /**
232          * Reading quality score.
233          */
234         double qualityScore;
235 
236         /**
237          * Original position.
238          */
239         int position;
240     }
241 
242     /**
243      * Contains a radio source with its associated quality score.
244      *
245      * @param <P> a {@link Point} type.
246      * @param <R> a {@link Reading} type.
247      */
248     static class RadioSourceSourceWithQualityScore<P extends Point<?>, R extends Reading<? extends RadioSource>> {
249         /**
250          * Radio source.
251          */
252         RadioSourceLocated<P> source;
253 
254         /**
255          * Radio source quality score.
256          */
257         double qualityScore;
258 
259         /**
260          * Original position.
261          */
262         int position;
263 
264         List<ReadingWithQualityScore<R>> readingsWithQualityScores;
265     }
266 
267     /**
268      * Comparator to order readings based on their quality scores.
269      *
270      * @param <R> a {@link Reading} type.
271      */
272     private static class ReadingComparator<R extends Reading<? extends RadioSource>> implements
273             Comparator<ReadingWithQualityScore<R>> {
274 
275         /**
276          * Orders readings so that readings with higher scores go first.
277          *
278          * @param o1 1st item to be compared.
279          * @param o2 2nd item to be compared.
280          * @return -1 if first item goes first, 0 if both are equal and 1 if first item
281          * goes second.
282          */
283         @Override
284         public int compare(final ReadingWithQualityScore<R> o1, final ReadingWithQualityScore<R> o2) {
285 
286             // Take reading type into account so that ranging readings go first,
287             // then ranging+RSSI and finally RSSI readings.
288             if (o1.reading.getType() != o2.reading.getType()) {
289                 switch (o1.reading.getType()) {
290                     case RANGING_READING:
291                         // o1 goes first (o2 last)
292                         return -1;
293 
294                     case RANGING_AND_RSSI_READING:
295                         if (o2.reading.getType() == ReadingType.RANGING_READING) {
296                             // RANGING_READING
297                             // o2 goes first (o1 last)
298                             return 1;
299                         } else {
300                             // RSSI_READING
301                             // o1 goes first (o2 last)
302                             return -1;
303                         }
304                     case RSSI_READING:
305                     default:
306                         // o2 goes first (o1 last)
307                         return 1;
308                 }
309             }
310 
311             // same reading type
312 
313             // order by reading quality score
314             return Double.compare(o2.qualityScore, o1.qualityScore);
315         }
316     }
317 
318     /**
319      * Comparator to order radio sources based on their quality scores.
320      *
321      * @param <P> a {@link Point} type.
322      * @param <R> a {@link Reading} type.
323      */
324     private static class RadioSourceComparator<P extends Point<?>, R extends Reading<? extends RadioSource>> implements
325             Comparator<RadioSourceSourceWithQualityScore<P, R>> {
326 
327         /**
328          * Orders radio sources so that radio sources with higher scores go first.
329          *
330          * @param o1 1st item to be compared.
331          * @param o2 2nd item to be compared.
332          * @return -1 if first item goes first, 0 if both are equal and 1 if first item
333          * goes second.
334          */
335         @Override
336         public int compare(final RadioSourceSourceWithQualityScore<P, R> o1,
337                            final RadioSourceSourceWithQualityScore<P, R> o2) {
338             return Double.compare(o2.qualityScore, o1.qualityScore);
339         }
340     }
341 }