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