~ubuntu-branches/ubuntu/precise/flightgear/precise

« back to all changes in this revision

Viewing changes to src/Environment/metarproperties.cxx

  • Committer: Package Import Robot
  • Author(s): Ove Kaaven
  • Date: 2011-09-03 22:16:12 UTC
  • mfrom: (3.1.9 sid)
  • Revision ID: package-import@ubuntu.com-20110903221612-2cjy0z7ztj5nkln5
Tags: 2.4.0-1
* New upstream release. Closes: #638588.
* Build-Depend on OpenSceneGraph 3.0, and the Subversion library.
* Recommend fgfs-scenery-base.
* Enable parallel builds (shorter compile times on multicore CPUs).
* Removed hack that tried to build without optimizations if
  building with optimizations fails.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// metarproperties.cxx -- Parse a METAR and write properties
 
2
//
 
3
// Written by David Megginson, started May 2002.
 
4
// Rewritten by Torsten Dreyer, August 2010
 
5
//
 
6
// Copyright (C) 2002  David Megginson - david@megginson.com
 
7
//
 
8
// This program is free software; you can redistribute it and/or
 
9
// modify it under the terms of the GNU General Public License as
 
10
// published by the Free Software Foundation; either version 2 of the
 
11
// License, or (at your option) any later version.
 
12
//
 
13
// This program is distributed in the hope that it will be useful, but
 
14
// WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
16
// General Public License for more details.
 
17
//
 
18
// You should have received a copy of the GNU General Public License
 
19
// along with this program; if not, write to the Free Software
 
20
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
21
//
 
22
 
 
23
#ifdef HAVE_CONFIG_H
 
24
#  include <config.h>
 
25
#endif
 
26
 
 
27
#include "metarproperties.hxx"
 
28
#include "fgmetar.hxx"
 
29
#include "environment.hxx"
 
30
#include "atmosphere.hxx"
 
31
#include "metarairportfilter.hxx"
 
32
#include <simgear/scene/sky/cloud.hxx>
 
33
#include <simgear/structure/exception.hxx>
 
34
#include <simgear/misc/strutils.hxx>
 
35
#include <simgear/magvar/magvar.hxx>
 
36
#include <simgear/timing/sg_time.hxx>
 
37
#include <Main/fg_props.hxx>
 
38
 
 
39
using std::string;
 
