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 )
177 sort(table.begin(), table.end(), bucket::lessThan);
181 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
183 double altitude_ft = altitude_n->getDoubleValue();
184 double altitude_agl_ft = altitude_agl_n->getDoubleValue();
185 double boundary_transition =
186 boundary_transition_n == NULL ? 500 : boundary_transition_n->getDoubleValue();
188 int length = _boundary_table.size();
192 double boundary_limit = _boundary_table[length-1]->altitude_ft;
193 if (boundary_limit >= altitude_agl_ft) {
194 do_interpolate(_boundary_table, altitude_agl_ft, _environment);
196 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
197 //TODO: this is 500ft above the top altitude of boundary layer
198 //shouldn't this be +/-250 ft off of the top altitude?
200 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
201 do_interpolate(_aloft_table, altitude_ft, &env2);
203 (altitude_agl_ft - boundary_limit) / boundary_transition;
204 interpolate(&env1, &env2, fraction, _environment);
209 do_interpolate(_aloft_table, altitude_ft, _environment);
213 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table, double altitude_ft, FGEnvironment * environment)
215 int length = table.size();
219 // Boundary conditions
220 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
221 environment->copy(table[0]->environment);
223 } else if (table[length-1]->altitude_ft <= altitude_ft) {
224 environment->copy(table[length-1]->environment);
227 // Search the interpolation table
228 for (int i = 0; i < length - 1; i++) {
229 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
230 FGEnvironment * env1 = &(table[i]->environment);
231 FGEnvironment * env2 = &(table[i+1]->environment);
233 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
237 ((altitude_ft - table[i]->altitude_ft) /
238 (table[i+1]->altitude_ft - table[i]->altitude_ft));
239 interpolate(env1, env2, fraction, environment);
247 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
249 return (altitude_ft < b.altitude_ft);
253 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
255 return (a->altitude_ft) < (b->altitude_ft);
259 ////////////////////////////////////////////////////////////////////////
260 // Implementation of FGMetarCtrl.
261 ////////////////////////////////////////////////////////////////////////
263 FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
264 : _environmentCtrl(environmentCtrl),
265 station_elevation_ft(0.0),
267 setup_winds_aloft(true),
268 wind_interpolation_required(true),
269 // Interpolation constant definitions.
270 EnvironmentUpdatePeriodSec( 0.2 ),
271 MaxWindChangeKtsSec( 0.2 ),
272 MaxVisChangePercentSec( 0.05 ),
273 MaxPressureChangeInHgSec( 0.0033 ),
274 MaxCloudAltitudeChangeFtSec( 20.0 ),
275 MaxCloudThicknessChangeFtSec( 50.0 ),
276 MaxCloudInterpolationHeightFt( 5000.0 ),
277 MaxCloudInterpolationDeltaFt( 4000.0 )
279 windModulator = new FGBasicWindModulator();
281 metar_base_n = fgGetNode( "/environment/metar", true );
282 station_id_n = metar_base_n->getNode("station-id", true );
283 station_elevation_n = metar_base_n->getNode("station-elevation-ft", true );
284 min_visibility_n = metar_base_n->getNode("min-visibility-m", true );
285 max_visibility_n = metar_base_n->getNode("max-visibility-m", true );
286 base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true );
287 base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true );
288 base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true );
289 base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
290 gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true );
291 temperature_n = metar_base_n->getNode("temperature-degc", true );
292 dewpoint_n = metar_base_n->getNode("dewpoint-degc", true );
293 humidity_n = metar_base_n->getNode("rel-humidity-norm", true );
294 pressure_n = metar_base_n->getNode("pressure-inhg", true );
295 clouds_n = metar_base_n->getNode("clouds", true );
296 rain_n = metar_base_n->getNode("rain-norm", true );
297 hail_n = metar_base_n->getNode("hail-norm", true );
298 snow_n = metar_base_n->getNode("snow-norm", true );
299 snow_cover_n = metar_base_n->getNode("snow-cover", true );
300 ground_elevation_n = fgGetNode( "/position/ground-elev-m", true );
301 longitude_n = fgGetNode( "/position/longitude-deg", true );
302 latitude_n = fgGetNode( "/position/latitude-deg", true );
303 environment_clouds_n = fgGetNode("/environment/clouds");
305 boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt");
306 boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
307 boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
308 boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
311 FGMetarCtrl::~FGMetarCtrl ()
315 void FGMetarCtrl::bind ()
317 fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid );
318 fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled );
319 fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft );
322 void FGMetarCtrl::unbind ()
324 fgUntie("/environment/metar/valid");
325 fgUntie("/environment/params/metar-updates-environment");
326 fgUntie("/environment/params/metar-updates-winds-aloft");
329 // use a "command" to set station temp at station elevation
330 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
332 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
333 node->setFloatValue( temp_degc );
334 node = args.getNode("altitude-ft", 0, true);
335 node->setFloatValue( altitude_ft );
336 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
339 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
341 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
342 node->setFloatValue( dewpoint_degc );
343 node = args.getNode("altitude-ft", 0, true);
344 node->setFloatValue( altitude_ft );
345 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
349 Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
352 wind-from-heading-deg
354 turbulence/magnitude-norm
357 wind-heading-change-deg how many degrees does the wind direction change at this level
358 wind-speed-change-rel relative change of wind speed at this level
359 turbulence/factor factor for the calculated turbulence magnitude at this level
361 static void setupWindBranch( string branchName, double dir, double speed, double gust )
363 SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true);
364 vector<SGPropertyNode_ptr> entries = branch->getChildren("entry");
365 for ( vector<SGPropertyNode_ptr>::iterator it = entries.begin(); it != entries.end(); it++) {
367 // change wind direction as configured
368 double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 );
369 if( layer_dir >= 360.0 ) layer_dir -= 360.0;
370 if( layer_dir < 0.0 ) layer_dir += 360.0;
371 (*it)->setDoubleValue("wind-from-heading-deg", layer_dir);
373 double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 ));
374 (*it)->setDoubleValue("wind-speed-kt", layer_speed );
376 // add some turbulence
377 SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true);
379 double turbulence_norm = speed/50;
381 turbulence_norm += (gust-speed)/25;
383 if( turbulence_norm > 1.0 ) turbulence_norm = 1.0;
385 turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 );
386 turbulence->setDoubleValue( "magnitude-norm", turbulence_norm );
390 static void setupWind( bool setup_boundary, bool setup_aloft, double dir, double speed, double gust )
393 setupWindBranch( "boundary", dir, speed, gust );
396 setupWindBranch( "aloft", dir, speed, gust );
399 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
401 double dval = EnvironmentUpdatePeriodSec * dt;
403 if (fabs(currentval - requiredval) < dval) return requiredval;
404 if (currentval < requiredval) return (currentval + dval);
405 if (currentval > requiredval) return (currentval - dval);
413 wind_interpolation_required = true;
417 FGMetarCtrl::reinit ()
422 static inline double convert_to_360( double d )
424 if( d < 0.0 ) return d + 360.0;
425 if( d >= 360.0 ) return d - 360.0;
429 static inline double convert_to_180( double d )
431 return d > 180.0 ? d - 360.0 : d;
435 FGMetarCtrl::update(double dt)
437 if( dt <= 0 || !metar_valid ||!enabled)
440 windModulator->update(dt);
441 // Interpolate the current configuration closer to the actual METAR
443 bool reinit_required = false;
444 bool layer_rebuild_required = false;
447 double dir = base_wind_dir_n->getDoubleValue();
448 double speed = base_wind_speed_n->getDoubleValue();
449 double gust = gust_wind_speed_n->getDoubleValue();
450 setupWind(true, setup_winds_aloft, dir, speed, gust);
452 double metarvis = min_visibility_n->getDoubleValue();
453 fgDefaultWeatherValue("visibility-m", metarvis);
455 double metarpressure = pressure_n->getDoubleValue();
456 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
458 // We haven't already loaded a METAR, so apply it immediately.
459 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
460 vector<SGPropertyNode_ptr>::const_iterator layer;
461 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
464 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
465 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
467 target->setStringValue("coverage",
468 (*layer)->getStringValue("coverage", "clear"));
469 target->setDoubleValue("elevation-ft",
470 (*layer)->getDoubleValue("elevation-ft"));
471 target->setDoubleValue("thickness-ft",
472 (*layer)->getDoubleValue("thickness-ft"));
473 target->setDoubleValue("span-m", 40000.0);
476 first_update = false;
477 reinit_required = true;
478 layer_rebuild_required = true;
481 if( wind_interpolation_required ) {
482 // Generate interpolated values between the METAR and the current
485 // Pick up the METAR wind values and convert them into a vector.
487 double metar_speed = base_wind_speed_n->getDoubleValue();
488 double metar_heading = base_wind_dir_n->getDoubleValue();
490 metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
491 metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
493 // Convert the current wind values and convert them into a vector
495 double speed = boundary_wind_speed_n->getDoubleValue();
496 double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
498 current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
499 current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
501 // Determine the maximum component-wise value that the wind can change.
502 // First we determine the fraction in the X and Y component, then
503 // factor by the maximum wind change.
504 double x = fabs(current[0] - metar[0]);
505 double y = fabs(current[1] - metar[1]);
507 // only interpolate if we have a difference
509 double dx = x / (x + y);
512 double maxdx = dx * MaxWindChangeKtsSec;
513 double maxdy = dy * MaxWindChangeKtsSec;
515 // Interpolate each component separately.
516 current[0] = interpolate_val(current[0], metar[0], maxdx);
517 current[1] = interpolate_val(current[1], metar[1], maxdy);
519 // Now convert back to polar coordinates.
520 if ((current[0] == 0.0) && (current[1] == 0.0)) {
521 // Special case where there is no wind (otherwise atan2 barfs)
524 // Some real wind to convert back from. Work out the speed
525 // and direction value in degrees.
526 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
527 dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
529 // Normalize the direction.
533 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
535 double gust = gust_wind_speed_n->getDoubleValue();
536 setupWind(true, setup_winds_aloft, dir_from, speed, gust);
537 reinit_required = true;
539 wind_interpolation_required = false;
541 } else { // if(wind_interpolation_required)
542 // interpolation of wind vector is finished, apply wind
543 // variations and gusts for the boundary layer only
545 // start with the main wind direction
546 double wind_dir = base_wind_dir_n->getDoubleValue();
547 double min = convert_to_180(base_wind_range_from_n->getDoubleValue());
548 double max = convert_to_180(base_wind_range_to_n->getDoubleValue());
550 // if variable winds configured, modulate the wind direction
551 double f = windModulator->get_direction_offset_norm();
552 wind_dir = min+(max-min)*f;
553 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
554 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
557 // start with main wind speed
558 double wind_speed = base_wind_speed_n->getDoubleValue();
559 max = gust_wind_speed_n->getDoubleValue();
560 if( max > wind_speed ) {
561 // if gusts are configured, modulate wind magnitude
562 double f = windModulator->get_magnitude_factor_norm();
563 wind_speed = wind_speed+(max-wind_speed)*f;
564 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
566 setupWind(true, false, wind_dir, wind_speed, max);
567 reinit_required = true;
570 // Now handle the visibility. We convert both visibility values
571 // to X-values, then interpolate from there, then back to real values.
572 // The length_scale is fixed to 1000m, so the visibility changes by
573 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
574 // whichever is more.
575 double vis = boundary_visibility_n->getDoubleValue();;
576 double metarvis = min_visibility_n->getDoubleValue();
577 if( vis != metarvis ) {
578 double currentxval = log(1000.0 + vis);
579 double metarxval = log(1000.0 + metarvis);
581 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
583 // Now convert back from an X-value to a straightforward visibility.
584 vis = exp(currentxval) - 1000.0;
585 fgDefaultWeatherValue("visibility-m", vis);
586 reinit_required = true;
589 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
590 double metarpressure = pressure_n->getDoubleValue();
591 if( pressure != metarpressure ) {
592 pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
593 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
594 reinit_required = true;
597 // Set the cloud layers by interpolating over the METAR versions.
598 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
599 vector<SGPropertyNode_ptr>::const_iterator layer;
600 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
602 double aircraft_alt = fgGetDouble("/position/altitude-ft");
605 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
606 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
608 // In the case of clouds, we want to avoid writing if nothing has
609 // changed, as these properties are tied to the renderer and will
610 // cause the clouds to be updated, reseting the texture locations.
612 // We don't interpolate the coverage values as no-matter how we
613 // do it, it will be quite a sudden change of texture. Better to
614 // have a single change than four or five.
615 const char *coverage = (*layer)->getStringValue("coverage", "clear");
616 SGPropertyNode *cov = target->getNode("coverage", true);
617 if (strcmp(cov->getStringValue(), coverage) != 0) {
618 cov->setStringValue(coverage);
619 layer_rebuild_required = true;
622 double required_alt = (*layer)->getDoubleValue("elevation-ft");
623 double current_alt = target->getDoubleValue("elevation-ft");
624 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
625 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
627 if (current_alt < -9000 || required_alt < -9000 ||
628 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
629 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
630 // We don't interpolate any layers that are
631 // - too far above us to be visible
632 // - too far below us to be visible
633 // - with too large a difference to make interpolation sensible
634 // - to or from -9999 (used as a placeholder)
635 // - any values that are too high above us,
636 if (current_alt != required_alt)
637 target->setDoubleValue("elevation-ft", required_alt);
639 if (thickness->getDoubleValue() != required_thickness)
640 thickness->setDoubleValue(required_thickness);
643 // Interpolate the other values in the usual way
644 if (current_alt != required_alt) {
645 current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
646 target->setDoubleValue("elevation-ft", current_alt);
649 double current_thickness = thickness->getDoubleValue();
651 if (current_thickness != required_thickness) {
652 current_thickness = interpolate_val(current_thickness,
654 MaxCloudThicknessChangeFtSec);
655 thickness->setDoubleValue(current_thickness);
661 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
662 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
663 //TODO: check if temperature/dewpoint have changed. This requires reinit.
665 // Force an update of the 3D clouds
666 if( layer_rebuild_required )
667 fgSetInt("/environment/rebuild-layers", 1 );
669 // Reinitializing of the environment controller required
670 if( reinit_required )
671 _environmentCtrl->reinit();
674 const char * FGMetarCtrl::get_metar(void) const
676 return metar.c_str();
679 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
680 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
682 void FGMetarCtrl::set_metar( const char * metar_string )
686 metar = metar_string;
688 SGSharedPtr<FGMetar> m;
690 m = new FGMetar( metar_string );
692 catch( sg_io_exception ) {
693 fprintf( stderr, "can't get metar: %s\n", metar_string );
698 wind_interpolation_required = true;
700 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
701 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
703 const SGMetarVisibility *dirvis = m->getDirVisibility();
704 for (i = 0; i < 8; i++, dirvis++) {
705 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
706 double v = dirvis->getVisibility_m();
708 vis->setDoubleValue("min-m", v);
709 vis->setDoubleValue("max-m", v);
712 base_wind_dir_n->setIntValue( m->getWindDir() );
713 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
714 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
715 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
716 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
717 temperature_n->setDoubleValue( m->getTemperature_C() );
718 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
719 humidity_n->setDoubleValue( m->getRelHumidity() );
720 pressure_n->setDoubleValue( m->getPressure_inHg() );
723 // get station elevation to compute cloud base
724 double station_elevation_ft = 0;
726 // 1. check the id given in the metar
727 FGAirport* a = FGAirport::findByIdent(m->getId());
729 // 2. if unknown, find closest airport with metar to current position
731 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
732 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
735 // 3. otherwise use ground elevation
737 station_elevation_ft = a->getElevation();
738 station_id_n->setStringValue( a->ident());
740 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
741 station_id_n->setStringValue( m->getId());
745 station_elevation_n->setDoubleValue( station_elevation_ft );
747 vector<SGMetarCloud> cv = m->getClouds();
748 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
750 int layer_cnt = environment_clouds_n->getChildren("layer").size();
751 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
754 const char *coverage = "clear";
755 double elevation = -9999.0;
756 double thickness = 0.0;
757 const double span = 40000.0;
759 if (cloud != cloud_end) {
760 int c = cloud->getCoverage();
761 coverage = coverage_string[c];
762 elevation = cloud->getAltitude_ft() + station_elevation_ft;
763 thickness = thickness_value[c];
767 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
769 // if the coverage has changed, a rebuild of the layer is needed
770 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
771 layer->setStringValue("coverage", coverage);
773 layer->setDoubleValue("elevation-ft", elevation);
774 layer->setDoubleValue("thickness-ft", thickness);
775 layer->setDoubleValue("span-m", span);
778 rain_n->setDoubleValue(m->getRain());
779 hail_n->setDoubleValue(m->getHail());
780 snow_n->setDoubleValue(m->getSnow());
781 snow_cover_n->setBoolValue(m->getSnowCover());
785 #if defined(ENABLE_THREADS)
787 * This class represents the thread of execution responsible for
788 * fetching the metar data.
790 class MetarThread : public OpenThreads::Thread {
792 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
796 * Fetche the metar data from the NOAA.
801 FGMetarFetcher * metar_fetcher;
804 void MetarThread::run()
807 string airport_id = metar_fetcher->request_queue.pop();
809 if( airport_id.size() == 0 )
812 if( metar_fetcher->_error_count > 3 ) {
813 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
817 metar_fetcher->fetch( airport_id );
822 FGMetarFetcher::FGMetarFetcher() :
823 #if defined(ENABLE_THREADS)
832 longitude_n = fgGetNode( "/position/longitude-deg", true );
833 latitude_n = fgGetNode( "/position/latitude-deg", true );
834 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
836 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
837 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
838 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
839 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
841 output_n = fgGetNode("/environment/metar/data", true );
842 #if defined(ENABLE_THREADS)
843 metar_thread = new MetarThread(this);
844 // FIXME: do we really need setProcessorAffinity()?
845 // metar_thread->setProcessorAffinity(1);
846 metar_thread->start();
847 #endif // ENABLE_THREADS
851 FGMetarFetcher::~FGMetarFetcher()
853 #if defined(ENABLE_THREADS)
854 request_queue.push("");
855 metar_thread->join();
857 #endif // ENABLE_THREADS
860 void FGMetarFetcher::init ()
867 current_airport_id.clear();
870 void FGMetarFetcher::reinit ()
875 /* search for closest airport with metar every xx seconds */
876 static const int search_interval_sec = 60;
878 /* fetch metar for airport, even if airport has not changed every xx seconds */
879 static const int fetch_interval_sec = 900;
881 /* reset error counter after xxx seconds */
882 static const int error_timer_sec = 3;
884 void FGMetarFetcher::update (double delta_time_sec)
886 fetch_timer -= delta_time_sec;
887 search_timer -= delta_time_sec;
888 error_timer -= delta_time_sec;
890 if( error_timer <= 0.0 ) {
891 error_timer = error_timer_sec;
895 if( enable_n->getBoolValue() == false )
898 FGAirport * a = NULL;
900 if( search_timer <= 0.0 ) {
901 // search timer expired, search closest airport with metar
902 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
903 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
904 search_timer = search_interval_sec;
911 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
912 // fetch timer expired or airport has changed, schedule a fetch
913 current_airport_id = a->ident();
914 fetch_timer = fetch_interval_sec;
915 #if defined(ENABLE_THREADS)
916 // push this airport id into the queue for the worker thread
917 request_queue.push( current_airport_id );
919 // if there is no worker thread, immediately fetch the data
920 fetch( current_airport_id );
925 void FGMetarFetcher::fetch( const string & id )
927 SGSharedPtr<FGMetar> result = NULL;
929 // fetch current metar data
931 string host = proxy_host_n->getStringValue();
932 string auth = proxy_auth_n->getStringValue();
933 string port = proxy_port_n->getStringValue();
935 result = new FGMetar( id, host, port, auth);
937 long max_age = max_age_n->getLongValue();
938 long age = result->getAge_min();
940 if (max_age && age > max_age) {
941 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
942 if (++_stale_count > 10) {
944 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
950 } catch (const sg_io_exception& e) {
951 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
955 // write the metar to the property node, the rest is done by the methods tied to this property
956 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
957 if( result != NULL && enable_n->getBoolValue() == true )
958 output_n->setStringValue( result->getData() );
961 // end of environment_ctrl.cxx