813 lines
21 KiB
C++
813 lines
21 KiB
C++
// positioned.cxx - base class for objects which are positioned
|
|
//
|
|
// Copyright (C) 2008 James Turner
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
//
|
|
// $Id$
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "positioned.hxx"
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <algorithm> // for sort
|
|
#include <queue>
|
|
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <osg/Math> // for osg::isNaN
|
|
|
|
#include <simgear/timing/timestamp.hxx>
|
|
#include <simgear/debug/logstream.hxx>
|
|
#include <simgear/structure/exception.hxx>
|
|
#include <simgear/math/SGGeometry.hxx>
|
|
|
|
|
|
|
|
typedef std::multimap<std::string, FGPositioned*> NamedPositionedIndex;
|
|
typedef std::pair<NamedPositionedIndex::const_iterator, NamedPositionedIndex::const_iterator> NamedIndexRange;
|
|
|
|
using std::lower_bound;
|
|
using std::upper_bound;
|
|
|
|
static NamedPositionedIndex global_identIndex;
|
|
static NamedPositionedIndex global_nameIndex;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace Octree
|
|
{
|
|
|
|
const double LEAF_SIZE = SG_NM_TO_METER * 8.0;
|
|
const double LEAF_SIZE_SQR = LEAF_SIZE * LEAF_SIZE;
|
|
|
|
/**
|
|
* Decorate an object with a double value, and use that value to order
|
|
* items, for the purpoises of the STL algorithms
|
|
*/
|
|
template <class T>
|
|
class Ordered
|
|
{
|
|
public:
|
|
Ordered(const T& v, double x) :
|
|
_order(x),
|
|
_inner(v)
|
|
{
|
|
}
|
|
|
|
Ordered(const Ordered<T>& a) :
|
|
_order(a._order),
|
|
_inner(a._inner)
|
|
{
|
|
}
|
|
|
|
Ordered<T>& operator=(const Ordered<T>& a)
|
|
{
|
|
_order = a._order;
|
|
_inner = a._inner;
|
|
return *this;
|
|
}
|
|
|
|
bool operator<(const Ordered<T>& other) const
|
|
{
|
|
return _order < other._order;
|
|
}
|
|
|
|
bool operator>(const Ordered<T>& other) const
|
|
{
|
|
return _order > other._order;
|
|
}
|
|
|
|
const T& get() const
|
|
{ return _inner; }
|
|
|
|
double order() const
|
|
{ return _order; }
|
|
|
|
private:
|
|
double _order;
|
|
T _inner;
|
|
};
|
|
|
|
class Node;
|
|
typedef Ordered<Node*> OrderedNode;
|
|
typedef std::greater<OrderedNode> FNPQCompare;
|
|
|
|
/**
|
|
* the priority queue is fundamental to our search algorithm. When searching,
|
|
* we know the front of the queue is the nearest unexpanded node (to the search
|
|
* location). The default STL pqueue returns the 'largest' item from top(), so
|
|
* to get the smallest, we need to replace the default Compare functor (less<>)
|
|
* with greater<>.
|
|
*/
|
|
typedef std::priority_queue<OrderedNode, std::vector<OrderedNode>, FNPQCompare> FindNearestPQueue;
|
|
|
|
typedef Ordered<FGPositioned*> OrderedPositioned;
|
|
typedef std::vector<OrderedPositioned> FindNearestResults;
|
|
|
|
Node* global_spatialOctree = NULL;
|
|
|
|
/**
|
|
* Octree node base class, tracks its bounding box and provides various
|
|
* queries relating to it
|
|
*/
|
|
class Node
|
|
{
|
|
public:
|
|
bool contains(const SGVec3d& aPos) const
|
|
{
|
|
return intersects(aPos, _box);
|
|
}
|
|
|
|
double distSqrToNearest(const SGVec3d& aPos) const
|
|
{
|
|
return distSqr(aPos, _box.getClosestPoint(aPos));
|
|
}
|
|
|
|
virtual void insert(FGPositioned* aP) = 0;
|
|
|
|
virtual void visit(const SGVec3d& aPos, double aCutoff,
|
|
FGPositioned::Filter* aFilter,
|
|
FindNearestResults& aResults, FindNearestPQueue&) = 0;
|
|
protected:
|
|
Node(const SGBoxd &aBox) :
|
|
_box(aBox)
|
|
{
|
|
}
|
|
|
|
const SGBoxd _box;
|
|
};
|
|
|
|
class Leaf : public Node
|
|
{
|
|
public:
|
|
Leaf(const SGBoxd& aBox) :
|
|
Node(aBox)
|
|
{
|
|
}
|
|
|
|
const FGPositioned::List& members() const
|
|
{ return _members; }
|
|
|
|
virtual void insert(FGPositioned* aP)
|
|
{
|
|
_members.push_back(aP);
|
|
}
|
|
|
|
virtual void visit(const SGVec3d& aPos, double aCutoff,
|
|
FGPositioned::Filter* aFilter,
|
|
FindNearestResults& aResults, FindNearestPQueue&)
|
|
{
|
|
int previousResultsSize = aResults.size();
|
|
int addedCount = 0;
|
|
|
|
for (unsigned int i=0; i<_members.size(); ++i) {
|
|
FGPositioned* p = _members[i];
|
|
double d2 = distSqr(aPos, p->cart());
|
|
if (d2 > aCutoff) {
|
|
continue;
|
|
}
|
|
|
|
if (aFilter) {
|
|
if (aFilter->hasTypeRange() && !aFilter->passType(p->type())) {
|
|
continue;
|
|
}
|
|
|
|
if (!aFilter->pass(p)) {
|
|
continue;
|
|
}
|
|
} // of have a filter
|
|
|
|
++addedCount;
|
|
aResults.push_back(OrderedPositioned(p, d2));
|
|
}
|
|
|
|
if (addedCount == 0) {
|
|
return;
|
|
}
|
|
|
|
// keep aResults sorted
|
|
// sort the new items, usually just one or two items
|
|
std::sort(aResults.begin() + previousResultsSize, aResults.end());
|
|
|
|
// merge the two sorted ranges together - in linear time
|
|
std::inplace_merge(aResults.begin(),
|
|
aResults.begin() + previousResultsSize, aResults.end());
|
|
}
|
|
private:
|
|
FGPositioned::List _members;
|
|
};
|
|
|
|
class Branch : public Node
|
|
{
|
|
public:
|
|
Branch(const SGBoxd& aBox) :
|
|
Node(aBox)
|
|
{
|
|
memset(children, 0, sizeof(Node*) * 8);
|
|
}
|
|
|
|
virtual void insert(FGPositioned* aP)
|
|
{
|
|
SGVec3d cart(aP->cart());
|
|
assert(contains(cart));
|
|
int childIndex = 0;
|
|
|
|
SGVec3d center(_box.getCenter());
|
|
// tests must match indices in SGbox::getCorner
|
|
if (cart.x() < center.x()) {
|
|
childIndex += 1;
|
|
}
|
|
|
|
if (cart.y() < center.y()) {
|
|
childIndex += 2;
|
|
}
|
|
|
|
if (cart.z() < center.z()) {
|
|
childIndex += 4;
|
|
}
|
|
|
|
Node* child = children[childIndex];
|
|
if (!child) { // lazy building of children
|
|
SGBoxd cb(boxForChild(childIndex));
|
|
double d2 = dot(cb.getSize(), cb.getSize());
|
|
if (d2 < LEAF_SIZE_SQR) {
|
|
child = new Leaf(cb);
|
|
} else {
|
|
child = new Branch(cb);
|
|
}
|
|
|
|
children[childIndex] = child;
|
|
}
|
|
|
|
child->insert(aP);
|
|
}
|
|
|
|
virtual void visit(const SGVec3d& aPos, double aCutoff,
|
|
FGPositioned::Filter*,
|
|
FindNearestResults&, FindNearestPQueue& aQ)
|
|
{
|
|
for (unsigned int i=0; i<8; ++i) {
|
|
if (!children[i]) {
|
|
continue;
|
|
}
|
|
|
|
double d2 = children[i]->distSqrToNearest(aPos);
|
|
if (d2 > aCutoff) {
|
|
continue; // exceeded cutoff
|
|
}
|
|
|
|
aQ.push(Ordered<Node*>(children[i], d2));
|
|
} // of child iteration
|
|
}
|
|
|
|
|
|
private:
|
|
/**
|
|
* Return the box for a child touching the specified corner
|
|
*/
|
|
SGBoxd boxForChild(unsigned int aCorner) const
|
|
{
|
|
SGBoxd r(_box.getCenter());
|
|
r.expandBy(_box.getCorner(aCorner));
|
|
return r;
|
|
}
|
|
|
|
Node* children[8];
|
|
};
|
|
|
|
void findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
|
|
{
|
|
aResults.clear();
|
|
FindNearestPQueue pq;
|
|
FindNearestResults results;
|
|
pq.push(Ordered<Node*>(global_spatialOctree, 0));
|
|
double cut = aCutoffM * aCutoffM;
|
|
|
|
while (!pq.empty()) {
|
|
if (!results.empty()) {
|
|
// terminate the search if we have sufficent results, and we are
|
|
// sure no node still on the queue contains a closer match
|
|
double furthestResultOrder = results.back().order();
|
|
if ((results.size() >= aN) && (furthestResultOrder < pq.top().order())) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Node* nd = pq.top().get();
|
|
pq.pop();
|
|
|
|
nd->visit(aPos, cut, aFilter, results, pq);
|
|
} // of queue iteration
|
|
|
|
// depending on leaf population, we may have (slighty) more results
|
|
// than requested
|
|
unsigned int numResults = std::min((unsigned int) results.size(), aN);
|
|
// copy results out
|
|
aResults.resize(numResults);
|
|
for (unsigned int r=0; r<numResults; ++r) {
|
|
aResults[r] = results[r].get();
|
|
}
|
|
}
|
|
|
|
void findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
|
|
{
|
|
aResults.clear();
|
|
FindNearestPQueue pq;
|
|
FindNearestResults results;
|
|
pq.push(Ordered<Node*>(global_spatialOctree, 0));
|
|
double rng = aRangeM * aRangeM;
|
|
|
|
while (!pq.empty()) {
|
|
Node* nd = pq.top().get();
|
|
pq.pop();
|
|
|
|
nd->visit(aPos, rng, aFilter, results, pq);
|
|
} // of queue iteration
|
|
|
|
unsigned int numResults = results.size();
|
|
// copy results out
|
|
aResults.resize(numResults);
|
|
for (unsigned int r=0; r<numResults; ++r) {
|
|
aResults[r] = results[r].get();
|
|
}
|
|
}
|
|
|
|
} // of namespace Octree
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void
|
|
addToIndices(FGPositioned* aPos)
|
|
{
|
|
assert(aPos);
|
|
if (!aPos->ident().empty()) {
|
|
std::string u(boost::to_upper_copy(aPos->ident()));
|
|
|
|
global_identIndex.insert(global_identIndex.begin(),
|
|
std::make_pair(u, aPos));
|
|
}
|
|
|
|
if (!aPos->name().empty()) {
|
|
std::string u(boost::to_upper_copy(aPos->name()));
|
|
|
|
global_nameIndex.insert(global_nameIndex.begin(),
|
|
std::make_pair(u, aPos));
|
|
}
|
|
|
|
if (!Octree::global_spatialOctree) {
|
|
double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
|
|
SGVec3d earthExtent(RADIUS_EARTH_M, RADIUS_EARTH_M, RADIUS_EARTH_M);
|
|
Octree::global_spatialOctree = new Octree::Branch(SGBox<double>(-earthExtent, earthExtent));
|
|
}
|
|
Octree::global_spatialOctree->insert(aPos);
|
|
}
|
|
|
|
static void
|
|
removeFromIndices(FGPositioned* aPos)
|
|
{
|
|
assert(aPos);
|
|
|
|
if (!aPos->ident().empty()) {
|
|
std::string u(boost::to_upper_copy(aPos->ident()));
|
|
NamedPositionedIndex::iterator it = global_identIndex.find(u);
|
|
while (it != global_identIndex.end() && (it->first == u)) {
|
|
if (it->second == aPos) {
|
|
global_identIndex.erase(it);
|
|
break;
|
|
}
|
|
|
|
++it;
|
|
} // of multimap walk
|
|
}
|
|
|
|
if (!aPos->name().empty()) {
|
|
std::string u(boost::to_upper_copy(aPos->name()));
|
|
NamedPositionedIndex::iterator it = global_nameIndex.find(u);
|
|
while (it != global_nameIndex.end() && (it->first == u)) {
|
|
if (it->second == aPos) {
|
|
global_nameIndex.erase(it);
|
|
break;
|
|
}
|
|
|
|
++it;
|
|
} // of multimap walk
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class OrderByName
|
|
{
|
|
public:
|
|
bool operator()(FGPositioned* a, FGPositioned* b) const
|
|
{
|
|
return a->name() < b->name();
|
|
}
|
|
};
|
|
|
|
void findInIndex(NamedPositionedIndex& aIndex, const std::string& aFind, std::vector<FGPositioned*>& aResult)
|
|
{
|
|
NamedPositionedIndex::const_iterator it = aIndex.begin();
|
|
NamedPositionedIndex::const_iterator end = aIndex.end();
|
|
|
|
bool haveFilter = !aFind.empty();
|
|
|
|
for (; it != end; ++it) {
|
|
FGPositioned::Type ty = it->second->type();
|
|
if ((ty < FGPositioned::AIRPORT) || (ty > FGPositioned::SEAPORT)) {
|
|
continue;
|
|
}
|
|
|
|
if (haveFilter && it->first.find(aFind) == std::string::npos) {
|
|
continue;
|
|
}
|
|
|
|
aResult.push_back(it->second);
|
|
} // of index iteration
|
|
}
|
|
|
|
/**
|
|
* A special purpose helper (imported by FGAirport::searchNamesAndIdents) to
|
|
* implement the AirportList dialog. It's unfortunate that it needs to reside
|
|
* here, but for now it's least ugly solution.
|
|
*/
|
|
char** searchAirportNamesAndIdents(const std::string& aFilter)
|
|
{
|
|
// note this is a vector of raw pointers, not smart pointers, because it
|
|
// may get very large and smart-pointer-atomicity-locking then becomes a
|
|
// bottleneck for this case.
|
|
std::vector<FGPositioned*> matches;
|
|
if (!aFilter.empty()) {
|
|
std::string filter = boost::to_upper_copy(aFilter);
|
|
findInIndex(global_identIndex, filter, matches);
|
|
findInIndex(global_nameIndex, filter, matches);
|
|
} else {
|
|
|
|
findInIndex(global_identIndex, std::string(), matches);
|
|
}
|
|
|
|
// sort alphabetically on name
|
|
std::sort(matches.begin(), matches.end(), OrderByName());
|
|
|
|
// convert results to format comptible with puaList
|
|
unsigned int numMatches = matches.size();
|
|
char** result = new char*[numMatches + 1];
|
|
result[numMatches] = NULL; // end-of-list marker
|
|
|
|
// nasty code to avoid excessive string copying and allocations.
|
|
// We format results as follows (note whitespace!):
|
|
// ' name-of-airport-chars (ident)'
|
|
// so the total length is:
|
|
// 1 + strlen(name) + 4 + strlen(icao) + 1 + 1 (for the null)
|
|
// which gives a grand total of 7 + name-length + icao-length.
|
|
// note the ident can be three letters (non-ICAO local strip), four
|
|
// (default ICAO) or more (extended format ICAO)
|
|
for (unsigned int i=0; i<numMatches; ++i) {
|
|
int nameLength = matches[i]->name().size();
|
|
int icaoLength = matches[i]->ident().size();
|
|
char* entry = new char[7 + nameLength + icaoLength];
|
|
char* dst = entry;
|
|
*dst++ = ' ';
|
|
memcpy(dst, matches[i]->name().c_str(), nameLength);
|
|
dst += nameLength;
|
|
*dst++ = ' ';
|
|
*dst++ = ' ';
|
|
*dst++ = ' ';
|
|
*dst++ = '(';
|
|
memcpy(dst, matches[i]->ident().c_str(), icaoLength);
|
|
dst += icaoLength;
|
|
*dst++ = ')';
|
|
*dst++ = 0;
|
|
result[i] = entry;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void validateSGGeod(const SGGeod& geod)
|
|
{
|
|
if (osg::isNaN(geod.getLatitudeDeg()) ||
|
|
osg::isNaN(geod.getLongitudeDeg()))
|
|
{
|
|
throw sg_range_exception("position is invalid, NaNs");
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool
|
|
FGPositioned::Filter::hasTypeRange() const
|
|
{
|
|
assert(minType() <= maxType());
|
|
return (minType() != INVALID) && (maxType() != INVALID);
|
|
}
|
|
|
|
bool
|
|
FGPositioned::Filter::passType(Type aTy) const
|
|
{
|
|
assert(hasTypeRange());
|
|
return (minType() <= aTy) && (maxType() >= aTy);
|
|
}
|
|
|
|
static FGPositioned::List
|
|
findAll(const NamedPositionedIndex& aIndex,
|
|
const std::string& aName,
|
|
FGPositioned::Filter* aFilter,
|
|
bool aExact)
|
|
{
|
|
FGPositioned::List result;
|
|
if (aName.empty()) {
|
|
return result;
|
|
}
|
|
|
|
std::string name = boost::to_upper_copy(aName);
|
|
NamedPositionedIndex::const_iterator upperBound;
|
|
|
|
if (aExact) {
|
|
upperBound = aIndex.upper_bound(name);
|
|
} else {
|
|
std::string upperBoundId = name;
|
|
upperBoundId[upperBoundId.size()-1]++;
|
|
upperBound = aIndex.lower_bound(upperBoundId);
|
|
}
|
|
|
|
NamedPositionedIndex::const_iterator it = aIndex.lower_bound(name);
|
|
|
|
for (; it != upperBound; ++it) {
|
|
FGPositionedRef candidate = it->second;
|
|
if (aFilter) {
|
|
if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
|
|
continue;
|
|
}
|
|
|
|
if (!aFilter->pass(candidate)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
result.push_back(candidate);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
FGPositioned::FGPositioned(Type ty, const std::string& aIdent, const SGGeod& aPos) :
|
|
mPosition(aPos),
|
|
mType(ty),
|
|
mIdent(aIdent)
|
|
{
|
|
}
|
|
|
|
void FGPositioned::init(bool aIndexed)
|
|
{
|
|
SGReferenced::get(this); // hold an owning ref, for the moment
|
|
mCart = SGVec3d::fromGeod(mPosition);
|
|
|
|
if (aIndexed) {
|
|
assert(mType != TAXIWAY && mType != PAVEMENT);
|
|
addToIndices(this);
|
|
}
|
|
}
|
|
|
|
FGPositioned::~FGPositioned()
|
|
{
|
|
//std::cout << "destroying:" << mIdent << "/" << nameForType(mType) << std::endl;
|
|
removeFromIndices(this);
|
|
}
|
|
|
|
FGPositioned*
|
|
FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
|
|
{
|
|
FGPositioned* wpt = new FGPositioned(WAYPOINT, aIdent, aPos);
|
|
wpt->init(true);
|
|
return wpt;
|
|
}
|
|
|
|
const SGVec3d&
|
|
FGPositioned::cart() const
|
|
{
|
|
return mCart;
|
|
}
|
|
|
|
FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
|
|
{
|
|
if (aName.empty() || (aName == "")) {
|
|
return INVALID;
|
|
}
|
|
|
|
typedef struct {
|
|
const char* _name;
|
|
Type _ty;
|
|
} NameTypeEntry;
|
|
|
|
const NameTypeEntry names[] = {
|
|
{"airport", AIRPORT},
|
|
{"vor", VOR},
|
|
{"ndb", NDB},
|
|
{"wpt", WAYPOINT},
|
|
{"fix", FIX},
|
|
{"tacan", TACAN},
|
|
{"dme", DME},
|
|
// aliases
|
|
{"waypoint", WAYPOINT},
|
|
{"apt", AIRPORT},
|
|
{"arpt", AIRPORT},
|
|
{"any", INVALID},
|
|
{"all", INVALID},
|
|
|
|
{NULL, INVALID}
|
|
};
|
|
|
|
std::string lowerName(boost::to_lower_copy(aName));
|
|
|
|
for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) {
|
|
if (::strcmp(n->_name, lowerName.c_str()) == 0) {
|
|
return n->_ty;
|
|
}
|
|
}
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
|
|
return INVALID;
|
|
}
|
|
|
|
const char* FGPositioned::nameForType(Type aTy)
|
|
{
|
|
switch (aTy) {
|
|
case RUNWAY: return "runway";
|
|
case TAXIWAY: return "taxiway";
|
|
case PAVEMENT: return "pavement";
|
|
case PARK_STAND: return "parking stand";
|
|
case FIX: return "fix";
|
|
case VOR: return "VOR";
|
|
case NDB: return "NDB";
|
|
case ILS: return "ILS";
|
|
case LOC: return "localiser";
|
|
case GS: return "glideslope";
|
|
case OM: return "outer-marker";
|
|
case MM: return "middle-marker";
|
|
case IM: return "inner-marker";
|
|
case AIRPORT: return "airport";
|
|
case HELIPORT: return "heliport";
|
|
case SEAPORT: return "seaport";
|
|
case WAYPOINT: return "waypoint";
|
|
case DME: return "dme";
|
|
case TACAN: return "tacan";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// search / query functions
|
|
|
|
FGPositionedRef
|
|
FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
FGPositioned::List r(findAll(global_identIndex, aIdent, aFilter, true));
|
|
if (r.empty()) {
|
|
return FGPositionedRef();
|
|
}
|
|
|
|
sortByRange(r, aPos);
|
|
return r.front();
|
|
}
|
|
|
|
FGPositioned::List
|
|
FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
List result;
|
|
Octree::findAllWithinRange(SGVec3d::fromGeod(aPos),
|
|
aRangeNm * SG_NM_TO_METER, aFilter, result);
|
|
return result;
|
|
}
|
|
|
|
FGPositioned::List
|
|
FGPositioned::findAllWithIdent(const std::string& aIdent, Filter* aFilter, bool aExact)
|
|
{
|
|
return findAll(global_identIndex, aIdent, aFilter, aExact);
|
|
}
|
|
|
|
FGPositioned::List
|
|
FGPositioned::findAllWithName(const std::string& aName, Filter* aFilter, bool aExact)
|
|
{
|
|
return findAll(global_nameIndex, aName, aFilter, aExact);
|
|
}
|
|
|
|
FGPositionedRef
|
|
FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
List l(findClosestN(aPos, 1, aCutoffNm, aFilter));
|
|
if (l.empty()) {
|
|
return NULL;
|
|
}
|
|
|
|
assert(l.size() == 1);
|
|
return l.front();
|
|
}
|
|
|
|
FGPositioned::List
|
|
FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
List result;
|
|
Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result);
|
|
return result;
|
|
}
|
|
|
|
FGPositionedRef
|
|
FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
|
|
{
|
|
if (aId.empty()) {
|
|
return NULL;
|
|
}
|
|
|
|
std::string id(boost::to_upper_copy(aId));
|
|
|
|
// It is essential to bound our search, to avoid iterating all the way to the end of the database.
|
|
// Do this by generating a second ID with the final character incremented by 1.
|
|
// e.g., if the partial ID is "KI", we wish to search "KIxxx" but not "KJ".
|
|
std::string upperBoundId = id;
|
|
upperBoundId[upperBoundId.size()-1]++;
|
|
NamedPositionedIndex::const_iterator upperBound = global_identIndex.lower_bound(upperBoundId);
|
|
|
|
NamedIndexRange range = global_identIndex.equal_range(id);
|
|
while (range.first != upperBound) {
|
|
for (; range.first != range.second; ++range.first) {
|
|
FGPositionedRef candidate = range.first->second;
|
|
if (aCur == candidate) {
|
|
aCur = NULL; // found our start point, next match will pass
|
|
continue;
|
|
}
|
|
|
|
if (aFilter) {
|
|
if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
|
|
continue;
|
|
}
|
|
|
|
if (!aFilter->pass(candidate)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!aCur) {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
// Unable to match the filter with this range - try the next range.
|
|
range = global_identIndex.equal_range(range.second->first);
|
|
}
|
|
|
|
return NULL; // Reached the end of the valid sequence with no match.
|
|
}
|
|
|
|
void
|
|
FGPositioned::sortByRange(List& aResult, const SGGeod& aPos)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
SGVec3d cartPos(SGVec3d::fromGeod(aPos));
|
|
// computer ordering values
|
|
Octree::FindNearestResults r;
|
|
List::iterator it = aResult.begin(), lend = aResult.end();
|
|
for (; it != lend; ++it) {
|
|
double d2 = distSqr((*it)->cart(), cartPos);
|
|
r.push_back(Octree::OrderedPositioned(*it, d2));
|
|
}
|
|
|
|
// sort
|
|
std::sort(r.begin(), r.end());
|
|
|
|
// convert to a plain list
|
|
unsigned int count = aResult.size();
|
|
for (unsigned int i=0; i<count; ++i) {
|
|
aResult[i] = r[i].get();
|
|
}
|
|
}
|