]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/positioned.cxx
Merge branch 'ehofman/sound'
[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 <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string/predicate.hpp>
34
35 #include <simgear/math/sg_geodesy.hxx>
36 #include <simgear/timing/timestamp.hxx>
37 #include <simgear/debug/logstream.hxx>
38 #include <simgear/structure/exception.hxx>
39
40 #include "positioned.hxx"
41
42 typedef std::multimap<std::string, FGPositioned*> NamedPositionedIndex;
43 typedef std::pair<NamedPositionedIndex::const_iterator, NamedPositionedIndex::const_iterator> NamedIndexRange;
44
45 using std::lower_bound;
46 using std::upper_bound;
47
48 /**
49  * Order positioned elements by type, then pointer address. This allows us to
50  * use range searches (lower_ and upper_bound) to grab items of a particular
51  * type out of bucket efficently.
52  */
53 class OrderByType
54 {
55 public:
56   bool operator()(const FGPositioned* a, const FGPositioned* b) const
57   {
58     if (a->type() == b->type()) return a < b;
59     return a->type() < b->type();
60   }
61 };
62
63 class LowerLimitOfType
64 {
65 public:
66   bool operator()(const FGPositioned* a, const FGPositioned::Type b) const
67   {
68     return a->type() < b;
69   }
70
71   bool operator()(const FGPositioned::Type a, const FGPositioned* b) const
72   {
73     return a < b->type();
74   }
75
76   // The operator below is required by VS2005 in debug mode
77   bool operator()(const FGPositioned* a, const FGPositioned* b) const
78   {
79     return a->type() < b->type();
80   }
81 };
82
83
84 typedef std::set<FGPositioned*, OrderByType> BucketEntry;
85 typedef std::map<long int, BucketEntry> SpatialPositionedIndex;
86
87 static NamedPositionedIndex global_identIndex;
88 static NamedPositionedIndex global_nameIndex;
89 static SpatialPositionedIndex global_spatialIndex;
90
91 SpatialPositionedIndex::iterator
92 bucketEntryForPositioned(FGPositioned* aPos)
93 {
94   int bucketIndex = aPos->bucket().gen_index();
95   SpatialPositionedIndex::iterator it = global_spatialIndex.find(bucketIndex);
96   if (it != global_spatialIndex.end()) {
97     return it;
98   }
99   
100   // create a new BucketEntry
101   return global_spatialIndex.insert(it, std::make_pair(bucketIndex, BucketEntry()));
102 }
103
104 static void
105 addToIndices(FGPositioned* aPos)
106 {
107   assert(aPos);
108   if (!aPos->ident().empty()) {
109     global_identIndex.insert(global_identIndex.begin(), 
110       std::make_pair(aPos->ident(), aPos));
111   }
112   
113   if (!aPos->name().empty()) {
114     global_nameIndex.insert(global_nameIndex.begin(), 
115                              std::make_pair(aPos->name(), aPos));
116   }
117
118   
119   SpatialPositionedIndex::iterator it = bucketEntryForPositioned(aPos);
120   it->second.insert(aPos);
121 }
122
123 static void
124 removeFromIndices(FGPositioned* aPos)
125 {
126   assert(aPos);
127   
128   if (!aPos->ident().empty()) {
129     NamedPositionedIndex::iterator it = global_identIndex.find(aPos->ident());
130     while (it != global_identIndex.end() && (it->first == aPos->ident())) {
131       if (it->second == aPos) {
132         global_identIndex.erase(it);
133         break;
134       }
135       
136       ++it;
137     } // of multimap walk
138   }
139   
140   if (!aPos->name().empty()) {
141     NamedPositionedIndex::iterator it = global_nameIndex.find(aPos->name());
142     while (it != global_nameIndex.end() && (it->first == aPos->name())) {
143       if (it->second == aPos) {
144         global_nameIndex.erase(it);
145         break;
146       }
147       
148       ++it;
149     } // of multimap walk
150   }
151
152   SpatialPositionedIndex::iterator sit = bucketEntryForPositioned(aPos);
153   sit->second.erase(aPos);
154 }
155
156 static void
157 spatialFilterInBucket(const SGBucket& aBucket, FGPositioned::Filter* aFilter, FGPositioned::List& aResult)
158 {
159   SpatialPositionedIndex::const_iterator it;
160   it = global_spatialIndex.find(aBucket.gen_index());
161   if (it == global_spatialIndex.end()) {
162     return;
163   }
164   
165   BucketEntry::const_iterator l = it->second.begin();
166   BucketEntry::const_iterator u = it->second.end();
167
168   if (!aFilter) { // pass everything
169     aResult.insert(aResult.end(), l, u);
170     return;
171   }
172
173   if (aFilter->hasTypeRange()) {
174     // avoid many calls to the filter hook
175     l = lower_bound(it->second.begin(), it->second.end(), aFilter->minType(), LowerLimitOfType());
176     u = upper_bound(l, it->second.end(), aFilter->maxType(), LowerLimitOfType());
177   }
178
179   for ( ; l != u; ++l) {
180     if ((*aFilter)(*l)) {
181       aResult.push_back(*l);
182     }
183   }
184 }
185
186 static void
187 spatialFind(const SGGeod& aPos, double aRange, 
188   FGPositioned::Filter* aFilter, FGPositioned::List& aResult)
189 {
190   SGBucket buck(aPos);
191   double lat = aPos.getLatitudeDeg(),
192     lon = aPos.getLongitudeDeg();
193   
194   int bx = (int)( aRange*SG_NM_TO_METER / buck.get_width_m() / 2);
195   int by = (int)( aRange*SG_NM_TO_METER / buck.get_height_m() / 2 );
196     
197   // loop over bucket range 
198   for ( int i=-bx; i<=bx; i++) {
199     for ( int j=-by; j<=by; j++) {
200       spatialFilterInBucket(sgBucketOffset(lon, lat, i, j), aFilter, aResult);
201     } // of j-iteration
202   } // of i-iteration  
203 }
204
205 /**
206  */
207 class RangePredictate
208 {
209 public:
210   RangePredictate(const SGGeod& aOrigin, double aRange) :
211     mOrigin(SGVec3d::fromGeod(aOrigin)),
212     mRangeSqr(aRange * aRange)
213   { ; }
214   
215   bool operator()(const FGPositionedRef& aPos)
216   {
217     double dSqr = distSqr(aPos->cart(), mOrigin);
218     return (dSqr > mRangeSqr);
219   }
220   
221 private:
222   SGVec3d mOrigin;
223   double mRangeSqr;
224 };
225
226 static void
227 filterListByRange(const SGGeod& aPos, double aRange, FGPositioned::List& aResult)
228 {
229   RangePredictate pred(aPos, aRange * SG_NM_TO_METER);
230   FGPositioned::List::iterator newEnd; 
231   newEnd = std::remove_if(aResult.begin(), aResult.end(), pred);
232   aResult.erase(newEnd, aResult.end());
233 }
234
235 class DistanceOrdering
236 {
237 public:
238   DistanceOrdering(const SGGeod& aPos) :
239     mPos(SGVec3d::fromGeod(aPos))
240   { }
241   
242   bool operator()(const FGPositionedRef& a, const FGPositionedRef& b) const
243   {
244     if (!a || !b) {
245       throw sg_exception("empty reference passed to DistanceOrdering");
246     }
247   
248     double dA = distSqr(a->cart(), mPos),
249       dB = distSqr(b->cart(), mPos);
250     return dA < dB;
251   }
252
253 private:
254   SGVec3d mPos;
255 };
256
257 static void
258 sortByDistance(const SGGeod& aPos, FGPositioned::List& aResult)
259 {
260   std::sort(aResult.begin(), aResult.end(), DistanceOrdering(aPos));
261 }
262
263 static FGPositionedRef
264 namedFindClosest(const NamedPositionedIndex& aIndex, const std::string& aName, 
265                  const SGGeod& aOrigin, FGPositioned::Filter* aFilter)
266 {
267   NamedIndexRange range = aIndex.equal_range(aName);
268   if (range.first == range.second) {
269     return NULL;
270   }
271   
272 // common case, only one result. looks a bit ugly because these are
273 // sequential iterators, not random-access ones
274   NamedPositionedIndex::const_iterator check = range.first;
275   if (++check == range.second) {
276     // excellent, only one match in the range
277     FGPositioned* r = range.first->second;
278     if (aFilter) {
279       if (aFilter->hasTypeRange() && !aFilter->passType(r->type())) {
280         return NULL;
281       }
282       
283       if (!aFilter->pass(r)) {
284         return NULL;
285       }
286     } // of have a filter
287   
288     return r;
289   } // of short-circuit logic for single-element range
290   
291 // multiple matches, we need to actually check the distance to each one
292   double minDist = HUGE_VAL;
293   FGPositionedRef result;
294   NamedPositionedIndex::const_iterator it = range.first;
295   SGVec3d cartOrigin(SGVec3d::fromGeod(aOrigin));
296   
297   for (; it != range.second; ++it) {
298     FGPositioned* r = it->second;
299     if (aFilter) {
300       if (aFilter->hasTypeRange() && !aFilter->passType(r->type())) {
301         continue;
302       }
303       
304       if (!aFilter->pass(r)) {
305         continue;
306       }
307     }
308     
309   // find distance
310     double d2 = distSqr(cartOrigin, r->cart());
311     if (d2 < minDist) {
312       minDist = d2;
313       result = r;
314     }
315   }
316   
317   return result;
318 }
319
320 static FGPositioned::List
321 spatialGetClosest(const SGGeod& aPos, unsigned int aN, double aCutoffNm, FGPositioned::Filter* aFilter)
322 {
323   FGPositioned::List result;
324   int radius = 1; // start at 1, radius 0 is handled explicitly
325   SGBucket buck;
326   double lat = aPos.getLatitudeDeg(),
327     lon = aPos.getLongitudeDeg();
328   // final cutoff is in metres, and scaled to account for testing the corners
329   // of the 'box' instead of the centre of each edge
330   double cutoffM = aCutoffNm * SG_NM_TO_METER * 1.5;
331   
332   // base case, simplifes loop to do it seperately here
333   spatialFilterInBucket(sgBucketOffset(lon, lat, 0, 0), aFilter, result);
334
335   for (;result.size() < aN; ++radius) {
336     // cutoff check
337     double az1, az2, d1, d2;
338     SGGeodesy::inverse(aPos, sgBucketOffset(lon, lat, -radius, -radius).get_center(), az1, az2, d1);
339     SGGeodesy::inverse(aPos, sgBucketOffset(lon, lat, radius, radius).get_center(), az1, az2, d2);  
340       
341     if ((d1 > cutoffM) && (d2 > cutoffM)) {
342       //std::cerr << "spatialGetClosest terminating due to range cutoff" << std::endl;
343       break;
344     }
345     
346     FGPositioned::List hits;
347     for ( int i=-radius; i<=radius; i++) {
348       spatialFilterInBucket(sgBucketOffset(lon, lat, i, -radius), aFilter, hits);
349       spatialFilterInBucket(sgBucketOffset(lon, lat, -radius, i), aFilter, hits);
350       spatialFilterInBucket(sgBucketOffset(lon, lat, i, radius), aFilter, hits);
351       spatialFilterInBucket(sgBucketOffset(lon, lat, radius, i), aFilter, hits);
352     }
353
354     result.insert(result.end(), hits.begin(), hits.end()); // append
355   } // of outer loop
356   
357   sortByDistance(aPos, result);
358   if (result.size() > aN) {
359     result.resize(aN); // truncate at requested number of matches
360   }
361   
362   return result;
363 }
364
365 //////////////////////////////////////////////////////////////////////////////
366
367 class OrderByName
368 {
369 public:
370   bool operator()(FGPositioned* a, FGPositioned* b) const
371   {
372     return a->name() < b->name();
373   }
374 };
375
376 /**
377  * A special purpose helper (imported by FGAirport::searchNamesAndIdents) to
378  * implement the AirportList dialog. It's unfortunate that it needs to reside
379  * here, but for now it's least ugly solution.
380  */
381 char** searchAirportNamesAndIdents(const std::string& aFilter)
382 {
383   const std::ctype<char> &ct = std::use_facet<std::ctype<char> >(std::locale());
384   std::string filter(aFilter);
385   bool hasFilter = !filter.empty();
386   if (hasFilter) {
387     ct.toupper((char *)filter.data(), (char *)filter.data() + filter.size());
388   }
389   
390   NamedPositionedIndex::const_iterator it = global_identIndex.begin();
391   NamedPositionedIndex::const_iterator end = global_identIndex.end();
392   
393   // note this is a vector of raw pointers, not smart pointers, because it
394   // may get very large and smart-pointer-atomicity-locking then becomes a
395   // bottleneck for this case.
396   std::vector<FGPositioned*> matches;
397   std::string upper;
398   
399   for (; it != end; ++it) {
400     FGPositioned::Type ty = it->second->type();
401     if ((ty < FGPositioned::AIRPORT) || (ty > FGPositioned::SEAPORT)) {
402       continue;
403     }
404     
405     if (hasFilter && (it->second->ident().find(aFilter) == std::string::npos)) {
406       upper = it->second->name(); // string copy, sadly
407       ct.toupper((char *)upper.data(), (char *)upper.data() + upper.size());
408       if (upper.find(aFilter) == std::string::npos) {
409         continue;
410       }
411     }
412     
413     matches.push_back(it->second);
414   }
415   
416   // sort alphabetically on name
417   std::sort(matches.begin(), matches.end(), OrderByName());
418   
419   // convert results to format comptible with puaList
420   unsigned int numMatches = matches.size();
421   char** result = new char*[numMatches + 1];
422   result[numMatches] = NULL; // end-of-list marker
423   
424   // nasty code to avoid excessive string copying and allocations.
425   // We format results as follows (note whitespace!):
426   //   ' name-of-airport-chars   (ident)'
427   // so the total length is:
428   //    1 + strlen(name) + 4 + 4 (for the ident) + 1 + 1 (for the null)
429   // which gives a grand total of 11 + the length of the name.
430   // note the ident is sometimes only three letters for non-ICAO small strips
431   for (unsigned int i=0; i<numMatches; ++i) {
432     int nameLength = matches[i]->name().size();
433     int icaoLength = matches[i]->ident().size();
434     char* entry = new char[nameLength + 11];
435     char* dst = entry;
436     *dst++ = ' ';
437     memcpy(dst, matches[i]->name().c_str(), nameLength);
438     dst += nameLength;
439     *dst++ = ' ';
440     *dst++ = ' ';
441     *dst++ = ' ';
442     *dst++ = '(';
443     memcpy(dst, matches[i]->ident().c_str(), icaoLength);
444     dst += icaoLength;
445     *dst++ = ')';
446     *dst++ = 0;
447     result[i] = entry;
448   }
449   
450   return result;
451 }
452
453 ///////////////////////////////////////////////////////////////////////////////
454
455 bool
456 FGPositioned::Filter::hasTypeRange() const
457 {
458   assert(minType() <= maxType());
459   return (minType() != INVALID) && (maxType() != INVALID);
460 }
461
462 bool
463 FGPositioned::Filter::passType(Type aTy) const
464 {
465   assert(hasTypeRange());
466   return (minType() <= aTy) && (maxType() >= aTy);
467 }
468
469 static FGPositioned::List
470 findAllSortedByRange(const NamedPositionedIndex& aIndex, 
471                              const std::string& aName, const SGGeod& aPos, FGPositioned::Filter* aFilter)
472 {
473   FGPositioned::List result;
474   NamedIndexRange range = aIndex.equal_range(aName);
475   for (; range.first != range.second; ++range.first) {
476     FGPositioned* candidate = range.first->second;
477     if (aFilter) {
478       if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
479         continue;
480       }
481       
482       if (!aFilter->pass(candidate)) {
483         continue;
484       }
485     }
486     
487     result.push_back(range.first->second);
488   }
489   
490   sortByDistance(aPos, result);
491   return result;
492 }
493
494 static FGPositionedRef
495 findWithPartial(const NamedPositionedIndex& aIndex, const std::string& aName, 
496                     FGPositioned::Filter* aFilter, int aOffset, bool& aNext)
497 {
498   // see comment in findNextWithPartialId concerning upperBoundId
499   std::string upperBoundId = aName;
500   upperBoundId[upperBoundId.size()-1]++;
501   NamedPositionedIndex::const_iterator upperBound = aIndex.lower_bound(upperBoundId);
502   
503   NamedIndexRange range = aIndex.equal_range(aName);
504   FGPositionedRef result;
505   
506   while (range.first != upperBound) {
507     for (; range.first != range.second; ++range.first) {
508       FGPositionedRef candidate = range.first->second;
509       if (aFilter) {
510         if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
511           continue;
512         }
513         
514         if (!aFilter->pass(candidate)) {
515           continue;
516         }
517       }
518       
519       if (result) {
520         aNext = true;
521         return result;
522       } else if (aOffset == 0) {
523         // okay, found our result. we need to go around once more to set aNext
524         result = candidate;
525       } else {
526         --aOffset; // seen one more valid result, decrement the count
527       }
528     }
529     
530     // Unable to match the filter with this range - try the next range.
531     range = aIndex.equal_range(range.second->first);
532   }
533   
534   // if we fell out, we reached the end of the valid range. We might have a
535   // valid result, but we definitiely don't have a next result.
536   aNext = false;
537   return result;  
538 }
539
540 ///////////////////////////////////////////////////////////////////////////////
541
542 FGPositioned::FGPositioned(Type ty, const std::string& aIdent, const SGGeod& aPos, bool aIndexed) :
543   mType(ty),
544   mPosition(aPos),
545   mIdent(aIdent)
546 {  
547   SGReferenced::get(this); // hold an owning ref, for the moment
548   
549   if (aIndexed) {
550     assert(ty != TAXIWAY && ty != PAVEMENT);
551     addToIndices(this);
552   }
553 }
554
555 FGPositioned::~FGPositioned()
556 {
557   //std::cout << "destroying:" << mIdent << "/" << nameForType(mType) << std::endl;
558   removeFromIndices(this);
559 }
560
561 FGPositioned*
562 FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
563 {
564   return new FGPositioned(WAYPOINT, aIdent, aPos, true);
565 }
566
567 SGBucket
568 FGPositioned::bucket() const
569 {
570   return SGBucket(mPosition);
571 }
572
573 SGVec3d
574 FGPositioned::cart() const
575 {
576   return SGVec3d::fromGeod(mPosition);
577 }
578
579 FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
580 {
581   if (aName.empty() || (aName == "")) {
582     return INVALID;
583   }
584
585   typedef struct {
586     const char* _name;
587     Type _ty;
588   } NameTypeEntry;
589   
590   const NameTypeEntry names[] = {
591     {"airport", AIRPORT},
592     {"vor", VOR},
593     {"ndb", NDB},
594     {"wpt", WAYPOINT},
595     {"fix", FIX},
596     {"tacan", TACAN},
597     {"dme", DME},
598   // aliases
599     {"waypoint", WAYPOINT},
600     {"apt", AIRPORT},
601     {"any", INVALID},
602     {"all", INVALID},
603     
604     {NULL, INVALID}
605   };
606   
607   std::string lowerName(boost::to_lower_copy(aName));
608   
609   for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) {
610     if (::strcmp(n->_name, lowerName.c_str()) == 0) {
611       return n->_ty;
612     }
613   }
614   
615   SG_LOG(SG_GENERAL, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
616   return INVALID;
617 }
618
619 const char* FGPositioned::nameForType(Type aTy)
620 {
621  switch (aTy) {
622  case RUNWAY: return "runway";
623  case TAXIWAY: return "taxiway";
624  case PAVEMENT: return "pavement";
625  case PARK_STAND: return "parking stand";
626  case FIX: return "fix";
627  case VOR: return "VOR";
628  case NDB: return "NDB";
629  case ILS: return "ILS";
630  case LOC: return "localiser";
631  case GS: return "glideslope";
632  case OM: return "outer-marker";
633  case MM: return "middle-marker";
634  case IM: return "inner-marker";
635  case AIRPORT: return "airport";
636  case HELIPORT: return "heliport";
637  case SEAPORT: return "seaport";
638  case WAYPOINT: return "waypoint";
639  case DME: return "dme";
640  case TACAN: return "tacan";
641  default:
642   return "unknown";
643  }
644 }
645
646 ///////////////////////////////////////////////////////////////////////////////
647 // search / query functions
648
649 FGPositionedRef
650 FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
651 {
652   return namedFindClosest(global_identIndex, aIdent, aPos, aFilter);
653 }
654
655 FGPositioned::List
656 FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
657 {
658   List result;
659   spatialFind(aPos, aRangeNm, aFilter, result);
660   filterListByRange(aPos, aRangeNm, result);
661   return result;
662 }
663
664 FGPositioned::List
665 FGPositioned::findAllWithIdentSortedByRange(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
666 {
667   return findAllSortedByRange(global_identIndex, aIdent, aPos, aFilter);
668 }
669
670 FGPositioned::List
671 FGPositioned::findAllWithNameSortedByRange(const std::string& aName, const SGGeod& aPos, Filter* aFilter)
672 {
673   return findAllSortedByRange(global_nameIndex, aName, aPos, aFilter);
674 }
675
676 FGPositionedRef
677 FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
678 {
679    FGPositioned::List l(spatialGetClosest(aPos, 1, aCutoffNm, aFilter));
680    if (l.empty()) {
681       return NULL;
682    }
683    
684    assert(l.size() == 1);
685    return l.front();
686 }
687
688 FGPositioned::List
689 FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
690 {
691   return spatialGetClosest(aPos, aN, aCutoffNm, aFilter);
692 }
693
694 FGPositionedRef
695 FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
696 {
697   std::string id(boost::to_upper_copy(aId));
698
699   // It is essential to bound our search, to avoid iterating all the way to the end of the database.
700   // Do this by generating a second ID with the final character incremented by 1.
701   // e.g., if the partial ID is "KI", we wish to search "KIxxx" but not "KJ".
702   std::string upperBoundId = id;
703   upperBoundId[upperBoundId.size()-1]++;
704   NamedPositionedIndex::const_iterator upperBound = global_identIndex.lower_bound(upperBoundId);
705
706   NamedIndexRange range = global_identIndex.equal_range(id);
707   while (range.first != upperBound) {
708     for (; range.first != range.second; ++range.first) {
709       FGPositionedRef candidate = range.first->second;
710       if (aCur == candidate) {
711         aCur = NULL; // found our start point, next match will pass
712         continue;
713       }
714
715       if (aFilter) {
716         if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
717           continue;
718         }
719
720         if (!aFilter->pass(candidate)) {
721           continue;
722         }
723       }
724
725       if (!aCur) {
726         return candidate;
727       }
728     }
729
730     // Unable to match the filter with this range - try the next range.
731     range = global_identIndex.equal_range(range.second->first);
732   }
733
734   return NULL; // Reached the end of the valid sequence with no match.  
735 }
736   
737 FGPositionedRef
738 FGPositioned::findWithPartialId(const std::string& aId, Filter* aFilter, int aOffset, bool& aNext)
739 {
740   return findWithPartial(global_identIndex, aId, aFilter, aOffset, aNext);
741 }
742
743
744 FGPositionedRef
745 FGPositioned::findWithPartialName(const std::string& aName, Filter* aFilter, int aOffset, bool& aNext)
746 {
747   return findWithPartial(global_nameIndex, aName, aFilter, aOffset, aNext);
748 }
749
750 /**
751  * Wrapper filter which proxies to an inner filter, but also ensures the
752  * ident starts with supplied partial ident.
753  */
754 class PartialIdentFilter : public FGPositioned::Filter
755 {
756 public:
757   PartialIdentFilter(const std::string& ident, FGPositioned::Filter* filter) :
758     _inner(filter)
759   {
760     _ident = boost::to_upper_copy(ident);
761   }
762   
763   virtual bool pass(FGPositioned* aPos) const
764   {
765     if (!_inner->pass(aPos)) {
766       return false;
767     }
768     
769     return (boost::algorithm::starts_with(aPos->ident(), _ident));
770   }
771     
772   virtual FGPositioned::Type minType() const
773   { return _inner->minType(); }
774     
775   virtual FGPositioned::Type maxType() const
776   { return _inner->maxType(); }
777     
778 private:
779   std::string _ident;
780   FGPositioned::Filter* _inner;
781 };
782
783 static FGPositionedRef
784 findClosestWithPartial(const SGGeod& aPos, FGPositioned::Filter* aFilter, int aOffset, bool& aNext)
785 {
786   // why aOffset +2 ? at offset=3, we want the fourth search result, but also
787   // to know if the fifth result exists (to set aNext flag for iterative APIs)
788   FGPositioned::List matches = 
789     spatialGetClosest(aPos, aOffset + 2, 1000.0, aFilter);
790   
791   if ((int) matches.size() <= aOffset) {
792     SG_LOG(SG_GENERAL, SG_INFO, "findClosestWithPartial, couldn't match enough with prefix");
793     aNext = false;
794     return NULL; // couldn't find a match within the cutoff distance
795   }
796   
797   aNext = ((int) matches.size() >= (aOffset + 2));
798   return matches[aOffset];
799
800 }
801
802 FGPositionedRef
803 FGPositioned::findClosestWithPartialId(const SGGeod& aPos, const std::string& aId, Filter* aFilter, int aOffset, bool& aNext)
804 {
805   PartialIdentFilter pf(aId, aFilter);
806   return findClosestWithPartial(aPos, &pf, aOffset, aNext);
807 }
808
809 /**
810  * Wrapper filter which proxies to an inner filter, but also ensures the
811  * name starts with supplied partial name.
812  */
813 class PartialNameFilter : public FGPositioned::Filter
814 {
815 public:
816   PartialNameFilter(const std::string& nm, FGPositioned::Filter* filter) :
817   _inner(filter)
818   {
819     _name = nm;
820   }
821   
822   virtual bool pass(FGPositioned* aPos) const
823   {
824     if (!_inner->pass(aPos)) {
825       return false;
826     }
827     
828     return (boost::algorithm::istarts_with(aPos->name(), _name));
829   }
830   
831   virtual FGPositioned::Type minType() const
832   { return _inner->minType(); }
833   
834   virtual FGPositioned::Type maxType() const
835   { return _inner->maxType(); }
836   
837 private:
838   std::string _name;
839   FGPositioned::Filter* _inner;
840 };
841
842 FGPositionedRef
843 FGPositioned::findClosestWithPartialName(const SGGeod& aPos, const std::string& aName, Filter* aFilter, int aOffset, bool& aNext)
844 {
845   PartialNameFilter pf(aName, aFilter);
846   return findClosestWithPartial(aPos, &pf, aOffset, aNext);
847 }
848