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