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_aloft, double dir, double speed, double gust )
396 setupWindBranch( "boundary", dir, speed, gust );
398 setupWindBranch( "aloft", dir, speed, gust );
401 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
403 double dval = EnvironmentUpdatePeriodSec * dt;
405 if (fabs(currentval - requiredval) < dval) return requiredval;
406 if (currentval < requiredval) return (currentval + dval);
407 if (currentval > requiredval) return (currentval - dval);
415 wind_interpolation_required = true;
419 FGMetarCtrl::reinit ()
424 static inline double convert_to_360( double d )
426 if( d < 0.0 ) return d + 360.0;
427 if( d >= 360.0 ) return d - 360.0;
431 static inline double convert_to_180( double d )
433 return d > 180.0 ? d - 360.0 : d;
437 FGMetarCtrl::update(double dt)
439 if( dt <= 0 || !metar_valid ||!enabled)
442 windModulator->update(dt);
443 // Interpolate the current configuration closer to the actual METAR
445 bool reinit_required = false;
446 bool layer_rebuild_required = false;
449 double dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
450 double speed = base_wind_speed_n->getDoubleValue();
451 double gust = gust_wind_speed_n->getDoubleValue();
452 setupWind(setup_winds_aloft, dir, speed, gust);
454 double metarvis = min_visibility_n->getDoubleValue();
455 fgDefaultWeatherValue("visibility-m", metarvis);
457 double metarpressure = pressure_n->getDoubleValue();
458 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
460 // We haven't already loaded a METAR, so apply it immediately.
461 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
462 vector<SGPropertyNode_ptr>::const_iterator layer;
463 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
466 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
467 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
469 target->setStringValue("coverage",
470 (*layer)->getStringValue("coverage", "clear"));
471 target->setDoubleValue("elevation-ft",
472 (*layer)->getDoubleValue("elevation-ft"));
473 target->setDoubleValue("thickness-ft",
474 (*layer)->getDoubleValue("thickness-ft"));
475 target->setDoubleValue("span-m", 40000.0);
478 first_update = false;
479 reinit_required = true;
480 layer_rebuild_required = true;
483 if( wind_interpolation_required ) {
484 // Generate interpolated values between the METAR and the current
487 // Pick up the METAR wind values and convert them into a vector.
489 double metar_speed = base_wind_speed_n->getDoubleValue();
490 double metar_heading = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
492 metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
493 metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
495 // Convert the current wind values and convert them into a vector
497 double speed = boundary_wind_speed_n->getDoubleValue();
498 double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
500 current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
501 current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
503 // Determine the maximum component-wise value that the wind can change.
504 // First we determine the fraction in the X and Y component, then
505 // factor by the maximum wind change.
506 double x = fabs(current[0] - metar[0]);
507 double y = fabs(current[1] - metar[1]);
509 // only interpolate if we have a difference
511 double dx = x / (x + y);
514 double maxdx = dx * MaxWindChangeKtsSec;
515 double maxdy = dy * MaxWindChangeKtsSec;
517 // Interpolate each component separately.
518 current[0] = interpolate_val(current[0], metar[0], maxdx);
519 current[1] = interpolate_val(current[1], metar[1], maxdy);
521 // Now convert back to polar coordinates.
522 if ((fabs(current[0]) > 0.1) || (fabs(current[1]) > 0.1)) {
523 // Some real wind to convert back from. Work out the speed
524 // and direction value in degrees.
525 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
526 dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
528 // Normalize the direction.
532 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
534 // Special case where there is no wind (otherwise atan2 barfs)
537 double gust = gust_wind_speed_n->getDoubleValue();
538 setupWind(setup_winds_aloft, dir_from, speed, gust);
539 reinit_required = true;
541 wind_interpolation_required = false;
543 } else { // if(wind_interpolation_required)
544 // interpolation of wind vector is finished, apply wind
545 // variations and gusts for the boundary layer only
548 bool wind_modulated = false;
550 // start with the main wind direction
551 double wind_dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
552 double min = convert_to_180(base_wind_range_from_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
553 double max = convert_to_180(base_wind_range_to_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
555 // if variable winds configured, modulate the wind direction
556 double f = windModulator->get_direction_offset_norm();
557 wind_dir = min+(max-min)*f;
558 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
559 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
560 wind_modulated = true;
563 // start with main wind speed
564 double wind_speed = base_wind_speed_n->getDoubleValue();
565 max = gust_wind_speed_n->getDoubleValue();
566 if( max > wind_speed ) {
567 // if gusts are configured, modulate wind magnitude
568 double f = windModulator->get_magnitude_factor_norm();
569 wind_speed = wind_speed+(max-wind_speed)*f;
570 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
571 wind_modulated = true;
573 if( wind_modulated ) {
574 setupWind(false, wind_dir, wind_speed, max);
575 reinit_required = true;
579 // Now handle the visibility. We convert both visibility values
580 // to X-values, then interpolate from there, then back to real values.
581 // The length_scale is fixed to 1000m, so the visibility changes by
582 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
583 // whichever is more.
584 double vis = boundary_visibility_n->getDoubleValue();;
585 double metarvis = min_visibility_n->getDoubleValue();
586 if( vis != metarvis ) {
587 double currentxval = log(1000.0 + vis);
588 double metarxval = log(1000.0 + metarvis);
590 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
592 // Now convert back from an X-value to a straightforward visibility.
593 vis = exp(currentxval) - 1000.0;
594 fgDefaultWeatherValue("visibility-m", vis);
595 reinit_required = true;
598 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
599 double metarpressure = pressure_n->getDoubleValue();
600 if( pressure != metarpressure ) {
601 pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
602 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
603 reinit_required = true;
606 // Set the cloud layers by interpolating over the METAR versions.
607 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
608 vector<SGPropertyNode_ptr>::const_iterator layer;
609 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
611 double aircraft_alt = fgGetDouble("/position/altitude-ft");
614 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
615 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
617 // In the case of clouds, we want to avoid writing if nothing has
618 // changed, as these properties are tied to the renderer and will
619 // cause the clouds to be updated, reseting the texture locations.
621 // We don't interpolate the coverage values as no-matter how we
622 // do it, it will be quite a sudden change of texture. Better to
623 // have a single change than four or five.
624 const char *coverage = (*layer)->getStringValue("coverage", "clear");
625 SGPropertyNode *cov = target->getNode("coverage", true);
626 if (strcmp(cov->getStringValue(), coverage) != 0) {
627 cov->setStringValue(coverage);
628 layer_rebuild_required = true;
631 double required_alt = (*layer)->getDoubleValue("elevation-ft");
632 double current_alt = target->getDoubleValue("elevation-ft");
633 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
634 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
636 if (current_alt < -9000 || required_alt < -9000 ||
637 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
638 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
639 // We don't interpolate any layers that are
640 // - too far above us to be visible
641 // - too far below us to be visible
642 // - with too large a difference to make interpolation sensible
643 // - to or from -9999 (used as a placeholder)
644 // - any values that are too high above us,
645 if (current_alt != required_alt)
646 target->setDoubleValue("elevation-ft", required_alt);
648 if (thickness->getDoubleValue() != required_thickness)
649 thickness->setDoubleValue(required_thickness);
652 // Interpolate the other values in the usual way
653 if (current_alt != required_alt) {
654 current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
655 target->setDoubleValue("elevation-ft", current_alt);
658 double current_thickness = thickness->getDoubleValue();
660 if (current_thickness != required_thickness) {
661 current_thickness = interpolate_val(current_thickness,
663 MaxCloudThicknessChangeFtSec);
664 thickness->setDoubleValue(current_thickness);
670 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
671 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
672 //TODO: check if temperature/dewpoint have changed. This requires reinit.
674 // Force an update of the 3D clouds
675 if( layer_rebuild_required )
676 fgSetInt("/environment/rebuild-layers", 1 );
678 // Reinitializing of the environment controller required
679 if( reinit_required )
680 _environmentCtrl->reinit();
683 const char * FGMetarCtrl::get_metar(void) const
685 return metar.c_str();
688 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
689 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
691 void FGMetarCtrl::set_metar( const char * metar_string )
695 metar = metar_string;
697 SGSharedPtr<FGMetar> m;
699 m = new FGMetar( metar_string );
701 catch( sg_io_exception ) {
702 fprintf( stderr, "can't get metar: %s\n", metar_string );
707 wind_interpolation_required = true;
709 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
710 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
712 const SGMetarVisibility *dirvis = m->getDirVisibility();
713 for (i = 0; i < 8; i++, dirvis++) {
714 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
715 double v = dirvis->getVisibility_m();
717 vis->setDoubleValue("min-m", v);
718 vis->setDoubleValue("max-m", v);
721 base_wind_dir_n->setIntValue( m->getWindDir() );
722 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
723 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
724 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
725 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
726 temperature_n->setDoubleValue( m->getTemperature_C() );
727 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
728 humidity_n->setDoubleValue( m->getRelHumidity() );
729 pressure_n->setDoubleValue( m->getPressure_inHg() );
732 // get station elevation to compute cloud base
733 double station_elevation_ft = 0;
735 // 1. check the id given in the metar
736 FGAirport* a = FGAirport::findByIdent(m->getId());
738 // 2. if unknown, find closest airport with metar to current position
740 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
741 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
744 // 3. otherwise use ground elevation
746 station_elevation_ft = a->getElevation();
747 station_id_n->setStringValue( a->ident());
749 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
750 station_id_n->setStringValue( m->getId());
754 station_elevation_n->setDoubleValue( station_elevation_ft );
756 vector<SGMetarCloud> cv = m->getClouds();
757 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
759 int layer_cnt = environment_clouds_n->getChildren("layer").size();
760 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
763 const char *coverage = "clear";
764 double elevation = -9999.0;
765 double thickness = 0.0;
766 const double span = 40000.0;
768 if (cloud != cloud_end) {
769 int c = cloud->getCoverage();
770 coverage = coverage_string[c];
771 elevation = cloud->getAltitude_ft() + station_elevation_ft;
772 thickness = thickness_value[c];
776 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
778 // if the coverage has changed, a rebuild of the layer is needed
779 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
780 layer->setStringValue("coverage", coverage);
782 layer->setDoubleValue("elevation-ft", elevation);
783 layer->setDoubleValue("thickness-ft", thickness);
784 layer->setDoubleValue("span-m", span);
787 rain_n->setDoubleValue(m->getRain());
788 hail_n->setDoubleValue(m->getHail());
789 snow_n->setDoubleValue(m->getSnow());
790 snow_cover_n->setBoolValue(m->getSnowCover());
794 #if defined(ENABLE_THREADS)
796 * This class represents the thread of execution responsible for
797 * fetching the metar data.
799 class MetarThread : public OpenThreads::Thread {
801 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
805 * Fetche the metar data from the NOAA.
810 FGMetarFetcher * metar_fetcher;
813 void MetarThread::run()
816 string airport_id = metar_fetcher->request_queue.pop();
818 if( airport_id.size() == 0 )
821 if( metar_fetcher->_error_count > 3 ) {
822 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
826 metar_fetcher->fetch( airport_id );
831 FGMetarFetcher::FGMetarFetcher() :
832 #if defined(ENABLE_THREADS)
842 longitude_n = fgGetNode( "/position/longitude-deg", true );
843 latitude_n = fgGetNode( "/position/latitude-deg", true );
844 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
846 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
847 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
848 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
849 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
851 output_n = fgGetNode("/environment/metar/data", true );
852 #if defined(ENABLE_THREADS)
853 metar_thread = new MetarThread(this);
854 // FIXME: do we really need setProcessorAffinity()?
855 // metar_thread->setProcessorAffinity(1);
856 metar_thread->start();
857 #endif // ENABLE_THREADS
861 FGMetarFetcher::~FGMetarFetcher()
863 #if defined(ENABLE_THREADS)
864 request_queue.push("");
865 metar_thread->join();
867 #endif // ENABLE_THREADS
870 void FGMetarFetcher::init ()
877 current_airport_id.clear();
880 void FGMetarFetcher::reinit ()
885 /* search for closest airport with metar every xx seconds */
886 static const int search_interval_sec = 60;
888 /* fetch metar for airport, even if airport has not changed every xx seconds */
889 static const int fetch_interval_sec = 900;
891 /* reset error counter after xxx seconds */
892 static const int error_timer_sec = 3;
894 void FGMetarFetcher::update (double delta_time_sec)
896 fetch_timer -= delta_time_sec;
897 search_timer -= delta_time_sec;
898 error_timer -= delta_time_sec;
900 if( error_timer <= 0.0 ) {
901 error_timer = error_timer_sec;
905 if( enable_n->getBoolValue() == false ) {
910 // we were just enabled, reset all timers to
911 // trigger immediate metar fetch
915 error_timer = error_timer_sec;
919 FGAirport * a = NULL;
921 if( search_timer <= 0.0 ) {
922 // search timer expired, search closest airport with metar
923 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
924 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
925 search_timer = search_interval_sec;
932 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
933 // fetch timer expired or airport has changed, schedule a fetch
934 current_airport_id = a->ident();
935 fetch_timer = fetch_interval_sec;
936 #if defined(ENABLE_THREADS)
937 // push this airport id into the queue for the worker thread
938 request_queue.push( current_airport_id );
940 // if there is no worker thread, immediately fetch the data
941 fetch( current_airport_id );
946 void FGMetarFetcher::fetch( const string & id )
948 SGSharedPtr<FGMetar> result = NULL;
950 // fetch current metar data
952 string host = proxy_host_n->getStringValue();
953 string auth = proxy_auth_n->getStringValue();
954 string port = proxy_port_n->getStringValue();
956 result = new FGMetar( id, host, port, auth);
958 long max_age = max_age_n->getLongValue();
959 long age = result->getAge_min();
961 if (max_age && age > max_age) {
962 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
963 if (++_stale_count > 10) {
965 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
971 } catch (const sg_io_exception& e) {
972 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
976 // write the metar to the property node, the rest is done by the methods tied to this property
977 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
978 if( result != NULL && enable_n->getBoolValue() == true )
979 output_n->setStringValue( result->getData() );
982 // end of environment_ctrl.cxx