1 // environment_ctrl.cxx -- manager for natural environment information.
3 // Written by David Megginson, started February 2002.
5 // Copyright (C) 2002 David Megginson - david@megginson.com
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <simgear/debug/logstream.hxx>
30 #include <simgear/structure/commands.hxx>
31 #include <simgear/structure/exception.hxx>
33 #include <Airports/simple.hxx>
34 #include <Main/fg_props.hxx>
35 #include <Main/util.hxx>
37 #include "fgmetar.hxx"
38 #include "environment_ctrl.hxx"
42 class AirportWithMetar : public FGAirport::AirportFilter {
44 virtual bool passAirport(FGAirport* aApt) const {
45 return aApt->getMetar();
49 static AirportWithMetar airportWithMetarFilter;
51 ////////////////////////////////////////////////////////////////////////
52 // Implementation of FGEnvironmentCtrl abstract base class.
53 ////////////////////////////////////////////////////////////////////////
55 FGEnvironmentCtrl::FGEnvironmentCtrl ()
63 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
68 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
70 _environment = environment;
74 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
80 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
86 FGEnvironmentCtrl::setElevationFt (double elev_ft)
92 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
101 ////////////////////////////////////////////////////////////////////////
102 // Implementation of FGInterpolateEnvironmentCtrl.
103 ////////////////////////////////////////////////////////////////////////
106 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
108 altitude_n = fgGetNode("/position/altitude-ft", true);
109 altitude_agl_n = fgGetNode("/position/altitude-agl-ft", true);
110 boundary_transition_n = fgGetNode("/environment/config/boundary-transition-ft", false );
111 boundary_n = fgGetNode("/environment/config/boundary", true );
112 aloft_n = fgGetNode("/environment/config/aloft", true );
115 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
118 for (i = 0; i < _boundary_table.size(); i++)
119 delete _boundary_table[i];
120 for (i = 0; i < _aloft_table.size(); i++)
121 delete _aloft_table[i];
127 FGInterpolateEnvironmentCtrl::init ()
129 read_table( boundary_n, _boundary_table);
130 read_table( aloft_n, _aloft_table);
134 FGInterpolateEnvironmentCtrl::reinit ()
136 // TODO: do we really need to throw away the old tables on reinit? Better recycle
138 for (i = 0; i < _boundary_table.size(); i++)
139 delete _boundary_table[i];
140 for (i = 0; i < _aloft_table.size(); i++)
141 delete _aloft_table[i];
142 _boundary_table.clear();
143 _aloft_table.clear();
148 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector<bucket *> &table)
150 for (int i = 0; i < node->nChildren(); i++) {
151 const SGPropertyNode * child = node->getChild(i);
152 if ( strcmp(child->getName(), "entry") == 0
153 && child->getStringValue("elevation-ft", "")[0] != '\0'
154 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
156 bucket * b = new bucket;
158 b->environment.copy(table[i-1]->environment);
159 b->environment.read(child);
160 b->altitude_ft = b->environment.get_elevation_ft();
164 sort(table.begin(), table.end(), bucket::lessThan);
168 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
170 double altitude_ft = altitude_n->getDoubleValue();
171 double altitude_agl_ft = altitude_agl_n->getDoubleValue();
172 double boundary_transition =
173 boundary_transition_n == NULL ? 500 : boundary_transition_n->getDoubleValue();
175 int length = _boundary_table.size();
179 double boundary_limit = _boundary_table[length-1]->altitude_ft;
180 if (boundary_limit >= altitude_agl_ft) {
181 do_interpolate(_boundary_table, altitude_agl_ft, _environment);
183 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
185 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
186 do_interpolate(_aloft_table, altitude_ft, &env2);
188 (altitude_agl_ft - boundary_limit) / boundary_transition;
189 interpolate(&env1, &env2, fraction, _environment);
194 do_interpolate(_aloft_table, altitude_ft, _environment);
198 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table, double altitude_ft, FGEnvironment * environment)
200 int length = table.size();
204 // Boundary conditions
205 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
206 environment->copy(table[0]->environment);
208 } else if (table[length-1]->altitude_ft <= altitude_ft) {
209 environment->copy(table[length-1]->environment);
212 // Search the interpolation table
213 for (int i = 0; i < length - 1; i++) {
214 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
215 FGEnvironment * env1 = &(table[i]->environment);
216 FGEnvironment * env2 = &(table[i+1]->environment);
218 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
222 ((altitude_ft - table[i]->altitude_ft) /
223 (table[i+1]->altitude_ft - table[i]->altitude_ft));
224 interpolate(env1, env2, fraction, environment);
232 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
234 return (altitude_ft < b.altitude_ft);
238 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
240 return (a->altitude_ft) < (b->altitude_ft);
244 ////////////////////////////////////////////////////////////////////////
245 // Implementation of FGMetarCtrl.
246 ////////////////////////////////////////////////////////////////////////
248 FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
249 : _environmentCtrl(environmentCtrl),
250 station_elevation_ft(0.0),
252 setup_winds_aloft(true),
253 // Interpolation constant definitions.
254 EnvironmentUpdatePeriodSec( 0.2 ),
255 MaxWindChangeKtsSec( 0.2 ),
256 MaxVisChangePercentSec( 0.05 ),
257 MaxPressureChangeInHgSec( 0.0033 ),
258 MaxCloudAltitudeChangeFtSec( 20.0 ),
259 MaxCloudThicknessChangeFtSec( 50.0 ),
260 MaxCloudInterpolationHeightFt( 5000.0 ),
261 MaxCloudInterpolationDeltaFt( 4000.0 )
263 metar_base_n = fgGetNode( "/environment/metar", true );
264 station_id_n = metar_base_n->getNode("station-id", true );
265 station_elevation_n = metar_base_n->getNode("station-elevation-ft", true );
266 min_visibility_n = metar_base_n->getNode("min-visibility-m", true );
267 max_visibility_n = metar_base_n->getNode("max-visibility-m", true );
268 base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true );
269 base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true );
270 base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true );
271 base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
272 gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true );
273 temperature_n = metar_base_n->getNode("temperature-degc", true );
274 dewpoint_n = metar_base_n->getNode("dewpoint-degc", true );
275 humidity_n = metar_base_n->getNode("rel-humidity-norm", true );
276 pressure_n = metar_base_n->getNode("pressure-inhg", true );
277 clouds_n = metar_base_n->getNode("clouds", true );
278 rain_n = metar_base_n->getNode("rain-norm", true );
279 hail_n = metar_base_n->getNode("hail-norm", true );
280 snow_n = metar_base_n->getNode("snow-norm", true );
281 snow_cover_n = metar_base_n->getNode("snow-cover", true );
282 ground_elevation_n = fgGetNode( "/position/ground-elev-m", true );
283 longitude_n = fgGetNode( "/position/longitude-deg", true );
284 latitude_n = fgGetNode( "/position/latitude-deg", true );
285 environment_clouds_n = fgGetNode("/environment/clouds");
287 boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt");
288 boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
289 boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
290 boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
293 FGMetarCtrl::~FGMetarCtrl ()
297 void FGMetarCtrl::bind ()
299 fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid );
300 fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled );
301 fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft );
304 void FGMetarCtrl::unbind ()
306 fgUntie("/environment/metar/valid");
307 fgUntie("/environment/params/metar-updates-environment");
308 fgUntie("/environment/params/metar-updates-winds-aloft");
311 // use a "command" to set station temp at station elevation
312 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
314 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
315 node->setFloatValue( temp_degc );
316 node = args.getNode("altitude-ft", 0, true);
317 node->setFloatValue( altitude_ft );
318 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
321 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
323 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
324 node->setFloatValue( dewpoint_degc );
325 node = args.getNode("altitude-ft", 0, true);
326 node->setFloatValue( altitude_ft );
327 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
331 Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
334 wind-from-heading-deg
336 turbulence/magnitude-norm
339 wind-heading-change-deg how many degrees does the wind direction change at this level
340 wind-speed-change-rel relative change of wind speed at this level
341 turbulence/factor factor for the calculated turbulence magnitude at this level
343 static void setupWindBranch( string branchName, double dir, double speed, double gust )
345 SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true);
346 vector<SGPropertyNode_ptr> entries = branch->getChildren("entry");
347 for ( vector<SGPropertyNode_ptr>::iterator it = entries.begin(); it != entries.end(); it++) {
349 // change wind direction as configured
350 double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 );
351 if( layer_dir >= 360.0 ) layer_dir -= 360.0;
352 if( layer_dir < 0.0 ) layer_dir += 360.0;
353 (*it)->setDoubleValue("wind-from-heading-deg", layer_dir);
355 double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 ));
356 (*it)->setDoubleValue("wind-speed-kt", layer_speed );
358 // add some turbulence
359 SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true);
361 double turbulence_norm = speed/50;
363 turbulence_norm += (gust-speed)/25;
365 if( turbulence_norm > 1.0 ) turbulence_norm = 1.0;
367 turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 );
368 turbulence->setDoubleValue( "magnitude-norm", turbulence_norm );
372 static void setupWind( bool setup_aloft, double dir, double speed, double gust )
374 setupWindBranch( "boundary", dir, speed, gust );
376 setupWindBranch( "aloft", dir, speed, gust );
379 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
381 double dval = EnvironmentUpdatePeriodSec * dt;
383 if (fabs(currentval - requiredval) < dval) return requiredval;
384 if (currentval < requiredval) return (currentval + dval);
385 if (currentval > requiredval) return (currentval - dval);
396 FGMetarCtrl::reinit ()
402 FGMetarCtrl::update(double dt)
404 if( dt <= 0 || !metar_valid ||!enabled)
407 // Interpolate the current configuration closer to the actual METAR
409 bool reinit_required = false;
410 bool layer_rebuild_required = false;
413 double dir = base_wind_dir_n->getDoubleValue();
414 double speed = base_wind_speed_n->getDoubleValue();
415 double gust = gust_wind_speed_n->getDoubleValue();
416 setupWind(setup_winds_aloft, dir, speed, gust);
418 double metarvis = min_visibility_n->getDoubleValue();
419 fgDefaultWeatherValue("visibility-m", metarvis);
421 double metarpressure = pressure_n->getDoubleValue();
422 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
424 // We haven't already loaded a METAR, so apply it immediately.
425 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
426 vector<SGPropertyNode_ptr>::const_iterator layer;
427 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
430 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
431 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
433 target->setStringValue("coverage",
434 (*layer)->getStringValue("coverage", "clear"));
435 target->setDoubleValue("elevation-ft",
436 (*layer)->getDoubleValue("elevation-ft"));
437 target->setDoubleValue("thickness-ft",
438 (*layer)->getDoubleValue("thickness-ft"));
439 target->setDoubleValue("span-m", 40000.0);
442 first_update = false;
443 reinit_required = true;
444 layer_rebuild_required = true;
447 // Generate interpolated values between the METAR and the current
450 // Pick up the METAR wind values and convert them into a vector.
452 double metar_speed = base_wind_speed_n->getDoubleValue();
453 double metar_heading = base_wind_dir_n->getDoubleValue();
455 metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
456 metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
458 // Convert the current wind values and convert them into a vector
460 double speed = boundary_wind_speed_n->getDoubleValue();
461 double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
463 current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
464 current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
466 // Determine the maximum component-wise value that the wind can change.
467 // First we determine the fraction in the X and Y component, then
468 // factor by the maximum wind change.
469 double x = fabs(current[0] - metar[0]);
470 double y = fabs(current[1] - metar[1]);
472 // only interpolate if we have a difference
474 double dx = x / (x + y);
477 double maxdx = dx * MaxWindChangeKtsSec;
478 double maxdy = dy * MaxWindChangeKtsSec;
480 // Interpolate each component separately.
481 current[0] = interpolate_val(current[0], metar[0], maxdx);
482 current[1] = interpolate_val(current[1], metar[1], maxdy);
484 // Now convert back to polar coordinates.
485 if ((current[0] == 0.0) && (current[1] == 0.0)) {
486 // Special case where there is no wind (otherwise atan2 barfs)
489 // Some real wind to convert back from. Work out the speed
490 // and direction value in degrees.
491 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
492 dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
494 // Normalize the direction.
498 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
500 double gust = gust_wind_speed_n->getDoubleValue();
501 setupWind(setup_winds_aloft, dir_from, speed, gust);
502 reinit_required = true;
505 // Now handle the visibility. We convert both visibility values
506 // to X-values, then interpolate from there, then back to real values.
507 // The length_scale is fixed to 1000m, so the visibility changes by
508 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
509 // whichever is more.
510 double vis = boundary_visibility_n->getDoubleValue();;
511 double metarvis = min_visibility_n->getDoubleValue();
512 if( vis != metarvis ) {
513 double currentxval = log(1000.0 + vis);
514 double metarxval = log(1000.0 + metarvis);
516 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
518 // Now convert back from an X-value to a straightforward visibility.
519 vis = exp(currentxval) - 1000.0;
520 fgDefaultWeatherValue("visibility-m", vis);
521 reinit_required = true;
524 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
525 double metarpressure = pressure_n->getDoubleValue();
526 if( pressure != metarpressure ) {
527 pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
528 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
529 reinit_required = true;
532 // Set the cloud layers by interpolating over the METAR versions.
533 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
534 vector<SGPropertyNode_ptr>::const_iterator layer;
535 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
537 double aircraft_alt = fgGetDouble("/position/altitude-ft");
540 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
541 SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
543 // In the case of clouds, we want to avoid writing if nothing has
544 // changed, as these properties are tied to the renderer and will
545 // cause the clouds to be updated, reseting the texture locations.
547 // We don't interpolate the coverage values as no-matter how we
548 // do it, it will be quite a sudden change of texture. Better to
549 // have a single change than four or five.
550 const char *coverage = (*layer)->getStringValue("coverage", "clear");
551 SGPropertyNode *cov = target->getNode("coverage", true);
552 if (strcmp(cov->getStringValue(), coverage) != 0) {
553 cov->setStringValue(coverage);
554 layer_rebuild_required = true;
557 double required_alt = (*layer)->getDoubleValue("elevation-ft");
558 double current_alt = target->getDoubleValue("elevation-ft");
559 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
560 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
562 if (current_alt < -9000 || required_alt < -9000 ||
563 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
564 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
565 // We don't interpolate any layers that are
566 // - too far above us to be visible
567 // - too far below us to be visible
568 // - with too large a difference to make interpolation sensible
569 // - to or from -9999 (used as a placeholder)
570 // - any values that are too high above us,
571 if (current_alt != required_alt)
572 target->setDoubleValue("elevation-ft", required_alt);
574 if (thickness->getDoubleValue() != required_thickness)
575 thickness->setDoubleValue(required_thickness);
578 // Interpolate the other values in the usual way
579 if (current_alt != required_alt) {
580 current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
581 target->setDoubleValue("elevation-ft", current_alt);
584 double current_thickness = thickness->getDoubleValue();
586 if (current_thickness != required_thickness) {
587 current_thickness = interpolate_val(current_thickness,
589 MaxCloudThicknessChangeFtSec);
590 thickness->setDoubleValue(current_thickness);
596 set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
597 set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
599 // Force an update of the 3D clouds
600 if( layer_rebuild_required )
601 fgSetInt("/environment/rebuild-layers", 1 );
603 // Reinitializing of the environment controller required
604 if( reinit_required )
605 _environmentCtrl->reinit();
608 const char * FGMetarCtrl::get_metar(void) const
610 return metar.c_str();
613 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
614 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
616 void FGMetarCtrl::set_metar( const char * metar_string )
620 metar = metar_string;
622 SGSharedPtr<FGMetar> m;
624 m = new FGMetar( metar_string );
626 catch( sg_io_exception ) {
627 fprintf( stderr, "can't get metar: %s\n", metar_string );
632 min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
633 max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
635 const SGMetarVisibility *dirvis = m->getDirVisibility();
636 for (i = 0; i < 8; i++, dirvis++) {
637 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
638 double v = dirvis->getVisibility_m();
640 vis->setDoubleValue("min-m", v);
641 vis->setDoubleValue("max-m", v);
644 base_wind_dir_n->setIntValue( m->getWindDir() );
645 base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
646 base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
647 base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
648 gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
649 temperature_n->setDoubleValue( m->getTemperature_C() );
650 dewpoint_n->setDoubleValue( m->getDewpoint_C() );
651 humidity_n->setDoubleValue( m->getRelHumidity() );
652 pressure_n->setDoubleValue( m->getPressure_inHg() );
655 // get station elevation to compute cloud base
656 double station_elevation_ft = 0;
658 // 1. check the id given in the metar
659 FGAirport* a = FGAirport::findByIdent(m->getId());
661 // 2. if unknown, find closest airport with metar to current position
663 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
664 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
667 // 3. otherwise use ground elevation
669 station_elevation_ft = a->getElevation();
670 station_id_n->setStringValue( a->ident());
672 station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
673 station_id_n->setStringValue( m->getId());
677 station_elevation_n->setDoubleValue( station_elevation_ft );
679 vector<SGMetarCloud> cv = m->getClouds();
680 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
682 int layer_cnt = environment_clouds_n->getChildren("layer").size();
683 for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
686 const char *coverage = "clear";
687 double elevation = -9999.0;
688 double thickness = 0.0;
689 const double span = 40000.0;
691 if (cloud != cloud_end) {
692 int c = cloud->getCoverage();
693 coverage = coverage_string[c];
694 elevation = cloud->getAltitude_ft() + station_elevation_ft;
695 thickness = thickness_value[c];
699 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
701 // if the coverage has changed, a rebuild of the layer is needed
702 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
703 layer->setStringValue("coverage", coverage);
705 layer->setDoubleValue("elevation-ft", elevation);
706 layer->setDoubleValue("thickness-ft", thickness);
707 layer->setDoubleValue("span-m", span);
710 rain_n->setDoubleValue(m->getRain());
711 hail_n->setDoubleValue(m->getHail());
712 snow_n->setDoubleValue(m->getSnow());
713 snow_cover_n->setBoolValue(m->getSnowCover());
717 #if defined(ENABLE_THREADS)
719 * This class represents the thread of execution responsible for
720 * fetching the metar data.
722 class MetarThread : public OpenThreads::Thread {
724 MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
728 * Fetche the metar data from the NOAA.
733 FGMetarFetcher * metar_fetcher;
736 void MetarThread::run()
739 string airport_id = metar_fetcher->request_queue.pop();
741 if( airport_id.size() == 0 )
744 if( metar_fetcher->_error_count > 3 ) {
745 SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
749 metar_fetcher->fetch( airport_id );
754 FGMetarFetcher::FGMetarFetcher()
756 #if defined(ENABLE_THREADS)
765 longitude_n = fgGetNode( "/position/longitude-deg", true );
766 latitude_n = fgGetNode( "/position/latitude-deg", true );
767 enable_n = fgGetNode( "/environment/params/real-world-weather-fetch", true );
769 proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
770 proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
771 proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
772 max_age_n = fgGetNode("/environment/params/metar-max-age-min", true);
774 output_n = fgGetNode("/environment/metar/data", true );
775 #if defined(ENABLE_THREADS)
776 metar_thread = new MetarThread(this);
777 // FIXME: do we really need setProcessorAffinity()?
778 // metar_thread->setProcessorAffinity(1);
779 metar_thread->start();
780 #endif // ENABLE_THREADS
784 FGMetarFetcher::~FGMetarFetcher()
786 #if defined(ENABLE_THREADS)
787 request_queue.push("");
788 metar_thread->join();
790 #endif // ENABLE_THREADS
793 void FGMetarFetcher::init ()
800 current_airport_id.clear();
803 void FGMetarFetcher::reinit ()
808 /* search for closest airport with metar every xx seconds */
809 static const int search_interval_sec = 60;
811 /* fetch metar for airport, even if airport has not changed every xx seconds */
812 static const int fetch_interval_sec = 900;
814 /* reset error counter after xxx seconds */
815 static const int error_timer_sec = 3;
817 void FGMetarFetcher::update (double delta_time_sec)
819 fetch_timer -= delta_time_sec;
820 search_timer -= delta_time_sec;
821 error_timer -= delta_time_sec;
823 if( error_timer <= 0.0 ) {
824 error_timer = error_timer_sec;
828 if( enable_n->getBoolValue() == false )
831 FGAirport * a = NULL;
833 if( search_timer <= 0.0 ) {
834 // search timer expired, search closest airport with metar
835 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
836 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
837 search_timer = search_interval_sec;
844 if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
845 // fetch timer expired or airport has changed, schedule a fetch
846 current_airport_id = a->ident();
847 fetch_timer = fetch_interval_sec;
848 #if defined(ENABLE_THREADS)
849 // push this airport id into the queue for the worker thread
850 request_queue.push( current_airport_id );
852 // if there is no worker thread, immediately fetch the data
853 fetch( current_airport_id );
858 void FGMetarFetcher::fetch( const string & id )
860 SGSharedPtr<FGMetar> result = NULL;
862 // fetch current metar data
864 string host = proxy_host_n->getStringValue();
865 string auth = proxy_auth_n->getStringValue();
866 string port = proxy_port_n->getStringValue();
868 result = new FGMetar( id, host, port, auth);
870 long max_age = max_age_n->getLongValue();
871 long age = result->getAge_min();
873 if (max_age && age > max_age) {
874 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
875 if (++_stale_count > 10) {
877 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
883 } catch (const sg_io_exception& e) {
884 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
888 // write the metar to the property node, the rest is done by the methods tied to this property
889 // don't write the metar data, if real-weather-fetch has been disabled in the meantime
890 if( result != NULL && enable_n->getBoolValue() == true )
891 output_n->setStringValue( result->getData() );
894 // end of environment_ctrl.cxx