X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FEnvironment%2Fenvironment_ctrl.cxx;h=e7c8820fa4a3d82a686577587b1d3eb315f85327;hb=520c002058c5b7a919eb84a1f75eb643441f3537;hp=6efcbde3ee747f3eb69bc90e32afcbfb4339fa56;hpb=d0be86810e3d070758cbad507d396523203f943a;p=flightgear.git diff --git a/src/Environment/environment_ctrl.cxx b/src/Environment/environment_ctrl.cxx index 6efcbde3e..e7c8820fa 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,7 +19,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 "config.h" @@ -26,1039 +26,324 @@ #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); - read_table( aloft_n, _aloft_table); -} - -void -FGInterpolateEnvironmentCtrl::reinit () -{ - init(); -} - -void -FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector &table) -{ - 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) - 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); -} - -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 = - (altitude_agl_ft - boundary_limit) / boundary_transition; - 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); - return; - } else if (table[length-1]->altitude_ft <= altitude_ft) { - environment->copy(table[length-1]->environment); - return; - } - // Search the interpolation table - for (int i = 0; i < length - 1; i++) { - if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) { - FGEnvironment * env1 = &(table[i]->environment); - FGEnvironment * env2 = &(table[i+1]->environment); - double fraction; - if (table[i]->altitude_ft == table[i+1]->altitude_ft) - fraction = 1.0; - else - fraction = - ((altitude_ft - table[i]->altitude_ft) / - (table[i+1]->altitude_ft - table[i]->altitude_ft)); - interpolate(env1, env2, fraction, environment); - - return; - } - } -} - -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 () +/** + * @brief Models a column of our atmosphere by stacking a number of environments above + * each other + */ +class LayerTable : public std::vector, public SGPropertyChangeListener { - 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); -} +public: + LayerTable( SGPropertyNode_ptr rootNode ) : + _rootNode(rootNode) {} + + ~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; +}; -/* - 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 +/** + *@brief Implementation of the LayerIterpolateController */ -static void setupWindBranch( string branchName, double dir, double speed, double gust ) +class LayerInterpolateControllerImplementation : public LayerInterpolateController { - 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++) { +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); - // 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); +private: + SGPropertyNode_ptr _rootNode; + bool _enabled; + double _boundary_transition; + SGPropertyNode_ptr _altitude_n; + SGPropertyNode_ptr _altitude_agl_n; - double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 )); - (*it)->setDoubleValue("wind-speed-kt", layer_speed ); + LayerTable _boundary_table; + LayerTable _aloft_table; - // add some turbulence - SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true); + FGEnvironment _environment; + simgear::TiedPropertyList _tiedProperties; +}; - double turbulence_norm = speed/50; - if( gust > speed ) { - turbulence_norm += (gust-speed)/25; - } - if( turbulence_norm > 1.0 ) turbulence_norm = 1.0; +////////////////////////////////////////////////////////////////////////////// + +LayerTable::~LayerTable() +{ + for( iterator it = begin(); it != end(); it++ ) + delete (*it); +} + +void LayerTable::read(FGEnvironment * parent ) +{ + double last_altitude_ft = 0.0; + double sort_required = false; + size_t i; + + 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(); + + // 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(); + } - turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 ); - turbulence->setDoubleValue( "magnitude-norm", turbulence_norm ); - } -} + if( sort_required ) + sort(begin(), end(), LayerTableBucket::lessThan); -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 ); + // 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_ENVIRONMENT, SG_ALERT, "Removing duplicate altitude entry in environment config for altitude " << at(n)->altitude_ft ); + erase( begin() + n ); + } + } } -double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dval ) +void LayerTable::Bind() { - if (fabs(currentval - requiredval) < dval) return requiredval; - if (currentval < requiredval) return (currentval + dval); - if (currentval > requiredval) return (currentval - dval); - return requiredval; + // 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 -FGMetarCtrl::init () +void LayerTable::Unbind() { - first_update = true; - wind_interpolation_required = true; + // 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 -FGMetarCtrl::reinit () +void LayerTable::valueChanged( SGPropertyNode * node ) { - init(); + // 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 ); } -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 ) +void LayerTable::interpolate( double altitude_ft, FGEnvironment * result ) { - 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 + int length = size(); + if (length == 0) + return; -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; + // 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 -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 +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 ) ) { - 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 ) +void LayerInterpolateControllerImplementation::init () { - int i; - - metar = metar_string; - - SGSharedPtr m; - try { - m = new FGMetar( metar_string ); - } - catch( sg_io_exception ) { - fprintf( stderr, "can't get metar: %s\n", 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; + _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); } -#if defined(ENABLE_THREADS) -/** - * This class represents the thread of execution responsible for - * fetching the metar data. - */ -class MetarThread : public OpenThreads::Thread { -public: - MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {} - ~MetarThread() {} - - /** - * Fetche the metar data from the NOAA. - */ - void run(); - -private: - FGMetarFetcher * metar_fetcher; -}; - -void MetarThread::run() +void LayerInterpolateControllerImplementation::reinit () { - 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 ); - } + _boundary_table.Unbind(); + _aloft_table.Unbind(); + init(); + postinit(); } -#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) -{ - longitude_n = fgGetNode( "/position/longitude-deg", true ); - latitude_n = fgGetNode( "/position/latitude-deg", true ); - enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true ); - - 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); - - 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 -} - -FGMetarFetcher::~FGMetarFetcher() +void LayerInterpolateControllerImplementation::postinit() { -#if defined(ENABLE_THREADS) - request_queue.push(""); - metar_thread->join(); - delete metar_thread; -#endif // ENABLE_THREADS + // we get here after 1. bind() and 2. init() was called by fg_init + _boundary_table.Bind(); + _aloft_table.Bind(); } -void FGMetarFetcher::init () +void LayerInterpolateControllerImplementation::bind() { - 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 ); - } - } + // 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 FGMetarFetcher::reinit () +void LayerInterpolateControllerImplementation::unbind() { - init(); + _boundary_table.Unbind(); + _aloft_table.Unbind(); + _tiedProperties.Untie(); + _environment.Untie(); } -/* 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::update (double delta_time_sec) { - 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( !_enabled || delta_time_sec <= SGLimitsd::min() ) + return; - if( enable_n->getBoolValue() == false ) { - enabled = false; - return; - } + double altitude_ft = _altitude_n->getDoubleValue(); + double altitude_agl_ft = _altitude_agl_n->getDoubleValue(); - // 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; - } + // avoid div by zero later on and init with a default value if not given + if( _boundary_transition <= SGLimitsd::min() ) + _boundary_transition = 500; - FGAirport * a = NULL; + int length = _boundary_table.size(); - 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 - } + 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); } -void FGMetarFetcher::fetch( const string & id ) -{ - 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() ); +LayerInterpolateController * LayerInterpolateController::createInstance( SGPropertyNode_ptr rootNode ) +{ + return new LayerInterpolateControllerImplementation( rootNode ); } -// end of environment_ctrl.cxx +////////////////////////////////////////////////////////////////////////////// +} // namespace