]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/positioned.cxx
Further work (still not enabled) on a fast + correct implementation of the
[flightgear.git] / src / Navaids / positioned.cxx
1 // positioned.cxx - base class for objects which are positioned 
2 //
3 // Copyright (C) 2008 James Turner
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 //
19 // $Id$
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include <map>
26 #include <set>
27 #include <algorithm> // for sort
28 #include <locale> // for char-traits toupper
29
30 #include <iostream>
31
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/timing/timestamp.hxx>
34
35 #include "positioned.hxx"
36
37 typedef std::multimap<std::string, FGPositioned*> NamedPositionedIndex;
38 typedef std::pair<NamedPositionedIndex::const_iterator, NamedPositionedIndex::const_iterator> NamedIndexRange;
39
40 /**
41  * Order positioned elements by type, then pointer address. This allows us to
42  * use range searches (lower_ and upper_bound) to grab items of a particular
43  * type out of bucket efficently.
44  */
45 class OrderByType
46 {
47 public:
48   bool operator()(const FGPositioned* a, const FGPositioned* b) const
49   {
50     if (a->type() == b->type()) return a < b;
51     return a->type() < b->type();
52   }
53 };
54
55 typedef std::set<FGPositioned*, OrderByType> BucketEntry;
56 typedef std::map<long int, BucketEntry> SpatialPositionedIndex;
57
58 static NamedPositionedIndex global_namedIndex;
59 static SpatialPositionedIndex global_spatialIndex;
60
61 SpatialPositionedIndex::iterator
62 bucketEntryForPositioned(FGPositioned* aPos)
63 {
64   int bucketIndex = aPos->bucket().gen_index();
65   SpatialPositionedIndex::iterator it = global_spatialIndex.find(bucketIndex);
66   if (it != global_spatialIndex.end()) {
67     return it;
68   }
69   
70   // create a new BucketEntry
71   return global_spatialIndex.insert(it, std::make_pair(bucketIndex, BucketEntry()));
72 }
73
74 static void
75 addToIndices(FGPositioned* aPos)
76 {
77   assert(aPos);
78   if (!aPos->ident().empty()) {
79     global_namedIndex.insert(global_namedIndex.begin(), 
80       std::make_pair(aPos->ident(), aPos));
81   }
82     
83   SpatialPositionedIndex::iterator it = bucketEntryForPositioned(aPos);
84   it->second.insert(aPos);
85 }
86
87 static void
88 removeFromIndices(FGPositioned* aPos)
89 {
90   assert(aPos);
91   
92   if (!aPos->ident().empty()) {
93     NamedPositionedIndex::iterator it = global_namedIndex.find(aPos->ident());
94     while (it != global_namedIndex.end() && (it->first == aPos->ident())) {
95       if (it->second == aPos) {
96         global_namedIndex.erase(it);
97         break;
98       }
99       
100       ++it;
101     } // of multimap walk
102   }
103   
104   SpatialPositionedIndex::iterator sit = bucketEntryForPositioned(aPos);
105   sit->second.erase(aPos);
106 }
107
108 static void
109 spatialFilterInBucket(const SGBucket& aBucket, FGPositioned::Filter* aFilter, FGPositioned::List& aResult)
110 {
111   SpatialPositionedIndex::const_iterator it;
112   it = global_spatialIndex.find(aBucket.gen_index());
113   if (it == global_spatialIndex.end()) {
114     return;
115   }
116   
117   BucketEntry::const_iterator l = it->second.begin();
118   BucketEntry::const_iterator u = it->second.end();
119
120   if (!aFilter) { // pass everything
121     aResult.insert(aResult.end(), l, u);
122     return;
123   }
124
125   for ( ; l != u; ++l) {
126     if ((*aFilter)(*l)) {
127       aResult.push_back(*l);
128     }
129   }
130 }
131
132 static void
133 spatialFind(const SGGeod& aPos, double aRange, 
134   FGPositioned::Filter* aFilter, FGPositioned::List& aResult)
135 {
136   SGBucket buck(aPos);
137   double lat = aPos.getLatitudeDeg(),
138     lon = aPos.getLongitudeDeg();
139   
140   int bx = (int)( aRange*SG_NM_TO_METER / buck.get_width_m() / 2);
141   int by = (int)( aRange*SG_NM_TO_METER / buck.get_height_m() / 2 );
142     
143   // loop over bucket range 
144   for ( int i=-bx; i<=bx; i++) {
145     for ( int j=-by; j<=by; j++) {
146       spatialFilterInBucket(sgBucketOffset(lon, lat, i, j), aFilter, aResult);
147     } // of j-iteration
148   } // of i-iteration  
149 }
150
151 /*
152 class LowerLimitOfType
153 {
154 public:
155   bool operator()(const FGPositioned* a, const FGPositioned::Type b) const
156   {
157     return a->type() < b;
158   }
159   
160   bool operator()(const FGPositioned::Type a, const FGPositioned* b) const
161   {
162     return a < b->type();
163   }
164 };
165
166
167 static void
168 spatialFindTyped(const SGGeod& aPos, double aRange, FGPositioned::Type aLower, FGPositioned::Type aUpper, FGPositioned::List& aResult)
169 {
170   SGBucket buck(aPos);
171   double lat = aPos.getLatitudeDeg(),
172     lon = aPos.getLongitudeDeg();
173   
174   int bx = (int)( aRange*SG_NM_TO_METER / buck.get_width_m() / 2);
175   int by = (int)( aRange*SG_NM_TO_METER / buck.get_height_m() / 2 );
176     
177   // loop over bucket range 
178   for ( int i=-bx; i<=bx; i++) {
179     for ( int j=-by; j<=by; j++) {
180       buck = sgBucketOffset(lon, lat, i, j);
181       
182       SpatialPositionedIndex::const_iterator it;
183       it = global_spatialIndex.find(buck.gen_index());
184       if (it == global_spatialIndex.end()) {
185         continue;
186       }
187       
188       BucketEntry::const_iterator l = std::lower_bound(it->second.begin(), it->second.end(), aLower, LowerLimitOfType());
189       BucketEntry::const_iterator u = std::upper_bound(l, it->second.end(), aUpper, LowerLimitOfType());
190       
191       for ( ; l != u; ++l) {
192         aResult.push_back(*l);
193       }
194       
195     } // of j-iteration
196   } // of i-iteration  
197 }
198 */
199
200 /**
201  */
202 class RangePredictate
203 {
204 public:
205   RangePredictate(const SGGeod& aOrigin, double aRange) :
206     mOrigin(SGVec3d::fromGeod(aOrigin)),
207     mRangeSqr(aRange * aRange)
208   { ; }
209   
210   bool operator()(const FGPositionedRef& aPos)
211   {
212     double dSqr = distSqr(aPos->cart(), mOrigin);
213     return (dSqr > mRangeSqr);
214   }
215   
216 private:
217   SGVec3d mOrigin;
218   double mRangeSqr;
219 };
220
221 static void
222 filterListByRange(const SGGeod& aPos, double aRange, FGPositioned::List& aResult)
223 {
224   RangePredictate pred(aPos, aRange * SG_NM_TO_METER);
225   FGPositioned::List::iterator newEnd; 
226   newEnd = std::remove_if(aResult.begin(), aResult.end(), pred);
227   aResult.erase(newEnd, aResult.end());
228 }
229
230 class DistanceOrdering
231 {
232 public:
233   DistanceOrdering(const SGGeod& aPos) :
234     mPos(SGVec3d::fromGeod(aPos))
235   { }
236   
237   bool operator()(const FGPositionedRef& a, const FGPositionedRef& b) const
238   {
239     double dA = distSqr(a->cart(), mPos),
240       dB = distSqr(b->cart(), mPos);
241     return dA < dB;
242   }
243
244 private:
245   SGVec3d mPos;
246 };
247
248 static void
249 sortByDistance(const SGGeod& aPos, FGPositioned::List& aResult)
250 {
251   std::sort(aResult.begin(), aResult.end(), DistanceOrdering(aPos));
252 }
253
254 static FGPositionedRef
255 namedFindClosest(const std::string& aIdent, const SGGeod& aOrigin, FGPositioned::Filter* aFilter)
256 {
257   NamedIndexRange range = global_namedIndex.equal_range(aIdent);
258   if (range.first == range.second) {
259     return NULL;
260   }
261   
262 // common case, only one result. looks a bit ugly because these are
263 // sequential iterators, not random-access ones
264   NamedPositionedIndex::const_iterator check = range.first;
265   if (++check == range.second) {
266     // excellent, only one match in the range - all we care about is the type
267     if (aFilter && !aFilter->pass(range.first->second)) {
268       return NULL; // type check failed
269     }
270     
271     return range.first->second;
272   } // of short-circuit logic for single-element range
273   
274 // multiple matches, we need to actually check the distance to each one
275   double minDist = HUGE_VAL;
276   FGPositionedRef result;
277   NamedPositionedIndex::const_iterator it = range.first;
278   SGVec3d cartOrigin(SGVec3d::fromGeod(aOrigin));
279   
280   for (; it != range.second; ++it) {
281     if (aFilter && !aFilter->pass(range.first->second)) {
282       continue;
283     }
284     
285   // find distance
286     double d2 = distSqr(cartOrigin, it->second->cart());
287     if (d2 < minDist) {
288       minDist = d2;
289       result = it->second;
290     }
291   }
292   
293   return result;
294 }
295
296 static FGPositioned::List
297 spatialGetClosest(const SGGeod& aPos, unsigned int aN, double aCutoffNm, FGPositioned::Filter* aFilter)
298 {
299   FGPositioned::List result;
300   int radius = 1; // start at 1, radius 0 is handled explicitly
301   SGBucket buck;
302   double lat = aPos.getLatitudeDeg(),
303     lon = aPos.getLongitudeDeg();
304   // final cutoff is in metres, and scaled to account for testing the corners
305   // of the 'box' instead of the centre of each edge
306   double cutoffM = aCutoffNm * SG_NM_TO_METER * 1.5;
307   
308   // base case, simplifes loop to do it seperately here
309   spatialFilterInBucket(sgBucketOffset(lon, lat, 0, 0), aFilter, result);
310
311   for (;result.size() < aN; ++radius) {
312     // cutoff check
313     double az1, az2, d1, d2;
314     SGGeodesy::inverse(aPos, sgBucketOffset(lon, lat, -radius, -radius).get_center(), az1, az2, d1);
315     SGGeodesy::inverse(aPos, sgBucketOffset(lon, lat, radius, radius).get_center(), az1, az2, d2);  
316       
317     if ((d1 > cutoffM) && (d2 > cutoffM)) {
318       //std::cerr << "spatialGetClosest terminating due to range cutoff" << std::endl;
319       break;
320     }
321     
322     FGPositioned::List hits;
323     for ( int i=-radius; i<=radius; i++) {
324       spatialFilterInBucket(sgBucketOffset(lon, lat, i, -radius), aFilter, hits);
325       spatialFilterInBucket(sgBucketOffset(lon, lat, -radius, i), aFilter, hits);
326       spatialFilterInBucket(sgBucketOffset(lon, lat, i, radius), aFilter, hits);
327       spatialFilterInBucket(sgBucketOffset(lon, lat, radius, i), aFilter, hits);
328     }
329
330     result.insert(result.end(), hits.begin(), hits.end()); // append
331   } // of outer loop
332   
333   sortByDistance(aPos, result);
334   if (result.size() > aN) {
335     result.resize(aN); // truncate at requested number of matches
336   }
337   
338   return result;
339 }
340
341 //////////////////////////////////////////////////////////////////////////////
342
343 class OrderByName
344 {
345 public:
346   bool operator()(FGPositioned* a, FGPositioned* b) const
347   {
348     return a->name() < b->name();
349   }
350 };
351
352 /**
353  * A special purpose helper (imported by FGAirport::searchNamesAndIdents) to
354  * implement the AirportList dialog. It's unfortunate that it needs to reside
355  * here, but for now it's least ugly solution.
356  */
357 char** searchAirportNamesAndIdents(const std::string& aFilter)
358 {
359   const std::ctype<char> &ct = std::use_facet<std::ctype<char> >(std::locale());
360   std::string filter(aFilter);
361   bool hasFilter = !filter.empty();
362   if (hasFilter) {
363     ct.toupper((char *)filter.data(), (char *)filter.data() + filter.size());
364   }
365   
366   NamedPositionedIndex::const_iterator it = global_namedIndex.begin();
367   NamedPositionedIndex::const_iterator end = global_namedIndex.end();
368   
369   // note this is a vector of raw pointers, not smart pointers, because it
370   // may get very large and smart-pointer-atomicity-locking then becomes a
371   // bottleneck for this case.
372   std::vector<FGPositioned*> matches;
373   
374   for (; it != end; ++it) {
375     FGPositioned::Type ty = it->second->type();
376     if ((ty < FGPositioned::AIRPORT) || (ty > FGPositioned::SEAPORT)) {
377       continue;
378     }
379     
380     if (hasFilter &&
381         (it->second->name().find(aFilter) == std::string::npos) &&
382         (it->second->ident().find(aFilter) == std::string::npos)) {
383       continue;
384     }
385     
386     matches.push_back(it->second);
387   }
388   
389   // sort alphabetically on name
390   std::sort(matches.begin(), matches.end(), OrderByName());
391   
392   // convert results to format comptible with puaList
393   unsigned int numMatches = matches.size();
394   char** result = new char*[numMatches + 1];
395   result[numMatches] = NULL; // end-of-list marker
396   
397   // nasty code to avoid excessive string copying and allocations.
398   // We format results as follows (note whitespace!):
399   //   ' name-of-airport-chars   (icao)'
400   // so the total length is:
401   //    1 + strlen(name) + 4 + 4 (for the ICAO) + 1 + 1 (for the null)
402   // which gives a grand total of 11 + the length of the name.
403   
404   for (unsigned int i=0; i<numMatches; ++i) {
405     int nameLength = matches[i]->name().size();
406     char* entry = new char[nameLength + 11];
407     entry[0] = ' ';
408     memcpy(entry + 1, matches[i]->name().c_str(), nameLength);
409     entry[nameLength + 1] = ' ';
410     entry[nameLength + 2] = ' ';
411     entry[nameLength + 3] = ' ';
412     entry[nameLength + 4] = '(';
413     memcpy(entry + nameLength + 5, matches[i]->ident().c_str(), 4);
414     entry[nameLength + 9] = ')';
415     entry[nameLength + 10] = 0;
416     result[i] = entry;
417   }
418   
419   return result;
420 }
421
422 ///////////////////////////////////////////////////////////////////////////////
423
424 FGPositioned::FGPositioned(Type ty, const std::string& aIdent, const SGGeod& aPos, bool aIndexed) :
425   mType(ty),
426   mPosition(aPos),
427   mIdent(aIdent)
428 {  
429   SGReferenced::get(this); // hold an owning ref, for the moment
430   
431   if (aIndexed) {
432     assert(ty != TAXIWAY);
433     addToIndices(this);
434   }
435 }
436
437 FGPositioned::~FGPositioned()
438 {
439   //std::cout << "destroying:" << mIdent << "/" << nameForType(mType) << std::endl;
440   removeFromIndices(this);
441 }
442
443 SGBucket
444 FGPositioned::bucket() const
445 {
446   return SGBucket(mPosition);
447 }
448
449 SGVec3d
450 FGPositioned::cart() const
451 {
452   return SGVec3d::fromGeod(mPosition);
453 }
454
455 const char* FGPositioned::nameForType(Type aTy)
456 {
457  switch (aTy) {
458  case RUNWAY: return "runway";
459  case TAXIWAY: return "taxiway";
460  case PARK_STAND: return "parking stand";
461  case FIX: return "fix";
462  case VOR: return "VOR";
463  case NDB: return "NDB";
464  case ILS: return "ILS";
465  case LOC: return "localiser";
466  case GS: return "glideslope";
467  case OM: return "outer-marker";
468  case MM: return "middle-marker";
469  case IM: return "inner-marker";
470  case AIRPORT: return "airport";
471  case HELIPORT: return "heliport";
472  case SEAPORT: return "seaport";
473  case WAYPOINT: return "waypoint";
474  case DME: return "dme";
475  case TACAN: return "tacan";
476  default:
477   return "unknown";
478  }
479 }
480
481 ///////////////////////////////////////////////////////////////////////////////
482 // search / query functions
483
484 FGPositionedRef
485 FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
486 {
487   return namedFindClosest(aIdent, aPos, aFilter);
488 }
489
490 FGPositioned::List
491 FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
492 {
493   List result;
494   spatialFind(aPos, aRangeNm, aFilter, result);
495   filterListByRange(aPos, aRangeNm, result);
496   return result;
497 }
498
499 FGPositioned::List
500 FGPositioned::findAllWithIdentSortedByRange(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
501 {
502   List result;
503   NamedIndexRange range = global_namedIndex.equal_range(aIdent);
504   for (; range.first != range.second; ++range.first) {
505     if (aFilter && !aFilter->pass(range.first->second)) {
506       continue;
507     }
508     
509     result.push_back(range.first->second);
510   }
511   
512   sortByDistance(aPos, result);
513   return result;
514 }
515
516 FGPositionedRef
517 FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
518 {
519    FGPositioned::List l(spatialGetClosest(aPos, 1, aCutoffNm, aFilter));
520    if (l.empty()) {
521       return NULL;
522    }
523    
524    assert(l.size() == 1);
525    return l.front();
526 }
527
528 FGPositioned::List
529 FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
530 {
531   return spatialGetClosest(aPos, aN, aCutoffNm, aFilter);
532 }
533
534 FGPositionedRef
535 FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
536 {
537   NamedIndexRange range = global_namedIndex.equal_range(aId);
538   for (; range.first != range.second; ++range.first) {
539     FGPositionedRef candidate = range.first->second;
540     if (aCur == candidate) {
541       aCur = NULL; // found our start point, next match will pass
542       continue;
543     }
544     
545     if (aFilter && !aFilter->pass(candidate)) {
546       continue;
547     }
548   
549     if (!aCur) {
550       return candidate;
551     }
552   }
553   
554   return NULL; // fell out, no match in range
555 }
556