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 },