1 // atis.cxx - routines to generate the ATIS info string
2 // This is the implementation of the FGATIS class
4 // Written by David Luff, started October 2001.
5 // Extended by Thorsten Brehm, October 2012.
7 // Copyright (C) 2001 David C Luff - david.luff@nottingham.ac.uk
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 ///// TODO: _Cumulative_ sky coverage.
25 ///// TODO: wind _gust_
26 ///// TODO: more-sensible encoding of voice samples
27 ///// u-law? outright synthesis?
35 #include "atis_lexicon.hxx"
37 #include <simgear/compiler.h>
38 #include <simgear/math/sg_random.h>
39 #include <simgear/misc/sg_path.hxx>
40 #include <simgear/misc/strutils.hxx>
42 #include <stdlib.h> // atoi()
43 #include <stdio.h> // sprintf
47 #include <boost/tuple/tuple.hpp>
48 #include <boost/algorithm/string.hpp>
49 #include <boost/algorithm/string/case_conv.hpp>
51 #include <Environment/environment_mgr.hxx>
52 #include <Environment/environment.hxx>
53 #include <Environment/atmosphere.hxx>
55 #include <Main/fg_props.hxx>
56 #include <Main/globals.hxx>
57 #include <Airports/runways.hxx>
58 #include <Airports/dynamics.hxx>
60 #include <ATC/CommStation.hxx>
61 #include <Navaids/navrecord.hxx>
63 #include "ATCutils.hxx"
64 #include "ATISmgr.hxx"
72 using flightgear::CommStation;
74 FGATIS::FGATIS(const std::string& name, int num) :
77 _cb_attention(this, &FGATIS::attend, fgGetNode("/environment/attention", true)),
86 _check_transmission(true),
88 _time_before_search_sec(0),
91 _root = fgGetNode("/instrumentation", true)->getNode(_name, num, true);
92 _volume = _root->getNode("volume",true);
93 _serviceable = _root->getNode("serviceable",true);
97 // only drive "operable" for non-nav instruments (nav radio drives this separately)
98 _operable = _root->getNode("operable",true);
99 _operable->setBoolValue(false);
102 _electrical = fgGetNode("/systems/electrical/outputs",true)->getNode(_name,num, true);
103 _atis = _root->getNode("atis",true);
104 _freq = _root->getNode("frequencies/selected-mhz",true);
107 _lon_node = fgGetNode("/position/longitude-deg", true);
108 _lat_node = fgGetNode("/position/latitude-deg", true);
109 _elev_node = fgGetNode("/position/altitude-ft", true);
111 // backward compatibility: some properties may not exist (but default to "ON")
112 if (!_serviceable->hasValue())
113 _serviceable->setBoolValue(true);
114 if (!_electrical->hasValue())
115 _electrical->setDoubleValue(24.0);
118 // FIXME: This would be more flexible and more extensible
119 // if the mappings were taken from an XML file, not hard-coded ...
120 // ... although having it in a .hxx file is better than nothing.
122 // Load the remap list from the .hxx file:
126 # define REMAP(from,to) _remap[#from] = to;
127 # include "atis_remap.hxx"
132 SG_LOG(SG_ATC, SG_ALERT, "ATIS initialized");
139 // http://localhost:5400/environment/attention?value=1&submit=update
141 FGATCVoice* FGATIS::GetVoicePointer()
143 FGATISMgr* pAtisMgr = globals->get_ATIS_mgr();
146 SG_LOG(SG_ATC, SG_ALERT, "ERROR! No ATIS manager! Oops...");
150 return pAtisMgr->GetVoicePointer(ATIS);
155 // Nothing to see here. Move along.
158 void FGATIS::reinit()
160 _time_before_search_sec = 0;
161 _check_transmission = true;
165 FGATIS::attend(SGPropertyNode* node)
167 if (node->getBoolValue())
170 int flag = fgGetInt("/sim/logging/atmo");
172 FGAltimeter().check_model();
173 FGAltimeter().dump_stack();
179 // Main update function - checks whether we are displaying or not the correct message.
180 void FGATIS::update(double dt) {
181 cur_time = globals->get_time_params()->get_cur_time();
182 msg_OK = (msg_time < cur_time);
185 if (msg_OK || _display != _prev_display) {
186 cout << "ATIS Update: " << _display << " " << _prev_display
187 << " len: " << transmission.length()
188 << " oldvol: " << old_volume
189 << " dt: " << dt << endl;
195 if ((_electrical->getDoubleValue() > 8) && _serviceable->getBoolValue())
197 // radio is switched on and OK
198 if (_operable.valid())
199 _operable->setBoolValue(true);
201 _check_transmission |= search(dt);
205 volume = _volume->getDoubleValue();
211 if (_operable.valid())
212 _operable->setBoolValue(false);
213 _time_before_search_sec = 0;
218 bool changed = false;
219 if (_check_transmission)
221 _check_transmission = false;
222 // Check if we need to update the message
223 // - basically every hour and if the weather changes significantly at the station
224 // If !_prev_display, the radio had been detuned for a while and our
225 // "transmission" variable was lost when we were de-instantiated.
226 if (genTransmission(!_prev_display, _attention))
228 // update output property
234 if (changed || volume != old_volume) {
235 // audio output enabled
236 Render(transmission, volume, _name, true);
239 _prev_display = _display;
243 _prev_display = false;
250 // Replace all occurrences of a given word.
251 // Words in the original string must be separated by hyphens (not spaces).
252 // We check for the word as given, and for the all-caps version thereof.
253 string replace_word(const string _orig, const string _www, const string _nnn){
254 // The following are so we can match words at the beginning
255 // and end of the string.
256 string orig = "-" + _orig + "-";
257 string www = "-" + _www + "-";
258 string nnn = "-" + _nnn + "-";
261 for ( ; (where = orig.find(www, where)) != string::npos ; ) {
262 orig.replace(where, www.length(), nnn);
263 where += nnn.length();
266 www = simgear::strutils::uppercase(www);
267 for ( ; (where = orig.find(www, where)) != string::npos ; ) {
268 orig.replace(where, www.length(), nnn);
269 where += nnn.length();
271 where = orig.length();
272 return orig.substr(1, where-2);
275 // Normally the interval is 1 hour,
276 // but you can shorten it for testing.
277 const int minute(60); // measured in seconds
279 const int ATIS_interval(2*minute);
281 const int ATIS_interval(60*minute);
284 // FIXME: This is heuristic. It gets the right answer for
285 // more than 90% of the world's airports, which is a lot
286 // better than nothing ... but it's not 100%.
287 // We know "most" of the world uses millibars,
288 // but the US, Canada and *some* other places use inches of mercury,
289 // but (a) we have not implemented a reliable method of
290 // ascertaining which airports are in the US, let alone
291 // (b) ascertaining which other places use inches.
293 bool Apt_US_CA(const string id)
295 // Assume all IDs have length 3 or 4.
296 // No counterexamples have been seen.
297 if (id.length() == 4) {
298 if (id.substr(0,1) == "K") return true;
299 if (id.substr(0,2) == "CY") return true;
301 for (string::const_iterator ptr = id.begin(); ptr != id.end(); ptr++) {
302 if (isdigit(*ptr)) return true;
308 static const string BRK = ".\n";
309 static const string PAUSE = " / ";
311 /** Generate the actual broadcast ATIS transmission.
312 * 'regen' triggers a regeneration of the /current/ transmission.
313 * 'forceUpdate' generates a new transmission, with a new sequence.
314 * Returns 1 if we actually generated something.
316 bool FGATIS::genTransmission(const int regen, bool forceUpdate)
320 // ATIS updated hourly, AWOS updated more frequently
321 int interval = _type == ATIS ? ATIS_interval : 2*minute;
323 // check if pressure has changed significantly and we need to update ATIS
324 double Psl = fgGetDouble("/environment/pressure-sea-level-inhg");
325 if (fabs(Psl-_report.psl) >= 0.15)
328 FGAirport* apt = FGAirport::findByIdent(ident);
329 int sequence = apt->getDynamics()->updateAtisSequence(interval, forceUpdate);
330 if (!regen && sequence > LTRS) {
331 //xx if (msg_OK) cout << "ATIS: no change: " << sequence << endl;
332 //xx msg_time = cur_time;
333 return false; // no change since last time
339 // collect data and create report
346 // ATIS phraseology starts with "... airport information"
347 transmission += airport_information + " ";
350 transmission += Automated_weather_observation + " ";
353 string phonetic_seq_string = GetPhoneticLetter(sequence); // Add the sequence letter
354 transmission += phonetic_seq_string + BRK;
358 // some warnings may appear at the beginning
361 if (_type == ATIS) // as opposed to AWOS
364 // some warnings may appear after runway info
368 genTransitionLevel(apt);
371 if (!_report.concise)
372 transmission += Weather + BRK;
376 // clouds and visibility
378 string vis_info, cloud_info;
379 bool v = genVisibilityInfo(vis_info);
380 bool c = genCloudInfo(cloud_info);
381 _report.cavok = !(v || c);
384 // there is some visibility or cloud restriction
385 transmission += vis_info + cloud_info;
389 // Abbreviation CAVOK vs full "clouds and visibility..." does not really depend on
390 // US vs rest of the world, it really seems to depend on the airport. Just use
391 // it as a heuristic.
392 if ((_report.US_CA)||(_report.concise))
393 transmission += cav_ok + BRK;
395 transmission += clouds_and_visibility_OK + BRK;
400 genPrecipitationInfo();
403 genTemperatureInfo();
408 // TODO check whether "no significant change" applies - somehow...
409 transmission += No_sig + BRK; // sounds better with festival than "nosig"
411 // some warnings may appear at the very end
414 if ((!_report.concise)|| _report.US_CA)
415 transmission += Advise_on_initial_contact_you_have_information;
417 transmission += information;
418 transmission += " " + phonetic_seq_string + ".";
422 // non-US ATIS ends with "out!"
423 transmission += " " + out;
426 // Pause in between two messages must be 3-5 seconds
427 transmission += " / / / / / / / / ";
429 /////////////////////////////////////////////////////////
431 /////////////////////////////////////////////////////////
432 transmission_readable = transmission;
434 // Take the previous readable string and munge it to
435 // be relatively-more acceptable to the primitive tts system.
436 // Note that : ; and . are among the token-delimiters recognized
437 // by the tts system.
438 for (size_t where;;) {
439 where = transmission.find_first_of(":.");
440 if (where == string::npos) break;
441 transmission.replace(where, 1, PAUSE);
447 /** Collect (most of) the data and create report.
449 void FGATIS::createReport(const FGAirport* apt)
452 _report.US_CA = Apt_US_CA(ident);
454 // switch to enable brief ATIS message (really depends on the airport)
455 _report.concise = fgGetBool("/sim/atis/concise-reports", false);
460 string time_str = fgGetString("sim/time/gmt-string");
461 // Warning - this is fragile if the time string format changes
462 _report.hours = time_str.substr(0,2).c_str();
463 _report.mins = time_str.substr(3,2).c_str();
465 // pressure/temperature
468 double Tsl = fgGetDouble("/environment/temperature-sea-level-degc");
469 tie(press, temp) = PT_vs_hpt(_geod.getElevationM(), _report.psl*atmodel::inHg, Tsl + atmodel::freezing);
471 SG_LOG(SG_ATC, SG_ALERT, "Field P: " << press << " T: " << temp);
472 SG_LOG(SG_ATC, SG_ALERT, "based on elev " << elev
476 _report.qnh = FGAtmo().QNH(_geod.getElevationM(), press);
477 _report.temp = int(SGMiscd::round(FGAtmo().fake_T_vs_a_us(_geod.getElevationFt(), Tsl)));
481 double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc");
482 _report.dewpoint = int(SGMiscd::round(FGAtmo().fake_dp_vs_a_us(dpsl, _geod.getElevationFt())));
485 _report.rain_norm = fgGetDouble("environment/rain-norm");
486 _report.snow_norm = fgGetDouble("environment/snow-norm");
490 if (fgGetBool("/sim/atis/random-notams", true))
492 _report.notam = fgGetInt("/sim/atis/notam-id", 0); // fixed NOTAM for testing/debugging only
495 // select pseudo-random NOTAM (changes every hour, differs for each airport)
497 string name = apt->getName();
498 for(string::iterator p = name.begin(); p != name.end(); p++)
502 cksum ^= atoi(_report.hours.c_str());
503 _report.notam = cksum % 12; // 12 intentionally higher than number of available NOTAMs, so they don't appear too often
505 //fgSetInt("/sim/atis/selected-notam", _report.notam);
510 void FGATIS::genPrecipitationInfo(void)
514 double rain_norm = _report.rain_norm;
515 double snow_norm = _report.snow_norm;
517 // report rain or snow - which ever is worse
519 transmission += heavy + " " + rain + BRK;
522 transmission += heavy + " " + snow + BRK;
525 transmission += moderate + " " + rain + BRK;
528 transmission += moderate + " " + snow + BRK;
531 transmission += light + " " + rain + BRK;
533 if (snow_norm > 0.05)
534 transmission += light + " " + snow + BRK;
536 if (rain_norm > 0.05)
537 transmission += light + " " + drizzle + BRK;
540 void FGATIS::genTimeInfo(void)
544 if (!_report.concise)
545 transmission += Time + " ";
547 // speak each digit separately:
548 transmission += ConvertNumToSpokenDigits(_report.hours + _report.mins);
549 transmission += " " + zulu + BRK;
552 bool FGATIS::genVisibilityInfo(string& vis_info)
556 double visibility = fgGetDouble("/environment/config/boundary/entry[0]/visibility-m");
558 bool USE_KM = !_report.US_CA;
560 vis_info += Visibility + ": ";
563 visibility /= 1000.0; // convert to statute miles
564 // integer kilometers
565 if (visibility >= 9.5)
570 snprintf(buf, sizeof(buf), "%i", int(.5 + visibility));
571 // "kelometers" instead of "kilometers" since the festival language generator doesn't get it right otherwise
572 vis_info += ConvertNumToSpokenDigits(buf) + " " + kelometers;
576 visibility /= atmodel::sm; // convert to statute miles
577 if (visibility < 0.25) {
578 vis_info += less_than_one_quarter;
579 } else if (visibility < 0.5) {
580 vis_info += one_quarter;
581 } else if (visibility < 0.75) {
582 vis_info += one_half;
583 } else if (visibility < 1.0) {
584 vis_info += three_quarters;
585 } else if (visibility >= 1.5 && visibility < 2.0) {
586 vis_info += one_and_one_half;
589 if (visibility > 9.5)
594 snprintf(buf, sizeof(buf), "%i", int(.5 + visibility));
595 vis_info += ConvertNumToSpokenDigits(buf);
600 vis_info += " " + or_more;
606 void FGATIS::addTemperature(int Temp)
609 transmission += lex::minus + " ";
613 transmission += lex::plus + " ";
615 snprintf(buf, sizeof(buf), "%i", abs(Temp));
616 transmission += ConvertNumToSpokenDigits(buf);
618 transmission += " " + lex::Celsius;
621 void FGATIS::genTemperatureInfo()
624 transmission += lex::Temperature + ": ";
625 addTemperature(_report.temp);
628 transmission += BRK + lex::Dewpoint + ": ";
629 addTemperature(_report.dewpoint);
634 bool FGATIS::genCloudInfo(string& cloud_info)
638 bool did_some = false;
639 bool did_ceiling = false;
641 for (int layer = 0; layer <= 4; layer++) {
642 snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/coverage", layer);
643 string coverage = fgGetString(buf);
644 if (coverage == clear)
646 snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/thickness-ft", layer);
647 if (fgGetDouble(buf) == 0)
649 snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/elevation-ft", layer);
650 double ceiling = int(fgGetDouble(buf) - _geod.getElevationFt());
654 // BEWARE: At the present time, the environment system has no
655 // way (so far as I know) to represent a "thin broken" or
656 // "thin overcast" layer. If/when such things are implemented
657 // in the environment system, code will have to be written here
660 // First, do the prefix if any:
661 if (coverage == scattered || coverage == few) {
664 cloud_info += Clouds + ": ";
666 cloud_info += Sky_condition + ": ";
669 } else /* must be a ceiling */ if (!did_ceiling) {
670 cloud_info += " " + Ceiling + ": ";
674 cloud_info += " "; // no prefix required
676 int cig00 = int(SGMiscd::round(ceiling/100)); // hundreds of feet
678 int cig000 = cig00/10;
679 cig00 -= cig000*10; // just the hundreds digit
681 snprintf(buf, sizeof(buf), "%i", cig000);
682 cloud_info += ConvertNumToSpokenDigits(buf);
683 cloud_info += " " + thousand + " ";
686 snprintf(buf, sizeof(buf), "%i", cig00);
687 cloud_info += ConvertNumToSpokenDigits(buf);
688 cloud_info += " " + hundred + " ";
691 // Should this be "sky obscured?"
692 cloud_info += " " + zero + " "; // not "zero hundred"
694 cloud_info += coverage + BRK;
697 cloud_info += " " + Sky + " " + clear + BRK;
701 void FGATIS::genFacilityInfo(void)
703 if ((!_report.US_CA)&&(!_report.concise))
705 // UK CAA radiotelephony manual indicates ATIS transmissions start
706 // with "This is ...", while US just starts with airport name.
707 transmission += lex::This_is + " ";
710 // SG_LOG(SG_ATC, SG_ALERT, "ATIS: facility name: " << name);
712 // Note that at this point, multi-word facility names
713 // will sometimes contain hyphens, not spaces.
714 std::vector<std::string> name_words;
715 boost::split(name_words, name, boost::is_any_of(" -"));
717 for( std::vector<string>::const_iterator wordp = name_words.begin();
718 wordp != name_words.end(); wordp++) {
720 // Remap some abbreviations that occur in apt.dat, to
721 // make things nicer for the text-to-speech system:
722 for (MSS::const_iterator replace = _remap.begin();
723 replace != _remap.end(); replace++) {
724 // Due to inconsistent capitalisation in the apt.dat file, we need
725 // to do a case-insensitive comparison here.
726 string tmp1 = word, tmp2 = replace->first;
727 boost::algorithm::to_lower(tmp1);
728 boost::algorithm::to_lower(tmp2);
730 word = replace->second;
734 transmission += word + " ";
738 void FGATIS::genWindInfo(void)
742 transmission += Wind + ": ";
744 double wind_speed = fgGetDouble("/environment/config/boundary/entry[0]/wind-speed-kt");
745 double wind_dir = fgGetDouble("/environment/config/boundary/entry[0]/wind-from-heading-deg");
746 while (wind_dir <= 0) wind_dir += 360;
747 // The following isn't as bad a kludge as it might seem.
748 // It combines the magvar at the /aircraft/ location with
749 // the wind direction in the environment/config array.
750 // But if the aircraft is close enough to the station to
751 // be receiving the ATIS signal, this should be a good-enough
752 // approximation. For more-distant aircraft, the wind_dir
753 // shouldn't be corrected anyway.
754 // The less-kludgy approach would be to use the magvar associated
755 // with the station, but that is not tabulated in the stationweather
756 // structure as it stands, and computing it would be expensive.
757 // Also note that as it stands, there is only one environment in
758 // the entire FG universe, so the aircraft environment is the same
759 // as the station environment anyway.
760 wind_dir -= fgGetDouble("/environment/magnetic-variation-deg"); // wind_dir now magnetic
761 if (wind_speed == 0) {
762 // Force west-facing rwys to be used in no-wind situations
763 // which is consistent with Flightgear's initial setup:
765 transmission += " " + light_and_variable;
767 // FIXME: get gust factor in somehow
768 snprintf(buf, sizeof(buf), "%03.0f", 5*SGMiscd::round(wind_dir/5));
769 transmission += ConvertNumToSpokenDigits(buf);
770 if (!_report.concise)
771 transmission += " " + degrees;
773 snprintf(buf, sizeof(buf), "%1.0f", wind_speed);
774 transmission += at + " " + ConvertNumToSpokenDigits(buf);
775 if (!_report.concise)
776 transmission += " " + knots;
781 void FGATIS::genTransitionLevel(const FGAirport* apt)
783 double hPa = _report.qnh/atmodel::mbar;
785 /* Transition level is the flight level above which aircraft must use standard pressure and below
786 * which airport pressure settings must be used.
787 * Following definitions are taken from German ATIS:
788 * QNH <= 977 hPa: TRL 80
789 * QNH <= 1013 hPa: TRL 70
790 * QNH > 1013 hPa: TRL 60
791 * (maybe differs slightly for other countries...)
800 // add an offset to the transition level for high altitude airports (just guessing here,
802 double elevationFt = apt->getElevation();
803 int e = int(elevationFt / 1000.0);
806 // TL steps in 10(00)ft
810 snprintf(buf, sizeof(buf), "%02i", tl);
811 transmission += lex::Transition_level + ": " + ConvertNumToSpokenDigits(buf) + BRK;
814 void FGATIS::genPressureInfo(void)
818 // hectopascal for most of the world (not US, not CA)
820 double hPa = _report.qnh/atmodel::mbar;
821 transmission += QNH + ": ";
822 snprintf(buf, sizeof(buf), "%03.0f", _report.qnh / atmodel::mbar);
823 transmission += ConvertNumToSpokenDigits(buf);
824 // "hectopascal" replaced "millibars" in new ATIS standard since 2011
825 if ((!_report.concise)||(hPa < 1000))
826 transmission += " " + hectopascal; // "hectopascal" must be provided for values below 1000 (to avoid confusion with inHg)
828 // Many (European) airports (with lots of US traffic?) provide both, hPa and inHg announcements.
829 // Europeans keep the "decimal" in inHg readings to make the distinction to hPa even more apparent.
830 // hPa/inHg separated by "equals" or "or" with some airports
832 transmission += " " + equals + " ";
834 transmission += " " + Or + " ";
835 snprintf(buf, sizeof(buf), "%04.2f", _report.qnh / atmodel::inHg);
836 transmission += ConvertNumToSpokenDigits(buf);
837 if (!_report.concise)
838 transmission += " " + inches;
841 // use inches of mercury for US/CA
842 transmission += Altimeter + ": ";
843 double asetting = _report.qnh / atmodel::inHg;
844 // shift two decimal places, US/CA airports omit the "decimal" in inHg settings
846 snprintf(buf, sizeof(buf), "%04.0f", asetting);
847 transmission += ConvertNumToSpokenDigits(buf);
853 void FGATIS::genRunwayInfo(const FGAirport* apt)
860 FGRunway* rwy = apt->getActiveRunwayForUsage();
864 string rwy_no = rwy->ident();
867 FGNavRecord* ils = rwy->ILS();
871 transmission += Expect_I_L_S_approach + " "+ runway + " "+ConvertRwyNumToSpokenString(rwy_no) + BRK;
872 if (fgGetBool("/sim/atis/announce-ils-frequency", false))
874 // this is handy - but really non-standard (so disabled by default)
875 snprintf(buf, sizeof(buf), "%5.2f", ils->get_freq()/100.0);
876 transmission += I_L_S + " " + ConvertNumToSpokenDigits(buf) + BRK;
881 transmission += Expect_visual_approach + " "+ runway + " "+ConvertRwyNumToSpokenString(rwy_no) + BRK;
884 transmission += Landing_and_departing_runway + " ";
885 transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK;
889 cout << "In atis.cxx, r.rwy_no: " << rwy_no
890 << " wind_dir: " << wind_dir << endl;
896 void FGATIS::genWarnings(int position)
899 bool dayTime = (fgGetDouble("/sim/time/sun-angle-rad") < 1.57);
901 if (position == -1) // warnings at beginning of ATIS
903 // bird related warnings at day-time only (birds are VFR-only! ;-) )
906 if (_report.notam == 1)
907 transmission += Attention + ": " + flock_of_birds + " " + in_the_vicinity_of_the_airport + BRK;
909 if (_report.notam == 2)
910 transmission += Attention + ": " + bird_activity + " " + in_the_vicinity_of_the_airport + BRK;
914 if (position == 0) // warnings after runway messages
916 if ((_report.notam == 3)&&(_report.ils))
918 // "__I_LS_" necessary to trick the language generator into pronouncing it properly
919 transmission += Attention + ": " + short_time__I_LS_interference_possible_by_taxiing_aircraft + BRK;
923 if (position == 1) // warnings at the end of the report
925 // "runway wet-wet-wet" warning in heavy rain
926 if (_report.rain_norm > 0.6)
928 // "wet" is repeated 3 times in ATIS warnings, since the word is difficult
929 // to understand over radio - but the message is important.
930 transmission += runway_wet + " " + wet + " " + wet + BRK;
933 if (_report.notam == 4)
935 // intentional: "reed" instead of "read" since festival gets it wrong otherwise
936 transmission += reed_back_all_runway_hold_instructions + BRK;
939 if ((_report.notam == 5)&& _report.cavok && dayTime &&
940 (_report.rain_norm == 0) && (_report.snow_norm == 0)) // ;-)
942 transmission += Attention + ": " + glider_operation_in_sector + BRK;
947 // Put the transmission into the property tree.
948 // You can see it by pointing a web browser
949 // at the property tree. The second comm radio is:
950 // http://localhost:5400/instrumentation/comm[1]
952 // (Also, if in debug mode, dump it to the console.)
953 void FGATIS::treeOut(int msg_OK)
955 _atis->setStringValue("<pre>\n" + transmission_readable + "</pre>\n");
956 SG_LOG(SG_ATC, SG_DEBUG, "**** ATIS active on: " << _name <<
957 "transmission: " << transmission_readable);
961 class RangeFilter : public CommStation::Filter
964 RangeFilter( const SGGeod & pos ) :
965 CommStation::Filter(),
966 _cart(SGVec3d::fromGeod(pos)),
971 virtual bool pass(FGPositioned* aPos) const
973 flightgear::CommStation * stn = dynamic_cast<flightgear::CommStation*>(aPos);
977 // do the range check in cartesian space, since the distances are potentially
978 // large enough that the geodetic functions become unstable
979 // (eg, station on opposite side of the planet)
980 double rangeM = SGMiscd::max( stn->rangeNm(), 10.0 ) * SG_NM_TO_METER;
981 double d2 = distSqr( aPos->cart(), _cart);
983 return d2 <= (rangeM * rangeM);
986 virtual CommStation::Type minType() const
988 return CommStation::FREQ_ATIS;
991 virtual CommStation::Type maxType() const
993 return CommStation::FREQ_AWOS;
1001 // Search for ATC stations by frequency
1002 bool FGATIS::search(double dt)
1004 double frequency = _freq->getDoubleValue();
1006 // Note: 122.375 must be rounded DOWN to 122370
1007 // in order to be consistent with apt.dat et cetera.
1008 int freqKhz = 10 * static_cast<int>(frequency * 100 + 0.25);
1010 // only search tuned frequencies when necessary
1011 _time_before_search_sec -= dt;
1013 // throttle frequency searches
1014 if ((freqKhz == _last_frequency)&&(_time_before_search_sec > 0))
1017 _last_frequency = freqKhz;
1018 _time_before_search_sec = 4.0;
1020 // Position of the Users Aircraft
1021 SGGeod aircraftPos = SGGeod::fromDegFt(_lon_node->getDoubleValue(),
1022 _lat_node->getDoubleValue(),
1023 _elev_node->getDoubleValue());
1025 RangeFilter rangeFilter(aircraftPos );
1026 CommStation* sta = CommStation::findByFreq(freqKhz, aircraftPos, &rangeFilter );