]> git.mxchange.org Git - flightgear.git/blobdiff - src/ATCDCL/atis.cxx
MapWidget: make use of the new POI system and display cities on the map.
[flightgear.git] / src / ATCDCL / atis.cxx
index 95acc757b3dd33e01b05270faa915cd77fcd3465..3bc9e2f1f5fe2500ffd1a3bc5e7da7771ee857ae 100644 (file)
@@ -2,6 +2,7 @@
 // This is the implementation of the FGATIS class
 //
 // Written by David Luff, started October 2001.
+// Extended by Thorsten Brehm, October 2012.
 //
 // Copyright (C) 2001  David C Luff - david.luff@nottingham.ac.uk
 //
@@ -36,6 +37,7 @@
 #include <simgear/compiler.h>
 #include <simgear/math/sg_random.h>
 #include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/strutils.hxx>
 
 #include <stdlib.h> // atoi()
 #include <stdio.h>  // sprintf
@@ -55,6 +57,8 @@
 #include <Airports/runways.hxx>
 #include <Airports/dynamics.hxx>
 
+#include <ATC/CommStation.hxx>
+#include <Navaids/navrecord.hxx>
 
 #include "ATCutils.hxx"
 #include "ATISmgr.hxx"
@@ -65,18 +69,50 @@ using std::cout;
 using std::cout;
 using boost::ref;
 using boost::tie;
+using flightgear::CommStation;
 
-FGATIS::FGATIS(const string& commbase) :
+FGATIS::FGATIS(const std::string& name, int num) :
+  _name(name),
+  _num(num),
+  _cb_attention(this, &FGATIS::attend, fgGetNode("/environment/attention", true)),
   transmission(""),
   trans_ident(""),
   old_volume(0),
   atis_failed(false),
+  msg_time(0),
+  cur_time(0),
   msg_OK(0),
-  attention(0),
+  _attention(false),
+  _check_transmission(true),
   _prev_display(0),
-  _commbase(commbase)
+  _time_before_search_sec(0),
+  _last_frequency(0)
 {
-  fgTie("/environment/attention", this, (int_getter)0, &FGATIS::attend);
+  _root         = fgGetNode("/instrumentation", true)->getNode(_name, num, true);
+  _volume       = _root->getNode("volume",true);
+  _serviceable  = _root->getNode("serviceable",true);
+
+  if (name != "nav")
+  {
+      // only drive "operable" for non-nav instruments (nav radio drives this separately)
+      _operable = _root->getNode("operable",true);
+      _operable->setBoolValue(false);
+  }
+
+  _electrical   = fgGetNode("/systems/electrical/outputs",true)->getNode(_name,num, true);
+  _atis         = _root->getNode("atis",true);
+  _freq         = _root->getNode("frequencies/selected-mhz",true);
+
+  // current position
+  _lon_node  = fgGetNode("/position/longitude-deg", true);
+  _lat_node  = fgGetNode("/position/latitude-deg",  true);
+  _elev_node = fgGetNode("/position/altitude-ft",   true);
+
+  // backward compatibility: some properties may not exist (but default to "ON")
+  if (!_serviceable->hasValue())
+      _serviceable->setBoolValue(true);
+  if (!_electrical->hasValue())
+      _electrical->setDoubleValue(24.0);
 
 ///////////////
 // FIXME:  This would be more flexible and more extensible
@@ -85,24 +121,23 @@ FGATIS::FGATIS(const string& commbase) :
 //
 // Load the remap list from the .hxx file:
   using namespace lex;
-# define NIL ""
-# define REMAP(from,to) _remap[#from] = to;
-# include "atis_remap.hxx"
-# undef REMAP
-# undef NIL
 
-#ifdef ATIS_TEST
-  SG_LOG(SG_ATC, SG_ALERT, "ATIS initialized");
-#endif
+  # define NIL ""
+  # define REMAP(from,to) _remap[#from] = to;
+  # include "atis_remap.hxx"
+  # undef REMAP
+  # undef NIL
+
+  #ifdef ATIS_TEST
+    SG_LOG(SG_ATC, SG_ALERT, "ATIS initialized");
+  #endif
+
+    _report.psl = 0;
 }
 
 // Hint:
 // http://localhost:5400/environment/attention?value=1&submit=update
 
-FGATIS::~FGATIS() {
-  fgUntie("/environment/attention");
-}
-
 FGATCVoice* FGATIS::GetVoicePointer()
 {
     FGATISMgr* pAtisMgr = globals->get_ATIS_mgr();
@@ -115,26 +150,34 @@ FGATCVoice* FGATIS::GetVoicePointer()
     return pAtisMgr->GetVoicePointer(ATIS);
 }
 
