FlightGear next
metarproperties.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: metarproperties.cxx
3 * SPDX-FileComment: Parse a METAR and write properties
4 * SPDX-FileCopyrightText: Copyright (C) 2002 David Megginson - david@megginson.com
5 * SPDX-FileContributor: Rewritten by Torsten Dreyer, August 2010
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#ifdef HAVE_CONFIG_H
10# include <config.h>
11#endif
12
13#include <cstring> // for strlen
14
15#include "metarproperties.hxx"
16#include "environment_mgr.hxx"
17#include "fgmetar.hxx"
18#include "environment.hxx"
19#include "atmosphere.hxx"
21#include <simgear/scene/sky/cloud.hxx>
22#include <simgear/structure/exception.hxx>
23#include <simgear/misc/strutils.hxx>
24#include <simgear/magvar/magvar.hxx>
25#include <simgear/timing/sg_time.hxx>
26#include <Main/fg_props.hxx>
27
28using std::string;
29
30namespace Environment {
31
32static std::vector<string> coverage_string;
33
38class MagneticVariation : public SGMagVar {
39public:
43 MagneticVariation() : _lat(1), _lon(1), _alt(1) {
44 recalc( 0.0, 0.0, 0.0 );
45 }
46
54 double get_variation_deg( double lon, double lat, double alt );
55
63 double get_dip_deg( double lon, double lat, double alt );
64private:
65 void recalc( double lon, double lat, double alt );
66 SGTime _time;
67 double _lat, _lon, _alt;
68};
69
70inline void MagneticVariation::recalc( double lon, double lat, double alt )
71{
72 // calculation of magnetic variation is expensive. Cache the position
73 // and perform this calculation only if it has changed
74 if( _lon != lon || _lat != lat || _alt != alt ) {
75 SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "Recalculating magvar for lon=" << lon << ", lat=" << lat << ", alt=" << alt );
76 _lon = lon;
77 _lat = lat;
78 _alt = alt;
79
80 SGGeod location(SGGeod::fromDegFt(lon, lat, alt));
81 _time.update( location, 0, 0 );
82 update( lon, lat, alt, _time.getJD() );
83 }
84}
85
86inline double MagneticVariation::get_variation_deg( double lon, double lat, double alt )
87{
88 recalc( lon, lat, alt );
89 return get_magvar() * SGD_RADIANS_TO_DEGREES;
90}
91
92inline double MagneticVariation::get_dip_deg( double lon, double lat, double alt )
93{
94 recalc( lon, lat, alt );
95 return get_magdip() * SGD_RADIANS_TO_DEGREES;
96}
97
98MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
99 _rootNode(rootNode),
100 _metarValidNode( rootNode->getNode( "valid", true ) ),
101 _station_elevation(0.0),
102 _station_latitude(0.0),
103 _station_longitude(0.0),
104 _min_visibility(16000.0),
105 _max_visibility(16000.0),
106 _base_wind_dir(0),
107 _base_wind_range_from(0),
108 _base_wind_range_to(0),
109 _wind_speed(0.0),
110 _wind_from_north_fps(0.0),
111 _wind_from_east_fps(0.0),
112 _gusts(0.0),
113 _temperature(0.0),
114 _dewpoint(0.0),
115 _humidity(0.0),
116 _pressure(0.0),
117 _sea_level_temperature(0.0),
118 _sea_level_dewpoint(0.0),
119 _sea_level_pressure(29.92),
120 _rain(0.0),
121 _hail(0.0),
122 _snow(0.0),
123 _snow_cover(false),
124 _day(0),
125 _hour(0),
126 _minute(0),
127 _cavok(false),
129{
130 // Hack to avoid static initialization order problems on OSX
131 if( coverage_string.empty() ) {
132 coverage_string.push_back(SGCloudLayer::SG_CLOUD_CLEAR_STRING);
133 coverage_string.push_back(SGCloudLayer::SG_CLOUD_FEW_STRING);
134 coverage_string.push_back(SGCloudLayer::SG_CLOUD_SCATTERED_STRING);
135 coverage_string.push_back(SGCloudLayer::SG_CLOUD_BROKEN_STRING);
136 coverage_string.push_back(SGCloudLayer::SG_CLOUD_OVERCAST_STRING);
137 }
138 // don't tie metar-valid, so listeners get triggered
139 _metarValidNode->setBoolValue( false );
140 _tiedProperties.setRoot( _rootNode );
141 _tiedProperties.Tie("data", this, &MetarProperties::get_metar, &MetarProperties::set_metar );
142 _tiedProperties.Tie("station-id", this, &MetarProperties::get_station_id, &MetarProperties::set_station_id );
143 _tiedProperties.Tie("station-elevation-ft", &_station_elevation );
144 _tiedProperties.Tie("station-latitude-deg", &_station_latitude );
145 _tiedProperties.Tie("station-longitude-deg", &_station_longitude );
146 _tiedProperties.Tie("station-magnetic-variation-deg", this, &MetarProperties::get_magnetic_variation_deg );
147 _tiedProperties.Tie("station-magnetic-dip-deg", this, &MetarProperties::get_magnetic_dip_deg );
148 _tiedProperties.Tie("min-visibility-m", &_min_visibility );
149 _tiedProperties.Tie("max-visibility-m", &_max_visibility );
150 _tiedProperties.Tie("base-wind-range-from", &_base_wind_range_from );
151 _tiedProperties.Tie("base-wind-range-to", &_base_wind_range_to );
152 _tiedProperties.Tie("base-wind-speed-kt", this, &MetarProperties::get_wind_speed, &MetarProperties::set_wind_speed );
153 _tiedProperties.Tie("base-wind-dir-deg", this, &MetarProperties::get_base_wind_dir, &MetarProperties::set_base_wind_dir );
154 _tiedProperties.Tie("base-wind-from-north-fps", this, &MetarProperties::get_wind_from_north_fps, &MetarProperties::set_wind_from_north_fps );
155 _tiedProperties.Tie("base-wind-from-east-fps",this, &MetarProperties::get_wind_from_east_fps, &MetarProperties::set_wind_from_east_fps );
156 _tiedProperties.Tie("gust-wind-speed-kt", &_gusts );
157 _tiedProperties.Tie("temperature-degc", &_temperature );
158 _tiedProperties.Tie("dewpoint-degc", &_dewpoint );
159 _tiedProperties.Tie("rel-humidity-norm", &_humidity );
160 _tiedProperties.Tie("pressure-inhg", &_pressure );
161 _tiedProperties.Tie("temperature-sea-level-degc", &_sea_level_temperature );
162 _tiedProperties.Tie("dewpoint-sea-level-degc", &_sea_level_dewpoint );
163 _tiedProperties.Tie("pressure-sea-level-inhg", &_sea_level_pressure );
164 _tiedProperties.Tie("rain-norm", &_rain );
165 _tiedProperties.Tie("hail-norm", &_hail );
166 _tiedProperties.Tie("snow-norm", &_snow);
167 _tiedProperties.Tie("snow-cover", &_snow_cover );
168 _tiedProperties.Tie("day", &_day );
169 _tiedProperties.Tie("hour", &_hour );
170 _tiedProperties.Tie("minute", &_minute );
171 _tiedProperties.Tie("decoded", this, &MetarProperties::get_decoded );
172 _tiedProperties.Tie("cavok", &_cavok );
173 _tiedProperties.Tie("description", this, &MetarProperties::get_description );
174
175 // mark proeprties as listener-safe, we invoke valueChanged explicitly
176 _tiedProperties.setAttribute(SGPropertyNode::LISTENER_SAFE, true);
177}
178
183
185{
186 if( _metarValidNode->getBoolValue() )
187 _metarValidNode->setBoolValue(false);
188}
189
190static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
191
192const char* MetarProperties::get_metar() const
193{
194 if (!_metar || _metarData.empty())
195 return "";
196 return _metarData.c_str();
197}
198
199void MetarProperties::set_metar( const char * metarString )
200{
201 SGSharedPtr<FGMetar> m;
202 if (!metarString) {
203 setMetar(m);
204 return;
205 }
206
207 std::string trimmedMetar = simgear::strutils::strip(std::string{metarString});
208 if (trimmedMetar.empty()) {
209 setMetar(m);
210 return;
211 }
212
213 try {
214 m = new FGMetar(trimmedMetar);
215 }
216 catch( sg_io_exception& ) {
217 SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar:'" << trimmedMetar << "'");
218 _metarValidNode->setBoolValue(false);
219 return;
220 }
221
222 setMetar(m);
223}
224
225void MetarProperties::setMetar( SGSharedPtr<FGMetar> m )
226{
227 _metar = m;
228 _decoded.clear();
229 if (!m) {
230 _metarData.clear();
231 return;
232 }
233
234 // copy the string so we have guranteed storage for get_metar tied property API
235 _metarData = _metar->getDataString();
236
237 const std::vector<string> weather = m->getWeather();
238 for( std::vector<string>::const_iterator it = weather.begin(); it != weather.end(); ++it ) {
239 if( !_decoded.empty() ) _decoded.append(", ");
240 _decoded.append(*it);
241 }
242
243 _min_visibility = m->getMinVisibility().getVisibility_m();
244 _max_visibility = m->getMaxVisibility().getVisibility_m();
245
246 const SGMetarVisibility *dirvis = m->getDirVisibility();
247 for ( int i = 0; i < 8; i++, dirvis++) {
248 SGPropertyNode *vis = _rootNode->getChild("visibility", i, true);
249 double v = dirvis->getVisibility_m();
250
251 vis->setDoubleValue("min-m", v);
252 vis->setDoubleValue("max-m", v);
253 }
254
255 set_base_wind_dir(m->getWindDir());
256 _base_wind_range_from = m->getWindRangeFrom();
257 _base_wind_range_to = m->getWindRangeTo();
258 set_wind_speed(m->getWindSpeed_kt());
259
260 _gusts = m->getGustSpeed_kt();
261 _temperature = m->getTemperature_C();
262 _dewpoint = m->getDewpoint_C();
263 _humidity = m->getRelHumidity();
264 _pressure = m->getPressure_inHg();
265
266 {
267 // 1. check the id given in the metar
268 FGAirport* a = FGAirport::findByIdent(m->getId());
269
270 // 2. if unknown, find closest airport with metar to current position
271 if( a == NULL ) {
272 SGGeod pos = SGGeod::fromDeg(
273 fgGetDouble( "/position/longitude-deg", 0.0 ),
274 fgGetDouble( "/position/latitude-deg", 0.0 ) );
276 }
277
278 // 3. otherwise use ground elevation
279 if( a != NULL ) {
280 _station_elevation = a->getElevation();
281 const SGGeod & towerPosition = a->getTowerLocation();
282 _station_latitude = towerPosition.getLatitudeDeg();
283 _station_longitude = towerPosition.getLongitudeDeg();
284 _station_id = a->ident();
285 } else {
286 _station_elevation = fgGetDouble("/position/ground-elev-m", 0.0 ) * SG_METER_TO_FEET;
287 _station_latitude = fgGetDouble( "/position/latitude-deg", 0.0 );
288 _station_longitude = fgGetDouble( "/position/longitude-deg", 0.0 );
289 _station_id = "XXXX";
290 }
291 }
292
293 { // calculate sea level temperature, dewpoint and pressure
294 FGEnvironment dummy; // instantiate a dummy so we can leech a method
295 dummy.set_is_isa( globals->get_subsystem<FGEnvironmentMgr>()->getEnvironment().get_is_isa() );
296 dummy.set_elevation_ft( _station_elevation );
297 dummy.set_temperature_degc( _temperature );
298 dummy.set_dewpoint_degc( _dewpoint );
299 _sea_level_temperature = dummy.get_temperature_sea_level_degc();
300 _sea_level_dewpoint = dummy.get_dewpoint_sea_level_degc();
301
302 double elevation_m = _station_elevation * SG_FEET_TO_METER;
303 double fieldPressure = FGAtmo::fieldPressure( elevation_m, _pressure * atmodel::inHg );
304 _sea_level_pressure = P_layer(0, elevation_m, fieldPressure, _temperature + atmodel::freezing, atmodel::ISA::lam0) / atmodel::inHg;
305 }
306
307 bool isBC = false;
308 bool isBR = false;
309 bool isFG = false;
310 bool isMI = false;
311 bool isHZ = false;
312
313 {
314 for( unsigned i = 0; i < 3; i++ ) {
315 SGPropertyNode_ptr n = _rootNode->getChild("weather", i, true );
316 std::vector<struct SGMetar::Weather> weather = m->getWeather2();
317 struct SGMetar::Weather * w = i < weather.size() ? &weather[i] : NULL;
318 n->getNode("intensity",true)->setIntValue( w != NULL ? w->intensity : 0 );
319 n->getNode("vincinity",true)->setBoolValue( w != NULL ? w->vincinity : false );
320 for( unsigned j = 0; j < 3; j++ ) {
321
322 const string & phenomenon = w != NULL && j < w->phenomena.size() ? w->phenomena[j].c_str() : "";
323 n->getChild( "phenomenon", j, true )->setStringValue( phenomenon );
324
325 const string & description = w != NULL && j < w->descriptions.size() ? w->descriptions[j].c_str() : "";
326 n->getChild( "description", j, true )->setStringValue( description );
327
328 // need to know later,
329 // if its fog(FG) (might be shallow(MI) or patches(BC)) or haze (HZ) or mist(BR)
330 if( phenomenon == "FG" ) isFG = true;
331 if( phenomenon == "HZ" ) isHZ = true;
332 if( phenomenon == "BR" ) isBR = true;
333 if( description == "MI" ) isMI = true;
334 if( description == "BC" ) isBC = true;
335 }
336 }
337 }
338
339 {
340 static const char * LAYER = "layer";
341 SGPropertyNode_ptr cloudsNode = _rootNode->getNode("clouds", true );
342 const std::vector<SGMetarCloud> & metarClouds = m->getClouds();
343 unsigned layerOffset = 0; // Oh, this is ugly!
344
345 // fog/mist/haze cloud layer does not work with 3d clouds yet :-(
346 bool setGroundCloudLayer = _rootNode->getBoolValue("set-ground-cloud-layer", false ) &&
347 !fgGetBool("/sim/rendering/clouds3d-enable", false);
348
349 // track the coverage of the previous layer, so we can use it
350 // for higher layers which don't have coverage set
351 // see: https://sourceforge.net/p/flightgear/codetickets/2765/
352 SGMetarCloud::Coverage coverageBelow = SGMetarCloud::COVERAGE_NIL;
353
354 if( setGroundCloudLayer ) {
355 // create a cloud layer #0 starting at the ground if its fog, mist or haze
356
357 // make sure layer actually starts at ground and set it's bottom at a constant
358 // value below the station's elevation
359 const double LAYER_BOTTOM_STATION_OFFSET =
360 fgGetDouble( "/environment/params/fog-mist-haze-layer/offset-from-station-elevation-ft", -200 );
361
362 SGMetarCloud::Coverage coverage = SGMetarCloud::COVERAGE_NIL;
363 double thickness = 0;
364 double alpha = 1.0;
365
366 if( isFG ) { // fog
367 coverage = SGMetarCloud::getCoverage( isBC ?
368 fgGetString( "/environment/params/fog-mist-haze-layer/fog-bc-2dlayer-coverage", SGMetarCloud::COVERAGE_SCATTERED_STRING ) :
369 fgGetString( "/environment/params/fog-mist-haze-layer/fog-2dlayer-coverage", SGMetarCloud::COVERAGE_BROKEN_STRING )
370 );
371
372 thickness = isMI ?
373 fgGetDouble("/environment/params/fog-mist-haze-layer/fog-shallow-thickness-ft",30) - LAYER_BOTTOM_STATION_OFFSET : // shallow fog, 10m/30ft
374 fgGetDouble("/environment/params/fog-mist-haze-layer/fog-thickness-ft",500) - LAYER_BOTTOM_STATION_OFFSET; // fog, 150m/500ft
375 alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/fog-2dlayer-alpha", 1.0);
376 } else if( isBR ) { // mist
377 coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING));
378 thickness = fgGetDouble("/environment/params/fog-mist-haze-layer/mist-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET;
379 alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/mist-2dlayer-alpha",0.8);
380 } else if( isHZ ) { // haze
381 coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING));
382 thickness = fgGetDouble("/environment/params/fog-mist-haze-layer/haze-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET;
383 alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/haze-2dlayer-alpha",0.6);
384 }
385
386 if( coverage != SGMetarCloud::COVERAGE_NIL ) {
387
388 // if there is a layer above the fog, limit the top to one foot below that layer's bottom
389 if( metarClouds.size() > 0 && metarClouds[0].getCoverage() != SGMetarCloud::COVERAGE_CLEAR )
390 thickness = metarClouds[0].getAltitude_ft() - LAYER_BOTTOM_STATION_OFFSET - 1;
391
392 SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, 0, true );
393 layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) );
394 layerNode->setStringValue( "coverage", coverage_string[coverage] );
395 layerNode->setDoubleValue( "elevation-ft", _station_elevation + LAYER_BOTTOM_STATION_OFFSET );
396 layerNode->setDoubleValue( "thickness-ft", thickness );
397 layerNode->setDoubleValue( "visibility-m", _min_visibility );
398 layerNode->setDoubleValue( "alpha", alpha );
399 _min_visibility = _max_visibility =
400 fgGetDouble("/environment/params/fog-mist-haze-layer/visibility-above-layer-m",20000.0); // assume good visibility above the fog
401 layerOffset = 1; // shudder
402
403 coverageBelow = coverage;
404 }
405 }
406
407 for( unsigned i = 0; i < 5-layerOffset; i++ ) {
408 SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, i+layerOffset, true );
409 SGMetarCloud::Coverage coverage = i < metarClouds.size() ? metarClouds[i].getCoverage() : SGMetarCloud::COVERAGE_CLEAR;
410 if (coverage == SGMetarCloud::COVERAGE_NIL) {
411 coverage = coverageBelow; // invalid coverage, use value of layer below
412 } else {
413 coverageBelow = coverage; // valid coverage, save for future layers
414 }
415
416 if (coverage == SGMetarCloud::COVERAGE_NIL) {
417 SG_LOG(SG_ENVIRONMENT, SG_WARN, "METAR: skipping cloud layer " << i << " because no coverage is set");
418 continue;
419 }
420
421 double elevation =
422 i >= metarClouds.size() || coverage == SGMetarCloud::COVERAGE_CLEAR ?
423 -9999.0 :
424 metarClouds[i].getAltitude_ft() + _station_elevation;
425
426 layerNode->setDoubleValue( "alpha", 1.0 );
427 layerNode->setStringValue( "coverage", coverage_string[coverage] );
428 layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) );
429 layerNode->setDoubleValue( "elevation-ft", elevation );
430 layerNode->setDoubleValue( "thickness-ft", thickness_value[coverage]);
431 layerNode->setDoubleValue( "span-m", 40000 );
432 layerNode->setDoubleValue( "visibility-m", 50.0 );
433 }
434 }
435
436 _rain = m->getRain();
437 _hail = m->getHail();
438 _snow = m->getSnow();
439 _snow_cover = m->getSnowCover();
440 _day = m->getDay();
441 _hour = m->getHour();
442 _minute = m->getMinute();
443 _cavok = m->getCAVOK();
444 _tiedProperties.fireValueChanged();
445 _metarValidNode->setBoolValue(true);
446 _description = m->getDescription(-1);
447}
448
449void MetarProperties::setStationId( const std::string & value )
450{
451 set_station_id(simgear::strutils::strip(value).c_str());
452}
453
454double MetarProperties::get_magnetic_variation_deg() const
455{
456 return _magneticVariation->get_variation_deg( _station_longitude, _station_latitude, _station_elevation );
457}
458
459double MetarProperties::get_magnetic_dip_deg() const
460{
461 return _magneticVariation->get_dip_deg( _station_longitude, _station_latitude, _station_elevation );
462}
463
464static inline void calc_wind_hs( double north_fps, double east_fps, int & heading_deg, double & speed_kt )
465{
466 speed_kt = sqrt((north_fps)*(north_fps)+(east_fps)*(east_fps)) * 3600.0 / (SG_NM_TO_METER * SG_METER_TO_FEET);
467 heading_deg = SGMiscd::roundToInt(
468 SGMiscd::normalizeAngle2( atan2( east_fps, north_fps ) ) * SGD_RADIANS_TO_DEGREES );
469}
470
471void MetarProperties::set_wind_from_north_fps( double value )
472{
473 _wind_from_north_fps = value;
474 calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed );
475}
476
477void MetarProperties::set_wind_from_east_fps( double value )
478{
479 _wind_from_east_fps = value;
480 calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed );
481}
482
483static inline void calc_wind_ne( double heading_deg, double speed_kt, double & north_fps, double & east_fps )
484{
485 double speed_fps = speed_kt * SG_NM_TO_METER * SG_METER_TO_FEET / 3600.0;
486 north_fps = speed_fps * cos(heading_deg * SGD_DEGREES_TO_RADIANS);
487 east_fps = speed_fps * sin(heading_deg * SGD_DEGREES_TO_RADIANS);
488}
489
490void MetarProperties::set_base_wind_dir( double value )
491{
492 _base_wind_dir = value;
493 calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps );
494}
495
496void MetarProperties::set_wind_speed( double value )
497{
498 _wind_speed = value;
499 calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps );
500}
501
502
503} // namespace Environment
#define i(x)
double P_layer(const double height, const double href, const double Pref, const double Tref, const double lapse)
Helper class to wrap SGMagVar functionality and cache the variation and dip for a certain position.
double get_dip_deg(double lon, double lat, double alt)
get the magnetic dip for a specific position at the current time
double get_variation_deg(double lon, double lat, double alt)
get the magnetic variation for a specific position at the current time
static MetarAirportFilter * instance()
virtual void setStationId(const std::string &value)
MetarProperties(SGPropertyNode_ptr rootNode)
MagneticVariation * _magneticVariation
virtual void setMetar(SGSharedPtr< FGMetar > m)
simgear::TiedPropertyList _tiedProperties
double getElevation() const
Definition airport.hxx:61
static FGAirportRef findClosest(const SGGeod &aPos, double aCuttofNm, Filter *filter=NULL)
Syntactic wrapper around FGPositioned::findClosest - find the closest match for filter,...
Definition airport.cxx:425
static FGAirportRef findByIdent(const std::string &aIdent)
Helper to look up an FGAirport instance by unique ident.
Definition airport.cxx:489
SGGeod getTowerLocation() const
Definition airport.cxx:696
static double fieldPressure(const double field_elev, const double qnh)
Invert the QNH calculation to get the field pressure from a metar report.
Manage environment information.
virtual FGEnvironment getEnvironment() const
Get the environment information for the plane's current position.
Model the natural environment.
virtual void set_dewpoint_degc(double d)
virtual void set_is_isa(bool isa)
virtual double get_dewpoint_sea_level_degc() const
virtual bool get_is_isa() const
virtual double get_temperature_sea_level_degc() const
virtual void set_elevation_ft(double elevation_ft)
virtual void set_temperature_degc(double t)
const std::string & ident() const
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
static void calc_wind_ne(double heading_deg, double speed_kt, double &north_fps, double &east_fps)
static const double thickness_value[]
static std::vector< string > coverage_string
static void calc_wind_hs(double north_fps, double east_fps, int &heading_deg, double &speed_kt)
const double lam0(.0065)
const double inHg(101325.0/760 *1000 *inch)
const double freezing(273.15)
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30