From 5c6fe952598053fa63631fc0161d666f22a50f51 Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Sat, 11 Sep 2010 16:11:35 +0200 Subject: [PATCH] Environment controller overhaul This is the initial commit for a reworked environment controller. The main intention is to decouple the individual modules like metar fetch, metar properties, environment interpolation etc. to make it easier for other weather module developers to modify environment settings. As a side effect, the dialogs for weather-scenario, weather-conditions, clouds and precipitations have been merged into a single dialog --- src/Environment/Makefile.am | 7 +- src/Environment/environment.cxx | 241 +++-- src/Environment/environment.hxx | 34 +- src/Environment/environment_ctrl.cxx | 1290 ++++++------------------ src/Environment/environment_ctrl.hxx | 238 +---- src/Environment/environment_mgr.cxx | 321 ++---- src/Environment/environment_mgr.hxx | 13 +- src/Environment/metarairportfilter.cxx | 34 + src/Environment/metarairportfilter.hxx | 52 + src/Environment/metarproperties.cxx | 212 ++++ src/Environment/metarproperties.hxx | 77 ++ src/Environment/realwx_ctrl.cxx | 301 ++++++ src/Environment/realwx_ctrl.hxx | 36 + src/Main/options.cxx | 4 +- 14 files changed, 1262 insertions(+), 1598 deletions(-) create mode 100644 src/Environment/metarairportfilter.cxx create mode 100644 src/Environment/metarairportfilter.hxx create mode 100644 src/Environment/metarproperties.cxx create mode 100644 src/Environment/metarproperties.hxx create mode 100644 src/Environment/realwx_ctrl.cxx create mode 100644 src/Environment/realwx_ctrl.hxx diff --git a/src/Environment/Makefile.am b/src/Environment/Makefile.am index 6c7ee1f51..5e9bcc463 100644 --- a/src/Environment/Makefile.am +++ b/src/Environment/Makefile.am @@ -9,10 +9,13 @@ libEnvironment_a_SOURCES = \ environment_mgr.cxx environment_mgr.hxx \ environment_ctrl.cxx environment_ctrl.hxx \ fgmetar.cxx fgmetar.hxx fgclouds.cxx fgclouds.hxx \ - fgwind.cxx fgwind.hxx \ + realwx_ctrl.cxx realwx_ctrl.hxx \ + metarproperties.cxx metarproperties.hxx \ + metarairportfilter.cxx metarairportfilter.hxx \ atmosphere.cxx atmosphere.hxx \ precipitation_mgr.cxx precipitation_mgr.hxx \ ridge_lift.cxx ridge_lift.hxx \ - ephemeris.cxx ephemeris.hxx + ephemeris.cxx ephemeris.hxx \ + terrainsampler.cxx terrainsampler.cxx INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src diff --git a/src/Environment/environment.cxx b/src/Environment/environment.cxx index 30354ffd9..ba10d8652 100644 --- a/src/Environment/environment.cxx +++ b/src/Environment/environment.cxx @@ -18,8 +18,6 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // -// $Id$ - #ifdef HAVE_CONFIG_H # include @@ -138,9 +136,6 @@ void FGEnvironment::_init() wind_from_north_fps = 0; wind_from_east_fps = 0; wind_from_down_fps = 0; - thermal_lift_fps = 0; - ridge_lift_fps= 0; - local_weather_lift_fps=0; altitude_half_to_sun_m = 1000; altitude_tropo_top_m = 10000; #ifdef USING_TABLES @@ -164,6 +159,13 @@ FGEnvironment::FGEnvironment (const FGEnvironment &env) FGEnvironment::~FGEnvironment() { + Untie(); +} + +FGEnvironment & FGEnvironment::operator = ( const FGEnvironment & other ) +{ + copy( other ); + return *this; } void @@ -181,9 +183,6 @@ FGEnvironment::copy (const FGEnvironment &env) wind_from_north_fps = env.wind_from_north_fps; wind_from_east_fps = env.wind_from_east_fps; wind_from_down_fps = env.wind_from_down_fps; - thermal_lift_fps = env.thermal_lift_fps; - ridge_lift_fps= env.ridge_lift_fps; - local_weather_lift_fps = env.local_weather_lift_fps; turbulence_magnitude_norm = env.turbulence_magnitude_norm; turbulence_rate_hz = env.turbulence_rate_hz; } @@ -260,6 +259,107 @@ FGEnvironment::read (const SGPropertyNode * node) set_live_update(live_update); } +void FGEnvironment::Tie( SGPropertyNode_ptr base, bool archivable ) +{ + _tiedProperties.setRoot( base ); + + _tiedProperties.Tie( "visibility-m", this, + &FGEnvironment::get_visibility_m, + &FGEnvironment::set_visibility_m) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("temperature-sea-level-degc", this, + &FGEnvironment::get_temperature_sea_level_degc, + &FGEnvironment::set_temperature_sea_level_degc) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("temperature-degc", this, + &FGEnvironment::get_temperature_degc, + &FGEnvironment::set_temperature_degc) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("temperature-degf", this, + &FGEnvironment::get_temperature_degf); + + _tiedProperties.Tie("dewpoint-sea-level-degc", this, + &FGEnvironment::get_dewpoint_sea_level_degc, + &FGEnvironment::set_dewpoint_sea_level_degc) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("dewpoint-degc", this, + &FGEnvironment::get_dewpoint_degc, + &FGEnvironment::set_dewpoint_degc) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("pressure-sea-level-inhg", this, + &FGEnvironment::get_pressure_sea_level_inhg, + &FGEnvironment::set_pressure_sea_level_inhg) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("pressure-inhg", this, + &FGEnvironment::get_pressure_inhg, + &FGEnvironment::set_pressure_inhg) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("density-slugft3", this, + &FGEnvironment::get_density_slugft3); // read-only + + _tiedProperties.Tie("relative-humidity", this, + &FGEnvironment::get_relative_humidity); //ro + + _tiedProperties.Tie("atmosphere/density-tropo-avg", this, + &FGEnvironment::get_density_tropo_avg_kgm3); //ro + + _tiedProperties.Tie("atmosphere/altitude-half-to-sun", this, + &FGEnvironment::get_altitude_half_to_sun_m, + &FGEnvironment::set_altitude_half_to_sun_m) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("atmosphere/altitude-troposphere-top", this, + &FGEnvironment::get_altitude_tropo_top_m, + &FGEnvironment::set_altitude_tropo_top_m) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("wind-from-heading-deg", this, + &FGEnvironment::get_wind_from_heading_deg, + &FGEnvironment::set_wind_from_heading_deg) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("wind-speed-kt", this, + &FGEnvironment::get_wind_speed_kt, + &FGEnvironment::set_wind_speed_kt) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("wind-from-north-fps", this, + &FGEnvironment::get_wind_from_north_fps, + &FGEnvironment::set_wind_from_north_fps) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("wind-from-east-fps", this, + &FGEnvironment::get_wind_from_east_fps, + &FGEnvironment::set_wind_from_east_fps) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("wind-from-down-fps", this, + &FGEnvironment::get_wind_from_down_fps, + &FGEnvironment::set_wind_from_down_fps) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("turbulence/magnitude-norm", this, + &FGEnvironment::get_turbulence_magnitude_norm, + &FGEnvironment::set_turbulence_magnitude_norm) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); + + _tiedProperties.Tie("turbulence/rate-hz", this, + &FGEnvironment::get_turbulence_rate_hz, + &FGEnvironment::set_turbulence_rate_hz) + ->setAttribute( SGPropertyNode::ARCHIVE, archivable ); +} + +void FGEnvironment::Untie() +{ + _tiedProperties.Untie(); +} double FGEnvironment::get_visibility_m () const @@ -369,24 +469,6 @@ FGEnvironment::get_wind_from_down_fps () const return wind_from_down_fps; } -double -FGEnvironment::get_thermal_lift_fps () const -{ - return thermal_lift_fps; -} - -double -FGEnvironment::get_ridge_lift_fps () const -{ - return ridge_lift_fps; -} - -double -FGEnvironment::get_local_weather_lift_fps () const -{ - return local_weather_lift_fps; -} - double FGEnvironment::get_turbulence_magnitude_norm () const { @@ -527,33 +609,6 @@ FGEnvironment::set_wind_from_down_fps (double d) } } -void -FGEnvironment::set_thermal_lift_fps (double th) -{ - thermal_lift_fps = th; - if( live_update ) { - _recalc_updraft(); - } -} - -void -FGEnvironment::set_ridge_lift_fps (double ri) -{ - ridge_lift_fps = ri; - if( live_update ) { - _recalc_updraft(); - } -} - -void -FGEnvironment::set_local_weather_lift_fps (double lwl) -{ - local_weather_lift_fps = lwl; - if( live_update ) { - _recalc_updraft(); - } -} - void FGEnvironment::set_turbulence_magnitude_norm (double t) { @@ -600,34 +655,15 @@ FGEnvironment::set_altitude_tropo_top_m (double alt) void FGEnvironment::_recalc_hdgspd () { - double angle_rad; + wind_from_heading_deg = + atan2(wind_from_east_fps, wind_from_north_fps) * SGD_RADIANS_TO_DEGREES; - if (wind_from_east_fps == 0) { - angle_rad = (wind_from_north_fps >= 0 ? SGD_PI_2 : -SGD_PI_2); - } else { - angle_rad = atan(wind_from_north_fps/wind_from_east_fps); - } - wind_from_heading_deg = angle_rad * SGD_RADIANS_TO_DEGREES; - if (wind_from_east_fps >= 0) - wind_from_heading_deg = 90 - wind_from_heading_deg; - else - wind_from_heading_deg = 270 - wind_from_heading_deg; + if( wind_from_heading_deg < 0 ) + wind_from_heading_deg += 360.0; -#if 0 - // FIXME: Windspeed can become negative with these formulas. - // which can cause problems for animations that rely - // on the windspeed property. - if (angle_rad == 0) - wind_speed_kt = fabs(wind_from_east_fps - * SG_METER_TO_NM * SG_FEET_TO_METER * 3600); - else - wind_speed_kt = (wind_from_north_fps / sin(angle_rad)) - * SG_METER_TO_NM * SG_FEET_TO_METER * 3600; -#else wind_speed_kt = sqrt(wind_from_north_fps * wind_from_north_fps + wind_from_east_fps * wind_from_east_fps) * SG_METER_TO_NM * SG_FEET_TO_METER * 3600; -#endif } void @@ -642,12 +678,6 @@ FGEnvironment::_recalc_ne () sin(wind_from_heading_deg * SGD_DEGREES_TO_RADIANS); } -void -FGEnvironment::_recalc_updraft () -{ - wind_from_down_fps = thermal_lift_fps + ridge_lift_fps + local_weather_lift_fps ; -} - // Intended to help with the interpretation of METAR data, // not for random in-flight outside-air temperatures. void @@ -736,7 +766,8 @@ FGEnvironment::_recalc_alt_pt () } } #endif - double press, temp; + double press = pressure_inhg * inHg; + double temp = temperature_degc + freezing; boost::tie(press, temp) = PT_vs_hpt(elevation_ft * foot, pressure_sea_level_inhg * inHg, temperature_sea_level_degc + freezing); temperature_degc = temp - freezing; @@ -830,57 +861,57 @@ do_interp_deg (double a, double b, double fraction) return fmod(do_interp(a, b, fraction), 360); } -void -interpolate (const FGEnvironment * env1, const FGEnvironment * env2, - double fraction, FGEnvironment * result) +FGEnvironment & +FGEnvironment::interpolate( const FGEnvironment & env2, + double fraction, FGEnvironment * result) const { // don't calculate each internal property every time we set a single value // we trigger that at the end of the interpolation process bool live_update = result->set_live_update( false ); result->set_visibility_m - (do_interp(env1->get_visibility_m(), - env2->get_visibility_m(), + (do_interp(get_visibility_m(), + env2.get_visibility_m(), fraction)); result->set_temperature_sea_level_degc - (do_interp(env1->get_temperature_sea_level_degc(), - env2->get_temperature_sea_level_degc(), + (do_interp(get_temperature_sea_level_degc(), + env2.get_temperature_sea_level_degc(), fraction)); result->set_dewpoint_sea_level_degc - (do_interp(env1->get_dewpoint_sea_level_degc(), - env2->get_dewpoint_sea_level_degc(), + (do_interp(get_dewpoint_sea_level_degc(), + env2.get_dewpoint_sea_level_degc(), fraction)); result->set_pressure_sea_level_inhg - (do_interp(env1->get_pressure_sea_level_inhg(), - env2->get_pressure_sea_level_inhg(), + (do_interp(get_pressure_sea_level_inhg(), + env2.get_pressure_sea_level_inhg(), fraction)); result->set_wind_from_heading_deg - (do_interp_deg(env1->get_wind_from_heading_deg(), - env2->get_wind_from_heading_deg(), + (do_interp_deg(get_wind_from_heading_deg(), + env2.get_wind_from_heading_deg(), fraction)); result->set_wind_speed_kt - (do_interp(env1->get_wind_speed_kt(), - env2->get_wind_speed_kt(), + (do_interp(get_wind_speed_kt(), + env2.get_wind_speed_kt(), fraction)); result->set_elevation_ft - (do_interp(env1->get_elevation_ft(), - env2->get_elevation_ft(), + (do_interp(get_elevation_ft(), + env2.get_elevation_ft(), fraction)); result->set_turbulence_magnitude_norm - (do_interp(env1->get_turbulence_magnitude_norm(), - env2->get_turbulence_magnitude_norm(), + (do_interp(get_turbulence_magnitude_norm(), + env2.get_turbulence_magnitude_norm(), fraction)); result->set_turbulence_rate_hz - (do_interp(env1->get_turbulence_rate_hz(), - env2->get_turbulence_rate_hz(), + (do_interp(get_turbulence_rate_hz(), + env2.get_turbulence_rate_hz(), fraction)); // calculate derived properties here to avoid duplicate expensive computations @@ -891,6 +922,8 @@ interpolate (const FGEnvironment * env1, const FGEnvironment * env2, result->_recalc_relative_humidity(); result->set_live_update(live_update); + + return *result; } // end of environment.cxx diff --git a/src/Environment/environment.hxx b/src/Environment/environment.hxx index c27013226..048b17992 100644 --- a/src/Environment/environment.hxx +++ b/src/Environment/environment.hxx @@ -18,8 +18,6 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // -// $Id$ - #ifndef _ENVIRONMENT_HXX #define _ENVIRONMENT_HXX @@ -27,6 +25,7 @@ #include #include +#include "tiedpropertylist.hxx" /** * Model the natural environment. @@ -45,9 +44,11 @@ public: FGEnvironment (const FGEnvironment &environment); virtual ~FGEnvironment(); - virtual void copy (const FGEnvironment &environment); + FGEnvironment & operator = ( const FGEnvironment & other ); virtual void read (const SGPropertyNode * node); + virtual void Tie( SGPropertyNode_ptr base, bool setArchivable = true ); + virtual void Untie(); virtual double get_visibility_m () const; @@ -70,9 +71,6 @@ public: virtual double get_wind_from_north_fps () const; virtual double get_wind_from_east_fps () const; virtual double get_wind_from_down_fps () const; - virtual double get_thermal_lift_fps () const; - virtual double get_ridge_lift_fps () const; - virtual double get_local_weather_lift_fps () const; virtual double get_turbulence_magnitude_norm () const; virtual double get_turbulence_rate_hz () const; @@ -91,9 +89,6 @@ public: virtual void set_wind_from_north_fps (double n); virtual void set_wind_from_east_fps (double e); virtual void set_wind_from_down_fps (double d); - virtual void set_thermal_lift_fps (double th); - virtual void set_ridge_lift_fps (double ri); - virtual void set_local_weather_lift_fps (double lwl); virtual void set_turbulence_magnitude_norm (double t); virtual void set_turbulence_rate_hz (double t); @@ -105,21 +100,23 @@ public: virtual bool set_live_update(bool live_update); - void _recalc_ne (); - void _recalc_alt_dewpoint (); - void _recalc_density (); - void _recalc_relative_humidity (); - void _recalc_alt_pt (); + + FGEnvironment & interpolate (const FGEnvironment & env2, double fraction, FGEnvironment * result) const; private: + virtual void copy (const FGEnvironment &environment); void _init(); void _recalc_hdgspd (); - void _recalc_updraft (); void _recalc_sl_temperature (); void _recalc_sl_dewpoint (); void _recalc_sl_pressure (); void _recalc_density_tropo_avg_kgm3 (); + void _recalc_ne (); + void _recalc_alt_dewpoint (); + void _recalc_density (); + void _recalc_relative_humidity (); + void _recalc_alt_pt (); double elevation_ft; double visibility_m; @@ -147,15 +144,10 @@ private: double wind_from_north_fps; double wind_from_east_fps; double wind_from_down_fps; - double thermal_lift_fps; - double ridge_lift_fps; - double local_weather_lift_fps; bool live_update; + TiedPropertyList _tiedProperties; }; -void interpolate (const FGEnvironment * env1, const FGEnvironment * env2, - double fraction, FGEnvironment * result); - #endif // _ENVIRONMENT_HXX diff --git a/src/Environment/environment_ctrl.cxx b/src/Environment/environment_ctrl.cxx index 0cc434e69..48b71dfee 100644 --- a/src/Environment/environment_ctrl.cxx +++ b/src/Environment/environment_ctrl.cxx @@ -1,6 +1,7 @@ // environment_ctrl.cxx -- manager for natural environment information. // // Written by David Megginson, started February 2002. +// Partly rewritten by Torsten Dreyer, August 2010. // // Copyright (C) 2002 David Megginson - david@megginson.com // @@ -18,1058 +19,331 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // -// $Id$ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include -#include -#include -#include -#include - -#include #include
-#include
- -#include "atmosphere.hxx" -#include "fgmetar.hxx" #include "environment_ctrl.hxx" +#include "environment.hxx" -using std::sort; +namespace Environment { -class AirportWithMetar : public FGAirport::AirportFilter { -public: - virtual bool passAirport(FGAirport* aApt) const { - return aApt->getMetar(); - } - - // permit heliports and seaports too - virtual FGPositioned::Type maxType() const - { return FGPositioned::SEAPORT; } +/** + * @brief Describes an element of a LayerTable. A defined environment at a given altitude. +*/ +struct LayerTableBucket { + double altitude_ft; + FGEnvironment environment; + inline bool operator< (const LayerTableBucket &b) const { + return (altitude_ft < b.altitude_ft); + } + /** + * @brief LessThan predicate for bucket pointers. + */ + static bool lessThan(LayerTableBucket *a, LayerTableBucket *b) { + return (a->altitude_ft) < (b->altitude_ft); + } }; -static AirportWithMetar airportWithMetarFilter; - -//////////////////////////////////////////////////////////////////////// -// Implementation of FGEnvironmentCtrl abstract base class. -//////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// -FGEnvironmentCtrl::FGEnvironmentCtrl () - : _environment(0), - _lon_deg(0), - _lat_deg(0), - _elev_ft(0) -{ -} - -FGEnvironmentCtrl::~FGEnvironmentCtrl () -{ -} - -void -FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment) -{ - _environment = environment; -} - -void -FGEnvironmentCtrl::setLongitudeDeg (double lon_deg) -{ - _lon_deg = lon_deg; -} - -void -FGEnvironmentCtrl::setLatitudeDeg (double lat_deg) -{ - _lat_deg = lat_deg; -} - -void -FGEnvironmentCtrl::setElevationFt (double elev_ft) -{ - _elev_ft = elev_ft; -} - -void -FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft) -{ - _lon_deg = lon_deg; - _lat_deg = lat_deg; - _elev_ft = elev_ft; -} - - - -//////////////////////////////////////////////////////////////////////// -// Implementation of FGInterpolateEnvironmentCtrl. -//////////////////////////////////////////////////////////////////////// - - -FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl () -{ - altitude_n = fgGetNode("/position/altitude-ft", true); - altitude_agl_n = fgGetNode("/position/altitude-agl-ft", true); - boundary_transition_n = fgGetNode("/environment/config/boundary-transition-ft", false ); - boundary_n = fgGetNode("/environment/config/boundary", true ); - aloft_n = fgGetNode("/environment/config/aloft", true ); -} - -FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl () -{ - unsigned int i; - for (i = 0; i < _boundary_table.size(); i++) - delete _boundary_table[i]; - for (i = 0; i < _aloft_table.size(); i++) - delete _aloft_table[i]; -} - - - -void -FGInterpolateEnvironmentCtrl::init () -{ - read_table( boundary_n, _boundary_table); - // pass in a pointer to the environment of the last bondary layer as - // a starting point - read_table( aloft_n, _aloft_table, - _boundary_table.size() > 0 ? - &(*(_boundary_table.end()-1))->environment : NULL ); -} - -void -FGInterpolateEnvironmentCtrl::reinit () -{ - init(); -} - -void -FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector &table, FGEnvironment * parent ) -{ - double last_altitude_ft = 0.0; - double sort_required = false; - size_t i; - - for (i = 0; i < (size_t)node->nChildren(); i++) { - const SGPropertyNode * child = node->getChild(i); - if ( strcmp(child->getName(), "entry") == 0 - && child->getStringValue("elevation-ft", "")[0] != '\0' - && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) ) - { - bucket * b; - if( i < table.size() ) { - // recycle existing bucket - b = table[i]; - } else { - // more nodes than buckets in table, add a new one - b = new bucket; - table.push_back(b); - } - if (i == 0 && parent != NULL ) - b->environment.copy( *parent ); - if (i > 0) - b->environment.copy(table[i-1]->environment); - - b->environment.read(child); - b->altitude_ft = b->environment.get_elevation_ft(); - - // check, if altitudes are in ascending order - if( b->altitude_ft < last_altitude_ft ) - sort_required = true; - last_altitude_ft = b->altitude_ft; - } - } - // remove leftover buckets - while( table.size() > i ) { - bucket * b = *(table.end() - 1); - delete b; - table.pop_back(); - } - - if( sort_required ) - sort(table.begin(), table.end(), bucket::lessThan); - - // cleanup entries with (almost)same altitude - for( vector::size_type n = 1; n < table.size(); n++ ) { - if( fabs(table[n]->altitude_ft - table[n-1]->altitude_ft ) < 1 ) { - SG_LOG( SG_GENERAL, SG_ALERT, "Removing duplicate altitude entry in environment config for altitude " << table[n]->altitude_ft ); - table.erase( table.begin() + n ); - } - } -} - -void -FGInterpolateEnvironmentCtrl::update (double delta_time_sec) -{ - double altitude_ft = altitude_n->getDoubleValue(); - double altitude_agl_ft = altitude_agl_n->getDoubleValue(); - double boundary_transition = - boundary_transition_n == NULL ? 500 : boundary_transition_n->getDoubleValue(); - - int length = _boundary_table.size(); - - if (length > 0) { - // boundary table - double boundary_limit = _boundary_table[length-1]->altitude_ft; - if (boundary_limit >= altitude_agl_ft) { - do_interpolate(_boundary_table, altitude_agl_ft, _environment); - return; - } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) { - //TODO: this is 500ft above the top altitude of boundary layer - //shouldn't this be +/-250 ft off of the top altitude? - // both tables - do_interpolate(_boundary_table, altitude_agl_ft, &env1); - do_interpolate(_aloft_table, altitude_ft, &env2); - double fraction = boundary_transition > SGLimitsd::min() ? - (altitude_agl_ft - boundary_limit) / boundary_transition : 1.0; - interpolate(&env1, &env2, fraction, _environment); - return; - } - } - // aloft table - do_interpolate(_aloft_table, altitude_ft, _environment); -} - -void -FGInterpolateEnvironmentCtrl::do_interpolate (vector &table, double altitude_ft, FGEnvironment * environment) -{ - int length = table.size(); - if (length == 0) - return; - - // Boundary conditions - if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) { - environment->copy(table[0]->environment); // below bottom of table - return; - } else if (table[length-1]->altitude_ft <= altitude_ft) { - environment->copy(table[length-1]->environment); // above top of table - return; - } - - // Search the interpolation table - int layer; - for ( layer = 1; // can't be below bottom layer, handled above - layer < length && table[layer]->altitude_ft <= altitude_ft; - layer++); - FGEnvironment * env1 = &(table[layer-1]->environment); - FGEnvironment * env2 = &(table[layer]->environment); - // two layers of same altitude were sorted out in read_table - double fraction = ((altitude_ft - table[layer-1]->altitude_ft) / - (table[layer]->altitude_ft - table[layer-1]->altitude_ft)); - interpolate(env1, env2, fraction, environment); -} - -bool -FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const -{ - return (altitude_ft < b.altitude_ft); -} - -bool -FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b) -{ - return (a->altitude_ft) < (b->altitude_ft); -} - - -//////////////////////////////////////////////////////////////////////// -// Implementation of FGMetarCtrl. -//////////////////////////////////////////////////////////////////////// - -FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl ) - : - metar_valid(false), - setup_winds_aloft(true), - wind_interpolation_required(true), - metar_sealevel_temperature(15.0), - metar_sealevel_dewpoint(5.0), - // Interpolation constant definitions. - MaxWindChangeKtsSec( 0.2 ), - MaxVisChangePercentSec( 0.05 ), - MaxPressureChangeInHgSec( 0.0005 ), // approx 1hpa/min - MaxTemperatureChangeDegcSec(10.0/60.0), // approx 10degc/min - MaxCloudAltitudeChangeFtSec( 20.0 ), - MaxCloudThicknessChangeFtSec( 50.0 ), - MaxCloudInterpolationHeightFt( 5000.0 ), - MaxCloudInterpolationDeltaFt( 4000.0 ), - _environmentCtrl(environmentCtrl) -{ - windModulator = new FGBasicWindModulator(); - - metar_base_n = fgGetNode( "/environment/metar", true ); - station_id_n = metar_base_n->getNode("station-id", true ); - station_elevation_n = metar_base_n->getNode("station-elevation-ft", true ); - min_visibility_n = metar_base_n->getNode("min-visibility-m", true ); - max_visibility_n = metar_base_n->getNode("max-visibility-m", true ); - base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true ); - base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true ); - base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true ); - base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true ); - gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true ); - temperature_n = metar_base_n->getNode("temperature-degc", true ); - dewpoint_n = metar_base_n->getNode("dewpoint-degc", true ); - humidity_n = metar_base_n->getNode("rel-humidity-norm", true ); - pressure_n = metar_base_n->getNode("pressure-inhg", true ); - clouds_n = metar_base_n->getNode("clouds", true ); - rain_n = metar_base_n->getNode("rain-norm", true ); - hail_n = metar_base_n->getNode("hail-norm", true ); - snow_n = metar_base_n->getNode("snow-norm", true ); - snow_cover_n = metar_base_n->getNode("snow-cover", true ); - magnetic_variation_n = fgGetNode( "/environment/magnetic-variation-deg", true ); - ground_elevation_n = fgGetNode( "/position/ground-elev-m", true ); - longitude_n = fgGetNode( "/position/longitude-deg", true ); - latitude_n = fgGetNode( "/position/latitude-deg", true ); - environment_clouds_n = fgGetNode("/environment/clouds"); - - boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt", true ); - boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg", true ); - boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m", true ); - boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg", true ); - boundary_sea_level_temperature_n = fgGetNode("/environment/config/boundary/entry/temperature-sea-level-degc", true ); - boundary_sea_level_dewpoint_n = fgGetNode("/environment/config/boundary/entry/dewpoint-sea-level-degc", true ); -} - -FGMetarCtrl::~FGMetarCtrl () -{ -} - -void FGMetarCtrl::bind () -{ - fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid ); - fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled ); - fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft ); -} - -void FGMetarCtrl::unbind () -{ - fgUntie("/environment/metar/valid"); - fgUntie("/environment/params/metar-updates-environment"); - fgUntie("/environment/params/metar-updates-winds-aloft"); -} - -// use a "command" to set station temp at station elevation -static void set_temp_at_altitude( double temp_degc, double altitude_ft ) { - SGPropertyNode args; - SGPropertyNode *node = args.getNode("temp-degc", 0, true); - node->setDoubleValue( temp_degc ); - node = args.getNode("altitude-ft", 0, true); - node->setDoubleValue( altitude_ft ); - globals->get_commands()->execute( altitude_ft == 0.0 ? - "set-sea-level-air-temp-degc" : - "set-outside-air-temp-degc", &args); -} - -static void set_dewpoint_at_altitude( double dewpoint_degc, double altitude_ft ) { - SGPropertyNode args; - SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true); - node->setDoubleValue( dewpoint_degc ); - node = args.getNode("altitude-ft", 0, true); - node->setDoubleValue( altitude_ft ); - globals->get_commands()->execute( altitude_ft == 0.0 ? - "set-dewpoint-sea-level-air-temp-degc" : - "set-dewpoint-temp-degc", &args); -} - -/* - Setup the wind nodes for a branch in the /environment/config//entry nodes - - Output properties: - wind-from-heading-deg - wind-speed-kt - turbulence/magnitude-norm - - Input properties: - wind-heading-change-deg how many degrees does the wind direction change at this level - wind-speed-change-rel relative change of wind speed at this level - turbulence/factor factor for the calculated turbulence magnitude at this level - */ -static void setupWindBranch( string branchName, double dir, double speed, double gust ) -{ - SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true); - vector entries = branch->getChildren("entry"); - for ( vector::iterator it = entries.begin(); it != entries.end(); it++) { - - // change wind direction as configured - double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 ); - if( layer_dir >= 360.0 ) layer_dir -= 360.0; - if( layer_dir < 0.0 ) layer_dir += 360.0; - (*it)->setDoubleValue("wind-from-heading-deg", layer_dir); - - double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 )); - (*it)->setDoubleValue("wind-speed-kt", layer_speed ); - - // add some turbulence - SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true); - - double turbulence_norm = speed/50; - if( gust > speed ) { - turbulence_norm += (gust-speed)/25; - } - if( turbulence_norm > 1.0 ) turbulence_norm = 1.0; - - turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 ); - turbulence->setDoubleValue( "magnitude-norm", turbulence_norm ); - } -} - -static void setupWind( bool setup_aloft, double dir, double speed, double gust ) -{ - setupWindBranch( "boundary", dir, speed, gust ); - if( setup_aloft ) - setupWindBranch( "aloft", dir, speed, gust ); -} - -double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dval ) -{ - if (fabs(currentval - requiredval) < dval) return requiredval; - if (currentval < requiredval) return (currentval + dval); - if (currentval > requiredval) return (currentval - dval); - return requiredval; -} - -void -FGMetarCtrl::init () -{ - first_update = true; - wind_interpolation_required = true; -} - -void -FGMetarCtrl::reinit () -{ - init(); -} - -static inline double convert_to_360( double d ) -{ - if( d < 0.0 ) return d + 360.0; - if( d >= 360.0 ) return d - 360.0; - return d; -} - -static inline double convert_to_180( double d ) -{ - return d > 180.0 ? d - 360.0 : d; -} - -// Return the sea level pressure for a metar observation, in inHg. -// This is different from QNH because it accounts for the current -// temperature at the observation point. -// metarPressure in inHg -// fieldHt in ft -// fieldTemp in C - -static double reducePressureSl(double metarPressure, double fieldHt, - double fieldTemp) -{ - double elev = fieldHt * SG_FEET_TO_METER; - double fieldPressure - = FGAtmo::fieldPressure(elev, metarPressure * atmodel::inHg); - double slPressure = P_layer(0, elev, fieldPressure, - fieldTemp + atmodel::freezing, atmodel::ISA::lam0); - return slPressure / atmodel::inHg; -} - -void -FGMetarCtrl::update(double dt) -{ - if( dt <= 0 || !metar_valid ||!enabled) - return; - - windModulator->update(dt); - // Interpolate the current configuration closer to the actual METAR - - bool reinit_required = false; - bool layer_rebuild_required = false; - double station_elevation_ft = station_elevation_n->getDoubleValue(); - - if (first_update) { - double dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue(); - double speed = base_wind_speed_n->getDoubleValue(); - double gust = gust_wind_speed_n->getDoubleValue(); - setupWind(setup_winds_aloft, dir, speed, gust); - - double metarvis = min_visibility_n->getDoubleValue(); - fgDefaultWeatherValue("visibility-m", metarvis); - - set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft); - set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft); - - double metarpressure = pressure_n->getDoubleValue(); - fgDefaultWeatherValue("pressure-sea-level-inhg", - reducePressureSl(metarpressure, - station_elevation_ft, - temperature_n->getDoubleValue())); - - // We haven't already loaded a METAR, so apply it immediately. - vector layers = clouds_n->getChildren("layer"); - vector::const_iterator layer; - vector::const_iterator layers_end = layers.end(); - - int i; - for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) { - SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true); - - target->setStringValue("coverage", - (*layer)->getStringValue("coverage", "clear")); - target->setDoubleValue("elevation-ft", - (*layer)->getDoubleValue("elevation-ft")); - target->setDoubleValue("thickness-ft", - (*layer)->getDoubleValue("thickness-ft")); - target->setDoubleValue("span-m", 40000.0); - } - - first_update = false; - reinit_required = true; - layer_rebuild_required = true; - - } else { - if( wind_interpolation_required ) { - // Generate interpolated values between the METAR and the current - // configuration. - - // Pick up the METAR wind values and convert them into a vector. - double metar[2]; - double metar_speed = base_wind_speed_n->getDoubleValue(); - double metar_heading = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue(); - - metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS ); - metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS); - - // Convert the current wind values and convert them into a vector - double current[2]; - double speed = boundary_wind_speed_n->getDoubleValue(); - double dir_from = boundary_wind_from_heading_n->getDoubleValue();; - - current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS ); - current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS ); - - // Determine the maximum component-wise value that the wind can change. - // First we determine the fraction in the X and Y component, then - // factor by the maximum wind change. - double x = fabs(current[0] - metar[0]); - double y = fabs(current[1] - metar[1]); - - // only interpolate if we have a difference - if (x + y > 0.01 ) { - double dx = x / (x + y); - double dy = 1 - dx; - - double maxdx = dx * MaxWindChangeKtsSec; - double maxdy = dy * MaxWindChangeKtsSec; - - // Interpolate each component separately. - current[0] = interpolate_val(current[0], metar[0], maxdx*dt); - current[1] = interpolate_val(current[1], metar[1], maxdy*dt); - - // Now convert back to polar coordinates. - if ((fabs(current[0]) > 0.1) || (fabs(current[1]) > 0.1)) { - // Some real wind to convert back from. Work out the speed - // and direction value in degrees. - speed = sqrt((current[0] * current[0]) + (current[1] * current[1])); - dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES ); - - // Normalize the direction. - if (dir_from < 0.0) - dir_from += 360.0; - - SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed); - } else { - // Special case where there is no wind (otherwise atan2 barfs) - speed = 0.0; - } - double gust = gust_wind_speed_n->getDoubleValue(); - setupWind(setup_winds_aloft, dir_from, speed, gust); - reinit_required = true; - } else { - wind_interpolation_required = false; - } - } else { // if(wind_interpolation_required) - // interpolation of wind vector is finished, apply wind - // variations and gusts for the boundary layer only - - - bool wind_modulated = false; - - // start with the main wind direction - double wind_dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue(); - double min = convert_to_180(base_wind_range_from_n->getDoubleValue()+magnetic_variation_n->getDoubleValue()); - double max = convert_to_180(base_wind_range_to_n->getDoubleValue()+magnetic_variation_n->getDoubleValue()); - if( max > min ) { - // if variable winds configured, modulate the wind direction - double f = windModulator->get_direction_offset_norm(); - wind_dir = min+(max-min)*f; - double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue()); - wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt )); - wind_modulated = true; - } - - // start with main wind speed - double wind_speed = base_wind_speed_n->getDoubleValue(); - max = gust_wind_speed_n->getDoubleValue(); - if( max > wind_speed ) { - // if gusts are configured, modulate wind magnitude - double f = windModulator->get_magnitude_factor_norm(); - wind_speed = wind_speed+(max-wind_speed)*f; - wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt ); - wind_modulated = true; - } - if( wind_modulated ) { - setupWind(false, wind_dir, wind_speed, max); - reinit_required = true; - } - } - - // Now handle the visibility. We convert both visibility values - // to X-values, then interpolate from there, then back to real values. - // The length_scale is fixed to 1000m, so the visibility changes by - // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec, - // whichever is more. - double vis = boundary_visibility_n->getDoubleValue();; - double metarvis = min_visibility_n->getDoubleValue(); - if( vis != metarvis ) { - double currentxval = log(1000.0 + vis); - double metarxval = log(1000.0 + metarvis); - - currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec*dt); - - // Now convert back from an X-value to a straightforward visibility. - vis = exp(currentxval) - 1000.0; - fgDefaultWeatherValue("visibility-m", vis); - reinit_required = true; - } - - double pressure = boundary_sea_level_pressure_n->getDoubleValue(); - double metarpressure = pressure_n->getDoubleValue(); - double newpressure = reducePressureSl(metarpressure, - station_elevation_ft, - temperature_n->getDoubleValue()); - if( pressure != newpressure ) { - pressure = interpolate_val( pressure, newpressure, MaxPressureChangeInHgSec*dt ); - fgDefaultWeatherValue("pressure-sea-level-inhg", pressure); - reinit_required = true; - } - - { - double temperature = boundary_sea_level_temperature_n->getDoubleValue(); - double dewpoint = boundary_sea_level_dewpoint_n->getDoubleValue(); - if( metar_sealevel_temperature != temperature ) { - temperature = interpolate_val( temperature, metar_sealevel_temperature, MaxTemperatureChangeDegcSec*dt ); - set_temp_at_altitude( temperature, 0.0 ); - } - if( metar_sealevel_dewpoint != dewpoint ) { - dewpoint = interpolate_val( dewpoint, metar_sealevel_dewpoint, MaxTemperatureChangeDegcSec*dt ); - set_dewpoint_at_altitude( dewpoint, 0.0 ); - } - } - - // Set the cloud layers by interpolating over the METAR versions. - vector layers = clouds_n->getChildren("layer"); - vector::const_iterator layer; - vector::const_iterator layers_end = layers.end(); - - double aircraft_alt = fgGetDouble("/position/altitude-ft"); - int i; - - for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) { - SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true); - - // In the case of clouds, we want to avoid writing if nothing has - // changed, as these properties are tied to the renderer and will - // cause the clouds to be updated, reseting the texture locations. - - // We don't interpolate the coverage values as no-matter how we - // do it, it will be quite a sudden change of texture. Better to - // have a single change than four or five. - const char *coverage = (*layer)->getStringValue("coverage", "clear"); - SGPropertyNode *cov = target->getNode("coverage", true); - if (strcmp(cov->getStringValue(), coverage) != 0) { - cov->setStringValue(coverage); - layer_rebuild_required = true; - } - - double required_alt = (*layer)->getDoubleValue("elevation-ft"); - double current_alt = target->getDoubleValue("elevation-ft"); - double required_thickness = (*layer)->getDoubleValue("thickness-ft"); - SGPropertyNode *thickness = target->getNode("thickness-ft", true); - - if (current_alt < -9000 || required_alt < -9000 || - fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt || - fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) { - // We don't interpolate any layers that are - // - too far above us to be visible - // - too far below us to be visible - // - with too large a difference to make interpolation sensible - // - to or from -9999 (used as a placeholder) - // - any values that are too high above us, - if (current_alt != required_alt) - target->setDoubleValue("elevation-ft", required_alt); - - if (thickness->getDoubleValue() != required_thickness) - thickness->setDoubleValue(required_thickness); - - } else { - // Interpolate the other values in the usual way - if (current_alt != required_alt) { - current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec*dt); - target->setDoubleValue("elevation-ft", current_alt); - } - - double current_thickness = thickness->getDoubleValue(); - - if (current_thickness != required_thickness) { - current_thickness = interpolate_val(current_thickness, - required_thickness, - MaxCloudThicknessChangeFtSec*dt); - thickness->setDoubleValue(current_thickness); - } - } - } - } - - // Force an update of the 3D clouds - if( layer_rebuild_required ) - fgSetInt("/environment/rebuild-layers", 1 ); - - // Reinitializing of the environment controller required - if( reinit_required ) - _environmentCtrl->reinit(); -} - -const char * FGMetarCtrl::get_metar(void) const -{ - return metar.c_str(); -} - -static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" }; -static const double thickness_value[] = { 0, 65, 600, 750, 1000 }; - -void FGMetarCtrl::set_metar( const char * metar_string ) -{ - int i; - - metar = metar_string; - - SGSharedPtr m; - try { - m = new FGMetar( metar_string ); - } - catch( sg_io_exception ) { - SG_LOG( SG_GENERAL, SG_WARN, "Can't get metar: " << metar_string ); - metar_valid = false; - return; - } - - wind_interpolation_required = true; - - min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() ); - max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() ); - - const SGMetarVisibility *dirvis = m->getDirVisibility(); - for (i = 0; i < 8; i++, dirvis++) { - SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true); - double v = dirvis->getVisibility_m(); - - vis->setDoubleValue("min-m", v); - vis->setDoubleValue("max-m", v); - } - - base_wind_dir_n->setIntValue( m->getWindDir() ); - base_wind_range_from_n->setIntValue( m->getWindRangeFrom() ); - base_wind_range_to_n->setIntValue( m->getWindRangeTo() ); - base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() ); - gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() ); - temperature_n->setDoubleValue( m->getTemperature_C() ); - dewpoint_n->setDoubleValue( m->getDewpoint_C() ); - humidity_n->setDoubleValue( m->getRelHumidity() ); - pressure_n->setDoubleValue( m->getPressure_inHg() ); - - - // get station elevation to compute cloud base - double station_elevation_ft = 0; - { - // 1. check the id given in the metar - FGAirport* a = FGAirport::findByIdent(m->getId()); - - // 2. if unknown, find closest airport with metar to current position - if( a == NULL ) { - SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue()); - a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter); - } - - // 3. otherwise use ground elevation - if( a != NULL ) { - station_elevation_ft = a->getElevation(); - station_id_n->setStringValue( a->ident()); - } else { - station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET; - station_id_n->setStringValue( m->getId()); - } - } - - station_elevation_n->setDoubleValue( station_elevation_ft ); - - { // calculate sea level temperature and dewpoint - FGEnvironment dummy; // instantiate a dummy so we can leech a method - dummy.set_elevation_ft( station_elevation_ft ); - dummy.set_temperature_degc( temperature_n->getDoubleValue() ); - dummy.set_dewpoint_degc( dewpoint_n->getDoubleValue() ); - metar_sealevel_temperature = dummy.get_temperature_sea_level_degc(); - metar_sealevel_dewpoint = dummy.get_dewpoint_sea_level_degc(); - } - - vector cv = m->getClouds(); - vector::const_iterator cloud, cloud_end = cv.end(); - - int layer_cnt = environment_clouds_n->getChildren("layer").size(); - for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) { - - - const char *coverage = "clear"; - double elevation = -9999.0; - double thickness = 0.0; - const double span = 40000.0; - - if (cloud != cloud_end) { - int c = cloud->getCoverage(); - coverage = coverage_string[c]; - elevation = cloud->getAltitude_ft() + station_elevation_ft; - thickness = thickness_value[c]; - ++cloud; - } - - SGPropertyNode *layer = clouds_n->getChild("layer", i, true ); - - // if the coverage has changed, a rebuild of the layer is needed - if( strcmp(layer->getStringValue("coverage"), coverage ) ) { - layer->setStringValue("coverage", coverage); - } - layer->setDoubleValue("elevation-ft", elevation); - layer->setDoubleValue("thickness-ft", thickness); - layer->setDoubleValue("span-m", span); - } - - rain_n->setDoubleValue(m->getRain()); - hail_n->setDoubleValue(m->getHail()); - snow_n->setDoubleValue(m->getSnow()); - snow_cover_n->setBoolValue(m->getSnowCover()); - metar_valid = true; -} - -#if defined(ENABLE_THREADS) /** - * This class represents the thread of execution responsible for - * fetching the metar data. + * @brief Models a column of our atmosphere by stacking a number of environments above + * each other */ -class MetarThread : public OpenThreads::Thread { +class LayerTable : public std::vector, public SGPropertyChangeListener +{ public: - MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {} - ~MetarThread() {} + LayerTable( SGPropertyNode_ptr rootNode ) : + _rootNode(rootNode) {} - /** - * Fetche the metar data from the NOAA. - */ - void run(); + ~LayerTable(); + + /** + * @brief Read the environment column from properties relative to the given root node + * @param environment A template environment to copy values from, not given in the configuration + */ + void read( FGEnvironment * parent = NULL ); + + /** + *@brief Interpolate and write environment values for a given altitude + *@param altitude_ft The altitude for the desired environment + *@environment the destination to write the resulting environment properties to + */ + void interpolate(double altitude_ft, FGEnvironment * environment); + + /** + *@brief Bind all environments properties to property nodes and initialize the listeners + */ + void Bind(); + + /** + *@brief Unbind all environments properties from property nodes and deregister listeners + */ + void Unbind(); +private: + /** + * @brief Implementation of SGProertyChangeListener::valueChanged() + * Takes care of consitent sea level pressure for the entire column + */ + void valueChanged( SGPropertyNode * node ); + SGPropertyNode_ptr _rootNode; +}; + +////////////////////////////////////////////////////////////////////////////// + + +/** + *@brief Implementation of the LayerIterpolateController + */ +class LayerInterpolateControllerImplementation : public LayerInterpolateController +{ +public: + LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode ); + + virtual void init (); + virtual void reinit (); + virtual void postinit(); + virtual void bind(); + virtual void unbind(); + virtual void update (double delta_time_sec); private: - FGMetarFetcher * metar_fetcher; + SGPropertyNode_ptr _rootNode; + bool _enabled; + double _boundary_transition; + SGPropertyNode_ptr _altitude_n; + SGPropertyNode_ptr _altitude_agl_n; + + LayerTable _boundary_table; + LayerTable _aloft_table; + + FGEnvironment _environment; + TiedPropertyList _tiedProperties; }; -void MetarThread::run() +////////////////////////////////////////////////////////////////////////////// + +LayerTable::~LayerTable() { - for( ;; ) { - string airport_id = metar_fetcher->request_queue.pop(); - - if( airport_id.size() == 0 ) - break; - - if( metar_fetcher->_error_count > 3 ) { - SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently."); - break; - } - - metar_fetcher->fetch( airport_id ); - } + for( iterator it = begin(); it != end(); it++ ) + delete (*it); } -#endif -FGMetarFetcher::FGMetarFetcher() : -#if defined(ENABLE_THREADS) - metar_thread(NULL), -#endif - fetch_timer(0.0), - search_timer(0.0), - error_timer(0.0), - _stale_count(0), - _error_count(0), - enabled(false) +void LayerTable::read(FGEnvironment * parent ) { - longitude_n = fgGetNode( "/position/longitude-deg", true ); - latitude_n = fgGetNode( "/position/latitude-deg", true ); - enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true ); + double last_altitude_ft = 0.0; + double sort_required = false; + size_t i; - proxy_host_n = fgGetNode("/sim/presets/proxy/host", true); - proxy_port_n = fgGetNode("/sim/presets/proxy/port", true); - proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true); - max_age_n = fgGetNode("/environment/params/metar-max-age-min", true); + for (i = 0; i < (size_t)_rootNode->nChildren(); i++) { + const SGPropertyNode * child = _rootNode->getChild(i); + if ( child->getNameString() == "entry" + && child->getStringValue("elevation-ft", "")[0] != '\0' + && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) ) + { + LayerTableBucket * b; + if( i < size() ) { + // recycle existing bucket + b = at(i); + } else { + // more nodes than buckets in table, add a new one + b = new LayerTableBucket; + push_back(b); + } + if (i == 0 && parent != NULL ) + b->environment = *parent; + if (i > 0) + b->environment = at(i-1)->environment; + + b->environment.read(child); + b->altitude_ft = b->environment.get_elevation_ft(); - output_n = fgGetNode("/environment/metar/data", true ); -#if defined(ENABLE_THREADS) - metar_thread = new MetarThread(this); -// FIXME: do we really need setProcessorAffinity()? -// metar_thread->setProcessorAffinity(1); - metar_thread->start(); -#endif // ENABLE_THREADS + // check, if altitudes are in ascending order + if( b->altitude_ft < last_altitude_ft ) + sort_required = true; + last_altitude_ft = b->altitude_ft; + } + } + // remove leftover buckets + while( size() > i ) { + LayerTableBucket * b = *(end() - 1); + delete b; + pop_back(); + } + + if( sort_required ) + sort(begin(), end(), LayerTableBucket::lessThan); + + // cleanup entries with (almost)same altitude + for( size_type n = 1; n < size(); n++ ) { + if( fabs(at(n)->altitude_ft - at(n-1)->altitude_ft ) < 1 ) { + SG_LOG( SG_GENERAL, SG_ALERT, "Removing duplicate altitude entry in environment config for altitude " << at(n)->altitude_ft ); + erase( begin() + n ); + } + } +} + +void LayerTable::Bind() +{ + // tie all environments to ~/entry[n]/xxx + // register this as a changelistener of ~/entry[n]/pressure-sea-level-inhg + for( unsigned i = 0; i < size(); i++ ) { + SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true ); + at(i)->environment.Tie( baseNode ); + baseNode->getNode( "pressure-sea-level-inhg", true )->addChangeListener( this ); + } +} + +void LayerTable::Unbind() +{ + // untie all environments to ~/entry[n]/xxx + // deregister this as a changelistener of ~/entry[n]/pressure-sea-level-inhg + for( unsigned i = 0; i < size(); i++ ) { + SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true ); + at(i)->environment.Untie(); + baseNode->getNode( "pressure-sea-level-inhg", true )->removeChangeListener( this ); + } +} + +void LayerTable::valueChanged( SGPropertyNode * node ) +{ + // Make sure all environments in our column use the same sea level pressure + double value = node->getDoubleValue(); + for( iterator it = begin(); it != end(); it++ ) + (*it)->environment.set_pressure_sea_level_inhg( value ); } -FGMetarFetcher::~FGMetarFetcher() +void LayerTable::interpolate( double altitude_ft, FGEnvironment * result ) { -#if defined(ENABLE_THREADS) - request_queue.push(""); - metar_thread->join(); - delete metar_thread; -#endif // ENABLE_THREADS + int length = size(); + if (length == 0) + return; + + // Boundary conditions + if ((length == 1) || (at(0)->altitude_ft >= altitude_ft)) { + *result = at(0)->environment; // below bottom of table + return; + } else if (at(length-1)->altitude_ft <= altitude_ft) { + *result = at(length-1)->environment; // above top of table + return; + } + + // Search the interpolation table + int layer; + for ( layer = 1; // can't be below bottom layer, handled above + layer < length && at(layer)->altitude_ft <= altitude_ft; + layer++); + FGEnvironment & env1 = (at(layer-1)->environment); + FGEnvironment & env2 = (at(layer)->environment); + // two layers of same altitude were sorted out in read_table + double fraction = ((altitude_ft - at(layer-1)->altitude_ft) / + (at(layer)->altitude_ft - at(layer-1)->altitude_ft)); + env1.interpolate(env2, fraction, result); } -void FGMetarFetcher::init () +////////////////////////////////////////////////////////////////////////////// + +LayerInterpolateControllerImplementation::LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode ) : + _rootNode( rootNode ), + _enabled(true), + _boundary_transition(0.0), + _altitude_n( fgGetNode("/position/altitude-ft", true)), + _altitude_agl_n( fgGetNode("/position/altitude-agl-ft", true)), + _boundary_table( rootNode->getNode("boundary", true ) ), + _aloft_table( rootNode->getNode("aloft", true ) ) { - fetch_timer = 0.0; - search_timer = 0.0; - error_timer = 0.0; - _stale_count = 0; - _error_count = 0; - current_airport_id.clear(); - /* Torsten Dreyer: - hack to stop startup.nas complaining if metar arrives after nasal-dir-initialized - is fired. Immediately fetch and wait for the METAR before continuing. This gets the - /environment/metar/xxx properties filled before nasal-dir is initialized. - Maybe the runway selection should happen here to make startup.nas obsolete? - */ - const char * startup_airport = fgGetString("/sim/startup/options/airport"); - if( *startup_airport ) { - FGAirport * a = FGAirport::getByIdent( startup_airport ); - if( a ) { - SGGeod pos = SGGeod::fromDeg(a->getLongitude(), a->getLatitude()); - a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter); - current_airport_id = a->getId(); - fetch( current_airport_id ); - } - } } -void FGMetarFetcher::reinit () +void LayerInterpolateControllerImplementation::init () { - init(); + _boundary_table.read(); + // pass in a pointer to the environment of the last bondary layer as + // a starting point + _aloft_table.read(&(*(_boundary_table.end()-1))->environment); } -/* search for closest airport with metar every xx seconds */ -static const int search_interval_sec = 60; - -/* fetch metar for airport, even if airport has not changed every xx seconds */ -static const int fetch_interval_sec = 900; - -/* reset error counter after xxx seconds */ -static const int error_timer_sec = 3; - -void FGMetarFetcher::update (double delta_time_sec) +void LayerInterpolateControllerImplementation::reinit () { - fetch_timer -= delta_time_sec; - search_timer -= delta_time_sec; - error_timer -= delta_time_sec; - - if( error_timer <= 0.0 ) { - error_timer = error_timer_sec; - _error_count = 0; - } - - if( enable_n->getBoolValue() == false ) { - enabled = false; - return; - } - - // we were just enabled, reset all timers to - // trigger immediate metar fetch - if( !enabled ) { - search_timer = 0.0; - fetch_timer = 0.0; - error_timer = error_timer_sec; - enabled = true; - } - - FGAirport * a = NULL; - - if( search_timer <= 0.0 ) { - // search timer expired, search closest airport with metar - SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue()); - a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter); - search_timer = search_interval_sec; - } - - if( a == NULL ) - return; - - - if( a->ident() != current_airport_id || fetch_timer <= 0 ) { - // fetch timer expired or airport has changed, schedule a fetch - current_airport_id = a->ident(); - fetch_timer = fetch_interval_sec; -#if defined(ENABLE_THREADS) - // push this airport id into the queue for the worker thread - request_queue.push( current_airport_id ); -#else - // if there is no worker thread, immediately fetch the data - fetch( current_airport_id ); -#endif - } + _boundary_table.Unbind(); + _aloft_table.Unbind(); + init(); + postinit(); } -void FGMetarFetcher::fetch( const string & id ) +void LayerInterpolateControllerImplementation::postinit() { - if( enable_n->getBoolValue() == false ) - return; - - SGSharedPtr result = NULL; - - // fetch current metar data - try { - string host = proxy_host_n->getStringValue(); - string auth = proxy_auth_n->getStringValue(); - string port = proxy_port_n->getStringValue(); - - result = new FGMetar( id, host, port, auth); - - long max_age = max_age_n->getLongValue(); - long age = result->getAge_min(); - - if (max_age && age > max_age) { - SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min)."); - if (++_stale_count > 10) { - _error_count = 1000; - throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!"); - } - } else { - _stale_count = 0; - } - - } catch (const sg_io_exception& e) { - SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() ); - result = NULL; - // remove METAR flag from the airport - FGAirport * a = FGAirport::findByIdent( id ); - if( a ) a->setMetar( false ); - // immediately schedule a new search - search_timer = 0.0; - } - - // write the metar to the property node, the rest is done by the methods tied to this property - // don't write the metar data, if real-weather-fetch has been disabled in the meantime - if( result != NULL && enable_n->getBoolValue() == true ) - output_n->setStringValue( result->getData() ); + // we get here after 1. bind() and 2. init() was called by fg_init + _boundary_table.Bind(); + _aloft_table.Bind(); } -// end of environment_ctrl.cxx +void LayerInterpolateControllerImplementation::bind() +{ + // don't bind the layer tables here, because they have not been read in yet. + _environment.Tie( _rootNode->getNode( "interpolated", true ) ); + _tiedProperties.Tie( _rootNode->getNode("enabled", true), &_enabled ); + _tiedProperties.Tie( _rootNode->getNode("boundary-transition-ft", true ), &_boundary_transition ); +} +void LayerInterpolateControllerImplementation::unbind() +{ + _boundary_table.Unbind(); + _aloft_table.Unbind(); + _tiedProperties.Untie(); + _environment.Untie(); +} + +void LayerInterpolateControllerImplementation::update (double delta_time_sec) +{ + if( !_enabled || delta_time_sec <= SGLimitsd::min() ) + return; + + double altitude_ft = _altitude_n->getDoubleValue(); + double altitude_agl_ft = _altitude_agl_n->getDoubleValue(); + + // avoid div by zero later on and init with a default value if not given + if( _boundary_transition <= SGLimitsd::min() ) + _boundary_transition = 500; + + int length = _boundary_table.size(); + + if (length > 0) { + // If a boundary table is defined, get the top of the boundary layer + double boundary_limit = _boundary_table[length-1]->altitude_ft; + if (boundary_limit >= altitude_agl_ft) { + // If current altitude is below top of boundary layer, interpolate + // only in boundary layer + _boundary_table.interpolate(altitude_agl_ft, &_environment); + return; + } else if ((boundary_limit + _boundary_transition) >= altitude_agl_ft) { + // If current altitude is above top of boundary layer and within the + // transition altitude, interpolate boundary and aloft layers + FGEnvironment env1, env2; + _boundary_table.interpolate( altitude_agl_ft, &env1); + _aloft_table.interpolate(altitude_ft, &env2); + double fraction = (altitude_agl_ft - boundary_limit) / _boundary_transition; + env1.interpolate(env2, fraction, &_environment); + return; + } + } + // If no boundary layer is defined or altitude is above top boundary-layer plus boundary-transition + // altitude, use only the aloft table + _aloft_table.interpolate( altitude_ft, &_environment); +} + +////////////////////////////////////////////////////////////////////////////// + +LayerInterpolateController * LayerInterpolateController::createInstance( SGPropertyNode_ptr rootNode ) +{ + return new LayerInterpolateControllerImplementation( rootNode ); +} + +////////////////////////////////////////////////////////////////////////////// + +} // namespace diff --git a/src/Environment/environment_ctrl.hxx b/src/Environment/environment_ctrl.hxx index 1f81c0259..fd734e8bb 100644 --- a/src/Environment/environment_ctrl.hxx +++ b/src/Environment/environment_ctrl.hxx @@ -18,243 +18,17 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // -// $Id$ #ifndef _ENVIRONMENT_CTRL_HXX #define _ENVIRONMENT_CTRL_HXX -#include #include -#if defined(ENABLE_THREADS) -# include -# include -#endif - -#include -#include - -#include -#include -#include "fgwind.hxx" - -// forward decls -class SGPropertyNode; -class SGSampleGroup; -class FGMetar; - -/** - * Interface to control environment information for a specific location. - */ -class FGEnvironmentCtrl : public SGSubsystem -{ - -public: - - FGEnvironmentCtrl (); - virtual ~FGEnvironmentCtrl (); - - virtual void setEnvironment (FGEnvironment * environment); - - virtual const FGEnvironment * getEnvironment () const { return _environment; } - - virtual void setLongitudeDeg (double lon_deg); - virtual void setLatitudeDeg (double lat_deg); - virtual void setElevationFt (double elev_ft); - virtual void setPosition (double lon_deg, double lat_deg, double elev_ft); - - virtual double getLongitudeDeg () const { return _lon_deg; } - virtual double getLatitudeDeg () const { return _lat_deg; } - virtual double getElevationFt () const { return _elev_ft; } - -protected: - - FGEnvironment * _environment; - double _lon_deg; - double _lat_deg; - double _elev_ft; - -}; - - - -/** - * Interplation controller using user-supplied parameters. - */ -class FGInterpolateEnvironmentCtrl : public FGEnvironmentCtrl -{ -public: - FGInterpolateEnvironmentCtrl (); - virtual ~FGInterpolateEnvironmentCtrl (); - - virtual void init (); - virtual void reinit (); - virtual void update (double delta_time_sec); - -private: - - struct bucket { - double altitude_ft; - FGEnvironment environment; - bool operator< (const bucket &b) const; - // LessThan predicate for bucket pointers. - static bool lessThan(bucket *a, bucket *b); - }; - - void read_table (const SGPropertyNode * node, std::vector &table, FGEnvironment * parent = NULL ); - void do_interpolate (std::vector &table, double altitude_ft, - FGEnvironment * environment); - - FGEnvironment env1, env2; // temporaries - - std::vector _boundary_table; - std::vector _aloft_table; - - SGPropertyNode_ptr altitude_n; - SGPropertyNode_ptr altitude_agl_n; - SGPropertyNode_ptr boundary_transition_n; - SGPropertyNode_ptr boundary_n; - SGPropertyNode_ptr aloft_n; -}; - - - -/** - * Interplation controller using the FGMetar class - */ - -class FGMetarCtrl : public SGSubsystem -{ -public: - FGMetarCtrl (SGSubsystem * environmentCtrl); - virtual ~FGMetarCtrl (); - - virtual void init (); - virtual void reinit (); - virtual void update (double delta_time_sec); - - void set_metar( const char * metar ); - const char * get_metar(void) const; - bool get_valid(void) const { return metar_valid; } - void set_enabled(bool _enabled) { enabled = _enabled; } - bool get_enabled(void) const { return enabled; } - void set_setup_winds_aloft(bool _setup_winds_aloft) { setup_winds_aloft = _setup_winds_aloft; } - bool get_setup_winds_aloft(void) const { return setup_winds_aloft; } - -private: - void bind(); - void unbind(); - - SGSharedPtr windModulator; - bool metar_valid; - bool enabled; - bool setup_winds_aloft; - bool first_update; - bool wind_interpolation_required; - string metar; - double metar_sealevel_temperature; - double metar_sealevel_dewpoint; - double interpolate_prop(const char * currentname, const char * requiredname, double dvalue); - double interpolate_val(double currentval, double requiredval, double dvalue); - const double MaxWindChangeKtsSec; // Max wind change in kts/sec - const double MaxVisChangePercentSec; // Max visibility change in %/sec - const double MaxPressureChangeInHgSec; // Max pressure change in InHg/sec - const double MaxTemperatureChangeDegcSec; // Max temperature change in degc/s - const double MaxCloudAltitudeChangeFtSec; // Max cloud altitude change in ft/s - const double MaxCloudThicknessChangeFtSec; // Max cloud thickness change in ft/s - const double MaxCloudInterpolationHeightFt; // Max distance from aircraft to - // interpolate at. Any cloud - // changes above this height - // difference are not interpolated - const double MaxCloudInterpolationDeltaFt; // Max difference in altitude to - // interpolate. Any cloud changing height - // by more than this value is not - // interpolated - - SGSubsystem * _environmentCtrl; - - SGPropertyNode_ptr metar_base_n; - SGPropertyNode_ptr station_id_n; - SGPropertyNode_ptr station_elevation_n; - SGPropertyNode_ptr min_visibility_n; - SGPropertyNode_ptr max_visibility_n; - SGPropertyNode_ptr base_wind_range_from_n; - SGPropertyNode_ptr base_wind_range_to_n; - SGPropertyNode_ptr base_wind_dir_n; - SGPropertyNode_ptr base_wind_speed_n; - SGPropertyNode_ptr gust_wind_speed_n; - SGPropertyNode_ptr temperature_n; - SGPropertyNode_ptr dewpoint_n; - SGPropertyNode_ptr humidity_n; - SGPropertyNode_ptr pressure_n; - SGPropertyNode_ptr clouds_n; - SGPropertyNode_ptr environment_clouds_n; - SGPropertyNode_ptr rain_n; - SGPropertyNode_ptr hail_n; - SGPropertyNode_ptr snow_n; - SGPropertyNode_ptr snow_cover_n; - SGPropertyNode_ptr ground_elevation_n; - SGPropertyNode_ptr longitude_n; - SGPropertyNode_ptr latitude_n; - SGPropertyNode_ptr magnetic_variation_n; - - SGPropertyNode_ptr boundary_wind_speed_n; - SGPropertyNode_ptr boundary_wind_from_heading_n; - SGPropertyNode_ptr boundary_visibility_n; - SGPropertyNode_ptr boundary_sea_level_pressure_n; - SGPropertyNode_ptr boundary_sea_level_temperature_n; - SGPropertyNode_ptr boundary_sea_level_dewpoint_n; -private: - -}; - -/* - * The subsyste to load real world weather - */ -class FGMetarFetcher : public SGSubsystem -{ -public: - FGMetarFetcher(); - virtual ~FGMetarFetcher(); - - virtual void init (); - virtual void reinit (); - virtual void update (double delta_time_sec); - -private: - friend class MetarThread; -#if defined(ENABLE_THREADS) - /** - * FIFO queue which holds a pointer to the metar requests. - */ - SGBlockingQueue request_queue; - - OpenThreads::Thread * metar_thread; -#endif - - void fetch( const string & id ); - - SGPropertyNode_ptr enable_n; - - SGPropertyNode_ptr longitude_n; - SGPropertyNode_ptr latitude_n; - - SGPropertyNode_ptr proxy_host_n; - SGPropertyNode_ptr proxy_port_n; - SGPropertyNode_ptr proxy_auth_n; - SGPropertyNode_ptr max_age_n; - - SGPropertyNode_ptr output_n; - - string current_airport_id; - double fetch_timer; - double search_timer; - double error_timer; - - long _stale_count; - long _error_count; - bool enabled; -}; - +namespace Environment { + class LayerInterpolateController : public SGSubsystem { + public: + static LayerInterpolateController * createInstance( SGPropertyNode_ptr rootNode ); + }; +} // namespace #endif // _ENVIRONMENT_CTRL_HXX diff --git a/src/Environment/environment_mgr.cxx b/src/Environment/environment_mgr.cxx index c81022258..3f1c14431 100644 --- a/src/Environment/environment_mgr.cxx +++ b/src/Environment/environment_mgr.cxx @@ -17,8 +17,6 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// $Id$ #ifdef HAVE_CONFIG_H # include @@ -39,34 +37,26 @@ #include "environment.hxx" #include "environment_mgr.hxx" #include "environment_ctrl.hxx" +#include "realwx_ctrl.hxx" #include "fgclouds.hxx" #include "precipitation_mgr.hxx" #include "ridge_lift.hxx" +#include "terrainsampler.hxx" class SGSky; extern SGSky *thesky; - - -FGEnvironmentMgr::FGEnvironmentMgr () - : _environment(new FGEnvironment) +FGEnvironmentMgr::FGEnvironmentMgr () : + _environment(new FGEnvironment()), + fgClouds(new FGClouds()), + _altitudeNode(fgGetNode("/position/altitude-ft", true)), + _cloudLayersDirty(true) { + set_subsystem("controller", Environment::LayerInterpolateController::createInstance( fgGetNode("/environment/config", true ) )); + set_subsystem("realwx", Environment::RealWxController::createInstance( fgGetNode("/environment/realwx", true ) ), 1.0 ); - _controller = new FGInterpolateEnvironmentCtrl; - _controller->setEnvironment(_environment); - set_subsystem("controller", _controller, 0.1 ); - - fgClouds = new FGClouds(); - - _metarcontroller = new FGMetarCtrl(_controller ); - set_subsystem("metarcontroller", _metarcontroller, 0.1 ); - - _metarfetcher = new FGMetarFetcher(); - set_subsystem("metarfetcher", _metarfetcher, 1.0 ); - - _precipitationManager = new FGPrecipitationMgr; - set_subsystem("precipitation", _precipitationManager); - + set_subsystem("precipitation", new FGPrecipitationMgr); + set_subsystem("terrainsampler", Environment::TerrainSampler::createInstance( fgGetNode("/environment/terrain", true ) )); set_subsystem("ridgelift", new FGRidgeLift); } @@ -78,20 +68,27 @@ FGEnvironmentMgr::~FGEnvironmentMgr () remove_subsystem( "ridgelift" ); delete subsys; + subsys = get_subsystem( "terrainsampler" ); + remove_subsystem( "terrainsampler" ); + delete subsys; + + subsys = get_subsystem( "precipitation" ); remove_subsystem("precipitation"); - delete _precipitationManager; - - remove_subsystem("metarcontroller"); - delete _metarfetcher; + delete subsys; + subsys = get_subsystem("metarfetcher"); remove_subsystem("metarfetcher"); - delete _metarcontroller; + delete subsys; + + subsys = get_subsystem("metarcontroller"); + remove_subsystem("metarcontroller"); + delete subsys; + + subsys = get_subsystem("controller"); + remove_subsystem("controller"); + delete subsys; delete fgClouds; - - remove_subsystem("controller"); - delete _controller; - delete _environment; } @@ -100,7 +97,6 @@ FGEnvironmentMgr::init () { SG_LOG( SG_GENERAL, SG_INFO, "Initializing environment subsystem"); SGSubsystemGroup::init(); - //_update_fdm(); } void @@ -108,191 +104,86 @@ FGEnvironmentMgr::reinit () { SG_LOG( SG_GENERAL, SG_INFO, "Reinitializing environment subsystem"); SGSubsystemGroup::reinit(); - //_update_fdm(); } void FGEnvironmentMgr::bind () { SGSubsystemGroup::bind(); - fgTie("/environment/visibility-m", _environment, - &FGEnvironment::get_visibility_m, &FGEnvironment::set_visibility_m); - fgSetArchivable("/environment/visibility-m"); - fgTie("/environment/effective-visibility-m", thesky, - &SGSky::get_visibility ); - fgTie("/environment/temperature-sea-level-degc", _environment, - &FGEnvironment::get_temperature_sea_level_degc, - &FGEnvironment::set_temperature_sea_level_degc); - fgSetArchivable("/environment/temperature-sea-level-degc"); - fgTie("/environment/temperature-degc", _environment, - &FGEnvironment::get_temperature_degc); // FIXME: read-only for now - fgTie("/environment/temperature-degf", _environment, - &FGEnvironment::get_temperature_degf); // FIXME: read-only for now - fgTie("/environment/dewpoint-sea-level-degc", _environment, - &FGEnvironment::get_dewpoint_sea_level_degc, - &FGEnvironment::set_dewpoint_sea_level_degc); - fgSetArchivable("/environment/dewpoint-sea-level-degc"); - fgTie("/environment/dewpoint-degc", _environment, - &FGEnvironment::get_dewpoint_degc); // FIXME: read-only for now - fgTie("/environment/pressure-sea-level-inhg", _environment, - &FGEnvironment::get_pressure_sea_level_inhg, - &FGEnvironment::set_pressure_sea_level_inhg); - fgSetArchivable("/environment/pressure-sea-level-inhg"); - fgTie("/environment/pressure-inhg", _environment, - &FGEnvironment::get_pressure_inhg); // FIXME: read-only for now - fgTie("/environment/density-slugft3", _environment, - &FGEnvironment::get_density_slugft3); // read-only - fgTie("/environment/relative-humidity", _environment, - &FGEnvironment::get_relative_humidity); //ro - fgTie("/environment/atmosphere/density-tropo-avg", _environment, - &FGEnvironment::get_density_tropo_avg_kgm3); //ro - fgTie("/environment/atmosphere/altitude-half-to-sun", _environment, - &FGEnvironment::get_altitude_half_to_sun_m, - &FGEnvironment::set_altitude_half_to_sun_m); - fgTie("/environment/atmosphere/altitude-troposphere-top", _environment, - &FGEnvironment::get_altitude_tropo_top_m, - &FGEnvironment::set_altitude_tropo_top_m); - fgTie("/environment/wind-from-heading-deg", _environment, - &FGEnvironment::get_wind_from_heading_deg, - &FGEnvironment::set_wind_from_heading_deg); - fgTie("/environment/wind-speed-kt", _environment, - &FGEnvironment::get_wind_speed_kt, &FGEnvironment::set_wind_speed_kt); - fgTie("/environment/wind-from-north-fps", _environment, - &FGEnvironment::get_wind_from_north_fps, - &FGEnvironment::set_wind_from_north_fps); - fgSetArchivable("/environment/wind-from-north-fps"); - fgTie("/environment/wind-from-east-fps", _environment, - &FGEnvironment::get_wind_from_east_fps, - &FGEnvironment::set_wind_from_east_fps); - fgSetArchivable("/environment/wind-from-east-fps"); - fgTie("/environment/wind-from-down-fps", _environment, - &FGEnvironment::get_wind_from_down_fps, - &FGEnvironment::set_wind_from_down_fps); - fgSetArchivable("/environment/wind-from-down-fps"); - - fgTie("/environment/thermal-lift-fps", _environment, - &FGEnvironment::get_thermal_lift_fps, - &FGEnvironment::set_thermal_lift_fps); - fgSetArchivable("/environment/thermal-lift-fps"); - fgTie("/environment/ridge-lift-fps", _environment, - &FGEnvironment::get_ridge_lift_fps, - &FGEnvironment::set_ridge_lift_fps); - fgSetArchivable("/environment/ridge-lift-fps"); + _environment->Tie( fgGetNode("/environment", true ) ); - fgTie("/environment/local-weather-lift", _environment, - &FGEnvironment::get_local_weather_lift_fps); //read-only - - fgTie("/environment/turbulence/magnitude-norm", _environment, - &FGEnvironment::get_turbulence_magnitude_norm, - &FGEnvironment::set_turbulence_magnitude_norm); - fgSetArchivable("/environment/turbulence/magnitude-norm"); - fgTie("/environment/turbulence/rate-hz", _environment, - &FGEnvironment::get_turbulence_rate_hz, - &FGEnvironment::set_turbulence_rate_hz); - fgSetArchivable("/environment/turbulence/rate-hz"); + _tiedProperties.setRoot( fgGetNode( "/environment", true ) ); + + _tiedProperties.Tie( "effective-visibility-m", thesky, + &SGSky::get_visibility ); + + _tiedProperties.Tie("rebuild-layers", fgClouds, + &FGClouds::get_update_event, + &FGClouds::set_update_event); + + _tiedProperties.Tie("turbulence/use-cloud-turbulence", &sgEnviro, + &SGEnviro::get_turbulence_enable_state, + &SGEnviro::set_turbulence_enable_state); for (int i = 0; i < MAX_CLOUD_LAYERS; i++) { - char buf[128]; - sprintf(buf, "/environment/clouds/layer[%d]/span-m", i); - fgTie(buf, this, i, + SGPropertyNode_ptr layerNode = fgGetNode("/environment/clouds",true)->getChild("layer", i, true ); + + _tiedProperties.Tie( layerNode->getNode("span-m",true), this, i, &FGEnvironmentMgr::get_cloud_layer_span_m, &FGEnvironmentMgr::set_cloud_layer_span_m); - fgSetArchivable(buf); - sprintf(buf, "/environment/clouds/layer[%d]/elevation-ft", i); - fgTie(buf, this, i, + + _tiedProperties.Tie( layerNode->getNode("elevation-ft",true), this, i, &FGEnvironmentMgr::get_cloud_layer_elevation_ft, &FGEnvironmentMgr::set_cloud_layer_elevation_ft); - fgSetArchivable(buf); - sprintf(buf, "/environment/clouds/layer[%d]/thickness-ft", i); - fgTie(buf, this, i, + + _tiedProperties.Tie( layerNode->getNode("thickness-ft",true), this, i, &FGEnvironmentMgr::get_cloud_layer_thickness_ft, &FGEnvironmentMgr::set_cloud_layer_thickness_ft); - fgSetArchivable(buf); - sprintf(buf, "/environment/clouds/layer[%d]/transition-ft", i); - fgTie(buf, this, i, + + _tiedProperties.Tie( layerNode->getNode("transition-ft",true), this, i, &FGEnvironmentMgr::get_cloud_layer_transition_ft, &FGEnvironmentMgr::set_cloud_layer_transition_ft); - fgSetArchivable(buf); - sprintf(buf, "/environment/clouds/layer[%d]/coverage", i); - fgTie(buf, this, i, + + _tiedProperties.Tie( layerNode->getNode("coverage",true), this, i, &FGEnvironmentMgr::get_cloud_layer_coverage, &FGEnvironmentMgr::set_cloud_layer_coverage); - fgSetArchivable(buf); + + _tiedProperties.Tie( layerNode->getNode("coverage-type",true), this, i, + &FGEnvironmentMgr::get_cloud_layer_coverage_type, + &FGEnvironmentMgr::set_cloud_layer_coverage_type); + } - fgTie("/environment/metar/data", _metarcontroller, - &FGMetarCtrl::get_metar, &FGMetarCtrl::set_metar ); + _tiedProperties.setRoot( fgGetNode("/sim/rendering", true ) ); - fgTie("/sim/rendering/clouds3d-enable", fgClouds, + _tiedProperties.Tie( "clouds3d-enable", fgClouds, &FGClouds::get_3dClouds, &FGClouds::set_3dClouds); - fgTie("/sim/rendering/clouds3d-density", thesky, + + _tiedProperties.Tie( "clouds3d-density", thesky, &SGSky::get_3dCloudDensity, &SGSky::set_3dCloudDensity); - fgTie("/sim/rendering/clouds3d-vis-range", thesky, + + _tiedProperties.Tie("clouds3d-vis-range", thesky, &SGSky::get_3dCloudVisRange, &SGSky::set_3dCloudVisRange); - fgTie("/sim/rendering/precipitation-enable", &sgEnviro, + _tiedProperties.Tie("precipitation-enable", &sgEnviro, &SGEnviro::get_precipitation_enable_state, &SGEnviro::set_precipitation_enable_state); - fgTie("/environment/rebuild-layers", fgClouds, - &FGClouds::get_update_event, - &FGClouds::set_update_event); - fgTie("/sim/rendering/lightning-enable", &sgEnviro, + + _tiedProperties.Tie("lightning-enable", &sgEnviro, &SGEnviro::get_lightning_enable_state, &SGEnviro::set_lightning_enable_state); - fgTie("/environment/turbulence/use-cloud-turbulence", &sgEnviro, - &SGEnviro::get_turbulence_enable_state, - &SGEnviro::set_turbulence_enable_state); + sgEnviro.config(fgGetNode("/sim/rendering/precipitation")); } void FGEnvironmentMgr::unbind () { - fgUntie("/environment/visibility-m"); - fgUntie("/environment/effective-visibility-m"); - fgUntie("/environment/temperature-sea-level-degc"); - fgUntie("/environment/temperature-degc"); - fgUntie("/environment/dewpoint-sea-level-degc"); - fgUntie("/environment/dewpoint-degc"); - fgUntie("/environment/pressure-sea-level-inhg"); - fgUntie("/environment/pressure-inhg"); - fgUntie("/environment/density-inhg"); - fgUntie("/environment/relative-humidity"); - fgUntie("/environment/atmosphere/density-tropo-avg"); - fgUntie("/environment/wind-from-north-fps"); - fgUntie("/environment/wind-from-east-fps"); - fgUntie("/environment/wind-from-down-fps"); - - fgUntie("/environment/thermal-lift-fps"); - fgUntie("/environment/ridge-lift-fps"); - fgUntie("/environment/local-weather-lift"); - - fgUntie("/environment/atmosphere/altitude-half-to-sun"); - fgUntie("/environment/atmosphere/altitude-troposphere-top"); - for (int i = 0; i < MAX_CLOUD_LAYERS; i++) { - char buf[128]; - sprintf(buf, "/environment/clouds/layer[%d]/span-m", i); - fgUntie(buf); - sprintf(buf, "/environment/clouds/layer[%d]/elevation-ft", i); - fgUntie(buf); - sprintf(buf, "/environment/clouds/layer[%d]/thickness-ft", i); - fgUntie(buf); - sprintf(buf, "/environment/clouds/layer[%d]/transition-ft", i); - fgUntie(buf); - sprintf(buf, "/environment/clouds/layer[%d]/type", i); - fgUntie(buf); - } - fgUntie("/sim/rendering/clouds3d-enable"); - fgUntie("/sim/rendering/clouds3d-vis-range"); - fgUntie("/sim/rendering/clouds3d-density"); - fgUntie("/sim/rendering/precipitation-enable"); - fgUntie("/environment/rebuild-layers"); - fgUntie("/environment/weather-scenario"); - fgUntie("/sim/rendering/lightning-enable"); - fgUntie("/environment/turbulence/use-cloud-turbulence"); + _tiedProperties.Untie(); + _environment->Untie(); SGSubsystemGroup::unbind(); } @@ -301,14 +192,14 @@ FGEnvironmentMgr::update (double dt) { SGSubsystemGroup::update(dt); - _environment->set_elevation_ft(fgGetDouble("/position/altitude-ft")); - _environment->set_local_weather_lift_fps(fgGetDouble("/local-weather/current/thermal-lift")); - osg::Vec3 windVec(_environment->get_wind_from_north_fps(), - -_environment->get_wind_from_east_fps(), - 0); - simgear::Particles::setWindVector(windVec * SG_FEET_TO_METER); - //simgear::Particles::setWindFrom( _environment->get_wind_from_heading_deg(), - // _environment->get_wind_speed_kt() ); + _environment->set_elevation_ft( _altitudeNode->getDoubleValue() ); + + simgear::Particles::setWindFrom( _environment->get_wind_from_heading_deg(), + _environment->get_wind_speed_kt() ); + if( _cloudLayersDirty ) { + _cloudLayersDirty = false; + fgClouds->set_update_event( fgClouds->get_update_event()+1 ); + } } FGEnvironment @@ -320,9 +211,9 @@ FGEnvironmentMgr::getEnvironment () const FGEnvironment FGEnvironmentMgr::getEnvironment (double lat, double lon, double alt) const { - // Always returns the same environment - // for now; we'll make it interesting - // later. + // Always returns the same environment + // for now; we'll make it interesting + // later. FGEnvironment env = *_environment; env.set_elevation_ft(alt); return env; @@ -404,48 +295,34 @@ FGEnvironmentMgr::set_cloud_layer_transition_ft (int index, const char * FGEnvironmentMgr::get_cloud_layer_coverage (int index) const { - switch (thesky->get_cloud_layer(index)->getCoverage()) { - case SGCloudLayer::SG_CLOUD_OVERCAST: - return "overcast"; - case SGCloudLayer::SG_CLOUD_BROKEN: - return "broken"; - case SGCloudLayer::SG_CLOUD_SCATTERED: - return "scattered"; - case SGCloudLayer::SG_CLOUD_FEW: - return "few"; - case SGCloudLayer::SG_CLOUD_CIRRUS: - return "cirrus"; - case SGCloudLayer::SG_CLOUD_CLEAR: - return "clear"; - default: - return "unknown"; - } + return thesky->get_cloud_layer(index)->getCoverageString().c_str(); } void FGEnvironmentMgr::set_cloud_layer_coverage (int index, const char * coverage_name) { - SGCloudLayer::Coverage coverage; - if (!strcmp(coverage_name, "overcast")) - coverage = SGCloudLayer::SG_CLOUD_OVERCAST; - else if (!strcmp(coverage_name, "broken")) - coverage = SGCloudLayer::SG_CLOUD_BROKEN; - else if (!strcmp(coverage_name, "scattered")) - coverage = SGCloudLayer::SG_CLOUD_SCATTERED; - else if (!strcmp(coverage_name, "few")) - coverage = SGCloudLayer::SG_CLOUD_FEW; - else if (!strcmp(coverage_name, "cirrus")) - coverage = SGCloudLayer::SG_CLOUD_CIRRUS; - else if (!strcmp(coverage_name, "clear")) - coverage = SGCloudLayer::SG_CLOUD_CLEAR; - else { - SG_LOG(SG_INPUT, SG_WARN, "Unknown cloud type " << coverage_name); - coverage = SGCloudLayer::SG_CLOUD_CLEAR; - } - thesky->get_cloud_layer(index)->setCoverage(coverage); + thesky->get_cloud_layer(index)->setCoverageString(coverage_name); + _cloudLayersDirty = true; +} + +int +FGEnvironmentMgr::get_cloud_layer_coverage_type (int index) const +{ + return thesky->get_cloud_layer(index)->getCoverage(); } +void +FGEnvironmentMgr::set_cloud_layer_coverage_type (int index, int type ) +{ + if( index < 0 || index >= SGCloudLayer::SG_MAX_CLOUD_COVERAGES ) { + SG_LOG(SG_ALL,SG_WARN,"Unknown cloud layer type " << type << " ignored" ); + return; + } + thesky->get_cloud_layer(index)->setCoverage(static_cast(type)); + _cloudLayersDirty = true; +} + // end of environment-mgr.cxx diff --git a/src/Environment/environment_mgr.hxx b/src/Environment/environment_mgr.hxx index 490340c4a..04758c97d 100644 --- a/src/Environment/environment_mgr.hxx +++ b/src/Environment/environment_mgr.hxx @@ -18,7 +18,6 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // -// $Id$ #ifndef _ENVIRONMENT_MGR_HXX #define _ENVIRONMENT_MGR_HXX @@ -26,6 +25,7 @@ #include #include #include +#include "tiedpropertylist.hxx" #ifdef SG_HAVE_STD_INCLUDES # include @@ -34,7 +34,6 @@ #endif class FGEnvironment; -class FGEnvironmentCtrl; class FGMetarCtrl; class FGMetarFetcher; class FGClouds; @@ -86,14 +85,14 @@ private: void set_cloud_layer_transition_ft (int index, double transition_ft); const char * get_cloud_layer_coverage (int index) const; void set_cloud_layer_coverage (int index, const char * coverage); + int get_cloud_layer_coverage_type (int index) const; + void set_cloud_layer_coverage_type (int index, int type ); FGEnvironment * _environment; // always the same, for now - FGEnvironmentCtrl * _controller; // always the same, for now - FGMetarCtrl * _metarcontroller; - FGMetarFetcher * _metarfetcher; - FGPrecipitationMgr* _precipitationManager; - FGClouds *fgClouds; + SGPropertyNode_ptr _altitudeNode; + bool _cloudLayersDirty; + TiedPropertyList _tiedProperties; }; #endif // _ENVIRONMENT_MGR_HXX diff --git a/src/Environment/metarairportfilter.cxx b/src/Environment/metarairportfilter.cxx new file mode 100644 index 000000000..b9bc2f880 --- /dev/null +++ b/src/Environment/metarairportfilter.cxx @@ -0,0 +1,34 @@ +// metarairportfilter.cxx -- Implementation of AirportFilter +// +// Written by Torsten Dreyer, August 2010 +// +// Copyright (C) 2010 Torsten Dreyer Torsten(at)t3r(dot)de +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "metarairportfilter.hxx" + +namespace Environment { + +MetarAirportFilter * MetarAirportFilter::_instance = NULL; + +MetarAirportFilter * MetarAirportFilter::instance() +{ + return _instance != NULL ? _instance : + (_instance = new MetarAirportFilter()); +} + +} // namespace Environment diff --git a/src/Environment/metarairportfilter.hxx b/src/Environment/metarairportfilter.hxx new file mode 100644 index 000000000..568a28b95 --- /dev/null +++ b/src/Environment/metarairportfilter.hxx @@ -0,0 +1,52 @@ +// metarairportfilter.hxx -- Implementation of AirportFilter +// +// Written by Torsten Dreyer, August 2010 +// +// Copyright (C) 2010 Torsten Dreyer Torsten(at)t3r(dot)de +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifndef __METARAIRPORTFILTER_HXX +#define __METARAIRPORTFILTER_HXX + +#include +#include +#include "tiedpropertylist.hxx" + +namespace Environment { + +/** + * @brief A AirportFilter for selection airports that provide a METAR + * Singleton implementation of FGAirport::AirportFilter + */ +class MetarAirportFilter : public FGAirport::AirportFilter { +public: + static MetarAirportFilter * instance(); +protected: + MetarAirportFilter() {} + virtual bool passAirport(FGAirport* aApt) const { + return aApt->getMetar(); + } + + // permit heliports and seaports too + virtual FGPositioned::Type maxType() const + { return FGPositioned::SEAPORT; } +private: + static MetarAirportFilter * _instance; +}; + +} +#endif diff --git a/src/Environment/metarproperties.cxx b/src/Environment/metarproperties.cxx new file mode 100644 index 000000000..f76ee6684 --- /dev/null +++ b/src/Environment/metarproperties.cxx @@ -0,0 +1,212 @@ +// metarproperties.cxx -- Parse a METAR and write properties +// +// Written by David Megginson, started May 2002. +// Rewritten by Torsten Dreyer, August 2010 +// +// Copyright (C) 2002 David Megginson - david@megginson.com +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "metarproperties.hxx" +#include "fgmetar.hxx" +#include "environment.hxx" +#include "atmosphere.hxx" +#include +#include + +using std::string; + +namespace Environment { + +MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) : + _rootNode(rootNode), + _metarValidNode( rootNode->getNode( "valid", true ) ), + _station_elevation(0.0), + _station_latitude(0.0), + _station_longitude(0.0), + _min_visibility(16000.0), + _max_visibility(16000.0), + _base_wind_dir(0), + _base_wind_range_from(0), + _base_wind_range_to(0), + _wind_speed(0.0), + _wind_from_north_fps(0.0), + _wind_from_east_fps(0.0), + _gusts(0.0), + _temperature(0.0), + _dewpoint(0.0), + _humidity(0.0), + _pressure(0.0), + _sea_level_temperature(0.0), + _sea_level_dewpoint(0.0), + _sea_level_pressure(29.92), + _rain(0.0), + _hail(0.0), + _snow(0.0), + _snow_cover(false) +{ + // don't tie metar-valid, so listeners get triggered + _metarValidNode->setBoolValue( false ); + _tiedProperties.setRoot( _rootNode ); + _tiedProperties.Tie("data", this, &MetarProperties::get_metar, &MetarProperties::set_metar ); + _tiedProperties.Tie("station-id", this, &MetarProperties::get_station_id ); + _tiedProperties.Tie("station-elevation-ft", &_station_elevation ); + _tiedProperties.Tie("station-latitude-deg", &_station_latitude ); + _tiedProperties.Tie("station-longitude-deg", &_station_longitude ); + _tiedProperties.Tie("min-visibility-m", &_min_visibility ); + _tiedProperties.Tie("max-visibility-m", &_max_visibility ); + _tiedProperties.Tie("base-wind-range-from", &_base_wind_range_from ); + _tiedProperties.Tie("base-wind-range-to", &_base_wind_range_to ); + _tiedProperties.Tie("base-wind-speed-kt", &_wind_speed ); + _tiedProperties.Tie("base-wind-dir-deg", &_base_wind_dir ); + _tiedProperties.Tie("base-wind-from-north-fps", &_wind_from_north_fps ); + _tiedProperties.Tie("base-wind-from-east-fps", &_wind_from_east_fps ); + _tiedProperties.Tie("gust-wind-speed-kt", &_gusts ); + _tiedProperties.Tie("temperature-degc", &_temperature ); + _tiedProperties.Tie("dewpoint-degc", &_dewpoint ); + _tiedProperties.Tie("rel-humidity-norm", &_humidity ); + _tiedProperties.Tie("pressure-inhg", &_pressure ); + _tiedProperties.Tie("temperature-sea-level-degc", &_sea_level_temperature ); + _tiedProperties.Tie("dewpoint-sea-level-degc", &_sea_level_dewpoint ); + _tiedProperties.Tie("pressure-sea-level-inhg", &_sea_level_pressure ); + _tiedProperties.Tie("rain-norm", &_rain ); + _tiedProperties.Tie("hail-norm", &_hail ); + _tiedProperties.Tie("snow-norm", &_snow); + _tiedProperties.Tie("snow-cover", &_snow_cover ); +} + +MetarProperties::~MetarProperties() +{ +} + +static const string coverage_string[] = { + SGCloudLayer::SG_CLOUD_CLEAR_STRING, + SGCloudLayer::SG_CLOUD_FEW_STRING, + SGCloudLayer::SG_CLOUD_SCATTERED_STRING, + SGCloudLayer::SG_CLOUD_BROKEN_STRING, + SGCloudLayer::SG_CLOUD_OVERCAST_STRING, +}; + +static const double thickness_value[] = { 0, 65, 600, 750, 1000 }; + +void MetarProperties::set_metar( const char * metar ) +{ + _metar = metar; + + SGSharedPtr m; + try { + m = new FGMetar( _metar ); + } + catch( sg_io_exception ) { + SG_LOG( SG_GENERAL, SG_WARN, "Can't parse metar: " << _metar ); + _metarValidNode->setBoolValue(false); + return; + } + + _min_visibility = m->getMinVisibility().getVisibility_m(); + _max_visibility = m->getMaxVisibility().getVisibility_m(); + + const SGMetarVisibility *dirvis = m->getDirVisibility(); + for ( int i = 0; i < 8; i++, dirvis++) { + SGPropertyNode *vis = _rootNode->getChild("visibility", i, true); + double v = dirvis->getVisibility_m(); + + vis->setDoubleValue("min-m", v); + vis->setDoubleValue("max-m", v); + } + + _base_wind_dir = m->getWindDir(); + _base_wind_range_from = m->getWindRangeFrom(); + _base_wind_range_to = m->getWindRangeTo(); + _wind_speed = m->getWindSpeed_kt(); + + double speed_fps = _wind_speed * SG_NM_TO_METER * SG_METER_TO_FEET / 3600.0; + _wind_from_north_fps = speed_fps * cos((double)_base_wind_dir * SGD_DEGREES_TO_RADIANS); + _wind_from_east_fps = speed_fps * sin((double)_base_wind_dir * SGD_DEGREES_TO_RADIANS); + _gusts = m->getGustSpeed_kt(); + _temperature = m->getTemperature_C(); + _dewpoint = m->getDewpoint_C(); + _humidity = m->getRelHumidity(); + _pressure = m->getPressure_inHg(); + + { + // 1. check the id given in the metar + FGAirport* a = FGAirport::findByIdent(m->getId()); +/* + // 2. if unknown, find closest airport with metar to current position + if( a == NULL ) { + SGGeod pos = SGGeod::fromDeg(_longitude_n->getDoubleValue(), _latitude_n->getDoubleValue()); + a = FGAirport::findClosest(pos, 10000.0, &_airportWithMetarFilter); + } +*/ + // 3. otherwise use ground elevation + if( a != NULL ) { + _station_elevation = a->getElevation(); + const SGGeod & towerPosition = a->getTowerLocation(); + _station_latitude = towerPosition.getLatitudeDeg(); + _station_longitude = towerPosition.getLongitudeDeg(); + _station_id = a->ident(); + } else { + _station_elevation = 0.0; + _station_latitude = 0.0; + _station_longitude = 0.0; + _station_id = "XXXX"; +// station_elevation_ft = _ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET; +// _station_id = m->getId(); + } + } + + { // calculate sea level temperature, dewpoint and pressure + FGEnvironment dummy; // instantiate a dummy so we can leech a method + dummy.set_elevation_ft( _station_elevation ); + dummy.set_temperature_degc( _temperature ); + dummy.set_dewpoint_degc( _dewpoint ); + _sea_level_temperature = dummy.get_temperature_sea_level_degc(); + _sea_level_dewpoint = dummy.get_dewpoint_sea_level_degc(); + + double elevation_m = _station_elevation * SG_FEET_TO_METER; + double fieldPressure = FGAtmo::fieldPressure( elevation_m, _pressure * atmodel::inHg ); + _sea_level_pressure = P_layer(0, elevation_m, fieldPressure, _temperature + atmodel::freezing, atmodel::ISA::lam0) / atmodel::inHg; + } + + vector cv = m->getClouds(); + vector::const_iterator cloud, cloud_end = cv.end(); + + { + static const char * LAYER = "layer"; + SGPropertyNode_ptr cloudsNode = _rootNode->getNode("clouds", true ); + const vector & metarClouds = m->getClouds(); + for( unsigned i = 0; i < 5; i++ ) { + SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, i, true ); + int coverage = i < metarClouds.size() ? metarClouds[i].getCoverage() : 0; + double elevation = i >= metarClouds.size() || coverage == 0 ? -9999.0 : metarClouds[i].getAltitude_ft() + _station_elevation; + layerNode->setStringValue( "coverage", coverage_string[coverage] ); + layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) ); + layerNode->setDoubleValue( "elevation-ft", elevation ); + layerNode->setDoubleValue( "thickness-ft", thickness_value[coverage]); + layerNode->setDoubleValue( "span-m", 40000 ); + } + + } + + _rain = m->getRain(); + _hail = m->getHail(); + _snow = m->getSnow(); + _snow_cover = m->getSnowCover(); + _metarValidNode->setBoolValue(true); +} + +} // namespace Environment diff --git a/src/Environment/metarproperties.hxx b/src/Environment/metarproperties.hxx new file mode 100644 index 000000000..060cb69f5 --- /dev/null +++ b/src/Environment/metarproperties.hxx @@ -0,0 +1,77 @@ +// metarproperties.hxx -- Parse a METAR and write properties +// +// Written by David Megginson, started May 2002. +// Rewritten by Torsten Dreyer, August 2010 +// +// Copyright (C) 2002 David Megginson - david@megginson.com +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifndef __METARPROPERTIES_HXX +#define __METARPROPERTIES_HXX + +#include +#include +#include "tiedpropertylist.hxx" + +namespace Environment { + +class MetarProperties : public SGReferenced +{ +public: + MetarProperties( SGPropertyNode_ptr rootNode ); + virtual ~MetarProperties(); + + SGPropertyNode_ptr get_root_node() const { return _rootNode; } + +private: + const char * get_metar() const { return _metar.c_str(); } + void set_metar( const char * metar ); + const char * get_station_id() const { return _station_id.c_str(); } + + SGPropertyNode_ptr _rootNode; + SGPropertyNode_ptr _metarValidNode; + std::string _metar; + std::string _station_id; + double _station_elevation; + double _station_latitude; + double _station_longitude; + double _min_visibility; + double _max_visibility; + int _base_wind_dir; + int _base_wind_range_from; + int _base_wind_range_to; + double _wind_speed; + double _wind_from_north_fps; + double _wind_from_east_fps; + double _gusts; + double _temperature; + double _dewpoint; + double _humidity; + double _pressure; + double _sea_level_temperature; + double _sea_level_dewpoint; + double _sea_level_pressure; + double _rain; + double _hail; + double _snow; + bool _snow_cover; + + TiedPropertyList _tiedProperties; +}; + +} // namespace +#endif // __METARPROPERTIES_HXX diff --git a/src/Environment/realwx_ctrl.cxx b/src/Environment/realwx_ctrl.cxx new file mode 100644 index 000000000..1c1131b04 --- /dev/null +++ b/src/Environment/realwx_ctrl.cxx @@ -0,0 +1,301 @@ +// realwx_ctrl.cxx -- Process real weather data +// +// Written by David Megginson, started February 2002. +// Rewritten by Torsten Dreyer, August 2010 +// +// Copyright (C) 2002 David Megginson - david@megginson.com +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "realwx_ctrl.hxx" +#include "tiedpropertylist.hxx" +#include "metarproperties.hxx" +#include "metarairportfilter.hxx" +#include "fgmetar.hxx" + +#include
+ +#include +#include +#include +#if defined(ENABLE_THREADS) +#include +#include +#endif + + +namespace Environment { + +class BasicRealWxController : public RealWxController +{ +public: + BasicRealWxController( SGPropertyNode_ptr rootNode ); + virtual ~BasicRealWxController (); + + virtual void init (); + virtual void reinit (); + +protected: + void bind(); + void unbind(); + + SGPropertyNode_ptr _rootNode; + SGPropertyNode_ptr _longitude_n; + SGPropertyNode_ptr _latitude_n; + SGPropertyNode_ptr _ground_elevation_n; + + bool _enabled; + TiedPropertyList _tiedProperties; + MetarProperties _metarProperties; +}; + +/* -------------------------------------------------------------------------------- */ +/* +Properties + ~/enabled: bool Enables/Disables the realwx controller + ~/metar[1..n]: string Target property path for metar data + */ + +BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode ) : + _rootNode(rootNode), + _longitude_n( fgGetNode( "/position/longitude-deg", true )), + _latitude_n( fgGetNode( "/position/latitude-deg", true )), + _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )), + _enabled(true), + _metarProperties( fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ) ) +{ +} + +BasicRealWxController::~BasicRealWxController() +{ +} + +void BasicRealWxController::init() +{ + update(0); // fetch data ASAP +} + +void BasicRealWxController::reinit() +{ +} + +void BasicRealWxController::bind() +{ + _tiedProperties.setRoot( _rootNode ); + _tiedProperties.Tie( "enabled", &_enabled ); +} + +void BasicRealWxController::unbind() +{ + _tiedProperties.Untie(); +} + +/* -------------------------------------------------------------------------------- */ + +class NoaaMetarRealWxController : public BasicRealWxController { +public: + NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ); + virtual ~NoaaMetarRealWxController(); + virtual void update (double delta_time_sec); + + class MetarLoadRequest { + public: + MetarLoadRequest( const string & stationId ) { + _stationId = stationId; + _proxyHost = fgGetNode("/sim/presets/proxy/host", true)->getStringValue(); + _proxyPort = fgGetNode("/sim/presets/proxy/port", true)->getStringValue(); + _proxyAuth = fgGetNode("/sim/presets/proxy/authentication", true)->getStringValue(); + } + string _stationId; + string _proxyHost; + string _proxyPort; + string _proxyAuth; + private: + }; +private: + double _metarTimeToLive; + double _positionTimeToLive; + double _minimumRequestInterval; + + SGPropertyNode_ptr _metarDataNode; + SGPropertyNode_ptr _metarValidNode; + SGPropertyNode_ptr _metarStationIdNode; + + +#if defined(ENABLE_THREADS) + class MetarLoadThread : public OpenThreads::Thread { + public: + MetarLoadThread( NoaaMetarRealWxController & controller ); + void requestMetar( const MetarLoadRequest & metarRequest ); + bool hasMetar() { return _responseQueue.size() > 0; } + string getMetar() { return _responseQueue.pop(); } + virtual void run(); + private: + NoaaMetarRealWxController & _controller; + SGBlockingQueue _requestQueue; + SGBlockingQueue _responseQueue; + }; + + MetarLoadThread * _metarLoadThread; +#endif +}; + +NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) : + BasicRealWxController(rootNode), + _metarTimeToLive(0.0), + _positionTimeToLive(0.0), + _minimumRequestInterval(0.0), + _metarDataNode(_metarProperties.get_root_node()->getNode("data",true)), + _metarValidNode(_metarProperties.get_root_node()->getNode("valid",true)), + _metarStationIdNode(_metarProperties.get_root_node()->getNode("station-id",true)) +{ +#if defined(ENABLE_THREADS) + _metarLoadThread = new MetarLoadThread(*this); + _metarLoadThread->start(); +#endif +} + +NoaaMetarRealWxController::~NoaaMetarRealWxController() +{ +#if defined(ENABLE_THREADS) + if( _metarLoadThread ) { + MetarLoadRequest request(""); + _metarLoadThread->requestMetar(request); + _metarLoadThread->join(); + delete _metarLoadThread; + } +#endif // ENABLE_THREADS +} + +void NoaaMetarRealWxController::update( double dt ) +{ + if( !_enabled ) + return; + + if( _metarLoadThread->hasMetar() ) + _metarDataNode->setStringValue( _metarLoadThread->getMetar() ); + + _metarTimeToLive -= dt; + _positionTimeToLive -= dt; + _minimumRequestInterval -= dt; + + bool valid = _metarValidNode->getBoolValue(); + string stationId = valid ? _metarStationIdNode->getStringValue() : ""; + + + if( _metarTimeToLive <= 0.0 ) { + valid = false; + _metarTimeToLive = 900; + _positionTimeToLive = 0; + } + + if( _positionTimeToLive <= 0.0 || valid == false ) { + _positionTimeToLive = 60.0; + + SGGeod pos = SGGeod::fromDeg(_longitude_n->getDoubleValue(), _latitude_n->getDoubleValue()); + + FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() ); + if( nearestAirport == NULL ) { + SG_LOG(SG_ALL,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM" ); + return; + } + + if( stationId != nearestAirport->ident() ) { + valid = false; + stationId = nearestAirport->ident(); + } + + } + + if( !valid ) { + if( _minimumRequestInterval <= 0 && stationId.length() > 0 ) { + MetarLoadRequest request( stationId ); + _metarLoadThread->requestMetar( request ); + _minimumRequestInterval = 10; + } + } + +} + +/* -------------------------------------------------------------------------------- */ + +#if defined(ENABLE_THREADS) +NoaaMetarRealWxController::MetarLoadThread::MetarLoadThread( NoaaMetarRealWxController & controller ) : + _controller(controller) +{ +} + +void NoaaMetarRealWxController::MetarLoadThread::requestMetar( const MetarLoadRequest & metarRequest ) +{ + if( _requestQueue.size() > 10 ) { + SG_LOG(SG_ALL,SG_ALERT, + "NoaaMetarRealWxController::MetarLoadThread::requestMetar() more than 10 outstanding METAR requests, dropping " + << metarRequest._stationId ); + return; + } + + _requestQueue.push( metarRequest ); +} + +void NoaaMetarRealWxController::MetarLoadThread::run() +{ + for( ;; ) { + const MetarLoadRequest request = _requestQueue.pop(); + + if( request._stationId.size() == 0 ) + break; + + SGSharedPtr result = NULL; + + try { + result = new FGMetar( request._stationId, request._proxyHost, request._proxyPort, request._proxyAuth ); + } catch (const sg_io_exception& e) { + SG_LOG( SG_GENERAL, SG_WARN, "NoaaMetarRealWxController::fetchMetar(): can't get METAR for " + << request._stationId << ":" << e.getFormattedMessage().c_str() ); + continue; + } + + if( result == NULL ) + continue; + + string reply = result->getData(); + std::replace(reply.begin(), reply.end(), '\n', ' '); + string metar = simgear::strutils::strip( reply ); + if( metar.length() > 0 ) + _responseQueue.push( metar ); + } +} +#endif + +/* -------------------------------------------------------------------------------- */ + +RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode ) +{ +// string dataSource = rootNode->getStringValue("data-source", "noaa" ); +// if( dataSource == "nwx" ) { +// return new NwxMetarRealWxController( rootNode ); +// } else { + return new NoaaMetarRealWxController( rootNode ); +// } +} + +/* -------------------------------------------------------------------------------- */ + +} // namespace Environment diff --git a/src/Environment/realwx_ctrl.hxx b/src/Environment/realwx_ctrl.hxx new file mode 100644 index 000000000..93155e6e8 --- /dev/null +++ b/src/Environment/realwx_ctrl.hxx @@ -0,0 +1,36 @@ +// realwx_ctrl.cxx -- Process real weather data +// +// Written by David Megginson, started May 2002. +// Rewritten by Torsten Dreyer, August 2010 +// +// Copyright (C) 2002 David Megginson - david@megginson.com +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifndef _REALWX_CTRL_HXX +#define _REALWX_CTRL_HXX + +#include + +namespace Environment { +class RealWxController : public SGSubsystem +{ +public: + static RealWxController * createInstance( SGPropertyNode_ptr rootNode ); +}; + +} // namespace +#endif // _REALWX_CTRL_HXX diff --git a/src/Main/options.cxx b/src/Main/options.cxx index e572ebf84..4eebeead5 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -1294,8 +1294,8 @@ struct OptionDesc { {"enable-mouse-pointer", false, OPTION_STRING, "/sim/startup/mouse-pointer", false, "enabled", 0 }, {"disable-random-objects", false, OPTION_BOOL, "/sim/rendering/random-objects", false, "", 0 }, {"enable-random-objects", false, OPTION_BOOL, "/sim/rendering/random-objects", true, "", 0 }, - {"disable-real-weather-fetch", false, OPTION_BOOL, "/environment/params/real-world-weather-fetch", false, "", 0 }, - {"enable-real-weather-fetch", false, OPTION_BOOL, "/environment/params/real-world-weather-fetch", true, "", 0 }, + {"disable-real-weather-fetch", false, OPTION_BOOL, "/environment/realwx/enabled", false, "", 0 }, + {"enable-real-weather-fetch", false, OPTION_BOOL, "/environment/realwx/enabled", true, "", 0 }, {"metar", true, OPTION_STRING, "/environment/metar/data", false, "", 0 }, {"disable-ai-models", false, OPTION_BOOL, "/sim/ai/enabled", false, "", 0 }, {"enable-ai-models", false, OPTION_BOOL, "/sim/ai/enabled", true, "", 0 },