963 lines
30 KiB
C++
963 lines
30 KiB
C++
// environment_ctrl.cxx -- manager for natural environment information.
|
||
//
|
||
// Written by David Megginson, started February 2002.
|
||
//
|
||
// 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.
|
||
//
|
||
// $Id$
|
||
|
||
#ifdef HAVE_CONFIG_H
|
||
# include "config.h"
|
||
#endif
|
||
|
||
#include <algorithm>
|
||
|
||
#include <simgear/debug/logstream.hxx>
|
||
#include <simgear/structure/commands.hxx>
|
||
#include <simgear/structure/exception.hxx>
|
||
|
||
#include <Airports/simple.hxx>
|
||
#include <Main/fg_props.hxx>
|
||
#include <Main/util.hxx>
|
||
|
||
#include "fgmetar.hxx"
|
||
#include "environment_ctrl.hxx"
|
||
|
||
using std::sort;
|
||
|
||
class AirportWithMetar : public FGAirport::AirportFilter {
|
||
public:
|
||
virtual bool passAirport(FGAirport* aApt) const {
|
||
return aApt->getMetar();
|
||
}
|
||
};
|
||
|
||
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<bucket *> &table)
|
||
{
|
||
double last_altitude_ft = 0.0;
|
||
double sort_required = false;
|
||
int i;
|
||
|
||
for (i = 0; i < 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
|
||
vector<bucket*>::iterator it = table.begin() + i;
|
||
while( it != table.end() )
|
||
table.erase( it );
|
||
|
||
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<bucket *> &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 )
|
||
: _environmentCtrl(environmentCtrl),
|
||
station_elevation_ft(0.0),
|
||
metar_valid(false),
|
||
setup_winds_aloft(true),
|
||
wind_interpolation_required(true),
|
||
// Interpolation constant definitions.
|
||
EnvironmentUpdatePeriodSec( 0.2 ),
|
||
MaxWindChangeKtsSec( 0.2 ),
|
||
MaxVisChangePercentSec( 0.05 ),
|
||
MaxPressureChangeInHgSec( 0.0033 ),
|
||
MaxCloudAltitudeChangeFtSec( 20.0 ),
|
||
MaxCloudThicknessChangeFtSec( 50.0 ),
|
||
MaxCloudInterpolationHeightFt( 5000.0 ),
|
||
MaxCloudInterpolationDeltaFt( 4000.0 )
|
||
{
|
||
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 );
|
||
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");
|
||
boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
|
||
boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
|
||
boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
|
||
}
|
||
|
||
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( float temp_degc, float altitude_ft ) {
|
||
SGPropertyNode args;
|
||
SGPropertyNode *node = args.getNode("temp-degc", 0, true);
|
||
node->setFloatValue( temp_degc );
|
||
node = args.getNode("altitude-ft", 0, true);
|
||
node->setFloatValue( altitude_ft );
|
||
globals->get_commands()->execute("set-outside-air-temp-degc", &args);
|
||
}
|
||
|
||
static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
|
||
SGPropertyNode args;
|
||
SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
|
||
node->setFloatValue( dewpoint_degc );
|
||
node = args.getNode("altitude-ft", 0, true);
|
||
node->setFloatValue( altitude_ft );
|
||
globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
|
||
}
|
||
|
||
/*
|
||
Setup the wind nodes for a branch in the /environment/config/<branchName>/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<SGPropertyNode_ptr> entries = branch->getChildren("entry");
|
||
for ( vector<SGPropertyNode_ptr>::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_boundary, bool setup_aloft, double dir, double speed, double gust )
|
||
{
|
||
if( setup_boundary )
|
||
setupWindBranch( "boundary", dir, speed, gust );
|
||
|
||
if( setup_aloft )
|
||
setupWindBranch( "aloft", dir, speed, gust );
|
||
}
|
||
|
||
double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
|
||
{
|
||
double dval = EnvironmentUpdatePeriodSec * dt;
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
|
||
if (first_update) {
|
||
double dir = base_wind_dir_n->getDoubleValue();
|
||
double speed = base_wind_speed_n->getDoubleValue();
|
||
double gust = gust_wind_speed_n->getDoubleValue();
|
||
setupWind(true, setup_winds_aloft, dir, speed, gust);
|
||
|
||
double metarvis = min_visibility_n->getDoubleValue();
|
||
fgDefaultWeatherValue("visibility-m", metarvis);
|
||
|
||
double metarpressure = pressure_n->getDoubleValue();
|
||
fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
|
||
|
||
// We haven't already loaded a METAR, so apply it immediately.
|
||
vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
|
||
vector<SGPropertyNode_ptr>::const_iterator layer;
|
||
vector<SGPropertyNode_ptr>::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();
|
||
|
||
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);
|
||
current[1] = interpolate_val(current[1], metar[1], maxdy);
|
||
|
||
// Now convert back to polar coordinates.
|
||
if ((current[0] == 0.0) && (current[1] == 0.0)) {
|
||
// Special case where there is no wind (otherwise atan2 barfs)
|
||
speed = 0.0;
|
||
} else {
|
||
// 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);
|
||
}
|
||
double gust = gust_wind_speed_n->getDoubleValue();
|
||
setupWind(true, 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
|
||
|
||
// start with the main wind direction
|
||
double wind_dir = base_wind_dir_n->getDoubleValue();
|
||
double min = convert_to_180(base_wind_range_from_n->getDoubleValue());
|
||
double max = convert_to_180(base_wind_range_to_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 ));
|
||
}
|
||
|
||
// 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 );
|
||
}
|
||
setupWind(true, 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);
|
||
|
||
// 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();
|
||
if( pressure != metarpressure ) {
|
||
pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
|
||
fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
|
||
reinit_required = true;
|
||
}
|
||
|
||
// Set the cloud layers by interpolating over the METAR versions.
|
||
vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
|
||
vector<SGPropertyNode_ptr>::const_iterator layer;
|
||
vector<SGPropertyNode_ptr>::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);
|
||
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);
|
||
thickness->setDoubleValue(current_thickness);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
|
||
set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
|
||
//TODO: check if temperature/dewpoint have changed. This requires reinit.
|
||
|
||
// 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<FGMetar> 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 );
|
||
|
||
vector<SGMetarCloud> cv = m->getClouds();
|
||
vector<SGMetarCloud>::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.
|
||
*/
|
||
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()
|
||
{
|
||
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 );
|
||
}
|
||
}
|
||
#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)
|
||
{
|
||
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()
|
||
{
|
||
#if defined(ENABLE_THREADS)
|
||
request_queue.push("");
|
||
metar_thread->join();
|
||
delete metar_thread;
|
||
#endif // ENABLE_THREADS
|
||
}
|
||
|
||
void FGMetarFetcher::init ()
|
||
{
|
||
fetch_timer = 0.0;
|
||
search_timer = 0.0;
|
||
error_timer = 0.0;
|
||
_stale_count = 0;
|
||
_error_count = 0;
|
||
current_airport_id.clear();
|
||
}
|
||
|
||
void FGMetarFetcher::reinit ()
|
||
{
|
||
init();
|
||
}
|
||
|
||
/* 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)
|
||
{
|
||
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 )
|
||
return;
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
void FGMetarFetcher::fetch( const string & id )
|
||
{
|
||
SGSharedPtr<FGMetar> 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;
|
||
}
|
||
|
||
// 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() );
|
||
}
|
||
|
||
// end of environment_ctrl.cxx
|
||
|