]> git.mxchange.org Git - flightgear.git/blob - src/ATC/ATISEncoder.cxx
Fix a typo breaking some takeoff-state logic.
[flightgear.git] / src / ATC / ATISEncoder.cxx
1 /*
2 Encode an ATIS into spoken words
3 Copyright (C) 2014 Torsten Dreyer
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 */
19
20 #include "ATISEncoder.hxx"
21 #include <Airports/dynamics.hxx>
22 #include <Main/globals.hxx>
23 #include <Main/locale.hxx>
24 #include <simgear/structure/exception.hxx>
25 #include <simgear/props/props_io.hxx>
26 #include <boost/lexical_cast.hpp>
27
28 #include <map>
29 #include <vector>
30
31 using std::string;
32 using std::vector;
33 using simgear::PropertyList;
34
35 static string NO_ATIS("nil");
36 static string EMPTY("");
37 #define SPACE append(1,' ')
38
39 const char * ATCSpeech::getSpokenDigit( int i )
40 {
41   string key = "n" + boost::lexical_cast<std::string>( i );
42   return globals->get_locale()->getLocalizedString(key.c_str(), "atc", "" );
43 }
44
45 string ATCSpeech::getSpokenNumber( string number )
46 {
47   string result;
48   for( string::iterator it = number.begin(); it != number.end(); ++it ) {
49     if( false == result.empty() )
50       result.SPACE;
51     result.append( getSpokenDigit( (*it) - '0' ));
52   }
53   return result;
54 }
55
56 string ATCSpeech::getSpokenNumber( int number, bool leadingZero, int digits )
57 {
58   vector<const char *> spokenDigits;
59   bool negative = false;
60   if( number < 0 ) {
61     negative = true;
62     number = -number;
63   }
64
65   int n = 0;
66   while( number > 0 ) {
67     spokenDigits.push_back( getSpokenDigit(number%10) );
68     number /= 10;
69     n++;
70   }
71
72   if( digits > 0 ) {
73     while( n++ < digits ) {
74       spokenDigits.push_back( getSpokenDigit(0) );
75     }
76   }
77
78   string result;
79   if( negative ) {
80     result.append( globals->get_locale()->getLocalizedString("minus", "atc", "minus" ) );
81   }
82
83   while( false == spokenDigits.empty() ) {
84     if( false == result.empty() )
85       result.SPACE;
86
87     result.append( spokenDigits.back() );
88     spokenDigits.pop_back();
89   }
90
91   return result;
92 }
93
94 string ATCSpeech::getSpokenAltitude( int altitude )
95 {
96   string result;
97   int thousands = altitude / 1000;
98   int hundrets = (altitude % 1000) / 100;
99
100   if( thousands > 0 ) {
101     result.append( getSpokenNumber(thousands) );
102     result.SPACE;
103     result.append( getSpokenDigit(1000) );
104     result.SPACE;
105   }
106   if( hundrets > 0 )
107       result.append( getSpokenNumber(hundrets) )
108             .SPACE
109             .append( getSpokenDigit(100) );
110
111   return result;
112 }
113
114 ATISEncoder::ATISEncoder()
115 {
116   handlerMap.insert( std::make_pair( "text", &ATISEncoder::processTextToken ));
117   handlerMap.insert( std::make_pair( "token", &ATISEncoder::processTokenToken ));
118   handlerMap.insert( std::make_pair( "if", &ATISEncoder::processIfToken ));
119
120   handlerMap.insert( std::make_pair( "id", &ATISEncoder::getAtisId ));
121   handlerMap.insert( std::make_pair( "airport-name", &ATISEncoder::getAirportName ));
122   handlerMap.insert( std::make_pair( "time", &ATISEncoder::getTime ));
123   handlerMap.insert( std::make_pair( "approach-type", &ATISEncoder::getApproachType ));
124   handlerMap.insert( std::make_pair( "rwy-land", &ATISEncoder::getLandingRunway ));
125   handlerMap.insert( std::make_pair( "rwy-to", &ATISEncoder::getTakeoffRunway ));
126   handlerMap.insert( std::make_pair( "transition-level", &ATISEncoder::getTransitionLevel ));
127   handlerMap.insert( std::make_pair( "wind-dir", &ATISEncoder::getWindDirection ));
128   handlerMap.insert( std::make_pair( "wind-from", &ATISEncoder::getWindMinDirection ));
129   handlerMap.insert( std::make_pair( "wind-to", &ATISEncoder::getWindMaxDirection ));
130   handlerMap.insert( std::make_pair( "wind-speed-kn", &ATISEncoder::getWindspeedKnots ));
131   handlerMap.insert( std::make_pair( "gusts", &ATISEncoder::getGustsKnots ));
132   handlerMap.insert( std::make_pair( "visibility-metric", &ATISEncoder::getVisibilityMetric ));
133   handlerMap.insert( std::make_pair( "phenomena", &ATISEncoder::getPhenomena ));
134   handlerMap.insert( std::make_pair( "clouds", &ATISEncoder::getClouds ));
135   handlerMap.insert( std::make_pair( "cavok", &ATISEncoder::getCavok ));
136   handlerMap.insert( std::make_pair( "temperature-deg", &ATISEncoder::getTemperatureDeg ));
137   handlerMap.insert( std::make_pair( "dewpoint-deg", &ATISEncoder::getDewpointDeg ));
138   handlerMap.insert( std::make_pair( "qnh", &ATISEncoder::getQnh ));
139   handlerMap.insert( std::make_pair( "inhg", &ATISEncoder::getInhg ));
140   handlerMap.insert( std::make_pair( "trend", &ATISEncoder::getTrend ));
141 }
142
143 ATISEncoder::~ATISEncoder()
144 {
145 }
146
147 SGPropertyNode_ptr findAtisTemplate( const std::string & stationId, SGPropertyNode_ptr atisSchemaNode )
148 {
149   using simgear::strutils::starts_with;
150   SGPropertyNode_ptr atisTemplate;
151
152   PropertyList schemaNodes = atisSchemaNode->getChildren("atis-schema");
153   for( PropertyList::iterator asit = schemaNodes.begin(); asit != schemaNodes.end(); ++asit ) {
154     SGPropertyNode_ptr ppp = (*asit)->getNode("station-starts-with", false );
155     atisTemplate = (*asit)->getNode("atis", false );
156     if( false == atisTemplate.valid() ) continue; // no <atis> node - ignore entry
157
158     PropertyList startsWithNodes = (*asit)->getChildren("station-starts-with");
159     for( PropertyList::iterator swit = startsWithNodes.begin(); swit != startsWithNodes.end(); ++swit ) {
160    
161       if( starts_with( stationId,  (*swit)->getStringValue() ) ) {
162         return atisTemplate;
163       }
164     }
165
166   }
167
168   return atisTemplate;
169 }
170
171 string ATISEncoder::encodeATIS( ATISInformationProvider * atisInformation )
172 {
173   using simgear::strutils::lowercase;
174
175   if( false == atisInformation->isValid() ) return NO_ATIS;
176
177   airport = FGAirport::getByIdent( atisInformation->airportId() );
178   if( false == airport.valid() ) {
179     SG_LOG( SG_ATC, SG_WARN, "ATISEncoder: unknown airport id " << atisInformation->airportId() );
180     return NO_ATIS;
181   }
182
183   _atis = atisInformation;
184
185   // lazily load the schema file on the first call
186   if( false == atisSchemaNode.valid() ) {
187     atisSchemaNode = new SGPropertyNode();
188     try
189     {
190       SGPath path = globals->resolve_maybe_aircraft_path("ATC/atis.xml");
191       readProperties( path.str(), atisSchemaNode );
192     }
193     catch (const sg_exception& e)
194     {
195       SG_LOG( SG_ATC, SG_ALERT, "ATISEncoder: Failed to load atis schema definition: " << e.getMessage());
196       return NO_ATIS;
197     }
198   }
199
200   string stationId = lowercase( airport->ident() );
201   
202   SGPropertyNode_ptr atisTemplate = findAtisTemplate( stationId, atisSchemaNode );;
203   if( false == atisTemplate.valid() ) {
204     SG_LOG(SG_ATC, SG_WARN, "no matching atis template for station " << stationId  );
205     return NO_ATIS; // no template for this station!?
206   }
207
208   return processTokens( atisTemplate );
209 }
210
211 string ATISEncoder::processTokens( SGPropertyNode_ptr node )
212 {
213   string result;
214   if( node.valid() ) {
215     for( int i = 0; i < node->nChildren(); i++ ) {
216       result.append(processToken( node->getChild(i) ));
217     }
218   }
219   return result;
220 }
221
222 string ATISEncoder::processToken( SGPropertyNode_ptr token ) 
223 {
224   HandlerMap::iterator it = handlerMap.find( token->getName());
225   if( it == handlerMap.end() ) {
226     SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getName() );
227     return EMPTY;
228   }
229   handler_t h = it->second;
230   return (this->*h)( token );
231 }
232
233 string ATISEncoder::processTextToken( SGPropertyNode_ptr token )
234 {
235   return token->getStringValue();
236 }
237
238 string ATISEncoder::processTokenToken( SGPropertyNode_ptr token )
239 {
240   HandlerMap::iterator it = handlerMap.find( token->getStringValue());
241   if( it == handlerMap.end() ) {
242     SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getStringValue() );
243     return EMPTY;
244   }
245   handler_t h = it->second;
246   return (this->*h)( token );
247
248   token->getStringValue();
249 }
250
251 string ATISEncoder::processIfToken( SGPropertyNode_ptr token )
252 {
253   SGPropertyNode_ptr n;
254
255   if( (n = token->getChild("empty", false )).valid() ) {
256     return checkEmptyCondition( n, true) ? 
257            processTokens(token->getChild("then",false)) : 
258            processTokens(token->getChild("else",false));
259   }
260
261   if( (n = token->getChild("not-empty", false )).valid() ) {
262     return checkEmptyCondition( n, false) ? 
263            processTokens(token->getChild("then",false)) : 
264            processTokens(token->getChild("else",false));
265   }
266
267   if( (n = token->getChild("equals", false )).valid() ) {
268     return checkEqualsCondition( n, true) ? 
269            processTokens(token->getChild("then",false)) : 
270            processTokens(token->getChild("else",false));
271   }
272
273   if( (n = token->getChild("not-equals", false )).valid() ) {
274     return checkEqualsCondition( n, false) ? 
275            processTokens(token->getChild("then",false)) : 
276            processTokens(token->getChild("else",false));
277   }
278
279   SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: no valid token found for <if> element" );
280
281   return EMPTY;
282 }
283
284 bool ATISEncoder::checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty ) 
285 {
286   SGPropertyNode_ptr n1 = node->getNode( "token", false );
287   if( false == n1.valid() ) {
288       SG_LOG(SG_ATC, SG_WARN, "missing <token> node for (not)-empty"  );
289       return false;
290   }
291   return processToken( n1 ).empty() == isEmpty;
292 }
293
294 bool ATISEncoder::checkEqualsCondition( SGPropertyNode_ptr node, bool isEqual ) 
295 {
296   SGPropertyNode_ptr n1 = node->getNode( "token", 0, false );
297   SGPropertyNode_ptr n2 = node->getNode( "token", 1, false );
298   if( false == n1.valid() || false == n2.valid()) {
299     SG_LOG(SG_ATC, SG_WARN, "missing <token> node for (not)-equals"  );
300     return false;
301   }
302
303   bool comp = processToken( n1 ).compare( processToken( n2 ) ) == 0;
304   return comp == isEqual;
305 }
306
307 string ATISEncoder::getAtisId( SGPropertyNode_ptr )
308 {
309   FGAirportDynamics * dynamics = airport->getDynamics();
310   if( NULL != dynamics ) {
311     dynamics->updateAtisSequence( 30*60, false );
312     return dynamics->getAtisSequence();
313   }
314   return EMPTY;
315 }
316
317 string ATISEncoder::getAirportName( SGPropertyNode_ptr )
318 {
319   return airport->getName();
320 }
321
322 string ATISEncoder::getTime( SGPropertyNode_ptr )
323 {
324   return getSpokenNumber( _atis->getTime() % (100*100), true, 4 );
325 }
326
327 static inline FGRunwayRef findBestRunwayForWind( FGAirportRef airport, int windDeg, int windKt )
328 {
329   struct FGAirport::FindBestRunwayForHeadingParams p;
330   //TODO: ramp down the heading weight with wind speed
331   p.ilsWeight = 4;
332   return airport->findBestRunwayForHeading( windDeg, &p );
333 }
334
335 string ATISEncoder::getApproachType( SGPropertyNode_ptr )
336 {
337   FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
338   if( runway.valid() ) {
339     if( NULL != runway->ILS() ) return globals->get_locale()->getLocalizedString("ils", "atc", "ils" );
340     //TODO: any chance to find other approach types? localizer-dme, vor-dme, vor, ndb?
341   }
342
343   return globals->get_locale()->getLocalizedString("visual", "atc", "visual" );
344 }
345
346 string ATISEncoder::getLandingRunway( SGPropertyNode_ptr )
347 {
348   FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
349   if( runway.valid() ) {
350     string runwayIdent = runway->ident();
351     if(runwayIdent != "NN") {
352       return getSpokenNumber(runwayIdent);
353     }
354   }
355   return EMPTY;
356 }
357
358 string ATISEncoder::getTakeoffRunway( SGPropertyNode_ptr p )
359 {
360   //TODO: if the airport has more than one runway, probably pick another one?
361   return getLandingRunway( p );
362 }
363
364 string ATISEncoder::getTransitionLevel( SGPropertyNode_ptr )
365 {
366   double hPa = _atis->getQnh();
367
368   /* Transition level is the flight level above which aircraft must use standard pressure and below
369    * which airport pressure settings must be used.
370    * Following definitions are taken from German ATIS:
371    *      QNH <=  977 hPa: TRL 80
372    *      QNH <= 1013 hPa: TRL 70
373    *      QNH >  1013 hPa: TRL 60
374    * (maybe differs slightly for other countries...)
375    */
376   int tl;
377   if (hPa <= 978) {
378     tl = 80;
379   } else if( hPa > 978 && hPa <= 1013 ) {
380     tl = 70;
381   } else if( hPa > 1013 && hPa <= 1046 ) {
382     tl = 60;
383   } else {
384     tl = 50;
385   }
386
387   // add an offset to the transition level for high altitude airports (just guessing here,
388   // seems reasonable)
389   int e = int(airport->getElevation() / 1000.0);
390   if (e >= 3) {
391     // TL steps in 10(00)ft
392     tl += (e-2)*10;
393   }
394
395   return getSpokenNumber(tl);
396 }
397
398 string ATISEncoder::getWindDirection( SGPropertyNode_ptr )
399 {
400   return getSpokenNumber( _atis->getWindDeg(), true, 3 );
401 }
402
403 string ATISEncoder::getWindMinDirection( SGPropertyNode_ptr )
404 {
405   return getSpokenNumber( _atis->getWindMinDeg(), true, 3 );
406 }
407
408 string ATISEncoder::getWindMaxDirection( SGPropertyNode_ptr )
409 {
410   return getSpokenNumber( _atis->getWindMaxDeg(), true, 3 );
411 }
412
413 string ATISEncoder::getWindspeedKnots( SGPropertyNode_ptr )
414 {
415   return getSpokenNumber( _atis->getWindSpeedKt() );
416 }
417
418 string ATISEncoder::getGustsKnots( SGPropertyNode_ptr )
419 {
420   int g = _atis->getGustsKt();
421   return g > 0 ? getSpokenNumber( g ) : EMPTY;
422 }
423
424 string ATISEncoder::getCavok( SGPropertyNode_ptr )
425 {
426   string CAVOK = globals->get_locale()->getLocalizedString("cavok", "atc", "cavok" );
427
428   return _atis->isCavok() ? CAVOK : EMPTY;
429 }
430
431 string ATISEncoder::getVisibilityMetric( SGPropertyNode_ptr )
432 {
433   string m = globals->get_locale()->getLocalizedString("meters", "atc", "meters" );
434   string km = globals->get_locale()->getLocalizedString("kilometers", "atc", "kilometers" );
435   string or_more = globals->get_locale()->getLocalizedString("ormore", "atc", "or more" );
436
437   int v = _atis->getVisibilityMeters();
438   string reply;
439   if( v < 5000 ) return reply.append( getSpokenAltitude( v ) ).SPACE.append( m );
440   if( v >= 9999 ) return reply.append( getSpokenNumber(10) ).SPACE.append( km ).SPACE.append(or_more);
441   return reply.append( getSpokenNumber( v/1000 ).SPACE.append( km ) );
442 }
443
444 string ATISEncoder::getPhenomena( SGPropertyNode_ptr )
445 {
446   return _atis->getPhenomena();
447 }
448
449 string ATISEncoder::getClouds( SGPropertyNode_ptr )
450 {
451   string FEET =  globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
452   string reply;
453
454   ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
455
456   for( ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); it++ ) {
457     if( false == reply.empty() ) reply.SPACE;
458     reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first).SPACE.append( FEET ) );
459   }
460   return reply;
461 }
462
463 string ATISEncoder::getTemperatureDeg( SGPropertyNode_ptr )
464 {
465   return getSpokenNumber( _atis->getTemperatureDeg() );
466 }
467
468 string ATISEncoder::getDewpointDeg( SGPropertyNode_ptr )
469 {
470   return getSpokenNumber( _atis->getDewpointDeg() );
471 }
472
473 string ATISEncoder::getQnh( SGPropertyNode_ptr )
474 {
475   return getSpokenNumber( _atis->getQnh() );
476 }
477
478 string ATISEncoder::getInhg( SGPropertyNode_ptr )
479 {
480   string DECIMAL = globals->get_locale()->getLocalizedString("dp", "atc", "decimal" );
481   double intpart = .0;
482   int fractpart = 1000 * ::modf( _atis->getQnh() * 100.0 / SG_INHG_TO_PA, &intpart );
483   fractpart += 5;
484   fractpart /= 10; 
485
486   string reply;
487   reply.append( getSpokenNumber( (int)intpart ) )
488        .SPACE.append( DECIMAL ).SPACE
489        .append( getSpokenNumber( fractpart ) );
490   return reply;
491 }
492
493 string ATISEncoder::getTrend( SGPropertyNode_ptr )
494 {
495   return _atis->getTrend();
496 }
497