40
 
 
41
namespace Environment {
 
42
 
 
43
static vector<string> coverage_string;
 
44
 
 
45
/**
 
46
 * @brief Helper class to wrap SGMagVar functionality and cache the variation and dip for
 
47
 *        a certain position. 
 
48
 */
 
49
class MagneticVariation : public SGMagVar {
 
50
public:
 
51
  /**
 
52
   * Constructor
 
53
   */
 
54
  MagneticVariation() : _lat(1), _lon(1), _alt(1) {
 
55
    recalc( 0.0, 0.0, 0.0 );
 
56
  }
 
57
 
 
58
  /**
 
59
   * @brief get the magnetic variation for a specific position at the current time
 
60
   * @param lon the positions longitude in degrees
 
61
   * @param lat the positions latitude in degrees
 
62
   * @param alt the positions height above MSL (aka altitude) in feet
 
63
   * @return the magnetic variation in degrees
 
64
   */
 
65
  double get_variation_deg( double lon, double lat, double alt );
 
66
 
 
67
  /**
 
68
   * @brief get the magnetic dip for a specific position at the current time
 
69
   * @param lon the positions longitude in degrees
 
70
   * @param lat the positions latitude in degrees
 
71
   * @param alt the positions height above MSL (aka altitude) in feet
 
72
   * @return the magnetic dip in degrees
 
73
   */
 
74
  double get_dip_deg( double lon, double lat, double alt );
 
75
private:
 
76
  void recalc( double lon, double lat, double alt );
 
77
  SGTime _time;
 
78
  double _lat, _lon, _alt;
 
79
};
 
80
 
 
81
inline void MagneticVariation::recalc( double lon, double lat, double alt )
 
82
{
 
83
  // calculation of magnetic variation is expensive. Cache the position
 
84
  // and perform this calculation only if it has changed
 
85
  if( _lon != lon || _lat != lat || _alt != alt ) {
 
86
    SG_LOG(SG_ALL, SG_DEBUG, "Recalculating magvar for lon=" << lon << ", lat=" << lat << ", alt=" << alt );
 
87
    _lon = lon;
 
88
    _lat = lat;
 
89
    _alt = alt;
 
90
 
 
91
    lon *= SGD_DEGREES_TO_RADIANS;
 
92
    lat *= SGD_DEGREES_TO_RADIANS;
 
93
    alt *= SG_FEET_TO_METER;
 
94
   _time.update( lon, lat, 0, 0 );
 
95
    update( lon, lat, alt, _time.getJD() );
 
96
  }
 
97
}
 
98
 
 
99
inline double MagneticVariation::get_variation_deg( double lon, double lat, double alt )
 
100
{
 
101
  recalc( lon, lat, alt );
 
102
  return get_magvar() * SGD_RADIANS_TO_DEGREES;
 
103
}
 
104
 
 
105
inline double MagneticVariation::get_dip_deg( double lon, double lat, double alt )
 
106
{
 
107
  recalc( lon, lat, alt );
 
108
  return get_magdip() * SGD_RADIANS_TO_DEGREES;
 
109
}
 
110
 
 
111
MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
 
112
  _rootNode(rootNode),
 
113
  _metarValidNode( rootNode->getNode( "valid", true ) ),
 
114
  _station_elevation(0.0),
 
115
  _station_latitude(0.0),
 
116
  _station_longitude(0.0),
 
117
  _min_visibility(16000.0),
 
118
  _max_visibility(16000.0),
 
119
  _base_wind_dir(0),
 
120
  _base_wind_range_from(0),
 
121
  _base_wind_range_to(0),
 
122
  _wind_speed(0.0),
 
123
  _wind_from_north_fps(0.0),
 
124
  _wind_from_east_fps(0.0),
 
125
  _gusts(0.0),
 
126
  _temperature(0.0),
 
127
  _dewpoint(0.0),
 
128
  _humidity(0.0),
 
129
  _pressure(0.0),
 
130
  _sea_level_temperature(0.0),
 
131
  _sea_level_dewpoint(0.0),
 
132
  _sea_level_pressure(29.92),
 
133
  _rain(0.0),
 
134
  _hail(0.0),
 
135
  _snow(0.0),
 
136
  _snow_cover(false),
 
137
  _magneticVariation(new MagneticVariation())
 
138
{
 
139
  // Hack to avoid static initialization order problems on OSX
 
140
  if( coverage_string.size() == 0 ) {
 
141
    coverage_string.push_back(SGCloudLayer::SG_CLOUD_CLEAR_STRING);
 
142
    coverage_string.push_back(SGCloudLayer::SG_CLOUD_FEW_STRING);
 
143
    coverage_string.push_back(SGCloudLayer::SG_CLOUD_SCATTERED_STRING);
 
144
    coverage_string.push_back(SGCloudLayer::SG_CLOUD_BROKEN_STRING);
 
145
    coverage_string.push_back(SGCloudLayer::SG_CLOUD_OVERCAST_STRING);
 
146
  }
 
147
  // don't tie metar-valid, so listeners get triggered
 
148
  _metarValidNode->setBoolValue( false );
 
149
  _tiedProperties.setRoot( _rootNode );
 
150
  _tiedProperties.Tie("data", this, &MetarProperties::get_metar, &MetarProperties::set_metar );
 
151
  _tiedProperties.Tie("station-id", this, &MetarProperties::get_station_id, &MetarProperties::set_station_id );
 
152
  _tiedProperties.Tie("station-elevation-ft", &_station_elevation );
 
153
  _tiedProperties.Tie("station-latitude-deg", &_station_latitude );
 
154
  _tiedProperties.Tie("station-longitude-deg", &_station_longitude );
 
155
  _tiedProperties.Tie("station-magnetic-variation-deg", this, &MetarProperties::get_magnetic_variation_deg );
 
156
  _tiedProperties.Tie("station-magnetic-dip-deg", this, &MetarProperties::get_magnetic_dip_deg );
 
157
  _tiedProperties.Tie("min-visibility-m", &_min_visibility );
 
158
  _tiedProperties.Tie("max-visibility-m", &_max_visibility );
 
159
  _tiedProperties.Tie("base-wind-range-from", &_base_wind_range_from );
 
160
  _tiedProperties.Tie("base-wind-range-to", &_base_wind_range_to );
 
161
  _tiedProperties.Tie("base-wind-speed-kt", this, &MetarProperties::get_wind_speed, &MetarProperties::set_wind_speed );
 
162
  _tiedProperties.Tie("base-wind-dir-deg", this, &MetarProperties::get_base_wind_dir, &MetarProperties::set_base_wind_dir );
 
163
  _tiedProperties.Tie("base-wind-from-north-fps", this, &MetarProperties::get_wind_from_north_fps, &MetarProperties::set_wind_from_north_fps );
 
164
  _tiedProperties.Tie("base-wind-from-east-fps",this, &MetarProperties::get_wind_from_east_fps, &MetarProperties::set_wind_from_east_fps );
 
165
  _tiedProperties.Tie("gust-wind-speed-kt", &_gusts );
 
166
  _tiedProperties.Tie("temperature-degc", &_temperature );
 
167
  _tiedProperties.Tie("dewpoint-degc", &_dewpoint );
 
168
  _tiedProperties.Tie("rel-humidity-norm", &_humidity );
 
169
  _tiedProperties.Tie("pressure-inhg", &_pressure );
 
170
  _tiedProperties.Tie("temperature-sea-level-degc", &_sea_level_temperature );
 
171
  _tiedProperties.Tie("dewpoint-sea-level-degc", &_sea_level_dewpoint );
 
172
  _tiedProperties.Tie("pressure-sea-level-inhg", &_sea_level_pressure );
 
173
  _tiedProperties.Tie("rain-norm", &_rain );
 
174
  _tiedProperties.Tie("hail-norm", &_hail );
 
175
  _tiedProperties.Tie("snow-norm", &_snow);
 
176
  _tiedProperties.Tie("snow-cover", &_snow_cover );
 
177
  _tiedProperties.Tie("decoded", this, &MetarProperties::get_decoded );
 
178
}
 
179
 
 
180
MetarProperties::~MetarProperties()
 
181
{
 
182
  delete _magneticVariation;
 
183
}
 
184
 
 
185
 
 
186
static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
 
187
 
 
188
void MetarProperties::set_metar( const char * metar )
 
189
{
 
190
    _metar = metar;
 
191
 
 
192
    SGSharedPtr<FGMetar> m;
 
193
    try {
 
194
        m = new FGMetar( _metar );
 
195
    }
 
196
    catch( sg_io_exception ) {
 
197
        SG_LOG( SG_GENERAL, SG_WARN, "Can't parse metar: " << _metar );
 
198
        _metarValidNode->setBoolValue(false);
 
199
        return;
 
200
    }
 
201
 
 
202
    _decoded.clear();
 
203
    const vector<string> weather = m->getWeather();
 
204
    for( vector<string>::const_iterator it = weather.begin(); it != weather.end(); it++ ) {
 
205
        if( false == _decoded.empty() ) _decoded.append(", ");
 
206
        _decoded.append(*it);
 
207
    }
 
208
 
 
209
    _min_visibility = m->getMinVisibility().getVisibility_m();
 
210
    _max_visibility = m->getMaxVisibility().getVisibility_m();
 
211
 
 
212
    const SGMetarVisibility *dirvis = m->getDirVisibility();
 
213
    for ( int i = 0; i < 8; i++, dirvis++) {
 
214
        SGPropertyNode *vis = _rootNode->getChild("visibility", i, true);
 
215
        double v = dirvis->getVisibility_m();
 
216
 
 
217
        vis->setDoubleValue("min-m", v);
 
218
        vis->setDoubleValue("max-m", v);
 
219
    }
 
220
 
 
221
    set_base_wind_dir(m->getWindDir());
 
222
    _base_wind_range_from = m->getWindRangeFrom();
 
223
    _base_wind_range_to = m->getWindRangeTo();
 
224
    set_wind_speed(m->getWindSpeed_kt());
 
225
 
 
226
    _gusts = m->getGustSpeed_kt();
 
227
    _temperature = m->getTemperature_C();
 
228
    _dewpoint = m->getDewpoint_C();
 
229
    _humidity = m->getRelHumidity();
 
230
    _pressure = m->getPressure_inHg();
 
231
 
 
232
    {
 
233
        // 1. check the id given in the metar
 
234
        FGAirport* a = FGAirport::findByIdent(m->getId());
 
235
 
 
236
        // 2. if unknown, find closest airport with metar to current position
 
237
        if( a == NULL ) {
 
238
            SGGeod pos = SGGeod::fromDeg(
 
239
                fgGetDouble( "/position/longitude-deg", 0.0 ),
 
240
                fgGetDouble( "/position/latitude-deg", 0.0 ) );
 
241
            a = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
 
242
        }
 
243
 
 
244
        // 3. otherwise use ground elevation
 
245
        if( a != NULL ) {
 
246
            _station_elevation = a->getElevation();
 
247
            const SGGeod & towerPosition = a->getTowerLocation();
 
248
            _station_latitude = towerPosition.getLatitudeDeg();
 
249
            _station_longitude = towerPosition.getLongitudeDeg();
 
250
            _station_id = a->ident();
 
251
        } else {
 
252
            _station_elevation = fgGetDouble("/position/ground-elev-m", 0.0 ) * SG_METER_TO_FEET;
 
253
            _station_latitude = fgGetDouble( "/position/latitude-deg", 0.0 );
 
254
            _station_longitude = fgGetDouble( "/position/longitude-deg", 0.0 );
 
255
            _station_id = "XXXX";
 
256
        }
 
257
    }
 
258
 
 
259
    {    // calculate sea level temperature, dewpoint and pressure
 
260
        FGEnvironment dummy; // instantiate a dummy so we can leech a method
 
261
        dummy.set_elevation_ft( _station_elevation );
 
262
        dummy.set_temperature_degc( _temperature );
 
263
        dummy.set_dewpoint_degc( _dewpoint );
 
264
        _sea_level_temperature = dummy.get_temperature_sea_level_degc();
 
265
        _sea_level_dewpoint = dummy.get_dewpoint_sea_level_degc();
 
266
 
 
267
        double elevation_m = _station_elevation * SG_FEET_TO_METER;
 
268
        double fieldPressure = FGAtmo::fieldPressure( elevation_m, _pressure * atmodel::inHg );
 
269
        _sea_level_pressure = P_layer(0, elevation_m, fieldPressure, _temperature + atmodel::freezing, atmodel::ISA::lam0) / atmodel::inHg;
 
270
    }
 
271
 
 
272
    bool isBC = false;
 
273
    bool isBR = false;
 
274
    bool isFG = false;
 
275
    bool isMI = false;
 
276
    bool isHZ = false;
 
277
 
 
278
    {
 
279
        for( unsigned i = 0; i < 3; i++ ) {
 
280
            SGPropertyNode_ptr n = _rootNode->getChild("weather", i, true );
 
281
            vector<struct SGMetar::Weather> weather = m->getWeather2();
 
282
            struct SGMetar::Weather * w = i < weather.size() ? &weather[i] : NULL;
 
283
            n->getNode("intensity",true)->setIntValue( w != NULL ? w->intensity : 0 );
 
284
            n->getNode("vincinity",true)->setBoolValue( w != NULL ? w->vincinity : false );
 
285
            for( unsigned j = 0; j < 3; j++ ) { 
 
286
 
 
287
                const string & phenomenon = w != NULL && j < w->phenomena.size() ? w->phenomena[j].c_str() : "";
 
288
                n->getChild( "phenomenon", j, true )->setStringValue( phenomenon );
 
289
 
 
290
                const string & description = w != NULL && j < w->descriptions.size() ? w->descriptions[j].c_str() : "";
 
291
                n->getChild( "description", j, true )->setStringValue( description );
 
292
 
 
293
                // need to know later, 
 
294
                // if its fog(FG) (might be shallow(MI) or patches(BC)) or haze (HZ) or mist(BR)
 
295
                if( phenomenon == "FG" ) isFG = true;
 
296
                if( phenomenon == "HZ" ) isHZ = true;
 
297
                if( phenomenon == "BR" ) isBR = true;
 
298
                if( description == "MI" ) isMI = true;
 
299
                if( description == "BC" ) isBC = true;
 
300
            }
 
301
        }
 
302
    }
 
303
 
 
304
    {
 
305
        static const char * LAYER = "layer";
 
306
        SGPropertyNode_ptr cloudsNode = _rootNode->getNode("clouds", true );
 
307
        const vector<SGMetarCloud> & metarClouds = m->getClouds();
 
308
        unsigned layerOffset = 0; // Oh, this is ugly!
 
309
 
 
310
        // fog/mist/haze cloud layer does not work with 3d clouds yet :-(
 
311
        bool setGroundCloudLayer = _rootNode->getBoolValue("set-ground-cloud-layer", false ) &&
 
312
              false == (fgGetBool("/sim/rendering/shader-effects", false ) && 
 
313
                        fgGetBool("/sim/rendering/clouds3d-enable", false ) );
 
314
 
 
315
        if( setGroundCloudLayer ) {
 
316
            // create a cloud layer #0 starting at the ground if its fog, mist or haze
 
317
 
 
318
            // make sure layer actually starts at ground and set it's bottom at a constant
 
319
            // value below the station's elevation
 
320
            const double LAYER_BOTTOM_STATION_OFFSET =
 
321
              fgGetDouble( "/environment/params/fog-mist-haze-layer/offset-from-station-elevation-ft", -200 );
 
322
 
 
323
            SGMetarCloud::Coverage coverage = SGMetarCloud::COVERAGE_NIL;
 
324
            double thickness = 0;
 
325
            double alpha = 1.0;
 
326
 
 
327
            if( isFG ) { // fog
 
328
                coverage = SGMetarCloud::getCoverage( isBC ? 
 
329
                    fgGetString( "/environment/params/fog-mist-haze-layer/fog-bc-2dlayer-coverage", SGMetarCloud::COVERAGE_SCATTERED_STRING ) :
 
330
                    fgGetString( "/environment/params/fog-mist-haze-layer/fog-2dlayer-coverage", SGMetarCloud::COVERAGE_BROKEN_STRING )
 
331
                );
 
332
 
 
333
                thickness = isMI ? 
 
334
                   fgGetDouble("/environment/params/fog-mist-haze-layer/fog-shallow-thickness-ft",30) - LAYER_BOTTOM_STATION_OFFSET : // shallow fog, 10m/30ft
 
335
                   fgGetDouble("/environment/params/fog-mist-haze-layer/fog-thickness-ft",500) - LAYER_BOTTOM_STATION_OFFSET; // fog, 150m/500ft
 
336
                alpha =  fgGetDouble("/environment/params/fog-mist-haze-layer/fog-2dlayer-alpha", 1.0);
 
337
            } else if( isBR ) { // mist
 
338
                coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING));
 
339
                thickness =  fgGetDouble("/environment/params/fog-mist-haze-layer/mist-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET;
 
340
                alpha =  fgGetDouble("/environment/params/fog-mist-haze-layer/mist-2dlayer-alpha",0.8);
 
341
            } else if( isHZ ) { // haze
 
342
                coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING));
 
343
                thickness =  fgGetDouble("/environment/params/fog-mist-haze-layer/haze-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET;
 
344
                alpha =  fgGetDouble("/environment/params/fog-mist-haze-layer/haze-2dlayer-alpha",0.6);
 
345
            }
 
346
 
 
347
            if( coverage != SGMetarCloud::COVERAGE_NIL ) {
 
348
 
 
349
                // if there is a layer above the fog, limit the top to one foot below that layer's bottom
 
350
                if( metarClouds.size() > 0 && metarClouds[0].getCoverage() != SGMetarCloud::COVERAGE_CLEAR )
 
351
                    thickness = metarClouds[0].getAltitude_ft() - LAYER_BOTTOM_STATION_OFFSET - 1;
 
352
 
 
353
                SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, 0, true );
 
354
                layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) );
 
