#endif
#include <algorithm>
+#include <cstring>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/commands.hxx>
#include <Main/fg_props.hxx>
#include <Main/util.hxx>
+#include "atmosphere.hxx"
#include "fgmetar.hxx"
#include "environment_ctrl.hxx"
virtual bool passAirport(FGAirport* aApt) const {
return aApt->getMetar();
}
+
+ // permit heliports and seaports too
+ virtual FGPositioned::Type maxType() const
+ { return FGPositioned::SEAPORT; }
};
static AirportWithMetar airportWithMetarFilter;
FGInterpolateEnvironmentCtrl::init ()
{
read_table( boundary_n, _boundary_table);
- read_table( aloft_n, _aloft_table);
+ // pass in a pointer to the environment of the last bondary layer as
+ // a starting point
+ read_table( aloft_n, _aloft_table, &(*(_boundary_table.end()-1))->environment);
}
void
}
void
-FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector<bucket *> &table)
+FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector<bucket *> &table, FGEnvironment * parent )
{
double last_altitude_ft = 0.0;
double sort_required = false;
- int i;
+ size_t i;
- for (i = 0; i < node->nChildren(); 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'
b = new bucket;
table.push_back(b);
}
+ if (i == 0 && parent != NULL )
+ b->environment.copy( *parent );
if (i > 0)
b->environment.copy(table[i-1]->environment);
+
b->environment.read(child);
b->altitude_ft = b->environment.get_elevation_ft();
if( sort_required )
sort(table.begin(), table.end(), bucket::lessThan);
+
+ // 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 );
+ }
+ }
}
void
int length = _boundary_table.size();
if (length > 0) {
- // boundary table
+ // 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);
} 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
+ // 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;
+ double fraction = boundary_transition > SGLimitsd::min() ?
+ (altitude_agl_ft - boundary_limit) / boundary_transition : 1.0;
interpolate(&env1, &env2, fraction, _environment);
return;
}
}
- // aloft table
+ // aloft table
do_interpolate(_aloft_table, altitude_ft, _environment);
}
if (length == 0)
return;
- // Boundary conditions
+ // Boundary conditions
if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
- environment->copy(table[0]->environment);
+ environment->copy(table[0]->environment); // below bottom of table
return;
} else if (table[length-1]->altitude_ft <= altitude_ft) {
- environment->copy(table[length-1]->environment);
+ environment->copy(table[length-1]->environment); // above top of table
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;
- }
- }
+ }
+
+ // Search the interpolation table
+ int layer;
+ for ( layer = 1; // can't be below bottom layer, handled above
+ layer < length && table[layer]->altitude_ft <= altitude_ft;
+ layer++);
+ FGEnvironment * env1 = &(table[layer-1]->environment);
+ FGEnvironment * env2 = &(table[layer]->environment);
+ // two layers of same altitude were sorted out in read_table
+ double fraction = ((altitude_ft - table[layer-1]->altitude_ft) /
+ (table[layer]->altitude_ft - table[layer-1]->altitude_ft));
+ interpolate(env1, env2, fraction, environment);
}
bool
////////////////////////////////////////////////////////////////////////
FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
- : _environmentCtrl(environmentCtrl),
- station_elevation_ft(0.0),
+ :
metar_valid(false),
setup_winds_aloft(true),
wind_interpolation_required(true),
+ metar_sealevel_temperature(15.0),
+ metar_sealevel_dewpoint(5.0),
// Interpolation constant definitions.
- EnvironmentUpdatePeriodSec( 0.2 ),
MaxWindChangeKtsSec( 0.2 ),
MaxVisChangePercentSec( 0.05 ),
- MaxPressureChangeInHgSec( 0.0033 ),
+ 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 )
+ MaxCloudInterpolationDeltaFt( 4000.0 ),
+ _environmentCtrl(environmentCtrl)
{
windModulator = new FGBasicWindModulator();
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");
+ 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 ()
}
// use a "command" to set station temp at station elevation
-static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
+static void set_temp_at_altitude( double temp_degc, double altitude_ft ) {
SGPropertyNode args;
SGPropertyNode *node = args.getNode("temp-degc", 0, true);
- node->setFloatValue( temp_degc );
+ node->setDoubleValue( temp_degc );
node = args.getNode("altitude-ft", 0, true);
- node->setFloatValue( altitude_ft );
- globals->get_commands()->execute("set-outside-air-temp-degc", &args);
+ 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( float dewpoint_degc, float altitude_ft ) {
+static void set_dewpoint_at_altitude( double dewpoint_degc, double altitude_ft ) {
SGPropertyNode args;
SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
- node->setFloatValue( dewpoint_degc );
+ node->setDoubleValue( dewpoint_degc );
node = args.getNode("altitude-ft", 0, true);
- node->setFloatValue( altitude_ft );
- globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
+ node->setDoubleValue( altitude_ft );
+ globals->get_commands()->execute( altitude_ft == 0.0 ?
+ "set-dewpoint-sea-level-air-temp-degc" :
+ "set-dewpoint-temp-degc", &args);
}
/*
setupWindBranch( "aloft", dir, speed, gust );
}
-double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
+double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dval )
{
- double dval = EnvironmentUpdatePeriodSec * dt;
-
if (fabs(currentval - requiredval) < dval) return requiredval;
if (currentval < requiredval) return (currentval + dval);
if (currentval > requiredval) return (currentval - dval);
return d > 180.0 ? d - 360.0 : d;
}
+// Return the sea level pressure for a metar observation, in inHg.
+// This is different from QNH because it accounts for the current
+// temperature at the observation point.
+// metarPressure in inHg
+// fieldHt in ft
+// fieldTemp in C
+
+static double reducePressureSl(double metarPressure, double fieldHt,
+ double fieldTemp)
+{
+ double elev = fieldHt * SG_FEET_TO_METER;
+ double fieldPressure
+ = FGAtmo::fieldPressure(elev, metarPressure * atmodel::inHg);
+ double slPressure = P_layer(0, elev, fieldPressure,
+ fieldTemp + atmodel::freezing, atmodel::ISA::lam0);
+ return slPressure / atmodel::inHg;
+}
+
void
FGMetarCtrl::update(double dt)
{
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 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", metarpressure);
+ 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<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
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);
+ 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)) {
double currentxval = log(1000.0 + vis);
double metarxval = log(1000.0 + metarvis);
- currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
+ currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec*dt);
// Now convert back from an X-value to a straightforward visibility.
vis = exp(currentxval) - 1000.0;
double pressure = boundary_sea_level_pressure_n->getDoubleValue();
double metarpressure = pressure_n->getDoubleValue();
- if( pressure != metarpressure ) {
- pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
+ 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<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
vector<SGPropertyNode_ptr>::const_iterator layer;
} else {
// Interpolate the other values in the usual way
if (current_alt != required_alt) {
- current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
+ current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec*dt);
target->setDoubleValue("elevation-ft", current_alt);
}
if (current_thickness != required_thickness) {
current_thickness = interpolate_val(current_thickness,
required_thickness,
- MaxCloudThicknessChangeFtSec);
+ MaxCloudThicknessChangeFtSec*dt);
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 );
m = new FGMetar( metar_string );
}
catch( sg_io_exception ) {
- fprintf( stderr, "can't get metar: %s\n", metar_string );
+ SG_LOG( SG_GENERAL, SG_WARN, "Can't get metar: " << metar_string );
metar_valid = false;
return;
}
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<SGMetarCloud> cv = m->getClouds();
vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
break;
}
- metar_fetcher->fetch( airport_id );
+ metar_fetcher->fetch( airport_id );
}
}
#endif
search_timer(0.0),
error_timer(0.0),
_stale_count(0),
- _error_count(0)
+ _error_count(0),
+ enabled(false)
{
longitude_n = fgGetNode( "/position/longitude-deg", true );
latitude_n = fgGetNode( "/position/latitude-deg", true );
_stale_count = 0;
_error_count = 0;
current_airport_id.clear();
+ /* Torsten Dreyer:
+ hack to stop startup.nas complaining if metar arrives after nasal-dir-initialized
+ is fired. Immediately fetch and wait for the METAR before continuing. This gets the
+ /environment/metar/xxx properties filled before nasal-dir is initialized.
+ Maybe the runway selection should happen here to make startup.nas obsolete?
+ */
+ const char * startup_airport = fgGetString("/sim/startup/options/airport");
+ if( *startup_airport ) {
+ FGAirport * a = FGAirport::getByIdent( startup_airport );
+ if( a ) {
+ SGGeod pos = SGGeod::fromDeg(a->getLongitude(), a->getLatitude());
+ a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
+ current_airport_id = a->getId();
+ fetch( current_airport_id );
+ }
+ }
}
void FGMetarFetcher::reinit ()
_error_count = 0;
}
- if( enable_n->getBoolValue() == false )
+ if( enable_n->getBoolValue() == false ) {
+ enabled = false;
return;
+ }
+
+ // we were just enabled, reset all timers to
+ // trigger immediate metar fetch
+ if( !enabled ) {
+ search_timer = 0.0;
+ fetch_timer = 0.0;
+ error_timer = error_timer_sec;
+ enabled = true;
+ }
FGAirport * a = NULL;
void FGMetarFetcher::fetch( const string & id )
{
+ if( enable_n->getBoolValue() == false )
+ return;
+
SGSharedPtr<FGMetar> result = NULL;
// fetch current metar data
}
} 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