432 lines
11 KiB
C++
432 lines
11 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 <memory>
|
|
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <simgear/timing/timestamp.hxx>
|
|
#include <simgear/debug/logstream.hxx>
|
|
#include <simgear/structure/exception.hxx>
|
|
#include <simgear/math/SGGeometry.hxx>
|
|
#include <simgear/sg_inlines.h>
|
|
|
|
#include "Navaids/PositionedOctree.hxx"
|
|
|
|
using std::string;
|
|
using namespace flightgear;
|
|
|
|
static void validateSGGeod(const SGGeod& geod)
|
|
{
|
|
if (SGMisc<double>::isNaN(geod.getLatitudeDeg()) ||
|
|
SGMisc<double>::isNaN(geod.getLongitudeDeg()))
|
|
{
|
|
throw sg_range_exception("position is invalid, NaNs");
|
|
}
|
|
}
|
|
|
|
static bool validateFilter(FGPositioned::Filter* filter)
|
|
{
|
|
if (filter->maxType() < filter->minType()) {
|
|
SG_LOG(SG_GENERAL, SG_WARN, "invalid positioned filter specified");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const PositionedID FGPositioned::TRANSIENT_ID = -2;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
FGPositioned::FGPositioned( PositionedID aGuid,
|
|
Type ty,
|
|
const std::string& aIdent,
|
|
const SGGeod& aPos ):
|
|
mGuid(aGuid),
|
|
mType(ty),
|
|
mIdent(aIdent),
|
|
mPosition(aPos),
|
|
mCart(SGVec3d::fromGeod(mPosition))
|
|
{
|
|
|
|
}
|
|
|
|
FGPositioned::~FGPositioned()
|
|
{
|
|
}
|
|
|
|
FGPositioned*
|
|
FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
|
|
{
|
|
NavDataCache* cache = NavDataCache::instance();
|
|
TypeFilter filter(WAYPOINT);
|
|
FGPositionedList existing = cache->findAllWithIdent(aIdent, &filter, true);
|
|
if (!existing.empty()) {
|
|
SG_LOG(SG_NAVAID, SG_WARN, "attempt to insert duplicate WAYPOINT:" << aIdent);
|
|
return existing.front().ptr();
|
|
}
|
|
|
|
PositionedID id = cache->createPOI(WAYPOINT, aIdent, aPos);
|
|
return cache->loadById(id);
|
|
}
|
|
|
|
bool FGPositioned::deleteUserWaypoint(const std::string& aIdent)
|
|
{
|
|
NavDataCache* cache = NavDataCache::instance();
|
|
return cache->removePOI(WAYPOINT, aIdent);
|
|
}
|
|
|
|
|
|
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},
|
|
{"loc", LOC},
|
|
{"ils", ILS},
|
|
{"gs", GS},
|
|
{"ndb", NDB},
|
|
{"wpt", WAYPOINT},
|
|
{"fix", FIX},
|
|
{"tacan", TACAN},
|
|
{"dme", DME},
|
|
{"atis", FREQ_ATIS},
|
|
{"awos", FREQ_AWOS},
|
|
{"tower", FREQ_TOWER},
|
|
{"ground", FREQ_GROUND},
|
|
{"approach", FREQ_APP_DEP},
|
|
{"departure", FREQ_APP_DEP},
|
|
{"runway", RUNWAY},
|
|
{"helipad", HELIPAD},
|
|
{"country", COUNTRY},
|
|
{"city", CITY},
|
|
{"town", TOWN},
|
|
{"village", VILLAGE},
|
|
|
|
// aliases
|
|
{"localizer", LOC},
|
|
{"gnd", FREQ_GROUND},
|
|
{"twr", FREQ_TOWER},
|
|
{"waypoint", WAYPOINT},
|
|
{"apt", AIRPORT},
|
|
{"arpt", AIRPORT},
|
|
{"rwy", RUNWAY},
|
|
{"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_NAVAID, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
|
|
return INVALID;
|
|
}
|
|
|
|
const char* FGPositioned::nameForType(Type aTy)
|
|
{
|
|
switch (aTy) {
|
|
case RUNWAY: return "runway";
|
|
case HELIPAD: return "helipad";
|
|
case TAXIWAY: return "taxiway";
|
|
case PAVEMENT: return "pavement";
|
|
case PARKING: return "parking stand";
|
|
case FIX: return "fix";
|
|
case VOR: return "VOR";
|
|
case NDB: return "NDB";
|
|
case ILS: return "ILS";
|
|
case LOC: return "localizer";
|
|
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";
|
|
case FREQ_TOWER: return "tower";
|
|
case FREQ_ATIS: return "atis";
|
|
case FREQ_AWOS: return "awos";
|
|
case FREQ_GROUND: return "ground";
|
|
case FREQ_CLEARANCE: return "clearance";
|
|
case FREQ_UNICOM: return "unicom";
|
|
case FREQ_APP_DEP: return "approach-departure";
|
|
case TAXI_NODE: return "taxi-node";
|
|
case COUNTRY: return "country";
|
|
case CITY: return "city";
|
|
case TOWN: return "town";
|
|
case VILLAGE: return "village";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// search / query functions
|
|
|
|
FGPositionedRef
|
|
FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
return NavDataCache::instance()->findClosestWithIdent(aIdent, aPos, aFilter);
|
|
}
|
|
|
|
FGPositionedRef
|
|
FGPositioned::findFirstWithIdent(const std::string& aIdent, Filter* aFilter)
|
|
{
|
|
if (aIdent.empty()) {
|
|
return NULL;
|
|
}
|
|
|
|
FGPositionedList r =
|
|
NavDataCache::instance()->findAllWithIdent(aIdent, aFilter, true);
|
|
if (r.empty()) {
|
|
return NULL;
|
|
}
|
|
|
|
return r.front();
|
|
}
|
|
|
|
FGPositionedList
|
|
FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
if (!validateFilter(aFilter)) {
|
|
return FGPositionedList();
|
|
}
|
|
|
|
FGPositionedList result;
|
|
Octree::findAllWithinRange(SGVec3d::fromGeod(aPos),
|
|
aRangeNm * SG_NM_TO_METER, aFilter, result, 0xffffff);
|
|
return result;
|
|
}
|
|
|
|
FGPositionedList
|
|
FGPositioned::findWithinRangePartial(const SGGeod& aPos, double aRangeNm, Filter* aFilter, bool& aPartial)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
if (!validateFilter(aFilter)) {
|
|
return FGPositionedList();
|
|
}
|
|
|
|
int limitMsec = 32;
|
|
FGPositionedList result;
|
|
aPartial = Octree::findAllWithinRange(SGVec3d::fromGeod(aPos),
|
|
aRangeNm * SG_NM_TO_METER, aFilter, result,
|
|
limitMsec);
|
|
return result;
|
|
}
|
|
|
|
FGPositionedList
|
|
FGPositioned::findAllWithIdent(const std::string& aIdent, Filter* aFilter, bool aExact)
|
|
{
|
|
if (!validateFilter(aFilter)) {
|
|
return FGPositionedList();
|
|
}
|
|
|
|
return NavDataCache::instance()->findAllWithIdent(aIdent, aFilter, aExact);
|
|
}
|
|
|
|
FGPositionedList
|
|
FGPositioned::findAllWithName(const std::string& aName, Filter* aFilter, bool aExact)
|
|
{
|
|
if (!validateFilter(aFilter)) {
|
|
return FGPositionedList();
|
|
}
|
|
|
|
return NavDataCache::instance()->findAllWithName(aName, aFilter, aExact);
|
|
}
|
|
|
|
FGPositionedRef
|
|
FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
if (!validateFilter(aFilter)) {
|
|
return NULL;
|
|
}
|
|
|
|
FGPositionedList l(findClosestN(aPos, 1, aCutoffNm, aFilter));
|
|
if (l.empty()) {
|
|
return NULL;
|
|
}
|
|
|
|
assert(l.size() == 1);
|
|
return l.front();
|
|
}
|
|
|
|
FGPositionedList
|
|
FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
FGPositionedList result;
|
|
int limitMsec = 0xffff;
|
|
Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result, limitMsec);
|
|
return result;
|
|
}
|
|
|
|
FGPositionedList
|
|
FGPositioned::findClosestNPartial(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter, bool &aPartial)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
FGPositionedList result;
|
|
int limitMsec = 32;
|
|
aPartial = Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result,
|
|
limitMsec);
|
|
return result;
|
|
}
|
|
|
|
void
|
|
FGPositioned::sortByRange(FGPositionedList& aResult, const SGGeod& aPos)
|
|
{
|
|
validateSGGeod(aPos);
|
|
|
|
SGVec3d cartPos(SGVec3d::fromGeod(aPos));
|
|
// computer ordering values
|
|
Octree::FindNearestResults r;
|
|
FGPositionedList::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();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FGPositioned::modifyPosition(const SGGeod& newPos)
|
|
{
|
|
const_cast<SGGeod&>(mPosition) = newPos;
|
|
const_cast<SGVec3d&>(mCart) = SGVec3d::fromGeod(newPos);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FGPositioned::invalidatePosition()
|
|
{
|
|
const_cast<SGGeod&>(mPosition) = SGGeod::fromDeg(999,999);
|
|
const_cast<SGVec3d&>(mCart) = SGVec3d::zeros();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
FGPositionedRef FGPositioned::loadByIdImpl(PositionedID id)
|
|
{
|
|
return flightgear::NavDataCache::instance()->loadById(id);
|
|
}
|
|
|
|
FGPositioned::TypeFilter::TypeFilter(Type aTy) :
|
|
mMinType(LAST_TYPE),
|
|
mMaxType(INVALID)
|
|
{
|
|
addType(aTy);
|
|
}
|
|
|
|
void FGPositioned::TypeFilter::addType(Type aTy)
|
|
{
|
|
if (aTy == INVALID) {
|
|
return;
|
|
}
|
|
|
|
types.push_back(aTy);
|
|
mMinType = std::min(mMinType, aTy);
|
|
mMaxType = std::max(mMaxType, aTy);
|
|
}
|
|
|
|
FGPositioned::TypeFilter
|
|
FGPositioned::TypeFilter::fromString(const std::string& aFilterSpec)
|
|
{
|
|
if (aFilterSpec.empty()) {
|
|
throw sg_format_exception("empty filter spec:", aFilterSpec);
|
|
}
|
|
|
|
string_list parts = simgear::strutils::split(aFilterSpec, ",");
|
|
TypeFilter f;
|
|
|
|
BOOST_FOREACH(std::string token, parts) {
|
|
f.addType(typeFromName(token));
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
bool
|
|
FGPositioned::TypeFilter::pass(FGPositioned* aPos) const
|
|
{
|
|
if (types.empty()) {
|
|
return true;
|
|
}
|
|
|
|
std::vector<Type>::const_iterator it = types.begin(),
|
|
end = types.end();
|
|
for (; it != end; ++it) {
|
|
if (aPos->type() == *it) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|