355
                layerNode->setStringValue( "coverage", coverage_string[coverage] );
 
356
                layerNode->setDoubleValue( "elevation-ft", _station_elevation + LAYER_BOTTOM_STATION_OFFSET );
 
357
                layerNode->setDoubleValue( "thickness-ft", thickness );
 
358
                layerNode->setDoubleValue( "visibility-m", _min_visibility );
 
359
                layerNode->setDoubleValue( "alpha", alpha );
 
360
                _min_visibility = _max_visibility =
 
361
                  fgGetDouble("/environment/params/fog-mist-haze-layer/visibility-above-layer-m",20000.0); // assume good visibility above the fog
 
362
                layerOffset = 1;  // shudder
 
363
            }
 
364
        } 
 
365
 
 
366
        for( unsigned i = 0; i < 5-layerOffset; i++ ) {
 
367
            SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, i+layerOffset, true );
 
368
            SGMetarCloud::Coverage coverage = i < metarClouds.size() ? metarClouds[i].getCoverage() : SGMetarCloud::COVERAGE_CLEAR;
 
369
            double elevation = 
 
370
                i >= metarClouds.size() || coverage == SGMetarCloud::COVERAGE_CLEAR ? 
 
371
                -9999.0 : 
 
372
                metarClouds[i].getAltitude_ft() + _station_elevation;
 
