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 vector<bucket*>::iterator it = table.begin() + i;
174 while( it != table.end() )
178 sort(table.begin(), table.end(), bucket::lessThan);
182 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
184 double altitude_ft = altitude_n->getDoubleValue();
185 double altitude_agl_ft = altitude_agl_n->getDoubleValue();
186 double boundary_transition =
187 boundary_transition_n == NULL ? 500 : boundary_transition_n->getDoubleValue();
189 int length = _boundary_table.size();
193 double boundary_limit = _boundary_table[length-1]->altitude_ft;
194 if (boundary_limit >= altitude_agl_ft) {
195 do_interpolate(_boundary_table, altitude_agl_ft, _environment);
197 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
198 //TODO: this is 500ft above the top altitude of boundary layer
199 //shouldn't this be +/-250 ft off of the top altitude?
201 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
202 do_interpolate(_aloft_table, altitude_ft, &env2);
204 (altitude_agl_ft - boundary_limit) / boundary_transition;
205 interpolate(&env1, &env2, fraction, _environment);
210 do_interpolate(_aloft_table, altitude_ft, _environment);
214 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table, double altitude_ft, FGEnvironment * environment)
216 int length = table.size();
220 // Boundary conditions
221 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
222 environment->copy(table[0]->environment);
224 } else if (table[length-1]->altitude_ft <= altitude_ft) {
225 environment->copy(table[length-1]->environment);
228 // Search the interpolation table
229 for (int i = 0; i < length - 1; i++) {
230 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
231 FGEnvironment * env1 = &(table[i]->environment);
232 FGEnvironment * env2 = &(table[i+1]->environment);
234 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
238 ((altitude_ft - table[i]->altitude_ft) /
239 (table[i+1]->altitude_ft - table[i]->altitude_ft));
240 interpolate(env1, env2, fraction, environment);
248 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
250 return (altitude_ft < b.altitude_ft);
254 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
256 return (a->altitude_ft) < (b->altitude_ft);
260 ////////////////////////////////////////////////////////////////////////
261 // Implementation of FGMetarCtrl.
262 ////////////////////////////////////////////////////////////////////////
264 FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
265 : _environmentCtrl(environmentCtrl),
266 station_elevation_ft(0.0),
268 setup_winds_aloft(true),
269 wind_interpolation_required(true),
270 // Interpolation constant definitions.
271 EnvironmentUpdatePeriodSec( 0.2 ),
272 MaxWindChangeKtsSec( 0.2 ),
273 MaxVisChangePercentSec( 0.05 ),
274 MaxPressureChangeInHgSec( 0.0033 ),
275 MaxCloudAltitudeChangeFtSec( 20.0 ),
276 MaxCloudThicknessChangeFtSec( 50.0 ),
277 MaxCloudInterpolationHeightFt( 5000.0 ),
278 MaxCloudInterpolationDeltaFt( 4000.0 )
280 windModulator = new FGBasicWindModulator();
282 metar_base_n = fgGetNode( "/environment/metar", true );
283 station_id_n = metar_base_n->getNode("station-id", true );
284 station_elevation_n = metar_base_n->getNode("station-elevation-ft", true );
285 min_visibility_n = metar_base_n->getNode("min-visibility-m", true );
286 max_visibility_n = metar_base_n->getNode("max-visibility-m", true );
287 base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true );
288 base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true );
289 base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true );
290 base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
291 gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true );
292 temperature_n = metar_base_n->getNode("temperature-degc", true );
293 dewpoint_n = metar_base_n->getNode("dewpoint-degc", true );
294 humidity_n = metar_base_n->getNode("rel-humidity-norm", true );
295 pressure_n = metar_base_n->getNode("pressure-inhg", true );
296 clouds_n = metar_base_n->getNode("clouds", true );
297 rain_n = metar_base_n->getNode("rain-norm", true );
298 hail_n = metar_base_n->getNode("hail-norm", true );
299 snow_n = metar_base_n->getNode("snow-norm", true );
300 snow_cover_n = metar_base_n->getNode("snow-cover", true );
301 ground_elevation_n = fgGetNode( "/position/ground-elev-m", true );
302 longitude_n = fgGetNode( "/position/longitude-deg", true );
303 latitude_n = fgGetNode( "/position/latitude-deg", true );
304 environment_clouds_n = fgGetNode("/environment/clouds");
306 boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt");
307 boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
308 boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
309 boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
312 FGMetarCtrl::~FGMetarCtrl ()
316 void FGMetarCtrl::bind ()
318 fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid );
319 fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled );
320 fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft );
323 void FGMetarCtrl::unbind ()
325 fgUntie("/environment/metar/valid");
326 fgUntie("/environment/params/metar-updates-environment");
327 fgUntie("/environment/params/metar-updates-winds-aloft");
330 // use a "command" to set station temp at station elevation
331 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
333 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
334 node->setFloatValue( temp_degc );
335 node = args.getNode("altitude-ft", 0, true);
336 node->setFloatValue( altitude_ft );
337 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
340 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
342 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
343 node->setFloatValue( dewpoint_degc );
344 node = args.getNode("altitude-ft", 0, true);
345 node->setFloatValue( altitude_ft );
346 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
350 Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
353 wind-from-heading-deg
355 turbulence/magnitude-norm
358 wind-heading-change-deg how many degrees does the wind direction change at this level
359 wind-speed-change-rel relative change of wind speed at this level
360 turbulence/factor factor for the calculated turbulence magnitude at this level
362 static void setupWindBranch( string branchName, double dir, double speed, double gust )
364 SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true);
365 vector<SGPropertyNode_ptr> entries = branch->getChildren("entry");
366 for ( vector<SGPropertyNode_ptr>::iterator it = entries.begin(); it != entries.end(); it++) {
368 // change wind direction as configured
369 double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 );
370 if( layer_dir >= 360.0 ) layer_dir -= 360.0;
371 if( layer_dir < 0.0 ) layer_dir += 360.0;
372 (*it)->setDoubleValue("wind-from-heading-deg", layer_dir);
374 double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 ));
375 (*it)->setDoubleValue("wind-speed-kt", layer_speed );
377 // add some turbulence
378 SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true);
380 double turbulence_norm = speed/50;
382 turbulence_norm += (gust-speed)/25;
384 if( turbulence_norm > 1.0 ) turbulence_norm = 1.0;
386 turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 );
387 turbulence->setDoubleValue( "magnitude-norm", turbulence_norm );
391 static void setupWind( bool setup_boundary, bool setup_aloft, double dir, double speed, double gust )
394 setupWindBranch( "boundary", dir, speed, gust );
397 setupWindBranch( "aloft", dir, speed, gust );
400 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
402 double dval = EnvironmentUpdatePeriodSec * dt;
404 if (fabs(currentval - requiredval) < dval) return requiredval;
405 if (currentval < requiredval) return (currentval + dval);
406 if (currentval > requiredval) return (currentval - dval);
414 wind_interpolation_required = true;
418 FGMetarCtrl::reinit ()
423 static inline double convert_to_360( double d )
425 if( d < 0.0 ) return d + 360.0;
426 if( d >= 360.0 ) return d - 360.0;
430 static inline double convert_to_180( double d )
432 return d > 180.0 ? d - 360.0 : d;
436 FGMetarCtrl::update(double dt)
438 if( dt <= 0 || !metar_valid ||!enabled)
441 windModulator->update(dt);
442 // Interpolate the current configuration closer to the actual METAR
444 bool reinit_required = false;
445 bool layer_rebuild_required = false;
448 double dir = base_wind_dir_n->getDoubleValue();
449 double speed = base_wind_speed_n->getDoubleValue();
450 double gust = gust_wind_speed_n->getDoubleValue();
451 setupWind(true, setup_winds_aloft, dir, speed, gust);
453 double metarvis = min_visibility_n->getDoubleValue();
454 fgDefaultWeatherValue("visibility-m", metarvis);
456 double metarpressure = pressure_n->getDoubleValue();
457 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
459 // We haven't already loaded a METAR, so apply it immediately.
460 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
461 vector<SGPropertyNode_ptr>::const_iterator layer;
462 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
465 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
466 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
468 target->setStringValue("coverage",
469 (*layer)->getStringValue("coverage", "clear"));
470 target->setDoubleValue("elevation-ft",
471 (*layer)->getDoubleValue("elevation-ft"));
472 target->setDoubleValue("thickness-ft",
473 (*layer)->getDoubleValue("thickness-ft"));
474 target->setDoubleValue("span-m", 40000.0);
477 first_update = false;
478 reinit_required = true;
479 layer_rebuild_required = true;
482 if( wind_interpolation_required ) {
483 // Generate interpolated values between the METAR and the current
486 // Pick up the METAR wind values and convert them into a vector.
488 double metar_speed = base_wind_speed_n->getDoubleValue();
489 double metar_heading = base_wind_dir_n->getDoubleValue();
491 metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
492 metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
494 // Convert the current wind values and convert them into a vector
496 double speed = boundary_wind_speed_n->getDoubleValue();
497 double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
499 current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
500 current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
502 // Determine the maximum component-wise value that the wind can change.
503 // First we determine the fraction in the X and Y component, then
504 // factor by the maximum wind change.
505 double x = fabs(current[0] - metar[0]);
506 double y = fabs(current[1] - metar[1]);
508 // only interpolate if we have a difference
510 double dx = x / (x + y);
513 double maxdx = dx * MaxWindChangeKtsSec;
514 double maxdy = dy * MaxWindChangeKtsSec;
516 // Interpolate each component separately.
517 current[0] = interpolate_val(current[0], metar[0], maxdx);
518 current[1] = interpolate_val(current[1], metar[1], maxdy);
520 // Now convert back to polar coordinates.
521 if ((current[0] == 0.0) && (current[1] == 0.0)) {
522 // Special case where there is no wind (otherwise atan2 barfs)
525 // Some real wind to convert back from. Work out the speed
526 // and direction value in degrees.
527 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
528 dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
530 // Normalize the direction.
534 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
536 double gust = gust_wind_speed_n->getDoubleValue();
537 setupWind(true, setup_winds_aloft, dir_from, speed, gust);
538 reinit_required = true;
540 wind_interpolation_required = false;
542 } else { // if(wind_interpolation_required)
543 // interpolation of wind vector is finished, apply wind
544 // variations and gusts for the boundary layer only
546 // start with the main wind direction
547 double wind_dir = base_wind_dir_n->getDoubleValue();
548 double min = convert_to_180(base_wind_range_from_n->getDoubleValue());
549 double max = convert_to_180(base_wind_range_to_n->getDoubleValue());
551 // if variable winds configured, modulate the wind direction
552 double f = windModulator->get_direction_offset_norm();
553 wind_dir = min+(max-min)*f;
554 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
555 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
558 // start with main wind speed
559 double wind_speed = base_wind_speed_n->getDoubleValue();
560 max = gust_wind_speed_n->getDoubleValue();
561 if( max > wind_speed ) {
562 // if gusts are configured, modulate wind magnitude
563 double f = windModulator->get_magnitude_factor_norm();
564 wind_speed = wind_speed+(max-wind_speed)*f;
565 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
567 setupWind(true, false, wind_dir, wind_speed, max);
568 reinit_required = true;
571 // Now handle the visibility. We convert both visibility values
572 // to X-values, then interpolate from there, then back to real values.
573 // The length_scale is fixed to 1000m, so the visibility changes by
574 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
575 // whichever is more.
576 double vis = boundary_visibility_n->getDoubleValue();;
577 double metarvis = min_visibility_n->getDoubleValue();
578 if( vis != metarvis ) {
579 double currentxval = log(1000.0 + vis);
580 double metarxval = log(1000.0 + metarvis);
582 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
584 // Now convert back from an X-value to a straightforward visibility.
585 vis = exp(currentxval) - 1000.0;
586 fgDefaultWeatherValue("visibility-m", vis);
587 reinit_required = true;
590 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
591 double metarpressure = pressure_n->getDoubleValue();
592 if( pressure != metarpressure ) {
593 pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
594 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
595 reinit_required = true;
598 // Set the cloud layers by interpolating over the METAR versions.
599 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
600 vector<SGPropertyNode_ptr>::const_iterator layer;
601 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
603 double aircraft_alt = fgGetDouble("/position/altitude-ft");
606 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
607 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
609 // In the case of clouds, we want to avoid writing if nothing has
610 // changed, as these properties are tied to the renderer and will
611 // cause the clouds to be updated, reseting the texture locations.
613 // We don't interpolate the coverage values as no-matter how we
614 // do it, it will be quite a sudden change of texture. Better to
615 // have a single change than four or five.
616 const char *coverage = (*layer)->getStringValue("coverage", "clear");
617 SGPropertyNode *cov = target->getNode("coverage", true);
618 if (strcmp(cov->getStringValue(), coverage) != 0) {
619 cov->setStringValue(coverage);
620 layer_rebuild_required = true;
623 double required_alt = (*layer)->getDoubleValue("elevation-ft");
624 double current_alt = target->getDoubleValue("elevation-ft");
625 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
626 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
628 if (current_alt < -9000 || required_alt < -9000 ||
629 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
630 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
631 // We don't interpolate any layers that are
632 // - too far above us to be visible
633 // - too far below us to be visible
634 // - with too large a difference to make interpolation sensible
635 // - to or from -9999 (used as a placeholder)
636 // - any values that are too high above us,
637 if (current_alt != required_alt)
638 target->setDoubleValue("elevation-ft", required_alt);
640 if (thickness->getDoubleValue() != required_thickness)
641 thickness->setDoubleValue(required_thickness);
644 // Interpolate the other values in the usual way
645 if (current_alt != required_alt) {
646 current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
647 target->setDoubleValue("elevation-ft", current_alt);
650 double current_thickness = thickness->getDoubleValue();
652 if (current_thickness != required_thickness) {
653 current_thickness = interpolate_val(current_thickness,
655 MaxCloudThicknessChangeFtSec);
656 thickness->setDoubleValue(current_thickness);
662 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
663 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
664 //TODO: check if temperature/dewpoint have changed. This requires reinit.
666 // Force an update of the 3D clouds
667 if( layer_rebuild_required )
668 fgSetInt("/environment/rebuild-layers", 1 );
670 // Reinitializing of the environment controller required
671 if( reinit_required )
672 _environmentCtrl->reinit();
675 const char * FGMetarCtrl::get_metar(void) const
677 return metar.c_str();
680 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
681 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
683 void FGMetarCtrl::set_metar( const char * metar_string )
687 metar = metar_string;
689 SGSharedPtr<FGMetar> m;
691 m = new FGMetar( metar_string );
693 catch( sg_io_exception ) {
694 fprintf( stderr, "can't get metar: %s\n", metar_string );
699 wind_interpolation_required = true;
701 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
702 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
704 const SGMetarVisibility *dirvis = m->getDirVisibility();
705 for (i = 0; i < 8; i++, dirvis++) {
706 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
707 double v = dirvis->getVisibility_m();
709 vis->setDoubleValue("min-m", v);
710 vis->setDoubleValue("max-m", v);
713 base_wind_dir_n->setIntValue( m->getWindDir() );
714 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
715 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
716 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
717 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
718 temperature_n->setDoubleValue( m->getTemperature_C() );
719 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
720 humidity_n->setDoubleValue( m->getRelHumidity() );
721 pressure_n->setDoubleValue( m->getPressure_inHg() );
724 // get station elevation to compute cloud base
725 double station_elevation_ft = 0;
727 // 1. check the id given in the metar
728 FGAirport* a = FGAirport::findByIdent(m->getId());
730 // 2. if unknown, find closest airport with metar to current position
732 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
733 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
736 // 3. otherwise use ground elevation
738 station_elevation_ft = a->getElevation();
739 station_id_n->setStringValue( a->ident());
741 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
742 station_id_n->setStringValue( m->getId());
746 station_elevation_n->setDoubleValue( station_elevation_ft );
748 vector<SGMetarCloud> cv = m->getClouds();
749 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
751 int layer_cnt = environment_clouds_n->getChildren("layer").size();
752 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
755 const char *coverage = "clear";
756 double elevation = -9999.0;
757 double thickness = 0.0;
758 const double span = 40000.0;
760 if (cloud != cloud_end) {
761 int c = cloud->getCoverage();
762 coverage = coverage_string[c];
763 elevation = cloud->getAltitude_ft() + station_elevation_ft;
764 thickness = thickness_value[c];
768 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
770 // if the coverage has changed, a rebuild of the layer is needed
771 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
772 layer->setStringValue("coverage", coverage);
774 layer->setDoubleValue("elevation-ft", elevation);
775 layer->setDoubleValue("thickness-ft", thickness);
776 layer->setDoubleValue("span-m", span);
779 rain_n->setDoubleValue(m->getRain());
780 hail_n->setDoubleValue(m->getHail());
781 snow_n->setDoubleValue(m->getSnow());
782 snow_cover_n->setBoolValue(m->getSnowCover());
786 #if defined(ENABLE_THREADS)
788 * This class represents the thread of execution responsible for
789 * fetching the metar data.
791 class MetarThread : public OpenThreads::Thread {
793 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
797 * Fetche the metar data from the NOAA.
802 FGMetarFetcher * metar_fetcher;
805 void MetarThread::run()
808 string airport_id = metar_fetcher->request_queue.pop();
810 if( airport_id.size() == 0 )
813 if( metar_fetcher->_error_count > 3 ) {
814 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
818 metar_fetcher->fetch( airport_id );
823 FGMetarFetcher::FGMetarFetcher() :
824 #if defined(ENABLE_THREADS)
833 longitude_n = fgGetNode( "/position/longitude-deg", true );
834 latitude_n = fgGetNode( "/position/latitude-deg", true );
835 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
837 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
838 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
839 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
840 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
842 output_n = fgGetNode("/environment/metar/data", true );
843 #if defined(ENABLE_THREADS)
844 metar_thread = new MetarThread(this);
845 // FIXME: do we really need setProcessorAffinity()?
846 // metar_thread->setProcessorAffinity(1);
847 metar_thread->start();
848 #endif // ENABLE_THREADS
852 FGMetarFetcher::~FGMetarFetcher()
854 #if defined(ENABLE_THREADS)
855 request_queue.push("");
856 metar_thread->join();
858 #endif // ENABLE_THREADS
861 void FGMetarFetcher::init ()
868 current_airport_id.clear();
871 void FGMetarFetcher::reinit ()
876 /* search for closest airport with metar every xx seconds */
877 static const int search_interval_sec = 60;
879 /* fetch metar for airport, even if airport has not changed every xx seconds */
880 static const int fetch_interval_sec = 900;
882 /* reset error counter after xxx seconds */
883 static const int error_timer_sec = 3;
885 void FGMetarFetcher::update (double delta_time_sec)
887 fetch_timer -= delta_time_sec;
888 search_timer -= delta_time_sec;
889 error_timer -= delta_time_sec;
891 if( error_timer <= 0.0 ) {
892 error_timer = error_timer_sec;
896 if( enable_n->getBoolValue() == false )
899 FGAirport * a = NULL;
901 if( search_timer <= 0.0 ) {
902 // search timer expired, search closest airport with metar
903 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
904 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
905 search_timer = search_interval_sec;
912 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
913 // fetch timer expired or airport has changed, schedule a fetch
914 current_airport_id = a->ident();
915 fetch_timer = fetch_interval_sec;
916 #if defined(ENABLE_THREADS)
917 // push this airport id into the queue for the worker thread
918 request_queue.push( current_airport_id );
920 // if there is no worker thread, immediately fetch the data
921 fetch( current_airport_id );
926 void FGMetarFetcher::fetch( const string & id )
928 SGSharedPtr<FGMetar> result = NULL;
930 // fetch current metar data
932 string host = proxy_host_n->getStringValue();
933 string auth = proxy_auth_n->getStringValue();
934 string port = proxy_port_n->getStringValue();
936 result = new FGMetar( id, host, port, auth);
938 long max_age = max_age_n->getLongValue();
939 long age = result->getAge_min();
941 if (max_age && age > max_age) {
942 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
943 if (++_stale_count > 10) {
945 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
951 } catch (const sg_io_exception& e) {
952 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
956 // write the metar to the property node, the rest is done by the methods tied to this property
957 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
958 if( result != NULL && enable_n->getBoolValue() == true )
959 output_n->setStringValue( result->getData() );
962 // end of environment_ctrl.cxx