]> git.mxchange.org Git - flightgear.git/blob - src/ATCDCL/atis.cxx
Merge branch 'tat/framework'
[flightgear.git] / src / ATCDCL / atis.cxx
1 // atis.cxx - routines to generate the ATIS info string
2 // This is the implementation of the FGATIS class
3 //
4 // Written by David Luff, started October 2001.
5 //
6 // Copyright (C) 2001  David C Luff - david.luff@nottingham.ac.uk
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21
22 /////
23 ///// TODO:  _Cumulative_ sky coverage.
24 ///// TODO:  wind _gust_
25 ///// TODO:  more-sensible encoding of voice samples
26 /////       u-law?  outright synthesis?
27 /////
28
29 #ifdef HAVE_CONFIG_H
30 #  include <config.h>
31 #endif
32
33 #include "atis.hxx"
34
35 #include <simgear/compiler.h>
36
37 #include <stdlib.h> // atoi()
38 #include <stdio.h>  // sprintf
39 #include <string>
40 #include <iostream>
41
42
43 #include <boost/tuple/tuple.hpp>
44
45 #include <simgear/misc/sg_path.hxx>
46
47 #include <Environment/environment_mgr.hxx>
48 #include <Environment/environment.hxx>
49 #include <Environment/atmosphere.hxx>
50
51 #include <Main/fg_props.hxx>
52 #include <Main/globals.hxx>
53 #include <Airports/runways.hxx>
54
55
56 #include "commlist.hxx"
57 #include "ATCutils.hxx"
58 #include "ATCmgr.hxx"
59
60 using std::cout;
61 using std::cout;
62 using boost::ref;
63 using boost::tie;
64
65 FGATIS::FGATIS() :
66   transmission(""),
67   trans_ident(""),
68   old_volume(0),
69   atis_failed(false),
70   attention(0),
71   _prev_display(0),
72   refname("atis")
73 {
74   _vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS);
75   _voiceOK = (_vPtr == NULL ? false : true);
76   if (!(_type != ATIS || _type == AWOS)) {
77        SG_LOG(SG_ATC, SG_ALERT, "ERROR - _type not ATIS or AWOS in atis.cxx");
78   }
79   fgTie("/environment/attention", this, (int_getter)0, &FGATIS::attend);
80 }
81
82 // Hint:
83 // http://localhost:5400/environment/attention?value=1&submit=update
84
85 FGATIS::~FGATIS() {
86   fgUntie("/environment/attention");
87 }
88
89 void FGATIS::Init() {
90 // Nothing to see here.  Move along.
91 }
92
93 void
94 FGATIS::attend (int attn)
95 {
96   attention = attn;
97 #ifdef ATMO_TEST
98   int flag = fgGetInt("/sim/logging/atmo");
99   if (flag) {
100     FGAltimeter().check_model();
101         FGAltimeter().dump_stack();
102   }
103 #endif
104 }
105
106
107 // Main update function - checks whether we are displaying or not the correct message.
108 void FGATIS::Update(double dt) {
109   cur_time = globals->get_time_params()->get_cur_time();
110   msg_OK = (msg_time < cur_time);
111 #if 0
112   if (msg_OK || _display != _prev_display) {
113     cout << "ATIS Update: " << _display << "  " << _prev_display
114       << "  len: " << transmission.length()
115       << "  oldvol: " << old_volume
116       << "  dt: " << dt << endl;
117     msg_time = cur_time;
118   }
119 #endif
120   if(_display) {
121     double volume(0);
122     for (map<string,int>::iterator act = active_on.begin();
123     act != active_on.end(); act++) {
124       string prop = "/instrumentation/" + act->first + "/volume";
125       volume += globals->get_props()->getDoubleValue(prop.c_str());
126     }
127
128 // Check if we need to update the message
129 // - basically every hour and if the weather changes significantly at the station
130 // If !_prev_display, the radio had been detuned for a while and our
131 // "transmission" variable was lost when we were de-instantiated.
132     int rslt = GenTransmission(!_prev_display, attention);
133     if (rslt || volume != old_volume) {
134       //cout << "ATIS calling ATC::render  volume: " << volume << endl;
135       Render(transmission, volume, refname, true);
136       old_volume = volume;
137     }
138   } else {
139 // We shouldn't be displaying
140     //cout << "ATIS.CXX - calling NoRender()..." << endl;
141     NoRender(refname);
142   }
143   _prev_display = _display;
144   attention = 0;
145 }
146
147 string uppercase(const string &s) {
148   string rslt(s);
149   for(string::iterator p = rslt.begin(); p != rslt.end(); p++){
150     *p = toupper(*p);
151   }
152   return rslt;
153 }
154
155 // Replace all occurrences of a given word.
156 // Words in the original string must be separated by hyphens (not spaces).
157 // We check for the word as given, and for the all-caps version thereof.
158 string replace_word(const string _orig, const string _www, const string _nnn){
159 // The following are so we can match words at the beginning
160 // and end of the string.
161   string orig = "-" + _orig + "-";
162   string www = "-" + _www + "-";
163   string nnn = "-" + _nnn + "-";
164
165   size_t where(0);
166   for ( ; (where = orig.find(www, where)) != string::npos ; ) {
167     orig.replace(where, www.length(), nnn);
168     where += nnn.length();
169   }
170   
171   www = uppercase(www);
172   for ( ; (where = orig.find(www, where)) != string::npos ; ) {
173     orig.replace(where, www.length(), nnn);
174     where += nnn.length();
175   }
176   where = orig.length();
177   return orig.substr(1, where-2);
178 }
179
180 // Normally the interval is 1 hour, 
181 // but you can shorten it for testing.
182 const int minute(60);           // measured in seconds
183 #ifdef ATIS_TEST
184   const int ATIS_interval(2*minute);
185 #else
186   const int ATIS_interval(60*minute);
187 #endif
188
189 // Generate the actual broadcast ATIS transmission.
190 // Regen means regenerate the /current/ transmission.
191 // Special means generate a new transmission, with a new sequence.
192 // Returns 1 if we actually generated something.
193 int FGATIS::GenTransmission(const int regen, const int special) {
194   using namespace atmodel;
195
196   string BRK = ".\n";
197
198   double tstamp = atof(fgGetString("sim/time/elapsed-sec"));
199   int interval = ATIS ? ATIS_interval : 2*minute;       // AWOS updated frequently
200   int sequence = current_commlist->GetAtisSequence(ident, 
201                         tstamp, interval, special);
202   if (!regen && sequence > LTRS) {
203 //xx      if (msg_OK) cout << "ATIS:  no change: " << sequence << endl;
204 //xx      msg_time = cur_time;
205     return 0;   // no change since last time
206   }
207
208   const int bs(100);
209   char buf[bs];
210   string time_str = fgGetString("sim/time/gmt-string");
211   string hours, mins;
212   string phonetic_seq_string;
213
214   transmission = "";
215
216 // UK CAA radiotelephony manual indicated ATIS transmissions start
217 // with "This is ..." 
218 // In the US they just start with the airport name.
219 // transmission += "This_is ";
220
221   // SG_LOG(SG_ATC, SG_ALERT, "ATIS: facility name: " << name);
222
223 // Note that at this point, multi-word facility names
224 // will sometimes contain hyphens, not spaces.
225 // Force the issue, just to be safe:
226
227   string name2 = name;
228   for(string::iterator p = name2.begin(); p != name2.end(); p++){
229     if (*p == ' ') *p = '-';
230   }
231
232 ///////////////
233 // FIXME:  This would be more flexible and more extensible
234 // if the mappings were taken from an XML file, not hard-coded.
235 ///////////////
236
237 // Remap some abbreviations that occur in apt.dat, to
238 // make things nicer for the text-to-speech system:
239   name2 = replace_word(name2, "Intl",  "International");
240   name2 = replace_word(name2, "Rgnl",  "Regional");
241   name2 = replace_word(name2, "Co",    "County");
242   name2 = replace_word(name2, "Muni",  "Municipal");
243   name2 = replace_word(name2, "Mem",   "Memorial");
244   name2 = replace_word(name2, "Fld",   "Field");
245   name2 = replace_word(name2, "AFB",   "Air-Force-Base");
246   name2 = replace_word(name2, "AAF",   "Army-Air-Field");
247   name2 = replace_word(name2, "MCAS",  "Marine-Corps-Air-Station");
248   transmission += name2 + " ";
249   if (_type == ATIS /* as opposed to AWOS */) {
250     transmission += "airport_information ";
251     phonetic_seq_string = GetPhoneticLetter(sequence);  // Add the sequence letter
252     transmission += phonetic_seq_string + BRK;
253   }
254   transmission += "Automated_weather_observation ";
255 // Warning - this is fragile if the time string format changes
256   hours = time_str.substr(0,2).c_str();
257   mins  = time_str.substr(3,2).c_str();
258 // speak each digit separately:
259   transmission += ConvertNumToSpokenDigits(hours + mins);
260   transmission += " zulu weather" + BRK;
261
262   transmission += "Wind: ";
263
264   double wind_speed = fgGetDouble("/environment/config/boundary/entry[0]/wind-speed-kt");
265   double wind_dir = fgGetDouble("/environment/config/boundary/entry[0]/wind-from-heading-deg");
266   while (wind_dir <= 0) wind_dir += 360;
267 // The following isn't as bad a kludge as it might seem.
268 // It combines the magvar at the /aircraft/ location with
269 // the wind direction in the environment/config array.
270 // But if the aircraft is close enough to the station to
271 // be receiving the ATIS signal, this should be a good-enough
272 // approximation.  For more-distant aircraft, the wind_dir
273 // shouldn't be corrected anyway.
274 // The less-kludgy approach would be to use the magvar associated
275 // with the station, but that is not tabulated in the stationweather
276 // structure as it stands, and computing it would be expensive.
277 // Also note that as it stands, there is only one environment in
278 // the entire FG universe, so the aircraft environment is the same
279 // as the station environment anyway.
280   wind_dir -= fgGetDouble("/environment/magnetic-variation-deg");       // wind_dir now magnetic
281   if (wind_speed == 0) {
282 // Force west-facing rwys to be used in no-wind situations
283 // which is consistent with Flightgear's initial setup:
284       wind_dir = 270;
285       transmission += " light_and_variable";
286   } else {
287       // FIXME: get gust factor in somehow
288       snprintf(buf, bs, "%03.0f", 5*SGMiscd::round(wind_dir/5));
289       transmission += ConvertNumToSpokenDigits(buf);
290
291       snprintf(buf, bs, "%1.0f", wind_speed);
292       transmission += " at " + ConvertNumToSpokenDigits(buf) + BRK;
293   }
294
295   int did_some(0);
296   int did_ceiling(0);
297
298   for (int layer = 0; layer <= 4; layer++) {
299     snprintf(buf, bs, "/environment/clouds/layer[%i]/coverage", layer);
300     string coverage = fgGetString(buf);
301     if (coverage == "clear") continue;
302     snprintf(buf, bs, "/environment/clouds/layer[%i]/thickness-ft", layer);
303     if (fgGetDouble(buf) == 0) continue;
304     snprintf(buf, bs, "/environment/clouds/layer[%i]/elevation-ft", layer);
305     double ceiling = int(fgGetDouble(buf) - _geod.getElevationFt());
306     if (ceiling > 12000) continue;
307     if (coverage == "scattered") {
308       if (!did_some) transmission += "   Sky_condition: ";
309       did_some++;
310     } else /* must be a ceiling */  if (!did_ceiling) {
311       transmission += "   Ceiling: ";
312       did_ceiling++;
313       did_some++;
314     } else {
315       transmission += "   ";
316     }
317     int cig00  = int(SGMiscd::round(ceiling/100));  // hundreds of feet
318     if (cig00) {
319       int cig000 = cig00/10;
320       cig00 -= cig000*10;       // just the hundreds digit
321       if (cig000) {
322     snprintf(buf, bs, "%i", cig000);
323     transmission += ConvertNumToSpokenDigits(buf);
324     transmission += " thousand ";
325       }
326       if (cig00) {
327     snprintf(buf, bs, "%i", cig00);
328     transmission += ConvertNumToSpokenDigits(buf);
329     transmission += " hundred ";
330       }
331     } else {
332       // Should this be "sky obscured?"
333       transmission += " zero ";     // not "zero hundred"
334     }
335     transmission += coverage + BRK;
336   }
337
338   transmission += "Temperature: ";
339   double Tsl = fgGetDouble("/environment/temperature-sea-level-degc");
340   int temp = int(SGMiscd::round(FGAtmo().fake_T_vs_a_us(_geod.getElevationFt(), Tsl)));
341   if(temp < 0) {
342       transmission += "minus ";
343   }
344   snprintf(buf, bs, "%i", abs(temp));
345   transmission += ConvertNumToSpokenDigits(buf);
346   transmission += " dewpoint ";
347   double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc");
348   temp = int(SGMiscd::round(FGAtmo().fake_dp_vs_a_us(dpsl, _geod.getElevationFt())));
349   if(temp < 0) {
350       transmission += "minus ";
351   }
352   snprintf(buf, bs, "%i", abs(temp));
353   transmission += ConvertNumToSpokenDigits(buf) + BRK;
354
355
356   transmission += "Visibility: ";
357   double visibility = fgGetDouble("/environment/config/boundary/entry[0]/visibility-m");
358   visibility /= atmodel::sm;    // convert to statute miles
359   if (visibility < 0.25) {
360     transmission += "less than one quarter";
361   } else if (visibility < 0.5) {
362     transmission += "one quarter";
363   } else if (visibility < 0.75) {
364     transmission += "one half";
365   } else if (visibility < 1.0) {
366     transmission += "three quarters";
367   } else if (visibility >= 1.5 && visibility < 2.0) {
368     transmission += "one and one half";
369   } else {
370     // integer miles
371     if (visibility > 10) visibility = 10;
372     sprintf(buf, "%i", int(.5 + visibility));
373     transmission += ConvertNumToSpokenDigits(buf);
374   }
375   transmission += BRK;
376
377   transmission += "Altimeter: ";
378   double myQNH;
379   double Psl = fgGetDouble("/environment/pressure-sea-level-inhg");
380   {
381     double press, temp;
382     
383     tie(press, temp) = PT_vs_hpt(_geod.getElevationM(), Psl*inHg, Tsl + freezing);
384 #if 0
385     SG_LOG(SG_ATC, SG_ALERT, "Field P: " << press << "  T: " << temp);
386     SG_LOG(SG_ATC, SG_ALERT, "based on elev " << elev 
387                                 << "  Psl: " << Psl
388                                 << "  Tsl: " << Tsl);
389 #endif
390     myQNH = FGAtmo().QNH(_geod.getElevationM(), press);
391   }
392   if(ident.substr(0,2) == "EG" && fgGetBool("/sim/atc/use-millibars")) {
393     // Convert to millibars for the UK!
394     myQNH /= mbar;
395     if  (myQNH > 1000) myQNH -= 1000;       // drop high digit
396     snprintf(buf, bs, "%03.0f", myQNH);
397   } else {
398     myQNH /= inHg;
399     myQNH *= 100.;                        // shift two decimal places
400     snprintf(buf, bs, "%04.0f", myQNH);
401   }
402   transmission += ConvertNumToSpokenDigits(buf) + BRK;
403
404   if (_type == ATIS /* as opposed to AWOS */) {
405       const FGAirport* apt = fgFindAirportID(ident);
406       assert(apt);
407         string rwy_no = apt->getActiveRunwayForUsage()->ident();
408       if(rwy_no != "NN") {
409         transmission += "Landing_and_departing_runway ";
410         transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK;
411         if (msg_OK) {
412           msg_time = cur_time;
413           //cout << "In atis.cxx, r.rwy_no: " << rwy_no
414           //   << " wind_dir: " << wind_dir << endl;
415         }
416     }
417     transmission += "On_initial_contact_advise_you_have_information ";
418     transmission += phonetic_seq_string;
419     transmission += "... " + BRK;
420   }
421 #ifdef ATIS_TEST
422   cout << "**** ATIS active on:";
423 #endif
424   for (map<string,int>::iterator act = active_on.begin(); act != active_on.end(); act++){
425     string prop = "/instrumentation/" + act->first + "/atis";
426     globals->get_props()->setStringValue(prop.c_str(),
427       ("<pre>\n" + transmission + "</pre>\n").c_str());
428 #ifdef ATIS_TEST
429     cout << "  " << prop;
430 #endif
431   }
432 #ifdef ATIS_TEST
433   cout << " ****" << endl;
434   cout << transmission << endl;
435 // Note that even if we aren't outputting the transmission
436 // on stdout, you can still see it by pointing a web browser
437 // at the property tree.  The second comm radio is:
438 // http://localhost:5400/instrumentation/comm[1]
439 #endif
440
441 // Take the previous English-looking string and munge it to
442 // be relatively-more acceptable to the primitive tts system.
443 // Note that : ; and . are among the token-delimeters recognized
444 // by the tts system.
445   for (size_t where;;) {
446     where = transmission.find_first_of(":.");
447     if (where == string::npos) break;
448     transmission.replace(where, 1, " /_ ");
449   }
450   return 1;
451 }