373
 
 
374
            layerNode->setDoubleValue( "alpha", 1.0 );
 
375
            layerNode->setStringValue( "coverage", coverage_string[coverage] );
 
376
            layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) );
 
377
            layerNode->setDoubleValue( "elevation-ft", elevation );
 
378
            layerNode->setDoubleValue( "thickness-ft", thickness_value[coverage]);
 
379
            layerNode->setDoubleValue( "span-m", 40000 );
 
380
            layerNode->setDoubleValue( "visibility-m", 50.0 );
 
381
        }
 
382
    }
 
383
 
 
384
    _rain = m->getRain();
 
385
    _hail = m->getHail();
 
386
    _snow = m->getSnow();
 
387
    _snow_cover = m->getSnowCover();
 
388
    _metarValidNode->setBoolValue(true);
 
389
}
 
390
 
 
391
void MetarProperties::setStationId( const std::string & value )
 
392
 
393
    set_station_id(simgear::strutils::strip(value).c_str());
 
394
}
 
395
 
 
396
double MetarProperties::get_magnetic_variation_deg() const 
 
397
{
 
398
  return _magneticVariation->get_variation_deg( _station_longitude, _station_latitude, _station_elevation );
 
399
}
 
400
 
 
401
double MetarProperties::get_magnetic_dip_deg() const
 
