// navlist.cxx -- navaids management class // // Written by Curtis Olson, started April 2000. // // Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt // // 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 #endif #include #include #include #include #include #include #include "navlist.hxx" #include #include using std::string; namespace { // anonymous class NavRecordDistanceSortPredicate { public: NavRecordDistanceSortPredicate( const SGGeod & position ) : _position(SGVec3d::fromGeod(position)) {} bool operator()( const nav_rec_ptr & n1, const nav_rec_ptr & n2 ) { if( n1 == NULL || n2 == NULL ) return false; return distSqr(n1->cart(), _position) < distSqr(n2->cart(), _position); } private: SGVec3d _position; }; // discount navids if they conflict with another on the same frequency // this only applies to navids associated with opposite ends of a runway, // with matching frequencies. bool navidUsable(FGNavRecord* aNav, const SGGeod &aircraft) { FGRunway* r(aNav->runway()); if (!r || !r->reciprocalRunway()) { return true; } // check if the runway frequency is paired FGNavRecord* locA = r->ILS(); FGNavRecord* locB = r->reciprocalRunway()->ILS(); if (!locA || !locB || (locA->get_freq() != locB->get_freq())) { return true; // not paired, ok } // okay, both ends have an ILS, and they're paired. We need to select based on // aircraft position. What we're going to use is *runway* (not navid) position, // ie whichever runway end we are closer too. This makes back-course / missed // approach behaviour incorrect, but that's the price we accept. double crs = SGGeodesy::courseDeg(aircraft, r->geod()); double hdgDiff = crs - r->headingDeg(); SG_NORMALIZE_RANGE(hdgDiff, -180.0, 180.0); return (fabs(hdgDiff) < 90.0); } } // of anonymous namespace // FGNavList ------------------------------------------------------------------ FGNavList::TypeFilter::TypeFilter(const FGPositioned::Type type) { if (type == FGPositioned::INVALID) { _mintype = FGPositioned::NDB; _maxtype = FGPositioned::GS; } else { _mintype = _maxtype = type; } } FGNavList::TypeFilter::TypeFilter(const FGPositioned::Type minType, const FGPositioned::Type maxType) : _mintype(minType), _maxtype(maxType) { } /** * Filter returning Tacan stations. Checks for both pure TACAN stations * but also co-located VORTACs. This is done by searching for DMEs whose * name indicates they are a TACAN or VORTAC; not a great solution. */ class TacanFilter : public FGNavList::TypeFilter { public: TacanFilter() : TypeFilter(FGPositioned::DME, FGPositioned::TACAN) { } virtual bool pass(FGPositioned* pos) const { if (pos->type() == FGPositioned::TACAN) { return true; } assert(pos->type() == FGPositioned::DME); string::size_type loc1 = pos->name().find( "TACAN" ); string::size_type loc2 = pos->name().find( "VORTAC" ); return (loc1 != string::npos) || (loc2 != string::npos); } }; FGNavList::TypeFilter* FGNavList::locFilter() { static TypeFilter tf(FGPositioned::ILS, FGPositioned::LOC); return &tf; } FGNavList::TypeFilter* FGNavList::ndbFilter() { static TypeFilter tf(FGPositioned::NDB); return &tf; } FGNavList::TypeFilter* FGNavList::navFilter() { static TypeFilter tf(FGPositioned::VOR, FGPositioned::LOC); return &tf; } FGNavList::TypeFilter* FGNavList::tacanFilter() { static TacanFilter tf; return &tf; } FGNavList::TypeFilter* FGNavList::carrierFilter() { static TypeFilter tf(FGPositioned::MOBILE_TACAN); return &tf; } FGNavRecord *FGNavList::findByFreq( double freq, const SGGeod& position, TypeFilter* filter) { flightgear::NavDataCache* cache = flightgear::NavDataCache::instance(); int freqKhz = static_cast(freq * 100 + 0.5); PositionedIDVec stations(cache->findNavaidsByFreq(freqKhz, position, filter)); if (stations.empty()) { return NULL; } // now walk the (sorted) results list to find a usable, in-range navaid SGVec3d acCart(SGVec3d::fromGeod(position)); double min_dist = FG_NAV_MAX_RANGE*SG_NM_TO_METER*FG_NAV_MAX_RANGE*SG_NM_TO_METER; BOOST_FOREACH(PositionedID id, stations) { FGNavRecord* station = (FGNavRecord*) cache->loadById(id); if (!filter->pass(station)) { continue; } double d2 = distSqr(station->cart(), acCart); if (d2 > min_dist) { // since results are sorted by proximity, as soon as we pass the // distance cutoff we're done - fall out and return NULL break; } if (navidUsable(station, position)) { return station; } } // fell out of the loop, no usable match return NULL; } FGNavRecord *FGNavList::findByFreq( double freq, TypeFilter* filter) { flightgear::NavDataCache* cache = flightgear::NavDataCache::instance(); int freqKhz = static_cast(freq * 100 + 0.5); PositionedIDVec stations(cache->findNavaidsByFreq(freqKhz, filter)); if (stations.empty()) { return NULL; } BOOST_FOREACH(PositionedID id, stations) { FGNavRecord* station = (FGNavRecord*) cache->loadById(id); if (filter->pass(station)) { return station; } } return NULL; } nav_list_type FGNavList::findAllByFreq( double freq, const SGGeod& position, TypeFilter* filter) { nav_list_type stations; flightgear::NavDataCache* cache = flightgear::NavDataCache::instance(); int freqKhz = static_cast(freq * 1000 + 0.5); PositionedIDVec ids(cache->findNavaidsByFreq(freqKhz, position, filter)); BOOST_FOREACH(PositionedID id, ids) { FGNavRecord* station = (FGNavRecord*) cache->loadById(id); if (!filter->pass(station)) { continue; } stations.push_back(station); } return stations; } nav_list_type FGNavList::findByIdentAndFreq(const string& ident, const double freq, TypeFilter* filter) { nav_list_type reply; int f = (int)(freq*100.0 + 0.5); FGPositioned::List stations = FGPositioned::findAllWithIdent(ident, filter); BOOST_FOREACH(FGPositionedRef ref, stations) { FGNavRecord* nav = static_cast(ref.ptr()); if ( f <= 0.0 || nav->get_freq() == f) { reply.push_back( nav ); } } return reply; } // Given an Ident and optional frequency and type , // return a list of matching stations sorted by distance to the given position nav_list_type FGNavList::findByIdentAndFreq( const SGGeod & position, const std::string& ident, const double freq, TypeFilter* filter) { nav_list_type reply = findByIdentAndFreq( ident, freq, filter ); NavRecordDistanceSortPredicate sortPredicate( position ); std::sort( reply.begin(), reply.end(), sortPredicate ); return reply; } // FGTACANList ---------------------------------------------------------------- FGTACANList::FGTACANList( void ) { } FGTACANList::~FGTACANList( void ) { } bool FGTACANList::init() { return true; } // add an entry to the lists bool FGTACANList::add( FGTACANRecord *c ) { ident_channels[c->get_channel()].push_back(c); return true; } // Given a TACAN Channel return the first matching frequency FGTACANRecord *FGTACANList::findByChannel( const string& channel ) { const tacan_list_type& stations = ident_channels[channel]; SG_LOG( SG_INSTR, SG_DEBUG, "findByChannel " << channel<< " size " << stations.size() ); if (!stations.empty()) { return stations[0]; } return NULL; }