]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/positioned.cxx
gcc warning fixes
[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 <queue>
29
30 #include <boost/algorithm/string/case_conv.hpp>
31 #include <boost/algorithm/string/predicate.hpp>
32
33 #include <simgear/timing/timestamp.hxx>
34 #include <simgear/debug/logstream.hxx>
35 #include <simgear/structure/exception.hxx>
36 #include <simgear/math/SGGeometry.hxx>
37
38 #include "positioned.hxx"
39
40 typedef std::multimap<std::string, FGPositioned*> NamedPositionedIndex;
41 typedef std::pair<NamedPositionedIndex::const_iterator, NamedPositionedIndex::const_iterator> NamedIndexRange;
42
43 using std::lower_bound;
44 using std::upper_bound;
45
46 static NamedPositionedIndex global_identIndex;
47 static NamedPositionedIndex global_nameIndex;
48
49 //////////////////////////////////////////////////////////////////////////////
50
51 namespace Octree
52 {
53
54 const double LEAF_SIZE = SG_NM_TO_METER * 8.0;
55 const double LEAF_SIZE_SQR = LEAF_SIZE * LEAF_SIZE;
56
57 /**
58  * Decorate an object with a double value, and use that value to order 
59  * items, for the purpoises of the STL algorithms
60  */
61 template <class T>
62 class Ordered
63 {
64 public:
65     Ordered(const T& v, double x) :
66         _order(x),
67         _inner(v)
68     {
69     }
70     
71     Ordered(const Ordered<T>& a) :
72         _order(a._order),
73         _inner(a._inner)
74     {
75     }
76     
77     Ordered<T>& operator=(const Ordered<T>& a)
78     {
79         _order = a._order;
80         _inner = a._inner;
81         return *this;
82     }
83     
84     bool operator<(const Ordered<T>& other) const
85     {
86         return _order < other._order;
87     }
88     
89     bool operator>(const Ordered<T>& other) const
90     {
91         return _order > other._order;
92     }
93     
94     const T& get() const
95         { return _inner; }
96     
97     double order() const
98         { return _order; }
99     
100 private:    
101     double _order;
102     T _inner;
103 };
104
105 class Node;
106 typedef Ordered<Node*> OrderedNode;
107 typedef std::greater<OrderedNode> FNPQCompare; 
108
109 /**
110  * the priority queue is fundamental to our search algorithm. When searching,
111  * we know the front of the queue is the nearest unexpanded node (to the search
112  * location). The default STL pqueue returns the 'largest' item from top(), so
113  * to get the smallest, we need to replace the default Compare functor (less<>)
114  * with greater<>.
115  */
116 typedef std::priority_queue<OrderedNode, std::vector<OrderedNode>, FNPQCompare> FindNearestPQueue;
117
118 typedef Ordered<FGPositioned*> OrderedPositioned;
119 typedef std::vector<OrderedPositioned> FindNearestResults;
120
121 Node* global_spatialOctree = NULL;
122
123 /**
124  * Octree node base class, tracks its bounding box and provides various
125  * queries relating to it
126  */
127 class Node
128 {
129 public:
130     bool contains(const SGVec3d& aPos) const
131     {
132         return intersects(aPos, _box);
133     }
134
135     double distSqrToNearest(const SGVec3d& aPos) const
136     {
137         return distSqr(aPos, _box.getClosestPoint(aPos));
138     }
139     
140     virtual void insert(FGPositioned* aP) = 0;
141     
142     virtual void visit(const SGVec3d& aPos, double aCutoff, 
143       FGPositioned::Filter* aFilter, 
144       FindNearestResults& aResults, FindNearestPQueue&) = 0;
145 protected:
146     Node(const SGBoxd &aBox) :
147         _box(aBox)
148     {
149     }
150     
151     const SGBoxd _box;
152 };
153
154 class Leaf : public Node
155 {
156 public:
157     Leaf(const SGBoxd& aBox) :
158         Node(aBox)
159     {
160     }
161     
162     const FGPositioned::List& members() const
163     { return _members; }
164     
165     virtual void insert(FGPositioned* aP)
166     {
167         _members.push_back(aP);
168     }
169     
170     virtual void visit(const SGVec3d& aPos, double aCutoff, 
171       FGPositioned::Filter* aFilter, 
172       FindNearestResults& aResults, FindNearestPQueue&)
173     {
174         int previousResultsSize = aResults.size();
175         int addedCount = 0;
176         
177         for (unsigned int i=0; i<_members.size(); ++i) {
178             FGPositioned* p = _members[i];
179             double d2 = distSqr(aPos, p->cart());
180             if (d2 > aCutoff) {
181                 continue;
182             }
183             
184             if (aFilter) {
185               if (aFilter->hasTypeRange() && !aFilter->passType(p->type())) {
186                 continue;
187               }
188       
189               if (!aFilter->pass(p)) {
190                 continue;
191               }
192             } // of have a filter
193
194             ++addedCount;
195             aResults.push_back(OrderedPositioned(p, d2));
196         }
197         
198         if (addedCount == 0) {
199           return;
200         }
201         
202       // keep aResults sorted
203         // sort the new items, usually just one or two items
204         std::sort(aResults.begin() + previousResultsSize, aResults.end());
205         
206         // merge the two sorted ranges together - in linear time
207         std::inplace_merge(aResults.begin(), 
208           aResults.begin() + previousResultsSize, aResults.end());
209       }
210 private:
211     FGPositioned::List _members;
212 };
213
214 class Branch : public Node
215 {
216 public:
217     Branch(const SGBoxd& aBox) :
218         Node(aBox)
219     {
220         memset(children, 0, sizeof(Node*) * 8);
221     }
222     
223     virtual void insert(FGPositioned* aP)
224     {
225         SGVec3d cart(aP->cart());
226         assert(contains(cart));
227         int childIndex = 0;
228         
229         SGVec3d center(_box.getCenter());
230     // tests must match indices in SGbox::getCorner
231         if (cart.x() < center.x()) {
232             childIndex += 1;
233         }
234         
235         if (cart.y() < center.y()) {
236             childIndex += 2;
237         }
238         
239         if (cart.z() < center.z()) {
240             childIndex += 4;
241         }
242         
243         Node* child = children[childIndex];
244         if (!child) { // lazy building of children
245             SGBoxd cb(boxForChild(childIndex));            
246             double d2 = dot(cb.getSize(), cb.getSize());
247             if (d2 < LEAF_SIZE_SQR) {
248                 child = new Leaf(cb);
249             } else {
250                 child = new Branch(cb);
251             }
252             
253             children[childIndex] = child; 
254         }
255         
256         child->insert(aP);
257     }
258     
259     virtual void visit(const SGVec3d& aPos, double aCutoff, 
260       FGPositioned::Filter*, 
261       FindNearestResults&, FindNearestPQueue& aQ)
262     {    
263         for (unsigned int i=0; i<8; ++i) {
264             if (!children[i]) {
265                 continue;
266             }
267             
268             double d2 = children[i]->distSqrToNearest(aPos);
269             if (d2 > aCutoff) {
270                 continue; // exceeded cutoff
271             }
272             
273             aQ.push(Ordered<Node*>(children[i], d2));
274         } // of child iteration
275     }
276     
277     
278 private:
279     /**
280      * Return the box for a child touching the specified corner
281      */
282     SGBoxd boxForChild(unsigned int aCorner) const
283     {
284         SGBoxd r(_box.getCenter());
285         r.expandBy(_box.getCorner(aCorner));
286         return r;
287     }
288     
289     Node* children[8];
290 };
291
292 void findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
293 {
294     aResults.clear();
295     FindNearestPQueue pq;
296     FindNearestResults results;
297     pq.push(Ordered<Node*>(global_spatialOctree, 0));
298     double cut = aCutoffM * aCutoffM;
299     
300     while (!pq.empty()) {
301         if (!results.empty()) {
302           // terminate the search if we have sufficent results, and we are
303           // sure no node still on the queue contains a closer match
304           double furthestResultOrder = results.back().order();
305           if ((results.size() >= aN) && (furthestResultOrder < pq.top().order())) {
306             break;
307           }
308         }
309         
310         Node* nd = pq.top().get();
311         pq.pop();
312         
313         nd->visit(aPos, cut, aFilter, results, pq);
314     } // of queue iteration
315     
316     // depending on leaf population, we may have (slighty) more results
317     // than requested
318     unsigned int numResults = std::min((unsigned int) results.size(), aN);
319   // copy results out
320     aResults.resize(numResults);
321     for (unsigned int r=0; r<numResults; ++r) {
322       aResults[r] = results[r].get();
323     }
324 }
325
326 void findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
327 {
328     aResults.clear();
329     FindNearestPQueue pq;
330     FindNearestResults results;
331     pq.push(Ordered<Node*>(global_spatialOctree, 0));
332     double rng = aRangeM * aRangeM;
333     
334     while (!pq.empty()) {
335         Node* nd = pq.top().get();
336         pq.pop();
337         
338         nd->visit(aPos, rng, aFilter, results, pq);
339     } // of queue iteration
340     
341     unsigned int numResults = results.size();
342   // copy results out
343     aResults.resize(numResults);
344     for (unsigned int r=0; r<numResults; ++r) {
345       aResults[r] = results[r].get();
346     }
347 }
348
349 } // of namespace Octree
350
351 //////////////////////////////////////////////////////////////////////////////
352
353 static void
354 addToIndices(FGPositioned* aPos)
355 {
356   assert(aPos);
357   if (!aPos->ident().empty()) {
358     global_identIndex.insert(global_identIndex.begin(), 
359       std::make_pair(aPos->ident(), aPos));
360   }
361   
362   if (!aPos->name().empty()) {
363     global_nameIndex.insert(global_nameIndex.begin(), 
364                              std::make_pair(aPos->name(), aPos));
365   }
366
367   if (!Octree::global_spatialOctree) {
368     double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
369     SGVec3d earthExtent(RADIUS_EARTH_M, RADIUS_EARTH_M, RADIUS_EARTH_M);
370     Octree::global_spatialOctree = new Octree::Branch(SGBox<double>(-earthExtent, earthExtent));
371   }
372   Octree::global_spatialOctree->insert(aPos);
373 }
374
375 static void
376 removeFromIndices(FGPositioned* aPos)
377 {
378   assert(aPos);
379   
380   if (!aPos->ident().empty()) {
381     NamedPositionedIndex::iterator it = global_identIndex.find(aPos->ident());
382     while (it != global_identIndex.end() && (it->first == aPos->ident())) {
383       if (it->second == aPos) {
384         global_identIndex.erase(it);
385         break;
386       }
387       
388       ++it;
389     } // of multimap walk
390   }
391   
392   if (!aPos->name().empty()) {
393     NamedPositionedIndex::iterator it = global_nameIndex.find(aPos->name());
394     while (it != global_nameIndex.end() && (it->first == aPos->name())) {
395       if (it->second == aPos) {
396         global_nameIndex.erase(it);
397         break;
398       }
399       
400       ++it;
401     } // of multimap walk
402   }
403 }
404
405 class DistanceOrdering
406 {
407 public:
408   DistanceOrdering(const SGGeod& aPos) :
409     mPos(SGVec3d::fromGeod(aPos))
410   { }
411   
412   bool operator()(const FGPositionedRef& a, const FGPositionedRef& b) const
413   {
414     if (!a || !b) {
415       throw sg_exception("empty reference passed to DistanceOrdering");
416     }
417   
418     double dA = distSqr(a->cart(), mPos),
419       dB = distSqr(b->cart(), mPos);
420     return dA < dB;
421   }
422
423 private:
424   SGVec3d mPos;
425 };
426
427 static void
428 sortByDistance(const SGGeod& aPos, FGPositioned::List& aResult)
429 {
430   std::sort(aResult.begin(), aResult.end(), DistanceOrdering(aPos));
431 }
432
433 static FGPositionedRef
434 namedFindClosest(const NamedPositionedIndex& aIndex, const std::string& aName, 
435                  const SGGeod& aOrigin, FGPositioned::Filter* aFilter)
436 {
437   NamedIndexRange range = aIndex.equal_range(aName);
438   if (range.first == range.second) {
439     return NULL;
440   }
441   
442 // common case, only one result. looks a bit ugly because these are
443 // sequential iterators, not random-access ones
444   NamedPositionedIndex::const_iterator check = range.first;
445   if (++check == range.second) {
446     // excellent, only one match in the range
447     FGPositioned* r = range.first->second;
448     if (aFilter) {
449       if (aFilter->hasTypeRange() && !aFilter->passType(r->type())) {
450         return NULL;
451       }
452       
453       if (!aFilter->pass(r)) {
454         return NULL;
455       }
456     } // of have a filter
457   
458     return r;
459   } // of short-circuit logic for single-element range
460   
461 // multiple matches, we need to actually check the distance to each one
462   double minDist = HUGE_VAL;
463   FGPositionedRef result;
464   NamedPositionedIndex::const_iterator it = range.first;
465   SGVec3d cartOrigin(SGVec3d::fromGeod(aOrigin));
466   
467   for (; it != range.second; ++it) {
468     FGPositioned* r = it->second;
469     if (aFilter) {
470       if (aFilter->hasTypeRange() && !aFilter->passType(r->type())) {
471         continue;
472       }
473       
474       if (!aFilter->pass(r)) {
475         continue;
476       }
477     }
478     
479   // find distance
480     double d2 = distSqr(cartOrigin, r->cart());
481     if (d2 < minDist) {
482       minDist = d2;
483       result = r;
484     }
485   }
486   
487   return result;
488 }
489
490 //////////////////////////////////////////////////////////////////////////////
491
492 class OrderByName
493 {
494 public:
495   bool operator()(FGPositioned* a, FGPositioned* b) const
496   {
497     return a->name() < b->name();
498   }
499 };
500
501 /**
502  * A special purpose helper (imported by FGAirport::searchNamesAndIdents) to
503  * implement the AirportList dialog. It's unfortunate that it needs to reside
504  * here, but for now it's least ugly solution.
505  */
506 char** searchAirportNamesAndIdents(const std::string& aFilter)
507 {
508   const std::ctype<char> &ct = std::use_facet<std::ctype<char> >(std::locale());
509   std::string filter(aFilter);
510   bool hasFilter = !filter.empty();
511   if (hasFilter) {
512     ct.toupper((char *)filter.data(), (char *)filter.data() + filter.size());
513   }
514   
515   NamedPositionedIndex::const_iterator it = global_identIndex.begin();
516   NamedPositionedIndex::const_iterator end = global_identIndex.end();
517   
518   // note this is a vector of raw pointers, not smart pointers, because it
519   // may get very large and smart-pointer-atomicity-locking then becomes a
520   // bottleneck for this case.
521   std::vector<FGPositioned*> matches;
522   std::string upper;
523   
524   for (; it != end; ++it) {
525     FGPositioned::Type ty = it->second->type();
526     if ((ty < FGPositioned::AIRPORT) || (ty > FGPositioned::SEAPORT)) {
527       continue;
528     }
529     
530     if (hasFilter && (it->second->ident().find(aFilter) == std::string::npos)) {
531       upper = it->second->name(); // string copy, sadly
532       ct.toupper((char *)upper.data(), (char *)upper.data() + upper.size());
533       if (upper.find(aFilter) == std::string::npos) {
534         continue;
535       }
536     }
537     
538     matches.push_back(it->second);
539   }
540   
541   // sort alphabetically on name
542   std::sort(matches.begin(), matches.end(), OrderByName());
543   
544   // convert results to format comptible with puaList
545   unsigned int numMatches = matches.size();
546   char** result = new char*[numMatches + 1];
547   result[numMatches] = NULL; // end-of-list marker
548   
549   // nasty code to avoid excessive string copying and allocations.
550   // We format results as follows (note whitespace!):
551   //   ' name-of-airport-chars   (ident)'
552   // so the total length is:
553   //    1 + strlen(name) + 4 + strlen(icao) + 1 + 1 (for the null)
554   // which gives a grand total of 7 + name-length + icao-length.
555   // note the ident can be three letters (non-ICAO local strip), four
556   // (default ICAO) or more (extended format ICAO)
557   for (unsigned int i=0; i<numMatches; ++i) {
558     int nameLength = matches[i]->name().size();
559     int icaoLength = matches[i]->ident().size();
560     char* entry = new char[7 + nameLength + icaoLength];
561     char* dst = entry;
562     *dst++ = ' ';
563     memcpy(dst, matches[i]->name().c_str(), nameLength);
564     dst += nameLength;
565     *dst++ = ' ';
566     *dst++ = ' ';
567     *dst++ = ' ';
568     *dst++ = '(';
569     memcpy(dst, matches[i]->ident().c_str(), icaoLength);
570     dst += icaoLength;
571     *dst++ = ')';
572     *dst++ = 0;
573     result[i] = entry;
574   }
575   
576   return result;
577 }
578
579 ///////////////////////////////////////////////////////////////////////////////
580
581 bool
582 FGPositioned::Filter::hasTypeRange() const
583 {
584   assert(minType() <= maxType());
585   return (minType() != INVALID) && (maxType() != INVALID);
586 }
587
588 bool
589 FGPositioned::Filter::passType(Type aTy) const
590 {
591   assert(hasTypeRange());
592   return (minType() <= aTy) && (maxType() >= aTy);
593 }
594
595 static FGPositioned::List
596 findAllSortedByRange(const NamedPositionedIndex& aIndex, 
597                              const std::string& aName, const SGGeod& aPos, FGPositioned::Filter* aFilter)
598 {
599   FGPositioned::List result;
600   NamedIndexRange range = aIndex.equal_range(aName);
601   for (; range.first != range.second; ++range.first) {
602     FGPositioned* candidate = range.first->second;
603     if (aFilter) {
604       if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
605         continue;
606       }
607       
608       if (!aFilter->pass(candidate)) {
609         continue;
610       }
611     }
612     
613     result.push_back(range.first->second);
614   }
615   
616   sortByDistance(aPos, result);
617   return result;
618 }
619
620 static FGPositionedRef
621 findWithPartial(const NamedPositionedIndex& aIndex, const std::string& aName, 
622                     FGPositioned::Filter* aFilter, int aOffset, bool& aNext)
623 {
624   // see comment in findNextWithPartialId concerning upperBoundId
625   std::string upperBoundId = aName;
626   upperBoundId[upperBoundId.size()-1]++;
627   NamedPositionedIndex::const_iterator upperBound = aIndex.lower_bound(upperBoundId);
628   
629   NamedIndexRange range = aIndex.equal_range(aName);
630   FGPositionedRef result;
631   
632   while (range.first != upperBound) {
633     for (; range.first != range.second; ++range.first) {
634       FGPositionedRef candidate = range.first->second;
635       if (aFilter) {
636         if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
637           continue;
638         }
639         
640         if (!aFilter->pass(candidate)) {
641           continue;
642         }
643       }
644       
645       if (result) {
646         aNext = true;
647         return result;
648       } else if (aOffset == 0) {
649         // okay, found our result. we need to go around once more to set aNext
650         result = candidate;
651       } else {
652         --aOffset; // seen one more valid result, decrement the count
653       }
654     }
655     
656     // Unable to match the filter with this range - try the next range.
657     range = aIndex.equal_range(range.second->first);
658   }
659   
660   // if we fell out, we reached the end of the valid range. We might have a
661   // valid result, but we definitiely don't have a next result.
662   aNext = false;
663   return result;  
664 }
665
666 ///////////////////////////////////////////////////////////////////////////////
667
668 FGPositioned::FGPositioned(Type ty, const std::string& aIdent, const SGGeod& aPos, bool aIndexed) :
669   mPosition(aPos),
670   mType(ty),
671   mIdent(aIdent)
672 {  
673   SGReferenced::get(this); // hold an owning ref, for the moment
674   
675   if (aIndexed) {
676     assert(ty != TAXIWAY && ty != PAVEMENT);
677     addToIndices(this);
678   }
679 }
680
681 FGPositioned::~FGPositioned()
682 {
683   //std::cout << "destroying:" << mIdent << "/" << nameForType(mType) << std::endl;
684   removeFromIndices(this);
685 }
686
687 FGPositioned*
688 FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
689 {
690   return new FGPositioned(WAYPOINT, aIdent, aPos, true);
691 }
692
693 SGVec3d
694 FGPositioned::cart() const
695 {
696   return SGVec3d::fromGeod(mPosition);
697 }
698
699 FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
700 {
701   if (aName.empty() || (aName == "")) {
702     return INVALID;
703   }
704
705   typedef struct {
706     const char* _name;
707     Type _ty;
708   } NameTypeEntry;
709   
710   const NameTypeEntry names[] = {
711     {"airport", AIRPORT},
712     {"vor", VOR},
713     {"ndb", NDB},
714     {"wpt", WAYPOINT},
715     {"fix", FIX},
716     {"tacan", TACAN},
717     {"dme", DME},
718   // aliases
719     {"waypoint", WAYPOINT},
720     {"apt", AIRPORT},
721     {"arpt", AIRPORT},
722     {"any", INVALID},
723     {"all", INVALID},
724     
725     {NULL, INVALID}
726   };
727   
728   std::string lowerName(boost::to_lower_copy(aName));
729   
730   for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) {
731     if (::strcmp(n->_name, lowerName.c_str()) == 0) {
732       return n->_ty;
733     }
734   }
735   
736   SG_LOG(SG_GENERAL, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
737   return INVALID;
738 }
739
740 const char* FGPositioned::nameForType(Type aTy)
741 {
742  switch (aTy) {
743  case RUNWAY: return "runway";
744  case TAXIWAY: return "taxiway";
745  case PAVEMENT: return "pavement";
746  case PARK_STAND: return "parking stand";
747  case FIX: return "fix";
748  case VOR: return "VOR";
749  case NDB: return "NDB";
750  case ILS: return "ILS";
751  case LOC: return "localiser";
752  case GS: return "glideslope";
753  case OM: return "outer-marker";
754  case MM: return "middle-marker";
755  case IM: return "inner-marker";
756  case AIRPORT: return "airport";
757  case HELIPORT: return "heliport";
758  case SEAPORT: return "seaport";
759  case WAYPOINT: return "waypoint";
760  case DME: return "dme";
761  case TACAN: return "tacan";
762  default:
763   return "unknown";
764  }
765 }
766
767 ///////////////////////////////////////////////////////////////////////////////
768 // search / query functions
769
770 FGPositionedRef
771 FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
772 {
773   return namedFindClosest(global_identIndex, aIdent, aPos, aFilter);
774 }
775
776 FGPositioned::List
777 FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
778 {
779   List result;
780   Octree::findAllWithinRange(SGVec3d::fromGeod(aPos), 
781     aRangeNm * SG_NM_TO_METER, aFilter, result);
782   return result;
783 }
784
785 FGPositioned::List
786 FGPositioned::findAllWithIdentSortedByRange(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
787 {
788   return findAllSortedByRange(global_identIndex, aIdent, aPos, aFilter);
789 }
790
791 FGPositioned::List
792 FGPositioned::findAllWithNameSortedByRange(const std::string& aName, const SGGeod& aPos, Filter* aFilter)
793 {
794   return findAllSortedByRange(global_nameIndex, aName, aPos, aFilter);
795 }
796
797 FGPositionedRef
798 FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
799 {
800    List l(findClosestN(aPos, 1, aCutoffNm, aFilter));
801    if (l.empty()) {
802       return NULL;
803    }
804    
805    assert(l.size() == 1);
806    return l.front();
807 }
808
809 FGPositioned::List
810 FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
811 {
812   List result;
813   Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result);
814   return result;
815 }
816
817 FGPositionedRef
818 FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
819 {
820   if (aId.empty()) {
821     return NULL;
822   }
823   
824   std::string id(boost::to_upper_copy(aId));
825
826   // It is essential to bound our search, to avoid iterating all the way to the end of the database.
827   // Do this by generating a second ID with the final character incremented by 1.
828   // e.g., if the partial ID is "KI", we wish to search "KIxxx" but not "KJ".
829   std::string upperBoundId = id;
830   upperBoundId[upperBoundId.size()-1]++;
831   NamedPositionedIndex::const_iterator upperBound = global_identIndex.lower_bound(upperBoundId);
832
833   NamedIndexRange range = global_identIndex.equal_range(id);
834   while (range.first != upperBound) {
835     for (; range.first != range.second; ++range.first) {
836       FGPositionedRef candidate = range.first->second;
837       if (aCur == candidate) {
838         aCur = NULL; // found our start point, next match will pass
839         continue;
840       }
841
842       if (aFilter) {
843         if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
844           continue;
845         }
846
847         if (!aFilter->pass(candidate)) {
848           continue;
849         }
850       }
851
852       if (!aCur) {
853         return candidate;
854       }
855     }
856
857     // Unable to match the filter with this range - try the next range.
858     range = global_identIndex.equal_range(range.second->first);
859   }
860
861   return NULL; // Reached the end of the valid sequence with no match.  
862 }
863   
864 FGPositionedRef
865 FGPositioned::findWithPartialId(const std::string& aId, Filter* aFilter, int aOffset, bool& aNext)
866 {
867   return findWithPartial(global_identIndex, aId, aFilter, aOffset, aNext);
868 }
869
870
871 FGPositionedRef
872 FGPositioned::findWithPartialName(const std::string& aName, Filter* aFilter, int aOffset, bool& aNext)
873 {
874   return findWithPartial(global_nameIndex, aName, aFilter, aOffset, aNext);
875 }
876
877 /**
878  * Wrapper filter which proxies to an inner filter, but also ensures the
879  * ident starts with supplied partial ident.
880  */
881 class PartialIdentFilter : public FGPositioned::Filter
882 {
883 public:
884   PartialIdentFilter(const std::string& ident, FGPositioned::Filter* filter) :
885     _inner(filter)
886   {
887     _ident = boost::to_upper_copy(ident);
888   }
889   
890   virtual bool pass(FGPositioned* aPos) const
891   {
892     if (!_inner->pass(aPos)) {
893       return false;
894     }
895     
896     return (boost::algorithm::starts_with(aPos->ident(), _ident));
897   }
898     
899   virtual FGPositioned::Type minType() const
900   { return _inner->minType(); }
901     
902   virtual FGPositioned::Type maxType() const
903   { return _inner->maxType(); }
904     
905 private:
906   std::string _ident;
907   FGPositioned::Filter* _inner;
908 };
909
910 static FGPositionedRef
911 findClosestWithPartial(const SGGeod& aPos, FGPositioned::Filter* aFilter, int aOffset, bool& aNext)
912 {
913   // why aOffset +2 ? at offset=3, we want the fourth search result, but also
914   // to know if the fifth result exists (to set aNext flag for iterative APIs)
915   FGPositioned::List matches;
916   Octree::findNearestN(SGVec3d::fromGeod(aPos), aOffset + 2, 1000 * SG_NM_TO_METER, aFilter, matches);
917   
918   if ((int) matches.size() <= aOffset) {
919     SG_LOG(SG_GENERAL, SG_INFO, "findClosestWithPartial, couldn't match enough with prefix");
920     aNext = false;
921     return NULL; // couldn't find a match within the cutoff distance
922   }
923   
924   aNext = ((int) matches.size() >= (aOffset + 2));
925   return matches[aOffset];
926
927 }
928
929 FGPositionedRef
930 FGPositioned::findClosestWithPartialId(const SGGeod& aPos, const std::string& aId, Filter* aFilter, int aOffset, bool& aNext)
931 {
932   PartialIdentFilter pf(aId, aFilter);
933   return findClosestWithPartial(aPos, &pf, aOffset, aNext);
934 }
935
936 /**
937  * Wrapper filter which proxies to an inner filter, but also ensures the
938  * name starts with supplied partial name.
939  */
940 class PartialNameFilter : public FGPositioned::Filter
941 {
942 public:
943   PartialNameFilter(const std::string& nm, FGPositioned::Filter* filter) :
944   _inner(filter)
945   {
946     _name = nm;
947   }
948   
949   virtual bool pass(FGPositioned* aPos) const
950   {
951     if (!_inner->pass(aPos)) {
952       return false;
953     }
954     
955     return (boost::algorithm::istarts_with(aPos->name(), _name));
956   }
957   
958   virtual FGPositioned::Type minType() const
959   { return _inner->minType(); }
960   
961   virtual FGPositioned::Type maxType() const
962   { return _inner->maxType(); }
963   
964 private:
965   std::string _name;
966   FGPositioned::Filter* _inner;
967 };
968
969 FGPositionedRef
970 FGPositioned::findClosestWithPartialName(const SGGeod& aPos, const std::string& aName, Filter* aFilter, int aOffset, bool& aNext)
971 {
972   PartialNameFilter pf(aName, aFilter);
973   return findClosestWithPartial(aPos, &pf, aOffset, aNext);
974 }
975