]> git.mxchange.org Git - flightgear.git/blobdiff - src/ATCDCL/atis.cxx
#591: night-time rendering issues, avoid negative color values
[flightgear.git] / src / ATCDCL / atis.cxx
index 0747903de5b49ca9004bdf3514ec52dcbb11d196..b87369e1a01b7c41c6a91c96cc9a89d801e34ff0 100644 (file)
 #endif
 
 #include "atis.hxx"
+#include "atis_lexicon.hxx"
 
 #include <simgear/compiler.h>
+#include <simgear/math/sg_random.h>
+#include <simgear/misc/sg_path.hxx>
 
 #include <stdlib.h> // atoi()
 #include <stdio.h>  // sprintf
 #include <string>
 #include <iostream>
 
-
 #include <boost/tuple/tuple.hpp>
-
-#include <simgear/misc/sg_path.hxx>
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/case_conv.hpp>
 
 #include <Environment/environment_mgr.hxx>
 #include <Environment/environment.hxx>
 #include <Main/fg_props.hxx>
 #include <Main/globals.hxx>
 #include <Airports/runways.hxx>
+#include <Airports/dynamics.hxx>
 
 
-#include "commlist.hxx"
 #include "ATCutils.hxx"
 #include "ATCmgr.hxx"
 
+using std::string;
+using std::map;
 using std::cout;
 using std::cout;
 using boost::ref;
 using boost::tie;
 
 FGATIS::FGATIS() :
+  transmission(""),
+  trans_ident(""),
   old_volume(0),
   atis_failed(false),
   msg_OK(0),
@@ -70,12 +76,36 @@ FGATIS::FGATIS() :
   _prev_display(0),
   refname("atis")
 {
-  _vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS);
+  FGATCMgr* pAtcMgr = globals->get_ATC_mgr();
+  if (!pAtcMgr)
+  {
+      SG_LOG(SG_ATC, SG_ALERT, "ERROR! No ATC manager! Oops...");
+      _vPtr = NULL;
+  }
+  else
+      _vPtr = pAtcMgr->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);
+
+///////////////
+// FIXME:  This would be more flexible and more extensible
+// if the mappings were taken from an XML file, not hard-coded ...
+// ... although having it in a .hxx file is better than nothing.
+//
+// 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
 }
 
 // Hint:
@@ -107,7 +137,7 @@ FGATIS::attend (int attn)
 void FGATIS::Update(double dt) {
   cur_time = globals->get_time_params()->get_cur_time();
   msg_OK = (msg_time < cur_time);
-#if 0
+#ifdef ATIS_TEST
   if (msg_OK || _display != _prev_display) {
     cout << "ATIS Update: " << _display << "  " << _prev_display
       << "  len: " << transmission.length()
@@ -129,6 +159,7 @@ void FGATIS::Update(double dt) {
 // 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);
+    TreeOut(msg_OK);
     if (rslt || volume != old_volume) {
       //cout << "ATIS calling ATC::render  volume: " << volume << endl;
       Render(transmission, volume, refname, true);
@@ -185,19 +216,45 @@ const int minute(60);             // measured in seconds
   const int ATIS_interval(60*minute);
 #endif
 
+// FIXME:  This is heuristic.  It gets the right answer for
+// more than 90% of the world's airports, which is a lot
+// better than nothing ... but it's not 100%.
+// We know "most" of the world uses millibars,
+// but the US, Canada and *some* other places use inches of mercury,
+// but (a) we have not implemented a reliable method of
+// 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;
+}
+
 // 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;
 
   string BRK = ".\n";
+  string PAUSE = " / ";
+
+  int interval = _type == ATIS ?
+        ATIS_interval   // ATIS updated hourly
+      : 2*minute;      // AWOS updated more frequently
 
-  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);
+  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;
@@ -212,53 +269,61 @@ int FGATIS::GenTransmission(const int regen, const int special) {
 
   transmission = "";
 
-// UK CAA radiotelephony manual indicated ATIS transmissions start
+  int US_CA = Apt_US_CA(ident);
+
+  if (!US_CA) {
+// UK CAA radiotelephony manual indicates ATIS transmissions start
 // with "This is ..." 
-// In the US they just start with the airport name.
-// transmission += "This_is ";
+    transmission += This_is + " ";
+  } else {
+    // In the US they just start with the airport name.
+  }
 
   // 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.
-///////////////
+  
+  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:
-  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 + " ";
+    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;
+      }
+    }
+    transmission += word + " ";
+  }
+
   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 += airport_information + " ";
+  } else {
+    transmission += Automated_weather_observation + " ";
   }
-  transmission += "Automated_weather_observation ";
+
+  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 += " " + zulu + " " + weather + BRK;
 
-  transmission += "Wind: ";
+  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");
@@ -281,90 +346,106 @@ int FGATIS::GenTransmission(const int regen, const int special) {
 // 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";
+      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);
 
       snprintf(buf, bs, "%1.0f", wind_speed);
-      transmission += " at " + ConvertNumToSpokenDigits(buf) + BRK;
+      transmission += " " + at + " " + ConvertNumToSpokenDigits(buf) + BRK;
   }
 
