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 station_elevation_ft(0.0),
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 ),
281 _environmentCtrl(environmentCtrl)
283 windModulator = new FGBasicWindModulator();
285 metar_base_n = fgGetNode( "/environment/metar", true );
286 station_id_n = metar_base_n->getNode("station-id", true );
287 station_elevation_n = metar_base_n->getNode("station-elevation-ft", true );
288 min_visibility_n = metar_base_n->getNode("min-visibility-m", true );
289 max_visibility_n = metar_base_n->getNode("max-visibility-m", true );
290 base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true );
291 base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true );
292 base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true );
293 base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
294 gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true );
295 temperature_n = metar_base_n->getNode("temperature-degc", true );
296 dewpoint_n = metar_base_n->getNode("dewpoint-degc", true );
297 humidity_n = metar_base_n->getNode("rel-humidity-norm", true );
298 pressure_n = metar_base_n->getNode("pressure-inhg", true );
299 clouds_n = metar_base_n->getNode("clouds", true );
300 rain_n = metar_base_n->getNode("rain-norm", true );
301 hail_n = metar_base_n->getNode("hail-norm", true );
302 snow_n = metar_base_n->getNode("snow-norm", true );
303 snow_cover_n = metar_base_n->getNode("snow-cover", true );
304 magnetic_variation_n = fgGetNode( "/environment/magnetic-variation-deg", true );
305 ground_elevation_n = fgGetNode( "/position/ground-elev-m", true );
306 longitude_n = fgGetNode( "/position/longitude-deg", true );
307 latitude_n = fgGetNode( "/position/latitude-deg", true );
308 environment_clouds_n = fgGetNode("/environment/clouds");
310 boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt");
311 boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
312 boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
313 boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
316 FGMetarCtrl::~FGMetarCtrl ()
320 void FGMetarCtrl::bind ()
322 fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid );
323 fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled );
324 fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft );
327 void FGMetarCtrl::unbind ()
329 fgUntie("/environment/metar/valid");
330 fgUntie("/environment/params/metar-updates-environment");
331 fgUntie("/environment/params/metar-updates-winds-aloft");
334 // use a "command" to set station temp at station elevation
335 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
337 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
338 node->setFloatValue( temp_degc );
339 node = args.getNode("altitude-ft", 0, true);
340 node->setFloatValue( altitude_ft );
341 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
344 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
346 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
347 node->setFloatValue( dewpoint_degc );
348 node = args.getNode("altitude-ft", 0, true);
349 node->setFloatValue( altitude_ft );
350 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
354 Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
357 wind-from-heading-deg
359 turbulence/magnitude-norm
362 wind-heading-change-deg how many degrees does the wind direction change at this level
363 wind-speed-change-rel relative change of wind speed at this level
364 turbulence/factor factor for the calculated turbulence magnitude at this level
366 static void setupWindBranch( string branchName, double dir, double speed, double gust )
368 SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true);
369 vector<SGPropertyNode_ptr> entries = branch->getChildren("entry");
370 for ( vector<SGPropertyNode_ptr>::iterator it = entries.begin(); it != entries.end(); it++) {
372 // change wind direction as configured
373 double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 );
374 if( layer_dir >= 360.0 ) layer_dir -= 360.0;
375 if( layer_dir < 0.0 ) layer_dir += 360.0;
376 (*it)->setDoubleValue("wind-from-heading-deg", layer_dir);
378 double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 ));
379 (*it)->setDoubleValue("wind-speed-kt", layer_speed );
381 // add some turbulence
382 SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true);
384 double turbulence_norm = speed/50;
386 turbulence_norm += (gust-speed)/25;
388 if( turbulence_norm > 1.0 ) turbulence_norm = 1.0;
390 turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 );
391 turbulence->setDoubleValue( "magnitude-norm", turbulence_norm );
395 static void setupWind( bool setup_aloft, double dir, double speed, double gust )
397 setupWindBranch( "boundary", dir, speed, gust );
399 setupWindBranch( "aloft", dir, speed, gust );
402 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
404 double dval = EnvironmentUpdatePeriodSec * dt;
406 if (fabs(currentval - requiredval) < dval) return requiredval;
407 if (currentval < requiredval) return (currentval + dval);
408 if (currentval > requiredval) return (currentval - dval);
416 wind_interpolation_required = true;
420 FGMetarCtrl::reinit ()
425 static inline double convert_to_360( double d )
427 if( d < 0.0 ) return d + 360.0;
428 if( d >= 360.0 ) return d - 360.0;
432 static inline double convert_to_180( double d )
434 return d > 180.0 ? d - 360.0 : d;
438 FGMetarCtrl::update(double dt)
440 if( dt <= 0 || !metar_valid ||!enabled)
443 windModulator->update(dt);
444 // Interpolate the current configuration closer to the actual METAR
446 bool reinit_required = false;
447 bool layer_rebuild_required = false;
450 double dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
451 double speed = base_wind_speed_n->getDoubleValue();
452 double gust = gust_wind_speed_n->getDoubleValue();
453 setupWind(setup_winds_aloft, dir, speed, gust);
455 double metarvis = min_visibility_n->getDoubleValue();
456 fgDefaultWeatherValue("visibility-m", metarvis);
458 double metarpressure = pressure_n->getDoubleValue();
459 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
461 // We haven't already loaded a METAR, so apply it immediately.
462 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
463 vector<SGPropertyNode_ptr>::const_iterator layer;
464 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
467 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
468 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
470 target->setStringValue("coverage",
471 (*layer)->getStringValue("coverage", "clear"));
472 target->setDoubleValue("elevation-ft",
473 (*layer)->getDoubleValue("elevation-ft"));
474 target->setDoubleValue("thickness-ft",
475 (*layer)->getDoubleValue("thickness-ft"));
476 target->setDoubleValue("span-m", 40000.0);
479 first_update = false;
480 reinit_required = true;
481 layer_rebuild_required = true;
484 if( wind_interpolation_required ) {
485 // Generate interpolated values between the METAR and the current
488 // Pick up the METAR wind values and convert them into a vector.
490 double metar_speed = base_wind_speed_n->getDoubleValue();
491 double metar_heading = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
493 metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
494 metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
496 // Convert the current wind values and convert them into a vector
498 double speed = boundary_wind_speed_n->getDoubleValue();
499 double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
501 current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
502 current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
504 // Determine the maximum component-wise value that the wind can change.
505 // First we determine the fraction in the X and Y component, then
506 // factor by the maximum wind change.
507 double x = fabs(current[0] - metar[0]);
508 double y = fabs(current[1] - metar[1]);
510 // only interpolate if we have a difference
512 double dx = x / (x + y);
515 double maxdx = dx * MaxWindChangeKtsSec;
516 double maxdy = dy * MaxWindChangeKtsSec;
518 // Interpolate each component separately.
519 current[0] = interpolate_val(current[0], metar[0], maxdx);
520 current[1] = interpolate_val(current[1], metar[1], maxdy);
522 // Now convert back to polar coordinates.
523 if ((fabs(current[0]) > 0.1) || (fabs(current[1]) > 0.1)) {
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 // Special case where there is no wind (otherwise atan2 barfs)
538 double gust = gust_wind_speed_n->getDoubleValue();
539 setupWind(setup_winds_aloft, dir_from, speed, gust);
540 reinit_required = true;
542 wind_interpolation_required = false;
544 } else { // if(wind_interpolation_required)
545 // interpolation of wind vector is finished, apply wind
546 // variations and gusts for the boundary layer only
549 bool wind_modulated = false;
551 // start with the main wind direction
552 double wind_dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
553 double min = convert_to_180(base_wind_range_from_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
554 double max = convert_to_180(base_wind_range_to_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
556 // if variable winds configured, modulate the wind direction
557 double f = windModulator->get_direction_offset_norm();
558 wind_dir = min+(max-min)*f;
559 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
560 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
561 wind_modulated = true;
564 // start with main wind speed
565 double wind_speed = base_wind_speed_n->getDoubleValue();
566 max = gust_wind_speed_n->getDoubleValue();
567 if( max > wind_speed ) {
568 // if gusts are configured, modulate wind magnitude
569 double f = windModulator->get_magnitude_factor_norm();
570 wind_speed = wind_speed+(max-wind_speed)*f;
571 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
572 wind_modulated = true;
574 if( wind_modulated ) {
575 setupWind(false, wind_dir, wind_speed, max);
576 reinit_required = true;
580 // Now handle the visibility. We convert both visibility values
581 // to X-values, then interpolate from there, then back to real values.
582 // The length_scale is fixed to 1000m, so the visibility changes by
583 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
584 // whichever is more.
585 double vis = boundary_visibility_n->getDoubleValue();;
586 double metarvis = min_visibility_n->getDoubleValue();
587 if( vis != metarvis ) {
588 double currentxval = log(1000.0 + vis);
589 double metarxval = log(1000.0 + metarvis);
591 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
593 // Now convert back from an X-value to a straightforward visibility.
594 vis = exp(currentxval) - 1000.0;
595 fgDefaultWeatherValue("visibility-m", vis);
596 reinit_required = true;
599 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
600 double metarpressure = pressure_n->getDoubleValue();
601 if( pressure != metarpressure ) {
602 pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
603 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
604 reinit_required = true;
607 // Set the cloud layers by interpolating over the METAR versions.
608 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
609 vector<SGPropertyNode_ptr>::const_iterator layer;
610 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
612 double aircraft_alt = fgGetDouble("/position/altitude-ft");
615 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
616 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
618 // In the case of clouds, we want to avoid writing if nothing has
619 // changed, as these properties are tied to the renderer and will
620 // cause the clouds to be updated, reseting the texture locations.
622 // We don't interpolate the coverage values as no-matter how we
623 // do it, it will be quite a sudden change of texture. Better to
624 // have a single change than four or five.
625 const char *coverage = (*layer)->getStringValue("coverage", "clear");
626 SGPropertyNode *cov = target->getNode("coverage", true);
627 if (strcmp(cov->getStringValue(), coverage) != 0) {
628 cov->setStringValue(coverage);
629 layer_rebuild_required = true;
632 double required_alt = (*layer)->getDoubleValue("elevation-ft");
633 double current_alt = target->getDoubleValue("elevation-ft");
634 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
635 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
637 if (current_alt < -9000 || required_alt < -9000 ||
638 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
639 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
640 // We don't interpolate any layers that are
641 // - too far above us to be visible
642 // - too far below us to be visible
643 // - with too large a difference to make interpolation sensible
644 // - to or from -9999 (used as a placeholder)
645 // - any values that are too high above us,
646 if (current_alt != required_alt)
647 target->setDoubleValue("elevation-ft", required_alt);
649 if (thickness->getDoubleValue() != required_thickness)
650 thickness->setDoubleValue(required_thickness);
653 // Interpolate the other values in the usual way
654 if (current_alt != required_alt) {
655 current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
656 target->setDoubleValue("elevation-ft", current_alt);
659 double current_thickness = thickness->getDoubleValue();
661 if (current_thickness != required_thickness) {
662 current_thickness = interpolate_val(current_thickness,
664 MaxCloudThicknessChangeFtSec);
665 thickness->setDoubleValue(current_thickness);
671 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
672 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
673 //TODO: check if temperature/dewpoint have changed. This requires reinit.
675 // Force an update of the 3D clouds
676 if( layer_rebuild_required )
677 fgSetInt("/environment/rebuild-layers", 1 );
679 // Reinitializing of the environment controller required
680 if( reinit_required )
681 _environmentCtrl->reinit();
684 const char * FGMetarCtrl::get_metar(void) const
686 return metar.c_str();
689 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
690 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
692 void FGMetarCtrl::set_metar( const char * metar_string )
696 metar = metar_string;
698 SGSharedPtr<FGMetar> m;
700 m = new FGMetar( metar_string );
702 catch( sg_io_exception ) {
703 fprintf( stderr, "can't get metar: %s\n", metar_string );
708 wind_interpolation_required = true;
710 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
711 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
713 const SGMetarVisibility *dirvis = m->getDirVisibility();
714 for (i = 0; i < 8; i++, dirvis++) {
715 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
716 double v = dirvis->getVisibility_m();
718 vis->setDoubleValue("min-m", v);
719 vis->setDoubleValue("max-m", v);
722 base_wind_dir_n->setIntValue( m->getWindDir() );
723 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
724 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
725 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
726 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
727 temperature_n->setDoubleValue( m->getTemperature_C() );
728 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
729 humidity_n->setDoubleValue( m->getRelHumidity() );
730 pressure_n->setDoubleValue( m->getPressure_inHg() );
733 // get station elevation to compute cloud base
734 double station_elevation_ft = 0;
736 // 1. check the id given in the metar
737 FGAirport* a = FGAirport::findByIdent(m->getId());
739 // 2. if unknown, find closest airport with metar to current position
741 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
742 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
745 // 3. otherwise use ground elevation
747 station_elevation_ft = a->getElevation();
748 station_id_n->setStringValue( a->ident());
750 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
751 station_id_n->setStringValue( m->getId());
755 station_elevation_n->setDoubleValue( station_elevation_ft );
757 vector<SGMetarCloud> cv = m->getClouds();
758 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
760 int layer_cnt = environment_clouds_n->getChildren("layer").size();
761 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
764 const char *coverage = "clear";
765 double elevation = -9999.0;
766 double thickness = 0.0;
767 const double span = 40000.0;
769 if (cloud != cloud_end) {
770 int c = cloud->getCoverage();
771 coverage = coverage_string[c];
772 elevation = cloud->getAltitude_ft() + station_elevation_ft;
773 thickness = thickness_value[c];
777 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
779 // if the coverage has changed, a rebuild of the layer is needed
780 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
781 layer->setStringValue("coverage", coverage);
783 layer->setDoubleValue("elevation-ft", elevation);
784 layer->setDoubleValue("thickness-ft", thickness);
785 layer->setDoubleValue("span-m", span);
788 rain_n->setDoubleValue(m->getRain());
789 hail_n->setDoubleValue(m->getHail());
790 snow_n->setDoubleValue(m->getSnow());
791 snow_cover_n->setBoolValue(m->getSnowCover());
795 #if defined(ENABLE_THREADS)
797 * This class represents the thread of execution responsible for
798 * fetching the metar data.
800 class MetarThread : public OpenThreads::Thread {
802 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
806 * Fetche the metar data from the NOAA.
811 FGMetarFetcher * metar_fetcher;
814 void MetarThread::run()
817 string airport_id = metar_fetcher->request_queue.pop();
819 if( airport_id.size() == 0 )
822 if( metar_fetcher->_error_count > 3 ) {
823 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
827 metar_fetcher->fetch( airport_id );
832 FGMetarFetcher::FGMetarFetcher() :
833 #if defined(ENABLE_THREADS)
843 longitude_n = fgGetNode( "/position/longitude-deg", true );
844 latitude_n = fgGetNode( "/position/latitude-deg", true );
845 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
847 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
848 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
849 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
850 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
852 output_n = fgGetNode("/environment/metar/data", true );
853 #if defined(ENABLE_THREADS)
854 metar_thread = new MetarThread(this);
855 // FIXME: do we really need setProcessorAffinity()?
856 // metar_thread->setProcessorAffinity(1);
857 metar_thread->start();
858 #endif // ENABLE_THREADS
862 FGMetarFetcher::~FGMetarFetcher()
864 #if defined(ENABLE_THREADS)
865 request_queue.push("");
866 metar_thread->join();
868 #endif // ENABLE_THREADS
871 void FGMetarFetcher::init ()
878 current_airport_id.clear();
880 hack to stop startup.nas complaining if metar arrives after nasal-dir-initialized
881 is fired. Immediately fetch and wait for the METAR before continuing. This gets the
882 /environment/metar/xxx properties filled before nasal-dir is initialized.
883 Maybe the runway selection should happen here to make startup.nas obsolete?
885 const char * startup_airport = fgGetString("/sim/startup/options/airport");
886 if( *startup_airport ) {
887 FGAirport * a = FGAirport::getByIdent( startup_airport );
889 SGGeod pos = SGGeod::fromDeg(a->getLongitude(), a->getLatitude());
890 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
891 current_airport_id = a->getId();
892 fetch( current_airport_id );
897 void FGMetarFetcher::reinit ()
902 /* search for closest airport with metar every xx seconds */
903 static const int search_interval_sec = 60;
905 /* fetch metar for airport, even if airport has not changed every xx seconds */
906 static const int fetch_interval_sec = 900;
908 /* reset error counter after xxx seconds */
909 static const int error_timer_sec = 3;
911 void FGMetarFetcher::update (double delta_time_sec)
913 fetch_timer -= delta_time_sec;
914 search_timer -= delta_time_sec;
915 error_timer -= delta_time_sec;
917 if( error_timer <= 0.0 ) {
918 error_timer = error_timer_sec;
922 if( enable_n->getBoolValue() == false ) {
927 // we were just enabled, reset all timers to
928 // trigger immediate metar fetch
932 error_timer = error_timer_sec;
936 FGAirport * a = NULL;
938 if( search_timer <= 0.0 ) {
939 // search timer expired, search closest airport with metar
940 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
941 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
942 search_timer = search_interval_sec;
949 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
950 // fetch timer expired or airport has changed, schedule a fetch
951 current_airport_id = a->ident();
952 fetch_timer = fetch_interval_sec;
953 #if defined(ENABLE_THREADS)
954 // push this airport id into the queue for the worker thread
955 request_queue.push( current_airport_id );
957 // if there is no worker thread, immediately fetch the data
958 fetch( current_airport_id );
963 void FGMetarFetcher::fetch( const string & id )
965 if( enable_n->getBoolValue() == false )
968 SGSharedPtr<FGMetar> result = NULL;
970 // fetch current metar data
972 string host = proxy_host_n->getStringValue();
973 string auth = proxy_auth_n->getStringValue();
974 string port = proxy_port_n->getStringValue();
976 result = new FGMetar( id, host, port, auth);
978 long max_age = max_age_n->getLongValue();
979 long age = result->getAge_min();
981 if (max_age && age > max_age) {
982 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
983 if (++_stale_count > 10) {
985 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
991 } catch (const sg_io_exception& e) {
992 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
994 // remove METAR flag from the airport
995 FGAirport * a = FGAirport::findByIdent( id );
996 if( a ) a->setMetar( false );
997 // immediately schedule a new search
1001 // write the metar to the property node, the rest is done by the methods tied to this property
1002 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
1003 if( result != NULL && enable_n->getBoolValue() == true )
1004 output_n->setStringValue( result->getData() );
1007 // end of environment_ctrl.cxx