X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FInstrumentation%2Fdclgps.cxx;h=f193c5624c0bad1d6b48335dd9b0ebbcc2292ef0;hb=d141beee0f032e677b80fbb7a4bed6a9262d921f;hp=d4084136f9a318a6359e7c9cc44b4f7efbd6926b;hpb=3d5afec3407745aaafff98dfad5ec68e2defc1b9;p=flightgear.git diff --git a/src/Instrumentation/dclgps.cxx b/src/Instrumentation/dclgps.cxx index d4084136f..f193c5624 100644 --- a/src/Instrumentation/dclgps.cxx +++ b/src/Instrumentation/dclgps.cxx @@ -5,7 +5,7 @@ // // Written by David Luff, started 2005. // -// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk +// Copyright (C) 2005 - David C Luff: daveluff --AT-- ntlworld --D0T-- com // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -19,103 +19,42 @@ // // 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ #include "dclgps.hxx" #include -#include -#include
-#include -SG_USING_STD(cout); - -//using namespace std; - -// Command callbacks for FlightGear - -static bool do_kln89_msg_pressed(const SGPropertyNode* arg) { - //cout << "do_kln89_msg_pressed called!\n"; - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->MsgPressed(); - return(true); -} - -static bool do_kln89_obs_pressed(const SGPropertyNode* arg) { - //cout << "do_kln89_obs_pressed called!\n"; - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->OBSPressed(); - return(true); -} +#include +#include +#include +#include -static bool do_kln89_alt_pressed(const SGPropertyNode* arg) { - //cout << "do_kln89_alt_pressed called!\n"; - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->AltPressed(); - return(true); -} - -static bool do_kln89_nrst_pressed(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->NrstPressed(); - return(true); -} - -static bool do_kln89_dto_pressed(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->DtoPressed(); - return(true); -} - -static bool do_kln89_clr_pressed(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->ClrPressed(); - return(true); -} - -static bool do_kln89_ent_pressed(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->EntPressed(); - return(true); -} - -static bool do_kln89_crsr_pressed(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->CrsrPressed(); - return(true); -} - -static bool do_kln89_knob1left1(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->Knob1Left1(); - return(true); -} - -static bool do_kln89_knob1right1(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->Knob1Right1(); - return(true); -} - -static bool do_kln89_knob2left1(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->Knob2Left1(); - return(true); -} +#include
+#include +#include +#include +#include -static bool do_kln89_knob2right1(const SGPropertyNode* arg) { - DCLGPS* gps = (DCLGPS*)globals->get_subsystem("kln89"); - gps->Knob2Right1(); - return(true); -} +#include +#include -// End command callbacks +using namespace std; GPSWaypoint::GPSWaypoint() { appType = GPS_APP_NONE; } +GPSWaypoint::GPSWaypoint(const std::string& aIdent, float aLat, float aLon, GPSWpType aType) : + id(aIdent), + lat(aLat), + lon(aLon), + type(aType), + appType(GPS_APP_NONE) +{ +} + GPSWaypoint::~GPSWaypoint() {} string GPSWaypoint::GetAprId() { @@ -126,6 +65,45 @@ string GPSWaypoint::GetAprId() { else return(id); } +static GPSWpType +GPSWpTypeFromFGPosType(FGPositioned::Type aType) +{ + switch (aType) { + case FGPositioned::AIRPORT: + case FGPositioned::SEAPORT: + case FGPositioned::HELIPORT: + return GPS_WP_APT; + + case FGPositioned::VOR: + return GPS_WP_VOR; + + case FGPositioned::NDB: + return GPS_WP_NDB; + + case FGPositioned::WAYPOINT: + return GPS_WP_USR; + + case FGPositioned::FIX: + return GPS_WP_INT; + + default: + return GPS_WP_USR; + } +} + +GPSWaypoint* GPSWaypoint::createFromPositioned(const FGPositioned* aPos) +{ + if (!aPos) { + return NULL; // happens if find returns no match + } + + return new GPSWaypoint(aPos->ident(), + aPos->latitude() * SG_DEGREES_TO_RADIANS, + aPos->longitude() * SG_DEGREES_TO_RADIANS, + GPSWpTypeFromFGPosType(aPos->type()) + ); +} + ostream& operator << (ostream& os, GPSAppWpType type) { switch(type) { case(GPS_IAF): return(os << "IAF"); @@ -152,65 +130,28 @@ FGNPIAP::FGNPIAP() { FGNPIAP::~FGNPIAP() { } -GPSPage::GPSPage(DCLGPS* parent) { - _parent = parent; - _subPage = 0; -} - -GPSPage::~GPSPage() { -} - -void GPSPage::Update(double dt) {} - -void GPSPage::Knob1Left1() {} -void GPSPage::Knob1Right1() {} - -void GPSPage::Knob2Left1() { - _parent->_activePage->LooseFocus(); - _subPage--; - if(_subPage < 0) _subPage = _nSubPages - 1; +ClockTime::ClockTime() { + _hr = 0; + _min = 0; } -void GPSPage::Knob2Right1() { - _parent->_activePage->LooseFocus(); - _subPage++; - if(_subPage >= _nSubPages) _subPage = 0; +ClockTime::ClockTime(int hr, int min) { + while(hr < 0) { hr += 24; } + _hr = hr % 24; + while(min < 0) { min += 60; } + while(min > 60) { min -= 60; } + _min = min; } -void GPSPage::CrsrPressed() {} -void GPSPage::EntPressed() {} -void GPSPage::ClrPressed() {} -void GPSPage::DtoPressed() {} -void GPSPage::NrstPressed() {} -void GPSPage::AltPressed() {} -void GPSPage::OBSPressed() {} -void GPSPage::MsgPressed() {} - -string GPSPage::GPSitoa(int n) { - char buf[4]; - // TODO - sanity check n! - sprintf(buf, "%i", n); - string s = buf; - return(s); +ClockTime::~ClockTime() { } -void GPSPage::CleanUp() {} -void GPSPage::LooseFocus() {} -void GPSPage::SetId(const string& s) {} - // ------------------------------------------------------------------------------------- // DCLGPS::DCLGPS(RenderArea2D* instrument) { _instrument = instrument; _nFields = 1; _maxFields = 2; - _pages.clear(); - - // Units - lets default to US units - FG can set them to other units from config during startup if desired. - _altUnits = GPS_ALT_UNITS_FT; - _baroUnits = GPS_PRES_UNITS_IN; - _velUnits = GPS_VEL_UNITS_KT; - _distUnits = GPS_DIST_UNITS_NM; _lon_node = fgGetNode("/instrumentation/gps/indicated-longitude-deg", true); _lat_node = fgGetNode("/instrumentation/gps/indicated-latitude-deg", true); @@ -256,12 +197,14 @@ DCLGPS::DCLGPS(RenderArea2D* instrument) { _departed = false; _departureTimeString = "----"; _elapsedTime = 0.0; + _powerOnTime.set_hr(0); + _powerOnTime.set_min(0); + _powerOnTimerSet = false; + _alarmSet = false; // Configuration Initialisation // Should this be in kln89.cxx ? _turnAnticipationEnabled = false; - _suaAlertEnabled = false; - _altAlertEnabled = false; _time = new SGTime; @@ -278,343 +221,21 @@ DCLGPS::DCLGPS(RenderArea2D* instrument) { DCLGPS::~DCLGPS() { delete _time; - for(gps_waypoint_map_iterator itr = _waypoints.begin(); itr != _waypoints.end(); ++itr) { - for(unsigned int i = 0; i < (*itr).second.size(); ++i) { - delete(((*itr).second)[i]); - } - } - delete _approachFP; // Don't need to delete the waypoints inside since they point to + delete _approachFP; // Don't need to delete the waypoints inside since they point to // the waypoints in the approach database. // TODO - may need to delete the approach database!! } -void DCLGPS::draw() { - //cout << "draw called!\n"; - _instrument->draw(); +void DCLGPS::draw(osg::State& state) { + _instrument->Draw(state); } void DCLGPS::init() { - globals->get_commands()->addCommand("kln89_msg_pressed", do_kln89_msg_pressed); - globals->get_commands()->addCommand("kln89_obs_pressed", do_kln89_obs_pressed); - globals->get_commands()->addCommand("kln89_alt_pressed", do_kln89_alt_pressed); - globals->get_commands()->addCommand("kln89_nrst_pressed", do_kln89_nrst_pressed); - globals->get_commands()->addCommand("kln89_dto_pressed", do_kln89_dto_pressed); - globals->get_commands()->addCommand("kln89_clr_pressed", do_kln89_clr_pressed); - globals->get_commands()->addCommand("kln89_ent_pressed", do_kln89_ent_pressed); - globals->get_commands()->addCommand("kln89_crsr_pressed", do_kln89_crsr_pressed); - globals->get_commands()->addCommand("kln89_knob1left1", do_kln89_knob1left1); - globals->get_commands()->addCommand("kln89_knob1right1", do_kln89_knob1right1); - globals->get_commands()->addCommand("kln89_knob2left1", do_kln89_knob2left1); - globals->get_commands()->addCommand("kln89_knob2right1", do_kln89_knob2right1); - - // Build the GPS-specific databases. - // TODO - consider splitting into real life GPS database regions - eg Americas, Europe etc. - // Note that this needs to run after FG's airport and nav databases are up and running - _waypoints.clear(); - const airport_list* apts = globals->get_airports()->getAirportList(); - for(unsigned int i = 0; i < apts->size(); ++i) { - FGAirport* a = (*apts)[i]; - GPSWaypoint* w = new GPSWaypoint; - w->id = a->getId(); - w->lat = a->getLatitude() * SG_DEGREES_TO_RADIANS; - w->lon = a->getLongitude() * SG_DEGREES_TO_RADIANS; - w->type = GPS_WP_APT; - gps_waypoint_map_iterator wtr = _waypoints.find(a->getId()); - if(wtr == _waypoints.end()) { - gps_waypoint_array arr; - arr.push_back(w); - _waypoints[w->id] = arr; - } else { - wtr->second.push_back(w); - } - } - nav_map_type navs = globals->get_navlist()->get_navaids(); - for(nav_map_iterator itr = navs.begin(); itr != navs.end(); ++itr) { - nav_list_type nlst = itr->second; - for(unsigned int i = 0; i < nlst.size(); ++i) { - FGNavRecord* n = nlst[i]; - if(n->get_fg_type() == FG_NAV_VOR || n->get_fg_type() == FG_NAV_NDB) { // We don't bother with ILS etc. - GPSWaypoint* w = new GPSWaypoint; - w->id = n->get_ident(); - w->lat = n->get_lat() * SG_DEGREES_TO_RADIANS; - w->lon = n->get_lon() * SG_DEGREES_TO_RADIANS; - w->type = (n->get_fg_type() == FG_NAV_VOR ? GPS_WP_VOR : GPS_WP_NDB); - gps_waypoint_map_iterator wtr = _waypoints.find(n->get_ident()); - if(wtr == _waypoints.end()) { - gps_waypoint_array arr; - arr.push_back(w); - _waypoints[w->id] = arr; - } else { - wtr->second.push_back(w); - } - } - } - } - const fix_map_type* fixes = globals->get_fixlist()->getFixList(); - for(fix_map_const_iterator itr = fixes->begin(); itr != fixes->end(); ++itr) { - FGFix f = itr->second; - GPSWaypoint* w = new GPSWaypoint; - w->id = f.get_ident(); - w->lat = f.get_lat() * SG_DEGREES_TO_RADIANS; - w->lon = f.get_lon() * SG_DEGREES_TO_RADIANS; - w->type = GPS_WP_INT; - gps_waypoint_map_iterator wtr = _waypoints.find(f.get_ident()); - if(wtr == _waypoints.end()) { - gps_waypoint_array arr; - arr.push_back(w); - _waypoints[w->id] = arr; - } else { - wtr->second.push_back(w); - } - } - // TODO - add USR waypoints as well. - + // Not sure if this should be here, but OK for now. CreateDefaultFlightPlans(); - - // Hack - hardwire some instrument approaches for testing. - // TODO - read these from file - either all at startup or as needed. - FGNPIAP* iap = new FGNPIAP; - iap->_id = "KHWD"; - iap->_name = "VOR/DME OR GPS-B"; - iap->_abbrev = "VOR/D"; - iap->_rwyStr = "B"; - iap->_IAF.clear(); - iap->_IAP.clear(); - iap->_MAP.clear(); - // ------- - GPSWaypoint* wp = new GPSWaypoint; - wp->id = "SUNOL"; - bool multi; - // Nasty using the find any function here, but it saves converting data from FGFix etc. - const GPSWaypoint* fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "MABRY"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "IMPLY"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAP; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "DECOT"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_FAF; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "MAPVV"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_MAP; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "OAK"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_MAHP; - iap->_MAP.push_back(wp); - // ------- - _np_iap[iap->_id].push_back(iap); - // ----------------------- - // ----------------------- - iap = new FGNPIAP; - iap->_id = "KHWD"; - iap->_name = "VOR OR GPS-A"; - iap->_abbrev = "VOR-"; - iap->_rwyStr = "A"; - iap->_IAF.clear(); - iap->_IAP.clear(); - iap->_MAP.clear(); - // ------- - wp = new GPSWaypoint; - wp->id = "SUNOL"; - // Nasty using the find any function here, but it saves converting data from FGFix etc. - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "MABRY"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "IMPLY"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAP; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "DECOT"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_FAF; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "MAPVV"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_MAP; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "OAK"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_MAHP; - iap->_MAP.push_back(wp); - // ------- - _np_iap[iap->_id].push_back(iap); - // ------------------ - // ------------------ - /* - // Ugh - don't load this one - the waypoints required aren't in fix.dat.gz - result: program crash! - // TODO - make the IAP loader robust to absent waypoints. - iap = new FGNPIAP; - iap->_id = "KHWD"; - iap->_name = "GPS RWY 28L"; - iap->_abbrev = "GPS"; - iap->_rwyStr = "28L"; - iap->_IAF.clear(); - iap->_IAP.clear(); - iap->_MAP.clear(); - // ------- - wp = new GPSWaypoint; - wp->id = "SUNOL"; - // Nasty using the find any function here, but it saves converting data from FGFix etc. - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "SJC"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "JOCPI"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAP; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "SUDGE"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_FAF; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "RW28L"; - wp->appType = GPS_MAP; - if(wp->id.substr(0, 2) == "RW" && wp->appType == GPS_MAP) { - // Assume that this is a missed-approach point based on the runway number - // Get the runway threshold location etc - } else { - fp = FindFirstById(wp->id, multi, true); - if(fp == NULL) { - cout << "Failed to find waypoint " << wp->id << " in database...\n"; - } else { - *wp = *fp; - } - } - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "OAK"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_MAHP; - iap->_MAP.push_back(wp); - // ------- - _np_iap[iap->_id].push_back(iap); - */ - iap = new FGNPIAP; - iap->_id = "C83"; - iap->_name = "GPS RWY 30"; - iap->_abbrev = "GPS"; - iap->_rwyStr = "30"; - iap->_IAF.clear(); - iap->_IAP.clear(); - iap->_MAP.clear(); - // ------- - wp = new GPSWaypoint; - wp->id = "PATYY"; - // Nasty using the find any function here, but it saves converting data from FGFix etc. - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "TRACY"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAF; - iap->_IAF.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "TRACY"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_IAP; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "BABPI"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_FAF; - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "AMOSY"; - wp->appType = GPS_MAP; - if(wp->id.substr(0, 2) == "RW" && wp->appType == GPS_MAP) { - // Assume that this is a missed-approach point based on the runway number - // TODO: Get the runway threshold location etc - cout << "TODO - implement missed-approach point based on rwy no.\n"; - } else { - fp = FindFirstById(wp->id, multi, true); - if(fp == NULL) { - cout << "Failed to find waypoint " << wp->id << " in database...\n"; - } else { - *wp = *fp; - wp->appType = GPS_MAP; - } - } - iap->_IAP.push_back(wp); - // ------- - wp = new GPSWaypoint; - wp->id = "HAIRE"; - fp = FindFirstById(wp->id, multi, true); - *wp = *fp; - wp->appType = GPS_MAHP; - iap->_MAP.push_back(wp); - // ------- - _np_iap[iap->_id].push_back(iap); + + LoadApproachData(); } void DCLGPS::bind() { @@ -657,6 +278,20 @@ void DCLGPS::update(double dt) { _checkLon = _gpsLon; _checkLat = _gpsLat; + // TODO - check for unit power before running this. + if(!_powerOnTimerSet) { + SetPowerOnTimer(); + } + + // Check if an alarm timer has expired + if(_alarmSet) { + if(_alarmTime.hr() == atoi(fgGetString("/instrumentation/clock/indicated-hour")) + && _alarmTime.min() == atoi(fgGetString("/instrumentation/clock/indicated-min"))) { + _messageStack.push_back("*Timer Expired"); + _alarmSet = false; + } + } + if(!_departed) { if(_groundSpeed_kts > 30.0) { _departed = true; @@ -842,14 +477,13 @@ void DCLGPS::update(double dt) { // Do nothing } else if(_activeWaypoint.appType == GPS_MAP) { // Don't sequence beyond the missed approach point - cout << "ACTIVE WAYPOINT is MAP - not sequencing!!!!!\n"; + //cout << "ACTIVE WAYPOINT is MAP - not sequencing!!!!!\n"; } else { - cout << "Sequencing...\n"; + //cout << "Sequencing...\n"; _fromWaypoint = _activeWaypoint; _activeWaypoint = *_activeFP->waypoints[idx + 1]; _dto = false; - // TODO - course alteration message format is dependent on whether we are slaved HSI/CDI indicator or not. - // For now assume we are not. + // Course alteration message format is dependent on whether we are slaved HSI/CDI indicator or not. string s; if(fgGetBool("/instrumentation/nav[0]/slaved-to-gps")) { // TODO - avoid the hardwiring on nav[0] @@ -884,27 +518,496 @@ void DCLGPS::update(double dt) { } } +/* + Expand a SIAP ident to the full procedure name (as shown on the approach chart). + NOTE: Some of this is inferred from data, some is from documentation. + + Example expansions from ARINC 424-18 [and the airport they're taken from]: + "R10LY" <--> "RNAV (GPS) Y RWY 10 L" [KBOI] + "R10-Z" <--> "RNAV (GPS) Z RWY 10" [KHTO] + "S25" <--> "VOR or GPS RWY 25" [KHHR] + "P20" <--> "GPS RWY 20" [KDAN] + "NDB-B" <--> "NDB or GPS-B" [KDAW] + "NDBC" <--> "NDB or GPS-C" [KEMT] + "VDMA" <--> "VOR/DME or GPS-A" [KDAW] + "VDM-A" <--> "VOR/DME or GPS-A" [KEAG] + "VDMB" <--> "VOR/DME or GPS-B" [KDKX] + "VORA" <--> "VOR or GPS-A" [KEMT] + + It seems that there are 2 basic types of expansions; those that include + the runway and those that don't. Of those that don't, it seems that 2 + different positions within the string to encode the identifying letter + are used, i.e. with a dash and without. +*/ +string DCLGPS::ExpandSIAPIdent(const string& ident) { + string name; + bool has_rwy = false; + + switch(ident[0]) { + case 'N': name = "NDB or GPS"; has_rwy = false; break; + case 'P': name = "GPS"; has_rwy = true; break; + case 'R': name = "RNAV (GPS)"; has_rwy = true; break; + case 'S': name = "VOR or GPS"; has_rwy = true; break; + case 'V': + if(ident[1] == 'D') name = "VOR/DME or GPS"; + else name = "VOR or GPS"; + has_rwy = false; + break; + default: // TODO output a log message + break; + } + + if(has_rwy) { + // Add the identifying letter if present + if(ident.size() == 5) { + name += ' '; + name += ident[4]; + } + + // Add the runway + name += " RWY "; + name += ident.substr(1, 2); + + // Add a left/right/centre indication if present. + if(ident.size() > 3) { + if((ident[3] != '-') && (ident[3] != ' ')) { // Early versions of the spec allowed a blank instead of a dash so check for both + name += ' '; + name += ident[3]; + } + } + } else { + // Add the identifying letter, which I *think* should always be present, but seems to be inconsistent as to whether a dash is used. + if(ident.size() == 5) { + name += '-'; + name += ident[4]; + } else if(ident.size() == 4) { + name += '-'; + name += ident[3]; + } else { + // No suffix letter + } + } + + return(name); +} + +/* + Load instrument approaches from an ARINC 424 file. + Tested on ARINC 424-18. + Known / current best guess at the format: + Col 1: Always 'S'. If it isn't, ditch it. + Col 2-4: "Customer area" code, eg "USA", "CAN". I think that CAN is used for Alaska. + Col 5: Section code. Used in conjunction with sub-section code. Definitions are with sub-section code. + Col 6: Always blank. + Col 7-10: ICAO (or FAA) airport ident. Left justified if < 4 chars. + Col 11-12: Based on ICAO geographical region. + Col 13: Sub-section code. Used in conjunction with section code. + "HD/E/F" => Helicopter record. + "HS" => Helicopter minimum safe altitude. + "PA" => Airport record. + "PF" => Approach segment. + "PG" => Runway record. + "PP" => Path point record. ??? + "PS" => MSA record (minimum safe altitude). + + ------ The following is for "PF", approach segment ------- + + Col 14-19: SIAP ident for this approach (left justified). This is a standardised abbreviated approach name. + e.g. "R10LZ" expands to "RNAV (GPS) Z RWY 10 L". See the comment block for ExpandSIAPIdent for full details. + Col 20: Route type. This is tricky - I don't have full documentation and am having to guess a bit. + 'A' => Arrival route? This seems to be used to encode arrival routes from the IAF to the approach proper. + Note that the final fix of the arrival route is duplicated in the approach proper. + 'D' => VOR/DME or GPS + 'N' => NDB or GPS + 'P' => GPS (ARINC 424-18), GPS and RNAV (GPS) (ARINC 424-15 and before). + 'R' => RNAV (GPS) (ARINC 424-18). + 'S' => VOR or GPS + Col 21-25: Transition identifier. AFAICT, this is the ident of the IAF for this initial approach route, and left blank for the final approach course. See col 30-34 for the actual fix ident. + Col 26: BLANK + Col 27-29: Sequence number - position within the route segment. Rule: 10-20-30 etc. + Col 30-34: Fix identifer. The ident of the waypoint. + Col 35-36: ICAO geographical region code. I think we can ignore this for now. + Col 37: Section code - ??? I don't know what this means + Col 38 Subsection code - ??? ditto - no idea! + Col 40: Waypoint type. + 'A' => Airport as waypoint + 'E' => Essential waypoint (e.g. change of heading at this waypoint, etc). + 'G' => Runway or helipad as waypoint + 'H' => Heliport as waypoint + 'N' => NDB as waypoint + 'P' => Phantom waypoint (not sure if this is used in rev 18?) + 'V' => VOR as waypoint + Col 41: Waypoint type. + 'B' => Flyover, approach transition, or final approach. + 'E' => end of route segment (transition waypoint). (Actually "End of terminal procedure route type" in the docs). + 'N' => ??? I've also seen 'N' in this column, but don't know what it indicates. + 'Y' => Flyover. + Col 43: Waypoint type. May also be blank when none of the below. + 'A' => Initial approach fix (IAF) + 'F' => Final approach fix + 'H' => Holding fix + 'I' => Final approach course fix + 'M' => Missed approach point + 'P' => ??? This is present, but I don't know what this means and it wasn't in the FAA docs that I found the above in! + ??? Possibly procedure turn? + 'C' => ??? This is also present in the data, but missing from the docs. Is at airport 00R. + Col 107-111 MSA center fix. We can ignore this. +*/ +void DCLGPS::LoadApproachData() { + FGNPIAP* iap = NULL; + GPSWaypoint* wp; + GPSFlightPlan* fp; + const GPSWaypoint* cwp; + + std::ifstream fin; + SGPath path = globals->get_fg_root(); + path.append("Navaids/rnav.dat"); + fin.open(path.c_str(), ios::in); + if(!fin) { + //cout << "Unable to open input file " << path.c_str() << '\n'; + return; + } else { + //cout << "Opened " << path.c_str() << " for reading\n"; + } + char tmp[256]; + string s; + + string apt_ident; // This gets set to the ICAO code of the current airport being processed. + string iap_ident; // The abbreviated name of the current approach being processed. + string wp_ident; // The ident of the waypoint of the current line + string last_apt_ident; + string last_iap_ident; + string last_wp_ident; + // There is no need to save the full name - it can be generated on the fly from the abbreviated name as and when needed. + bool apt_in_progress = false; // Set true whilst loading all the approaches for a given airport. + bool iap_in_progress = false; // Set true whilst loading a given approach. + bool iap_error = false; // Set true if there is an error loading a given approach. + bool route_in_progress = false; // Set true when we are loading a "route" segment of the approach. + int last_sequence_number = 0; // Position within the route, rule (rev 18): 10, 20, 30 etc. + int sequence_number; + char last_route_type = 0; + char route_type; + char waypoint_fix_type; // This is the waypoint type from col 43, i.e. the type of fix. May be blank. + + int j; + + // Debugging info + unsigned int nLoaded = 0; + unsigned int nErrors = 0; + + //for(i=0; i<64; ++i) { + while(!fin.eof()) { + fin.getline(tmp, 256); + //s = Fake_rnav_dat[i]; + s = tmp; + if(s.size() < 132) continue; + if(s[0] == 'S') { // Valid line + string country_code = s.substr(1, 3); + if(country_code == "USA") { // For now we'll stick to US procedures in case there are unknown gotchas with others + if(s[4] == 'P') { // Includes approaches. + if(s[12] == 'A') { // Airport record + apt_ident = s.substr(6, 4); + // Trim any whitespace from the ident. The ident is left justified, + // so any space will be at the end. + if(apt_ident[3] == ' ') apt_ident = apt_ident.substr(0, 3); + // I think that all idents are either 3 or 4 chars - could check this though! + if(!apt_in_progress) { + last_apt_ident = apt_ident; + apt_in_progress = 1; + } else { + if(last_apt_ident != apt_ident) { + if(iap_in_progress) { + if(iap_error) { + //cout << "ERROR: Unable to load approach " << iap->_ident << " at " << iap->_aptIdent << '\n'; + nErrors++; + } else { + _np_iap[iap->_aptIdent].push_back(iap); + //cout << "** Loaded " << iap->_aptIdent << "\t" << iap->_ident << '\n'; + nLoaded++; + } + iap_in_progress = false; + } + } + last_apt_ident = apt_ident; + } + iap_in_progress = 0; + } else if(s[12] == 'F') { // Approach segment + if(apt_in_progress) { + iap_ident = s.substr(13, 6); + // Trim any whitespace from the RH end. + for(j=0; j<6; ++j) { + if(iap_ident[5-j] == ' ') { + iap_ident = iap_ident.substr(0, 5-j); + } else { + // It's important to break here, since earlier versions of ARINC 424 allowed spaces in the ident. + break; + } + } + if(iap_in_progress) { + if(iap_ident != last_iap_ident) { + // This is a new approach - store the last one and trigger + // starting afresh by setting the in progress flag to false. + if(iap_error) { + //cout << "ERROR: Unable to load approach " << iap->_ident << " at " << iap->_aptIdent << '\n'; + nErrors++; + } else { + _np_iap[iap->_aptIdent].push_back(iap); + //cout << "Loaded " << iap->_aptIdent << "\t" << iap->_ident << '\n'; + nLoaded++; + } + iap_in_progress = false; + } + } + if(!iap_in_progress) { + iap = new FGNPIAP; + iap->_aptIdent = apt_ident; + iap->_ident = iap_ident; + iap->_name = ExpandSIAPIdent(iap_ident); // I suspect that it's probably better to just store idents, and to expand the names as needed. + // Note, we haven't set iap->_rwyStr yet. + last_iap_ident = iap_ident; + iap_in_progress = true; + iap_error = false; + } + + // Route type + route_type = s[19]; + sequence_number = atoi(s.substr(26,3).c_str()); + wp_ident = s.substr(29, 5); + waypoint_fix_type = s[42]; + // Trim any whitespace from the RH end + for(j=0; j<5; ++j) { + if(wp_ident[4-j] == ' ') { + wp_ident = wp_ident.substr(0, 4-j); + } else { + break; + } + } + + // Ignore lines with no waypoint ID for now - these are normally part of the + // missed approach procedure, and we don't use them in the KLN89. + if(!wp_ident.empty()) { + // Make a local copy of the waypoint for now, since we're not yet sure if we'll be using it + GPSWaypoint w; + w.id = wp_ident; + bool wp_error = false; + if(w.id.substr(0, 2) == "RW" && waypoint_fix_type == 'M') { + // Assume that this is a missed-approach point based on the runway number, which appears to be standard for most approaches. + // Note: Currently fgFindAirportID returns NULL on error, but getRunwayByIdent throws an exception. + const FGAirport* apt = fgFindAirportID(iap->_aptIdent); + if(apt) { + string rwystr; + try { + rwystr = w.id.substr(2, 2); + // TODO - sanity check the rwystr at this point to ensure we have a double digit number + if(w.id.size() > 4) { + if(w.id[4] == 'L' || w.id[4] == 'C' || w.id[4] == 'R') { + rwystr += w.id[4]; + } + } + FGRunway* rwy = apt->getRunwayByIdent(rwystr); + w.lat = rwy->begin().getLatitudeRad(); + w.lon = rwy->begin().getLongitudeRad(); + } catch(const sg_exception&) { + SG_LOG(SG_INSTR, SG_WARN, "Unable to find runway " << w.id.substr(2, 2) << " at airport " << iap->_aptIdent); + //cout << "Unable to find runway " << w.id.substr(2, 2) << " at airport " << iap->_aptIdent << " ( w.id = " << w.id << ", rwystr = " << rwystr << " )\n"; + wp_error = true; + } + } else { + wp_error = true; + } + } else { + cwp = FindFirstByExactId(w.id); + if(cwp) { + w = *cwp; + } else { + wp_error = true; + } + } + switch(waypoint_fix_type) { + case 'A': w.appType = GPS_IAF; break; + case 'F': w.appType = GPS_FAF; break; + case 'H': w.appType = GPS_MAHP; break; + case 'I': w.appType = GPS_IAP; break; + case 'M': w.appType = GPS_MAP; break; + case ' ': w.appType = GPS_APP_NONE; break; + //default: cout << "Unknown waypoint_fix_type: \'" << waypoint_fix_type << "\' [" << apt_ident << ", " << iap_ident << "]\n"; + } + + if(wp_error) { + //cout << "Unable to find waypoint " << w.id << " [" << apt_ident << ", " << iap_ident << "]\n"; + iap_error = true; + } + + if(!wp_error) { + if(route_in_progress) { + if(sequence_number > last_sequence_number) { + // TODO - add a check for runway numbers + // Check for the waypoint ID being the same as the previous line. + // This is often the case for the missed approach holding point. + if(wp_ident == last_wp_ident) { + if(waypoint_fix_type == 'H') { + if(!iap->_IAP.empty()) { + if(iap->_IAP[iap->_IAP.size() - 1]->appType == GPS_APP_NONE) { + iap->_IAP[iap->_IAP.size() - 1]->appType = GPS_MAHP; + } else { + //cout << "Waypoint is MAHP and another type! " << w.id << " [" << apt_ident << ", " << iap_ident << "]\n"; + } + } + } + } else { + // Create a new waypoint on the heap, copy the local copy into it, and push it onto the approach. + wp = new GPSWaypoint; + *wp = w; + if(route_type == 'A') { + fp->waypoints.push_back(wp); + } else { + iap->_IAP.push_back(wp); + } + } + } else if(sequence_number == last_sequence_number) { + // This seems to happen once per final approach route - one of the waypoints + // is duplicated with the same sequence number - I'm not sure what information + // the second line give yet so ignore it for now. + // TODO - figure this out! + } else { + // Finalise the current route and start a new one + // + // Finalise the current route + if(last_route_type == 'A') { + // Push the flightplan onto the approach + iap->_approachRoutes.push_back(fp); + } else { + // All the waypoints get pushed individually - don't need to do it. + } + // Start a new one + // There are basically 2 possibilities here - either it's one of the arrival transitions, + // or it's the core final approach course. + wp = new GPSWaypoint; + *wp = w; + if(route_type == 'A') { // It's one of the arrival transition(s) + fp = new GPSFlightPlan; + fp->waypoints.push_back(wp); + } else { + iap->_IAP.push_back(wp); + } + route_in_progress = true; + } + } else { + // Start a new route. + // There are basically 2 possibilities here - either it's one of the arrival transitions, + // or it's the core final approach course. + wp = new GPSWaypoint; + *wp = w; + if(route_type == 'A') { // It's one of the arrival transition(s) + fp = new GPSFlightPlan; + fp->waypoints.push_back(wp); + } else { + iap->_IAP.push_back(wp); + } + route_in_progress = true; + } + last_route_type = route_type; + last_wp_ident = wp_ident; + last_sequence_number = sequence_number; + } + } + } else { + // ERROR - no airport record read. + } + } + } else { + // Check and finalise any approaches in progress + // TODO - sanity check that the approach has all the required elements + if(iap_in_progress) { + // This is a new approach - store the last one and trigger + // starting afresh by setting the in progress flag to false. + if(iap_error) { + //cout << "ERROR: Unable to load approach " << iap->_ident << " at " << iap->_aptIdent << '\n'; + nErrors++; + } else { + _np_iap[iap->_aptIdent].push_back(iap); + //cout << "* Loaded " << iap->_aptIdent << "\t" << iap->_ident << '\n'; + nLoaded++; + } + iap_in_progress = false; + } + } + } + } + } + + // If we get to the end of the file, load any approach that is still in progress + // TODO - sanity check that the approach has all the required elements + if(iap_in_progress) { + if(iap_error) { + //cout << "ERROR: Unable to load approach " << iap->_ident << " at " << iap->_aptIdent << '\n'; + nErrors++; + } else { + _np_iap[iap->_aptIdent].push_back(iap); + //cout << "*** Loaded " << iap->_aptIdent << "\t" << iap->_ident << '\n'; + nLoaded++; + } + } + + //cout << "Done loading approach database\n"; + //cout << "Loaded: " << nLoaded << '\n'; + //cout << "Failed: " << nErrors << '\n'; + + fin.close(); +} + +GPSWaypoint* DCLGPS::GetActiveWaypoint() { + return &_activeWaypoint; +} + +// Returns meters +float DCLGPS::GetDistToActiveWaypoint() { + return _dist2Act; +} + +// I don't yet fully understand all the gotchas about where to source time from. +// This function sets the initial timer before the clock exports properties +// and the one below uses the clock to be consistent with the rest of the code. +// It might change soonish... +void DCLGPS::SetPowerOnTimer() { + struct tm *t = globals->get_time_params()->getGmt(); + _powerOnTime.set_hr(t->tm_hour); + _powerOnTime.set_min(t->tm_min); + _powerOnTimerSet = true; +} + +void DCLGPS::ResetPowerOnTimer() { + _powerOnTime.set_hr(atoi(fgGetString("/instrumentation/clock/indicated-hour"))); + _powerOnTime.set_min(atoi(fgGetString("/instrumentation/clock/indicated-min"))); + _powerOnTimerSet = true; +} + double DCLGPS::GetCDIDeflection() const { double xtd = CalcCrossTrackDeviation(); //nm return((xtd / _currentCdiScale) * 5.0 * 2.5 * -1.0); } void DCLGPS::DtoInitiate(const string& s) { - cout << "DtoInitiate, s = " << s << '\n'; - bool multi; - const GPSWaypoint* wp = FindFirstById(s, multi, true); + const GPSWaypoint* wp = FindFirstByExactId(s); if(wp) { - cout << "Waypoint found, starting dto operation!\n"; + // TODO - Currently we start DTO operation unconditionally, regardless of which mode we are in. + // In fact, the following rules apply: + // In LEG mode, start DTO as we currently do. + // In OBS mode, set the active waypoint to the requested waypoint, and then: + // If the KLN89 is not connected to an external HSI or CDI, set the OBS course to go direct to the waypoint. + // If the KLN89 *is* connected to an external HSI or CDI, it cannot set the course itself, and will display + // a scratchpad message with the course to set manually on the HSI/CDI. + // In both OBS cases, leave _dto false, since we don't need the virtual waypoint created. _dto = true; _activeWaypoint = *wp; _fromWaypoint.lat = _gpsLat; _fromWaypoint.lon = _gpsLon; _fromWaypoint.type = GPS_WP_VIRT; - _fromWaypoint.id = "DTOWP"; + _fromWaypoint.id = "_DTOWP_"; + delete wp; } else { - cout << "Waypoint not found, ignoring dto request\n"; - // Should bring up the user waypoint page, but we're not implementing that yet. - _dto = false; // TODO - implement this some day. + // TODO - Should bring up the user waypoint page. + _dto = false; } } @@ -917,23 +1020,40 @@ void DCLGPS::DtoCancel() { _dto = false; } -void DCLGPS::Knob1Left1() {} -void DCLGPS::Knob1Right1() {} -void DCLGPS::Knob2Left1() {} -void DCLGPS::Knob2Right1() {} -void DCLGPS::CrsrPressed() { _activePage->CrsrPressed(); } -void DCLGPS::EntPressed() { _activePage->EntPressed(); } -void DCLGPS::ClrPressed() { _activePage->ClrPressed(); } -void DCLGPS::DtoPressed() {} -void DCLGPS::NrstPressed() {} -void DCLGPS::AltPressed() {} - -void DCLGPS::OBSPressed() { +void DCLGPS::ToggleOBSMode() { _obsMode = !_obsMode; if(_obsMode) { - if(!_activeWaypoint.id.empty()) { - _obsHeading = _dtkMag; + // We need to set the OBS heading. The rules for this are: + // + // If the KLN89 is connected to an external HSI or CDI, then the OBS heading must be set + // to the OBS radial on the external indicator, and cannot be changed in the KLN89. + // + // If the KLN89 is not connected to an external indicator, then: + // If there is an active waypoint, the OBS heading is set such that the + // deviation indicator remains at the same deviation (i.e. set to DTK, + // although there may be some small difference). + // + // If there is not an active waypoint, I am not sure what value should be + // set. + // + if(fgGetBool("/instrumentation/nav/slaved-to-gps")) { + _obsHeading = static_cast(fgGetDouble("/instrumentation/nav/radials/selected-deg") + 0.5); + } else if(!_activeWaypoint.id.empty()) { + // NOTE: I am not sure if this is completely correct. There are sometimes small differences + // between DTK and default OBS heading in the simulator (~ 1 or 2 degrees). It might also + // be that OBS heading should be stored as float and only displayed as int, in order to maintain + // the initial bar deviation? + _obsHeading = static_cast(_dtkMag + 0.5); + //cout << "_dtkMag = " << _dtkMag << ", _dtkTrue = " << _dtkTrue << ", _obsHeading = " << _obsHeading << '\n'; + } else { + // TODO - what should we really do here? + _obsHeading = 0; } + + // Valid OBS heading values are 0 -> 359 degrees inclusive (from kln89 simulator). + if(_obsHeading > 359) _obsHeading -= 360; + if(_obsHeading < 0) _obsHeading += 360; + // TODO - the _fromWaypoint location will change as the OBS heading changes. // Might need to store the OBS initiation position somewhere in case it is needed again. SetOBSFromWaypoint(); @@ -947,11 +1067,10 @@ void DCLGPS::SetOBSFromWaypoint() { // TODO - base the 180 deg correction on the to/from flag. _fromWaypoint = GetPositionOnMagRadial(_activeWaypoint, 10, _obsHeading + 180.0); - _fromWaypoint.id = "OBSWP"; + _fromWaypoint.type = GPS_WP_VIRT; + _fromWaypoint.id = "_OBSWP_"; } -void DCLGPS::MsgPressed() {} - void DCLGPS::CDIFSDIncrease() { if(_currentCdiScaleIndex == 0) { _currentCdiScaleIndex = _cdiScales.size() - 1; @@ -973,16 +1092,6 @@ void DCLGPS::DrawChar(char c, int field, int px, int py, bool bold) { void DCLGPS::DrawText(const string& s, int field, int px, int py, bool bold) { } -void DCLGPS::SetBaroUnits(int n, bool wrap) { - if(n < 1) { - _baroUnits = (GPSPressureUnits)(wrap ? 3 : 1); - } else if(n > 3) { - _baroUnits = (GPSPressureUnits)(wrap ? 1 : 3); - } else { - _baroUnits = (GPSPressureUnits)n; - } -} - void DCLGPS::CreateDefaultFlightPlans() {} // Get the time to the active waypoint in seconds. @@ -1034,10 +1143,10 @@ double DCLGPS::GetTimeToWaypoint(const string& id) { } else if(id == _activeWaypoint.id) { return(_eta); } else { - bool multi; - const GPSWaypoint* wp = FindFirstById(id, multi, true); + const GPSWaypoint* wp = FindFirstByExactId(id); if(wp == NULL) return(-1.0); double distm = GetGreatCircleDistance(_gpsLat, _gpsLon, wp->lat, wp->lon); + delete wp; return(distm / _groundSpeed_ms); } return(-1.0); // Hopefully we never get here! @@ -1235,353 +1344,93 @@ void DCLGPS::CreateFlightPlan(GPSFlightPlan* fp, vector ids, vectorsecond)[0]); +class DCLGPSFilter : public FGPositioned::Filter +{ +public: + virtual bool pass(const FGPositioned* aPos) const { + switch (aPos->type()) { + case FGPositioned::AIRPORT: + // how about heliports and seaports? + case FGPositioned::NDB: + case FGPositioned::VOR: + case FGPositioned::WAYPOINT: + case FGPositioned::FIX: + break; + default: return false; // reject all other types } -} + return true; + } +}; -const GPSWaypoint* DCLGPS::FindFirstById(const string& id, bool &multi, bool exact) { - multi = false; - if(exact) return(ActualFindFirstById(id, exact)); - - // OK, that was the easy case, now the fuzzy case - const GPSWaypoint* w1 = ActualFindFirstById(id); - if(w1 == NULL) return(w1); - - // The non-trivial code from here to the end of the function is all to deal with the fact that - // the KLN89 alphabetical order (numbers AFTER letters) differs from ASCII order (numbers BEFORE letters). - string id2 = id; - //string id3 = id+'0'; - string id4 = id+'A'; - // Increment the last char to provide the boundary. Note that 'Z' -> '[' but we also need to check '0' for all since GPS has numbers after letters - //bool alfa = isalpha(id2[id2.size() - 1]); - id2[id2.size() - 1] = id2[id2.size() - 1] + 1; - const GPSWaypoint* w2 = ActualFindFirstById(id2); - //FGAirport* a3 = globals->get_airports()->findFirstById(id3); - const GPSWaypoint* w4 = ActualFindFirstById(id4); - //cout << "Strings sent were " << id << ", " << id2 << " and " << id4 << '\n'; - //cout << "Airports returned were (a1, a2, a4): " << a1->getId() << ", " << a2->getId() << ", " << a4->getId() << '\n'; - //cout << "Pointers were " << a1 << ", " << a2 << ", " << a4 << '\n'; - - // TODO - the below handles the imediately following char OK - // eg id = "KD" returns "KDAA" instead of "KD5" - // but it doesn't handle numbers / letters further down the string, - // eg - id = "I" returns "IA01" instead of "IAN" - // We either need to provide a custom comparison operator, or recurse this function if !isalpha further down the string. - // (Currenly fixed with recursion). - - if(w4 != w2) { // A-Z match - preferred - //cout << "A-Z match!\n"; - if(w4->id.size() - id.size() > 2) { - // Check for numbers further on - for(unsigned int i=id.size(); iid.size(); ++i) { - if(!isalpha(w4->id[i])) { - //cout << "SUBSTR is " << (a4->getId()).substr(0, i) << '\n'; - return(FindFirstById(w4->id.substr(0, i), multi, exact)); - } - } - } - return(w4); - } else if(w1 != w2) { // 0-9 match - //cout << "0-9 match!\n"; - if(w1->id.size() - id.size() > 2) { - // Check for numbers further on - for(unsigned int i=id.size(); iid.size(); ++i) { - if(!isalpha(w1->id[i])) { - //cout << "SUBSTR2 is " << (a4->getId()).substr(0, i) << '\n'; - return(FindFirstById(w1->id.substr(0, i), multi, exact)); - } - } - } - return(w1); - } else { // No match - return(NULL); - } - return NULL; +GPSWaypoint* DCLGPS::FindFirstById(const string& id) const +{ + DCLGPSFilter filter; + FGPositionedRef result = FGPositioned::findNextWithPartialId(NULL, id, &filter); + return GPSWaypoint::createFromPositioned(result); } -// Host specific lookup functions -// TODO - add the ASCII / alphabetical stuff from the Atlas version -FGNavRecord* DCLGPS::FindFirstVorById(const string& id, bool &multi, bool exact) { - // NOTE - at the moment multi is never set. - multi = false; - //if(exact) return(_overlays->FindFirstVorById(id, exact)); - - nav_list_type nav = globals->get_navlist()->findFirstByIdent(id, FG_NAV_VOR, exact); - - if(nav.size() > 1) multi = true; - //return(nav.empty() ? NULL : *(nav.begin())); - - // The above is sort of what we want - unfortunately we can't guarantee no NDB/ILS at the moment - if(nav.empty()) return(NULL); - - for(nav_list_iterator it = nav.begin(); it != nav.end(); ++it) { - if((*it)->get_type() == 3) return(*it); - } - return(NULL); // Shouldn't get here! +GPSWaypoint* DCLGPS::FindFirstByExactId(const string& id) const +{ + SGGeod pos(SGGeod::fromRad(_lon, _lat)); + FGPositionedRef result = FGPositioned::findClosestWithIdent(id, pos); + return GPSWaypoint::createFromPositioned(result); } -#if 0 -Overlays::NAV* DCLGPS::FindFirstVorById(const string& id, bool &multi, bool exact) { - // NOTE - at the moment multi is never set. - multi = false; - if(exact) return(_overlays->FindFirstVorById(id, exact)); - - // OK, that was the easy case, now the fuzzy case - Overlays::NAV* n1 = _overlays->FindFirstVorById(id); - if(n1 == NULL) return(n1); - - string id2 = id; - string id3 = id+'0'; - string id4 = id+'A'; - // Increment the last char to provide the boundary. Note that 'Z' -> '[' but we also need to check '0' for all since GPS has numbers after letters - bool alfa = isalpha(id2[id2.size() - 1]); - id2[id2.size() - 1] = id2[id2.size() - 1] + 1; - Overlays::NAV* n2 = _overlays->FindFirstVorById(id2); - //Overlays::NAV* n3 = _overlays->FindFirstVorById(id3); - //Overlays::NAV* n4 = _overlays->FindFirstVorById(id4); - //cout << "Strings sent were " << id << ", " << id2 << ", " << id3 << ", " << id4 << '\n'; - - - if(alfa) { - if(n1 != n2) { // match - return(n1); - } else { - return(NULL); - } - } - - /* - if(n1 != n2) { - // Something matches - the problem is the number/letter preference order is reversed between the GPS and the STL - if(n4 != n2) { - // There's a letter match - return that - return(n4); - } else { - // By definition we must have a number match - if(n3 == n2) cout << "HELP - LOGIC FLAW in find VOR!\n"; - return(n3); - } - } else { - // No match - return(NULL); - } - */ - return NULL; -} -#endif //0 - -// TODO - add the ASCII / alphabetical stuff from the Atlas version -FGNavRecord* DCLGPS::FindFirstNDBById(const string& id, bool &multi, bool exact) { - // NOTE - at the moment multi is never set. - multi = false; - //if(exact) return(_overlays->FindFirstVorById(id, exact)); - - nav_list_type nav = globals->get_navlist()->findFirstByIdent(id, FG_NAV_NDB, exact); - - if(nav.size() > 1) multi = true; - //return(nav.empty() ? NULL : *(nav.begin())); - - // The above is sort of what we want - unfortunately we can't guarantee no NDB/ILS at the moment - if(nav.empty()) return(NULL); - - for(nav_list_iterator it = nav.begin(); it != nav.end(); ++it) { - if((*it)->get_type() == 2) return(*it); - } - return(NULL); // Shouldn't get here! -} -#if 0 -Overlays::NAV* DCLGPS::FindFirstNDBById(const string& id, bool &multi, bool exact) { - // NOTE - at the moment multi is never set. - multi = false; - if(exact) return(_overlays->FindFirstNDBById(id, exact)); - - // OK, that was the easy case, now the fuzzy case - Overlays::NAV* n1 = _overlays->FindFirstNDBById(id); - if(n1 == NULL) return(n1); - - string id2 = id; - string id3 = id+'0'; - string id4 = id+'A'; - // Increment the last char to provide the boundary. Note that 'Z' -> '[' but we also need to check '0' for all since GPS has numbers after letters - bool alfa = isalpha(id2[id2.size() - 1]); - id2[id2.size() - 1] = id2[id2.size() - 1] + 1; - Overlays::NAV* n2 = _overlays->FindFirstNDBById(id2); - //Overlays::NAV* n3 = _overlays->FindFirstNDBById(id3); - //Overlays::NAV* n4 = _overlays->FindFirstNDBById(id4); - //cout << "Strings sent were " << id << ", " << id2 << ", " << id3 << ", " << id4 << '\n'; - - - if(alfa) { - if(n1 != n2) { // match - return(n1); - } else { - return(NULL); - } - } - - /* - if(n1 != n2) { - // Something matches - the problem is the number/letter preference order is reversed between the GPS and the STL - if(n4 != n2) { - // There's a letter match - return that - return(n4); - } else { - // By definition we must have a number match - if(n3 == n2) cout << "HELP - LOGIC FLAW in find VOR!\n"; - return(n3); - } - } else { - // No match - return(NULL); - } - */ - return NULL; -} -#endif //0 // TODO - add the ASCII / alphabetical stuff from the Atlas version -const FGFix* DCLGPS::FindFirstIntById(const string& id, bool &multi, bool exact) { - // NOTE - at the moment multi is never set, and indeed can't be - // since FG can only map one Fix per ID at the moment. - multi = false; - if(exact) return(globals->get_fixlist()->findFirstByIdent(id, exact)); - - const FGFix* f1 = globals->get_fixlist()->findFirstByIdent(id, exact); - if(f1 == NULL) return(f1); - - // The non-trivial code from here to the end of the function is all to deal with the fact that - // the KLN89 alphabetical order (numbers AFTER letters) differs from ASCII order (numbers BEFORE letters). - // It is copied from the airport version which is definately needed, but at present I'm not actually - // sure if any fixes in FG or real-life have numbers in them! - string id2 = id; - //string id3 = id+'0'; - string id4 = id+'A'; - // Increment the last char to provide the boundary. Note that 'Z' -> '[' but we also need to check '0' for all since GPS has numbers after letters - //bool alfa = isalpha(id2[id2.size() - 1]); - id2[id2.size() - 1] = id2[id2.size() - 1] + 1; - const FGFix* f2 = globals->get_fixlist()->findFirstByIdent(id2); - //const FGFix* a3 = globals->get_fixlist()->findFirstByIdent(id3); - const FGFix* f4 = globals->get_fixlist()->findFirstByIdent(id4); - - // TODO - the below handles the imediately following char OK - // eg id = "KD" returns "KDAA" instead of "KD5" - // but it doesn't handle numbers / letters further down the string, - // eg - id = "I" returns "IA01" instead of "IAN" - // We either need to provide a custom comparison operator, or recurse this function if !isalpha further down the string. - // (Currenly fixed with recursion). - - if(f4 != f2) { // A-Z match - preferred - //cout << "A-Z match!\n"; - if(f4->get_ident().size() - id.size() > 2) { - // Check for numbers further on - for(unsigned int i=id.size(); iget_ident().size(); ++i) { - if(!isalpha(f4->get_ident()[i])) { - //cout << "SUBSTR is " << (a4->getId()).substr(0, i) << '\n'; - return(FindFirstIntById(f4->get_ident().substr(0, i), multi, exact)); - } - } - } - return(f4); - } else if(f1 != f2) { // 0-9 match - //cout << "0-9 match!\n"; - if(f1->get_ident().size() - id.size() > 2) { - // Check for numbers further on - for(unsigned int i=id.size(); iget_ident().size(); ++i) { - if(!isalpha(f1->get_ident()[i])) { - //cout << "SUBSTR2 is " << (a4->getId()).substr(0, i) << '\n'; - return(FindFirstIntById(f1->get_ident().substr(0, i), multi, exact)); - } - } - } - return(f1); - } else { // No match - return(NULL); - } - - return NULL; // Don't think we can ever get here. -} - -const FGAirport* DCLGPS::FindFirstAptById(const string& id, bool &multi, bool exact) { - // NOTE - at the moment multi is never set. - //cout << "FindFirstAptById, id = " << id << '\n'; - multi = false; - if(exact) return(globals->get_airports()->findFirstById(id, exact)); - - // OK, that was the easy case, now the fuzzy case - const FGAirport* a1 = globals->get_airports()->findFirstById(id); - if(a1 == NULL) return(a1); - - // The non-trivial code from here to the end of the function is all to deal with the fact that - // the KLN89 alphabetical order (numbers AFTER letters) differs from ASCII order (numbers BEFORE letters). - string id2 = id; - //string id3 = id+'0'; - string id4 = id+'A'; - // Increment the last char to provide the boundary. Note that 'Z' -> '[' but we also need to check '0' for all since GPS has numbers after letters - //bool alfa = isalpha(id2[id2.size() - 1]); - id2[id2.size() - 1] = id2[id2.size() - 1] + 1; - const FGAirport* a2 = globals->get_airports()->findFirstById(id2); - //FGAirport* a3 = globals->get_airports()->findFirstById(id3); - const FGAirport* a4 = globals->get_airports()->findFirstById(id4); - //cout << "Strings sent were " << id << ", " << id2 << " and " << id4 << '\n'; - //cout << "Airports returned were (a1, a2, a4): " << a1->getId() << ", " << a2->getId() << ", " << a4->getId() << '\n'; - //cout << "Pointers were " << a1 << ", " << a2 << ", " << a4 << '\n'; - - // TODO - the below handles the imediately following char OK - // eg id = "KD" returns "KDAA" instead of "KD5" - // but it doesn't handle numbers / letters further down the string, - // eg - id = "I" returns "IA01" instead of "IAN" - // We either need to provide a custom comparison operator, or recurse this function if !isalpha further down the string. - // (Currenly fixed with recursion). - - if(a4 != a2) { // A-Z match - preferred - //cout << "A-Z match!\n"; - if(a4->getId().size() - id.size() > 2) { - // Check for numbers further on - for(unsigned int i=id.size(); igetId().size(); ++i) { - if(!isalpha(a4->getId()[i])) { - //cout << "SUBSTR is " << (a4->getId()).substr(0, i) << '\n'; - return(FindFirstAptById(a4->getId().substr(0, i), multi, exact)); - } - } - } - return(a4); - } else if(a1 != a2) { // 0-9 match - //cout << "0-9 match!\n"; - if(a1->getId().size() - id.size() > 2) { - // Check for numbers further on - for(unsigned int i=id.size(); igetId().size(); ++i) { - if(!isalpha(a1->getId()[i])) { - //cout << "SUBSTR2 is " << (a4->getId()).substr(0, i) << '\n'; - return(FindFirstAptById(a1->getId().substr(0, i), multi, exact)); - } - } - } - return(a1); - } else { // No match - return(NULL); - } - - return NULL; -} - -FGNavRecord* DCLGPS::FindClosestVor(double lat_rad, double lon_rad) { - return(globals->get_navlist()->findClosest(lon_rad, lat_rad, 0.0, FG_NAV_VOR)); +FGPositioned* DCLGPS::FindTypedFirstById(const string& id, FGPositioned::Type ty, bool &multi, bool exact) +{ + multi = false; + FGPositioned::TypeFilter filter(ty); + + if (exact) { + FGPositioned::List matches = + FGPositioned::findAllWithIdent(id, &filter); + FGPositioned::sortByRange(matches, SGGeod::fromRad(_lon, _lat)); + multi = (matches.size() > 1); + return matches.empty() ? NULL : matches.front().ptr(); + } + + return FGPositioned::findNextWithPartialId(NULL, id, &filter); +} + +FGNavRecord* DCLGPS::FindFirstVorById(const string& id, bool &multi, bool exact) +{ + return dynamic_cast(FindTypedFirstById(id, FGPositioned::VOR, multi, exact)); +} + +FGNavRecord* DCLGPS::FindFirstNDBById(const string& id, bool &multi, bool exact) +{ + return dynamic_cast(FindTypedFirstById(id, FGPositioned::NDB, multi, exact)); +} + +const FGFix* DCLGPS::FindFirstIntById(const string& id, bool &multi, bool exact) +{ + return dynamic_cast(FindTypedFirstById(id, FGPositioned::FIX, multi, exact)); +} + +const FGAirport* DCLGPS::FindFirstAptById(const string& id, bool &multi, bool exact) +{ + return dynamic_cast(FindTypedFirstById(id, FGPositioned::AIRPORT, multi, exact)); +} + +FGNavRecord* DCLGPS::FindClosestVor(double lat_rad, double lon_rad) { + FGPositioned::TypeFilter filter(FGPositioned::VOR); + double cutoff = 1000; // nautical miles + FGPositionedRef v = FGPositioned::findClosest(SGGeod::fromRad(lon_rad, lat_rad), cutoff, &filter); + if (!v) { + return NULL; + } + + return dynamic_cast(v.ptr()); } //----------------------------------------------------------------------------------------------------------