+// 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;
+    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++;
+
+// 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++;
+      }
     } else /* must be a ceiling */  if (!did_ceiling) {
-      transmission += "   Ceiling: ";
+      transmission += "   " + Ceiling + ": ";
       did_ceiling++;
       did_some++;
     } else {
-      transmission += "   ";
+      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);
-    transmission += ConvertNumToSpokenDigits(buf);
-    transmission += " thousand ";
+        snprintf(buf, bs, "%i", cig000);
+        transmission += ConvertNumToSpokenDigits(buf);
+        transmission += " " + thousand + " ";
       }
       if (cig00) {
-    snprintf(buf, bs, "%i", cig00);
-    transmission += ConvertNumToSpokenDigits(buf);
-    transmission += " hundred ";
+        snprintf(buf, bs, "%i", cig00);
+        transmission += ConvertNumToSpokenDigits(buf);
+        transmission += " " + hundred + " ";
       }
     } else {
       // Should this be "sky obscured?"
-      transmission += " zero ";     // not "zero hundred"
+      transmission += " " + zero + " ";     // not "zero hundred"
     }
     transmission += coverage + BRK;
   }
+  if (!did_some) transmission += "   " + Sky + " " + clear + BRK;
 
-  transmission += "Temperature: ";
+  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 += "minus ";
+      transmission += lex::minus + " ";
   }
   snprintf(buf, bs, "%i", abs(temp));
   transmission += ConvertNumToSpokenDigits(buf);
-  transmission += " dewpoint ";
+  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 += "minus ";
+      transmission += lex::minus + " ";
   }
   snprintf(buf, bs, "%i", abs(temp));
-  transmission += ConvertNumToSpokenDigits(buf) + BRK;
-
+  transmission += ConvertNumToSpokenDigits(buf);
+  if (US_CA) transmission += " " + Celsius;
+  transmission += BRK;
 
-  transmission += "Visibility: ";
+  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";
+    transmission += less_than_one_quarter;
   } else if (visibility < 0.5) {
-    transmission += "one quarter";
+    transmission += one_quarter;
   } else if (visibility < 0.75) {
-    transmission += "one half";
+    transmission += one_half;
   } else if (visibility < 1.0) {
-    transmission += "three quarters";
+    transmission += three_quarters;
   } else if (visibility >= 1.5 && visibility < 2.0) {
-    transmission += "one and one half";
+    transmission += one_and_one_half;
   } else {
     // integer miles
     if (visibility > 10) visibility = 10;
@@ -373,7 +454,6 @@ int FGATIS::GenTransmission(const int regen, const int special) {
   }
   transmission += BRK;
 
-  transmission += "Altimeter: ";
   double myQNH;
   double Psl = fgGetDouble("/environment/pressure-sea-level-inhg");
   {
@@ -388,63 +468,75 @@ int FGATIS::GenTransmission(const int regen, const int special) {
 #endif
     myQNH = FGAtmo().QNH(_geod.getElevationM(), press);
   }
-  if(ident.substr(0,2) == "EG" && fgGetBool("/sim/atc/use-millibars")) {
-    // Convert to millibars for the UK!
+
+// 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 {
-    myQNH /= inHg;
-    myQNH *= 100.;                        // shift two decimal places
-    snprintf(buf, bs, "%04.0f", myQNH);
+    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;
   }
-  transmission += ConvertNumToSpokenDigits(buf) + BRK;
 
   if (_type == ATIS /* as opposed to AWOS */) {
-      const FGAirport* apt = fgFindAirportID(ident);
-      assert(apt);
-        string rwy_no = apt->getActiveRunwayForUsage()->ident();
+    const FGAirport* apt = fgFindAirportID(ident);
+    if (apt) {
+      string rwy_no = apt->getActiveRunwayForUsage()->ident();
       if(rwy_no != "NN") {
-        transmission += "Landing_and_departing_runway ";
+        transmission += Landing_and_departing_runway + " ";
         transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK;
+#ifdef ATIS_TEST
         if (msg_OK) {
           msg_time = cur_time;
-          //cout << "In atis.cxx, r.rwy_no: " << rwy_no
-          //   << " wind_dir: " << wind_dir << endl;
+          cout << "In atis.cxx, r.rwy_no: " << rwy_no
+             << " wind_dir: " << wind_dir << endl;
         }
+#endif
+      }
     }
-    transmission += "On_initial_contact_advise_you_have_information ";
+    transmission += On_initial_contact_advise_you_have_information + " ";
     transmission += phonetic_seq_string;
-    transmission += "... " + BRK;
+    transmission += "... " + BRK + PAUSE + PAUSE;
   }
-#ifdef ATIS_TEST
-  cout << "**** ATIS active on:";
-#endif
-  for (map<string,int>::iterator act = active_on.begin(); act != active_on.end(); act++){
-    string prop = "/instrumentation/" + act->first + "/atis";
-    globals->get_props()->setStringValue(prop.c_str(),
-      ("<pre>\n" + transmission + "</pre>\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
+  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, " /_ ");
+    transmission.replace(where, 1, PAUSE);
   }
   return 1;
 }
+
+// Put the transmission into the property tree,
+// possibly in multiple places if multiple radios
+// are tuned to the same ATIS.
+// You can see it by pointing a web browser
+// at the property tree.  The second comm radio is:
+// http://localhost:5400/instrumentation/comm[1]
+//
+// (Also, if in debug mode, dump it to the console.)
+void FGATIS::TreeOut(int msg_OK){
+  for (map<string,int>::iterator act = active_on.begin();
+                act != active_on.end();
+                act++){
+    string prop = "/instrumentation/" + act->first + "/atis";
+    globals->get_props()->setStringValue(prop.c_str(),
+      ("<pre>\n" + transmission_readable + "</pre>\n").c_str());
+#ifdef ATIS_TEST
+    if (msg_OK) cout << "**** ATIS active on: " << prop << endl;
+#endif
+  }
+#ifdef ATIS_TEST
+  if (msg_OK) cout << transmission_readable << endl;
+#endif
+}