From 14a09673b958e197ce4450c7e4a5b3da55170d3d Mon Sep 17 00:00:00 2001 From: jmt Date: Fri, 18 Sep 2009 15:27:19 +0000 Subject: [PATCH] ATIS overhaul by John Denker, adapted to trunk by me. 8:: AWOS is available at AWOS locations. (Previously only ATIS was implemented.) 9:: ATIS phraseology now more nearly conforms to international standard METAR pattern, and therefore to usual FAA practice.(*) Items marked with a (*) are fully implemented in the /text/ of the ATIS message, but the voiced version of the message is degraded by limitations of the FGFS built-in text-to-speech system. 10:: ATIS now reports sky condition.(*) 11:: ATIS now reports multiple layers of clouds, not just the lowest layer.(*) 12:: ATIS now takes field elevation into account when calculating sky condition and ceiling. 13:: ATIS now reports dewpoint.(*) 14:: ATIS now can handle negative quantities (temperature and dewpoint).(*) 15:: ATIS can now report report fractional-mile visibility.(*) 16:: ATIS now uses magnetic (not true) wind directions, as it should. 17:: ATIS generates correct runway number and suffix (nine right, one one left). 18:: ATIS can be received on nav frequencies, not just comm. 19:: Nothing bad happens if the same ATIS is tuned up on more than one receiver. 20:: ATIS can be updated at times other than at the top of the hour. 21:: ATIS listens for an "attention" signal, and responds to changes in the weather by issuing a new ATIS message (somewhat like a "special observation"). 22:: ATIS volume now responds to radio volume setting. 23:: Area-related services (i.e. approach radar) are handled more-nearly consistently with radio-frequency related services. 24:: ATIS sequence-letter generation has been fixed. 25:: ATIS messages are now in the property tree, so they can be read e.g. via the http interface. --- src/ATCDCL/AILocalTraffic.cxx | 1 + src/ATCDCL/AIMgr.cxx | 21 +- src/ATCDCL/AIMgr.hxx | 13 +- src/ATCDCL/AIPlane.cxx | 37 +- src/ATCDCL/AIPlane.hxx | 2 +- src/ATCDCL/ATC.cxx | 166 +++-- src/ATCDCL/ATC.hxx | 150 ++--- src/ATCDCL/ATCDialog.cxx | 18 +- src/ATCDCL/ATCDialog.hxx | 3 +- src/ATCDCL/ATCVoice.cxx | 90 +-- src/ATCDCL/ATCVoice.hxx | 12 +- src/ATCDCL/ATCmgr.cxx | 1004 +++++++++++++------------------ src/ATCDCL/ATCmgr.hxx | 57 +- src/ATCDCL/ATCutils.cxx | 120 ++-- src/ATCDCL/ATCutils.hxx | 20 +- src/ATCDCL/approach.cxx | 23 +- src/ATCDCL/atis.cxx | 566 +++++++++++------ src/ATCDCL/atis.hxx | 25 +- src/ATCDCL/commlist.cxx | 419 ++++++------- src/ATCDCL/commlist.hxx | 112 ++-- src/ATCDCL/tower.cxx | 21 +- src/ATCDCL/tower.hxx | 1 + src/Airports/apt_loader.cxx | 84 ++- src/Airports/apt_loader.hxx | 7 +- src/Environment/environment.cxx | 1 - src/Main/fg_init.cxx | 7 +- 26 files changed, 1519 insertions(+), 1461 deletions(-) diff --git a/src/ATCDCL/AILocalTraffic.cxx b/src/ATCDCL/AILocalTraffic.cxx index e38e43149..2acf7d10d 100644 --- a/src/ATCDCL/AILocalTraffic.cxx +++ b/src/ATCDCL/AILocalTraffic.cxx @@ -546,6 +546,7 @@ void FGAILocalTraffic::Update(double dt) { // And to avoid compiler warnings... case APPROACH: break; case ATIS: break; + case AWOS: break; case ENROUTE: break; case DEPARTURE: break; case INVALID: break; diff --git a/src/ATCDCL/AIMgr.cxx b/src/ATCDCL/AIMgr.cxx index 073acd7e7..8b6c448bc 100644 --- a/src/ATCDCL/AIMgr.cxx +++ b/src/ATCDCL/AIMgr.cxx @@ -79,10 +79,6 @@ void FGAIMgr::init() { lon_node = fgGetNode("/position/longitude-deg", true); lat_node = fgGetNode("/position/latitude-deg", true); elev_node = fgGetNode("/position/altitude-ft", true); - - lon = lon_node->getDoubleValue(); - lat = lat_node->getDoubleValue(); - elev = elev_node->getDoubleValue(); // Load up models at the start to avoid pausing later // Hack alert - Hardwired paths!! @@ -167,7 +163,7 @@ void FGAIMgr::update(double dt) { //cout << activated.size() << '\n'; - SGGeod userPos = SGGeod::fromDegM(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue()); + SGGeod userPos = SGGeod::fromDegM(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue()); // TODO - make these class variables!! static int i = 0; @@ -475,10 +471,9 @@ void FGAIMgr::SearchByPos(double range) { //cout << "In SearchByPos(...)" << endl; // get bucket number for plane position - lon = lon_node->getDoubleValue(); - lat = lat_node->getDoubleValue(); - elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; - SGBucket buck(lon, lat); + _userAircraftPos = SGGeod::fromDegFt(lon_node->getDoubleValue(), + lat_node->getDoubleValue(), elev_node->getDoubleValue()); + SGBucket buck(_userAircraftPos); // get neigboring buckets int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2); @@ -492,7 +487,7 @@ void FGAIMgr::SearchByPos(double range) { //cout << "i loop\n"; for ( int j=-by; j<=by; j++) { //cout << "j loop\n"; - buck = sgBucketOffset(lon, lat, i, j); + buck = sgBucketOffset(_userAircraftPos.getLongitudeDeg(), _userAircraftPos.getLatitudeDeg(), i, j); long int bucket = buck.gen_index(); //cout << "bucket is " << bucket << endl; if(facilities.find(bucket) != facilities.end()) { @@ -530,14 +525,14 @@ void FGAIMgr::SearchByPos(double range) { comm_list_type towered; comm_list_iterator twd_itr; - int num_twd = current_commlist->FindByPos(lon, lat, elev, range, &towered, TOWER); + int num_twd = current_commlist->FindByPos(_userAircraftPos, range, &towered, TOWER); if (num_twd != 0) { double closest = 1000000; string s = ""; for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) { // Only activate the closest airport not already activated each time. if(activated.find(twd_itr->ident) == activated.end()) { - double sep = dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), fgGetAirportPos(twd_itr->ident)); + double sep = dclGetHorizontalSeparation(_userAircraftPos, fgGetAirportPos(twd_itr->ident)); if(sep < closest) { closest = sep; s = twd_itr->ident; @@ -601,7 +596,7 @@ string FGAIMgr::GenerateShortForm(const string& callsign, const string& plane_st //cout << c << '\n'; string tmp = ""; tmp += c; - if(isalpha(c)) s += GetPhoneticIdent(c); + if(isalpha(c)) s += GetPhoneticLetter(c); else s += ConvertNumToSpokenDigits(tmp); if(i > 1) s += '-'; } diff --git a/src/ATCDCL/AIMgr.hxx b/src/ATCDCL/AIMgr.hxx index d08d6f566..8d87c5c98 100644 --- a/src/ATCDCL/AIMgr.hxx +++ b/src/ATCDCL/AIMgr.hxx @@ -81,15 +81,12 @@ private: typedef ai_callsigns_map_type::iterator ai_callsigns_map_iterator; ai_callsigns_map_type ai_callsigns_used; - // Position of the Users Aircraft - double lon; - double lat; - double elev; - // Pointers to current users position - SGPropertyNode_ptr lon_node; - SGPropertyNode_ptr lat_node; - SGPropertyNode_ptr elev_node; + SGGeod _userAircraftPos; + // Pointers to current users position + SGPropertyNode_ptr lon_node; + SGPropertyNode_ptr lat_node; + SGPropertyNode_ptr elev_node; public: FGAIMgr(); diff --git a/src/ATCDCL/AIPlane.cxx b/src/ATCDCL/AIPlane.cxx index f3bf43b8c..1c394d638 100644 --- a/src/ATCDCL/AIPlane.cxx +++ b/src/ATCDCL/AIPlane.cxx @@ -105,7 +105,9 @@ void FGAIPlane::Update(double dt) { if(1) { // For now assume in range !!! // TODO - implement range checking - Render(plane.callsign, false); + // TODO - at the moment the volume is always set off comm1 + double volume = fgGetDouble("/instrumentation/comm[0]/volume"); + Render(plane.callsign, volume, false); } } // Run the callback regardless of whether on same freq as user or not. @@ -165,7 +167,9 @@ void FGAIPlane::ConditionalTransmit(double timeout, int callback_code) { } void FGAIPlane::ImmediateTransmit(int callback_code) { - Render(plane.callsign, false); + // TODO - at the moment the volume is always set off comm1 + double volume = fgGetDouble("/instrumentation/comm[0]/volume"); + Render(plane.callsign, volume, false); if(callback_code) { ProcessCallback(callback_code); } @@ -179,26 +183,25 @@ void FGAIPlane::ProcessCallback(int code) { // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. -void FGAIPlane::Render(const string& refname, bool repeating) { +void FGAIPlane::Render(const string& refname, const double volume, bool repeating) { fgSetString("/sim/messages/ai-plane", pending_transmission.c_str()); #ifdef ENABLE_AUDIO_SUPPORT voice = (voiceOK && fgGetBool("/sim/sound/voice")); if(voice) { - int len; - unsigned char* buf = vPtr->WriteMessage((char*)pending_transmission.c_str(), len, voice); - if(voice) { - SGSoundSample* simple = new SGSoundSample(buf, len, 8000); - // TODO - at the moment the volume is always set off comm1 - // and can't be changed after the transmission has started. - simple->set_volume(5.0 * fgGetDouble("/instrumentation/comm[0]/volume")); - globals->get_soundmgr()->add(simple, refname); - if(repeating) { - globals->get_soundmgr()->play_looped(refname); - } else { - globals->get_soundmgr()->play_once(refname); - } + string buf = vPtr->WriteMessage((char*)pending_transmission.c_str(), voice); + if(voice) { + SGSoundSample* simple = + new SGSoundSample((unsigned char*)buf.c_str(), buf.length(), 8000, AL_FORMAT_MONO8 ); + // TODO - at the moment the volume can't be changed + // after the transmission has started. + simple->set_volume(volume); + globals->get_soundmgr()->add(simple, refname); + if(repeating) { + globals->get_soundmgr()->play_looped(refname); + } else { + globals->get_soundmgr()->play_once(refname); } - delete[] buf; + } } #endif // ENABLE_AUDIO_SUPPORT if(!voice) { diff --git a/src/ATCDCL/AIPlane.hxx b/src/ATCDCL/AIPlane.hxx index 489ec1793..e1f581c6c 100644 --- a/src/ATCDCL/AIPlane.hxx +++ b/src/ATCDCL/AIPlane.hxx @@ -140,7 +140,7 @@ private: // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. - void Render(const string& refname, bool repeating); + void Render(const string& refname, const double volume, bool repeating); // Cease rendering a transmission. // Requires the sound manager refname if audio, else "". diff --git a/src/ATCDCL/ATC.cxx b/src/ATCDCL/ATC.cxx index 2bc321805..193b82457 100644 --- a/src/ATCDCL/ATC.cxx +++ b/src/ATCDCL/ATC.cxx @@ -22,43 +22,46 @@ # include #endif +#include "ATC.hxx" + +#include + #include #include #include
#include
-#include "ATC.hxx" -FGATC::FGATC() { - freqClear = true; - receiving = false; - respond = false; - runResponseCounter = false; - _runReleaseCounter = false; - responseID = ""; - responseReqd = false; - _type = INVALID; - _display = false; - _displaying = false; - + +FGATC::FGATC() : + _voiceOK(false), + freqClear(true), + receiving(false), + respond(false), + responseID(""), + runResponseCounter(false), + _runReleaseCounter(false), + responseReqd(false), + _type(INVALID), + _display(false), // Transmission timing stuff - pending_transmission = ""; - _timeout = 0; - _pending = false; - _callback_code = 0; - _transmit = false; - _transmitting = false; - _counter = 0.0; - _max_count = 5.0; - - _voiceOK = false; + pending_transmission(""), + _timeout(0), + _pending(false), + _callback_code(0), + _transmit(false), + _transmitting(false), + _counter(0.0), + _max_count(5.0) +{ } FGATC::~FGATC() { } -// Derived classes wishing to use the response counter should call this from their own Update(...). +// Derived classes wishing to use the response counter should +// call this from their own Update(...). void FGATC::Update(double dt) { if(runResponseCounter) { //cout << responseCounter << '\t' << responseTime << '\n'; @@ -200,12 +203,9 @@ int FGATC::RemovePlane() { } void FGATC::SetData(ATCData* d) { - lon = d->lon; - lat = d->lat; - elev = d->elev; - x = d->x; - y = d->y; - z = d->z; + _type = d->type; + _geod = d->geod; + _cart = d->cart; range = d->range; ident = d->ident; name = d->name; @@ -216,7 +216,8 @@ void FGATC::SetData(ATCData* d) { // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. -void FGATC::Render(string& msg, const string& refname, bool repeating) { +void FGATC::Render(string& msg, const double volume, + const string& refname, const bool repeating) { if (repeating) fgSetString("/sim/messages/atis", msg.c_str()); else @@ -225,26 +226,29 @@ void FGATC::Render(string& msg, const string& refname, bool repeating) { #ifdef ENABLE_AUDIO_SUPPORT _voice = (_voiceOK && fgGetBool("/sim/sound/voice")); if(_voice) { - int len; - unsigned char* buf = _vPtr->WriteMessage((char*)msg.c_str(), len, _voice); - if(_voice) { - try { - SGSoundSample *simple - = new SGSoundSample(buf, len, 8000); - // TODO - at the moment the volume is always set off comm1 - // and can't be changed after the transmission has started. - simple->set_volume(5.0 * fgGetDouble("/instrumentation/comm[0]/volume")); - globals->get_soundmgr()->add(simple, refname); - if(repeating) { - globals->get_soundmgr()->play_looped(refname); - } else { - globals->get_soundmgr()->play_once(refname); - } - } catch ( sg_io_exception &e ) { - SG_LOG(SG_GENERAL, SG_ALERT, e.getFormattedMessage()); - } + string buf = _vPtr->WriteMessage((char*)msg.c_str(), _voice); + if(_voice) { + NoRender(refname); + try { +// >>> Beware: must pass a (new) object to the (add) method, +// >>> because the (remove) method is going to do a (delete) +// >>> whether that's what you want or not. + SGSoundSample *simple = + new SGSoundSample((unsigned char*) buf.c_str(), + buf.length(), 8000, AL_FORMAT_MONO8); + // TODO - at the moment the volume can't be changed + // after the transmission has started. + simple->set_volume(volume); + globals->get_soundmgr()->add(simple, refname); + if(repeating) { + globals->get_soundmgr()->play_looped(refname); + } else { + globals->get_soundmgr()->play_once(refname); + } + } catch ( sg_io_exception &e ) { + SG_LOG(SG_GENERAL, SG_ALERT, e.getFormattedMessage()); } - delete[] buf; + } } #endif // ENABLE_AUDIO_SUPPORT if(!_voice) { @@ -279,13 +283,73 @@ string FGATC::GenText(const string& m, int c) { ostream& operator << (ostream& os, atc_type atc) { switch(atc) { - case(INVALID): return(os << "INVALID"); + case(AWOS): return(os << "AWOS"); case(ATIS): return(os << "ATIS"); case(GROUND): return(os << "GROUND"); case(TOWER): return(os << "TOWER"); case(APPROACH): return(os << "APPROACH"); case(DEPARTURE): return(os << "DEPARTURE"); case(ENROUTE): return(os << "ENROUTE"); + case(INVALID): return(os << "INVALID"); } return(os << "ERROR - Unknown switch in atc_type operator << "); } + +std::istream& operator >> ( std::istream& fin, ATCData& a ) +{ + double f; + char ch; + char tp; + + fin >> tp; + + switch(tp) { + case 'I': + a.type = ATIS; + break; + case 'T': + a.type = TOWER; + break; + case 'G': + a.type = GROUND; + break; + case 'A': + a.type = APPROACH; + break; + case '[': + a.type = INVALID; + return fin >> skipeol; + default: + SG_LOG(SG_GENERAL, SG_ALERT, "Warning - unknown type \'" << tp << "\' found whilst reading ATC frequency data!\n"); + a.type = INVALID; + return fin >> skipeol; + } + + double lat, lon, elev; + + fin >> lat >> lon >> elev >> f >> a.range >> a.ident; + a.geod = SGGeod::fromDegM(lon, lat, elev); + a.name = ""; + fin >> ch; + if(ch != '"') a.name += ch; + while(1) { + //in >> noskipws + fin.unsetf(std::ios::skipws); + fin >> ch; + if((ch == '"') || (ch == 0x0A)) { + break; + } // we shouldn't need the 0x0A but it makes a nice safely in case someone leaves off the " + a.name += ch; + } + fin.setf(std::ios::skipws); + //cout << "Comm name = " << a.name << '\n'; + + a.freq = (int)(f*100.0 + 0.5); + + // cout << a.ident << endl; + + // generate cartesian coordinates + a.cart = SGVec3d::fromGeod(a.geod); + return fin >> skipeol; +} + diff --git a/src/ATCDCL/ATC.hxx b/src/ATCDCL/ATC.hxx index 0a6d55a8b..adb62264e 100644 --- a/src/ATCDCL/ATC.hxx +++ b/src/ATCDCL/ATC.hxx @@ -28,17 +28,25 @@ #include #include -#include -#include - +#include #include #include "ATCVoice.hxx" -using std::ostream; -using std::string; -using std::ios; - +// Convert a frequency in MHz to tens of kHz +// so we can use it e.g. as an index into commlist_freq +// +// If freq > 1000 assume it's already in tens of KHz; +// otherwise assume MHz. +// +// Note: 122.375 must be rounded DOWN to 12237 +// in order to be consistent with apt.dat et cetera. +inline int kHz10(double freq) +{ + if (freq > 1000.) return int(freq); + return int(freq*100.0 + 0.25); +} + enum plane_type { UNKNOWN, GA_SINGLE, @@ -61,28 +69,24 @@ struct PlaneRec { // Possible types of ATC type that the radios may be tuned to. // INVALID implies not tuned in to anything. enum atc_type { - INVALID, + AWOS, ATIS, GROUND, TOWER, APPROACH, DEPARTURE, - ENROUTE + ENROUTE, + INVALID /* must be last element; see ATC_NUM_TYPES */ }; -const int ATC_NUM_TYPES = 7; +const int ATC_NUM_TYPES = 1 + INVALID; // DCL - new experimental ATC data store struct ATCData { atc_type type; - // I've deliberately used float instead of double here to keep the size down - we'll be storing thousands of these in memory. - // In fact, we could probably ditch x, y and z and generate on the fly as needed. - // On the other hand, we'll probably end up reading this data directly from the DAFIF eventually anyway!! - float lon, lat, elev; - float x, y, z; - //int freq; + SGGeod geod; + SGVec3d cart; unsigned short int freq; - //int range; unsigned short int range; std::string ident; std::string name; @@ -104,12 +108,14 @@ struct RunwayDetails { std::ostream& operator << (std::ostream& os, atc_type atc); class FGATC { - + friend class FGATCMgr; public: FGATC(); virtual ~FGATC(); + virtual void Init()=0; + // Run the internal calculations // Derived classes should call this method from their own Update methods if they // wish to use the response timer functionality. @@ -154,19 +160,7 @@ public: // Set the core ATC data void SetData(ATCData* d); - - inline double get_lon() const { return lon; } - inline void set_lon(const double ln) {lon = ln;} - inline double get_lat() const { return lat; } - inline void set_lat(const double lt) {lat = lt;} - inline double get_elev() const { return elev; } - inline void set_elev(const double ev) {elev = ev;} - inline double get_x() const { return x; } - inline void set_x(const double ecs) {x = ecs;} - inline double get_y() const { return y; } - inline void set_y(const double why) {y = why;} - inline double get_z() const { return z; } - inline void set_z(const double zed) {z = zed;} + inline int get_freq() const { return freq; } inline void set_freq(const int fq) {freq = fq;} inline int get_range() const { return range; } @@ -182,7 +176,8 @@ protected: // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. - void Render(std::string& msg, const std::string& refname = "", bool repeating = false); + void Render(std::string& msg, const double volume = 1.0, + const std::string& refname = "", bool repeating = false); // Cease rendering all transmission from this station. // Requires the sound manager refname if audio, else "". @@ -199,13 +194,15 @@ protected: virtual void ProcessCallback(int code); - double lon, lat, elev; - double x, y, z; + SGGeod _geod; + SGVec3d _cart; int freq; + std::map active_on; + int range; std::string ident; // Code of the airport its at. std::string name; // Name transmitted in the broadcast. - atc_type _type; + // Rendering related stuff bool _voice; // Flag - true if we are using voice @@ -213,28 +210,32 @@ protected: bool _voiceOK; // Flag - true if at least one voice has loaded OK FGATCVoice* _vPtr; - std::string pending_transmission; // derived classes set this string before calling Transmit(...) + bool freqClear; // Flag to indicate if the frequency is clear of ongoing dialog bool receiving; // Flag to indicate we are receiving a transmission - bool responseReqd; // Flag to indicate we should be responding to a request/report - bool runResponseCounter; // Flag to indicate the response counter should be run + + double responseTime; // Time to take from end of request transmission to beginning of response // The idea is that this will be slightly random. - double responseCounter; // counter to implement the above + + bool respond; // Flag to indicate now is the time to respond - ie set following the count down of the response timer. std::string responseID; // ID of the plane to respond to - bool respond; // Flag to indicate now is the time to respond - ie set following the count down of the response timer. + bool runResponseCounter; // Flag to indicate the response counter should be run + double responseCounter; // counter to implement the above // Derived classes only need monitor this flag, and use the response ID, as long as they call FGATC::Update(...) bool _runReleaseCounter; // A timer for releasing the frequency after giving the message enough time to display + bool responseReqd; // Flag to indicate we should be responding to a request/report double _releaseTime; double _releaseCounter; - + atc_type _type; bool _display; // Flag to indicate whether we should be outputting to the ATC display. - bool _displaying; // Flag to indicate whether we are outputting to the ATC display. + std::string pending_transmission; // derived classes set this string before calling Transmit(...) private: // Transmission timing stuff. - bool _pending; double _timeout; + bool _pending; + int _callback_code; // A callback code to be notified and processed by the derived classes // A value of zero indicates no callback required bool _transmit; // we are to transmit @@ -243,67 +244,6 @@ private: double _max_count; }; -inline std::istream& -operator >> ( std::istream& fin, ATCData& a ) -{ - double f; - char ch; - char tp; - - fin >> tp; - - switch(tp) { - case 'I': - a.type = ATIS; - break; - case 'T': - a.type = TOWER; - break; - case 'G': - a.type = GROUND; - break; - case 'A': - a.type = APPROACH; - break; - case '[': - a.type = INVALID; - return fin >> skipeol; - default: - SG_LOG(SG_GENERAL, SG_ALERT, "Warning - unknown type \'" << tp << "\' found whilst reading ATC frequency data!\n"); - a.type = INVALID; - return fin >> skipeol; - } - - fin >> a.lat >> a.lon >> a.elev >> f >> a.range - >> a.ident; - - a.name = ""; - fin >> ch; - if(ch != '"') a.name += ch; - while(1) { - //in >> noskipws - fin.unsetf(std::ios::skipws); - fin >> ch; - if((ch == '"') || (ch == 0x0A)) { - break; - } // we shouldn't need the 0x0A but it makes a nice safely in case someone leaves off the " - a.name += ch; - } - fin.setf(std::ios::skipws); - //cout << "Comm name = " << a.name << '\n'; - - a.freq = (int)(f*100.0 + 0.5); - - // cout << a.ident << endl; - - // generate cartesian coordinates - SGVec3d cart = SGVec3d::fromGeod(SGGeod::fromDegM(a.lon, a.lat, a.elev)); - a.x = cart.x(); - a.y = cart.y(); - a.z = cart.z(); - - return fin >> skipeol; -} - +std::istream& operator>> ( std::istream& fin, ATCData& a ); #endif // _FG_ATC_HXX diff --git a/src/ATCDCL/ATCDialog.cxx b/src/ATCDCL/ATCDialog.cxx index e356cee3f..f094b1170 100644 --- a/src/ATCDCL/ATCDialog.cxx +++ b/src/ATCDCL/ATCDialog.cxx @@ -66,7 +66,7 @@ ATCMenuEntry::ATCMenuEntry() { ATCMenuEntry::~ATCMenuEntry() { } -static void atcUppercase(string &s) { +void atcUppercase(string &s) { for(unsigned int i=0; iFindByPos(lon, lat, elev, 50.0, &atc_stations); + int num_stat = current_commlist->FindByPos(geod, 50.0, &atc_stations); if (num_stat != 0) { map uniq; // fill map (sorts by distance and removes duplicates) comm_list_iterator itr = atc_stations.begin(); for (; itr != atc_stations.end(); ++itr) { - SGVec3d station(itr->x, itr->y, itr->z); - double distance = distSqr(aircraft, station); + double distance = distSqr(aircraft, itr->cart); uniq[atcdata(itr->ident, itr->name, distance)] = true; } // create button per map entry (modified copy of ) @@ -382,7 +380,7 @@ void FGATCDialog::FreqDisplay(string& ident) { int n = 0; // Number of ATC frequencies at this airport comm_list_type stations; - int found = current_commlist->FindByPos(a->getLongitude(), a->getLatitude(), a->getElevation(), 20.0, &stations); + int found = current_commlist->FindByPos(a->geod(), 20.0, &stations); if(found) { ostringstream ostr; comm_list_iterator itr = stations.begin(); diff --git a/src/ATCDCL/ATCDialog.hxx b/src/ATCDCL/ATCDialog.hxx index ea393c907..b60fa599a 100644 --- a/src/ATCDCL/ATCDialog.hxx +++ b/src/ATCDCL/ATCDialog.hxx @@ -55,8 +55,9 @@ typedef atcmentry_vec_type::iterator atcmentry_vec_iterator; typedef map < string, atcmentry_vec_type > atcmentry_map_type; typedef atcmentry_map_type::iterator atcmentry_map_iterator; -//void ATCDialogInit(); +void atcUppercase(string &s); +//void ATCDialogInit(); //void ATCDoDialog(atc_type type); class FGATCDialog { diff --git a/src/ATCDCL/ATCVoice.cxx b/src/ATCDCL/ATCVoice.cxx index e4b05b75c..05f24daac 100644 --- a/src/ATCDCL/ATCVoice.cxx +++ b/src/ATCDCL/ATCVoice.cxx @@ -23,15 +23,20 @@ # include #endif +#include "ATCVoice.hxx" + +#include +#include +#include +#include + #include #include #include #include -#include
- -#include "ATCVoice.hxx" +#include -#include +#include
FGATCVoice::FGATCVoice() { SoundData = 0; @@ -47,20 +52,24 @@ FGATCVoice::~FGATCVoice() { // Load the two voice files - one containing the raw sound data (.wav) and one containing the word positions (.vce). // Return true if successful. bool FGATCVoice::LoadVoice(const string& voice) { - // FIXME CLO: disabled to try to see if this is causign problemcs + // FIXME CLO: disabled to try to see if this is causing problemcs // return false; - ifstream fin; + std::ifstream fin; SGPath path = globals->get_fg_root(); path.append( "ATC" ); string file = voice + ".wav"; - SoundData = new SGSoundSample(); - rawSoundData = (char *)SoundData->load_file(path.c_str(), file.c_str()); - rawDataSize = SoundData->get_size(); - + SGSoundSample SoundData; + rawSoundData = (char *)SoundData.load_file(path.c_str(), file.c_str()); + rawDataSize = SoundData.get_size(); +#ifdef VOICE_TEST + ALenum fmt = SoundData.get_format(); + cout << "ATCVoice: format: " << fmt + << " size: " << rawDataSize << endl; +#endif path = globals->get_fg_root(); string wordPath = "ATC/" + voice + ".vce"; path.append(wordPath); @@ -93,6 +102,13 @@ bool FGATCVoice::LoadVoice(const string& voice) { wd.offset = wrdOffset; wd.length = wrdLength; wordMap[wrdstr] = wd; + string ws2 = wrdstr; + for(string::iterator p = ws2.begin(); p != ws2.end(); p++){ + *p = tolower(*p); + if (*p == '-') *p = '_'; + } + if (wrdstr != ws2) wordMap[ws2] = wd; + //cout << wrd << "\t\t" << wrdOffset << "\t\t" << wrdLength << '\n'; //cout << i << '\n'; } @@ -105,8 +121,9 @@ bool FGATCVoice::LoadVoice(const string& voice) { typedef list < string > tokenList_type; typedef tokenList_type::iterator tokenList_iterator; -// Given a desired message, return a pointer to the data buffer and write the buffer length into len. -unsigned char* FGATCVoice::WriteMessage(char* message, int& len, bool& dataOK) { +// Given a desired message, return a string containing the +// sound-sample data +string FGATCVoice::WriteMessage(const char* message, bool& dataOK) { // What should we do here? // First - parse the message into a list of tokens. @@ -117,28 +134,36 @@ unsigned char* FGATCVoice::WriteMessage(char* message, int& len, bool& dataOK) { // TODO - at the moment we're effectively taking 3 passes through the data. // There is no need for this - 2 should be sufficient - we can probably ditch the tokenList. + size_t n1 = 1+strlen(message); + char msg[n1]; + strncpy(msg, message, n1); // strtok requires a non-const char* char* token; - char mes[1000]; int numWords = 0; - strcpy(mes, message); - const char delimiters[] = " \t.,;:\""; - token = strtok(mes, delimiters); + const char delimiters[] = " \t.,;:\"\n"; + char* context; + token = strtok_r(msg, delimiters, &context); while(token != NULL) { + for (char *t = token; *t; t++) { + *t = tolower(*t); // canonicalize the case, to + if (*t == '-') *t = '_'; // match what's in the index + } tokenList.push_back(token); ++numWords; - //cout << "token = " << token << '\n'; - token = strtok(NULL, delimiters); + SG_LOG(SG_ATC, SG_DEBUG, "voice synth: token: '" + << token << "'"); + token = strtok_r(NULL, delimiters, &context); } - WordData* wdptr = new WordData[numWords]; + WordData wdptr[numWords]; int word = 0; unsigned int cumLength = 0; tokenListItr = tokenList.begin(); while(tokenListItr != tokenList.end()) { if(wordMap.find(*tokenListItr) == wordMap.end()) { - // Oh dear - the token isn't in the sound file - //cout << "word " << *tokenListItr << " not found :-(\n"; + // Oh dear - the token isn't in the sound file + SG_LOG(SG_ATC, SG_ALERT, "voice synth: word '" + << *tokenListItr << "' not found"); } else { wdptr[word] = wordMap[*tokenListItr]; cumLength += wdptr[word].length; @@ -151,13 +176,10 @@ unsigned char* FGATCVoice::WriteMessage(char* message, int& len, bool& dataOK) { // Check for no tokens found else slScheduler can be crashed if(!word) { dataOK = false; - delete[] wdptr; - return(NULL); + return ""; } - unsigned char* tmpbuf = new unsigned char[cumLength]; - unsigned char* outbuf = new unsigned char[cumLength]; - len = cumLength; + char tmpbuf[cumLength]; unsigned int bufpos = 0; for(int i=0; i cumLength) offsetIn = cumLength; - memcpy(outbuf, tmpbuf + offsetIn, (cumLength - offsetIn)); - memcpy(outbuf + (cumLength - offsetIn), tmpbuf, offsetIn); - - delete[] tmpbuf; - delete[] wdptr; + + string front(tmpbuf, offsetIn); + string back(tmpbuf+offsetIn, cumLength - offsetIn); dataOK = true; - return(outbuf); + return back + front; } diff --git a/src/ATCDCL/ATCVoice.hxx b/src/ATCDCL/ATCVoice.hxx index bdd2b0a49..5da976848 100644 --- a/src/ATCDCL/ATCVoice.hxx +++ b/src/ATCDCL/ATCVoice.hxx @@ -23,17 +23,10 @@ #include -# include #include -#include #include -#include - -using std::map; -using std::list; -using std::string; - +class SGSoundSample; struct WordData { unsigned int offset; // Offset of beginning of word sample into raw sound sample @@ -57,8 +50,7 @@ public: // Given a desired message, return a pointer to the data buffer and write the buffer length into len. // Sets dataOK = true if the returned buffer is valid. - unsigned char* WriteMessage(char* message, int& len, bool& dataOK); - + std::string WriteMessage(const char* message, bool& dataOK); private: diff --git a/src/ATCDCL/ATCmgr.cxx b/src/ATCDCL/ATCmgr.cxx index bbf99dfa6..0d78c458b 100644 --- a/src/ATCDCL/ATCmgr.cxx +++ b/src/ATCDCL/ATCmgr.cxx @@ -24,6 +24,7 @@ #include #include + #include #include "ATCmgr.hxx" @@ -37,15 +38,12 @@ /* // periodic radio station search wrapper static void fgATCSearch( void ) { - globals->get_ATC_mgr()->Search(); + globals->get_ATC_mgr()->Search(); } */ //This wouldn't compile - including Time/event.hxx breaks it :-( // Is this still true?? -EMH- AirportATC::AirportATC() : - lon(0.0), - lat(0.0), - elev(0.0), atis_freq(0.0), atis_active(false), tower_freq(0.0), @@ -53,34 +51,20 @@ AirportATC::AirportATC() : ground_freq(0.0), ground_active(false), set_by_AI(false), - numAI(0) - //airport_atc_map.clear(); + numAI(0) + //airport_atc_map.clear(); { - for(int i=0; iget_event_mgr()->add( "fgATCSearch()", fgATCSearch, + //cout << "ATCMgr::init called..." << endl; + + lon_node = fgGetNode("/position/longitude-deg", true); + lat_node = fgGetNode("/position/latitude-deg", true); + elev_node = fgGetNode("/position/altitude-ft", true); + atc_list_itr = atc_list->begin(); + + // Search for connected ATC stations once per 0.8 seconds or so + // globals->get_event_mgr()->add( "fgATCSearch()", fgATCSearch, // FGEvent::FG_EVENT_READY, 800); // - // For some reason the above doesn't compile - including Time/event.hxx stops compilation. + // For some reason the above doesn't compile - including Time/event.hxx stops compilation. // Is this still true after the reorganization of the event managar?? // -EMH- - - // Initialise the frequency search map - current_commlist = new FGCommList; - SGPath p_comm( globals->get_fg_root() ); - current_commlist->init( p_comm ); - -#ifdef ENABLE_AUDIO_SUPPORT - // Load all available voices. - // For now we'll do one hardwired one - - v1 = new FGATCVoice; - try { - voiceOK = v1->LoadVoice("default"); - voice = true; - } catch ( sg_io_exception & ) { - voiceOK = false; - voice = false; - delete v1; - v1 = 0; - } - - /* I've loaded the voice even if /sim/sound/pause is true - * since I know no way of forcing load of the voice if the user - * subsequently switches /sim/sound/audible to true. + +#ifdef ENABLE_AUDIO_SUPPORT + // Load all available voices. + // For now we'll do one hardwired one + + v1 = new FGATCVoice; + try { + voiceOK = v1->LoadVoice("default"); + voice = true; + } catch ( sg_io_exception & ) { + voiceOK = false; + voice = false; + delete v1; + v1 = 0; + } + + /* I've loaded the voice even if /sim/sound/pause is true + * since I know no way of forcing load of the voice if the user + * subsequently switches /sim/sound/audible to true. * (which is the right thing to do -- CLO) :-) */ #else - voice = false; + voice = false; #endif - // Initialise the ATC Dialogs - //cout << "Initing Transmissions..." << endl; + // Initialise the ATC Dialogs + //cout << "Initing Transmissions..." << endl; SG_LOG(SG_ATC, SG_INFO, " ATC Transmissions"); current_transmissionlist = new FGTransmissionList; SGPath p_transmission( globals->get_fg_root() ); p_transmission.append( "ATC/default.transmissions" ); current_transmissionlist->init( p_transmission ); - //cout << "Done Transmissions" << endl; + //cout << "Done Transmissions" << endl; SG_LOG(SG_ATC, SG_INFO, " ATC Dialog System"); current_atcdialog = new FGATCDialog; current_atcdialog->Init(); - initDone = true; - //cout << "ATCmgr::init done!" << endl; + initDone = true; + //cout << "ATCmgr::init done!" << endl; } void FGATCMgr::update(double dt) { - if(!initDone) { - init(); - SG_LOG(SG_ATC, SG_WARN, "Warning - ATCMgr::update(...) called before ATCMgr::init()"); - } - - current_atcdialog->Update(dt); - - //cout << "Entering update..." << endl; - //Traverse the list of active stations. - //Only update one class per update step to avoid the whole ATC system having to calculate between frames. - //Eventually we should only update every so many steps. - //cout << "In FGATCMgr::update - atc_list.size = " << atc_list.size() << endl; - if(atc_list.size()) { - if(atc_list_itr == atc_list.end()) { - atc_list_itr = atc_list.begin(); - } - //cout << "Updating " << (*atc_list_itr)->get_ident() << ' ' << (*atc_list_itr)->GetType() << '\n'; - //cout << "Freq = " << (*atc_list_itr)->get_freq() << '\n'; - (*atc_list_itr)->Update(dt * atc_list.size()); - //cout << "Done ATC update..." << endl; - ++atc_list_itr; - } - - /* - cout << "ATC_LIST: " << atc_list.size() << ' '; - for(atc_list_iterator it = atc_list.begin(); it != atc_list.end(); it++) { - cout << (*it)->get_ident() << ' '; - } - cout << '\n'; - */ - - // Search the tuned frequencies every now and then - this should be done with the event scheduler - static int i = 0; // Very ugly - but there should only ever be one instance of FGATCMgr. - /* - if(i == 7) { - //cout << "About to AreaSearch()" << endl; - AreaSearch(); - } - */ - if(i == 15) { - //cout << "About to search(1)" << endl; - FreqSearch(1); - } - if(i == 30) { - //cout << "About to search(2)" << endl; - FreqSearch(2); - i = 0; - } - ++i; - - //cout << "comm1 type = " << comm_type[0] << '\n'; - //cout << "Leaving update..." << endl; + if(!initDone) { + init(); + SG_LOG(SG_ATC, SG_WARN, "Warning - ATCMgr::update(...) called before ATCMgr::init()"); + } + + current_atcdialog->Update(dt); + + //cout << "Entering update..." << endl; + //Traverse the list of active stations. + //Only update one class per update step to avoid the whole ATC system having to calculate between frames. + //Eventually we should only update every so many steps. + //cout << "In FGATCMgr::update - atc_list.size = " << atc_list->size() << endl; + if(atc_list->size()) { + if(atc_list_itr == atc_list->end()) { + atc_list_itr = atc_list->begin(); + } + //cout << "Updating " << (*atc_list_itr)->get_ident() << ' ' << (*atc_list_itr)->GetType() << '\n'; + //cout << "Freq = " << (*atc_list_itr)->get_freq() << '\n'; + (*atc_list_itr).second->Update(dt * atc_list->size()); + //cout << "Done ATC update..." << endl; + ++atc_list_itr; + } + +#ifdef ATC_TEST + cout << "ATC_LIST: " << atc_list->size() << ' '; + for(atc_list_iterator it = atc_list->begin(); it != atc_list->end(); it++) { + cout << (*it)->get_ident() << ' '; + } + cout << '\n'; +#endif + + // Search the tuned frequencies every now and then - this should be done with the event scheduler + static int i = 0; // Very ugly - but there should only ever be one instance of FGATCMgr. + /*** Area search is defeated. Why? + if(i == 7) { + //cout << "About to AreaSearch()" << endl; + AreaSearch(); + } + ***/ + if(i == 15) { + //cout << "About to search navcomm1" << endl; + FreqSearch("comm", 0); + FreqSearch("nav", 0); + } + if(i == 30) { + //cout << "About to search navcomm2" << endl; + FreqSearch("comm", 1); + FreqSearch("nav", 1); + i = 0; + } + ++i; + + //cout << "comm1 type = " << comm_type[0] << '\n'; + //cout << "Leaving update..." << endl; } // Returns frequency in KHz - should I alter this to return in MHz? unsigned short int FGATCMgr::GetFrequency(const string& ident, const atc_type& tp) { - ATCData test; - bool ok = current_commlist->FindByCode(ident, test, tp); - return(ok ? test.freq : 0); -} + ATCData test; + bool ok = current_commlist->FindByCode(ident, test, tp); + return(ok ? test.freq : 0); +} // Register the fact that the AI system wants to activate an airport // Might need more sophistication in this in the future - eg registration by aircraft call-sign. bool FGATCMgr::AIRegisterAirport(const string& ident) { - SG_LOG(SG_ATC, SG_BULK, "AI registered airport " << ident << " with the ATC system"); - //cout << "AI registered airport " << ident << " with the ATC system" << '\n'; - if(airport_atc_map.find(ident) != airport_atc_map.end()) { - airport_atc_map[ident]->set_by_AI = true; - airport_atc_map[ident]->numAI++; - return(true); - } else { - const FGAirport *ap = fgFindAirportID(ident); - if (ap) { - //cout << "ident = " << ident << '\n'; - AirportATC *a = new AirportATC; - // I'm not entirely sure that this AirportATC structure business is actually needed - it just duplicates what we can find out anyway! - a->lon = ap->getLongitude(); - a->lat = ap->getLatitude(); - a->elev = ap->getElevation(); - a->atis_freq = GetFrequency(ident, ATIS); - //cout << "ATIS freq = " << a->atis_freq << '\n'; - a->atis_active = false; - a->tower_freq = GetFrequency(ident, TOWER); - //cout << "Tower freq = " << a->tower_freq << '\n'; - a->tower_active = false; - a->ground_freq = GetFrequency(ident, GROUND); - //cout << "Ground freq = " << a->ground_freq << '\n'; - a->ground_active = false; - // TODO - some airports will have a tower/ground frequency but be inactive overnight. - a->set_by_AI = true; - a->numAI = 1; - airport_atc_map[ident] = a; - return(true); - } else { - SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't find airport " << ident << " in AIRegisterAirport(...)"); - } - } - return(false); + SG_LOG(SG_ATC, SG_BULK, "AI registered airport " << ident << " with the ATC system"); + //cout << "AI registered airport " << ident << " with the ATC system" << '\n'; + if(airport_atc_map.find(ident) != airport_atc_map.end()) { + airport_atc_map[ident]->set_by_AI = true; + airport_atc_map[ident]->numAI++; + return(true); + } else { + const FGAirport *ap = fgFindAirportID(ident); + if (ap) { + //cout << "ident = " << ident << '\n'; + AirportATC *a = new AirportATC; + // I'm not entirely sure that this AirportATC structure business is actually needed - it just duplicates what we can find out anyway! + a->geod = ap->geod(); + a->atis_freq = GetFrequency(ident, ATIS) + || GetFrequency(ident, AWOS); + //cout << "ATIS freq = " << a->atis_freq << '\n'; + a->atis_active = false; + a->tower_freq = GetFrequency(ident, TOWER); + //cout << "Tower freq = " << a->tower_freq << '\n'; + a->tower_active = false; + a->ground_freq = GetFrequency(ident, GROUND); + //cout << "Ground freq = " << a->ground_freq << '\n'; + a->ground_active = false; + // TODO - some airports will have a tower/ground frequency but be inactive overnight. + a->set_by_AI = true; + a->numAI = 1; + airport_atc_map[ident] = a; + return(true); + } else { + SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't find airport " << ident << " in AIRegisterAirport(...)"); + } + } + return(false); } - // Register the fact that the comm radio is tuned to an airport // Channel is zero based bool FGATCMgr::CommRegisterAirport(const string& ident, int chan, const atc_type& tp) { - SG_LOG(SG_ATC, SG_BULK, "Comm channel " << chan << " registered airport " << ident); - //cout << "Comm channel " << chan << " registered airport " << ident << ' ' << tp << '\n'; - if(airport_atc_map.find(ident) != airport_atc_map.end()) { - //cout << "IN MAP - flagging set by comm..." << endl; - airport_atc_map[ident]->set_by_comm[chan][tp] = true; - if(tp == ATIS) { - airport_atc_map[ident]->atis_active = true; - } else if(tp == TOWER) { - airport_atc_map[ident]->tower_active = true; - } else if(tp == GROUND) { - airport_atc_map[ident]->ground_active = true; - } else if(tp == APPROACH) { - //a->approach_active = true; - } // TODO - there *must* be a better way to do this!!! - return(true); - } else { - //cout << "NOT IN MAP - creating new..." << endl; - const FGAirport *ap = fgFindAirportID(ident); - if (ap) { - AirportATC *a = new AirportATC; - // I'm not entirely sure that this AirportATC structure business is actually needed - it just duplicates what we can find out anyway! - a->lon = ap->getLongitude(); - a->lat = ap->getLatitude(); - a->elev = ap->getElevation(); - a->atis_freq = GetFrequency(ident, ATIS); - a->atis_active = false; - a->tower_freq = GetFrequency(ident, TOWER); - a->tower_active = false; - a->ground_freq = GetFrequency(ident, GROUND); - a->ground_active = false; - if(tp == ATIS) { - a->atis_active = true; - } else if(tp == TOWER) { - a->tower_active = true; - } else if(tp == GROUND) { - a->ground_active = true; - } else if(tp == APPROACH) { - //a->approach_active = true; - } // TODO - there *must* be a better way to do this!!! - // TODO - some airports will have a tower/ground frequency but be inactive overnight. - a->set_by_AI = false; - a->numAI = 0; - a->set_by_comm[chan][tp] = true; - airport_atc_map[ident] = a; - return(true); - } - } - return(false); + SG_LOG(SG_ATC, SG_BULK, "Comm channel " << chan << " registered airport " << ident); + //cout << "Comm channel " << chan << " registered airport " << ident << ' ' << tp << '\n'; + if(airport_atc_map.find(ident) != airport_atc_map.end()) { + //cout << "IN MAP - flagging set by comm..." << endl; +//xx airport_atc_map[ident]->set_by_comm[chan][tp] = true; + if(tp == ATIS || tp == AWOS) { + airport_atc_map[ident]->atis_active = true; + } else if(tp == TOWER) { + airport_atc_map[ident]->tower_active = true; + } else if(tp == GROUND) { + airport_atc_map[ident]->ground_active = true; + } else if(tp == APPROACH) { + //a->approach_active = true; + } // TODO - there *must* be a better way to do this!!! + return(true); + } else { + //cout << "NOT IN MAP - creating new..." << endl; + const FGAirport *ap = fgFindAirportID(ident); + if (ap) { + AirportATC *a = new AirportATC; + // I'm not entirely sure that this AirportATC structure business is actually needed - it just duplicates what we can find out anyway! + a->geod = ap->geod(); + a->atis_freq = GetFrequency(ident, ATIS) + || GetFrequency(ident, AWOS); + a->atis_active = false; + a->tower_freq = GetFrequency(ident, TOWER); + a->tower_active = false; + a->ground_freq = GetFrequency(ident, GROUND); + a->ground_active = false; + if(tp == ATIS || tp == AWOS) { + a->atis_active = true; + } else if(tp == TOWER) { + a->tower_active = true; + } else if(tp == GROUND) { + a->ground_active = true; + } else if(tp == APPROACH) { + //a->approach_active = true; + } // TODO - there *must* be a better way to do this!!! + // TODO - some airports will have a tower/ground frequency but be inactive overnight. + a->set_by_AI = false; + a->numAI = 0; +//xx a->set_by_comm[chan][tp] = true; + airport_atc_map[ident] = a; + return(true); + } + } + return(false); } - -// Remove from list only if not needed by the AI system or the other comm channel -// Note that chan is zero based. -void FGATCMgr::CommRemoveFromList(const string& id, const atc_type& tp, int chan) { - SG_LOG(SG_ATC, SG_BULK, "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan); - //cout << "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan << '\n'; - if(airport_atc_map.find(id) != airport_atc_map.end()) { - AirportATC* a = airport_atc_map[id]; - //cout << "In CommRemoveFromList, a->ground_freq = " << a->ground_freq << endl; - if(a->set_by_AI && tp != ATIS) { - // Set by AI, so don't remove simply because user isn't tuned in any more - just stop displaying - SG_LOG(SG_ATC, SG_BULK, "In CommRemoveFromList, service was set by AI\n"); - FGATC* aptr = GetATCPointer(id, tp); - switch(chan) { - case 0: - //cout << "chan 1\n"; - a->set_by_comm[0][tp] = false; - if(!a->set_by_comm[1][tp]) { - //cout << "not set by comm2\n"; - if(aptr != NULL) { - //cout << "Got pointer\n"; - aptr->SetNoDisplay(); - //cout << "Setting no display...\n"; - } else { - //cout << "Not got pointer\n"; - } - } - break; - case 1: - a->set_by_comm[1][tp] = false; - if(!a->set_by_comm[0][tp]) { - if(aptr != NULL) { - aptr->SetNoDisplay(); - //cout << "Setting no display...\n"; - } - } - break; - } - //airport_atc_map[id] = a; - return; - } else { - switch(chan) { - case 0: - a->set_by_comm[0][tp] = false; - // Remove only if not also set by the other comm channel - if(!a->set_by_comm[1][tp]) { - a->tower_active = false; - a->ground_active = false; - RemoveFromList(id, tp); - } - break; - case 1: - a->set_by_comm[1][tp] = false; - if(!a->set_by_comm[0][tp]) { - a->tower_active = false; - a->ground_active = false; - RemoveFromList(id, tp); - } - break; - } - } - } -} - - -// Remove from list - should only be called from above or similar -// This function *will* remove it from the list regardless of who else might want it. -void FGATCMgr::RemoveFromList(const string& id, const atc_type& tp) { - //cout << "FGATCMgr::RemoveFromList called..." << endl; - //cout << "Requested type = " << tp << endl; - //cout << "id = " << id << endl; - atc_list_iterator it = atc_list.begin(); - while(it != atc_list.end()) { - //cout << "type = " << (*it)->GetType() << '\n'; - //cout << "Ident = " << (*it)->get_ident() << '\n'; - if( ((*it)->get_ident() == id) - && ((*it)->GetType() == tp) ) { - //Before removing it stop it transmitting!! - //cout << "OBLITERATING FROM LIST!!!\n"; - (*it)->SetNoDisplay(); - (*it)->Update(0.00833); - delete (*it); - atc_list.erase(it); - atc_list_itr = atc_list.begin(); // Reset the persistent itr incase we've left it off the end. - break; - } - ++it; - } +typedef map MSI; + +void FGATCMgr::ZapOtherService(const string ncunit, const string svc_name){ + for (atc_list_iterator svc = atc_list->begin(); svc != atc_list->end(); svc++) { + //cout << "Zapping " << navcomm + // << "[" << unit << "]" << " otherthan: " << svc_name << endl; + if (svc->first != svc_name) { + MSI &actv = svc->second->active_on; + // OK, we have found some OTHER service; + // see if it is (was) active on our unit: + if (!actv.count(ncunit)) continue; + // cout << "Eradicating '" << svc->first << "' from: " << ncunit << endl; + actv.erase(ncunit); + if (!actv.size()) { + //cout << "Eradicating service: '" << svc->first << "'" << endl; + svc->second->SetNoDisplay(); + svc->second->Update(0); // one last update + delete svc->second; + atc_list->erase(svc); +// ALL pointers into the ATC list are now invalid, +// so let's reset them: + atc_list_itr = atc_list->begin(); + } + break; // cannot be duplicates in the active list + } + } } @@ -404,28 +318,19 @@ void FGATCMgr::RemoveFromList(const string& id, const atc_type& tp) { // Return NULL if the given service is not in the list // - *** THE CALLING FUNCTION MUST CHECK FOR THIS *** FGATC* FGATCMgr::FindInList(const string& id, const atc_type& tp) { - //cout << "Entering FindInList for " << id << ' ' << tp << endl; - atc_list_iterator it = atc_list.begin(); - while(it != atc_list.end()) { - if( ((*it)->get_ident() == id) - && ((*it)->GetType() == tp) ) { - return(*it); - } - ++it; - } - // If we get here it's not in the list - //cout << "Couldn't find it in the list though :-(" << endl; - return(NULL); + string ndx = id + decimalNumeral(tp); + if (!atc_list->count(ndx)) return 0; + return (*atc_list)[ndx]; } // Returns true if the airport is found in the map bool FGATCMgr::GetAirportATCDetails(const string& icao, AirportATC* a) { - if(airport_atc_map.find(icao) != airport_atc_map.end()) { - *a = *airport_atc_map[icao]; - return(true); - } else { - return(false); - } + if(airport_atc_map.find(icao) != airport_atc_map.end()) { + *a = *airport_atc_map[icao]; + return(true); + } else { + return(false); + } } @@ -434,73 +339,73 @@ bool FGATCMgr::GetAirportATCDetails(const string& icao, AirportATC* a) { // We really ought to make this private and call it from the CommRegisterAirport / AIRegisterAirport functions // - at the moment all these GetATC... functions exposed are just too complicated. FGATC* FGATCMgr::GetATCPointer(const string& icao, const atc_type& type) { - if(airport_atc_map.find(icao) == airport_atc_map.end()) { - //cout << "Unable to find " << icao << ' ' << type << " in the airport_atc_map" << endl; - return NULL; - } - //cout << "In GetATCPointer, found " << icao << ' ' << type << endl; - AirportATC *a = airport_atc_map[icao]; - //cout << "a->lon = " << a->lon << '\n'; - //cout << "a->elev = " << a->elev << '\n'; - //cout << "a->tower_freq = " << a->tower_freq << '\n'; - switch(type) { - case TOWER: - if(a->tower_active) { - // Get the pointer from the list - return(FindInList(icao, type)); - } else { - ATCData data; - if(current_commlist->FindByFreq(a->lon, a->lat, a->elev, a->tower_freq, &data, TOWER)) { - FGTower* t = new FGTower; - t->SetData(&data); - atc_list.push_back(t); - a->tower_active = true; - airport_atc_map[icao] = a; - //cout << "Initing tower " << icao << " in GetATCPointer()\n"; - t->Init(); - return(t); - } else { - SG_LOG(SG_ATC, SG_ALERT, "ERROR - tower that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found"); - } - } - break; - case APPROACH: - break; - case ATIS: - SG_LOG(SG_ATC, SG_ALERT, "ERROR - ATIS station should not be requested from FGATCMgr::GetATCPointer"); - break; - case GROUND: - //cout << "IN CASE GROUND" << endl; - if(a->ground_active) { - // Get the pointer from the list - return(FindInList(icao, type)); - } else { - ATCData data; - if(current_commlist->FindByFreq(a->lon, a->lat, a->elev, a->ground_freq, &data, GROUND)) { - FGGround* g = new FGGround; - g->SetData(&data); - atc_list.push_back(g); - a->ground_active = true; - airport_atc_map[icao] = a; - g->Init(); - return(g); - } else { - SG_LOG(SG_ATC, SG_ALERT, "ERROR - ground control that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found"); - } - } - break; - case INVALID: - break; - case ENROUTE: - break; - case DEPARTURE: - break; - } - - SG_LOG(SG_ATC, SG_ALERT, "ERROR IN FGATCMgr - reached end of GetATCPointer"); - //cout << "ERROR IN FGATCMgr - reached end of GetATCPointer" << endl; - - return(NULL); + if(airport_atc_map.find(icao) == airport_atc_map.end()) { + //cout << "Unable to find " << icao << ' ' << type << " in the airport_atc_map" << endl; + return NULL; + } + //cout << "In GetATCPointer, found " << icao << ' ' << type << endl; + AirportATC *a = airport_atc_map[icao]; + //cout << "a->lon = " << a->lon << '\n'; + //cout << "a->elev = " << a->elev << '\n'; + //cout << "a->tower_freq = " << a->tower_freq << '\n'; + switch(type) { + case TOWER: + if(a->tower_active) { + // Get the pointer from the list + return(FindInList(icao, type)); + } else { + ATCData data; + if(current_commlist->FindByFreq(a->geod, a->tower_freq, &data, TOWER)) { + FGTower* t = new FGTower; + t->SetData(&data); + (*atc_list)[icao+decimalNumeral(type)] = t; + a->tower_active = true; + airport_atc_map[icao] = a; + //cout << "Initing tower " << icao << " in GetATCPointer()\n"; + t->Init(); + return(t); + } else { + SG_LOG(SG_ATC, SG_ALERT, "ERROR - tower that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found"); + } + } + break; + case APPROACH: + break; + case ATIS: case AWOS: + SG_LOG(SG_ATC, SG_ALERT, "ERROR - ATIS station should not be requested from FGATCMgr::GetATCPointer"); + break; + case GROUND: + //cout << "IN CASE GROUND" << endl; + if(a->ground_active) { + // Get the pointer from the list + return(FindInList(icao, type)); + } else { + ATCData data; + if(current_commlist->FindByFreq(a->geod, a->ground_freq, &data, GROUND)) { + FGGround* g = new FGGround; + g->SetData(&data); + (*atc_list)[icao+decimalNumeral(type)] = g; + a->ground_active = true; + airport_atc_map[icao] = a; + g->Init(); + return(g); + } else { + SG_LOG(SG_ATC, SG_ALERT, "ERROR - ground control that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found"); + } + } + break; + case INVALID: + break; + case ENROUTE: + break; + case DEPARTURE: + break; + } + + SG_LOG(SG_ATC, SG_ALERT, "ERROR IN FGATCMgr - reached end of GetATCPointer"); + //cout << "ERROR IN FGATCMgr - reached end of GetATCPointer" << endl; + + return(NULL); } // Return a pointer to an appropriate voice for a given type of ATC @@ -511,213 +416,148 @@ FGATC* FGATCMgr::GetATCPointer(const string& icao, const atc_type& type) { // specific voices, and possible make sure that the same voice doesn't get used // at different airports in quick succession if a large enough selection are available. FGATCVoice* FGATCMgr::GetVoicePointer(const atc_type& type) { - // TODO - implement me better - maintain a list of loaded voices and other voices!! - if(voice) { - switch(type) { - case ATIS: - if(voiceOK) { - return(v1); - } - case TOWER: - return(NULL); - case APPROACH: - return(NULL); - case GROUND: - return(NULL); - default: - return(NULL); - } - return(NULL); - } else { - return(NULL); - } + // TODO - implement me better - maintain a list of loaded voices and other voices!! + if(voice) { + switch(type) { + case ATIS: case AWOS: + if(voiceOK) { + return(v1); + } + case TOWER: + return(NULL); + case APPROACH: + return(NULL); + case GROUND: + return(NULL); + default: + return(NULL); + } + return(NULL); + } else { + return(NULL); + } } // Search for ATC stations by frequency -void FGATCMgr::FreqSearch(int channel) { - int chan = channel - 1; // Convert to zero-based for the arrays - - ATCData data; - double freq = comm_node[chan]->getDoubleValue(); - lon = lon_node->getDoubleValue(); - lat = lat_node->getDoubleValue(); - elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; - - // Query the data store and get the closest match if any - if(current_commlist->FindByFreq(lon, lat, elev, freq, &data)) { - // We have a match - // What's the logic? - // If this channel not previously valid then easy - add ATC to list - // If this channel was valid then - Have we tuned to a different service? - // If so - de-register one and add the other - if(comm_valid[chan]) { - if((comm_ident[chan] == data.ident) && (comm_type[chan] == data.type)) { - // Then we're still tuned into the same service so do nought and return - return; - } else { - // Something's changed - either the location or the service type - // We need to feed the channel in so we're not removing it if we're also tuned in on the other channel - CommRemoveFromList(comm_ident[chan], comm_type[chan], chan); - } - } - // At this point we can assume that we need to add the service. - comm_ident[chan] = data.ident; - comm_type[chan] = data.type; - comm_x[chan] = (double)data.x; - comm_y[chan] = (double)data.y; - comm_z[chan] = (double)data.z; - comm_lon[chan] = (double)data.lon; - comm_lat[chan] = (double)data.lat; - comm_elev[chan] = (double)data.elev; - comm_valid[chan] = true; - - // This was a switch-case statement but the compiler didn't like the new variable creation with it. - if(comm_type[chan] == ATIS) { - CommRegisterAirport(comm_ident[chan], chan, ATIS); - FGATC* app = FindInList(comm_ident[chan], ATIS); - if(app != NULL) { - // The station is already in the ATC list - //cout << "In list - flagging SetDisplay..." << endl; - comm_atc_ptr[chan] = app; - app->SetDisplay(); - } else { - // Generate the station and put in the ATC list - //cout << "Not in list - generating..." << endl; - FGATIS* a = new FGATIS; - a->SetData(&data); - comm_atc_ptr[chan] = a; - a->SetDisplay(); - //a->Init(); - atc_list.push_back(a); - } - } else if (comm_type[chan] == TOWER) { - //cout << "TOWER TOWER TOWER\n"; - CommRegisterAirport(comm_ident[chan], chan, TOWER); - //cout << "Done (TOWER)" << endl; - FGATC* app = FindInList(comm_ident[chan], TOWER); - if(app != NULL) { - // The station is already in the ATC list - SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is in list - flagging SetDisplay..."); - //cout << comm_ident[chan] << " is in list - flagging SetDisplay...\n"; - comm_atc_ptr[chan] = app; - app->SetDisplay(); - } else { - // Generate the station and put in the ATC list - SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is not in list - generating..."); - //cout << comm_ident[chan] << " is not in list - generating...\n"; - FGTower* t = new FGTower; - t->SetData(&data); - comm_atc_ptr[chan] = t; - //cout << "Initing tower in FreqSearch()\n"; - t->Init(); - t->SetDisplay(); - atc_list.push_back(t); - } - } else if (comm_type[chan] == GROUND) { - CommRegisterAirport(comm_ident[chan], chan, GROUND); - //cout << "Done (GROUND)" << endl; - FGATC* app = FindInList(comm_ident[chan], GROUND); - if(app != NULL) { - // The station is already in the ATC list - comm_atc_ptr[chan] = app; - app->SetDisplay(); - } else { - // Generate the station and put in the ATC list - FGGround* g = new FGGround; - g->SetData(&data); - comm_atc_ptr[chan] = g; - g->Init(); - g->SetDisplay(); - atc_list.push_back(g); - } - } else if (comm_type[chan] == APPROACH) { - // We have to be a bit more carefull here since approaches are also searched by area - CommRegisterAirport(comm_ident[chan], chan, APPROACH); - //cout << "Done (APPROACH)" << endl; - FGATC* app = FindInList(comm_ident[chan], APPROACH); - if(app != NULL) { - // The station is already in the ATC list - app->AddPlane("Player"); - app->SetDisplay(); - comm_atc_ptr[chan] = app; - } else { - // Generate the station and put in the ATC list - FGApproach* a = new FGApproach; - a->SetData(&data); - comm_atc_ptr[chan] = a; - a->Init(); - a->SetDisplay(); - a->AddPlane("Player"); - atc_list.push_back(a); - } - } - } else { - if(comm_valid[chan]) { - if(comm_type[chan] != APPROACH) { - // Currently approaches are removed by Alexander's out-of-range mechanism - CommRemoveFromList(comm_ident[chan], comm_type[chan], chan); - } - // Note that we *don't* call SetNoDisplay() here because the other comm channel - // might be tuned into the same station - this is handled by CommRemoveFromList(...) - comm_type[chan] = INVALID; - comm_atc_ptr[chan] = NULL; - comm_valid[chan] = false; - } - } +void FGATCMgr::FreqSearch(const string navcomm, const int unit) { + + + string ncunit = navcomm + "[" + decimalNumeral(unit) + "]"; + string commbase = "/instrumentation/" + ncunit; + string commfreq = commbase + "/frequencies/selected-mhz"; + SGPropertyNode_ptr comm_node = fgGetNode(commfreq.c_str(), false); + + //cout << "FreqSearch: " << ncunit + // << " node: " << comm_node << endl; + if (!comm_node) return; // no such radio unit + + ATCData data; + double freq = comm_node->getDoubleValue(); + _aircraftPos = SGGeod::fromDegFt(lon_node->getDoubleValue(), + lat_node->getDoubleValue(), elev_node->getDoubleValue()); + +// Query the data store and get the closest match if any + //cout << "Will FindByFreq: " << lat << " " << lon << " " << elev + // << " freq: " << freq << endl; + if(current_commlist->FindByFreq(_aircraftPos, freq, &data)) { + //cout << "FoundByFreq: " << freq + // << " ident: " << data.ident + // << " type: " << data.type << " ***" << endl; +// We are in range of something. + + +// Get rid of any *other* service that was on this radio unit: + string svc_name = data.ident+decimalNumeral(data.type); + ZapOtherService(ncunit, svc_name); +// See if the service already exists, possibly connected to +// some other radio unit: + if (atc_list->count(svc_name)) { + // make sure the service knows it's tuned on this radio: + FGATC* svc = (*atc_list)[svc_name]; + svc->active_on[ncunit] = 1; + svc->SetDisplay(); + if (data.type == APPROACH) svc->AddPlane("Player"); + return; + } + + CommRegisterAirport(data.ident, unit, data.type); + +// This was a switch-case statement but the compiler didn't like +// the new variable creation with it. + if (data.type == ATIS + || data.type == AWOS) (*atc_list)[svc_name] = new FGATIS; + else if (data.type == TOWER) (*atc_list)[svc_name] = new FGTower; + else if (data.type == GROUND) (*atc_list)[svc_name] = new FGGround; + else if (data.type == APPROACH) (*atc_list)[svc_name] = new FGApproach; + FGATC* svc = (*atc_list)[svc_name]; + svc->SetData(&data); + svc->active_on[ncunit] = 1; + svc->SetDisplay(); + svc->Init(); + if (data.type == APPROACH) svc->AddPlane("Player"); + } else { + // No services in range. Zap any service on this unit. + ZapOtherService(ncunit, "x x x"); + } } +#ifdef AREA_SEARCH +/* I don't think AreaSearch ever gets called */ // Search ATC stations by area in order that we appear 'on the radar' void FGATCMgr::AreaSearch() { - // Search for Approach stations - comm_list_type approaches; - comm_list_iterator app_itr; - - lon = lon_node->getDoubleValue(); - lat = lat_node->getDoubleValue(); - elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; - - // search stations in range - int num_app = current_commlist->FindByPos(lon, lat, elev, 100.0, &approaches, APPROACH); - if (num_app != 0) { - //cout << num_app << " approaches found in radiostack search !!!!" << endl; - - for(app_itr = approaches.begin(); app_itr != approaches.end(); app_itr++) { - - FGATC* app = FindInList(app_itr->ident, app_itr->type); - if(app != NULL) { - // The station is already in the ATC list - //cout << "In list adding player\n"; - app->AddPlane("Player"); - //app->Update(); - } else { - // Generate the station and put in the ATC list - FGApproach* a = new FGApproach; - a->SetData(&(*app_itr)); - //cout << "Adding player\n"; - a->AddPlane("Player"); - //a->Update(); - atc_list.push_back(a); - } - } - } - - // remove planes which are out of range - // TODO - I'm not entirely sure that this belongs here. - atc_list_iterator it = atc_list.begin(); - while(it != atc_list.end()) { - if((*it)->GetType() == APPROACH ) { - int np = (*it)->RemovePlane(); - // if approach has no planes left remove it from ATC list - if ( np == 0) { - //cout << "REMOVING AN APPROACH STATION WITH NO PLANES..." << endl; - (*it)->SetNoDisplay(); - (*it)->Update(0.00833); - delete (*it); - atc_list.erase(it); - atc_list_itr = atc_list.begin(); // Reset the persistent itr incase we've left it off the end. - break; // the other stations will be checked next time - } - } - ++it; - } + const string AREA("AREA"); + // Search for Approach stations + comm_list_type approaches; + comm_list_iterator app_itr; + + lon = lon_node->getDoubleValue(); + lat = lat_node->getDoubleValue(); + elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; + for (atc_list_iterator svc = atc_list->begin(); svc != atc_list->end(); svc++) { + MSI &actv = svc->second->active_on; + if (actv.count(AREA)) actv[AREA] = 0; // Mark all as maybe not in range + } + + // search stations in range + int num_app = current_commlist->FindByPos(lon, lat, elev, 100.0, &approaches, APPROACH); + if (num_app != 0) { + //cout << num_app << " approaches found in area search !!!!" << endl; + + for(app_itr = approaches.begin(); app_itr != approaches.end(); app_itr++) { + FGATC* app = FindInList(app_itr->ident, app_itr->type); + string svc_name = app_itr->ident+decimalNumeral(app_itr->type); + if(app != NULL) { + // The station is already in the ATC list + app->AddPlane("Player"); + } else { + // Generate the station and put in the ATC list + FGApproach* a = new FGApproach; + a->SetData(&(*app_itr)); + a->AddPlane("Player"); + (*atc_list)[svc_name] = a; + //cout << "New area service: " << svc_name << endl; + } + FGATC* svc = (*atc_list)[svc_name]; + svc->active_on[AREA] = 1; + } + } + + for (atc_list_iterator svc = atc_list->begin(); svc != atc_list->end(); svc++) { + MSI &actv = svc->second->active_on; + if (!actv.count(AREA)) continue; + if (!actv[AREA]) actv.erase(AREA); + if (!actv.size()) { // this service no longer active at all + cout << "Eradicating area service: " << svc->first << endl; + svc->second->SetNoDisplay(); + svc->second->Update(0); + delete (svc->second); + atc_list->erase(svc); +// Reset the persistent iterator, since any erase() makes it invalid: + atc_list_itr = atc_list->begin(); +// Hope we only move out of one approach-area; +// others will not be noticed until next update: + break; + } + } } +#endif diff --git a/src/ATCDCL/ATCmgr.hxx b/src/ATCDCL/ATCmgr.hxx index 9ac6a75d9..13349490d 100644 --- a/src/ATCDCL/ATCmgr.hxx +++ b/src/ATCDCL/ATCmgr.hxx @@ -45,9 +45,7 @@ using std::map; struct AirportATC { AirportATC(); - float lon; - float lat; - float elev; + SGGeod geod; float atis_freq; bool atis_active; float tower_freq; @@ -65,7 +63,7 @@ struct AirportATC { // Flags to ensure the stations don't get wrongly deactivated bool set_by_AI; // true when the AI manager has activated this station unsigned int numAI; // Ref count of the number of AI planes registered - bool set_by_comm[2][ATC_NUM_TYPES]; // true when the relevant comm_freq has activated this station and type +//xx bool set_by_comm[2][ATC_NUM_TYPES]; // true when the relevant comm_freq has activated this station and type }; class FGATCMgr : public SGSubsystem @@ -84,47 +82,26 @@ private: airport_atc_map_iterator airport_atc_map_itr; // A list of pointers to all currently active ATC classes - typedef list atc_list_type; + typedef map atc_list_type; typedef atc_list_type::iterator atc_list_iterator; typedef atc_list_type::const_iterator atc_list_const_iterator; // Everything put in this list should be created dynamically // on the heap and ***DELETED WHEN REMOVED!!!!!*** - atc_list_type atc_list; + atc_list_type* atc_list; atc_list_iterator atc_list_itr; // Any member function of FGATCMgr is permitted to leave this iterator pointing // at any point in or at the end of the list. // Hence any new access must explicitly first check for atc_list.end() before dereferencing. // Position of the Users Aircraft - double lon; - double lat; - double elev; - - // Type of ATC control that the user's radios are tuned to. - atc_type comm_type[2]; - - // Pointer to the ATC station that the user is currently tuned into. - FGATC* comm_atc_ptr[2]; - - double comm_freq[2]; - - // Pointers to users current communication frequencies. - SGPropertyNode_ptr comm_node[2]; + SGGeod _aircraftPos; // Pointers to current users position SGPropertyNode_ptr lon_node; SGPropertyNode_ptr lat_node; SGPropertyNode_ptr elev_node; - // Position of the ATC that the comm radios are tuned to in order to decide - // whether transmission will be received. - double comm_x[2], comm_y[2], comm_z[2], comm_lon[2], comm_lat[2], comm_elev[2]; - - double comm_range[2], comm_effective_range[2]; - bool comm_valid[2]; - string comm_ident[2]; - //string last_comm_ident[2]; //string approach_ident; bool last_in_range; @@ -170,10 +147,10 @@ public: // at different airports in quick succession if a large enough selection are available. FGATCVoice* GetVoicePointer(const atc_type& type); - atc_type GetComm1ATCType() { return(comm_type[0]); } - FGATC* GetComm1ATCPointer() { return(comm_atc_ptr[0]); } - atc_type GetComm2ATCType() { return(comm_type[1]); } - FGATC* GetComm2ATCPointer() { return(comm_atc_ptr[1]); } + atc_type GetComm1ATCType() { return(INVALID/* kludge */); } + FGATC* GetComm1ATCPointer() { return(0/* kludge */); } + atc_type GetComm2ATCType() { return(INVALID); } + FGATC* GetComm2ATCPointer() { return(0/* kludge */); } // Get the frequency of a given service at a given airport // Returns zero if not found @@ -189,11 +166,7 @@ private: // Remove a class from the atc_list and delete it from memory // *if* no other comm channel or AI plane is using it. - void CommRemoveFromList(const string& id, const atc_type& tp, int chan); - - // Remove a class from the atc_list and delete it from memory - // Should be called from the above - not directly!! - void RemoveFromList(const string& id, const atc_type& tp); + void ZapOtherService(const string ncunit, const string svc_name); // Return a pointer to a class in the list given ICAO code and type // (external interface to this is through GetATCPointer) @@ -201,11 +174,13 @@ private: // - *** THE CALLING FUNCTION MUST CHECK FOR THIS *** FGATC* FindInList(const string& id, const atc_type& tp); - // Search the specified channel for stations on the same frequency and in range. - void FreqSearch(int channel); + // Search the specified radio for stations on the same frequency and in range. + void FreqSearch(const string navcomm, const int unit); - // Search ATC stations by area in order that we appear 'on the radar' - void AreaSearch(); +#ifdef AREA_SEARCH + // Search ATC stations by area in order that we appear 'on the radar' + void AreaSearch(); +#endif }; diff --git a/src/ATCDCL/ATCutils.cxx b/src/ATCDCL/ATCutils.cxx index 94eaff246..fe484f256 100644 --- a/src/ATCDCL/ATCutils.cxx +++ b/src/ATCDCL/ATCutils.cxx @@ -35,7 +35,16 @@ #include "ATCutils.hxx" #include "ATCProjection.hxx" -static const string nums[10] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "niner"}; +static const string nums[10] = {"zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "niner"}; + +static const string letters[LTRS] = { + "alpha", "bravo", "charlie", "delta", "echo", + "foxtrot", "golf", "hotel", "india", "juliet", + "kilo", "lima", "mike", "november", "oscar", + "papa", "quebec", "romeo", "sierra", "tango", + "uniform", "victor", "whiskey", "xray", "yankee", "zulu" +}; // Convert any number to spoken digits string ConvertNumToSpokenDigits(const string &n) { @@ -59,101 +68,50 @@ string ConvertNumToSpokenDigits(const string &n) { return(str); } - -// Convert an integer to spoken digits -string ConvertNumToSpokenDigits(int n) { +// Convert an integer to a decimal numeral string +string decimalNumeral(const int& n) { std::ostringstream buf; buf << n; - return(ConvertNumToSpokenDigits(buf.str())); + return buf.str(); } - -// Convert a 2 digit rwy number to a spoken-style string -string ConvertRwyNumToSpokenString(int n) { - // Basic error/sanity checking - while(n < 0) { - n += 36; - } - while(n > 36) { - n -= 36; - } - if(n == 0) { - n = 36; // Is this right? - } - - string str = ""; - int index = n/10; - str += nums[index]; - n -= (index * 10); - //str += "-"; - str += " "; //Changed this for the benefit of the voice token parser - prefer the "-" in the visual output though. - str += nums[n]; - return(str); +// Convert an integer to spoken digits +string ConvertNumToSpokenDigits(const int& n) { + return ConvertNumToSpokenDigits(decimalNumeral(n)); } -// Assumes we get a two-digit string optionally appended with L, R or C -// eg 01 07L 29R 36 + +// Assumes we get a string of digits optionally appended with L, R or C +// eg 1 7L 29R 36 // Anything else is not guaranteed to be handled correctly! -string ConvertRwyNumToSpokenString(const string &s) { - if(s.size() < 3) { - return(ConvertRwyNumToSpokenString(atoi(s.c_str()))); - } else { - string r = ConvertRwyNumToSpokenString(atoi(s.substr(0,2).c_str())); - if(s.substr(2,1) == "L") { - r += " left"; - } else if(s.substr(2,1) == "R") { - r += " right"; - } else if(s.substr(2,1) == "C") { - r += " center"; - } else { - SG_LOG(SG_ATC, SG_WARN, "WARNING: Unknown suffix " << s.substr(2,1) << " from runway ID " << s << " in ConvertRwyNumToSpokenString(...)"); - } - return(r); - } +string ConvertRwyNumToSpokenString(const string &rwy) { + string rslt; + for (int ii = 0; ii < rwy.length(); ii++){ + if (rslt.length()) rslt += " "; + string ch = rwy.substr(ii,1); + if (isdigit(ch[0])) rslt += ConvertNumToSpokenDigits(atoi(ch.c_str())); + else if (ch == "R") rslt += "right"; + else if (ch == "C") rslt += "center"; + else if (ch == "L") rslt += "left"; + else { + rslt += GetPhoneticLetter(ch[0]); + SG_LOG(SG_ATC, SG_WARN, "WARNING: Unknown suffix '" << ch + << "' in runway " << rwy << " in ConvertRwyNumToSpokenString(...)"); + } + } + return rslt; } // Return the phonetic letter of a letter represented as an integer 1->26 -string GetPhoneticIdent(int i) { - // TODO - Check i is between 1 and 26 and wrap if necessary - return(GetPhoneticIdent(char('a' + (i-1)))); +string GetPhoneticLetter(const int i) { + return(letters[i % LTRS]); } // Return the phonetic letter of a character in the range a-z or A-Z. // Currently always returns prefixed by lowercase. -string GetPhoneticIdent(char c) { - c = tolower(c); - // TODO - Check c is between a and z and wrap if necessary - switch(c) { - case 'a' : return("alpha"); - case 'b' : return("bravo"); - case 'c' : return("charlie"); - case 'd' : return("delta"); - case 'e' : return("echo"); - case 'f' : return("foxtrot"); - case 'g' : return("golf"); - case 'h' : return("hotel"); - case 'i' : return("india"); - case 'j' : return("juliet"); - case 'k' : return("kilo"); - case 'l' : return("lima"); - case 'm' : return("mike"); - case 'n' : return("november"); - case 'o' : return("oscar"); - case 'p' : return("papa"); - case 'q' : return("quebec"); - case 'r' : return("romeo"); - case 's' : return("sierra"); - case 't' : return("tango"); - case 'u' : return("uniform"); - case 'v' : return("victor"); - case 'w' : return("whiskey"); - case 'x' : return("x-ray"); - case 'y' : return("yankee"); - case 'z' : return("zulu"); - } - // We shouldn't get here - return("Error"); +string GetPhoneticLetter(const char c) { + return GetPhoneticLetter(int(tolower(c) - 'a')); } // Get the compass direction associated with a heading in degrees diff --git a/src/ATCDCL/ATCutils.hxx b/src/ATCDCL/ATCutils.hxx index b26a55cd0..01f2e9e9d 100644 --- a/src/ATCDCL/ATCutils.hxx +++ b/src/ATCDCL/ATCutils.hxx @@ -41,24 +41,22 @@ using std::string; string ConvertNumToSpokenDigits(const string &n); // Convert an integer to spoken digits -string ConvertNumToSpokenDigits(int n); - -// Convert a 2 digit rwy number to a spoken-style string -string ConvertRwyNumToSpokenString(int n); +string ConvertNumToSpokenDigits(const int& n); +string decimalNumeral(const int& n); // Convert rwy number string to a spoken-style string -// eg "05L" to "zero five left" -// Assumes we get a two-digit string optionally appended with R, L, or C -// eg 01 07L 29R 36 -// Anything else is not guaranteed to be handled correctly! +// eg "15L" to "one five left" +// Assumes we get a string of digits optionally appended with R, L, or C +// eg 1 7L 29R 36 string ConvertRwyNumToSpokenString(const string &s); -// Return the phonetic letter of a letter represented as an integer 1->26 -string GetPhoneticIdent(int i); +const int LTRS(26); +// Return the phonetic letter of a letter represented as an integer 0..25 +string GetPhoneticLetter(const int i); // Return the phonetic letter of a character in the range a-z or A-Z. // Currently always returns prefixed by lowercase. -string GetPhoneticIdent(char c); +string GetPhoneticLetter(char c); // Get the compass direction associated with a heading in degrees // Currently returns 8 direction resolution (N, NE, E etc...) diff --git a/src/ATCDCL/approach.cxx b/src/ATCDCL/approach.cxx index ed396b0d9..e1ff8226e 100644 --- a/src/ATCDCL/approach.cxx +++ b/src/ATCDCL/approach.cxx @@ -408,18 +408,19 @@ double FGApproach::angle_diff_deg( const double &a1, const double &a2) { void FGApproach::calc_wp( const int &i ) { int j; - double course, d, cd, a1; + double course, d, cd, a1, az2; int wpn = planes[i].wpn; // waypoint 0: Threshold of active runway - course = SGGeoc::courseRad(SGGeoc::fromDegM(lon, lat, 6e6), SGGeoc::fromDegM(active_rw_lon, active_rw_lat, 6e6)); - d = SGGeoc::distanceM(SGGeoc::fromDegM(lon, lat, 6e6), SGGeoc::fromDegM(active_rw_lon, active_rw_lat, 6e6)); + SGGeod activeRunway(SGGeod::fromDeg(active_rw_lon, active_rw_lat)); + SGGeodesy::inverse(_geod, activeRunway, course, az2, d); + double d1 = active_rw_hdg+180.0; if ( d1 > 360.0 ) d1 -=360.0; - calc_cd_head_dist(360.0-course*SGD_RADIANS_TO_DEGREES, d/SG_NM_TO_METER, + calc_cd_head_dist(360.0-course, d/SG_NM_TO_METER, d1, active_rw_len/SG_NM_TO_METER/2.0, &planes[i].wpts[wpn][0], &planes[i].wpts[wpn][1]); - planes[i].wpts[wpn][2] = elev; + planes[i].wpts[wpn][2] = _geod.getElevationM(); planes[i].wpts[wpn][4] = 0.0; planes[i].wpts[wpn][5] = 0.0; wpn += 1; @@ -506,7 +507,7 @@ void FGApproach::calc_wp( const int &i ) { // ==================== // vertical navigation // ==================== - double alt = elev+3000.0; + double alt = _geod.getElevationM()+3000.0; planes[i].wpts[1][2] = round_alt( true, alt ); for ( j=2; jgetDoubleValue(); planes[i].spd = speed_node->getDoubleValue(); - double course, distance; - course = SGGeoc::courseRad(SGGeoc::fromDegM(lon, lat, 6e6), SGGeoc::fromDegM(planes[i].lon, active_rw_lat, 6e6)); - distance = SGGeoc::distanceM(SGGeoc::fromDegM(lon, lat, 6e6), SGGeoc::fromDegM(planes[i].lon, active_rw_lat, 6e6)); - planes[i].dist = distance/SG_NM_TO_METER; - planes[i].brg = 360.0-course*SGD_RADIANS_TO_DEGREES; + double course, distance, az2; + SGGeod plane(SGGeod::fromDeg(planes[1].lon, active_rw_lat)); + SGGeodesy::inverse(_geod, plane, course, az2, distance); + planes[i].dist = distance * SG_METER_TO_NM; + planes[i].brg = 360.0-course; //cout << "Plane Id: " << planes[i].ident << " Distance to " << ident // << " is " << planes[i].dist << " miles " << "Bearing " << planes[i].brg << endl; diff --git a/src/ATCDCL/atis.cxx b/src/ATCDCL/atis.cxx index d3be4ea23..e05556977 100644 --- a/src/ATCDCL/atis.cxx +++ b/src/ATCDCL/atis.cxx @@ -19,209 +19,433 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +///// +///// TODO: _Cumulative_ sky coverage. +///// TODO: wind _gust_ +///// TODO: more-sensible encoding of voice samples +///// u-law? outright synthesis? +///// #ifdef HAVE_CONFIG_H # include #endif +#include "atis.hxx" + #include -#include // atoi() -#include // sprintf +#include // atoi() +#include // sprintf #include -using std::string; - #include -using std::cout; + + +#include #include #include #include +#include #include
#include
#include -#include "atis.hxx" + #include "commlist.hxx" #include "ATCutils.hxx" #include "ATCmgr.hxx" +using std::cout; +using std::cout; +using boost::ref; +using boost::make_tuple; + FGATIS::FGATIS() : - transmission(""), - trans_ident(""), - atis_failed(false), - refname("atis") - //type(ATIS) + transmission(""), + trans_ident(""), + old_volume(0), + atis_failed(false), + attention(0), + _prev_display(0), + refname("atis") { - _vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS); - _voiceOK = (_vPtr == NULL ? false : true); - _type = ATIS; + _vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS); + _voiceOK = (_vPtr == NULL ? false : true); + if (!(_type != ATIS || _type == AWOS)) { + SG_LOG(SG_ATC, SG_ALERT, "ERROR - _type not ATIS or AWOS in atis.cxx"); + } + fgTie("/environment/attention", this, (int_getter)0, &FGATIS::attend); } +// Hint: +// http://localhost:5400/environment/attention?value=1&submit=update + FGATIS::~FGATIS() { + fgUntie("/environment/attention"); +} + +void FGATIS::Init() { +// Nothing to see here. Move along. } +void +FGATIS::attend (int attn) +{ + attention = attn; +#ifdef ATMO_TEST + int flag = fgGetInt("/sim/logging/atmo"); + if (flag) { + FGAltimeter().check_model(); + FGAltimeter().dump_stack(); + } +#endif +} + + // Main update function - checks whether we are displaying or not the correct message. void FGATIS::Update(double dt) { - if(_display) { - if(_displaying) { - // Check if we need to update the message - // - basically every hour and if the weather changes significantly at the station - } else { - // We need to get and display the message - UpdateTransmission(); - //cout << "ATIS.CXX - calling ATCMgr to render transmission..." << endl; - Render(transmission, refname, true); - _displaying = true; - } - } else { - // We shouldn't be displaying - if(_displaying) { - //cout << "ATIS.CXX - calling NoRender()..." << endl; - NoRender(refname); - _displaying = false; - } - } + cur_time = globals->get_time_params()->get_cur_time(); + msg_OK = (msg_time < cur_time); +#if 0 + if (msg_OK || _display != _prev_display) { + cout << "ATIS Update: " << _display << " " << _prev_display + << " len: " << transmission.length() + << " oldvol: " << old_volume + << " dt: " << dt << endl; + msg_time = cur_time; + } +#endif + if(_display) { + double volume(0); + for (map::iterator act = active_on.begin(); + act != active_on.end(); act++) { + string prop = "/instrumentation/" + act->first + "/volume"; + volume += globals->get_props()->getDoubleValue(prop.c_str()); + } + +// Check if we need to update the message +// - basically every hour and if the weather changes significantly at the station +// If !_prev_display, the radio had been detuned for a while and our +// "transmission" variable was lost when we were de-instantiated. + int rslt = GenTransmission(!_prev_display, attention); + if (rslt || volume != old_volume) { + //cout << "ATIS calling ATC::render volume: " << volume << endl; + Render(transmission, volume, refname, true); + old_volume = volume; + } + } else { +// We shouldn't be displaying + //cout << "ATIS.CXX - calling NoRender()..." << endl; + NoRender(refname); + } + _prev_display = _display; + attention = 0; } -// Sets the actual broadcast ATIS transmission. -void FGATIS::UpdateTransmission() { - double visibility; - char buf[10]; - int phonetic_id; - string phonetic_id_string; - string time_str = fgGetString("sim/time/gmt-string"); - int hours; - // int minutes; - - FGEnvironment stationweather = - ((FGEnvironmentMgr *)globals->get_subsystem("environment")) - ->getEnvironment(lat, lon, 0.0); - - transmission = ""; - - // UK CAA radiotelephony manual indicated ATIS transmissions start with "This is" - // Not sure about rest of the world though. - transmission += "This_is "; - // transmitted station name. - transmission += name; - // Add "Information" - transmission += " information"; - - //cout << "In atis.cxx, time_str = " << time_str << '\n'; - // Add the recording identifier - // For now we will assume we only transmit every hour - hours = atoi((time_str.substr(1,2)).c_str()); //Warning - this is fragile if the - //time string format changes - //cout << "In atis.cxx, hours = " << hours << endl; - phonetic_id = current_commlist->GetCallSign(ident, hours, 0); - phonetic_id_string = GetPhoneticIdent(phonetic_id); - transmission += " "; - transmission += phonetic_id_string; - - // Output the recording time. - we'll just output the last whole hour for now. - // FIXME - this only gets GMT time but that appears to be all the clock outputs for now - //cout << "in atis.cxx, time = " << time_str << endl; - transmission = transmission + " / Weather " + ConvertNumToSpokenDigits((time_str.substr(0,3) + "00")) + " hours zulu"; - - // Get the temperature - int temp; - temp = (int)stationweather.get_temperature_degc(); - - // HACK ALERT - at the moment the new environment subsystem returns bogus temperatures - // FIXME - take out this hack when this gets fixed upstream - if((temp < -50) || (temp > 60)) { - temp = 15; - } - - sprintf(buf, "%i", abs(temp)); - transmission += " / Temperature "; - if(temp < 0) { - transmission += "minus "; - } - string tempstr1 = buf; - string tempstr2; - transmission += ConvertNumToSpokenDigits(tempstr1); - transmission += " degrees_Celsius"; - - // Get the visibility - visibility = stationweather.get_visibility_m(); - sprintf(buf, "%i", int(visibility/1600)); - transmission += " / Visibility "; - tempstr1 = buf; - transmission += ConvertNumToSpokenDigits(tempstr1); - transmission += " miles"; - - // Get the cloudbase - // FIXME: kludge for now - if (strcmp(fgGetString("/environment/clouds/layer[0]/type"), "clear")) { - double cloudbase = - fgGetDouble("/environment/clouds/layer[0]/elevation-ft"); - // For some reason the altitude returned doesn't seem to correspond to the actual cloud altitude. - char buf3[10]; - char buf4[10]; - // cout << "cloudbase = " << cloudbase << endl; - sprintf(buf3, "%i", int(cloudbase)/1000); - sprintf(buf4, "%i", ((int)cloudbase % 1000)/100); - transmission += " / Cloudbase"; - if(int(cloudbase)/1000) { - tempstr1 = buf3; - transmission = transmission + " " + ConvertNumToSpokenDigits(tempstr1) + " thousand"; - } - if(((int)cloudbase % 1000)/100) { - tempstr1 = buf4; - transmission = transmission + " " + ConvertNumToSpokenDigits(tempstr1) + " hundred"; - } - transmission += " feet"; - } - - // Get the pressure / altimeter - double P = fgGetDouble("/environment/pressure-sea-level-inhg"); - if(ident.substr(0,2) == "EG" && fgGetBool("/sim/atc/use-millibars") == true) { - // Convert to millibars for the UK! - P *= 33.864; - sprintf(buf, "%.0f", P); - } else { - sprintf(buf, "%.2f", P); - } - transmission += " / Altimeter "; - tempstr1 = buf; - transmission += ConvertNumToSpokenDigits(tempstr1); - - // Based on the airport-id and wind get the active runway - //FGRunway *r; - double speed = stationweather.get_wind_speed_kt(); - double hdg = stationweather.get_wind_from_heading_deg(); - if (speed == 0) { - hdg = 270; // This forces West-facing rwys to be used in no-wind situations - // which is consistent with Flightgear's initial setup. - transmission += " / Winds_light_and_variable"; - } else { - // FIXME: get gust factor in somehow - char buf5[10]; - char buf6[10]; - sprintf(buf5, "%i", int(speed)); - sprintf(buf6, "%i", int(hdg)); - tempstr1 = buf5; - tempstr2 = buf6; - transmission = transmission + " / Winds " + ConvertNumToSpokenDigits(tempstr1) + " knots from " - + ConvertNumToSpokenDigits(tempstr2) + " degrees"; - } - - const FGAirport* apt = fgFindAirportID(ident); - assert(apt); - string rwy_no = apt->getActiveRunwayForUsage()->ident(); - if(rwy_no != "NN") { - transmission += " / Landing_and_departing_runway "; - transmission += ConvertRwyNumToSpokenString(atoi(rwy_no.c_str())); - //cout << "in atis.cxx, r.rwy_no = " << rwy_no << " r.id = " << r->id << " r.heading = " << r->heading << endl; - } - - // Anything else? - - transmission += " / Advise_controller_on_initial_contact_you_have "; - transmission += phonetic_id_string; - transmission += " /// "; +string uppercase(const string &s) { + string rslt(s); + for(string::iterator p = rslt.begin(); p != rslt.end(); p++){ + *p = toupper(*p); + } + return rslt; +} + +// Replace all occurrences of a given word. +// Words in the original string must be separated by hyphens (not spaces). +// We check for the word as given, and for the all-caps version thereof. +string replace_word(const string _orig, const string _www, const string _nnn){ +// The following are so we can match words at the beginning +// and end of the string. + string orig = "-" + _orig + "-"; + string www = "-" + _www + "-"; + string nnn = "-" + _nnn + "-"; + + size_t where(0); + for ( ; (where = orig.find(www, where)) != string::npos ; ) { + orig.replace(where, www.length(), nnn); + where += nnn.length(); + } + + www = uppercase(www); + for ( ; (where = orig.find(www, where)) != string::npos ; ) { + orig.replace(where, www.length(), nnn); + where += nnn.length(); + } + where = orig.length(); + return orig.substr(1, where-2); +} + +// Normally the interval is 1 hour, +// but you can shorten it for testing. +const int minute(60); // measured in seconds +#ifdef ATIS_TEST + const int ATIS_interval(2*minute); +#else + const int ATIS_interval(60*minute); +#endif + +// Generate the actual broadcast ATIS transmission. +// Regen means regenerate the /current/ transmission. +// Special means generate a new transmission, with a new sequence. +// Returns 1 if we actually generated something. +int FGATIS::GenTransmission(const int regen, const int special) { + using namespace atmodel; + + string BRK = ".\n"; + + double tstamp = atof(fgGetString("sim/time/elapsed-sec")); + int interval = ATIS ? ATIS_interval : 2*minute; // AWOS updated frequently + int sequence = current_commlist->GetAtisSequence(ident, + tstamp, interval, special); + if (!regen && sequence > LTRS) { +//xx if (msg_OK) cout << "ATIS: no change: " << sequence << endl; +//xx msg_time = cur_time; + return 0; // no change since last time + } + + const int bs(100); + char buf[bs]; + string time_str = fgGetString("sim/time/gmt-string"); + string hours, mins; + string phonetic_seq_string; + + transmission = ""; + +// UK CAA radiotelephony manual indicated ATIS transmissions start +// with "This is ..." +// In the US they just start with the airport name. +// transmission += "This_is "; + + // SG_LOG(SG_ATC, SG_ALERT, "ATIS: facility name: " << name); + +// Note that at this point, multi-word facility names +// will sometimes contain hyphens, not spaces. +// Force the issue, just to be safe: + + string name2 = name; + for(string::iterator p = name2.begin(); p != name2.end(); p++){ + if (*p == ' ') *p = '-'; + } + +/////////////// +// FIXME: This would be more flexible and more extensible +// if the mappings were taken from an XML file, not hard-coded. +/////////////// + +// Remap some abbreviations that occur in apt.dat, to +// make things nicer for the text-to-speech system: + name2 = replace_word(name2, "Intl", "International"); + name2 = replace_word(name2, "Rgnl", "Regional"); + name2 = replace_word(name2, "Co", "County"); + name2 = replace_word(name2, "Muni", "Municipal"); + name2 = replace_word(name2, "Mem", "Memorial"); + name2 = replace_word(name2, "Fld", "Field"); + name2 = replace_word(name2, "AFB", "Air-Force-Base"); + name2 = replace_word(name2, "AAF", "Army-Air-Field"); + name2 = replace_word(name2, "MCAS", "Marine-Corps-Air-Station"); + transmission += name2 + " "; + if (_type == ATIS /* as opposed to AWOS */) { + transmission += "airport_information "; + phonetic_seq_string = GetPhoneticLetter(sequence); // Add the sequence letter + transmission += phonetic_seq_string + BRK; + } + transmission += "Automated_weather_observation "; +// Warning - this is fragile if the time string format changes + hours = time_str.substr(0,2).c_str(); + mins = time_str.substr(3,2).c_str(); +// speak each digit separately: + transmission += ConvertNumToSpokenDigits(hours + mins); + transmission += " zulu weather" + BRK; + + transmission += "Wind: "; + + double wind_speed = fgGetDouble("/environment/config/boundary/entry[0]/wind-speed-kt"); + double wind_dir = fgGetDouble("/environment/config/boundary/entry[0]/wind-from-heading-deg"); + while (wind_dir <= 0) wind_dir += 360; +// The following isn't as bad a kludge as it might seem. +// It combines the magvar at the /aircraft/ location with +// the wind direction in the environment/config array. +// But if the aircraft is close enough to the station to +// be receiving the ATIS signal, this should be a good-enough +// approximation. For more-distant aircraft, the wind_dir +// shouldn't be corrected anyway. +// The less-kludgy approach would be to use the magvar associated +// with the station, but that is not tabulated in the stationweather +// structure as it stands, and computing it would be expensive. +// Also note that as it stands, there is only one environment in +// the entire FG universe, so the aircraft environment is the same +// as the station environment anyway. + wind_dir -= fgGetDouble("/environment/magnetic-variation-deg"); // wind_dir now magnetic + if (wind_speed == 0) { +// Force west-facing rwys to be used in no-wind situations +// which is consistent with Flightgear's initial setup: + wind_dir = 270; + transmission += " light_and_variable"; + } else { + // FIXME: get gust factor in somehow + snprintf(buf, bs, "%03.0f", 5*round(wind_dir/5)); + transmission += ConvertNumToSpokenDigits(buf); + + snprintf(buf, bs, "%1.0f", wind_speed); + transmission += " at " + ConvertNumToSpokenDigits(buf) + BRK; + } + + int did_some(0); + int did_ceiling(0); + + for (int layer = 0; layer <= 4; layer++) { + snprintf(buf, bs, "/environment/clouds/layer[%i]/coverage", layer); + string coverage = fgGetString(buf); + if (coverage == "clear") continue; + snprintf(buf, bs, "/environment/clouds/layer[%i]/thickness-ft", layer); + if (fgGetDouble(buf) == 0) continue; + snprintf(buf, bs, "/environment/clouds/layer[%i]/elevation-ft", layer); + double ceiling = int(fgGetDouble(buf) - _geod.getElevationFt()); + if (ceiling > 12000) continue; + if (coverage == "scattered") { + if (!did_some) transmission += " Sky_condition: "; + did_some++; + } else /* must be a ceiling */ if (!did_ceiling) { + transmission += " Ceiling: "; + did_ceiling++; + did_some++; + } else { + transmission += " "; + } + int cig00 = int(round(ceiling/100)); // hundreds of feet + if (cig00) { + int cig000 = cig00/10; + cig00 -= cig000*10; // just the hundreds digit + if (cig000) { + snprintf(buf, bs, "%i", cig000); + transmission += ConvertNumToSpokenDigits(buf); + transmission += " thousand "; + } + if (cig00) { + snprintf(buf, bs, "%i", cig00); + transmission += ConvertNumToSpokenDigits(buf); + transmission += " hundred "; + } + } else { + // Should this be "sky obscured?" + transmission += " zero "; // not "zero hundred" + } + transmission += coverage + BRK; + } + + transmission += "Temperature: "; + double Tsl = fgGetDouble("/environment/temperature-sea-level-degc"); + int temp = int(round(FGAtmo().fake_T_vs_a_us(_geod.getElevationFt(), Tsl))); + if(temp < 0) { + transmission += "minus "; + } + snprintf(buf, bs, "%i", abs(temp)); + transmission += ConvertNumToSpokenDigits(buf); + transmission += " dewpoint "; + double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc"); + temp = int(round(FGAtmo().fake_dp_vs_a_us(dpsl, _geod.getElevationFt()))); + if(temp < 0) { + transmission += "minus "; + } + snprintf(buf, bs, "%i", abs(temp)); + transmission += ConvertNumToSpokenDigits(buf) + BRK; + + + transmission += "Visibility: "; + double visibility = fgGetDouble("/environment/config/boundary/entry[0]/visibility-m"); + visibility /= atmodel::sm; // convert to statute miles + if (visibility < 0.25) { + transmission += "less than one quarter"; + } else if (visibility < 0.5) { + transmission += "one quarter"; + } else if (visibility < 0.75) { + transmission += "one half"; + } else if (visibility < 1.0) { + transmission += "three quarters"; + } else if (visibility >= 1.5 && visibility < 2.0) { + transmission += "one and one half"; + } else { + // integer miles + if (visibility > 10) visibility = 10; + sprintf(buf, "%i", int(.5 + visibility)); + transmission += ConvertNumToSpokenDigits(buf); + } + transmission += BRK; + + transmission += "Altimeter: "; + double myQNH; + double Psl = fgGetDouble("/environment/pressure-sea-level-inhg"); + { + double press, temp; + + make_tuple(ref(press), ref(temp)) = PT_vs_hpt(_geod.getElevationM(), Psl*inHg, Tsl + freezing); +#if 0 + SG_LOG(SG_ATC, SG_ALERT, "Field P: " << press << " T: " << temp); + SG_LOG(SG_ATC, SG_ALERT, "based on elev " << elev + << " Psl: " << Psl + << " Tsl: " << Tsl); +#endif + myQNH = FGAtmo().QNH(_geod.getElevationM(), press); + } + if(ident.substr(0,2) == "EG" && fgGetBool("/sim/atc/use-millibars")) { + // Convert to millibars for the UK! + myQNH /= mbar; + if (myQNH > 1000) myQNH -= 1000; // drop high digit + snprintf(buf, bs, "%03.0f", myQNH); + } else { + myQNH /= inHg; + myQNH *= 100.; // shift two decimal places + snprintf(buf, bs, "%04.0f", myQNH); + } + transmission += ConvertNumToSpokenDigits(buf) + BRK; + + if (_type == ATIS /* as opposed to AWOS */) { + const FGAirport* apt = fgFindAirportID(ident); + assert(apt); + string rwy_no = apt->getActiveRunwayForUsage()->ident(); + if(rwy_no != "NN") { + transmission += "Landing_and_departing_runway "; + transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK; + if (msg_OK) { + msg_time = cur_time; + //cout << "In atis.cxx, r.rwy_no: " << rwy_no + // << " wind_dir: " << wind_dir << endl; + } + } + transmission += "On_initial_contact_advise_you_have_information "; + transmission += phonetic_seq_string; + transmission += "... " + BRK; + } +#ifdef ATIS_TEST + cout << "**** ATIS active on:"; +#endif + for (map::iterator act = active_on.begin(); act != active_on.end(); act++){ + string prop = "/instrumentation/" + act->first + "/atis"; + globals->get_props()->setStringValue(prop.c_str(), + ("
\n" + transmission + "
\n").c_str()); +#ifdef ATIS_TEST + cout << " " << prop; +#endif + } +#ifdef ATIS_TEST + cout << " ****" << endl; + cout << transmission << endl; +// Note that even if we aren't outputting the transmission +// on stdout, you can still see it by pointing a web browser +// at the property tree. The second comm radio is: +// http://localhost:5400/instrumentation/comm[1] +#endif + +// Take the previous English-looking string and munge it to +// be relatively-more acceptable to the primitive tts system. +// Note that : ; and . are among the token-delimeters recognized +// by the tts system. + for (unsigned int where;;) { + where = transmission.find_first_of(":."); + if (where == string::npos) break; + transmission.replace(where, 1, " /_ "); + } + return 1; } diff --git a/src/ATCDCL/atis.hxx b/src/ATCDCL/atis.hxx index 60335e185..aeafe33fb 100644 --- a/src/ATCDCL/atis.hxx +++ b/src/ATCDCL/atis.hxx @@ -23,22 +23,18 @@ #ifndef _FG_ATIS_HXX #define _FG_ATIS_HXX -#include #include +#include #include -#include -#include -#include #include -# include - #include "ATC.hxx" //DCL - a complete guess for now. #define FG_ATIS_DEFAULT_RANGE 30 + class FGATIS : public FGATC { //atc_type type; @@ -48,8 +44,15 @@ class FGATIS : public FGATC { // for failure modeling std::string trans_ident; // transmitted ident + double old_volume; bool atis_failed; // atis failed? + time_t msg_time; // for moderating error messages + time_t cur_time; + int msg_OK; + int attention; + bool _prev_display; // Previous value of _display flag + // Aircraft position // ATIS is actually a special case in that unlike other ATC eg.tower it doesn't actually know about // or the whereabouts of the aircraft it is transmitting to. However, to ensure consistancy of @@ -63,7 +66,9 @@ class FGATIS : public FGATC { FGATIS(void); ~FGATIS(void); - + virtual void Init(); + void attend (int); + //run the ATIS instance void Update(double dt); @@ -75,10 +80,12 @@ class FGATIS : public FGATC { std::string refname; // Holds the refname of a transmission in progress - //Update the transmission string - void UpdateTransmission(void); + int GenTransmission(const int regen, + const int special); // Generate the transmission string friend std::istream& operator>> ( std::istream&, FGATIS& ); }; +typedef int (FGATIS::*int_getter)() const; + #endif // _FG_ATIS_HXX diff --git a/src/ATCDCL/commlist.cxx b/src/ATCDCL/commlist.cxx index 140b3f2da..baed76f37 100644 --- a/src/ATCDCL/commlist.cxx +++ b/src/ATCDCL/commlist.cxx @@ -24,14 +24,16 @@ # include #endif +#include "commlist.hxx" + #include +#include #include #include #include #include #include -#include "commlist.hxx" #include "ATCutils.hxx" @@ -40,6 +42,7 @@ FGCommList *current_commlist; // Constructor FGCommList::FGCommList( void ) { + sg_srandom_time(); } @@ -51,23 +54,23 @@ FGCommList::~FGCommList( void ) { // load the navaids and build the map bool FGCommList::init( const SGPath& path ) { - SGPath temp = path; + SGPath temp = path; commlist_freq.erase(commlist_freq.begin(), commlist_freq.end()); - commlist_bck.erase(commlist_bck.begin(), commlist_bck.end()); + commlist_bck.erase(commlist_bck.begin(), commlist_bck.end()); temp.append( "ATC/default.atis" ); - LoadComms(temp); - temp = path; - temp.append( "ATC/default.tower" ); - LoadComms(temp); - temp = path; - temp.append( "ATC/default.ground" ); - LoadComms(temp); - temp = path; - temp.append( "ATC/default.approach" ); - LoadComms(temp); - return true; + LoadComms(temp); + temp = path; + temp.append( "ATC/default.tower" ); + LoadComms(temp); + temp = path; + temp.append( "ATC/default.ground" ); + LoadComms(temp); + temp = path; + temp.append( "ATC/default.approach" ); + LoadComms(temp); + return true; } - + bool FGCommList::LoadComms(const SGPath& path) { @@ -76,34 +79,34 @@ bool FGCommList::LoadComms(const SGPath& path) { SG_LOG( SG_GENERAL, SG_ALERT, "Cannot open file: " << path.str() ); exit(-1); } - + // read in each line of the file fin >> skipcomment; while ( !fin.eof() ) { ATCData a; - fin >> a; - if(a.type == INVALID) { - SG_LOG(SG_GENERAL, SG_DEBUG, "WARNING - INVALID type found in " << path.str() << '\n'); - } else { - // Push all stations onto frequency map - commlist_freq[a.freq].push_back(a); - - // Push non-atis stations onto bucket map as well - // In fact, push all stations onto bucket map for now so FGATCMgr::GetFrequency() works. - //if(a.type != ATIS) { - // get bucket number - SGBucket bucket(a.lon, a.lat); - int bucknum = bucket.gen_index(); - commlist_bck[bucknum].push_back(a); - //} - } - - fin >> skipcomment; - } - - fin.close(); - return true; + fin >> a; + if(a.type == INVALID) { + SG_LOG(SG_GENERAL, SG_DEBUG, "WARNING - INVALID type found in " << path.str() << '\n'); + } else { + // Push all stations onto frequency map + commlist_freq[a.freq].push_back(a); + + // Push non-atis stations onto bucket map as well + // In fact, push all stations onto bucket map for now so FGATCMgr::GetFrequency() works. + //if(a.type != ATIS and/or AWOS?) { + // get bucket number + SGBucket bucket(a.geod); + int bucknum = bucket.gen_index(); + commlist_bck[bucknum].push_back(a); + //} + } + + fin >> skipcomment; + } + + fin.close(); + return true; } @@ -111,98 +114,89 @@ bool FGCommList::LoadComms(const SGPath& path) { // degrees, elev is in meters // If no atc_type is specified, it returns true if any non-invalid type is found // If atc_type is specifed, returns true only if the specified type is found -bool FGCommList::FindByFreq( double lon, double lat, double elev, double freq, +bool FGCommList::FindByFreq(const SGGeod& aPos, double freq, ATCData* ad, atc_type tp ) { - // HACK - if freq > 1000 assume it's in KHz, otherwise assume MHz. - // A bit ugly but it works for now!!!! - comm_list_type stations; - if(freq > 1000.0) { - stations = commlist_freq[(int)freq]; - } else { - stations = commlist_freq[(int)(freq*100.0 + 0.5)]; - } - comm_list_iterator current = stations.begin(); - comm_list_iterator last = stations.end(); - - // double az1, az2, s; - SGVec3d aircraft = SGVec3d::fromGeod(SGGeod::fromDegM(lon, lat, elev)); - const double orig_max_d = 1e100; - double max_d = orig_max_d; - // TODO - at the moment this loop returns the first match found in range - // We want to return the closest match in the event of a frequency conflict - for ( ; current != last ; ++current ) { - //cout << "testing " << current->get_ident() << endl; - SGVec3d station(current->x, current->y, current->z); - //cout << "aircraft = " << aircraft << endl; - //cout << "station = " << station << endl; - - double d = distSqr(aircraft, station); - - //cout << " dist = " << sqrt(d) - // << " range = " << current->range * SG_NM_TO_METER << endl; - - // TODO - match up to twice the published range so we can model - // reduced signal strength - // NOTE The below is squared since we match to distance3Dsquared (above) to avoid a sqrt. - if ( d < (current->range * SG_NM_TO_METER - * current->range * SG_NM_TO_METER ) ) { - //cout << "matched = " << current->ident << endl; - if((tp == INVALID) || (tp == (*current).type)) { - if(d < max_d) { - max_d = d; - *ad = *current; - } - } - } - } - - if(max_d < orig_max_d) { - return true; - } else { - return false; - } + comm_list_type stations; + stations = commlist_freq[kHz10(freq)]; + comm_list_iterator current = stations.begin(); + comm_list_iterator last = stations.end(); + + // double az1, az2, s; + SGVec3d aircraft = SGVec3d::fromGeod(aPos); + const double orig_max_d = 1e100; + double max_d = orig_max_d; + double d; + // TODO - at the moment this loop returns the first match found in range + // We want to return the closest match in the event of a frequency conflict + for ( ; current != last ; ++current ) { + d = distSqr(aircraft, current->cart); + + //cout << " dist = " << sqrt(d) + // << " range = " << current->range * SG_NM_TO_METER << endl; + + // TODO - match up to twice the published range so we can model + // reduced signal strength + // NOTE The below is squared since we match to distance3Dsquared (above) to avoid a sqrt. + if ( d < (current->range * SG_NM_TO_METER + * current->range * SG_NM_TO_METER ) ) { + //cout << "matched = " << current->ident << endl; + if((tp == INVALID) || (tp == (*current).type)) { + if(d < max_d) { + max_d = d; + *ad = *current; + } + } + } + } + + if(max_d < orig_max_d) { + return true; + } else { + return false; + } } -int FGCommList::FindByPos(double lon, double lat, double elev, double range, comm_list_type* stations, atc_type tp) +int FGCommList::FindByPos(const SGGeod& aPos, double range, comm_list_type* stations, atc_type tp) { - // number of relevant stations found within range - int found = 0; - stations->erase(stations->begin(), stations->end()); - - // get bucket number for plane position - SGBucket buck(lon, lat); - - // get neigboring buckets - int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2); - int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 ); - - // loop over bucket range - for ( int i=-bx; i<=bx; i++) { - for ( int j=-by; j<=by; j++) { - buck = sgBucketOffset(lon, lat, i, j); - long int bucket = buck.gen_index(); - comm_list_type Fstations = commlist_bck[bucket]; - comm_list_iterator current = Fstations.begin(); - comm_list_iterator last = Fstations.end(); - - // double az1, az2, s; - SGVec3d aircraft = SGVec3d::fromGeod(SGGeod::fromDegM(lon, lat, elev)); - for(; current != last; ++current) { - if((current->type == tp) || (tp == INVALID)) { - SGVec3d station(current->x, current->y, current->z); - double d = distSqr(aircraft, station); - // NOTE The below is squared since we match to distance3Dsquared (above) to avoid a sqrt. - if ( d < (current->range * SG_NM_TO_METER - * current->range * SG_NM_TO_METER ) ) { - stations->push_back(*current); - ++found; - } - } - } - } - } - return found; + // number of relevant stations found within range + int found = 0; + stations->erase(stations->begin(), stations->end()); + + // get bucket number for plane position + SGBucket buck(aPos); + + // get neigboring buckets + int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2); + int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 ); + + // loop over bucket range + for ( int i=-bx; i<=bx; i++) { + for ( int j=-by; j<=by; j++) { + buck = sgBucketOffset(aPos.getLongitudeDeg(), aPos.getLatitudeDeg(), i, j); + long int bucket = buck.gen_index(); + comm_list_type Fstations = commlist_bck[bucket]; + comm_list_iterator current = Fstations.begin(); + comm_list_iterator last = Fstations.end(); + + + // double az1, az2, s; + SGVec3d aircraft = SGVec3d::fromGeod(aPos); + double d; + for(; current != last; ++current) { + if((current->type == tp) || (tp == INVALID)) { + d = distSqr(aircraft, current->cart); + // NOTE The below is squared since we match to distance3Dsquared (above) to avoid a sqrt. + if ( d < (current->range * SG_NM_TO_METER + * current->range * SG_NM_TO_METER ) ) { + stations->push_back(*current); + ++found; + } + } + } + } + } + return found; } @@ -213,41 +207,39 @@ int FGCommList::FindByPos(double lon, double lat, double elev, double range, com // Note that the search algorithm starts at 10 miles and multiplies by 10 thereafter, so if // say 300 miles is specifed 10, then 100, then 1000 will be searched, breaking at first result // and giving up after 1000. -double FGCommList::FindClosest( double lon, double lat, double elev, ATCData& ad, atc_type tp, double max_range) { - int num_stations = 0; - int range = 10; - comm_list_type stations; - comm_list_iterator itr; - double distance = -9999.0; - - while(num_stations == 0) { - num_stations = FindByPos(lon, lat, elev, range, &stations, tp); - if(num_stations) { - double closest = max_range * SG_NM_TO_METER; - double tmp; - for(itr = stations.begin(); itr != stations.end(); ++itr) { - ATCData ad2 = *itr; - SGGeod p1 = SGGeod::fromDegM(ad2.lon, ad2.lat, ad2.elev); - const FGAirport *a = fgFindAirportID(ad2.ident); - if (a) { - SGGeod p2 = SGGeod::fromDegM(lon, lat, elev); - tmp = dclGetHorizontalSeparation(p1, p2); - if(tmp <= closest) { - closest = tmp; - distance = tmp; - ad = *itr; - } - } - } - //cout << "Closest station is " << ad.ident << " at a range of " << distance << " meters\n"; - return(distance); - } - if(range > max_range) { - break; - } - range *= 10; - } - return(-9999.0); +double FGCommList::FindClosest(const SGGeod& aPos, ATCData& ad, atc_type tp, double max_range) { + int num_stations = 0; + int range = 10; + comm_list_type stations; + comm_list_iterator itr; + double distance = -9999.0; + + while(num_stations == 0) { + num_stations = FindByPos(aPos, range, &stations, tp); + if(num_stations) { + double closest = max_range * SG_NM_TO_METER; + double tmp; + for(itr = stations.begin(); itr != stations.end(); ++itr) { + ATCData ad2 = *itr; + const FGAirport *a = fgFindAirportID(ad2.ident); + if (a) { + tmp = dclGetHorizontalSeparation(ad2.geod, aPos); + if(tmp <= closest) { + closest = tmp; + distance = tmp; + ad = *itr; + } + } + } + //cout << "Closest station is " << ad.ident << " at a range of " << distance << " meters\n"; + return(distance); + } + if(range > max_range) { + break; + } + range *= 10; + } + return(-9999.0); } @@ -257,72 +249,53 @@ double FGCommList::FindClosest( double lon, double lat, double elev, ATCData& ad bool FGCommList::FindByCode( const string& ICAO, ATCData& ad, atc_type tp ) { const FGAirport *a = fgFindAirportID( ICAO); if ( a) { - comm_list_type stations; - int found = FindByPos(a->getLongitude(), a->getLatitude(), a->getElevation(), 10.0, &stations, tp); - if(found) { - comm_list_iterator itr = stations.begin(); - while(itr != stations.end()) { - if(((*itr).ident == ICAO) && ((*itr).type == tp)) { - ad = *itr; - return true; - } - ++itr; - } - } - } else { - return false; + comm_list_type stations; + int found = FindByPos(a->geod(), 10.0, &stations, tp); + if(found) { + comm_list_iterator itr = stations.begin(); + while(itr != stations.end()) { + if(((*itr).ident == ICAO) && ((*itr).type == tp)) { + ad = *itr; + //cout << "FindByCode returns " << ICAO + // << " type: " << tp + // << " freq: " << itr->freq + // << endl; + return true; + } + ++itr; + } + } } - return false; + return false; } - // TODO - this function should move somewhere else eventually! -// Return an appropriate call-sign for an ATIS transmission. -int FGCommList::GetCallSign( const string& apt_id, int hours, int mins ) +// Return an appropriate sequence number for an ATIS transmission. +// Return sequence number + 2600 if sequence is unchanged since +// last time. +int FGCommList::GetAtisSequence( const string& apt_id, + const double tstamp, const int interval, const int special) { - atis_transmission_type tran; - - if(atislog.find(apt_id) == atislog.end()) { - // This station has not transmitted yet - return a random identifier - // and add the transmission to the log - tran.hours = hours; - tran.mins = mins; - sg_srandom_time(); - tran.callsign = int(sg_random() * 25) + 1; // This *should* give a random int between 1 and 26 - //atislog[apt_id].push_back(tran); - atislog[apt_id] = tran; - } else { - // This station has transmitted - calculate the appropriate identifier - // and add the transmission to the log if it has changed - tran = atislog[apt_id]; - // This next bit assumes that no-one comes back to the same ATIS station - // after running FlightGear for more than 24 hours !! - if((tran.hours == hours) && (tran.mins == mins)) { - return(tran.callsign); - } else { - if(tran.hours == hours) { - // The minutes must have changed - tran.mins = mins; - tran.callsign++; - } else { - if(hours < tran.hours) { - hours += 24; - } - tran.callsign += (hours - tran.hours); - if(mins != 0) { - // Assume transmissions were made on every hour - tran.callsign++; - } - tran.hours = hours; - tran.mins = mins; - } - // Wrap if we've exceeded Zulu - if(tran.callsign > 26) { - tran.callsign -= 26; - } - // And write the new transmission to the log - atislog[apt_id] = tran; - } - } - return(tran.callsign); + atis_transmission_type tran; + + if(atislog.find(apt_id) == atislog.end()) { // New station + tran.tstamp = tstamp - interval; +// Random number between 0 and 25 inclusive, i.e. 26 equiprobable outcomes: + tran.sequence = int(sg_random() * LTRS); + atislog[apt_id] = tran; + //cout << "New ATIS station: " << apt_id << " seq-1: " + // << tran.sequence << endl; + } + +// calculate the appropriate identifier and update the log + tran = atislog[apt_id]; + + int delta = int((tstamp - tran.tstamp) / interval); + tran.tstamp += delta * interval; + if (special && !delta) delta++; // a "special" ATIS update is required + tran.sequence = (tran.sequence + delta) % LTRS; + atislog[apt_id] = tran; + //if (delta) cout << "New ATIS sequence: " << tran.sequence + // << " Delta: " << delta << endl; + return(tran.sequence + (delta ? 0 : LTRS*1000)); } diff --git a/src/ATCDCL/commlist.hxx b/src/ATCDCL/commlist.hxx index e253e6e98..c5066f3e9 100644 --- a/src/ATCDCL/commlist.hxx +++ b/src/ATCDCL/commlist.hxx @@ -35,7 +35,6 @@ #include -#include #include #include @@ -44,24 +43,21 @@ #include "ATC.hxx" #include "atis.hxx" -using std::list; -using std::map; -using std::vector; -using std::string; +class SGPath; // A list of ATC stations -typedef list < ATCData > comm_list_type; +typedef std::list < ATCData > comm_list_type; typedef comm_list_type::iterator comm_list_iterator; typedef comm_list_type::const_iterator comm_list_const_iterator; // A map of ATC station lists -typedef map < int, comm_list_type > comm_map_type; +typedef std::map < int, comm_list_type > comm_map_type; typedef comm_map_type::iterator comm_map_iterator; typedef comm_map_type::const_iterator comm_map_const_iterator; class FGCommList { - + public: FGCommList(); @@ -71,60 +67,59 @@ public: bool init( const SGPath& path ); // query the database for the specified frequency, lon and lat are - // in degrees, elev is in meters. - // If no atc_type is specified, it returns true if any non-invalid type is found. - // If atc_type is specifed, returns true only if the specified type is found. - // Returns the station closest to the supplied position. - // The data found is written into the passed-in ATCData structure. - bool FindByFreq( double lon, double lat, double elev, double freq, ATCData* ad, atc_type tp = INVALID ); - + // If no atc_type is specified, it returns true if any non-invalid type is found. + // If atc_type is specifed, returns true only if the specified type is found. + // Returns the station closest to the supplied position. + // The data found is written into the passed-in ATCData structure. + bool FindByFreq(const SGGeod& aPos, double freq, ATCData* ad, atc_type tp = INVALID ); + // query the database by location, lon and lat are in degrees, elev is in meters, range is in nautical miles. - // Returns the number of stations of the specified atc_type tp that are in range of the position defined by - // lon, lat and elev, and pushes them into stations. - // If no atc_type is specifed, returns the number of all stations in range, and pushes them into stations - // ** stations is erased before use ** - int FindByPos( double lon, double lat, double elev, double range, comm_list_type* stations, atc_type tp = INVALID ); - - // Returns the distance in meters to the closest station of a given type, - // with the details written into ATCData& ad. If no type is specifed simply - // returns the distance to the closest station of any type. - // Returns -9999 if no stations found within max_range in nautical miles (default 100 miles). - // Note that the search algorithm starts at 10 miles and multiplies by 10 thereafter, so if - // say 300 miles is specifed 10, then 100, then 1000 will be searched, breaking at first result - // and giving up after 1000. - // !!!Be warned that searching anything over 100 miles will pause the sim unacceptably!!! - // (The ability to search longer ranges should be used during init only). - double FindClosest( double lon, double lat, double elev, ATCData& ad, atc_type tp = INVALID, double max_range = 100.0 ); - - // Find by Airport code. - bool FindByCode( const string& ICAO, ATCData& ad, atc_type tp = INVALID ); - - // Return the callsign for an ATIS transmission given transmission time and airport id - // This maybe should get moved somewhere else!! - int GetCallSign( const string& apt_id, int hours, int mins ); - + // Returns the number of stations of the specified atc_type tp that are in range of the position defined by + // lon, lat and elev, and pushes them into stations. + // If no atc_type is specifed, returns the number of all stations in range, and pushes them into stations + // ** stations is erased before use ** + int FindByPos(const SGGeod& aPos, double range, comm_list_type* stations, atc_type tp = INVALID ); + + // Returns the distance in meters to the closest station of a given type, + // with the details written into ATCData& ad. If no type is specifed simply + // returns the distance to the closest station of any type. + // Returns -9999 if no stations found within max_range in nautical miles (default 100 miles). + // Note that the search algorithm starts at 10 miles and multiplies by 10 thereafter, so if + // say 300 miles is specifed 10, then 100, then 1000 will be searched, breaking at first result + // and giving up after 1000. + // !!!Be warned that searching anything over 100 miles will pause the sim unacceptably!!! + // (The ability to search longer ranges should be used during init only). + double FindClosest(const SGGeod& aPos, ATCData& ad, atc_type tp = INVALID, double max_range = 100.0 ); + + // Find by Airport code. + bool FindByCode( const std::string& ICAO, ATCData& ad, atc_type tp = INVALID ); + + // Return the sequence letter for an ATIS transmission given transmission time and airport id + // This maybe should get moved somewhere else!! + int GetAtisSequence( const std::string& apt_id, const double tstamp, + const int interval, const int flush=0); + + // Comm stations mapped by frequency + comm_map_type commlist_freq; + + // Comm stations mapped by bucket + comm_map_type commlist_bck; + + // Load comms from a specified path (which must include the filename) private: - - // Comm stations mapped by frequency - comm_map_type commlist_freq; - - // Comm stations mapped by bucket - comm_map_type commlist_bck; - // Load comms from a specified path (which must include the filename) - bool LoadComms(const SGPath& path); + bool LoadComms(const SGPath& path); //----------- This stuff is left over from atislist.[ch]xx and maybe should move somewhere else - // Add structure and map for storing a log of atis transmissions - // made in this session of FlightGear. This allows the callsign - // to be allocated correctly wrt time. - typedef struct { - int hours; - int mins; - int callsign; - } atis_transmission_type; - - typedef map < string, atis_transmission_type > atis_log_type; + // Add structure and map for storing a log of atis transmissions + // made in this session of FlightGear. This allows the callsign + // to be allocated correctly wrt time. + typedef struct { + double tstamp; + int sequence; + } atis_transmission_type; + + typedef std::map < std::string, atis_transmission_type > atis_log_type; typedef atis_log_type::iterator atis_log_iterator; typedef atis_log_type::const_iterator atis_log_const_iterator; @@ -136,5 +131,6 @@ private: extern FGCommList *current_commlist; - #endif // _FG_COMMLIST_HXX + + diff --git a/src/ATCDCL/tower.cxx b/src/ATCDCL/tower.cxx index 1b92eaa16..d1886c338 100644 --- a/src/ATCDCL/tower.cxx +++ b/src/ATCDCL/tower.cxx @@ -1409,8 +1409,9 @@ void FGTower::CheckDepartureList(double dt) { //cout << "Dep list, checking " << t->plane.callsign; double distout; // meters - if(t->isUser) distout = dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); - else distout = dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), t->planePtr->getPos()); + if(t->isUser) distout = dclGetHorizontalSeparation(_geod, + SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); + else distout = dclGetHorizontalSeparation(_geod, t->planePtr->getPos()); //cout << " distout = " << distout << '\n'; if(t->isUser && !(t->clearedToTakeOff)) { // HACK - we use clearedToTakeOff to check if ATC already contacted with plane (and cleared take-off) or not if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) { @@ -1563,7 +1564,7 @@ bool FGTower::OnActiveRunway(const SGGeod& pt) { // Only call this at startup - reading the runways database is expensive and needs to be fixed! bool FGTower::OnAnyRunway(const SGGeod& pt, bool onGround) { ATCData ad; - double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 7.0); + double dist = current_commlist->FindClosest(_geod, ad, TOWER, 7.0); if(dist < 0.0) { return(false); } @@ -2540,7 +2541,7 @@ string FGTower::GenText(const string& m, int c) { else if ( strcmp ( tag, "@MI" ) == 0 ) { char buf[10]; //sprintf( buf, "%3.1f", tpars.miles ); - int dist_miles = (int)dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600; + int dist_miles = (int)dclGetHorizontalSeparation(_geod, SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600; sprintf(buf, "%i", dist_miles); strcat( &dum[0], &buf[0] ); } @@ -2560,7 +2561,7 @@ string FGTower::GenText(const string& m, int c) { } } else if(strcmp(tag, "@CD") == 0) { // @CD = compass direction - double h = GetHeadingFromTo(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); + double h = GetHeadingFromTo(_geod, SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); while(h < 0.0) h += 360.0; while(h > 360.0) h -= 360.0; if(h < 22.5 || h > 337.5) { @@ -2625,9 +2626,13 @@ string FGTower::GetWeather() { } string FGTower::GetATISID() { - int hours = fgGetInt("/sim/time/utc/hour"); - int phonetic_id = current_commlist->GetCallSign(ident, hours, 0); - return GetPhoneticIdent(phonetic_id); + double tstamp = atof(fgGetString("sim/time/elapsed-sec")); + const int minute(60); // in SI units + int interval = ATIS ? 60*minute : 2*minute; // AWOS updated frequently + int sequence = current_commlist->GetAtisSequence(ident, + tstamp, interval); + + return GetPhoneticLetter(sequence); // the sequence letter } ostream& operator << (ostream& os, tower_traffic_type ttt) { diff --git a/src/ATCDCL/tower.hxx b/src/ATCDCL/tower.hxx index 6a08f27c8..a09536993 100644 --- a/src/ATCDCL/tower.hxx +++ b/src/ATCDCL/tower.hxx @@ -27,6 +27,7 @@ #include #include +#include #include "ATC.hxx" #include "ATCProjection.hxx" diff --git a/src/Airports/apt_loader.cxx b/src/Airports/apt_loader.cxx index a8af8ca47..0cb01f3bb 100644 --- a/src/Airports/apt_loader.cxx +++ b/src/Airports/apt_loader.cxx @@ -26,6 +26,8 @@ # include #endif +#include "apt_loader.hxx" + #include #include // atof(), atoi() @@ -43,8 +45,11 @@ #include "simple.hxx" #include "runways.hxx" #include "pavement.hxx" +#include -#include "apt_loader.hxx" +#include + +using namespace std; static FGPositioned::Type fptypeFromRobinType(int aType) { @@ -61,9 +66,19 @@ static FGPositioned::Type fptypeFromRobinType(int aType) class APTLoader { public: - void parseAPT(const string &aptdb_file) + + APTLoader() + : last_apt_id(""), + last_apt_name(""), + last_apt_elev(0.0), + last_apt_info(""), + last_apt_type("") + {} + + void parseAPT(const string &aptdb_file, FGCommList *comm_list) { sg_gzifstream in( aptdb_file ); + if ( !in.is_open() ) { SG_LOG( SG_GENERAL, SG_ALERT, "Cannot open file: " << aptdb_file ); exit(-1); @@ -139,8 +154,10 @@ public: // custom startup locations (ignore) } else if ( line_id == 0 ) { // ?? - } else if ( line_id >= 50 && line_id <= 56 ) { - // frequency entries (ignore) + } else if ( line_id == 50 ) { + parseATISLine(comm_list, simgear::strutils::split(line)); + } else if ( line_id >= 51 && line_id <= 56 ) { + // other frequency entries (ignore) } else if ( line_id == 110 ) { pavement = true; parsePavementLine850(simgear::strutils::split(line, 0, 4)); @@ -166,6 +183,7 @@ public: } private: + vector token; double rwy_lat_accum; double rwy_lon_accum; double last_rwy_heading; @@ -175,7 +193,8 @@ private: string last_apt_name; double last_apt_elev; SGGeod tower; - int last_apt_type; + string last_apt_info; + string last_apt_type; string pavement_ident; bool pavement; @@ -209,7 +228,7 @@ private: SGGeod pos(SGGeod::fromDegFt(lon, lat, last_apt_elev)); FGAirport* apt = new FGAirport(last_apt_id, pos, tower, last_apt_name, false, - fptypeFromRobinType(last_apt_type)); + fptypeFromRobinType(atoi(last_apt_type.c_str()))); apt->setRunwaysAndTaxiways(runways, taxiways, pavements); } @@ -232,7 +251,7 @@ private: last_apt_name += " "; } last_apt_name += token[token.size() - 1]; - last_apt_type = atoi( token[0].c_str() ); + last_apt_type = token[0]; // clear runway list for start of next airport rwy_lon_accum = 0.0; @@ -433,15 +452,62 @@ private: pvt->addNode(pos, num == 113); } } + + void parseATISLine(FGCommList *comm_list, const vector& token) + { + if ( rwy_count <= 0 ) { + SG_LOG( SG_GENERAL, SG_ALERT, + "No runways; skipping AWOS for " + last_apt_id); + } + +// This assumes/requires that any code-50 line (ATIS or AWOS) + // applies to the preceding code-1 line (airport ID and name) + // and that a full set of code-10 lines (runway descriptors) + // has come between the code-1 and code-50 lines. + // typical code-50 lines: + // 50 11770 ATIS + // 50 11770 AWOS 3 + // This code parallels code found in "operator>>" in ATC.hxx; + // FIXME: unify the code. + ATCData a; + a.geod = SGGeod::fromDegFt(rwy_lon_accum / (double)rwy_count, + rwy_lat_accum / (double)rwy_count, last_apt_elev); + a.range = 50; // give all ATISs small range + a.ident = last_apt_id; + a.name = last_apt_name; + // short int representing tens of kHz: + a.freq = atoi(token[1].c_str()); + if (token[2] == "ATIS") a.type = ATIS; + else a.type = AWOS; // ASOS same as AWOS + + // generate cartesian coordinates + a.cart = SGVec3d::fromGeod(a.geod); + comm_list->commlist_freq[a.freq].push_back(a); + + SGBucket bucket(a.geod); + int bucknum = bucket.gen_index(); + comm_list->commlist_bck[bucknum].push_back(a); +#if 0 + SG_LOG( SG_GENERAL, SG_ALERT, + "Loaded ATIS/AWOS for airport: " << a.ident + << " lat: " << a.geod.getLatitudeDeg() + << " lon: " << a.geod.getLongitudeDeg() + << " freq: " << a.freq + << " type: " << a.type ); +#endif + } }; // Load the airport data base from the specified aptdb file. The // metar file is used to mark the airports as having metar available // or not. -bool fgAirportDBLoad( const string &aptdb_file, const string &metar_file ) +bool fgAirportDBLoad( const string &aptdb_file, + FGCommList *comm_list, const std::string &metar_file ) { + APTLoader ld; - ld.parseAPT(aptdb_file); + + ld.parseAPT(aptdb_file, comm_list); // // Load the metar.dat file and update apt db with stations that diff --git a/src/Airports/apt_loader.hxx b/src/Airports/apt_loader.hxx index af2d46aeb..54596e8d8 100644 --- a/src/Airports/apt_loader.hxx +++ b/src/Airports/apt_loader.hxx @@ -29,13 +29,14 @@ #include -#include "simple.hxx" - +// forward decls +class FGCommList; // Load the airport data base from the specified aptdb file. The // metar file is used to mark the airports as having metar available // or not. -bool fgAirportDBLoad( const string &aptdb_file, const std::string &metar_file ); +bool fgAirportDBLoad( const std::string &aptdb_file, + FGCommList *comm_list, const std::string &metar_file ); #endif // _FG_APT_LOADER_HXX diff --git a/src/Environment/environment.cxx b/src/Environment/environment.cxx index f915e5261..f6238c415 100644 --- a/src/Environment/environment.cxx +++ b/src/Environment/environment.cxx @@ -708,7 +708,6 @@ FGEnvironment::_recalc_alt_pt () << " and " << temperature_sea_level_degc << " :: " << this << " # " << count); - ///////////////////////////////////raise(SIGUSR1); } } #endif diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 953e4a254..ab5742ac4 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -127,6 +127,7 @@ #include "renderer.hxx" #include "viewmgr.hxx" #include "main.hxx" +#include "ATCDCL/commlist.hxx" #ifdef __APPLE__ # include @@ -968,7 +969,11 @@ fgInitNav () SGPath p_metar( globals->get_fg_root() ); p_metar.append( "Airports/metar.dat" ); - fgAirportDBLoad( aptdb.str(), p_metar.str() ); +// Initialise the frequency search map BEFORE reading +// the airport database: + current_commlist = new FGCommList; + current_commlist->init( globals->get_fg_root() ); + fgAirportDBLoad( aptdb.str(), current_commlist, p_metar.str() ); FGNavList *navlist = new FGNavList; FGNavList *loclist = new FGNavList; -- 2.39.5