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();
48 // permit heliports and seaports too
49 virtual FGPositioned::Type maxType() const
50 { return FGPositioned::SEAPORT; }
53 static AirportWithMetar airportWithMetarFilter;
55 ////////////////////////////////////////////////////////////////////////
56 // Implementation of FGEnvironmentCtrl abstract base class.
57 ////////////////////////////////////////////////////////////////////////
59 FGEnvironmentCtrl::FGEnvironmentCtrl ()
67 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
72 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
74 _environment = environment;
78 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
84 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
90 FGEnvironmentCtrl::setElevationFt (double elev_ft)
96 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
105 ////////////////////////////////////////////////////////////////////////
106 // Implementation of FGInterpolateEnvironmentCtrl.
107 ////////////////////////////////////////////////////////////////////////
110 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
112 altitude_n = fgGetNode("/position/altitude-ft", true);
113 altitude_agl_n = fgGetNode("/position/altitude-agl-ft", true);
114 boundary_transition_n = fgGetNode("/environment/config/boundary-transition-ft", false );
115 boundary_n = fgGetNode("/environment/config/boundary", true );
116 aloft_n = fgGetNode("/environment/config/aloft", true );
119 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
122 for (i = 0; i < _boundary_table.size(); i++)
123 delete _boundary_table[i];
124 for (i = 0; i < _aloft_table.size(); i++)
125 delete _aloft_table[i];
131 FGInterpolateEnvironmentCtrl::init ()
133 read_table( boundary_n, _boundary_table);
134 read_table( aloft_n, _aloft_table);
138 FGInterpolateEnvironmentCtrl::reinit ()
144 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector<bucket *> &table)
146 double last_altitude_ft = 0.0;
147 double sort_required = false;
150 for (i = 0; i < (size_t)node->nChildren(); i++) {
151 const SGPropertyNode * child = node->getChild(i);
152 if ( strcmp(child->getName(), "entry") == 0
153 && child->getStringValue("elevation-ft", "")[0] != '\0'
154 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
157 if( i < table.size() ) {
158 // recycle existing bucket
161 // more nodes than buckets in table, add a new one
166 b->environment.copy(table[i-1]->environment);
167 b->environment.read(child);
168 b->altitude_ft = b->environment.get_elevation_ft();
170 // check, if altitudes are in ascending order
171 if( b->altitude_ft < last_altitude_ft )
172 sort_required = true;
173 last_altitude_ft = b->altitude_ft;
176 // remove leftover buckets
177 while( table.size() > i ) {
178 bucket * b = *(table.end() - 1);
184 sort(table.begin(), table.end(), bucket::lessThan);
188 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
190 double altitude_ft = altitude_n->getDoubleValue();
191 double altitude_agl_ft = altitude_agl_n->getDoubleValue();
192 double boundary_transition =
193 boundary_transition_n == NULL ? 500 : boundary_transition_n->getDoubleValue();
195 int length = _boundary_table.size();
199 double boundary_limit = _boundary_table[length-1]->altitude_ft;
200 if (boundary_limit >= altitude_agl_ft) {
201 do_interpolate(_boundary_table, altitude_agl_ft, _environment);
203 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
204 //TODO: this is 500ft above the top altitude of boundary layer
205 //shouldn't this be +/-250 ft off of the top altitude?
207 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
208 do_interpolate(_aloft_table, altitude_ft, &env2);
210 (altitude_agl_ft - boundary_limit) / boundary_transition;
211 interpolate(&env1, &env2, fraction, _environment);
216 do_interpolate(_aloft_table, altitude_ft, _environment);
220 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table, double altitude_ft, FGEnvironment * environment)
222 int length = table.size();
226 // Boundary conditions
227 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
228 environment->copy(table[0]->environment);
230 } else if (table[length-1]->altitude_ft <= altitude_ft) {
231 environment->copy(table[length-1]->environment);
234 // Search the interpolation table
235 for (int i = 0; i < length - 1; i++) {
236 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
237 FGEnvironment * env1 = &(table[i]->environment);
238 FGEnvironment * env2 = &(table[i+1]->environment);
240 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
244 ((altitude_ft - table[i]->altitude_ft) /
245 (table[i+1]->altitude_ft - table[i]->altitude_ft));
246 interpolate(env1, env2, fraction, environment);
254 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
256 return (altitude_ft < b.altitude_ft);
260 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
262 return (a->altitude_ft) < (b->altitude_ft);
266 ////////////////////////////////////////////////////////////////////////
267 // Implementation of FGMetarCtrl.
268 ////////////////////////////////////////////////////////////////////////
270 FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
273 setup_winds_aloft(true),
274 wind_interpolation_required(true),
275 // Interpolation constant definitions.
276 EnvironmentUpdatePeriodSec( 0.2 ),
277 MaxWindChangeKtsSec( 0.2 ),
278 MaxVisChangePercentSec( 0.05 ),
279 MaxPressureChangeInHgSec( 0.0033 ),
280 MaxCloudAltitudeChangeFtSec( 20.0 ),
281 MaxCloudThicknessChangeFtSec( 50.0 ),
282 MaxCloudInterpolationHeightFt( 5000.0 ),
283 MaxCloudInterpolationDeltaFt( 4000.0 ),
284 _environmentCtrl(environmentCtrl)
286 windModulator = new FGBasicWindModulator();
288 metar_base_n = fgGetNode( "/environment/metar", true );
289 station_id_n = metar_base_n->getNode("station-id", true );
290 station_elevation_n = metar_base_n->getNode("station-elevation-ft", true );
291 min_visibility_n = metar_base_n->getNode("min-visibility-m", true );
292 max_visibility_n = metar_base_n->getNode("max-visibility-m", true );
293 base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true );
294 base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true );
295 base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true );
296 base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
297 gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true );
298 temperature_n = metar_base_n->getNode("temperature-degc", true );
299 dewpoint_n = metar_base_n->getNode("dewpoint-degc", true );
300 humidity_n = metar_base_n->getNode("rel-humidity-norm", true );
301 pressure_n = metar_base_n->getNode("pressure-inhg", true );
302 clouds_n = metar_base_n->getNode("clouds", true );
303 rain_n = metar_base_n->getNode("rain-norm", true );
304 hail_n = metar_base_n->getNode("hail-norm", true );
305 snow_n = metar_base_n->getNode("snow-norm", true );
306 snow_cover_n = metar_base_n->getNode("snow-cover", true );
307 magnetic_variation_n = fgGetNode( "/environment/magnetic-variation-deg", true );
308 ground_elevation_n = fgGetNode( "/position/ground-elev-m", true );
309 longitude_n = fgGetNode( "/position/longitude-deg", true );
310 latitude_n = fgGetNode( "/position/latitude-deg", true );
311 environment_clouds_n = fgGetNode("/environment/clouds");
313 boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt");
314 boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
315 boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
316 boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
319 FGMetarCtrl::~FGMetarCtrl ()
323 void FGMetarCtrl::bind ()
325 fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid );
326 fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled );
327 fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft );
330 void FGMetarCtrl::unbind ()
332 fgUntie("/environment/metar/valid");
333 fgUntie("/environment/params/metar-updates-environment");
334 fgUntie("/environment/params/metar-updates-winds-aloft");
337 // use a "command" to set station temp at station elevation
338 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
340 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
341 node->setFloatValue( temp_degc );
342 node = args.getNode("altitude-ft", 0, true);
343 node->setFloatValue( altitude_ft );
344 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
347 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
349 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
350 node->setFloatValue( dewpoint_degc );
351 node = args.getNode("altitude-ft", 0, true);
352 node->setFloatValue( altitude_ft );
353 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
357 Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
360 wind-from-heading-deg
362 turbulence/magnitude-norm
365 wind-heading-change-deg how many degrees does the wind direction change at this level
366 wind-speed-change-rel relative change of wind speed at this level
367 turbulence/factor factor for the calculated turbulence magnitude at this level
369 static void setupWindBranch( string branchName, double dir, double speed, double gust )
371 SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true);
372 vector<SGPropertyNode_ptr> entries = branch->getChildren("entry");
373 for ( vector<SGPropertyNode_ptr>::iterator it = entries.begin(); it != entries.end(); it++) {
375 // change wind direction as configured
376 double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 );
377 if( layer_dir >= 360.0 ) layer_dir -= 360.0;
378 if( layer_dir < 0.0 ) layer_dir += 360.0;
379 (*it)->setDoubleValue("wind-from-heading-deg", layer_dir);
381 double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 ));
382 (*it)->setDoubleValue("wind-speed-kt", layer_speed );
384 // add some turbulence
385 SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true);
387 double turbulence_norm = speed/50;
389 turbulence_norm += (gust-speed)/25;
391 if( turbulence_norm > 1.0 ) turbulence_norm = 1.0;
393 turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 );
394 turbulence->setDoubleValue( "magnitude-norm", turbulence_norm );
398 static void setupWind( bool setup_aloft, double dir, double speed, double gust )
400 setupWindBranch( "boundary", dir, speed, gust );
402 setupWindBranch( "aloft", dir, speed, gust );
405 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
407 double dval = EnvironmentUpdatePeriodSec * dt;
409 if (fabs(currentval - requiredval) < dval) return requiredval;
410 if (currentval < requiredval) return (currentval + dval);
411 if (currentval > requiredval) return (currentval - dval);
419 wind_interpolation_required = true;
423 FGMetarCtrl::reinit ()
428 static inline double convert_to_360( double d )
430 if( d < 0.0 ) return d + 360.0;
431 if( d >= 360.0 ) return d - 360.0;
435 static inline double convert_to_180( double d )
437 return d > 180.0 ? d - 360.0 : d;
441 FGMetarCtrl::update(double dt)
443 if( dt <= 0 || !metar_valid ||!enabled)
446 windModulator->update(dt);
447 // Interpolate the current configuration closer to the actual METAR
449 bool reinit_required = false;
450 bool layer_rebuild_required = false;
453 double dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
454 double speed = base_wind_speed_n->getDoubleValue();
455 double gust = gust_wind_speed_n->getDoubleValue();
456 setupWind(setup_winds_aloft, dir, speed, gust);
458 double metarvis = min_visibility_n->getDoubleValue();
459 fgDefaultWeatherValue("visibility-m", metarvis);
461 double metarpressure = pressure_n->getDoubleValue();
462 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
464 // We haven't already loaded a METAR, so apply it immediately.
465 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
466 vector<SGPropertyNode_ptr>::const_iterator layer;
467 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
470 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
471 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
473 target->setStringValue("coverage",
474 (*layer)->getStringValue("coverage", "clear"));
475 target->setDoubleValue("elevation-ft",
476 (*layer)->getDoubleValue("elevation-ft"));
477 target->setDoubleValue("thickness-ft",
478 (*layer)->getDoubleValue("thickness-ft"));
479 target->setDoubleValue("span-m", 40000.0);
482 first_update = false;
483 reinit_required = true;
484 layer_rebuild_required = true;
487 if( wind_interpolation_required ) {
488 // Generate interpolated values between the METAR and the current
491 // Pick up the METAR wind values and convert them into a vector.
493 double metar_speed = base_wind_speed_n->getDoubleValue();
494 double metar_heading = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
496 metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
497 metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
499 // Convert the current wind values and convert them into a vector
501 double speed = boundary_wind_speed_n->getDoubleValue();
502 double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
504 current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
505 current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
507 // Determine the maximum component-wise value that the wind can change.
508 // First we determine the fraction in the X and Y component, then
509 // factor by the maximum wind change.
510 double x = fabs(current[0] - metar[0]);
511 double y = fabs(current[1] - metar[1]);
513 // only interpolate if we have a difference
515 double dx = x / (x + y);
518 double maxdx = dx * MaxWindChangeKtsSec;
519 double maxdy = dy * MaxWindChangeKtsSec;
521 // Interpolate each component separately.
522 current[0] = interpolate_val(current[0], metar[0], maxdx);
523 current[1] = interpolate_val(current[1], metar[1], maxdy);
525 // Now convert back to polar coordinates.
526 if ((fabs(current[0]) > 0.1) || (fabs(current[1]) > 0.1)) {
527 // Some real wind to convert back from. Work out the speed
528 // and direction value in degrees.
529 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
530 dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
532 // Normalize the direction.
536 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
538 // Special case where there is no wind (otherwise atan2 barfs)
541 double gust = gust_wind_speed_n->getDoubleValue();
542 setupWind(setup_winds_aloft, dir_from, speed, gust);
543 reinit_required = true;
545 wind_interpolation_required = false;
547 } else { // if(wind_interpolation_required)
548 // interpolation of wind vector is finished, apply wind
549 // variations and gusts for the boundary layer only
552 bool wind_modulated = false;
554 // start with the main wind direction
555 double wind_dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
556 double min = convert_to_180(base_wind_range_from_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
557 double max = convert_to_180(base_wind_range_to_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
559 // if variable winds configured, modulate the wind direction
560 double f = windModulator->get_direction_offset_norm();
561 wind_dir = min+(max-min)*f;
562 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
563 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
564 wind_modulated = true;
567 // start with main wind speed
568 double wind_speed = base_wind_speed_n->getDoubleValue();
569 max = gust_wind_speed_n->getDoubleValue();
570 if( max > wind_speed ) {
571 // if gusts are configured, modulate wind magnitude
572 double f = windModulator->get_magnitude_factor_norm();
573 wind_speed = wind_speed+(max-wind_speed)*f;
574 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
575 wind_modulated = true;
577 if( wind_modulated ) {
578 setupWind(false, wind_dir, wind_speed, max);
579 reinit_required = true;
583 // Now handle the visibility. We convert both visibility values
584 // to X-values, then interpolate from there, then back to real values.
585 // The length_scale is fixed to 1000m, so the visibility changes by
586 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
587 // whichever is more.
588 double vis = boundary_visibility_n->getDoubleValue();;
589 double metarvis = min_visibility_n->getDoubleValue();
590 if( vis != metarvis ) {
591 double currentxval = log(1000.0 + vis);
592 double metarxval = log(1000.0 + metarvis);
594 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
596 // Now convert back from an X-value to a straightforward visibility.
597 vis = exp(currentxval) - 1000.0;
598 fgDefaultWeatherValue("visibility-m", vis);
599 reinit_required = true;
602 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
603 double metarpressure = pressure_n->getDoubleValue();
604 if( pressure != metarpressure ) {
605 pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
606 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
607 reinit_required = true;
610 // Set the cloud layers by interpolating over the METAR versions.
611 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
612 vector<SGPropertyNode_ptr>::const_iterator layer;
613 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
615 double aircraft_alt = fgGetDouble("/position/altitude-ft");
618 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
619 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
621 // In the case of clouds, we want to avoid writing if nothing has
622 // changed, as these properties are tied to the renderer and will
623 // cause the clouds to be updated, reseting the texture locations.
625 // We don't interpolate the coverage values as no-matter how we
626 // do it, it will be quite a sudden change of texture. Better to
627 // have a single change than four or five.
628 const char *coverage = (*layer)->getStringValue("coverage", "clear");
629 SGPropertyNode *cov = target->getNode("coverage", true);
630 if (strcmp(cov->getStringValue(), coverage) != 0) {
631 cov->setStringValue(coverage);
632 layer_rebuild_required = true;
635 double required_alt = (*layer)->getDoubleValue("elevation-ft");
636 double current_alt = target->getDoubleValue("elevation-ft");
637 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
638 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
640 if (current_alt < -9000 || required_alt < -9000 ||
641 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
642 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
643 // We don't interpolate any layers that are
644 // - too far above us to be visible
645 // - too far below us to be visible
646 // - with too large a difference to make interpolation sensible
647 // - to or from -9999 (used as a placeholder)
648 // - any values that are too high above us,
649 if (current_alt != required_alt)
650 target->setDoubleValue("elevation-ft", required_alt);
652 if (thickness->getDoubleValue() != required_thickness)
653 thickness->setDoubleValue(required_thickness);
656 // Interpolate the other values in the usual way
657 if (current_alt != required_alt) {
658 current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
659 target->setDoubleValue("elevation-ft", current_alt);
662 double current_thickness = thickness->getDoubleValue();
664 if (current_thickness != required_thickness) {
665 current_thickness = interpolate_val(current_thickness,
667 MaxCloudThicknessChangeFtSec);
668 thickness->setDoubleValue(current_thickness);
674 double station_elevation_ft = station_elevation_n->getDoubleValue();
675 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
676 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
678 //TODO: check if temperature/dewpoint have changed. This requires reinit.
680 // Force an update of the 3D clouds
681 if( layer_rebuild_required )
682 fgSetInt("/environment/rebuild-layers", 1 );
684 // Reinitializing of the environment controller required
685 if( reinit_required )
686 _environmentCtrl->reinit();
689 const char * FGMetarCtrl::get_metar(void) const
691 return metar.c_str();
694 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
695 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
697 void FGMetarCtrl::set_metar( const char * metar_string )
701 metar = metar_string;
703 SGSharedPtr<FGMetar> m;
705 m = new FGMetar( metar_string );
707 catch( sg_io_exception ) {
708 fprintf( stderr, "can't get metar: %s\n", metar_string );
713 wind_interpolation_required = true;
715 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
716 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
718 const SGMetarVisibility *dirvis = m->getDirVisibility();
719 for (i = 0; i < 8; i++, dirvis++) {
720 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
721 double v = dirvis->getVisibility_m();
723 vis->setDoubleValue("min-m", v);
724 vis->setDoubleValue("max-m", v);
727 base_wind_dir_n->setIntValue( m->getWindDir() );
728 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
729 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
730 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
731 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
732 temperature_n->setDoubleValue( m->getTemperature_C() );
733 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
734 humidity_n->setDoubleValue( m->getRelHumidity() );
735 pressure_n->setDoubleValue( m->getPressure_inHg() );
738 // get station elevation to compute cloud base
739 double station_elevation_ft = 0;
741 // 1. check the id given in the metar
742 FGAirport* a = FGAirport::findByIdent(m->getId());
744 // 2. if unknown, find closest airport with metar to current position
746 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
747 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
750 // 3. otherwise use ground elevation
752 station_elevation_ft = a->getElevation();
753 station_id_n->setStringValue( a->ident());
755 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
756 station_id_n->setStringValue( m->getId());
760 station_elevation_n->setDoubleValue( station_elevation_ft );
762 vector<SGMetarCloud> cv = m->getClouds();
763 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
765 int layer_cnt = environment_clouds_n->getChildren("layer").size();
766 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
769 const char *coverage = "clear";
770 double elevation = -9999.0;
771 double thickness = 0.0;
772 const double span = 40000.0;
774 if (cloud != cloud_end) {
775 int c = cloud->getCoverage();
776 coverage = coverage_string[c];
777 elevation = cloud->getAltitude_ft() + station_elevation_ft;
778 thickness = thickness_value[c];
782 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
784 // if the coverage has changed, a rebuild of the layer is needed
785 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
786 layer->setStringValue("coverage", coverage);
788 layer->setDoubleValue("elevation-ft", elevation);
789 layer->setDoubleValue("thickness-ft", thickness);
790 layer->setDoubleValue("span-m", span);
793 rain_n->setDoubleValue(m->getRain());
794 hail_n->setDoubleValue(m->getHail());
795 snow_n->setDoubleValue(m->getSnow());
796 snow_cover_n->setBoolValue(m->getSnowCover());
800 #if defined(ENABLE_THREADS)
802 * This class represents the thread of execution responsible for
803 * fetching the metar data.
805 class MetarThread : public OpenThreads::Thread {
807 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
811 * Fetche the metar data from the NOAA.
816 FGMetarFetcher * metar_fetcher;
819 void MetarThread::run()
822 string airport_id = metar_fetcher->request_queue.pop();
824 if( airport_id.size() == 0 )
827 if( metar_fetcher->_error_count > 3 ) {
828 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
832 metar_fetcher->fetch( airport_id );
837 FGMetarFetcher::FGMetarFetcher() :
838 #if defined(ENABLE_THREADS)
848 longitude_n = fgGetNode( "/position/longitude-deg", true );
849 latitude_n = fgGetNode( "/position/latitude-deg", true );
850 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
852 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
853 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
854 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
855 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
857 output_n = fgGetNode("/environment/metar/data", true );
858 #if defined(ENABLE_THREADS)
859 metar_thread = new MetarThread(this);
860 // FIXME: do we really need setProcessorAffinity()?
861 // metar_thread->setProcessorAffinity(1);
862 metar_thread->start();
863 #endif // ENABLE_THREADS
867 FGMetarFetcher::~FGMetarFetcher()
869 #if defined(ENABLE_THREADS)
870 request_queue.push("");
871 metar_thread->join();
873 #endif // ENABLE_THREADS
876 void FGMetarFetcher::init ()
883 current_airport_id.clear();
885 hack to stop startup.nas complaining if metar arrives after nasal-dir-initialized
886 is fired. Immediately fetch and wait for the METAR before continuing. This gets the
887 /environment/metar/xxx properties filled before nasal-dir is initialized.
888 Maybe the runway selection should happen here to make startup.nas obsolete?
890 const char * startup_airport = fgGetString("/sim/startup/options/airport");
891 if( *startup_airport ) {
892 FGAirport * a = FGAirport::getByIdent( startup_airport );
894 SGGeod pos = SGGeod::fromDeg(a->getLongitude(), a->getLatitude());
895 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
896 current_airport_id = a->getId();
897 fetch( current_airport_id );
902 void FGMetarFetcher::reinit ()
907 /* search for closest airport with metar every xx seconds */
908 static const int search_interval_sec = 60;
910 /* fetch metar for airport, even if airport has not changed every xx seconds */
911 static const int fetch_interval_sec = 900;
913 /* reset error counter after xxx seconds */
914 static const int error_timer_sec = 3;
916 void FGMetarFetcher::update (double delta_time_sec)
918 fetch_timer -= delta_time_sec;
919 search_timer -= delta_time_sec;
920 error_timer -= delta_time_sec;
922 if( error_timer <= 0.0 ) {
923 error_timer = error_timer_sec;
927 if( enable_n->getBoolValue() == false ) {
932 // we were just enabled, reset all timers to
933 // trigger immediate metar fetch
937 error_timer = error_timer_sec;
941 FGAirport * a = NULL;
943 if( search_timer <= 0.0 ) {
944 // search timer expired, search closest airport with metar
945 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
946 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
947 search_timer = search_interval_sec;
954 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
955 // fetch timer expired or airport has changed, schedule a fetch
956 current_airport_id = a->ident();
957 fetch_timer = fetch_interval_sec;
958 #if defined(ENABLE_THREADS)
959 // push this airport id into the queue for the worker thread
960 request_queue.push( current_airport_id );
962 // if there is no worker thread, immediately fetch the data
963 fetch( current_airport_id );
968 void FGMetarFetcher::fetch( const string & id )
970 if( enable_n->getBoolValue() == false )
973 SGSharedPtr<FGMetar> result = NULL;
975 // fetch current metar data
977 string host = proxy_host_n->getStringValue();
978 string auth = proxy_auth_n->getStringValue();
979 string port = proxy_port_n->getStringValue();
981 result = new FGMetar( id, host, port, auth);
983 long max_age = max_age_n->getLongValue();
984 long age = result->getAge_min();
986 if (max_age && age > max_age) {
987 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
988 if (++_stale_count > 10) {
990 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
996 } catch (const sg_io_exception& e) {
997 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
999 // remove METAR flag from the airport
1000 FGAirport * a = FGAirport::findByIdent( id );
1001 if( a ) a->setMetar( false );
1002 // immediately schedule a new search
1006 // write the metar to the property node, the rest is done by the methods tied to this property
1007 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
1008 if( result != NULL && enable_n->getBoolValue() == true )
1009 output_n->setStringValue( result->getData() );
1012 // end of environment_ctrl.cxx