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 < (size_t)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 )
269 setup_winds_aloft(true),
270 wind_interpolation_required(true),
271 // Interpolation constant definitions.
272 EnvironmentUpdatePeriodSec( 0.2 ),
273 MaxWindChangeKtsSec( 0.2 ),
274 MaxVisChangePercentSec( 0.05 ),
275 MaxPressureChangeInHgSec( 0.0033 ),
276 MaxCloudAltitudeChangeFtSec( 20.0 ),
277 MaxCloudThicknessChangeFtSec( 50.0 ),
278 MaxCloudInterpolationHeightFt( 5000.0 ),
279 MaxCloudInterpolationDeltaFt( 4000.0 ),
280 _environmentCtrl(environmentCtrl)
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 double station_elevation_ft = station_elevation_n->getDoubleValue();
671 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
672 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
674 //TODO: check if temperature/dewpoint have changed. This requires reinit.
676 // Force an update of the 3D clouds
677 if( layer_rebuild_required )
678 fgSetInt("/environment/rebuild-layers", 1 );
680 // Reinitializing of the environment controller required
681 if( reinit_required )
682 _environmentCtrl->reinit();
685 const char * FGMetarCtrl::get_metar(void) const
687 return metar.c_str();
690 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
691 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
693 void FGMetarCtrl::set_metar( const char * metar_string )
697 metar = metar_string;
699 SGSharedPtr<FGMetar> m;
701 m = new FGMetar( metar_string );
703 catch( sg_io_exception ) {
704 fprintf( stderr, "can't get metar: %s\n", metar_string );
709 wind_interpolation_required = true;
711 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
712 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
714 const SGMetarVisibility *dirvis = m->getDirVisibility();
715 for (i = 0; i < 8; i++, dirvis++) {
716 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
717 double v = dirvis->getVisibility_m();
719 vis->setDoubleValue("min-m", v);
720 vis->setDoubleValue("max-m", v);
723 base_wind_dir_n->setIntValue( m->getWindDir() );
724 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
725 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
726 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
727 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
728 temperature_n->setDoubleValue( m->getTemperature_C() );
729 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
730 humidity_n->setDoubleValue( m->getRelHumidity() );
731 pressure_n->setDoubleValue( m->getPressure_inHg() );
734 // get station elevation to compute cloud base
735 double station_elevation_ft = 0;
737 // 1. check the id given in the metar
738 FGAirport* a = FGAirport::findByIdent(m->getId());
740 // 2. if unknown, find closest airport with metar to current position
742 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
743 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
746 // 3. otherwise use ground elevation
748 station_elevation_ft = a->getElevation();
749 station_id_n->setStringValue( a->ident());
751 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
752 station_id_n->setStringValue( m->getId());
756 station_elevation_n->setDoubleValue( station_elevation_ft );
758 vector<SGMetarCloud> cv = m->getClouds();
759 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
761 int layer_cnt = environment_clouds_n->getChildren("layer").size();
762 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
765 const char *coverage = "clear";
766 double elevation = -9999.0;
767 double thickness = 0.0;
768 const double span = 40000.0;
770 if (cloud != cloud_end) {
771 int c = cloud->getCoverage();
772 coverage = coverage_string[c];
773 elevation = cloud->getAltitude_ft() + station_elevation_ft;
774 thickness = thickness_value[c];
778 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
780 // if the coverage has changed, a rebuild of the layer is needed
781 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
782 layer->setStringValue("coverage", coverage);
784 layer->setDoubleValue("elevation-ft", elevation);
785 layer->setDoubleValue("thickness-ft", thickness);
786 layer->setDoubleValue("span-m", span);
789 rain_n->setDoubleValue(m->getRain());
790 hail_n->setDoubleValue(m->getHail());
791 snow_n->setDoubleValue(m->getSnow());
792 snow_cover_n->setBoolValue(m->getSnowCover());
796 #if defined(ENABLE_THREADS)
798 * This class represents the thread of execution responsible for
799 * fetching the metar data.
801 class MetarThread : public OpenThreads::Thread {
803 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
807 * Fetche the metar data from the NOAA.
812 FGMetarFetcher * metar_fetcher;
815 void MetarThread::run()
818 string airport_id = metar_fetcher->request_queue.pop();
820 if( airport_id.size() == 0 )
823 if( metar_fetcher->_error_count > 3 ) {
824 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
828 metar_fetcher->fetch( airport_id );
833 FGMetarFetcher::FGMetarFetcher() :
834 #if defined(ENABLE_THREADS)
844 longitude_n = fgGetNode( "/position/longitude-deg", true );
845 latitude_n = fgGetNode( "/position/latitude-deg", true );
846 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
848 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
849 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
850 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
851 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
853 output_n = fgGetNode("/environment/metar/data", true );
854 #if defined(ENABLE_THREADS)
855 metar_thread = new MetarThread(this);
856 // FIXME: do we really need setProcessorAffinity()?
857 // metar_thread->setProcessorAffinity(1);
858 metar_thread->start();
859 #endif // ENABLE_THREADS
863 FGMetarFetcher::~FGMetarFetcher()
865 #if defined(ENABLE_THREADS)
866 request_queue.push("");
867 metar_thread->join();
869 #endif // ENABLE_THREADS
872 void FGMetarFetcher::init ()
879 current_airport_id.clear();
881 hack to stop startup.nas complaining if metar arrives after nasal-dir-initialized
882 is fired. Immediately fetch and wait for the METAR before continuing. This gets the
883 /environment/metar/xxx properties filled before nasal-dir is initialized.
884 Maybe the runway selection should happen here to make startup.nas obsolete?
886 const char * startup_airport = fgGetString("/sim/startup/options/airport");
887 if( *startup_airport ) {
888 FGAirport * a = FGAirport::getByIdent( startup_airport );
890 SGGeod pos = SGGeod::fromDeg(a->getLongitude(), a->getLatitude());
891 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
892 current_airport_id = a->getId();
893 fetch( current_airport_id );
898 void FGMetarFetcher::reinit ()
903 /* search for closest airport with metar every xx seconds */
904 static const int search_interval_sec = 60;
906 /* fetch metar for airport, even if airport has not changed every xx seconds */
907 static const int fetch_interval_sec = 900;
909 /* reset error counter after xxx seconds */
910 static const int error_timer_sec = 3;
912 void FGMetarFetcher::update (double delta_time_sec)
914 fetch_timer -= delta_time_sec;
915 search_timer -= delta_time_sec;
916 error_timer -= delta_time_sec;
918 if( error_timer <= 0.0 ) {
919 error_timer = error_timer_sec;
923 if( enable_n->getBoolValue() == false ) {
928 // we were just enabled, reset all timers to
929 // trigger immediate metar fetch
933 error_timer = error_timer_sec;
937 FGAirport * a = NULL;
939 if( search_timer <= 0.0 ) {
940 // search timer expired, search closest airport with metar
941 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
942 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
943 search_timer = search_interval_sec;
950 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
951 // fetch timer expired or airport has changed, schedule a fetch
952 current_airport_id = a->ident();
953 fetch_timer = fetch_interval_sec;
954 #if defined(ENABLE_THREADS)
955 // push this airport id into the queue for the worker thread
956 request_queue.push( current_airport_id );
958 // if there is no worker thread, immediately fetch the data
959 fetch( current_airport_id );
964 void FGMetarFetcher::fetch( const string & id )
966 if( enable_n->getBoolValue() == false )
969 SGSharedPtr<FGMetar> result = NULL;
971 // fetch current metar data
973 string host = proxy_host_n->getStringValue();
974 string auth = proxy_auth_n->getStringValue();
975 string port = proxy_port_n->getStringValue();
977 result = new FGMetar( id, host, port, auth);
979 long max_age = max_age_n->getLongValue();
980 long age = result->getAge_min();
982 if (max_age && age > max_age) {
983 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
984 if (++_stale_count > 10) {
986 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
992 } catch (const sg_io_exception& e) {
993 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
995 // remove METAR flag from the airport
996 FGAirport * a = FGAirport::findByIdent( id );
997 if( a ) a->setMetar( false );
998 // immediately schedule a new search
1002 // write the metar to the property node, the rest is done by the methods tied to this property
1003 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
1004 if( result != NULL && enable_n->getBoolValue() == true )
1005 output_n->setStringValue( result->getData() );
1008 // end of environment_ctrl.cxx