+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()+magnetic_variation_n->getDoubleValue();
+ double speed = base_wind_speed_n->getDoubleValue();
+ double gust = gust_wind_speed_n->getDoubleValue();
+ setupWind(setup_winds_aloft, dir, speed, gust);
+
+ double metarvis = min_visibility_n->getDoubleValue();
+ fgDefaultWeatherValue("visibility-m", metarvis);
+
+ 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()+magnetic_variation_n->getDoubleValue();
+
+ metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
+ metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
+
+ // Convert the current wind values and convert them into a vector
+ double current[2];
+ double speed = boundary_wind_speed_n->getDoubleValue();
+ double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
+
+ current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
+ current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
+
+ // Determine the maximum component-wise value that the wind can change.
+ // First we determine the fraction in the X and Y component, then
+ // factor by the maximum wind change.
+ double x = fabs(current[0] - metar[0]);
+ double y = fabs(current[1] - metar[1]);
+
+ // only interpolate if we have a difference
+ if (x + y > 0.01 ) {
+ double dx = x / (x + y);
+ double dy = 1 - dx;
+
+ double maxdx = dx * MaxWindChangeKtsSec;
+ double maxdy = dy * MaxWindChangeKtsSec;
+
+ // Interpolate each component separately.
+ current[0] = interpolate_val(current[0], metar[0], maxdx);
+ current[1] = interpolate_val(current[1], metar[1], maxdy);
+
+ // Now convert back to polar coordinates.
+ if ((fabs(current[0]) > 0.1) || (fabs(current[1]) > 0.1)) {
+ // Some real wind to convert back from. Work out the speed
+ // and direction value in degrees.
+ speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
+ dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
+
+ // Normalize the direction.
+ if (dir_from < 0.0)
+ dir_from += 360.0;
+
+ SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
+ } else {
+ // Special case where there is no wind (otherwise atan2 barfs)
+ speed = 0.0;
+ }
+ double gust = gust_wind_speed_n->getDoubleValue();
+ setupWind(setup_winds_aloft, dir_from, speed, gust);
+ reinit_required = true;
+ } else {
+ wind_interpolation_required = false;
+ }
+ } else { // if(wind_interpolation_required)
+ // interpolation of wind vector is finished, apply wind
+ // variations and gusts for the boundary layer only
+
+
+ bool wind_modulated = false;
+
+ // start with the main wind direction
+ double wind_dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
+ double min = convert_to_180(base_wind_range_from_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
+ double max = convert_to_180(base_wind_range_to_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
+ if( max > min ) {
+ // if variable winds configured, modulate the wind direction
+ double f = windModulator->get_direction_offset_norm();
+ wind_dir = min+(max-min)*f;
+ double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
+ wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
+ wind_modulated = true;
+ }
+
+ // start with main wind speed
+ double wind_speed = base_wind_speed_n->getDoubleValue();
+ max = gust_wind_speed_n->getDoubleValue();
+ if( max > wind_speed ) {
+ // if gusts are configured, modulate wind magnitude
+ double f = windModulator->get_magnitude_factor_norm();
+ wind_speed = wind_speed+(max-wind_speed)*f;
+ wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
+ wind_modulated = true;
+ }
+ if( wind_modulated ) {
+ setupWind(false, wind_dir, wind_speed, max);
+ reinit_required = true;
+ }
+ }
+
+ // Now handle the visibility. We convert both visibility values
+ // to X-values, then interpolate from there, then back to real values.
+ // The length_scale is fixed to 1000m, so the visibility changes by
+ // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
+ // whichever is more.
+ double vis = boundary_visibility_n->getDoubleValue();;
+ double metarvis = min_visibility_n->getDoubleValue();
+ if( vis != metarvis ) {
+ double currentxval = log(1000.0 + vis);
+ double metarxval = log(1000.0 + metarvis);
+
+ currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
+
+ // 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);
+ }
+ }
+ }
+ }
+ {
+ double station_elevation_ft = station_elevation_n->getDoubleValue();
+ 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 );
+ }
+}