]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/positioned.cxx
Use methods from SGMath when possible.
[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 + 4 (for the ident) + 1 + 1 (for the null)
554   // which gives a grand total of 11 + the length of the name.
555   // note the ident is sometimes only three letters for non-ICAO small strips
556   for (unsigned int i=0; i<numMatches; ++i) {
557     int nameLength = matches[i]->name().size();
558     int icaoLength = matches[i]->ident().size();
559     char* entry = new char[nameLength + 11];
560     char* dst = entry;
561     *dst++ = ' ';
562     memcpy(dst, matches[i]->name().c_str(), nameLength);
563     dst += nameLength;
564     *dst++ = ' ';
565     *dst++ = ' ';
566     *dst++ = ' ';
567     *dst++ = '(';
568     memcpy(dst, matches[i]->ident().c_str(), icaoLength);
569     dst += icaoLength;
570     *dst++ = ')';
571     *dst++ = 0;
572     result[i] = entry;
573   }
574   
575   return result;
576 }
577
578 ///////////////////////////////////////////////////////////////////////////////
579
580 bool
581 FGPositioned::Filter::hasTypeRange() const
582 {
583   assert(minType() <= maxType());
584   return (minType() != INVALID) && (maxType() != INVALID);
585 }
586
587 bool
588 FGPositioned::Filter::passType(Type aTy) const
589 {
590   assert(hasTypeRange());
591   return (minType() <= aTy) && (maxType() >= aTy);
592 }
593
594 static FGPositioned::List
595 findAllSortedByRange(const NamedPositionedIndex& aIndex, 
596                              const std::string& aName, const SGGeod& aPos, FGPositioned::Filter* aFilter)
597 {
598   FGPositioned::List result;
599   NamedIndexRange range = aIndex.equal_range(aName);
600   for (; range.first != range.second; ++range.first) {
601     FGPositioned* candidate = range.first->second;
602     if (aFilter) {
603       if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
604         continue;
605       }
606       
607       if (!aFilter->pass(candidate)) {
608         continue;
609       }
610     }
611     
612     result.push_back(range.first->second);
613   }
614   
615   sortByDistance(aPos, result);
616   return result;
617 }
618
619 static FGPositionedRef
620 findWithPartial(const NamedPositionedIndex& aIndex, const std::string& aName, 
621                     FGPositioned::Filter* aFilter, int aOffset, bool& aNext)
622 {
623   // see comment in findNextWithPartialId concerning upperBoundId
624   std::string upperBoundId = aName;
625   upperBoundId[upperBoundId.size()-1]++;
626   NamedPositionedIndex::const_iterator upperBound = aIndex.lower_bound(upperBoundId);
627   
628   NamedIndexRange range = aIndex.equal_range(aName);
629   FGPositionedRef result;
630   
631   while (range.first != upperBound) {
632     for (; range.first != range.second; ++range.first) {
633       FGPositionedRef candidate = range.first->second;
634       if (aFilter) {
635         if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
636           continue;
637         }
638         
639         if (!aFilter->pass(candidate)) {
640           continue;
641         }
642       }
643       
644       if (result) {
645         aNext = true;
646         return result;
647       } else if (aOffset == 0) {
648         // okay, found our result. we need to go around once more to set aNext
649         result = candidate;
650       } else {
651         --aOffset; // seen one more valid result, decrement the count
652       }
653     }
654     
655     // Unable to match the filter with this range - try the next range.
656     range = aIndex.equal_range(range.second->first);
657   }
658   
659   // if we fell out, we reached the end of the valid range. We might have a
660   // valid result, but we definitiely don't have a next result.
661   aNext = false;
662   return result;  
663 }
664
665 ///////////////////////////////////////////////////////////////////////////////
666
667 FGPositioned::FGPositioned(Type ty, const std::string& aIdent, const SGGeod& aPos, bool aIndexed) :
668   mType(ty),
669   mPosition(aPos),
670   mIdent(aIdent)
671 {  
672   SGReferenced::get(this); // hold an owning ref, for the moment
673   
674   if (aIndexed) {
675     assert(ty != TAXIWAY && ty != PAVEMENT);
676     addToIndices(this);
677   }
678 }
679
680 FGPositioned::~FGPositioned()
681 {
682   //std::cout << "destroying:" << mIdent << "/" << nameForType(mType) << std::endl;
683   removeFromIndices(this);
684 }
685
686 FGPositioned*
687 FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
688 {
689   return new FGPositioned(WAYPOINT, aIdent, aPos, true);
690 }
691
692 SGVec3d
693 FGPositioned::cart() const
694 {
695   return SGVec3d::fromGeod(mPosition);
696 }
697
698 FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
699 {
700   if (aName.empty() || (aName == "")) {
701     return INVALID;
702   }
703
704   typedef struct {
705     const char* _name;
706     Type _ty;
707   } NameTypeEntry;
708   
709   const NameTypeEntry names[] = {
710     {"airport", AIRPORT},
711     {"vor", VOR},
712     {"ndb", NDB},
713     {"wpt", WAYPOINT},
714     {"fix", FIX},
715     {"tacan", TACAN},
716     {"dme", DME},
717   // aliases
718     {"waypoint", WAYPOINT},
719     {"apt", AIRPORT},
720     {"arpt", AIRPORT},
721     {"any", INVALID},
722     {"all", INVALID},
723     
724     {NULL, INVALID}
725   };
726   
727   std::string lowerName(boost::to_lower_copy(aName));
728   
729   for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) {
730     if (::strcmp(n->_name, lowerName.c_str()) == 0) {
731       return n->_ty;
732     }
733   }
734   
735   SG_LOG(SG_GENERAL, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
736   return INVALID;
737 }
738
739 const char* FGPositioned::nameForType(Type aTy)
740 {
741  switch (aTy) {
742  case RUNWAY: return "runway";
743  case TAXIWAY: return "taxiway";
744  case PAVEMENT: return "pavement";
745  case PARK_STAND: return "parking stand";
746  case FIX: return "fix";
747  case VOR: return "VOR";
748  case NDB: return "NDB";
749  case ILS: return "ILS";
750  case LOC: return "localiser";
751  case GS: return "glideslope";
752  case OM: return "outer-marker";
753  case MM: return "middle-marker";
754  case IM: return "inner-marker";
755  case AIRPORT: return "airport";
756  case HELIPORT: return "heliport";
757  case SEAPORT: return "seaport";
758  case WAYPOINT: return "waypoint";
759  case DME: return "dme";
760  case TACAN: return "tacan";
761  default:
762   return "unknown";
763  }
764 }
765
766 ///////////////////////////////////////////////////////////////////////////////
767 // search / query functions
768
769 FGPositionedRef
770 FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
771 {
772   return namedFindClosest(global_identIndex, aIdent, aPos, aFilter);
773 }
774
775 FGPositioned::List
776 FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
777 {
778   List result;
779   Octree::findAllWithinRange(SGVec3d::fromGeod(aPos), 
780     aRangeNm * SG_NM_TO_METER, aFilter, result);
781   return result;
782 }
783
784 FGPositioned::List
785 FGPositioned::findAllWithIdentSortedByRange(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
786 {
787   return findAllSortedByRange(global_identIndex, aIdent, aPos, aFilter);
788 }
789
790 FGPositioned::List
791 FGPositioned::findAllWithNameSortedByRange(const std::string& aName, const SGGeod& aPos, Filter* aFilter)
792 {
793   return findAllSortedByRange(global_nameIndex, aName, aPos, aFilter);
794 }
795
796 FGPositionedRef
797 FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
798 {
799    List l(findClosestN(aPos, 1, aCutoffNm, aFilter));
800    if (l.empty()) {
801       return NULL;
802    }
803    
804    assert(l.size() == 1);
805    return l.front();
806 }
807
808 FGPositioned::List
809 FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
810 {
811   List result;
812   Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result);
813   return result;
814 }
815
816 FGPositionedRef
817 FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
818 {
819   if (aId.empty()) {
820     return NULL;
821   }
822   
823   std::string id(boost::to_upper_copy(aId));
824
825   // It is essential to bound our search, to avoid iterating all the way to the end of the database.
826   // Do this by generating a second ID with the final character incremented by 1.
827   // e.g., if the partial ID is "KI", we wish to search "KIxxx" but not "KJ".
828   std::string upperBoundId = id;
829   upperBoundId[upperBoundId.size()-1]++;
830   NamedPositionedIndex::const_iterator upperBound = global_identIndex.lower_bound(upperBoundId);
831
832   NamedIndexRange range = global_identIndex.equal_range(id);
833   while (range.first != upperBound) {
834     for (; range.first != range.second; ++range.first) {
835       FGPositionedRef candidate = range.first->second;
836       if (aCur == candidate) {
837         aCur = NULL; // found our start point, next match will pass
838         continue;
839       }
840
841       if (aFilter) {
842         if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
843           continue;
844         }
845
846         if (!aFilter->pass(candidate)) {
847           continue;
848         }
849       }
850
851       if (!aCur) {
852         return candidate;
853       }
854     }
855
856     // Unable to match the filter with this range - try the next range.
857     range = global_identIndex.equal_range(range.second->first);
858   }
859
860   return NULL; // Reached the end of the valid sequence with no match.  
861 }
862   
863 FGPositionedRef
864 FGPositioned::findWithPartialId(const std::string& aId, Filter* aFilter, int aOffset, bool& aNext)
865 {
866   return findWithPartial(global_identIndex, aId, aFilter, aOffset, aNext);
867 }
868
869
870 FGPositionedRef
871 FGPositioned::findWithPartialName(const std::string& aName, Filter* aFilter, int aOffset, bool& aNext)
872 {
873   return findWithPartial(global_nameIndex, aName, aFilter, aOffset, aNext);
874 }
875
876 /**
877  * Wrapper filter which proxies to an inner filter, but also ensures the
878  * ident starts with supplied partial ident.
879  */
880 class PartialIdentFilter : public FGPositioned::Filter
881 {
882 public:
883   PartialIdentFilter(const std::string& ident, FGPositioned::Filter* filter) :
884     _inner(filter)
885   {
886     _ident = boost::to_upper_copy(ident);
887   }
888   
889   virtual bool pass(FGPositioned* aPos) const
890   {
891     if (!_inner->pass(aPos)) {
892       return false;
893     }
894     
895     return (boost::algorithm::starts_with(aPos->ident(), _ident));
896   }
897     
898   virtual FGPositioned::Type minType() const
899   { return _inner->minType(); }
900     
901   virtual FGPositioned::Type maxType() const
902   { return _inner->maxType(); }
903     
904 private:
905   std::string _ident;
906   FGPositioned::Filter* _inner;
907 };
908
909 static FGPositionedRef
910 findClosestWithPartial(const SGGeod& aPos, FGPositioned::Filter* aFilter, int aOffset, bool& aNext)
911 {
912   // why aOffset +2 ? at offset=3, we want the fourth search result, but also
913   // to know if the fifth result exists (to set aNext flag for iterative APIs)
914   FGPositioned::List matches;
915   Octree::findNearestN(SGVec3d::fromGeod(aPos), aOffset + 2, 1000 * SG_NM_TO_METER, aFilter, matches);
916   
917   if ((int) matches.size() <= aOffset) {
918     SG_LOG(SG_GENERAL, SG_INFO, "findClosestWithPartial, couldn't match enough with prefix");
919     aNext = false;
920     return NULL; // couldn't find a match within the cutoff distance
921   }
922   
923   aNext = ((int) matches.size() >= (aOffset + 2));
924   return matches[aOffset];
925
926 }
927
928 FGPositionedRef
929 FGPositioned::findClosestWithPartialId(const SGGeod& aPos, const std::string& aId, Filter* aFilter, int aOffset, bool& aNext)
930 {
931   PartialIdentFilter pf(aId, aFilter);
932   return findClosestWithPartial(aPos, &pf, aOffset, aNext);
933 }
934
935 /**
936  * Wrapper filter which proxies to an inner filter, but also ensures the
937  * name starts with supplied partial name.
938  */
939 class PartialNameFilter : public FGPositioned::Filter
940 {
941 public:
942   PartialNameFilter(const std::string& nm, FGPositioned::Filter* filter) :
943   _inner(filter)
944   {
945     _name = nm;
946   }
947   
948   virtual bool pass(FGPositioned* aPos) const
949   {
950     if (!_inner->pass(aPos)) {
951       return false;
952     }
953     
954     return (boost::algorithm::istarts_with(aPos->name(), _name));
955   }
956   
957   virtual FGPositioned::Type minType() const
958   { return _inner->minType(); }
959   
960   virtual FGPositioned::Type maxType() const
961   { return _inner->maxType(); }
962   
963 private:
964   std::string _name;
965   FGPositioned::Filter* _inner;
966 };
967
968 FGPositionedRef
969 FGPositioned::findClosestWithPartialName(const SGGeod& aPos, const std::string& aName, Filter* aFilter, int aOffset, bool& aNext)
970 {
971   PartialNameFilter pf(aName, aFilter);
972   return findClosestWithPartial(aPos, &pf, aOffset, aNext);
973 }
974