1 // environment_ctrl.cxx -- manager for natural environment information.
3 // Written by David Megginson, started February 2002.
5 // Copyright (C) 2002 David Megginson - david@megginson.com
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <simgear/debug/logstream.hxx>
30 #include <simgear/structure/commands.hxx>
31 #include <simgear/structure/exception.hxx>
33 #include <Airports/simple.hxx>
34 #include <Main/fg_props.hxx>
35 #include <Main/util.hxx>
37 #include "fgmetar.hxx"
38 #include "environment_ctrl.hxx"
42 class AirportWithMetar : public FGAirport::AirportFilter {
44 virtual bool passAirport(FGAirport* aApt) const {
45 return aApt->getMetar();
49 static AirportWithMetar airportWithMetarFilter;
51 ////////////////////////////////////////////////////////////////////////
52 // Implementation of FGEnvironmentCtrl abstract base class.
53 ////////////////////////////////////////////////////////////////////////
55 FGEnvironmentCtrl::FGEnvironmentCtrl ()
63 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
68 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
70 _environment = environment;
74 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
80 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
86 FGEnvironmentCtrl::setElevationFt (double elev_ft)
92 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
101 ////////////////////////////////////////////////////////////////////////
102 // Implementation of FGInterpolateEnvironmentCtrl.
103 ////////////////////////////////////////////////////////////////////////
106 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
108 altitude_n = fgGetNode("/position/altitude-ft", true);
109 altitude_agl_n = fgGetNode("/position/altitude-agl-ft", true);
110 boundary_transition_n = fgGetNode("/environment/config/boundary-transition-ft", false );
111 boundary_n = fgGetNode("/environment/config/boundary", true );
112 aloft_n = fgGetNode("/environment/config/aloft", true );
115 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
118 for (i = 0; i < _boundary_table.size(); i++)
119 delete _boundary_table[i];
120 for (i = 0; i < _aloft_table.size(); i++)
121 delete _aloft_table[i];
127 FGInterpolateEnvironmentCtrl::init ()
129 read_table( boundary_n, _boundary_table);
130 read_table( aloft_n, _aloft_table);
134 FGInterpolateEnvironmentCtrl::reinit ()
140 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector<bucket *> &table)
142 double last_altitude_ft = 0.0;
143 double sort_required = false;
146 for (i = 0; i < node->nChildren(); i++) {
147 const SGPropertyNode * child = node->getChild(i);
148 if ( strcmp(child->getName(), "entry") == 0
149 && child->getStringValue("elevation-ft", "")[0] != '\0'
150 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
153 if( i < table.size() ) {
154 // recycle existing bucket
157 // more nodes than buckets in table, add a new one
162 b->environment.copy(table[i-1]->environment);
163 b->environment.read(child);
164 b->altitude_ft = b->environment.get_elevation_ft();
166 // check, if altitudes are in ascending order
167 if( b->altitude_ft < last_altitude_ft )
168 sort_required = true;
169 last_altitude_ft = b->altitude_ft;
172 // remove leftover buckets
173 while( table.size() > i ) {
174 bucket * b = *(table.end() - 1);
180 sort(table.begin(), table.end(), bucket::lessThan);
184 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
186 double altitude_ft = altitude_n->getDoubleValue();
187 double altitude_agl_ft = altitude_agl_n->getDoubleValue();
188 double boundary_transition =
189 boundary_transition_n == NULL ? 500 : boundary_transition_n->getDoubleValue();
191 int length = _boundary_table.size();
195 double boundary_limit = _boundary_table[length-1]->altitude_ft;
196 if (boundary_limit >= altitude_agl_ft) {
197 do_interpolate(_boundary_table, altitude_agl_ft, _environment);
199 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
200 //TODO: this is 500ft above the top altitude of boundary layer
201 //shouldn't this be +/-250 ft off of the top altitude?
203 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
204 do_interpolate(_aloft_table, altitude_ft, &env2);
206 (altitude_agl_ft - boundary_limit) / boundary_transition;
207 interpolate(&env1, &env2, fraction, _environment);
212 do_interpolate(_aloft_table, altitude_ft, _environment);
216 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table, double altitude_ft, FGEnvironment * environment)
218 int length = table.size();
222 // Boundary conditions
223 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
224 environment->copy(table[0]->environment);
226 } else if (table[length-1]->altitude_ft <= altitude_ft) {
227 environment->copy(table[length-1]->environment);
230 // Search the interpolation table
231 for (int i = 0; i < length - 1; i++) {
232 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
233 FGEnvironment * env1 = &(table[i]->environment);
234 FGEnvironment * env2 = &(table[i+1]->environment);
236 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
240 ((altitude_ft - table[i]->altitude_ft) /
241 (table[i+1]->altitude_ft - table[i]->altitude_ft));
242 interpolate(env1, env2, fraction, environment);
250 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
252 return (altitude_ft < b.altitude_ft);
256 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
258 return (a->altitude_ft) < (b->altitude_ft);
262 ////////////////////////////////////////////////////////////////////////
263 // Implementation of FGMetarCtrl.
264 ////////////////////////////////////////////////////////////////////////
266 FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
267 : _environmentCtrl(environmentCtrl),
268 station_elevation_ft(0.0),
270 setup_winds_aloft(true),
271 wind_interpolation_required(true),
272 // Interpolation constant definitions.
273 EnvironmentUpdatePeriodSec( 0.2 ),
274 MaxWindChangeKtsSec( 0.2 ),
275 MaxVisChangePercentSec( 0.05 ),
276 MaxPressureChangeInHgSec( 0.0033 ),
277 MaxCloudAltitudeChangeFtSec( 20.0 ),
278 MaxCloudThicknessChangeFtSec( 50.0 ),
279 MaxCloudInterpolationHeightFt( 5000.0 ),
280 MaxCloudInterpolationDeltaFt( 4000.0 )
282 windModulator = new FGBasicWindModulator();
284 metar_base_n = fgGetNode( "/environment/metar", true );
285 station_id_n = metar_base_n->getNode("station-id", true );
286 station_elevation_n = metar_base_n->getNode("station-elevation-ft", true );
287 min_visibility_n = metar_base_n->getNode("min-visibility-m", true );
288 max_visibility_n = metar_base_n->getNode("max-visibility-m", true );
289 base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true );
290 base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true );
291 base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true );
292 base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
293 gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true );
294 temperature_n = metar_base_n->getNode("temperature-degc", true );
295 dewpoint_n = metar_base_n->getNode("dewpoint-degc", true );
296 humidity_n = metar_base_n->getNode("rel-humidity-norm", true );
297 pressure_n = metar_base_n->getNode("pressure-inhg", true );
298 clouds_n = metar_base_n->getNode("clouds", true );
299 rain_n = metar_base_n->getNode("rain-norm", true );
300 hail_n = metar_base_n->getNode("hail-norm", true );
301 snow_n = metar_base_n->getNode("snow-norm", true );
302 snow_cover_n = metar_base_n->getNode("snow-cover", true );
303 magnetic_variation_n = fgGetNode( "/environment/magnetic-variation-deg", true );
304 ground_elevation_n = fgGetNode( "/position/ground-elev-m", true );
305 longitude_n = fgGetNode( "/position/longitude-deg", true );
306 latitude_n = fgGetNode( "/position/latitude-deg", true );
307 environment_clouds_n = fgGetNode("/environment/clouds");
309 boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt");
310 boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
311 boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
312 boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
315 FGMetarCtrl::~FGMetarCtrl ()
319 void FGMetarCtrl::bind ()
321 fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid );
322 fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled );
323 fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft );
326 void FGMetarCtrl::unbind ()
328 fgUntie("/environment/metar/valid");
329 fgUntie("/environment/params/metar-updates-environment");
330 fgUntie("/environment/params/metar-updates-winds-aloft");
333 // use a "command" to set station temp at station elevation
334 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
336 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
337 node->setFloatValue( temp_degc );
338 node = args.getNode("altitude-ft", 0, true);
339 node->setFloatValue( altitude_ft );
340 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
343 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
345 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
346 node->setFloatValue( dewpoint_degc );
347 node = args.getNode("altitude-ft", 0, true);
348 node->setFloatValue( altitude_ft );
349 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
353 Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
356 wind-from-heading-deg
358 turbulence/magnitude-norm
361 wind-heading-change-deg how many degrees does the wind direction change at this level
362 wind-speed-change-rel relative change of wind speed at this level
363 turbulence/factor factor for the calculated turbulence magnitude at this level
365 static void setupWindBranch( string branchName, double dir, double speed, double gust )
367 SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true);
368 vector<SGPropertyNode_ptr> entries = branch->getChildren("entry");
369 for ( vector<SGPropertyNode_ptr>::iterator it = entries.begin(); it != entries.end(); it++) {
371 // change wind direction as configured
372 double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 );
373 if( layer_dir >= 360.0 ) layer_dir -= 360.0;
374 if( layer_dir < 0.0 ) layer_dir += 360.0;
375 (*it)->setDoubleValue("wind-from-heading-deg", layer_dir);
377 double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 ));
378 (*it)->setDoubleValue("wind-speed-kt", layer_speed );
380 // add some turbulence
381 SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true);
383 double turbulence_norm = speed/50;
385 turbulence_norm += (gust-speed)/25;
387 if( turbulence_norm > 1.0 ) turbulence_norm = 1.0;
389 turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 );
390 turbulence->setDoubleValue( "magnitude-norm", turbulence_norm );
394 static void setupWind( bool setup_boundary, bool setup_aloft, double dir, double speed, double gust )
397 setupWindBranch( "boundary", dir, speed, gust );
400 setupWindBranch( "aloft", dir, speed, gust );
403 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
405 double dval = EnvironmentUpdatePeriodSec * dt;
407 if (fabs(currentval - requiredval) < dval) return requiredval;
408 if (currentval < requiredval) return (currentval + dval);
409 if (currentval > requiredval) return (currentval - dval);
417 wind_interpolation_required = true;
421 FGMetarCtrl::reinit ()
426 static inline double convert_to_360( double d )
428 if( d < 0.0 ) return d + 360.0;
429 if( d >= 360.0 ) return d - 360.0;
433 static inline double convert_to_180( double d )
435 return d > 180.0 ? d - 360.0 : d;
439 FGMetarCtrl::update(double dt)
441 if( dt <= 0 || !metar_valid ||!enabled)
444 windModulator->update(dt);
445 // Interpolate the current configuration closer to the actual METAR
447 bool reinit_required = false;
448 bool layer_rebuild_required = false;
451 double dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
452 double speed = base_wind_speed_n->getDoubleValue();
453 double gust = gust_wind_speed_n->getDoubleValue();
454 setupWind(true, setup_winds_aloft, dir, speed, gust);
456 double metarvis = min_visibility_n->getDoubleValue();
457 fgDefaultWeatherValue("visibility-m", metarvis);
459 double metarpressure = pressure_n->getDoubleValue();
460 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
462 // We haven't already loaded a METAR, so apply it immediately.
463 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
464 vector<SGPropertyNode_ptr>::const_iterator layer;
465 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
468 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
469 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
471 target->setStringValue("coverage",
472 (*layer)->getStringValue("coverage", "clear"));
473 target->setDoubleValue("elevation-ft",
474 (*layer)->getDoubleValue("elevation-ft"));
475 target->setDoubleValue("thickness-ft",
476 (*layer)->getDoubleValue("thickness-ft"));
477 target->setDoubleValue("span-m", 40000.0);
480 first_update = false;
481 reinit_required = true;
482 layer_rebuild_required = true;
485 if( wind_interpolation_required ) {
486 // Generate interpolated values between the METAR and the current
489 // Pick up the METAR wind values and convert them into a vector.
491 double metar_speed = base_wind_speed_n->getDoubleValue();
492 double metar_heading = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
494 metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
495 metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
497 // Convert the current wind values and convert them into a vector
499 double speed = boundary_wind_speed_n->getDoubleValue();
500 double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
502 current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
503 current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
505 // Determine the maximum component-wise value that the wind can change.
506 // First we determine the fraction in the X and Y component, then
507 // factor by the maximum wind change.
508 double x = fabs(current[0] - metar[0]);
509 double y = fabs(current[1] - metar[1]);
511 // only interpolate if we have a difference
513 double dx = x / (x + y);
516 double maxdx = dx * MaxWindChangeKtsSec;
517 double maxdy = dy * MaxWindChangeKtsSec;
519 // Interpolate each component separately.
520 current[0] = interpolate_val(current[0], metar[0], maxdx);
521 current[1] = interpolate_val(current[1], metar[1], maxdy);
523 // Now convert back to polar coordinates.
524 if ((current[0] == 0.0) && (current[1] == 0.0)) {
525 // Special case where there is no wind (otherwise atan2 barfs)
528 // Some real wind to convert back from. Work out the speed
529 // and direction value in degrees.
530 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
531 dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
533 // Normalize the direction.
537 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
539 double gust = gust_wind_speed_n->getDoubleValue();
540 setupWind(true, setup_winds_aloft, dir_from, speed, gust);
541 reinit_required = true;
543 wind_interpolation_required = false;
545 } else { // if(wind_interpolation_required)
546 // interpolation of wind vector is finished, apply wind
547 // variations and gusts for the boundary layer only
549 // start with the main wind direction
550 double wind_dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
551 double min = convert_to_180(base_wind_range_from_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
552 double max = convert_to_180(base_wind_range_to_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
554 // if variable winds configured, modulate the wind direction
555 double f = windModulator->get_direction_offset_norm();
556 wind_dir = min+(max-min)*f;
557 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
558 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
561 // start with main wind speed
562 double wind_speed = base_wind_speed_n->getDoubleValue();
563 max = gust_wind_speed_n->getDoubleValue();
564 if( max > wind_speed ) {
565 // if gusts are configured, modulate wind magnitude
566 double f = windModulator->get_magnitude_factor_norm();
567 wind_speed = wind_speed+(max-wind_speed)*f;
568 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
570 setupWind(true, false, wind_dir, wind_speed, max);
571 reinit_required = true;
574 // Now handle the visibility. We convert both visibility values
575 // to X-values, then interpolate from there, then back to real values.
576 // The length_scale is fixed to 1000m, so the visibility changes by
577 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
578 // whichever is more.
579 double vis = boundary_visibility_n->getDoubleValue();;
580 double metarvis = min_visibility_n->getDoubleValue();
581 if( vis != metarvis ) {
582 double currentxval = log(1000.0 + vis);
583 double metarxval = log(1000.0 + metarvis);
585 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
587 // Now convert back from an X-value to a straightforward visibility.
588 vis = exp(currentxval) - 1000.0;
589 fgDefaultWeatherValue("visibility-m", vis);
590 reinit_required = true;
593 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
594 double metarpressure = pressure_n->getDoubleValue();
595 if( pressure != metarpressure ) {
596 pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
597 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
598 reinit_required = true;
601 // Set the cloud layers by interpolating over the METAR versions.
602 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
603 vector<SGPropertyNode_ptr>::const_iterator layer;
604 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
606 double aircraft_alt = fgGetDouble("/position/altitude-ft");
609 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
610 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
612 // In the case of clouds, we want to avoid writing if nothing has
613 // changed, as these properties are tied to the renderer and will
614 // cause the clouds to be updated, reseting the texture locations.
616 // We don't interpolate the coverage values as no-matter how we
617 // do it, it will be quite a sudden change of texture. Better to
618 // have a single change than four or five.
619 const char *coverage = (*layer)->getStringValue("coverage", "clear");
620 SGPropertyNode *cov = target->getNode("coverage", true);
621 if (strcmp(cov->getStringValue(), coverage) != 0) {
622 cov->setStringValue(coverage);
623 layer_rebuild_required = true;
626 double required_alt = (*layer)->getDoubleValue("elevation-ft");
627 double current_alt = target->getDoubleValue("elevation-ft");
628 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
629 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
631 if (current_alt < -9000 || required_alt < -9000 ||
632 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
633 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
634 // We don't interpolate any layers that are
635 // - too far above us to be visible
636 // - too far below us to be visible
637 // - with too large a difference to make interpolation sensible
638 // - to or from -9999 (used as a placeholder)
639 // - any values that are too high above us,
640 if (current_alt != required_alt)
641 target->setDoubleValue("elevation-ft", required_alt);
643 if (thickness->getDoubleValue() != required_thickness)
644 thickness->setDoubleValue(required_thickness);
647 // Interpolate the other values in the usual way
648 if (current_alt != required_alt) {
649 current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
650 target->setDoubleValue("elevation-ft", current_alt);
653 double current_thickness = thickness->getDoubleValue();
655 if (current_thickness != required_thickness) {
656 current_thickness = interpolate_val(current_thickness,
658 MaxCloudThicknessChangeFtSec);
659 thickness->setDoubleValue(current_thickness);
665 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
666 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
667 //TODO: check if temperature/dewpoint have changed. This requires reinit.
669 // Force an update of the 3D clouds
670 if( layer_rebuild_required )
671 fgSetInt("/environment/rebuild-layers", 1 );
673 // Reinitializing of the environment controller required
674 if( reinit_required )
675 _environmentCtrl->reinit();
678 const char * FGMetarCtrl::get_metar(void) const
680 return metar.c_str();
683 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
684 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
686 void FGMetarCtrl::set_metar( const char * metar_string )
690 metar = metar_string;
692 SGSharedPtr<FGMetar> m;
694 m = new FGMetar( metar_string );
696 catch( sg_io_exception ) {
697 fprintf( stderr, "can't get metar: %s\n", metar_string );
702 wind_interpolation_required = true;
704 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
705 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
707 const SGMetarVisibility *dirvis = m->getDirVisibility();
708 for (i = 0; i < 8; i++, dirvis++) {
709 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
710 double v = dirvis->getVisibility_m();
712 vis->setDoubleValue("min-m", v);
713 vis->setDoubleValue("max-m", v);
716 base_wind_dir_n->setIntValue( m->getWindDir() );
717 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
718 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
719 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
720 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
721 temperature_n->setDoubleValue( m->getTemperature_C() );
722 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
723 humidity_n->setDoubleValue( m->getRelHumidity() );
724 pressure_n->setDoubleValue( m->getPressure_inHg() );
727 // get station elevation to compute cloud base
728 double station_elevation_ft = 0;
730 // 1. check the id given in the metar
731 FGAirport* a = FGAirport::findByIdent(m->getId());
733 // 2. if unknown, find closest airport with metar to current position
735 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
736 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
739 // 3. otherwise use ground elevation
741 station_elevation_ft = a->getElevation();
742 station_id_n->setStringValue( a->ident());
744 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
745 station_id_n->setStringValue( m->getId());
749 station_elevation_n->setDoubleValue( station_elevation_ft );
751 vector<SGMetarCloud> cv = m->getClouds();
752 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
754 int layer_cnt = environment_clouds_n->getChildren("layer").size();
755 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
758 const char *coverage = "clear";
759 double elevation = -9999.0;
760 double thickness = 0.0;
761 const double span = 40000.0;
763 if (cloud != cloud_end) {
764 int c = cloud->getCoverage();
765 coverage = coverage_string[c];
766 elevation = cloud->getAltitude_ft() + station_elevation_ft;
767 thickness = thickness_value[c];
771 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
773 // if the coverage has changed, a rebuild of the layer is needed
774 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
775 layer->setStringValue("coverage", coverage);
777 layer->setDoubleValue("elevation-ft", elevation);
778 layer->setDoubleValue("thickness-ft", thickness);
779 layer->setDoubleValue("span-m", span);
782 rain_n->setDoubleValue(m->getRain());
783 hail_n->setDoubleValue(m->getHail());
784 snow_n->setDoubleValue(m->getSnow());
785 snow_cover_n->setBoolValue(m->getSnowCover());
789 #if defined(ENABLE_THREADS)
791 * This class represents the thread of execution responsible for
792 * fetching the metar data.
794 class MetarThread : public OpenThreads::Thread {
796 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
800 * Fetche the metar data from the NOAA.
805 FGMetarFetcher * metar_fetcher;
808 void MetarThread::run()
811 string airport_id = metar_fetcher->request_queue.pop();
813 if( airport_id.size() == 0 )
816 if( metar_fetcher->_error_count > 3 ) {
817 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
821 metar_fetcher->fetch( airport_id );
826 FGMetarFetcher::FGMetarFetcher() :
827 #if defined(ENABLE_THREADS)
836 longitude_n = fgGetNode( "/position/longitude-deg", true );
837 latitude_n = fgGetNode( "/position/latitude-deg", true );
838 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
840 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
841 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
842 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
843 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
845 output_n = fgGetNode("/environment/metar/data", true );
846 #if defined(ENABLE_THREADS)
847 metar_thread = new MetarThread(this);
848 // FIXME: do we really need setProcessorAffinity()?
849 // metar_thread->setProcessorAffinity(1);
850 metar_thread->start();
851 #endif // ENABLE_THREADS
855 FGMetarFetcher::~FGMetarFetcher()
857 #if defined(ENABLE_THREADS)
858 request_queue.push("");
859 metar_thread->join();
861 #endif // ENABLE_THREADS
864 void FGMetarFetcher::init ()
871 current_airport_id.clear();
874 void FGMetarFetcher::reinit ()
879 /* search for closest airport with metar every xx seconds */
880 static const int search_interval_sec = 60;
882 /* fetch metar for airport, even if airport has not changed every xx seconds */
883 static const int fetch_interval_sec = 900;
885 /* reset error counter after xxx seconds */
886 static const int error_timer_sec = 3;
888 void FGMetarFetcher::update (double delta_time_sec)
890 fetch_timer -= delta_time_sec;
891 search_timer -= delta_time_sec;
892 error_timer -= delta_time_sec;
894 if( error_timer <= 0.0 ) {
895 error_timer = error_timer_sec;
899 if( enable_n->getBoolValue() == false )
902 FGAirport * a = NULL;
904 if( search_timer <= 0.0 ) {
905 // search timer expired, search closest airport with metar
906 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
907 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
908 search_timer = search_interval_sec;
915 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
916 // fetch timer expired or airport has changed, schedule a fetch
917 current_airport_id = a->ident();
918 fetch_timer = fetch_interval_sec;
919 #if defined(ENABLE_THREADS)
920 // push this airport id into the queue for the worker thread
921 request_queue.push( current_airport_id );
923 // if there is no worker thread, immediately fetch the data
924 fetch( current_airport_id );
929 void FGMetarFetcher::fetch( const string & id )
931 SGSharedPtr<FGMetar> result = NULL;
933 // fetch current metar data
935 string host = proxy_host_n->getStringValue();
936 string auth = proxy_auth_n->getStringValue();
937 string port = proxy_port_n->getStringValue();
939 result = new FGMetar( id, host, port, auth);
941 long max_age = max_age_n->getLongValue();
942 long age = result->getAge_min();
944 if (max_age && age > max_age) {
945 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
946 if (++_stale_count > 10) {
948 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
954 } catch (const sg_io_exception& e) {
955 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
959 // write the metar to the property node, the rest is done by the methods tied to this property
960 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
961 if( result != NULL && enable_n->getBoolValue() == true )
962 output_n->setStringValue( result->getData() );
965 // end of environment_ctrl.cxx