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.
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/structure/commands.hxx>
33 #include <simgear/structure/exception.hxx>
35 #include <Airports/simple.hxx>
36 #include <Main/fg_props.hxx>
37 #include <Main/util.hxx>
39 #include "environment_mgr.hxx"
40 #include "environment_ctrl.hxx"
44 class metar_filter : public FGAirportSearchFilter {
45 virtual bool pass(FGAirport *a) { return a->getMetar(); }
49 ////////////////////////////////////////////////////////////////////////
50 // Implementation of FGEnvironmentCtrl abstract base class.
51 ////////////////////////////////////////////////////////////////////////
53 FGEnvironmentCtrl::FGEnvironmentCtrl ()
61 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
66 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
68 _environment = environment;
72 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
78 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
84 FGEnvironmentCtrl::setElevationFt (double elev_ft)
90 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
99 ////////////////////////////////////////////////////////////////////////
100 // Implementation of FGUserDefEnvironmentCtrl.
101 ////////////////////////////////////////////////////////////////////////
103 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
104 : _base_wind_speed_node(0),
105 _gust_wind_speed_node(0),
106 _current_wind_speed_kt(0),
107 _delta_wind_speed_kt(0)
111 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
116 FGUserDefEnvironmentCtrl::init ()
118 // Fill in some defaults.
119 if (!fgHasNode("/environment/params/base-wind-speed-kt"))
120 fgSetDouble("/environment/params/base-wind-speed-kt",
121 fgGetDouble("/environment/wind-speed-kt"));
122 if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
123 fgSetDouble("/environment/params/gust-wind-speed-kt",
124 fgGetDouble("/environment/params/base-wind-speed-kt"));
126 _base_wind_speed_node =
127 fgGetNode("/environment/params/base-wind-speed-kt", true);
128 _gust_wind_speed_node =
129 fgGetNode("/environment/params/gust-wind-speed-kt", true);
131 _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
132 _delta_wind_speed_kt = 0.1;
136 FGUserDefEnvironmentCtrl::update (double dt)
138 double base_wind_speed = _base_wind_speed_node->getDoubleValue();
139 double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
141 if (gust_wind_speed < base_wind_speed) {
142 gust_wind_speed = base_wind_speed;
143 _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
146 if (base_wind_speed == gust_wind_speed) {
147 _current_wind_speed_kt = base_wind_speed;
149 int rn = rand() % 128;
150 int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
151 double gust = _current_wind_speed_kt - base_wind_speed;
152 double incr = gust / 50;
155 _delta_wind_speed_kt = - _delta_wind_speed_kt;
157 _delta_wind_speed_kt -= incr * sign;
159 _delta_wind_speed_kt += incr * sign;
161 _current_wind_speed_kt += _delta_wind_speed_kt;
163 if (_current_wind_speed_kt < base_wind_speed) {
164 _current_wind_speed_kt = base_wind_speed;
165 _delta_wind_speed_kt = 0.01;
166 } else if (_current_wind_speed_kt > gust_wind_speed) {
167 _current_wind_speed_kt = gust_wind_speed;
168 _delta_wind_speed_kt = -0.01;
172 if (_environment != 0)
173 _environment->set_wind_speed_kt(_current_wind_speed_kt);
178 ////////////////////////////////////////////////////////////////////////
179 // Implementation of FGInterpolateEnvironmentCtrl.
180 ////////////////////////////////////////////////////////////////////////
182 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
186 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
189 for (i = 0; i < _boundary_table.size(); i++)
190 delete _boundary_table[i];
191 for (i = 0; i < _aloft_table.size(); i++)
192 delete _aloft_table[i];
198 FGInterpolateEnvironmentCtrl::init ()
200 read_table(fgGetNode("/environment/config/boundary", true),
202 read_table(fgGetNode("/environment/config/aloft", true),
207 FGInterpolateEnvironmentCtrl::reinit ()
210 for (i = 0; i < _boundary_table.size(); i++)
211 delete _boundary_table[i];
212 for (i = 0; i < _aloft_table.size(); i++)
213 delete _aloft_table[i];
214 _boundary_table.clear();
215 _aloft_table.clear();
220 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
221 vector<bucket *> &table)
223 for (int i = 0; i < node->nChildren(); i++) {
224 const SGPropertyNode * child = node->getChild(i);
225 if ( strcmp(child->getName(), "entry") == 0
226 && child->getStringValue("elevation-ft", "")[0] != '\0'
227 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
229 bucket * b = new bucket;
231 b->environment.copy(table[i-1]->environment);
232 b->environment.read(child);
233 b->altitude_ft = b->environment.get_elevation_ft();
237 sort(table.begin(), table.end(), bucket::lessThan);
241 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
244 double altitude_ft = fgGetDouble("/position/altitude-ft");
245 double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
246 double boundary_transition =
247 fgGetDouble("/environment/config/boundary-transition-ft", 500);
249 // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
251 int length = _boundary_table.size();
255 double boundary_limit = _boundary_table[length-1]->altitude_ft;
256 if (boundary_limit >= altitude_agl_ft) {
257 do_interpolate(_boundary_table, altitude_agl_ft,
260 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
262 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
263 do_interpolate(_aloft_table, altitude_ft, &env2);
265 (altitude_agl_ft - boundary_limit) / boundary_transition;
266 interpolate(&env1, &env2, fraction, _environment);
272 do_interpolate(_aloft_table, altitude_ft, _environment);
276 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
278 FGEnvironment * environment)
280 int length = table.size();
284 // Boundary conditions
285 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
286 environment->copy(table[0]->environment);
288 } else if (table[length-1]->altitude_ft <= altitude_ft) {
289 environment->copy(table[length-1]->environment);
293 // Search the interpolation table
294 for (int i = 0; i < length - 1; i++) {
295 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
296 FGEnvironment * env1 = &(table[i]->environment);
297 FGEnvironment * env2 = &(table[i+1]->environment);
299 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
303 ((altitude_ft - table[i]->altitude_ft) /
304 (table[i+1]->altitude_ft - table[i]->altitude_ft));
305 interpolate(env1, env2, fraction, environment);
313 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
315 return (altitude_ft < b.altitude_ft);
319 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
321 return (a->altitude_ft) < (b->altitude_ft);
325 ////////////////////////////////////////////////////////////////////////
326 // Implementation of FGMetarEnvironmentCtrl.
327 ////////////////////////////////////////////////////////////////////////
329 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
330 : env( new FGInterpolateEnvironmentCtrl ),
332 metar_loaded( false ),
333 search_interval_sec( 60.0 ), // 1 minute
334 same_station_interval_sec( 900.0 ), // 15 minutes
335 search_elapsed( 9999.0 ),
336 fetch_elapsed( 9999.0 ),
338 proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
339 proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
340 proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
341 metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
343 // Interpolation constant definitions.
344 EnvironmentUpdatePeriodSec( 0.2 ),
345 MaxWindChangeKtsSec( 0.2 ),
346 MaxVisChangePercentSec( 0.05 ),
347 MaxPressureChangeInHgSec( 0.0033 ),
348 MaxCloudAltitudeChangeFtSec( 20.0 ),
349 MaxCloudThicknessChangeFtSec( 50.0 ),
350 MaxCloudInterpolationHeightFt( 5000.0 ),
357 #if defined(ENABLE_THREADS)
358 thread = new MetarThread(this);
360 #endif // ENABLE_THREADS
363 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
365 #if defined(ENABLE_THREADS)
367 #endif // ENABLE_THREADS
374 // use a "command" to set station temp at station elevation
375 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
377 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
378 node->setFloatValue( temp_degc );
379 node = args.getNode("altitude-ft", 0, true);
380 node->setFloatValue( altitude_ft );
381 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
385 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
387 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
388 node->setFloatValue( dewpoint_degc );
389 node = args.getNode("altitude-ft", 0, true);
390 node->setFloatValue( altitude_ft );
391 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
396 FGMetarEnvironmentCtrl::update_env_config ()
408 // Generate interpolated values between the METAR and the current
411 // Pick up the METAR wind values and convert them into a vector.
413 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
414 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
416 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
417 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
419 // Convert the current wind values and convert them into a vector
421 double current_speed =
422 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
423 double current_heading = fgGetDouble(
424 "/environment/config/boundary/entry/wind-from-heading-deg");
426 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
427 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
429 // Determine the maximum component-wise value that the wind can change.
430 // First we determine the fraction in the X and Y component, then
431 // factor by the maximum wind change.
432 double x = fabs(current[0] - metar[0]);
433 double y = fabs(current[1] - metar[1]);
434 double dx = x / (x + y);
437 double maxdx = dx * MaxWindChangeKtsSec;
438 double maxdy = dy * MaxWindChangeKtsSec;
440 // Interpolate each component separately.
441 current[0] = interpolate_val(current[0], metar[0], maxdx);
442 current[1] = interpolate_val(current[1], metar[1], maxdy);
444 // Now convert back to polar coordinates.
445 if ((current[0] == 0.0) && (current[1] == 0.0)) {
446 // Special case where there is no wind (otherwise atan2 barfs)
448 dir_from = current_heading;
451 // Some real wind to convert back from. Work out the speed
452 // and direction value in degrees.
453 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
454 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
456 // Normalize the direction.
460 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
463 // Now handle the visibility. We convert both visibility values
464 // to X-values, then interpolate from there, then back to real values.
465 // The length_scale is fixed to 1000m, so the visibility changes by
466 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
467 // whichever is more.
469 fgGetDouble("/environment/config/boundary/entry/visibility-m");
470 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
471 double currentxval = log(1000.0 + currentvis);
472 double metarxval = log(1000.0 + metarvis);
474 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
476 // Now convert back from an X-value to a straightforward visibility.
477 vis = exp(currentxval) - 1000.0;
479 pressure = interpolate_prop(
480 "/environment/config/boundary/entry/pressure-sea-level-inhg",
481 "/environment/metar/pressure-inhg",
482 MaxPressureChangeInHgSec);
484 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
485 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
486 temp = fgGetDouble("/environment/metar/temperature-degc");
487 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
489 // Set the cloud layers by interpolating over the METAR versions.
490 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
492 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
493 vector<SGPropertyNode_ptr>::const_iterator layer;
494 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
496 const char *cl = "/environment/clouds/layer[%i]";
497 double aircraft_alt = fgGetDouble("/position/altitude-ft");
501 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
505 // In the case of clouds, we want to avoid writing if nothing has
506 // changed, as these properties are tied to the renderer and will
507 // cause the clouds to be updated, reseting the texture locations.
509 // We don't interpolate the coverage values as no-matter how we
510 // do it, it will be quite a sudden change of texture. Better to
511 // have a single change than four or five.
512 snprintf(s, 128, cl, i);
513 strncat(s, "/coverage", 128);
514 const char* coverage = (*layer)->getStringValue("coverage", "clear");
515 if (strncmp(fgGetString(s), coverage, 128) != 0)
516 fgSetString(s, coverage);
518 snprintf(s, 128, cl, i);
519 strncat(s, "/elevation-ft", 128);
520 double current_alt = fgGetDouble(s);
521 double required_alt = (*layer)->getDoubleValue("elevation-ft");
523 if (current_alt < -9000 || required_alt < -9000
524 || fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt) {
525 // We don't interpolate any values that are too high above us,
526 // or too far below us to be visible. Nor do we interpolate
527 // values to or from -9999, which is used as a placeholder
528 // when there isn't actually a cloud layer present.
529 snprintf(s, 128, cl, i);
530 strncat(s, "/elevation-ft", 128);
531 if (current_alt != required_alt)
532 fgSetDouble(s, required_alt);
534 snprintf(s, 128, cl, i);
535 strncat(s, "/thickness-ft", 128);
536 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
537 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
540 // Interpolate the other values in the usual way
541 if (current_alt != required_alt) {
542 current_alt = interpolate_val(current_alt,
544 MaxCloudAltitudeChangeFtSec);
545 fgSetDouble(s, current_alt);
548 snprintf(s, 128, cl, i);
549 strncat(s, "/thickness-ft", 128);
550 currentval = fgGetDouble(s);
551 requiredval = (*layer)->getDoubleValue("thickness-ft");
553 if (currentval != requiredval) {
554 currentval = interpolate_val(currentval,
556 MaxCloudThicknessChangeFtSec);
557 fgSetDouble(s, currentval);
563 // We haven't already loaded a METAR, so apply it immediately.
564 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
565 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
566 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
567 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
568 vis = fgGetDouble("/environment/metar/min-visibility-m");
569 pressure = fgGetDouble("/environment/metar/pressure-inhg");
570 temp = fgGetDouble("/environment/metar/temperature-degc");
571 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
573 // Set the cloud layers by copying over the METAR versions.
574 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
576 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
577 vector<SGPropertyNode_ptr>::const_iterator layer;
578 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
580 const char *cl = "/environment/clouds/layer[%i]";
584 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
585 snprintf(s, 128, cl, i);
586 strncat(s, "/coverage", 128);
587 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
589 snprintf(s, 128, cl, i);
590 strncat(s, "/elevation-ft", 128);
591 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
593 snprintf(s, 128, cl, i);
594 strncat(s, "/thickness-ft", 128);
595 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
597 snprintf(s, 128, cl, i);
598 strncat(s, "/span-m", 128);
599 fgSetDouble(s, 40000.0);
603 fgSetupWind(dir_from, dir_to, speed, gust);
604 fgDefaultWeatherValue("visibility-m", vis);
605 set_temp_at_altitude(temp, station_elevation_ft);
606 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
607 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
609 // We've now successfully loaded a METAR into the configuration
613 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
614 const char * requiredname,
617 double currentval = fgGetDouble(currentname);
618 double requiredval = fgGetDouble(requiredname);
619 return interpolate_val(currentval, requiredval, dt);
622 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
626 double dval = EnvironmentUpdatePeriodSec * dt;
628 if (fabs(currentval - requiredval) < dval) return requiredval;
629 if (currentval < requiredval) return (currentval + dval);
630 if (currentval > requiredval) return (currentval - dval);
635 FGMetarEnvironmentCtrl::init ()
637 const SGPropertyNode *longitude
638 = fgGetNode( "/position/longitude-deg", true );
639 const SGPropertyNode *latitude
640 = fgGetNode( "/position/latitude-deg", true );
642 metar_loaded = false;
643 bool found_metar = false;
644 long max_age = metar_max_age->getLongValue();
645 // Don't check max age during init so that we don't loop over a lot
646 // of airports metar if there is a problem.
647 // The update() calls will find a correct metar if things went wrong here
648 metar_max_age->setLongValue(0);
650 while ( !found_metar && (_error_count < 3) ) {
651 const FGAirport* a = globals->get_airports()
652 ->search( longitude->getDoubleValue(),
653 latitude->getDoubleValue(),
656 FGMetarResult result = fetch_data( a->getId() );
657 if ( result.m != NULL ) {
658 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
662 search_elapsed = 0.0;
664 update_metar_properties( result.m );
669 // mark as no metar so it doesn't show up in subsequent
671 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
673 globals->get_airports()->no_metar( a->getId() );
677 metar_max_age->setLongValue(max_age);
681 FGMetarEnvironmentCtrl::reinit ()
694 FGMetarEnvironmentCtrl::update(double delta_time_sec)
697 _dt += delta_time_sec;
698 if (_error_count >= 3)
701 FGMetarResult result;
703 static const SGPropertyNode *longitude
704 = fgGetNode( "/position/longitude-deg", true );
705 static const SGPropertyNode *latitude
706 = fgGetNode( "/position/latitude-deg", true );
707 search_elapsed += delta_time_sec;
708 fetch_elapsed += delta_time_sec;
709 interpolate_elapsed += delta_time_sec;
711 // if time for a new search request, push it onto the request
713 if ( search_elapsed > search_interval_sec ) {
714 const FGAirport* a = globals->get_airports()
715 ->search( longitude->getDoubleValue(),
716 latitude->getDoubleValue(),
719 if ( !last_apt || last_apt->getId() != a->getId()
720 || fetch_elapsed > same_station_interval_sec )
722 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
724 request_queue.push( a->getId() );
727 search_elapsed = 0.0;
730 search_elapsed = 0.0;
731 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
732 << same_station_interval_sec - fetch_elapsed );
735 SG_LOG( SG_GENERAL, SG_WARN,
736 "Unable to find any airports with metar" );
738 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
739 // Interpolate the current configuration closer to the actual METAR
742 interpolate_elapsed = 0.0;
745 #if !defined(ENABLE_THREADS)
746 // No loader thread running so manually fetch the data
748 while ( !request_queue.empty() ) {
749 id = request_queue.front();
754 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
755 result = fetch_data( id );
756 result_queue.push( result );
758 #endif // ENABLE_THREADS
760 // process any results from the loader.
761 while ( !result_queue.empty() ) {
762 result = result_queue.front();
764 if ( result.m != NULL ) {
765 update_metar_properties( result.m );
770 // mark as no metar so it doesn't show up in subsequent
771 // searches, and signal an immediate re-search.
772 SG_LOG( SG_GENERAL, SG_WARN,
773 "no metar at station = " << result.icao );
774 globals->get_airports()->no_metar( result.icao );
775 search_elapsed = 9999.0;
779 env->update(delta_time_sec);
784 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
786 env->setEnvironment(environment);
790 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
792 FGMetarResult result;
795 // if the last error was more than three seconds ago,
796 // then pretent nothing happened.
805 // fetch station elevation if exists
806 const FGAirport* a = globals->get_airports()->search( icao );
808 station_elevation_ft = a->getElevation();
811 // fetch current metar data
813 string host = proxy_host->getStringValue();
814 string auth = proxy_auth->getStringValue();
815 string port = proxy_port->getStringValue();
816 result.m = new FGMetar( icao, host, port, auth);
818 long max_age = metar_max_age->getLongValue();
819 long age = result.m->getAge_min();
820 if (max_age && age > max_age) {
821 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
825 if (++_stale_count > 10) {
827 throw sg_io_exception("More than 10 stale METAR messages in a row."
828 " Check your system time!");
833 } catch (const sg_io_exception& e) {
834 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
835 << e.getFormattedMessage().c_str() );
836 #if defined(ENABLE_THREADS)
837 if (_error_count++ >= 3) {
838 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
853 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
859 fgSetString("/environment/metar/real-metar", m->getData());
860 // don't update with real weather when we use a custom weather scenario
861 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
862 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
864 fgSetString("/environment/metar/last-metar", m->getData());
865 fgSetString("/environment/metar/station-id", m->getId());
866 fgSetDouble("/environment/metar/min-visibility-m",
867 m->getMinVisibility().getVisibility_m() );
868 fgSetDouble("/environment/metar/max-visibility-m",
869 m->getMaxVisibility().getVisibility_m() );
871 const SGMetarVisibility *dirvis = m->getDirVisibility();
872 for (i = 0; i < 8; i++, dirvis++) {
873 const char *min = "/environment/metar/visibility[%d]/min-m";
874 const char *max = "/environment/metar/visibility[%d]/max-m";
876 d = dirvis->getVisibility_m();
878 snprintf(s, 128, min, i);
880 snprintf(s, 128, max, i);
884 fgSetInt("/environment/metar/base-wind-range-from",
885 m->getWindRangeFrom() );
886 fgSetInt("/environment/metar/base-wind-range-to",
887 m->getWindRangeTo() );
888 fgSetDouble("/environment/metar/base-wind-speed-kt",
889 m->getWindSpeed_kt() );
890 fgSetDouble("/environment/metar/gust-wind-speed-kt",
891 m->getGustSpeed_kt() );
892 fgSetDouble("/environment/metar/temperature-degc",
893 m->getTemperature_C() );
894 fgSetDouble("/environment/metar/dewpoint-degc",
895 m->getDewpoint_C() );
896 fgSetDouble("/environment/metar/rel-humidity-norm",
897 m->getRelHumidity() );
898 fgSetDouble("/environment/metar/pressure-inhg",
899 m->getPressure_inHg() );
901 vector<SGMetarCloud> cv = m->getClouds();
902 vector<SGMetarCloud>::const_iterator cloud;
904 const char *cl = "/environment/metar/clouds/layer[%i]";
905 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
906 const char *coverage_string[5] =
907 { "clear", "few", "scattered", "broken", "overcast" };
908 const double thickness[5] = { 0, 65, 600,750, 1000};
911 snprintf(s, 128, cl, i);
912 strncat(s, "/coverage", 128);
913 q = cloud->getCoverage();
914 fgSetString(s, coverage_string[q] );
916 snprintf(s, 128, cl, i);
917 strncat(s, "/elevation-ft", 128);
918 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
920 snprintf(s, 128, cl, i);
921 strncat(s, "/thickness-ft", 128);
922 fgSetDouble(s, thickness[q]);
924 snprintf(s, 128, cl, i);
925 strncat(s, "/span-m", 128);
926 fgSetDouble(s, 40000.0);
929 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
930 snprintf(s, 128, cl, i);
931 strncat(s, "/coverage", 128);
932 fgSetString(s, "clear");
934 snprintf(s, 128, cl, i);
935 strncat(s, "/elevation-ft", 128);
936 fgSetDouble(s, -9999);
938 snprintf(s, 128, cl, i);
939 strncat(s, "/thickness-ft", 128);
942 snprintf(s, 128, cl, i);
943 strncat(s, "/span-m", 128);
944 fgSetDouble(s, 40000.0);
947 fgSetDouble("/environment/metar/rain-norm", m->getRain());
948 fgSetDouble("/environment/metar/hail-norm", m->getHail());
949 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
950 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
954 #if defined(ENABLE_THREADS)
956 FGMetarEnvironmentCtrl::thread_stop()
958 request_queue.push( string() ); // ask thread to terminate
963 FGMetarEnvironmentCtrl::MetarThread::run()
967 string icao = fetcher->request_queue.pop();
970 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
971 FGMetarResult result = fetcher->fetch_data( icao );
972 fetcher->result_queue.push( result );
975 #endif // ENABLE_THREADS
978 // end of environment_ctrl.cxx