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