402
{
 
403
  return _magneticVariation->get_dip_deg( _station_longitude, _station_latitude, _station_elevation );
 
404
}
 
405
 
 
406
static inline void calc_wind_hs( double north_fps, double east_fps, int & heading_deg, double & speed_kt )
 
407
{
 
408
    speed_kt = sqrt((north_fps)*(north_fps)+(east_fps)*(east_fps)) * 3600.0 / (SG_NM_TO_METER * SG_METER_TO_FEET);
 
409
    heading_deg = SGMiscd::roundToInt( 
 
410
        SGMiscd::normalizeAngle2( atan2( east_fps, north_fps ) ) * SGD_RADIANS_TO_DEGREES );
 
411
}
 
412
 
 
413
void MetarProperties::set_wind_from_north_fps( double value )
 
414
{
 
415
    _wind_from_north_fps = value;
 
416
    calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed );
 
417
}
 
418
 
 
419
void MetarProperties::set_wind_from_east_fps( double value )
 
420
{
 
421
    _wind_from_east_fps = value;
 
422
    calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed );
 
423
}
 
424
 
 
425
static inline void calc_wind_ne( double heading_deg, double speed_kt, double & north_fps, double & east_fps )
 
426
{
 
427
    double speed_fps = speed_kt * SG_NM_TO_METER * SG_METER_TO_FEET / 3600.0;
 
428
    north_fps = speed_fps * cos(heading_deg * SGD_DEGREES_TO_RADIANS);
 
429
    east_fps = speed_fps * sin(heading_deg * SGD_DEGREES_TO_RADIANS);
 
430
}
 
431
 
 
432
void MetarProperties::set_base_wind_dir( double value )
 
433
{
 
434
    _base_wind_dir = value;
 
435
    calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps );
 
436
}
 
437
 
 
438
void MetarProperties::set_wind_speed( double value )
 
439
{
 
440
    _wind_speed = value;
 
441
    calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps );
 
442
}
 
443
 
 
444
 
 
445
} // namespace Environment