From e1019eb359544e10811acdad6a7d61134b6af366 Mon Sep 17 00:00:00 2001 From: mfranz Date: Fri, 3 Aug 2007 12:06:17 +0000 Subject: [PATCH] Stuart BUCHANAN: "interpolates METAR changes over time as follows: 1) Wind changes are interpolated as vectors, so the change is much more natural than before - no-longer will your aircraft be suddenly shoved to one side. 2) Visibility is interpolated as an X-value, which looks pretty neat. 3) Cloud heights and thicknesses are interpolated if they are close to the aircraft. Cloud textures are not." --- src/Environment/environment_ctrl.cxx | 273 +++++++++++++++++++++++++-- src/Environment/environment_ctrl.hxx | 14 ++ 2 files changed, 270 insertions(+), 17 deletions(-) diff --git a/src/Environment/environment_ctrl.cxx b/src/Environment/environment_ctrl.cxx index b6e8db586..aa777d288 100644 --- a/src/Environment/environment_ctrl.cxx +++ b/src/Environment/environment_ctrl.cxx @@ -24,11 +24,11 @@ # include "config.h" #endif -#include - #include +#include #include +#include #include #include @@ -321,19 +321,30 @@ FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl () : env( new FGInterpolateEnvironmentCtrl ), _icao( "" ), + metar_loaded( false ), search_interval_sec( 60.0 ), // 1 minute same_station_interval_sec( 900.0 ), // 15 minutes search_elapsed( 9999.0 ), fetch_elapsed( 9999.0 ), + last_apt( 0 ), proxy_host( fgGetNode("/sim/presets/proxy/host", true) ), proxy_port( fgGetNode("/sim/presets/proxy/port", true) ), proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ), metar_max_age( fgGetNode("/environment/params/metar-max-age-min", 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 ), + _error_count( 0 ), _stale_count( 0 ), _dt( 0.0 ), - _error_dt( 0.0 ), - last_apt(0) + _error_dt( 0.0 ) { #if defined(ENABLE_THREADS) thread = new MetarThread(this); @@ -376,19 +387,240 @@ static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) { void FGMetarEnvironmentCtrl::update_env_config () { - fgSetupWind( fgGetDouble("/environment/metar/base-wind-range-from"), - fgGetDouble("/environment/metar/base-wind-range-to"), - fgGetDouble("/environment/metar/base-wind-speed-kt"), - fgGetDouble("/environment/metar/gust-wind-speed-kt") ); + double dir_from; + double dir_to; + double speed; + double gust; + double vis; + double pressure; + double temp; + double dewpoint; + + if (metar_loaded) { + // 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 = fgGetDouble("/environment/metar/base-wind-speed-kt"); + double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from"); + + metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI); + metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI); + + // Convert the current wind values and convert them into a vector + double current[2]; + double current_speed = + fgGetDouble("/environment/config/boundary/entry/wind-speed-kt"); + double current_heading = fgGetDouble( + "/environment/config/boundary/entry/wind-from-heading-deg"); + + current[0] = current_speed * sin((current_heading / 180.0) * M_PI); + current[1] = current_speed * cos((current_heading / 180.0) * M_PI); + + // 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]); + 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; + dir_from = current_heading; + + } 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]) * 180.0 / M_PI); + + // Normalize the direction. + if (dir_from < 0.0) + dir_from += 360.0; + + SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed); + } + + // 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 currentvis = + fgGetDouble("/environment/config/boundary/entry/visibility-m"); + double metarvis = fgGetDouble("/environment/metar/min-visibility-m"); + double currentxval = log(1000.0 + currentvis); + 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; + + pressure = interpolate_prop( + "/environment/config/boundary/entry/pressure-sea-level-inhg", + "/environment/metar/pressure-inhg", + MaxPressureChangeInHgSec); + + dir_to = fgGetDouble("/environment/metar/base-wind-range-to"); + gust = fgGetDouble("/environment/metar/gust-wind-speed-kt"); + temp = fgGetDouble("/environment/metar/temperature-degc"); + dewpoint = fgGetDouble("/environment/metar/dewpoint-degc"); + + // Set the cloud layers by interpolating over the METAR versions. + SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds"); + + vector layers = clouds->getChildren("layer"); + vector::const_iterator layer; + vector::const_iterator layers_end = layers.end(); + + const char *cl = "/environment/clouds/layer[%i]"; + double aircraft_alt = fgGetDouble("/position/altitude-ft"); + char s[128]; + int i; + + for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) { + double currentval; + double requiredval; + + // 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. + snprintf(s, 128, cl, i); + strncat(s, "/coverage", 128); + const char* coverage = (*layer)->getStringValue("coverage", "clear"); + if (strncmp(fgGetString(s), coverage, 128) != 0) + fgSetString(s, coverage); + + snprintf(s, 128, cl, i); + strncat(s, "/elevation-ft", 128); + double current_alt = fgGetDouble(s); + double required_alt = (*layer)->getDoubleValue("elevation-ft"); + + if (current_alt < -9000 || required_alt < -9000 + || fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt) { + // We don't interpolate any values that are too high above us, + // or too far below us to be visible. Nor do we interpolate + // values to or from -9999, which is used as a placeholder + // when there isn't actually a cloud layer present. + snprintf(s, 128, cl, i); + strncat(s, "/elevation-ft", 128); + if (current_alt != required_alt) + fgSetDouble(s, required_alt); + + snprintf(s, 128, cl, i); + strncat(s, "/thickness-ft", 128); + if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft")) + fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft")); + + } else { + // Interpolate the other values in the usual way + if (current_alt != required_alt) { + current_alt = interpolate_val(current_alt, + required_alt, + MaxCloudAltitudeChangeFtSec); + fgSetDouble(s, current_alt); + } + + snprintf(s, 128, cl, i); + strncat(s, "/thickness-ft", 128); + currentval = fgGetDouble(s); + requiredval = (*layer)->getDoubleValue("thickness-ft"); + + if (currentval != requiredval) { + currentval = interpolate_val(currentval, + requiredval, + MaxCloudThicknessChangeFtSec); + fgSetDouble(s, currentval); + } + } + } + + } else { + // We haven't already loaded a METAR, so apply it immediately. + dir_from = fgGetDouble("/environment/metar/base-wind-range-from"); + dir_to = fgGetDouble("/environment/metar/base-wind-range-to"); + speed = fgGetDouble("/environment/metar/base-wind-speed-kt"); + gust = fgGetDouble("/environment/metar/gust-wind-speed-kt"); + vis = fgGetDouble("/environment/metar/min-visibility-m"); + pressure = fgGetDouble("/environment/metar/pressure-inhg"); + temp = fgGetDouble("/environment/metar/temperature-degc"); + dewpoint = fgGetDouble("/environment/metar/dewpoint-degc"); + + // Set the cloud layers by copying over the METAR versions. + SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds"); + + vector layers = clouds->getChildren("layer"); + vector::const_iterator layer; + vector::const_iterator layers_end = layers.end(); + + const char *cl = "/environment/clouds/layer[%i]"; + char s[128]; + int i; + + for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) { + snprintf(s, 128, cl, i); + strncat(s, "/coverage", 128); + fgSetString(s, (*layer)->getStringValue("coverage", "clear")); + + snprintf(s, 128, cl, i); + strncat(s, "/elevation-ft", 128); + fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft")); + + snprintf(s, 128, cl, i); + strncat(s, "/thickness-ft", 128); + fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft")); + + snprintf(s, 128, cl, i); + strncat(s, "/span-m", 128); + fgSetDouble(s, 40000.0); + } + } + + fgSetupWind(dir_from, dir_to, speed, gust); + fgDefaultWeatherValue("visibility-m", vis); + set_temp_at_altitude(temp, station_elevation_ft); + set_dewpoint_at_altitude(dewpoint, station_elevation_ft); + fgDefaultWeatherValue("pressure-sea-level-inhg", pressure); + + // We've now successfully loaded a METAR into the configuration + metar_loaded = true; +} + +double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname, + const char * requiredname, + double dt) +{ + double currentval = fgGetDouble(currentname); + double requiredval = fgGetDouble(requiredname); + return interpolate_val(currentval, requiredval, dt); +} + +double FGMetarEnvironmentCtrl::interpolate_val(double currentval, + double requiredval, + double dt) +{ + double dval = EnvironmentUpdatePeriodSec * dt; - fgDefaultWeatherValue( "visibility-m", - fgGetDouble("/environment/metar/min-visibility-m") ); - set_temp_at_altitude( fgGetDouble("/environment/metar/temperature-degc"), - station_elevation_ft ); - set_dewpoint_at_altitude( fgGetDouble("/environment/metar/dewpoint-degc"), - station_elevation_ft ); - fgDefaultWeatherValue( "pressure-sea-level-inhg", - fgGetDouble("/environment/metar/pressure-inhg") ); + if (fabs(currentval - requiredval) < dval) return requiredval; + if (currentval < requiredval) return (currentval + dval); + if (currentval > requiredval) return (currentval - dval); + return requiredval; } void @@ -399,6 +631,7 @@ FGMetarEnvironmentCtrl::init () const SGPropertyNode *latitude = fgGetNode( "/position/latitude-deg", true ); + metar_loaded = false; bool found_metar = false; long max_age = metar_max_age->getLongValue(); // Don't check max age during init so that we don't loop over a lot @@ -465,6 +698,7 @@ FGMetarEnvironmentCtrl::update(double delta_time_sec) = fgGetNode( "/position/latitude-deg", true ); search_elapsed += delta_time_sec; fetch_elapsed += delta_time_sec; + interpolate_elapsed += delta_time_sec; // if time for a new search request, push it onto the request // queue @@ -493,6 +727,11 @@ FGMetarEnvironmentCtrl::update(double delta_time_sec) SG_LOG( SG_GENERAL, SG_WARN, "Unable to find any airports with metar" ); } + } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) { + // Interpolate the current configuration closer to the actual METAR + update_env_config(); + env->reinit(); + interpolate_elapsed = 0.0; } #if !defined(ENABLE_THREADS) @@ -654,7 +893,7 @@ FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m ) vector cv = m->getClouds(); vector::const_iterator cloud; - const char *cl = "/environment/clouds/layer[%i]"; + const char *cl = "/environment/metar/clouds/layer[%i]"; for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) { const char *coverage_string[5] = { "clear", "few", "scattered", "broken", "overcast" }; diff --git a/src/Environment/environment_ctrl.hxx b/src/Environment/environment_ctrl.hxx index ffede337c..d82de9da1 100644 --- a/src/Environment/environment_ctrl.hxx +++ b/src/Environment/environment_ctrl.hxx @@ -173,11 +173,13 @@ private: FGInterpolateEnvironmentCtrl *env; string _icao; + bool metar_loaded; float station_elevation_ft; float search_interval_sec; float same_station_interval_sec; float search_elapsed; float fetch_elapsed; + float interpolate_elapsed; const FGAirport *last_apt; SGPropertyNode_ptr proxy_host; SGPropertyNode_ptr proxy_port; @@ -187,6 +189,18 @@ private: FGMetarResult fetch_data( const string &icao ); virtual void update_metar_properties( const FGMetar *m ); void update_env_config(); + double interpolate_prop(const char * currentname, const char * requiredname, double dvalue); + double interpolate_val(double currentval, double requiredval, double dvalue); + const double EnvironmentUpdatePeriodSec; // Seconds between interpolations + const double MaxWindChangeKtsSec; // Max wind change in kts/sec + const double MaxVisChangePercentSec; // Max visibility change in %/sec + const double MaxPressureChangeInHgSec; // Max pressure change in InHg/sec + const double MaxCloudAltitudeChangeFtSec; // Max cloud altitude change in ft/s + const double MaxCloudThicknessChangeFtSec; // Max cloud thickness change in ft/s + const double MaxCloudInterpolationHeightFt; // Max distance from aircraft to + // interpolate at. Any cloud + // changes above this height + // difference are not interpolated private: -- 2.39.5