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