-void FGATIS::Init() {
+void FGATIS::init()
+{
 // Nothing to see here.  Move along.
 }
 
+void FGATIS::reinit()
+{
+    _time_before_search_sec = 0;
+    _check_transmission = true;
+}
+
 void
-FGATIS::attend (int attn)
+FGATIS::attend(SGPropertyNode* node)
 {
-  attention = attn;
+  if (node->getBoolValue())
+      _attention = true;
 #ifdef ATMO_TEST
   int flag = fgGetInt("/sim/logging/atmo");
   if (flag) {
     FGAltimeter().check_model();
-       FGAltimeter().dump_stack();
+    FGAltimeter().dump_stack();
   }
 #endif
 }
 
 
 // Main update function - checks whether we are displaying or not the correct message.
-void FGATIS::Update(double dt) {
+void FGATIS::update(double dt) {
   cur_time = globals->get_time_params()->get_cur_time();
   msg_OK = (msg_time < cur_time);
 
@@ -148,37 +191,60 @@ void FGATIS::Update(double dt) {
   }
 #endif
 
-  if(_display)
+  double volume = 0;
+  if ((_electrical->getDoubleValue() > 8) && _serviceable->getBoolValue())
+  {
+      // radio is switched on and OK
+      if (_operable.valid())
+          _operable->setBoolValue(true);
+
+      _check_transmission |= search(dt);
+
+      if (_display)
+      {
+          volume = _volume->getDoubleValue();
+      }
+  }
+  else
   {
-    string prop = _commbase + "/volume";
-    double 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 changed = GenTransmission(!_prev_display, attention);
-    TreeOut(msg_OK);
+      // radio is OFF
+      if (_operable.valid())
+          _operable->setBoolValue(false);
+      _time_before_search_sec = 0;
+  }
+
+  if (volume > 0.05)
+  {
+    bool changed = false;
+    if (_check_transmission)
+    {
+        _check_transmission = false;
+        // 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.
+        if (genTransmission(!_prev_display, _attention))
+        {
+            // update output property
+            treeOut(msg_OK);
+            changed = true;
+        }
+    }
+
     if (changed || volume != old_volume) {
-      //cout << "ATIS calling ATC::render  volume: " << volume << endl;
-      Render(transmission, volume, _commbase, true);
+      // audio output enabled
+      Render(transmission, volume, _name, true);
       old_volume = volume;
     }
+    _prev_display = _display;
   } else {
-// We shouldn't be displaying
-    //cout << "ATIS.CXX - calling NoRender()..." << endl;
-    NoRender(_commbase);
+    // silence
+    NoRender(_name);
+    _prev_display = false;
   }
-  _prev_display = _display;
-  attention = 0;
-}
+  _attention = false;
 
-string uppercase(const string &s) {
-  string rslt(s);
-  for(string::iterator p = rslt.begin(); p != rslt.end(); p++){
-    *p = toupper(*p);
-  }
-  return rslt;
+  FGATC::update(dt);
 }
 
 // Replace all occurrences of a given word.
@@ -197,7 +263,7 @@ string replace_word(const string _orig, const string _www, const string _nnn){
     where += nnn.length();
   }
   
-  www = uppercase(www);
+  www = simgear::strutils::uppercase(www);
   for ( ; (where = orig.find(www, where)) != string::npos ; ) {
     orig.replace(where, www.length(), nnn);
     where += nnn.length();
@@ -224,296 +290,658 @@ const int minute(60);           // measured in seconds
 // ascertaining which airports are in the US, let alone
 // (b) ascertaining which other places use inches.
 //
-int Apt_US_CA(const string id) {
-// Assume all IDs have length 3 or 4.
-// No counterexamples have been seen.
-  if (id.length() == 4) {
-    if (id.substr(0,1) == "K") return 1;
-    if (id.substr(0,2) == "CY") return 1;
-  }
-  for (string::const_iterator ptr = id.begin(); ptr != id.end();  ptr++) {
-    if (isdigit(*ptr)) return 1;
-  }
-  return 0;
+bool Apt_US_CA(const string id)
+{
+    // Assume all IDs have length 3 or 4.
+    // No counterexamples have been seen.
+    if (id.length() == 4) {
+        if (id.substr(0,1) == "K") return true;
+        if (id.substr(0,2) == "CY") return true;
+    }
+    for (string::const_iterator ptr = id.begin(); ptr != id.end();  ptr++) {
+        if (isdigit(*ptr)) return true;
+    }
+    return false;
 }
 
-// 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;
-  using namespace lex;
+// voice spacers
+static const string BRK = ".\n";
+static const string PAUSE = " / ";
 
-  string BRK = ".\n";
-  string PAUSE = " / ";
+/** Generate the actual broadcast ATIS transmission.
+*   'regen' triggers a regeneration of the /current/ transmission.
+*   'forceUpdate' generates a new transmission, with a new sequence.
+*   Returns 1 if we actually generated something.
+*/
+bool FGATIS::genTransmission(const int regen, bool forceUpdate)
+{
+    using namespace lex;
 
-  int interval = _type == ATIS ?
-        ATIS_interval   // ATIS updated hourly
-      : 2*minute;      // AWOS updated more frequently
+    // ATIS updated hourly, AWOS updated more frequently
+    int interval = _type == ATIS ? ATIS_interval : 2*minute;
 
-  FGAirport* apt = FGAirport::findByIdent(ident);
-  int sequence = apt->getDynamics()->updateAtisSequence(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
-  }
+    // check if pressure has changed significantly and we need to update ATIS
+    double Psl = fgGetDouble("/environment/pressure-sea-level-inhg");
+    if (fabs(Psl-_report.psl) >= 0.15)
+        forceUpdate = true;
 
-  const int bs(100);
-  char buf[bs];
-  string time_str = fgGetString("sim/time/gmt-string");
-  string hours, mins;
-  string phonetic_seq_string;
+    FGAirport* apt = FGAirport::findByIdent(ident);
+    int sequence = apt->getDynamics()->updateAtisSequence(interval, forceUpdate);
+    if (!regen && sequence > LTRS) {
+        //xx      if (msg_OK) cout << "ATIS:  no change: " << sequence << endl;
+        //xx    msg_time = cur_time;
+        return false;   // no change since last time
+    }
 
-  transmission = "";
+    _report.psl = Psl;
+    transmission = "";
 
-  int US_CA = Apt_US_CA(ident);
+    // collect data and create report
+    createReport(apt);
 
-  if (!US_CA) {
-// UK CAA radiotelephony manual indicates ATIS transmissions start
-// with "This is ..." 
-    transmission += This_is + " ";
-  } else {
-    // In the US they just start with the airport name.
-  }
+    // add facility name
+    genFacilityInfo();
 
-  // SG_LOG(SG_ATC, SG_ALERT, "ATIS: facility name: " << name);
+    if (_type == ATIS) {
+        // ATIS phraseology starts with "... airport information"
+        transmission += airport_information + " ";
+    } else {
+        // AWOS
+        transmission += Automated_weather_observation + " ";
+    }
 
-// Note that at this point, multi-word facility names
-// will sometimes contain hyphens, not spaces.
-  
-  vector<string> name_words;
-  boost::split(name_words, name, boost::is_any_of(" -"));
-
-  for (vector<string>::const_iterator wordp = name_words.begin();
-                wordp != name_words.end(); wordp++) {
-    string word(*wordp);
-// Remap some abbreviations that occur in apt.dat, to
-// make things nicer for the text-to-speech system:
-    for (MSS::const_iterator replace = _remap.begin();
-          replace != _remap.end(); replace++) {
-      // Due to inconsistent capitalisation in the apt.dat file, we need
-      // to do a case-insensitive comparison here.
-      string tmp1 = word, tmp2 = replace->first;
-      boost::algorithm::to_lower(tmp1);
-      boost::algorithm::to_lower(tmp2);
-      if (tmp1 == tmp2) {
-        word = replace->second;
-        break;
+    string phonetic_seq_string = GetPhoneticLetter(sequence);  // Add the sequence letter
+    transmission += phonetic_seq_string + BRK;
+
+    genTimeInfo();
+
+    // some warnings may appear at the beginning
+    genWarnings(-1);
+
+    if (_type == ATIS) // as opposed to AWOS
+        genRunwayInfo(apt);
+
+    // some warnings may appear after runway info
+    genWarnings(0);
+
+    // transition level
+    genTransitionLevel(apt);
+
+    // weather
+    if (!_report.concise)
+        transmission += Weather + BRK;
+
+    genWindInfo();
+
+    // clouds and visibility
+    {
+        string vis_info, cloud_info;
+        bool v = genVisibilityInfo(vis_info);
+        bool c = genCloudInfo(cloud_info);
+        _report.cavok = !(v || c);
+        if (!_report.cavok)
+        {
+            // there is some visibility or cloud restriction
+            transmission += vis_info + cloud_info;
+        }
+        else
+        {
+            // Abbreviation CAVOK vs full "clouds and visibility..." does not really depend on
+            // US vs rest of the world, it really seems to depend on the airport. Just use
+            // it as a heuristic.
+            if ((_report.US_CA)||(_report.concise))
+                transmission += cav_ok + BRK;
+            else
+                transmission += clouds_and_visibility_OK + BRK;
+        }
+    }
+
+    // precipitation
+    genPrecipitationInfo();
+
+    // temperature
+    genTemperatureInfo();
+
+    // pressure
+    genPressureInfo();
+
+    // TODO check whether "no significant change" applies - somehow...
+    transmission += No_sig + BRK; // sounds better with festival than "nosig"
+
+    // some warnings may appear at the very end
+    genWarnings(1);
+
+    if ((!_report.concise)|| _report.US_CA)
+        transmission += Advise_on_initial_contact_you_have_information;
+    else
+        transmission += information;
+    transmission += " " + phonetic_seq_string + ".";
+
+    if (!_report.US_CA)
+    {
+        // non-US ATIS ends with "out!"
+        transmission += " " + out;
+    }
+
+    // Pause in between two messages must be 3-5 seconds
+    transmission += " / / / / / / / / ";
+
+    /////////////////////////////////////////////////////////
+    // post-processing
+    /////////////////////////////////////////////////////////
+    transmission_readable = transmission;
+
+    // Take the previous readable string and munge it to
+    // be relatively-more acceptable to the primitive tts system.
+    // Note that : ; and . are among the token-delimiters recognized
+    // by the tts system.
+    for (size_t where;;) {
+        where = transmission.find_first_of(":.");
+        if (where == string::npos) break;
+        transmission.replace(where, 1, PAUSE);
+    }
+
+    return true;
+}
+
+/** Collect (most of) the data and create report.
+ */
+void FGATIS::createReport(const FGAirport* apt)
+{
+    // check country
+    _report.US_CA = Apt_US_CA(ident);
+
+    // switch to enable brief ATIS message (really depends on the airport)
+    _report.concise = fgGetBool("/sim/atis/concise-reports", false);
+
+    _report.ils = false;
+
+    // time information
+    string time_str = fgGetString("sim/time/gmt-string");
+    // Warning - this is fragile if the time string format changes
+    _report.hours = time_str.substr(0,2).c_str();
+    _report.mins  = time_str.substr(3,2).c_str();
+
+    // pressure/temperature
+    {
+        double press, temp;
+        double Tsl = fgGetDouble("/environment/temperature-sea-level-degc");
+        tie(press, temp) = PT_vs_hpt(_geod.getElevationM(), _report.psl*atmodel::inHg, Tsl + atmodel::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
+        _report.qnh = FGAtmo().QNH(_geod.getElevationM(), press);
+        _report.temp = int(SGMiscd::round(FGAtmo().fake_T_vs_a_us(_geod.getElevationFt(), Tsl)));
+    }
+
+    // dew point
+    double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc");
+    _report.dewpoint = int(SGMiscd::round(FGAtmo().fake_dp_vs_a_us(dpsl, _geod.getElevationFt())));
+
+    // precipitation
+    _report.rain_norm = fgGetDouble("environment/rain-norm");
+    _report.snow_norm = fgGetDouble("environment/snow-norm");
+
+    // NOTAMs
+    _report.notam = 0;
+    if (fgGetBool("/sim/atis/random-notams", true))
+    {
+        _report.notam = fgGetInt("/sim/atis/notam-id", 0); // fixed NOTAM for testing/debugging only
+        if (!_report.notam)
+        {
+            // select pseudo-random NOTAM (changes every hour, differs for each airport)
+            char cksum = 0;
+            string name = apt->getName();
+            for(string::iterator p = name.begin(); p != name.end(); p++)
+            {
+                cksum += *p;
+            }
+            cksum ^= atoi(_report.hours.c_str());
+            _report.notam = cksum % 12; // 12 intentionally higher than number of available NOTAMs, so they don't appear too often
+            // debugging
+            //fgSetInt("/sim/atis/selected-notam", _report.notam);
+        }
+    }
+}
+
+void FGATIS::genPrecipitationInfo(void)
+{
+    using namespace lex;
+
+    double rain_norm = _report.rain_norm;
+    double snow_norm = _report.snow_norm;
+
+    // report rain or snow - which ever is worse
+    if (rain_norm > 0.7)
+        transmission += heavy    + " " + rain + BRK;
+    else
+    if (snow_norm > 0.7)
+        transmission += heavy    + " " + snow + BRK;
+    else
+    if (rain_norm > 0.4)
+        transmission += moderate + " " + rain + BRK;
+    else
+    if (snow_norm > 0.4)
+        transmission += moderate + " " + snow + BRK;
+    else
+    if (rain_norm > 0.2)
+        transmission += light    + " " + rain + BRK;
+    else
+    if (snow_norm > 0.05)
+        transmission += light    + " " + snow + BRK;
+    else
+    if (rain_norm > 0.05)
+        transmission += light    + " " + drizzle + BRK;
+}
+
+void FGATIS::genTimeInfo(void)
+{
+    using namespace lex;
+
+    if (!_report.concise)
+        transmission += Time + " ";
+
+    // speak each digit separately:
+    transmission += ConvertNumToSpokenDigits(_report.hours + _report.mins);
+    transmission += " " + zulu + BRK;
+}
+
+bool FGATIS::genVisibilityInfo(string& vis_info)
+{
+    using namespace lex;
+
+    double visibility = fgGetDouble("/environment/config/boundary/entry[0]/visibility-m");
+    bool IsMax = false;
+    bool USE_KM = !_report.US_CA;
+
+    vis_info += Visibility + ": ";
+    if (USE_KM)
+    {
+        visibility /= 1000.0;    // convert to statute miles
+        // integer kilometers
+        if (visibility >= 9.5)
+        {
+            visibility = 10;
+            IsMax = true;
+        }
+        snprintf(buf, sizeof(buf), "%i", int(.5 + visibility));
+        // "kelometers" instead of "kilometers" since the festival language generator doesn't get it right otherwise
+        vis_info += ConvertNumToSpokenDigits(buf) + " " + kelometers;
+    }
+    else
+    {
+        visibility /= atmodel::sm;    // convert to statute miles
+        if (visibility < 0.25) {
+          vis_info += less_than_one_quarter;
+        } else if (visibility < 0.5) {
+          vis_info += one_quarter;
+        } else if (visibility < 0.75) {
+          vis_info += one_half;
+        } else if (visibility < 1.0) {
+          vis_info += three_quarters;
+        } else if (visibility >= 1.5 && visibility < 2.0) {
+          vis_info += one_and_one_half;
+        } else {
+          // integer miles
+          if (visibility > 9.5)
+          {
+              visibility = 10;
+              IsMax = true;
+          }
+          snprintf(buf, sizeof(buf), "%i", int(.5 + visibility));
+          vis_info += ConvertNumToSpokenDigits(buf);
+        }
+    }
+    if (IsMax)
+    {
+        vis_info += " " + or_more;
+    }
+    vis_info += BRK;
+    return !IsMax;
+}
+
+void FGATIS::addTemperature(int Temp)
+{
+    if (Temp < 0)
+        transmission += lex::minus + " ";
+    else
+    if (Temp > 0)
+    {
+        transmission += lex::plus + " ";
+    }
+    snprintf(buf, sizeof(buf), "%i", abs(Temp));
+    transmission += ConvertNumToSpokenDigits(buf);
+    if (_report.US_CA)
+        transmission += " " + lex::Celsius;
+}
+
+void FGATIS::genTemperatureInfo()
+{
+    // temperature
+    transmission += lex::Temperature + ": ";
+    addTemperature(_report.temp);
+
+    // dewpoint
+    transmission += BRK + lex::Dewpoint + ": ";
+    addTemperature(_report.dewpoint);
+
+    transmission += BRK;
+}
+
+bool FGATIS::genCloudInfo(string& cloud_info)
+{
+    using namespace lex;
+
+    bool did_some = false;
+    bool did_ceiling = false;
+
+    for (int layer = 0; layer <= 4; layer++) {
+      snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/coverage", layer);
+      string coverage = fgGetString(buf);
+      if (coverage == clear)
+          continue;
+      snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/thickness-ft", layer);
+      if (fgGetDouble(buf) == 0)
+          continue;
+      snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/elevation-ft", layer);
+      double ceiling = int(fgGetDouble(buf) - _geod.getElevationFt());
+      if (ceiling > 12000)
+          continue;
+
+  // BEWARE:  At the present time, the environment system has no
+  // way (so far as I know) to represent a "thin broken" or
+  // "thin overcast" layer.  If/when such things are implemented
+  // in the environment system, code will have to be written here
+  // to handle them.
+
+      // First, do the prefix if any:
+      if (coverage == scattered || coverage == few) {
+        if (!did_some) {
+          if (_report.concise)
+              cloud_info += Clouds + ": ";
+          else
+              cloud_info += Sky_condition + ": ";
+          did_some = true;
+        }
+      } else /* must be a ceiling */  if (!did_ceiling) {
+        cloud_info += "   " + Ceiling + ": ";
+        did_ceiling = true;
+        did_some = true;
+      } else {
+        cloud_info += "   ";    // no prefix required
       }
+      int cig00  = int(SGMiscd::round(ceiling/100));  // hundreds of feet
+      if (cig00) {
+        int cig000 = cig00/10;
+        cig00 -= cig000*10;       // just the hundreds digit
+        if (cig000) {
+          snprintf(buf, sizeof(buf), "%i", cig000);
+          cloud_info += ConvertNumToSpokenDigits(buf);
+          cloud_info += " " + thousand + " ";
+        }
+        if (cig00) {
+          snprintf(buf, sizeof(buf), "%i", cig00);
+          cloud_info += ConvertNumToSpokenDigits(buf);
+          cloud_info += " " + hundred + " ";
+        }
+      } else {
+        // Should this be "sky obscured?"
+        cloud_info += " " + zero + " ";     // not "zero hundred"
+      }
+      cloud_info += coverage + BRK;
     }
-    transmission += word + " ";
-  }
+    if (!did_some)
+        cloud_info += "   " + Sky + " " + clear + BRK;
+    return did_some;
+}
 
-  if (_type == ATIS /* as opposed to AWOS */) {
-    transmission += airport_information + " ";
-  } else {
-    transmission += Automated_weather_observation + " ";
-  }
+void FGATIS::genFacilityInfo(void)
+{
+    if ((!_report.US_CA)&&(!_report.concise))
+    {
+        // UK CAA radiotelephony manual indicates ATIS transmissions start
+        // with "This is ...", while US just starts with airport name.
+        transmission += lex::This_is + " ";
+    }
 
-  phonetic_seq_string = GetPhoneticLetter(sequence);  // Add the sequence letter
-  transmission += phonetic_seq_string + BRK;
-
-// 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*SGMiscd::round(wind_dir/5));
-      transmission += ConvertNumToSpokenDigits(buf);
+    // SG_LOG(SG_ATC, SG_ALERT, "ATIS: facility name: " << name);
 
-      snprintf(buf, bs, "%1.0f", wind_speed);
-      transmission += " " + at + " " + ConvertNumToSpokenDigits(buf) + BRK;
-  }
+    // Note that at this point, multi-word facility names
+    // will sometimes contain hyphens, not spaces.
+    vector<string> name_words;
+    boost::split(name_words, name, boost::is_any_of(" -"));
 
-// Sounds better with a pause in there:
-  transmission += PAUSE;
-
-  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;
-
-// BEWARE:  At the present time, the environment system has no
-// way (so far as I know) to represent a "thin broken" or
-// "thin overcast" layer.  If/when such things are implemented
-// in the environment system, code will have to be written here
-// to handle them.
-
-// First, do the prefix if any:
-    if (coverage == scattered || coverage == few) {
-      if (!did_some) {
-        transmission += "   " + Sky_condition + ": ";
-        did_some++;
+    for (vector<string>::const_iterator wordp = name_words.begin();
+                  wordp != name_words.end(); wordp++) {
+      string word(*wordp);
+    // Remap some abbreviations that occur in apt.dat, to
+    // make things nicer for the text-to-speech system:
+      for (MSS::const_iterator replace = _remap.begin();
+            replace != _remap.end(); replace++) {
+        // Due to inconsistent capitalisation in the apt.dat file, we need
+        // to do a case-insensitive comparison here.
+        string tmp1 = word, tmp2 = replace->first;
+        boost::algorithm::to_lower(tmp1);
+        boost::algorithm::to_lower(tmp2);
+        if (tmp1 == tmp2) {
+          word = replace->second;
+          break;
+        }
       }
-    } else /* must be a ceiling */  if (!did_ceiling) {
-      transmission += "   " + Ceiling + ": ";
-      did_ceiling++;
-      did_some++;
+      transmission += word + " ";
+    }
+}
+
+void FGATIS::genWindInfo(void)
+{
+    using namespace lex;
+
+    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 {
-      transmission += "   ";    // no prefix required
-    }
-    int cig00  = int(SGMiscd::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);
+        // FIXME: get gust factor in somehow
+        snprintf(buf, sizeof(buf), "%03.0f", 5*SGMiscd::round(wind_dir/5));
         transmission += ConvertNumToSpokenDigits(buf);
-        transmission += " " + thousand + " ";
-      }
-      if (cig00) {
-        snprintf(buf, bs, "%i", cig00);
+        if (!_report.concise)
+            transmission += " " + degrees;
+        transmission += " ";
+        snprintf(buf, sizeof(buf), "%1.0f", wind_speed);
+        transmission += at + " " + ConvertNumToSpokenDigits(buf);
+        if (!_report.concise)
+            transmission += " " + knots;
+    }
+    transmission += BRK;
+}
+
+void FGATIS::genTransitionLevel(const FGAirport* apt)
+{
+    double hPa = _report.qnh/atmodel::mbar;
+
+    /* Transition level is the flight level above which aircraft must use standard pressure and below
+     * which airport pressure settings must be used.
+     * Following definitions are taken from German ATIS:
+     *      QNH <=  977 hPa: TRL 80
+     *      QNH <= 1013 hPa: TRL 70
+     *      QNH >  1013 hPa: TRL 60
+     * (maybe differs slightly for other countries...)
+     */
+    int tl = 60;
+    if (hPa <= 977)
+        tl = 80;
+    else
+    if (hPa <= 1013)
+        tl = 70;
+
+    // add an offset to the transition level for high altitude airports (just guessing here,
+    // seems reasonable)
+    double elevationFt = apt->getElevation();
+    int e = int(elevationFt / 1000.0);
+    if (e >= 3)
+    {
+        // TL steps in 10(00)ft
+        tl += (e-2)*10;
+    }
+
+    snprintf(buf, sizeof(buf), "%02i", tl);
+    transmission += lex::Transition_level + ": " + ConvertNumToSpokenDigits(buf) + BRK;
+}
+
+void FGATIS::genPressureInfo(void)
+{
+    using namespace lex;
+
+    // hectopascal for most of the world (not US, not CA)
+    if(!_report.US_CA) {
+        double hPa = _report.qnh/atmodel::mbar;
+        transmission += QNH + ": ";
+        snprintf(buf, sizeof(buf), "%03.0f", _report.qnh / atmodel::mbar);
         transmission += ConvertNumToSpokenDigits(buf);
-        transmission += " " + hundred + " ";
-      }
+        // "hectopascal" replaced "millibars" in new ATIS standard since 2011
+        if  ((!_report.concise)||(hPa < 1000))
+            transmission += " " + hectopascal; // "hectopascal" must be provided for values below 1000 (to avoid confusion with inHg)
+
+        // Many (European) airports (with lots of US traffic?) provide both, hPa and inHg announcements.
+        // Europeans keep the "decimal" in inHg readings to make the distinction to hPa even more apparent.
+        // hPa/inHg separated by "equals" or "or" with some airports
+        if (_report.concise)
+            transmission += " " + equals + " ";
+        else
+            transmission += " " + Or + " ";
+        snprintf(buf, sizeof(buf), "%04.2f", _report.qnh / atmodel::inHg);
+        transmission += ConvertNumToSpokenDigits(buf);
+        if (!_report.concise)
+            transmission += " " + inches;
+        transmission += BRK;
     } else {
-      // Should this be "sky obscured?"
-      transmission += " " + zero + " ";     // not "zero hundred"
+        // use inches of mercury for US/CA
+        transmission += Altimeter + ": ";
+        double asetting = _report.qnh / atmodel::inHg;
+        // shift two decimal places, US/CA airports omit the "decimal" in inHg settings
+        asetting *= 100.;
+        snprintf(buf, sizeof(buf), "%04.0f", asetting);
+        transmission += ConvertNumToSpokenDigits(buf);
     }
-    transmission += coverage + BRK;
-  }
-  if (!did_some) transmission += "   " + Sky + " " + clear + BRK;
 
-  transmission += Temperature + ": ";
-  double Tsl = fgGetDouble("/environment/temperature-sea-level-degc");
-  int temp = int(SGMiscd::round(FGAtmo().fake_T_vs_a_us(_geod.getElevationFt(), Tsl)));
-  if(temp < 0) {
-      transmission += lex::minus + " ";
-  }
-  snprintf(buf, bs, "%i", abs(temp));
-  transmission += ConvertNumToSpokenDigits(buf);
-  if (US_CA) transmission += " " + Celsius;
-  transmission += " " + dewpoint + " ";
-  double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc");
-  temp = int(SGMiscd::round(FGAtmo().fake_dp_vs_a_us(dpsl, _geod.getElevationFt())));
-  if(temp < 0) {
-      transmission += lex::minus + " ";
-  }
-  snprintf(buf, bs, "%i", abs(temp));
-  transmission += ConvertNumToSpokenDigits(buf);
-  if (US_CA) transmission += " " + Celsius;
-  transmission += 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 += BRK;
+}
 
-  double myQNH;
-  double Psl = fgGetDouble("/environment/pressure-sea-level-inhg");
-  {
-    double press, temp;
-    
-    tie(press, 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);
-  }
+void FGATIS::genRunwayInfo(const FGAirport* apt)
+{
+    using namespace lex;
 
-// Convert to millibars for most of the world (not US, not CA)
-  if((!US_CA) && fgGetBool("/sim/atc/use-millibars")) {
-    transmission += QNH + ": ";
-    myQNH /= mbar;
-    if  (myQNH > 1000) myQNH -= 1000;       // drop high digit
-    snprintf(buf, bs, "%03.0f", myQNH);
-    transmission += ConvertNumToSpokenDigits(buf) + " " + millibars + BRK;
-  } else {
-    transmission += Altimeter + ": ";
-    double asetting = myQNH / inHg;         // use inches of mercury
-    asetting *= 100.;                       // shift two decimal places
-    snprintf(buf, bs, "%04.0f", asetting);
-    transmission += ConvertNumToSpokenDigits(buf) + BRK;
-  }
+    if (!apt)
+        return;
+
+    FGRunway* rwy = apt->getActiveRunwayForUsage();
+    if (!rwy)
+        return;
+
+    string rwy_no = rwy->ident();
+    if(rwy_no != "NN")
+    {
+        FGNavRecord* ils = rwy->ILS();
+        if (ils)
+        {
+            _report.ils = true;
+            transmission += Expect_I_L_S_approach + " "+ runway + " "+ConvertRwyNumToSpokenString(rwy_no) + BRK;
+            if (fgGetBool("/sim/atis/announce-ils-frequency", false))
+            {
+                // this is handy - but really non-standard (so disabled by default)
+                snprintf(buf, sizeof(buf), "%5.2f", ils->get_freq()/100.0);
+                transmission += I_L_S + " " + ConvertNumToSpokenDigits(buf) + BRK;
+            }
+        }
+        else
+        {
+            transmission += Expect_visual_approach + " "+ runway + " "+ConvertRwyNumToSpokenString(rwy_no) + BRK;
+        }
 
-  if (_type == ATIS /* as opposed to AWOS */) {
-    const FGAirport* apt = fgFindAirportID(ident);
-    if (apt) {
-      string rwy_no = apt->getActiveRunwayForUsage()->ident();
-      if(rwy_no != "NN") {
         transmission += Landing_and_departing_runway + " ";
         transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK;
-#ifdef ATIS_TEST
+    #ifdef ATIS_TEST
         if (msg_OK) {
           msg_time = cur_time;
           cout << "In atis.cxx, r.rwy_no: " << rwy_no
              << " wind_dir: " << wind_dir << endl;
         }
-#endif
-      }
+    #endif
+    }
+}
+
+void FGATIS::genWarnings(int position)
+{
+    using namespace lex;
+    bool dayTime = (fgGetDouble("/sim/time/sun-angle-rad") < 1.57);
+
+    if (position == -1) // warnings at beginning of ATIS
+    {
+        // bird related warnings at day-time only (birds are VFR-only! ;-) )
+        if (dayTime)
+        {
+            if (_report.notam == 1)
+                transmission += Attention + ": " + flock_of_birds + " " + in_the_vicinity_of_the_airport + BRK;
+            else
+            if (_report.notam == 2)
+                transmission += Attention + ": " + bird_activity + " " + in_the_vicinity_of_the_airport + BRK;
+        }
+    }
+    else
+    if (position == 0)  // warnings after runway messages
+    {
+        if ((_report.notam == 3)&&(_report.ils))
+        {
+            // "__I_LS_" necessary to trick the language generator into pronouncing it properly
+            transmission += Attention + ": " + short_time__I_LS_interference_possible_by_taxiing_aircraft + BRK;
+        }
+    }
+    else
+    if (position == 1) // warnings at the end of the report
+    {
+        // "runway wet-wet-wet" warning in heavy rain
+        if (_report.rain_norm > 0.6)
+        {
+            // "wet" is repeated 3 times in ATIS warnings, since the word is difficult
+            // to understand over radio - but the message is important.
+            transmission += runway_wet + " " + wet + " " + wet + BRK;
+        }
+
+        if (_report.notam == 4)
+        {
+            // intentional: "reed" instead of "read" since festival gets it wrong otherwise
+            transmission += reed_back_all_runway_hold_instructions + BRK;
+        }
+        else
+        if ((_report.notam == 5)&& _report.cavok && dayTime &&
+            (_report.rain_norm == 0) && (_report.snow_norm == 0)) // ;-)
+        {
+            transmission += Attention + ": " + glider_operation_in_sector + BRK;
+        }
     }
-    transmission += On_initial_contact_advise_you_have_information + " ";
-    transmission += phonetic_seq_string;
-    transmission += "... " + BRK + PAUSE + PAUSE;
-  }
-  transmission_readable = transmission;
-// Take the previous readable 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 (size_t where;;) {
-    where = transmission.find_first_of(":.");
-    if (where == string::npos) break;
-    transmission.replace(where, 1, PAUSE);
-  }
-  return 1;
 }
 
 // Put the transmission into the property tree.
@@ -522,10 +950,89 @@ int FGATIS::GenTransmission(const int regen, const int special) {
 // http://localhost:5400/instrumentation/comm[1]
 //
 // (Also, if in debug mode, dump it to the console.)
-void FGATIS::TreeOut(int msg_OK){
-    string prop = _commbase + "/atis";
-    globals->get_props()->setStringValue(prop.c_str(),
-      ("<pre>\n" + transmission_readable + "</pre>\n").c_str());
-    SG_LOG(SG_ATC, SG_DEBUG, "**** ATIS active on: " << prop <<
+void FGATIS::treeOut(int msg_OK)
+{
+    _atis->setStringValue("<pre>\n" + transmission_readable + "</pre>\n");
+    SG_LOG(SG_ATC, SG_DEBUG, "**** ATIS active on: " << _name <<
            "transmission: " << transmission_readable);
 }
+
+
+class RangeFilter : public CommStation::Filter
+{
+public:
+    RangeFilter( const SGGeod & pos ) :
+      CommStation::Filter(),
+      _cart(SGVec3d::fromGeod(pos)),
+      _pos(pos)
+    {
+    }
+
+    virtual bool pass(FGPositioned* aPos) const
+    {
+        flightgear::CommStation * stn = dynamic_cast<flightgear::CommStation*>(aPos);
+        if( NULL == stn )
+            return false;
+
+        // do the range check in cartesian space, since the distances are potentially
+        // large enough that the geodetic functions become unstable
+        // (eg, station on opposite side of the planet)
+        double rangeM = SGMiscd::max( stn->rangeNm(), 10.0 ) * SG_NM_TO_METER;
+        double d2 = distSqr( aPos->cart(), _cart);
+
+        return d2 <= (rangeM * rangeM);
+    }
+
+    virtual CommStation::Type minType() const
+    {
+      return CommStation::FREQ_ATIS;
+    }
+
+    virtual CommStation::Type maxType() const
+    {
+      return CommStation::FREQ_AWOS;
+    }
+
+private:
+    SGVec3d _cart;
+    SGGeod _pos;
+};
+
+// Search for ATC stations by frequency
+bool FGATIS::search(double dt)
+{
+    double frequency = _freq->getDoubleValue();
+
+    // Note:  122.375 must be rounded DOWN to 122370
+    // in order to be consistent with apt.dat et cetera.
+    int freqKhz = 10 * static_cast<int>(frequency * 100 + 0.25);
+
+    // only search tuned frequencies when necessary
+    _time_before_search_sec -= dt;
+
+    // throttle frequency searches
+    if ((freqKhz == _last_frequency)&&(_time_before_search_sec > 0))
+        return false;
+
+    _last_frequency = freqKhz;
+    _time_before_search_sec = 4.0;
+
+    // Position of the Users Aircraft
+    SGGeod aircraftPos = SGGeod::fromDegFt(_lon_node->getDoubleValue(),
+                                           _lat_node->getDoubleValue(),
+                                           _elev_node->getDoubleValue());
+
+    RangeFilter rangeFilter(aircraftPos );
+    CommStation* sta = CommStation::findByFreq(freqKhz, aircraftPos, &rangeFilter );
+    SetStation(sta);
+    if (sta && sta->airport())
+    {
+        SG_LOG(SG_ATC, SG_DEBUG, "FGATIS " << _name << ": " << sta->airport()->name());
+    }
+    else
+    {
+        SG_LOG(SG_ATC, SG_DEBUG, "FGATIS " << _name << ": no station.");
+    }
+
+    return true;
+}