]> git.mxchange.org Git - flightgear.git/blob - src/ATC/ATISEncoder.cxx
Work with new SGPath API.
[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   handlerMap.insert( std::make_pair( "section", &ATISEncoder::processTokens ));
120
121   handlerMap.insert( std::make_pair( "id", &ATISEncoder::getAtisId ));
122   handlerMap.insert( std::make_pair( "airport-name", &ATISEncoder::getAirportName ));
123   handlerMap.insert( std::make_pair( "time", &ATISEncoder::getTime ));
124   handlerMap.insert( std::make_pair( "approach-type", &ATISEncoder::getApproachType ));
125   handlerMap.insert( std::make_pair( "rwy-land", &ATISEncoder::getLandingRunway ));
126   handlerMap.insert( std::make_pair( "rwy-to", &ATISEncoder::getTakeoffRunway ));
127   handlerMap.insert( std::make_pair( "transition-level", &ATISEncoder::getTransitionLevel ));
128   handlerMap.insert( std::make_pair( "wind-dir", &ATISEncoder::getWindDirection ));
129   handlerMap.insert( std::make_pair( "wind-from", &ATISEncoder::getWindMinDirection ));
130   handlerMap.insert( std::make_pair( "wind-to", &ATISEncoder::getWindMaxDirection ));
131   handlerMap.insert( std::make_pair( "wind-speed-kn", &ATISEncoder::getWindspeedKnots ));
132   handlerMap.insert( std::make_pair( "gusts", &ATISEncoder::getGustsKnots ));
133   handlerMap.insert( std::make_pair( "visibility-metric", &ATISEncoder::getVisibilityMetric ));
134   handlerMap.insert( std::make_pair( "visibility-miles", &ATISEncoder::getVisibilityMiles ));
135   handlerMap.insert( std::make_pair( "phenomena", &ATISEncoder::getPhenomena ));
136   handlerMap.insert( std::make_pair( "clouds", &ATISEncoder::getClouds ));
137   handlerMap.insert( std::make_pair( "clouds-brief", &ATISEncoder::getCloudsBrief ));
138   handlerMap.insert( std::make_pair( "cavok", &ATISEncoder::getCavok ));
139   handlerMap.insert( std::make_pair( "temperature-deg", &ATISEncoder::getTemperatureDeg ));
140   handlerMap.insert( std::make_pair( "dewpoint-deg", &ATISEncoder::getDewpointDeg ));
141   handlerMap.insert( std::make_pair( "qnh", &ATISEncoder::getQnh ));
142   handlerMap.insert( std::make_pair( "inhg", &ATISEncoder::getInhg ));
143   handlerMap.insert( std::make_pair( "inhg-integer", &ATISEncoder::getInhgInteger ));
144   handlerMap.insert( std::make_pair( "inhg-fraction", &ATISEncoder::getInhgFraction ));
145   handlerMap.insert( std::make_pair( "trend", &ATISEncoder::getTrend ));
146 }
147
148 ATISEncoder::~ATISEncoder()
149 {
150 }
151
152 SGPropertyNode_ptr findAtisTemplate( const std::string & stationId, SGPropertyNode_ptr atisSchemaNode )
153 {
154   using simgear::strutils::starts_with;
155   SGPropertyNode_ptr atisTemplate;
156
157   PropertyList schemaNodes = atisSchemaNode->getChildren("atis-schema");
158   for( PropertyList::iterator asit = schemaNodes.begin(); asit != schemaNodes.end(); ++asit ) {
159     SGPropertyNode_ptr ppp = (*asit)->getNode("station-starts-with", false );
160     atisTemplate = (*asit)->getNode("atis", false );
161     if( false == atisTemplate.valid() ) continue; // no <atis> node - ignore entry
162
163     PropertyList startsWithNodes = (*asit)->getChildren("station-starts-with");
164     for( PropertyList::iterator swit = startsWithNodes.begin(); swit != startsWithNodes.end(); ++swit ) {
165    
166       if( starts_with( stationId,  (*swit)->getStringValue() ) ) {
167         return atisTemplate;
168       }
169     }
170
171   }
172
173   return atisTemplate;
174 }
175
176 string ATISEncoder::encodeATIS( ATISInformationProvider * atisInformation )
177 {
178   using simgear::strutils::lowercase;
179
180   if( false == atisInformation->isValid() ) return NO_ATIS;
181
182   airport = FGAirport::getByIdent( atisInformation->airportId() );
183   if( false == airport.valid() ) {
184     SG_LOG( SG_ATC, SG_WARN, "ATISEncoder: unknown airport id " << atisInformation->airportId() );
185     return NO_ATIS;
186   }
187
188   _atis = atisInformation;
189
190   // lazily load the schema file on the first call
191   if( false == atisSchemaNode.valid() ) {
192     atisSchemaNode = new SGPropertyNode();
193     try
194     {
195       SGPath path = globals->resolve_maybe_aircraft_path("ATC/atis.xml");
196       readProperties( path, atisSchemaNode );
197     }
198     catch (const sg_exception& e)
199     {
200       SG_LOG( SG_ATC, SG_ALERT, "ATISEncoder: Failed to load atis schema definition: " << e.getMessage());
201       return NO_ATIS;
202     }
203   }
204
205   string stationId = lowercase( airport->ident() );
206   
207   SGPropertyNode_ptr atisTemplate = findAtisTemplate( stationId, atisSchemaNode );;
208   if( false == atisTemplate.valid() ) {
209     SG_LOG(SG_ATC, SG_WARN, "no matching atis template for station " << stationId  );
210     return NO_ATIS; // no template for this station!?
211   }
212
213   return processTokens( atisTemplate );
214 }
215
216 string ATISEncoder::processTokens( SGPropertyNode_ptr node )
217 {
218   string result;
219   if( node.valid() ) {
220     for( int i = 0; i < node->nChildren(); i++ ) {
221       result.append(processToken( node->getChild(i) ));
222     }
223   }
224   return result;
225 }
226
227 string ATISEncoder::processToken( SGPropertyNode_ptr token ) 
228 {
229   HandlerMap::iterator it = handlerMap.find( token->getName());
230   if( it == handlerMap.end() ) {
231     SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getName() );
232     return EMPTY;
233   }
234   handler_t h = it->second;
235   return (this->*h)( token );
236 }
237
238 string ATISEncoder::processTextToken( SGPropertyNode_ptr token )
239 {
240   return token->getStringValue();
241 }
242
243 string ATISEncoder::processTokenToken( SGPropertyNode_ptr token )
244 {
245   HandlerMap::iterator it = handlerMap.find( token->getStringValue());
246   if( it == handlerMap.end() ) {
247     SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: unknown token: " << token->getStringValue() );
248     return EMPTY;
249   }
250   handler_t h = it->second;
251   return (this->*h)( token );
252
253   token->getStringValue();
254 }
255
256 string ATISEncoder::processIfToken( SGPropertyNode_ptr token )
257 {
258   using namespace simgear::strutils;
259
260   SGPropertyNode_ptr n;
261
262   if( (n = token->getChild("empty", false )).valid() ) {
263     return checkEmptyCondition( n, true) ? 
264            processTokens(token->getChild("then",false)) : 
265            processTokens(token->getChild("else",false));
266   }
267
268   if( (n = token->getChild("not-empty", false )).valid() ) {
269     return checkEmptyCondition( n, false) ? 
270            processTokens(token->getChild("then",false)) : 
271            processTokens(token->getChild("else",false));
272   }
273
274   if( (n = token->getChild("contains", false )).valid() ) {
275     return checkCondition( n, true, &contains, "contains") ?
276            processTokens(token->getChild("then",false)) :
277            processTokens(token->getChild("else",false));
278   }
279
280   if( (n = token->getChild("not-contains", false )).valid() ) {
281     return checkCondition( n, false, &contains, "not-contains") ?
282            processTokens(token->getChild("then",false)) :
283            processTokens(token->getChild("else",false));
284   }
285
286   if( (n = token->getChild("ends-with", false )).valid() ) {
287     return checkCondition( n, true, &ends_with, "ends-with") ?
288            processTokens(token->getChild("then",false)) :
289            processTokens(token->getChild("else",false));
290   }
291
292   if( (n = token->getChild("not-ends-with", false )).valid() ) {
293     return checkCondition( n, false, &ends_with, "not-ends-with") ?
294            processTokens(token->getChild("then",false)) :
295            processTokens(token->getChild("else",false));
296   }
297
298   if( (n = token->getChild("equals", false )).valid() ) {
299     return checkCondition( n, true, &equals, "equals") ?
300            processTokens(token->getChild("then",false)) : 
301            processTokens(token->getChild("else",false));
302   }
303
304   if( (n = token->getChild("not-equals", false )).valid() ) {
305     return checkCondition( n, false, &equals, "not-equals") ?
306            processTokens(token->getChild("then",false)) :
307            processTokens(token->getChild("else",false));
308   }
309
310   if( (n = token->getChild("starts-with", false )).valid() ) {
311     return checkCondition( n, true, &starts_with, "starts-with") ?
312            processTokens(token->getChild("then",false)) :
313            processTokens(token->getChild("else",false));
314   }
315
316   if( (n = token->getChild("not-starts-with", false )).valid() ) {
317     return checkCondition( n, false, &starts_with, "not-starts-with") ?
318            processTokens(token->getChild("then",false)) : 
319            processTokens(token->getChild("else",false));
320   }
321
322   SG_LOG(SG_ATC, SG_WARN, "ATISEncoder: no valid token found for <if> element" );
323
324   return EMPTY;
325 }
326
327 bool ATISEncoder::checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty ) 
328 {
329   SGPropertyNode_ptr n1 = node->getNode( "token", false );
330   if( false == n1.valid() ) {
331       SG_LOG(SG_ATC, SG_WARN, "missing <token> node for (not)-empty"  );
332       return false;
333   }
334   return processToken( n1 ).empty() == isEmpty;
335 }
336
337 bool ATISEncoder::checkCondition( SGPropertyNode_ptr node, bool notInverted,
338     bool (*fp)(const string &, const string &), const string &name )
339 {
340   using namespace simgear::strutils;
341
342   SGPropertyNode_ptr n1 = node->getNode( "token", 0, false );
343   SGPropertyNode_ptr n2 = node->getNode( "token", 1, false );
344
345   if( n1.valid() && n2.valid() ) {
346     bool comp = fp( processToken( n1 ), processToken( n2 ) );
347     return comp == notInverted;
348   }
349
350   if( n1.valid() && !n2.valid() ) {
351     SGPropertyNode_ptr t1 = node->getNode( "text", 0, false );
352     if( t1.valid() ) {
353       string n1s = lowercase( strip( processToken( n1 ) ) );
354       string t1s = lowercase( strip( processTextToken( t1 ) ) );
355       return fp( n1s, t1s ) == notInverted;
356     }
357     SG_LOG(SG_ATC, SG_WARN, "missing <token> or <text> node for " << name);
358     return false;
359   }
360
361   SG_LOG(SG_ATC, SG_WARN, "missing <token> node for " << name);
362   return false;
363 }
364
365 string ATISEncoder::getAtisId( SGPropertyNode_ptr )
366 {
367   FGAirportDynamics * dynamics = airport->getDynamics();
368   if( NULL != dynamics ) {
369     dynamics->updateAtisSequence( 30*60, false );
370     return dynamics->getAtisSequence();
371   }
372   return EMPTY;
373 }
374
375 string ATISEncoder::getAirportName( SGPropertyNode_ptr )
376 {
377   return airport->getName();
378 }
379
380 string ATISEncoder::getTime( SGPropertyNode_ptr )
381 {
382   return getSpokenNumber( _atis->getTime() % (100*100), true, 4 );
383 }
384
385 static inline FGRunwayRef findBestRunwayForWind( FGAirportRef airport, int windDeg, int windKt )
386 {
387   struct FGAirport::FindBestRunwayForHeadingParams p;
388   //TODO: ramp down the heading weight with wind speed
389   p.ilsWeight = 4;
390   return airport->findBestRunwayForHeading( windDeg, &p );
391 }
392
393 string ATISEncoder::getApproachType( SGPropertyNode_ptr )
394 {
395   FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
396   if( runway.valid() ) {
397     if( NULL != runway->ILS() ) return globals->get_locale()->getLocalizedString("ils", "atc", "ils" );
398     //TODO: any chance to find other approach types? localizer-dme, vor-dme, vor, ndb?
399   }
400
401   return globals->get_locale()->getLocalizedString("visual", "atc", "visual" );
402 }
403
404 string ATISEncoder::getLandingRunway( SGPropertyNode_ptr )
405 {
406   FGRunwayRef runway = findBestRunwayForWind( airport, _atis->getWindDeg(), _atis->getWindSpeedKt() );
407   if( runway.valid() ) {
408     string runwayIdent = runway->ident();
409     if(runwayIdent != "NN") {
410       return getSpokenNumber(runwayIdent);
411     }
412   }
413   return EMPTY;
414 }
415
416 string ATISEncoder::getTakeoffRunway( SGPropertyNode_ptr p )
417 {
418   //TODO: if the airport has more than one runway, probably pick another one?
419   return getLandingRunway( p );
420 }
421
422 string ATISEncoder::getTransitionLevel( SGPropertyNode_ptr )
423 {
424   double hPa = _atis->getQnh();
425
426   /* Transition level is the flight level above which aircraft must use standard pressure and below
427    * which airport pressure settings must be used.
428    * Following definitions are taken from German ATIS:
429    *      QNH <=  977 hPa: TRL 80
430    *      QNH <= 1013 hPa: TRL 70
431    *      QNH >  1013 hPa: TRL 60
432    * (maybe differs slightly for other countries...)
433    */
434   int tl;
435   if (hPa <= 978) {
436     tl = 80;
437   } else if( hPa > 978 && hPa <= 1013 ) {
438     tl = 70;
439   } else if( hPa > 1013 && hPa <= 1046 ) {
440     tl = 60;
441   } else {
442     tl = 50;
443   }
444
445   // add an offset to the transition level for high altitude airports (just guessing here,
446   // seems reasonable)
447   int e = int(airport->getElevation() / 1000.0);
448   if (e >= 3) {
449     // TL steps in 10(00)ft
450     tl += (e-2)*10;
451   }
452
453   return getSpokenNumber(tl);
454 }
455
456 string ATISEncoder::getWindDirection( SGPropertyNode_ptr )
457 {
458   string variable = globals->get_locale()->getLocalizedString("variable", "atc", "variable" );
459
460   bool vrb = _atis->getWindMinDeg() == 0 && _atis->getWindMaxDeg() == 359;
461   return vrb ? variable : getSpokenNumber( _atis->getWindDeg(), true, 3 );
462 }
463
464 string ATISEncoder::getWindMinDirection( SGPropertyNode_ptr )
465 {
466   return getSpokenNumber( _atis->getWindMinDeg(), true, 3 );
467 }
468
469 string ATISEncoder::getWindMaxDirection( SGPropertyNode_ptr )
470 {
471   return getSpokenNumber( _atis->getWindMaxDeg(), true, 3 );
472 }
473
474 string ATISEncoder::getWindspeedKnots( SGPropertyNode_ptr )
475 {
476   return getSpokenNumber( _atis->getWindSpeedKt() );
477 }
478
479 string ATISEncoder::getGustsKnots( SGPropertyNode_ptr )
480 {
481   int g = _atis->getGustsKt();
482   return g > 0 ? getSpokenNumber( g ) : EMPTY;
483 }
484
485 string ATISEncoder::getCavok( SGPropertyNode_ptr )
486 {
487   string CAVOK = globals->get_locale()->getLocalizedString("cavok", "atc", "cavok" );
488
489   return _atis->isCavok() ? CAVOK : EMPTY;
490 }
491
492 string ATISEncoder::getVisibilityMetric( SGPropertyNode_ptr )
493 {
494   string m = globals->get_locale()->getLocalizedString("meters", "atc", "meters" );
495   string km = globals->get_locale()->getLocalizedString("kilometers", "atc", "kilometers" );
496   string or_more = globals->get_locale()->getLocalizedString("ormore", "atc", "or more" );
497
498   int v = _atis->getVisibilityMeters();
499   string reply;
500   if( v < 5000 ) return reply.append( getSpokenAltitude( v ) ).SPACE.append( m );
501   if( v >= 9999 ) return reply.append( getSpokenNumber(10) ).SPACE.append( km ).SPACE.append(or_more);
502   return reply.append( getSpokenNumber( v/1000 ).SPACE.append( km ) );
503 }
504
505 string ATISEncoder::getVisibilityMiles( SGPropertyNode_ptr )
506 {
507   string feet = globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
508
509   int v = _atis->getVisibilityMeters();
510   int vft = int( v * SG_METER_TO_FEET / 100 + 0.5 ) * 100; // Rounded to 100 ft
511   int vsm = int( v * SG_METER_TO_SM + 0.5 );
512
513   string reply;
514   if( vsm < 1 ) return reply.append( getSpokenAltitude( vft ) ).SPACE.append( feet );
515   if( vsm >= 10 ) return reply.append( getSpokenNumber(10) );
516   return reply.append( getSpokenNumber( vsm ) );
517 }
518
519 string ATISEncoder::getPhenomena( SGPropertyNode_ptr )
520 {
521   return _atis->getPhenomena();
522 }
523
524 string ATISEncoder::getClouds( SGPropertyNode_ptr )
525 {
526   string FEET =  globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
527   string reply;
528
529   ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
530
531   for( ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); it++ ) {
532     if( false == reply.empty() ) reply.SPACE;
533     reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first).SPACE.append( FEET ) );
534   }
535   return reply;
536 }
537
538 string ATISEncoder::getCloudsBrief( SGPropertyNode_ptr )
539 {
540   string reply;
541
542   ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
543
544   for( ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); it++ ) {
545     if( false == reply.empty() ) reply.append(",").SPACE;
546     reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first) );
547   }
548   return reply;
549 }
550
551 string ATISEncoder::getTemperatureDeg( SGPropertyNode_ptr )
552 {
553   return getSpokenNumber( _atis->getTemperatureDeg() );
554 }
555
556 string ATISEncoder::getDewpointDeg( SGPropertyNode_ptr )
557 {
558   return getSpokenNumber( _atis->getDewpointDeg() );
559 }
560
561 string ATISEncoder::getQnh( SGPropertyNode_ptr )
562 {
563   return getSpokenNumber( _atis->getQnh() );
564 }
565
566 string ATISEncoder::getInhgInteger( SGPropertyNode_ptr )
567 {
568   double qnh = _atis->getQnhInHg();
569   return getSpokenNumber( (int)qnh, true, 2 );
570 }
571
572 string ATISEncoder::getInhgFraction( SGPropertyNode_ptr )
573 {
574   double qnh = _atis->getQnhInHg();
575   int f = int(100 * (qnh - int(qnh)) + 0.5);
576   return getSpokenNumber( f, true, 2 );
577 }
578
579 string ATISEncoder::getInhg( SGPropertyNode_ptr node)
580 {
581   string DECIMAL = globals->get_locale()->getLocalizedString("dp", "atc", "decimal" );
582   return getInhgInteger(node)
583     .SPACE.append(DECIMAL).SPACE
584     .append(getInhgFraction(node));
585 }
586
587 string ATISEncoder::getTrend( SGPropertyNode_ptr )
588 {
589   return _atis->getTrend();
590 }
591