2002-05-07 00:03:54 +00:00
|
|
|
|
// 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
|
2006-02-21 01:16:04 +00:00
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2002-05-07 00:03:54 +00:00
|
|
|
|
//
|
|
|
|
|
// $Id$
|
|
|
|
|
|
2006-02-18 13:58:09 +00:00
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
# include "config.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2003-06-10 12:03:07 +00:00
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
2007-08-03 12:06:17 +00:00
|
|
|
|
#include <simgear/debug/logstream.hxx>
|
2004-02-22 14:21:37 +00:00
|
|
|
|
#include <simgear/structure/commands.hxx>
|
2004-02-21 15:00:49 +00:00
|
|
|
|
#include <simgear/structure/exception.hxx>
|
|
|
|
|
|
2004-02-22 14:21:37 +00:00
|
|
|
|
#include <Airports/simple.hxx>
|
2002-05-07 00:03:54 +00:00
|
|
|
|
#include <Main/fg_props.hxx>
|
2004-02-22 02:06:05 +00:00
|
|
|
|
#include <Main/util.hxx>
|
2002-05-07 00:03:54 +00:00
|
|
|
|
|
2009-10-26 06:54:39 +01:00
|
|
|
|
#include "atmosphere.hxx"
|
2009-05-14 20:55:09 +00:00
|
|
|
|
#include "fgmetar.hxx"
|
2002-05-07 00:03:54 +00:00
|
|
|
|
#include "environment_ctrl.hxx"
|
|
|
|
|
|
2008-07-27 16:25:13 +00:00
|
|
|
|
using std::sort;
|
2003-06-09 08:44:59 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
class AirportWithMetar : public FGAirport::AirportFilter {
|
2008-12-26 11:15:00 +00:00
|
|
|
|
public:
|
2009-05-29 10:26:35 +00:00
|
|
|
|
virtual bool passAirport(FGAirport* aApt) const {
|
|
|
|
|
return aApt->getMetar();
|
|
|
|
|
}
|
2009-10-13 06:48:52 +00:00
|
|
|
|
|
|
|
|
|
// permit heliports and seaports too
|
|
|
|
|
virtual FGPositioned::Type maxType() const
|
|
|
|
|
{ return FGPositioned::SEAPORT; }
|
2008-12-26 11:15:00 +00:00
|
|
|
|
};
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
static AirportWithMetar airportWithMetarFilter;
|
2002-05-07 00:03:54 +00:00
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Implementation of FGEnvironmentCtrl abstract base class.
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
FGEnvironmentCtrl::FGEnvironmentCtrl ()
|
2009-05-29 10:26:35 +00:00
|
|
|
|
: _environment(0),
|
|
|
|
|
_lon_deg(0),
|
|
|
|
|
_lat_deg(0),
|
|
|
|
|
_elev_ft(0)
|
2002-05-07 00:03:54 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FGEnvironmentCtrl::~FGEnvironmentCtrl ()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
_environment = environment;
|
2002-05-07 00:03:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
_lon_deg = lon_deg;
|
2002-05-07 00:03:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
_lat_deg = lat_deg;
|
2002-05-07 00:03:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGEnvironmentCtrl::setElevationFt (double elev_ft)
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
_elev_ft = elev_ft;
|
2002-05-07 00:03:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
_lon_deg = lon_deg;
|
|
|
|
|
_lat_deg = lat_deg;
|
|
|
|
|
_elev_ft = elev_ft;
|
2002-05-07 00:03:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2003-06-08 14:47:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Implementation of FGInterpolateEnvironmentCtrl.
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
2003-06-08 14:47:03 +00:00
|
|
|
|
FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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 );
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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];
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGInterpolateEnvironmentCtrl::init ()
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
read_table( boundary_n, _boundary_table);
|
|
|
|
|
read_table( aloft_n, _aloft_table);
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGInterpolateEnvironmentCtrl::reinit ()
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
init();
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2009-05-29 10:26:35 +00:00
|
|
|
|
FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector<bucket *> &table)
|
|
|
|
|
{
|
2009-06-03 16:29:58 +00:00
|
|
|
|
double last_altitude_ft = 0.0;
|
|
|
|
|
double sort_required = false;
|
2009-06-27 08:09:10 +00:00
|
|
|
|
size_t i;
|
2009-06-03 16:29:58 +00:00
|
|
|
|
|
2009-06-27 08:09:10 +00:00
|
|
|
|
for (i = 0; i < (size_t)node->nChildren(); i++) {
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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 ) )
|
2003-07-31 01:43:57 +00:00
|
|
|
|
{
|
2009-06-03 16:29:58 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
2009-05-29 10:26:35 +00:00
|
|
|
|
if (i > 0)
|
|
|
|
|
b->environment.copy(table[i-1]->environment);
|
|
|
|
|
b->environment.read(child);
|
|
|
|
|
b->altitude_ft = b->environment.get_elevation_ft();
|
2009-06-03 16:29:58 +00:00
|
|
|
|
|
|
|
|
|
// check, if altitudes are in ascending order
|
|
|
|
|
if( b->altitude_ft < last_altitude_ft )
|
|
|
|
|
sort_required = true;
|
|
|
|
|
last_altitude_ft = b->altitude_ft;
|
2009-05-29 10:26:35 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2009-06-03 16:29:58 +00:00
|
|
|
|
// remove leftover buckets
|
2009-06-04 09:59:29 +00:00
|
|
|
|
while( table.size() > i ) {
|
|
|
|
|
bucket * b = *(table.end() - 1);
|
|
|
|
|
delete b;
|
2009-06-03 22:18:58 +00:00
|
|
|
|
table.pop_back();
|
2009-06-04 09:59:29 +00:00
|
|
|
|
}
|
2009-06-03 16:29:58 +00:00
|
|
|
|
|
|
|
|
|
if( sort_required )
|
|
|
|
|
sort(table.begin(), table.end(), bucket::lessThan);
|
2010-07-17 18:49:31 +02:00
|
|
|
|
|
|
|
|
|
// cleanup entries with (almost)same altitude
|
|
|
|
|
for( vector<bucket *>::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 );
|
|
|
|
|
}
|
|
|
|
|
}
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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) {
|
2010-07-17 18:49:31 +02:00
|
|
|
|
// boundary table
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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) {
|
2009-06-03 16:29:58 +00:00
|
|
|
|
//TODO: this is 500ft above the top altitude of boundary layer
|
|
|
|
|
//shouldn't this be +/-250 ft off of the top altitude?
|
2010-07-17 18:49:31 +02:00
|
|
|
|
// both tables
|
2009-05-29 10:26:35 +00:00
|
|
|
|
do_interpolate(_boundary_table, altitude_agl_ft, &env1);
|
|
|
|
|
do_interpolate(_aloft_table, altitude_ft, &env2);
|
2010-07-17 18:49:31 +02:00
|
|
|
|
double fraction = boundary_transition > SGLimitsd::min() ?
|
|
|
|
|
(altitude_agl_ft - boundary_limit) / boundary_transition : 1.0;
|
2009-05-29 10:26:35 +00:00
|
|
|
|
interpolate(&env1, &env2, fraction, _environment);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-17 18:49:31 +02:00
|
|
|
|
// aloft table
|
2009-05-29 10:26:35 +00:00
|
|
|
|
do_interpolate(_aloft_table, altitude_ft, _environment);
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2009-05-29 10:26:35 +00:00
|
|
|
|
FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table, double altitude_ft, FGEnvironment * environment)
|
|
|
|
|
{
|
|
|
|
|
int length = table.size();
|
|
|
|
|
if (length == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2010-07-17 18:49:31 +02:00
|
|
|
|
// Boundary conditions
|
2009-05-29 10:26:35 +00:00
|
|
|
|
if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
|
2010-07-17 18:49:31 +02:00
|
|
|
|
environment->copy(table[0]->environment); // below bottom of table
|
2009-05-29 10:26:35 +00:00
|
|
|
|
return;
|
|
|
|
|
} else if (table[length-1]->altitude_ft <= altitude_ft) {
|
2010-07-17 18:49:31 +02:00
|
|
|
|
environment->copy(table[length-1]->environment); // above top of table
|
2009-05-29 10:26:35 +00:00
|
|
|
|
return;
|
2010-07-17 18:49:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
return (altitude_ft < b.altitude_ft);
|
2003-06-08 14:47:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-08-15 15:22:44 +00:00
|
|
|
|
bool
|
|
|
|
|
FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
|
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
return (a->altitude_ft) < (b->altitude_ft);
|
2007-08-15 15:22:44 +00:00
|
|
|
|
}
|
2003-06-08 14:47:03 +00:00
|
|
|
|
|
2004-02-21 12:56:16 +00:00
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
2009-05-29 10:26:35 +00:00
|
|
|
|
// Implementation of FGMetarCtrl.
|
2004-02-21 12:56:16 +00:00
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
|
2009-08-20 21:53:29 +00:00
|
|
|
|
:
|
2009-05-29 10:26:35 +00:00
|
|
|
|
metar_valid(false),
|
|
|
|
|
setup_winds_aloft(true),
|
2009-06-03 16:29:58 +00:00
|
|
|
|
wind_interpolation_required(true),
|
2010-03-24 14:25:36 +00:00
|
|
|
|
metar_sealevel_temperature(15.0),
|
|
|
|
|
metar_sealevel_dewpoint(5.0),
|
2009-05-29 10:26:35 +00:00
|
|
|
|
// Interpolation constant definitions.
|
|
|
|
|
MaxWindChangeKtsSec( 0.2 ),
|
|
|
|
|
MaxVisChangePercentSec( 0.05 ),
|
2010-03-24 14:25:36 +00:00
|
|
|
|
MaxPressureChangeInHgSec( 0.0005 ), // approx 1hpa/min
|
|
|
|
|
MaxTemperatureChangeDegcSec(10.0/60.0), // approx 10degc/min
|
2009-05-29 10:26:35 +00:00
|
|
|
|
MaxCloudAltitudeChangeFtSec( 20.0 ),
|
|
|
|
|
MaxCloudThicknessChangeFtSec( 50.0 ),
|
|
|
|
|
MaxCloudInterpolationHeightFt( 5000.0 ),
|
2009-08-20 21:53:29 +00:00
|
|
|
|
MaxCloudInterpolationDeltaFt( 4000.0 ),
|
|
|
|
|
_environmentCtrl(environmentCtrl)
|
2009-05-29 10:26:35 +00:00
|
|
|
|
{
|
2009-06-03 16:29:58 +00:00
|
|
|
|
windModulator = new FGBasicWindModulator();
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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 );
|
2009-06-03 16:29:58 +00:00
|
|
|
|
base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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 );
|
2009-06-04 09:59:29 +00:00
|
|
|
|
magnetic_variation_n = fgGetNode( "/environment/magnetic-variation-deg", true );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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");
|
|
|
|
|
|
2010-03-24 14:25:36 +00:00
|
|
|
|
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 );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FGMetarCtrl::~FGMetarCtrl ()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FGMetarCtrl::bind ()
|
|
|
|
|
{
|
2009-06-03 16:29:58 +00:00
|
|
|
|
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 );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FGMetarCtrl::unbind ()
|
|
|
|
|
{
|
2009-06-03 16:29:58 +00:00
|
|
|
|
fgUntie("/environment/metar/valid");
|
|
|
|
|
fgUntie("/environment/params/metar-updates-environment");
|
|
|
|
|
fgUntie("/environment/params/metar-updates-winds-aloft");
|
2004-02-21 12:56:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-02-22 14:21:37 +00:00
|
|
|
|
// use a "command" to set station temp at station elevation
|
2010-03-24 14:25:36 +00:00
|
|
|
|
static void set_temp_at_altitude( double temp_degc, double altitude_ft ) {
|
2009-05-29 10:26:35 +00:00
|
|
|
|
SGPropertyNode args;
|
|
|
|
|
SGPropertyNode *node = args.getNode("temp-degc", 0, true);
|
2010-03-24 14:25:36 +00:00
|
|
|
|
node->setDoubleValue( temp_degc );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
node = args.getNode("altitude-ft", 0, true);
|
2010-03-24 14:25:36 +00:00
|
|
|
|
node->setDoubleValue( altitude_ft );
|
|
|
|
|
globals->get_commands()->execute( altitude_ft == 0.0 ?
|
|
|
|
|
"set-sea-level-air-temp-degc" :
|
|
|
|
|
"set-outside-air-temp-degc", &args);
|
2004-02-22 14:21:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-24 14:25:36 +00:00
|
|
|
|
static void set_dewpoint_at_altitude( double dewpoint_degc, double altitude_ft ) {
|
2009-05-29 10:26:35 +00:00
|
|
|
|
SGPropertyNode args;
|
|
|
|
|
SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
|
2010-03-24 14:25:36 +00:00
|
|
|
|
node->setDoubleValue( dewpoint_degc );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
node = args.getNode("altitude-ft", 0, true);
|
2010-03-24 14:25:36 +00:00
|
|
|
|
node->setDoubleValue( altitude_ft );
|
|
|
|
|
globals->get_commands()->execute( altitude_ft == 0.0 ?
|
|
|
|
|
"set-dewpoint-sea-level-air-temp-degc" :
|
|
|
|
|
"set-dewpoint-temp-degc", &args);
|
2004-02-22 14:21:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
/*
|
|
|
|
|
Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
|
2004-02-22 14:21:37 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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 );
|
|
|
|
|
}
|
2004-02-23 01:39:12 +00:00
|
|
|
|
}
|
2004-02-21 14:04:40 +00:00
|
|
|
|
|
2009-06-05 09:14:22 +00:00
|
|
|
|
static void setupWind( bool setup_aloft, double dir, double speed, double gust )
|
2009-05-29 10:26:35 +00:00
|
|
|
|
{
|
2009-06-05 09:14:22 +00:00
|
|
|
|
setupWindBranch( "boundary", dir, speed, gust );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
if( setup_aloft )
|
|
|
|
|
setupWindBranch( "aloft", dir, speed, gust );
|
2004-02-21 12:56:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-24 14:25:36 +00:00
|
|
|
|
double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dval )
|
2004-02-21 12:56:16 +00:00
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
if (fabs(currentval - requiredval) < dval) return requiredval;
|
|
|
|
|
if (currentval < requiredval) return (currentval + dval);
|
|
|
|
|
if (currentval > requiredval) return (currentval - dval);
|
|
|
|
|
return requiredval;
|
2004-02-21 12:56:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2009-05-29 10:26:35 +00:00
|
|
|
|
FGMetarCtrl::init ()
|
|
|
|
|
{
|
|
|
|
|
first_update = true;
|
2009-06-03 16:29:58 +00:00
|
|
|
|
wind_interpolation_required = true;
|
2004-02-21 14:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
void
|
|
|
|
|
FGMetarCtrl::reinit ()
|
|
|
|
|
{
|
|
|
|
|
init();
|
|
|
|
|
}
|
2004-02-28 19:52:17 +00:00
|
|
|
|
|
2009-06-03 16:29:58 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2009-10-26 06:54:39 +01:00
|
|
|
|
// 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)
|
|
|
|
|
{
|
2010-03-24 14:25:36 +00:00
|
|
|
|
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;
|
2009-10-26 06:54:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
2004-02-21 14:04:40 +00:00
|
|
|
|
void
|
2009-05-29 10:26:35 +00:00
|
|
|
|
FGMetarCtrl::update(double dt)
|
|
|
|
|
{
|
|
|
|
|
if( dt <= 0 || !metar_valid ||!enabled)
|
|
|
|
|
return;
|
|
|
|
|
|
2009-06-03 16:29:58 +00:00
|
|
|
|
windModulator->update(dt);
|
2009-05-29 10:26:35 +00:00
|
|
|
|
// Interpolate the current configuration closer to the actual METAR
|
|
|
|
|
|
|
|
|
|
bool reinit_required = false;
|
|
|
|
|
bool layer_rebuild_required = false;
|
2010-03-24 14:25:36 +00:00
|
|
|
|
double station_elevation_ft = station_elevation_n->getDoubleValue();
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
if (first_update) {
|
2009-06-04 09:59:29 +00:00
|
|
|
|
double dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
|
2009-05-29 10:26:35 +00:00
|
|
|
|
double speed = base_wind_speed_n->getDoubleValue();
|
|
|
|
|
double gust = gust_wind_speed_n->getDoubleValue();
|
2009-06-05 09:14:22 +00:00
|
|
|
|
setupWind(setup_winds_aloft, dir, speed, gust);
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
double metarvis = min_visibility_n->getDoubleValue();
|
|
|
|
|
fgDefaultWeatherValue("visibility-m", metarvis);
|
|
|
|
|
|
2010-03-24 14:25:36 +00:00
|
|
|
|
set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
|
|
|
|
|
set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
double metarpressure = pressure_n->getDoubleValue();
|
2009-10-26 06:54:39 +01:00
|
|
|
|
fgDefaultWeatherValue("pressure-sea-level-inhg",
|
2010-03-24 14:25:36 +00:00
|
|
|
|
reducePressureSl(metarpressure,
|
|
|
|
|
station_elevation_ft,
|
|
|
|
|
temperature_n->getDoubleValue()));
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
// 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 {
|
2009-06-03 16:29:58 +00:00
|
|
|
|
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();
|
2009-06-04 09:59:29 +00:00
|
|
|
|
double metar_heading = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
|
2009-06-03 16:29:58 +00:00
|
|
|
|
|
|
|
|
|
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.
|
2010-03-24 14:25:36 +00:00
|
|
|
|
current[0] = interpolate_val(current[0], metar[0], maxdx*dt);
|
|
|
|
|
current[1] = interpolate_val(current[1], metar[1], maxdy*dt);
|
2009-06-03 16:29:58 +00:00
|
|
|
|
|
|
|
|
|
// Now convert back to polar coordinates.
|
2009-06-05 09:14:22 +00:00
|
|
|
|
if ((fabs(current[0]) > 0.1) || (fabs(current[1]) > 0.1)) {
|
2009-06-03 16:29:58 +00:00
|
|
|
|
// 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);
|
2009-06-05 09:14:22 +00:00
|
|
|
|
} else {
|
|
|
|
|
// Special case where there is no wind (otherwise atan2 barfs)
|
|
|
|
|
speed = 0.0;
|
2009-06-03 16:29:58 +00:00
|
|
|
|
}
|
|
|
|
|
double gust = gust_wind_speed_n->getDoubleValue();
|
2009-06-05 09:14:22 +00:00
|
|
|
|
setupWind(setup_winds_aloft, dir_from, speed, gust);
|
2009-06-03 16:29:58 +00:00
|
|
|
|
reinit_required = true;
|
|
|
|
|
} else {
|
|
|
|
|
wind_interpolation_required = false;
|
2009-05-29 10:26:35 +00:00
|
|
|
|
}
|
2009-06-03 16:29:58 +00:00
|
|
|
|
} else { // if(wind_interpolation_required)
|
|
|
|
|
// interpolation of wind vector is finished, apply wind
|
|
|
|
|
// variations and gusts for the boundary layer only
|
|
|
|
|
|
2009-06-05 09:14:22 +00:00
|
|
|
|
|
|
|
|
|
bool wind_modulated = false;
|
|
|
|
|
|
2009-06-03 16:29:58 +00:00
|
|
|
|
// start with the main wind direction
|
2009-06-04 09:59:29 +00:00
|
|
|
|
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());
|
2009-06-03 16:29:58 +00:00
|
|
|
|
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 ));
|
2009-06-05 09:14:22 +00:00
|
|
|
|
wind_modulated = true;
|
2009-06-03 16:29:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 );
|
2009-06-05 09:14:22 +00:00
|
|
|
|
wind_modulated = true;
|
|
|
|
|
}
|
|
|
|
|
if( wind_modulated ) {
|
|
|
|
|
setupWind(false, wind_dir, wind_speed, max);
|
|
|
|
|
reinit_required = true;
|
2009-06-03 16:29:58 +00:00
|
|
|
|
}
|
2009-05-29 10:26:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2010-03-24 14:25:36 +00:00
|
|
|
|
currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec*dt);
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
// 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();
|
2010-03-24 14:25:36 +00:00
|
|
|
|
double newpressure = reducePressureSl(metarpressure,
|
|
|
|
|
station_elevation_ft,
|
|
|
|
|
temperature_n->getDoubleValue());
|
2009-10-26 06:54:39 +01:00
|
|
|
|
if( pressure != newpressure ) {
|
2010-03-24 14:25:36 +00:00
|
|
|
|
pressure = interpolate_val( pressure, newpressure, MaxPressureChangeInHgSec*dt );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
|
|
|
|
|
reinit_required = true;
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-24 14:25:36 +00:00
|
|
|
|
{
|
|
|
|
|
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 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
// 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) {
|
2010-03-24 14:25:36 +00:00
|
|
|
|
current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec*dt);
|
2009-05-29 10:26:35 +00:00
|
|
|
|
target->setDoubleValue("elevation-ft", current_alt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double current_thickness = thickness->getDoubleValue();
|
|
|
|
|
|
|
|
|
|
if (current_thickness != required_thickness) {
|
|
|
|
|
current_thickness = interpolate_val(current_thickness,
|
|
|
|
|
required_thickness,
|
2010-03-24 14:25:36 +00:00
|
|
|
|
MaxCloudThicknessChangeFtSec*dt);
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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<FGMetar> m;
|
|
|
|
|
try {
|
|
|
|
|
m = new FGMetar( metar_string );
|
|
|
|
|
}
|
|
|
|
|
catch( sg_io_exception ) {
|
2010-07-17 18:49:31 +02:00
|
|
|
|
SG_LOG( SG_GENERAL, SG_WARN, "Can't get metar: " << metar_string );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
metar_valid = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-03 16:29:58 +00:00
|
|
|
|
wind_interpolation_required = true;
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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 );
|
|
|
|
|
|
2010-03-24 14:25:36 +00:00
|
|
|
|
{ // 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();
|
|
|
|
|
}
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2005-11-22 17:02:31 +00:00
|
|
|
|
#if defined(ENABLE_THREADS)
|
2009-05-29 10:26:35 +00:00
|
|
|
|
/**
|
|
|
|
|
* 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() {}
|
2004-08-21 11:43:48 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
/**
|
|
|
|
|
* Fetche the metar data from the NOAA.
|
|
|
|
|
*/
|
|
|
|
|
void run();
|
2004-02-26 10:23:48 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
private:
|
|
|
|
|
FGMetarFetcher * metar_fetcher;
|
|
|
|
|
};
|
2004-08-21 11:43:48 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
void MetarThread::run()
|
|
|
|
|
{
|
|
|
|
|
for( ;; ) {
|
|
|
|
|
string airport_id = metar_fetcher->request_queue.pop();
|
2004-02-21 12:56:16 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
if( airport_id.size() == 0 )
|
|
|
|
|
break;
|
2004-02-21 12:56:16 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
if( metar_fetcher->_error_count > 3 ) {
|
|
|
|
|
SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-22 16:54:58 +00:00
|
|
|
|
metar_fetcher->fetch( airport_id );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2009-06-03 16:29:58 +00:00
|
|
|
|
FGMetarFetcher::FGMetarFetcher() :
|
2009-05-29 10:26:35 +00:00
|
|
|
|
#if defined(ENABLE_THREADS)
|
|
|
|
|
metar_thread(NULL),
|
|
|
|
|
#endif
|
|
|
|
|
fetch_timer(0.0),
|
|
|
|
|
search_timer(0.0),
|
|
|
|
|
error_timer(0.0),
|
|
|
|
|
_stale_count(0),
|
2009-06-08 19:39:37 +00:00
|
|
|
|
_error_count(0),
|
|
|
|
|
enabled(false)
|
2009-05-29 10:26:35 +00:00
|
|
|
|
{
|
|
|
|
|
longitude_n = fgGetNode( "/position/longitude-deg", true );
|
|
|
|
|
latitude_n = fgGetNode( "/position/latitude-deg", true );
|
2009-06-03 16:29:58 +00:00
|
|
|
|
enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
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);
|
2009-06-03 16:29:58 +00:00
|
|
|
|
max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
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
|
2004-02-21 12:56:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-02-28 19:52:17 +00:00
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
FGMetarFetcher::~FGMetarFetcher()
|
|
|
|
|
{
|
2005-11-22 17:02:31 +00:00
|
|
|
|
#if defined(ENABLE_THREADS)
|
2009-05-29 10:26:35 +00:00
|
|
|
|
request_queue.push("");
|
|
|
|
|
metar_thread->join();
|
|
|
|
|
delete metar_thread;
|
|
|
|
|
#endif // ENABLE_THREADS
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FGMetarFetcher::init ()
|
2006-03-07 10:26:25 +00:00
|
|
|
|
{
|
2009-05-29 10:26:35 +00:00
|
|
|
|
fetch_timer = 0.0;
|
|
|
|
|
search_timer = 0.0;
|
|
|
|
|
error_timer = 0.0;
|
|
|
|
|
_stale_count = 0;
|
|
|
|
|
_error_count = 0;
|
|
|
|
|
current_airport_id.clear();
|
2009-06-23 20:23:37 +00:00
|
|
|
|
/* 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 ) {
|
2009-07-22 16:54:58 +00:00
|
|
|
|
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 );
|
|
|
|
|
}
|
2009-06-23 20:23:37 +00:00
|
|
|
|
}
|
2006-03-07 10:26:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
void FGMetarFetcher::reinit ()
|
|
|
|
|
{
|
|
|
|
|
init();
|
2004-02-25 15:31:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-08 19:39:37 +00:00
|
|
|
|
if( enable_n->getBoolValue() == false ) {
|
|
|
|
|
enabled = false;
|
2009-05-29 10:26:35 +00:00
|
|
|
|
return;
|
2009-06-08 19:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
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 )
|
|
|
|
|
{
|
2009-06-23 20:23:37 +00:00
|
|
|
|
if( enable_n->getBoolValue() == false )
|
|
|
|
|
return;
|
|
|
|
|
|
2009-05-29 10:26:35 +00:00
|
|
|
|
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;
|
2009-06-23 20:23:37 +00:00
|
|
|
|
}
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|
|
|
|
|
} catch (const sg_io_exception& e) {
|
|
|
|
|
SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
|
|
|
|
|
result = NULL;
|
2009-07-22 16:54:58 +00:00
|
|
|
|
// 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;
|
2009-05-29 10:26:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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() );
|
|
|
|
|
}
|
2004-02-21 12:56:16 +00:00
|
|
|
|
|
2002-05-07 00:03:54 +00:00
|
|
|
|
// end of environment_ctrl.cxx
|
2009-05-29 10:26:35 +00:00
|
|
|
|
|