1 // positioned.cxx - base class for objects which are positioned
3 // Copyright (C) 2008 James Turner
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.
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.
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.
25 #include "positioned.hxx"
29 #include <algorithm> // for sort
32 #include <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string/predicate.hpp>
35 #include <simgear/timing/timestamp.hxx>
36 #include <simgear/debug/logstream.hxx>
37 #include <simgear/structure/exception.hxx>
38 #include <simgear/math/SGGeometry.hxx>
42 typedef std::multimap<std::string, FGPositioned*> NamedPositionedIndex;
43 typedef std::pair<NamedPositionedIndex::const_iterator, NamedPositionedIndex::const_iterator> NamedIndexRange;
45 using std::lower_bound;
46 using std::upper_bound;
48 static NamedPositionedIndex global_identIndex;
49 static NamedPositionedIndex global_nameIndex;
51 //////////////////////////////////////////////////////////////////////////////
56 const double LEAF_SIZE = SG_NM_TO_METER * 8.0;
57 const double LEAF_SIZE_SQR = LEAF_SIZE * LEAF_SIZE;
60 * Decorate an object with a double value, and use that value to order
61 * items, for the purpoises of the STL algorithms
67 Ordered(const T& v, double x) :
73 Ordered(const Ordered<T>& a) :
79 Ordered<T>& operator=(const Ordered<T>& a)
86 bool operator<(const Ordered<T>& other) const
88 return _order < other._order;
91 bool operator>(const Ordered<T>& other) const
93 return _order > other._order;
108 typedef Ordered<Node*> OrderedNode;
109 typedef std::greater<OrderedNode> FNPQCompare;
112 * the priority queue is fundamental to our search algorithm. When searching,
113 * we know the front of the queue is the nearest unexpanded node (to the search
114 * location). The default STL pqueue returns the 'largest' item from top(), so
115 * to get the smallest, we need to replace the default Compare functor (less<>)
118 typedef std::priority_queue<OrderedNode, std::vector<OrderedNode>, FNPQCompare> FindNearestPQueue;
120 typedef Ordered<FGPositioned*> OrderedPositioned;
121 typedef std::vector<OrderedPositioned> FindNearestResults;
123 Node* global_spatialOctree = NULL;
126 * Octree node base class, tracks its bounding box and provides various
127 * queries relating to it
132 bool contains(const SGVec3d& aPos) const
134 return intersects(aPos, _box);
137 double distSqrToNearest(const SGVec3d& aPos) const
139 return distSqr(aPos, _box.getClosestPoint(aPos));
142 virtual void insert(FGPositioned* aP) = 0;
144 virtual void visit(const SGVec3d& aPos, double aCutoff,
145 FGPositioned::Filter* aFilter,
146 FindNearestResults& aResults, FindNearestPQueue&) = 0;
148 Node(const SGBoxd &aBox) :
156 class Leaf : public Node
159 Leaf(const SGBoxd& aBox) :
164 const FGPositioned::List& members() const
167 virtual void insert(FGPositioned* aP)
169 _members.push_back(aP);
172 virtual void visit(const SGVec3d& aPos, double aCutoff,
173 FGPositioned::Filter* aFilter,
174 FindNearestResults& aResults, FindNearestPQueue&)
176 int previousResultsSize = aResults.size();
179 for (unsigned int i=0; i<_members.size(); ++i) {
180 FGPositioned* p = _members[i];
181 double d2 = distSqr(aPos, p->cart());
187 if (aFilter->hasTypeRange() && !aFilter->passType(p->type())) {
191 if (!aFilter->pass(p)) {
194 } // of have a filter
197 aResults.push_back(OrderedPositioned(p, d2));
200 if (addedCount == 0) {
204 // keep aResults sorted
205 // sort the new items, usually just one or two items
206 std::sort(aResults.begin() + previousResultsSize, aResults.end());
208 // merge the two sorted ranges together - in linear time
209 std::inplace_merge(aResults.begin(),
210 aResults.begin() + previousResultsSize, aResults.end());
213 FGPositioned::List _members;
216 class Branch : public Node
219 Branch(const SGBoxd& aBox) :
222 memset(children, 0, sizeof(Node*) * 8);
225 virtual void insert(FGPositioned* aP)
227 SGVec3d cart(aP->cart());
228 assert(contains(cart));
231 SGVec3d center(_box.getCenter());
232 // tests must match indices in SGbox::getCorner
233 if (cart.x() < center.x()) {
237 if (cart.y() < center.y()) {
241 if (cart.z() < center.z()) {
245 Node* child = children[childIndex];
246 if (!child) { // lazy building of children
247 SGBoxd cb(boxForChild(childIndex));
248 double d2 = dot(cb.getSize(), cb.getSize());
249 if (d2 < LEAF_SIZE_SQR) {
250 child = new Leaf(cb);
252 child = new Branch(cb);
255 children[childIndex] = child;
261 virtual void visit(const SGVec3d& aPos, double aCutoff,
262 FGPositioned::Filter*,
263 FindNearestResults&, FindNearestPQueue& aQ)
265 for (unsigned int i=0; i<8; ++i) {
270 double d2 = children[i]->distSqrToNearest(aPos);
272 continue; // exceeded cutoff
275 aQ.push(Ordered<Node*>(children[i], d2));
276 } // of child iteration
282 * Return the box for a child touching the specified corner
284 SGBoxd boxForChild(unsigned int aCorner) const
286 SGBoxd r(_box.getCenter());
287 r.expandBy(_box.getCorner(aCorner));
294 void findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
297 FindNearestPQueue pq;
298 FindNearestResults results;
299 pq.push(Ordered<Node*>(global_spatialOctree, 0));
300 double cut = aCutoffM * aCutoffM;
302 while (!pq.empty()) {
303 if (!results.empty()) {
304 // terminate the search if we have sufficent results, and we are
305 // sure no node still on the queue contains a closer match
306 double furthestResultOrder = results.back().order();
307 if ((results.size() >= aN) && (furthestResultOrder < pq.top().order())) {
312 Node* nd = pq.top().get();
315 nd->visit(aPos, cut, aFilter, results, pq);
316 } // of queue iteration
318 // depending on leaf population, we may have (slighty) more results
320 unsigned int numResults = std::min((unsigned int) results.size(), aN);
322 aResults.resize(numResults);
323 for (unsigned int r=0; r<numResults; ++r) {
324 aResults[r] = results[r].get();
328 void findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
331 FindNearestPQueue pq;
332 FindNearestResults results;
333 pq.push(Ordered<Node*>(global_spatialOctree, 0));
334 double rng = aRangeM * aRangeM;
336 while (!pq.empty()) {
337 Node* nd = pq.top().get();
340 nd->visit(aPos, rng, aFilter, results, pq);
341 } // of queue iteration
343 unsigned int numResults = results.size();
345 aResults.resize(numResults);
346 for (unsigned int r=0; r<numResults; ++r) {
347 aResults[r] = results[r].get();
351 } // of namespace Octree
353 //////////////////////////////////////////////////////////////////////////////
356 addToIndices(FGPositioned* aPos)
359 if (!aPos->ident().empty()) {
360 std::string u(boost::to_upper_copy(aPos->ident()));
362 global_identIndex.insert(global_identIndex.begin(),
363 std::make_pair(u, aPos));
366 if (!aPos->name().empty()) {
367 std::string u(boost::to_upper_copy(aPos->name()));
369 global_nameIndex.insert(global_nameIndex.begin(),
370 std::make_pair(u, aPos));
373 if (!Octree::global_spatialOctree) {
374 double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
375 SGVec3d earthExtent(RADIUS_EARTH_M, RADIUS_EARTH_M, RADIUS_EARTH_M);
376 Octree::global_spatialOctree = new Octree::Branch(SGBox<double>(-earthExtent, earthExtent));
378 Octree::global_spatialOctree->insert(aPos);
382 removeFromIndices(FGPositioned* aPos)
386 if (!aPos->ident().empty()) {
387 std::string u(boost::to_upper_copy(aPos->ident()));
388 NamedPositionedIndex::iterator it = global_identIndex.find(u);
389 while (it != global_identIndex.end() && (it->first == u)) {
390 if (it->second == aPos) {
391 global_identIndex.erase(it);
396 } // of multimap walk
399 if (!aPos->name().empty()) {
400 std::string u(boost::to_upper_copy(aPos->name()));
401 NamedPositionedIndex::iterator it = global_nameIndex.find(u);
402 while (it != global_nameIndex.end() && (it->first == u)) {
403 if (it->second == aPos) {
404 global_nameIndex.erase(it);
409 } // of multimap walk
413 //////////////////////////////////////////////////////////////////////////////
418 bool operator()(FGPositioned* a, FGPositioned* b) const
420 return a->name() < b->name();
424 void findInIndex(NamedPositionedIndex& aIndex, const std::string& aFind, std::vector<FGPositioned*>& aResult)
426 NamedPositionedIndex::const_iterator it = aIndex.begin();
427 NamedPositionedIndex::const_iterator end = aIndex.end();
429 bool haveFilter = !aFind.empty();
431 for (; it != end; ++it) {
432 FGPositioned::Type ty = it->second->type();
433 if ((ty < FGPositioned::AIRPORT) || (ty > FGPositioned::SEAPORT)) {
437 if (haveFilter && it->first.find(aFind) == std::string::npos) {
441 aResult.push_back(it->second);
442 } // of index iteration
446 * A special purpose helper (imported by FGAirport::searchNamesAndIdents) to
447 * implement the AirportList dialog. It's unfortunate that it needs to reside
448 * here, but for now it's least ugly solution.
450 char** searchAirportNamesAndIdents(const std::string& aFilter)
452 // note this is a vector of raw pointers, not smart pointers, because it
453 // may get very large and smart-pointer-atomicity-locking then becomes a
454 // bottleneck for this case.
455 std::vector<FGPositioned*> matches;
456 if (!aFilter.empty()) {
457 std::string filter = boost::to_upper_copy(aFilter);
458 findInIndex(global_identIndex, filter, matches);
459 findInIndex(global_nameIndex, filter, matches);
462 findInIndex(global_identIndex, std::string(), matches);
465 // sort alphabetically on name
466 std::sort(matches.begin(), matches.end(), OrderByName());
468 // convert results to format comptible with puaList
469 unsigned int numMatches = matches.size();
470 char** result = new char*[numMatches + 1];
471 result[numMatches] = NULL; // end-of-list marker
473 // nasty code to avoid excessive string copying and allocations.
474 // We format results as follows (note whitespace!):
475 // ' name-of-airport-chars (ident)'
476 // so the total length is:
477 // 1 + strlen(name) + 4 + strlen(icao) + 1 + 1 (for the null)
478 // which gives a grand total of 7 + name-length + icao-length.
479 // note the ident can be three letters (non-ICAO local strip), four
480 // (default ICAO) or more (extended format ICAO)
481 for (unsigned int i=0; i<numMatches; ++i) {
482 int nameLength = matches[i]->name().size();
483 int icaoLength = matches[i]->ident().size();
484 char* entry = new char[7 + nameLength + icaoLength];
487 memcpy(dst, matches[i]->name().c_str(), nameLength);
493 memcpy(dst, matches[i]->ident().c_str(), icaoLength);
503 ///////////////////////////////////////////////////////////////////////////////
506 FGPositioned::Filter::hasTypeRange() const
508 assert(minType() <= maxType());
509 return (minType() != INVALID) && (maxType() != INVALID);
513 FGPositioned::Filter::passType(Type aTy) const
515 assert(hasTypeRange());
516 return (minType() <= aTy) && (maxType() >= aTy);
519 static FGPositioned::List
520 findAll(const NamedPositionedIndex& aIndex,
521 const std::string& aName,
522 FGPositioned::Filter* aFilter,
525 FGPositioned::List result;
530 std::string name = boost::to_upper_copy(aName);
531 NamedPositionedIndex::const_iterator upperBound;
534 upperBound = aIndex.upper_bound(name);
536 std::string upperBoundId = name;
537 upperBoundId[upperBoundId.size()-1]++;
538 upperBound = aIndex.lower_bound(upperBoundId);
541 NamedPositionedIndex::const_iterator it = aIndex.lower_bound(name);
543 for (; it != upperBound; ++it) {
544 FGPositionedRef candidate = it->second;
546 if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
550 if (!aFilter->pass(candidate)) {
555 result.push_back(candidate);
561 ///////////////////////////////////////////////////////////////////////////////
563 FGPositioned::FGPositioned(Type ty, const std::string& aIdent, const SGGeod& aPos) :
570 void FGPositioned::init(bool aIndexed)
572 SGReferenced::get(this); // hold an owning ref, for the moment
573 mCart = SGVec3d::fromGeod(mPosition);
576 assert(mType != TAXIWAY && mType != PAVEMENT);
581 FGPositioned::~FGPositioned()
583 //std::cout << "destroying:" << mIdent << "/" << nameForType(mType) << std::endl;
584 removeFromIndices(this);
588 FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
590 FGPositioned* wpt = new FGPositioned(WAYPOINT, aIdent, aPos);
596 FGPositioned::cart() const
601 FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
603 if (aName.empty() || (aName == "")) {
612 const NameTypeEntry names[] = {
613 {"airport", AIRPORT},
621 {"waypoint", WAYPOINT},
630 std::string lowerName(boost::to_lower_copy(aName));
632 for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) {
633 if (::strcmp(n->_name, lowerName.c_str()) == 0) {
638 SG_LOG(SG_GENERAL, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
642 const char* FGPositioned::nameForType(Type aTy)
645 case RUNWAY: return "runway";
646 case TAXIWAY: return "taxiway";
647 case PAVEMENT: return "pavement";
648 case PARK_STAND: return "parking stand";
649 case FIX: return "fix";
650 case VOR: return "VOR";
651 case NDB: return "NDB";
652 case ILS: return "ILS";
653 case LOC: return "localiser";
654 case GS: return "glideslope";
655 case OM: return "outer-marker";
656 case MM: return "middle-marker";
657 case IM: return "inner-marker";
658 case AIRPORT: return "airport";
659 case HELIPORT: return "heliport";
660 case SEAPORT: return "seaport";
661 case WAYPOINT: return "waypoint";
662 case DME: return "dme";
663 case TACAN: return "tacan";
669 ///////////////////////////////////////////////////////////////////////////////
670 // search / query functions
673 FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
675 FGPositioned::List r(findAll(global_identIndex, aIdent, aFilter, true));
677 return FGPositionedRef();
680 sortByRange(r, aPos);
685 FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
688 Octree::findAllWithinRange(SGVec3d::fromGeod(aPos),
689 aRangeNm * SG_NM_TO_METER, aFilter, result);
694 FGPositioned::findAllWithIdent(const std::string& aIdent, Filter* aFilter, bool aExact)
696 return findAll(global_identIndex, aIdent, aFilter, aExact);
700 FGPositioned::findAllWithName(const std::string& aName, Filter* aFilter, bool aExact)
702 return findAll(global_nameIndex, aName, aFilter, aExact);
706 FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
708 List l(findClosestN(aPos, 1, aCutoffNm, aFilter));
713 assert(l.size() == 1);
718 FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
721 Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result);
726 FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
732 std::string id(boost::to_upper_copy(aId));
734 // It is essential to bound our search, to avoid iterating all the way to the end of the database.
735 // Do this by generating a second ID with the final character incremented by 1.
736 // e.g., if the partial ID is "KI", we wish to search "KIxxx" but not "KJ".
737 std::string upperBoundId = id;
738 upperBoundId[upperBoundId.size()-1]++;
739 NamedPositionedIndex::const_iterator upperBound = global_identIndex.lower_bound(upperBoundId);
741 NamedIndexRange range = global_identIndex.equal_range(id);
742 while (range.first != upperBound) {
743 for (; range.first != range.second; ++range.first) {
744 FGPositionedRef candidate = range.first->second;
745 if (aCur == candidate) {
746 aCur = NULL; // found our start point, next match will pass
751 if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
755 if (!aFilter->pass(candidate)) {
765 // Unable to match the filter with this range - try the next range.
766 range = global_identIndex.equal_range(range.second->first);
769 return NULL; // Reached the end of the valid sequence with no match.
773 FGPositioned::sortByRange(List& aResult, const SGGeod& aPos)
775 SGVec3d cartPos(SGVec3d::fromGeod(aPos));
776 // computer ordering values
777 Octree::FindNearestResults r;
778 List::iterator it = aResult.begin(), lend = aResult.end();
779 for (; it != lend; ++it) {
780 double d2 = distSqr((*it)->cart(), cartPos);
781 r.push_back(Octree::OrderedPositioned(*it, d2));
785 std::sort(r.begin(), r.end());
787 // convert to a plain list
788 unsigned int count = aResult.size();
789 for (unsigned int i=0; i<count; ++i) {
790 aResult[i] = r[i].get();