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"
46 ////////////////////////////////////////////////////////////////////////
47 // Implementation of FGEnvironmentCtrl abstract base class.
48 ////////////////////////////////////////////////////////////////////////
50 FGEnvironmentCtrl::FGEnvironmentCtrl ()
58 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
63 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
65 _environment = environment;
69 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
75 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
81 FGEnvironmentCtrl::setElevationFt (double elev_ft)
87 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
96 ////////////////////////////////////////////////////////////////////////
97 // Implementation of FGUserDefEnvironmentCtrl.
98 ////////////////////////////////////////////////////////////////////////
100 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
101 : _base_wind_speed_node(0),
102 _gust_wind_speed_node(0),
103 _current_wind_speed_kt(0),
104 _delta_wind_speed_kt(0)
108 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
113 FGUserDefEnvironmentCtrl::init ()
115 // Fill in some defaults.
116 if (!fgHasNode("/environment/params/base-wind-speed-kt"))
117 fgSetDouble("/environment/params/base-wind-speed-kt",
118 fgGetDouble("/environment/wind-speed-kt"));
119 if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
120 fgSetDouble("/environment/params/gust-wind-speed-kt",
121 fgGetDouble("/environment/params/base-wind-speed-kt"));
123 _base_wind_speed_node =
124 fgGetNode("/environment/params/base-wind-speed-kt", true);
125 _gust_wind_speed_node =
126 fgGetNode("/environment/params/gust-wind-speed-kt", true);
128 _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
129 _delta_wind_speed_kt = 0.1;
133 FGUserDefEnvironmentCtrl::update (double dt)
135 double base_wind_speed = _base_wind_speed_node->getDoubleValue();
136 double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
138 if (gust_wind_speed < base_wind_speed) {
139 gust_wind_speed = base_wind_speed;
140 _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
143 if (base_wind_speed == gust_wind_speed) {
144 _current_wind_speed_kt = base_wind_speed;
146 int rn = rand() % 128;
147 int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
148 double gust = _current_wind_speed_kt - base_wind_speed;
149 double incr = gust / 50;
152 _delta_wind_speed_kt = - _delta_wind_speed_kt;
154 _delta_wind_speed_kt -= incr * sign;
156 _delta_wind_speed_kt += incr * sign;
158 _current_wind_speed_kt += _delta_wind_speed_kt;
160 if (_current_wind_speed_kt < base_wind_speed) {
161 _current_wind_speed_kt = base_wind_speed;
162 _delta_wind_speed_kt = 0.01;
163 } else if (_current_wind_speed_kt > gust_wind_speed) {
164 _current_wind_speed_kt = gust_wind_speed;
165 _delta_wind_speed_kt = -0.01;
169 if (_environment != 0)
170 _environment->set_wind_speed_kt(_current_wind_speed_kt);
175 ////////////////////////////////////////////////////////////////////////
176 // Implementation of FGInterpolateEnvironmentCtrl.
177 ////////////////////////////////////////////////////////////////////////
179 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
183 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
186 for (i = 0; i < _boundary_table.size(); i++)
187 delete _boundary_table[i];
188 for (i = 0; i < _aloft_table.size(); i++)
189 delete _aloft_table[i];
195 FGInterpolateEnvironmentCtrl::init ()
197 read_table(fgGetNode("/environment/config/boundary", true),
199 read_table(fgGetNode("/environment/config/aloft", true),
204 FGInterpolateEnvironmentCtrl::reinit ()
207 for (i = 0; i < _boundary_table.size(); i++)
208 delete _boundary_table[i];
209 for (i = 0; i < _aloft_table.size(); i++)
210 delete _aloft_table[i];
211 _boundary_table.clear();
212 _aloft_table.clear();
217 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
218 vector<bucket *> &table)
220 for (int i = 0; i < node->nChildren(); i++) {
221 const SGPropertyNode * child = node->getChild(i);
222 if ( strcmp(child->getName(), "entry") == 0
223 && child->getStringValue("elevation-ft", "")[0] != '\0'
224 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
226 bucket * b = new bucket;
228 b->environment.copy(table[i-1]->environment);
229 b->environment.read(child);
230 b->altitude_ft = b->environment.get_elevation_ft();
234 sort(table.begin(), table.end());
238 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
241 double altitude_ft = fgGetDouble("/position/altitude-ft");
242 double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
243 double boundary_transition =
244 fgGetDouble("/environment/config/boundary-transition-ft", 500);
246 // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
248 int length = _boundary_table.size();
252 double boundary_limit = _boundary_table[length-1]->altitude_ft;
253 if (boundary_limit >= altitude_agl_ft) {
254 do_interpolate(_boundary_table, altitude_agl_ft,
257 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
259 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
260 do_interpolate(_aloft_table, altitude_ft, &env2);
262 (altitude_agl_ft - boundary_limit) / boundary_transition;
263 interpolate(&env1, &env2, fraction, _environment);
269 do_interpolate(_aloft_table, altitude_ft, _environment);
273 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
275 FGEnvironment * environment)
277 int length = table.size();
281 // Boundary conditions
282 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
283 environment->copy(table[0]->environment);
285 } else if (table[length-1]->altitude_ft <= altitude_ft) {
286 environment->copy(table[length-1]->environment);
290 // Search the interpolation table
291 for (int i = 0; i < length - 1; i++) {
292 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
293 FGEnvironment * env1 = &(table[i]->environment);
294 FGEnvironment * env2 = &(table[i+1]->environment);
296 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
300 ((altitude_ft - table[i]->altitude_ft) /
301 (table[i+1]->altitude_ft - table[i]->altitude_ft));
302 interpolate(env1, env2, fraction, environment);
310 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
312 return (altitude_ft < b.altitude_ft);
317 ////////////////////////////////////////////////////////////////////////
318 // Implementation of FGMetarEnvironmentCtrl.
319 ////////////////////////////////////////////////////////////////////////
321 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
322 : env( new FGInterpolateEnvironmentCtrl ),
324 metar_loaded( false ),
325 search_interval_sec( 60.0 ), // 1 minute
326 same_station_interval_sec( 900.0 ), // 15 minutes
327 search_elapsed( 9999.0 ),
328 fetch_elapsed( 9999.0 ),
330 proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
331 proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
332 proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
333 metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
335 // Interpolation constant definitions.
336 EnvironmentUpdatePeriodSec( 0.2 ),
337 MaxWindChangeKtsSec( 0.2 ),
338 MaxVisChangePercentSec( 0.05 ),
339 MaxPressureChangeInHgSec( 0.0033 ),
340 MaxCloudAltitudeChangeFtSec( 20.0 ),
341 MaxCloudThicknessChangeFtSec( 50.0 ),
342 MaxCloudInterpolationHeightFt( 5000.0 ),
349 #if defined(ENABLE_THREADS)
350 thread = new MetarThread(this);
352 #endif // ENABLE_THREADS
355 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
357 #if defined(ENABLE_THREADS)
359 #endif // ENABLE_THREADS
366 // use a "command" to set station temp at station elevation
367 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
369 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
370 node->setFloatValue( temp_degc );
371 node = args.getNode("altitude-ft", 0, true);
372 node->setFloatValue( altitude_ft );
373 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
377 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
379 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
380 node->setFloatValue( dewpoint_degc );
381 node = args.getNode("altitude-ft", 0, true);
382 node->setFloatValue( altitude_ft );
383 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
388 FGMetarEnvironmentCtrl::update_env_config ()
400 // Generate interpolated values between the METAR and the current
403 // Pick up the METAR wind values and convert them into a vector.
405 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
406 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
408 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
409 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
411 // Convert the current wind values and convert them into a vector
413 double current_speed =
414 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
415 double current_heading = fgGetDouble(
416 "/environment/config/boundary/entry/wind-from-heading-deg");
418 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
419 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
421 // Determine the maximum component-wise value that the wind can change.
422 // First we determine the fraction in the X and Y component, then
423 // factor by the maximum wind change.
424 double x = fabs(current[0] - metar[0]);
425 double y = fabs(current[1] - metar[1]);
426 double dx = x / (x + y);
429 double maxdx = dx * MaxWindChangeKtsSec;
430 double maxdy = dy * MaxWindChangeKtsSec;
432 // Interpolate each component separately.
433 current[0] = interpolate_val(current[0], metar[0], maxdx);
434 current[1] = interpolate_val(current[1], metar[1], maxdy);
436 // Now convert back to polar coordinates.
437 if ((current[0] == 0.0) && (current[1] == 0.0)) {
438 // Special case where there is no wind (otherwise atan2 barfs)
440 dir_from = current_heading;
443 // Some real wind to convert back from. Work out the speed
444 // and direction value in degrees.
445 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
446 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
448 // Normalize the direction.
452 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
455 // Now handle the visibility. We convert both visibility values
456 // to X-values, then interpolate from there, then back to real values.
457 // The length_scale is fixed to 1000m, so the visibility changes by
458 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
459 // whichever is more.
461 fgGetDouble("/environment/config/boundary/entry/visibility-m");
462 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
463 double currentxval = log(1000.0 + currentvis);
464 double metarxval = log(1000.0 + metarvis);
466 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
468 // Now convert back from an X-value to a straightforward visibility.
469 vis = exp(currentxval) - 1000.0;
471 pressure = interpolate_prop(
472 "/environment/config/boundary/entry/pressure-sea-level-inhg",
473 "/environment/metar/pressure-inhg",
474 MaxPressureChangeInHgSec);
476 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
477 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
478 temp = fgGetDouble("/environment/metar/temperature-degc");
479 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
481 // Set the cloud layers by interpolating over the METAR versions.
482 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
484 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
485 vector<SGPropertyNode_ptr>::const_iterator layer;
486 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
488 const char *cl = "/environment/clouds/layer[%i]";
489 double aircraft_alt = fgGetDouble("/position/altitude-ft");
493 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
497 // In the case of clouds, we want to avoid writing if nothing has
498 // changed, as these properties are tied to the renderer and will
499 // cause the clouds to be updated, reseting the texture locations.
501 // We don't interpolate the coverage values as no-matter how we
502 // do it, it will be quite a sudden change of texture. Better to
503 // have a single change than four or five.
504 snprintf(s, 128, cl, i);
505 strncat(s, "/coverage", 128);
506 const char* coverage = (*layer)->getStringValue("coverage", "clear");
507 if (strncmp(fgGetString(s), coverage, 128) != 0)
508 fgSetString(s, coverage);
510 snprintf(s, 128, cl, i);
511 strncat(s, "/elevation-ft", 128);
512 double current_alt = fgGetDouble(s);
513 double required_alt = (*layer)->getDoubleValue("elevation-ft");
515 if (current_alt < -9000 || required_alt < -9000
516 || fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt) {
517 // We don't interpolate any values that are too high above us,
518 // or too far below us to be visible. Nor do we interpolate
519 // values to or from -9999, which is used as a placeholder
520 // when there isn't actually a cloud layer present.
521 snprintf(s, 128, cl, i);
522 strncat(s, "/elevation-ft", 128);
523 if (current_alt != required_alt)
524 fgSetDouble(s, required_alt);
526 snprintf(s, 128, cl, i);
527 strncat(s, "/thickness-ft", 128);
528 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
529 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
532 // Interpolate the other values in the usual way
533 if (current_alt != required_alt) {
534 current_alt = interpolate_val(current_alt,
536 MaxCloudAltitudeChangeFtSec);
537 fgSetDouble(s, current_alt);
540 snprintf(s, 128, cl, i);
541 strncat(s, "/thickness-ft", 128);
542 currentval = fgGetDouble(s);
543 requiredval = (*layer)->getDoubleValue("thickness-ft");
545 if (currentval != requiredval) {
546 currentval = interpolate_val(currentval,
548 MaxCloudThicknessChangeFtSec);
549 fgSetDouble(s, currentval);
555 // We haven't already loaded a METAR, so apply it immediately.
556 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
557 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
558 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
559 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
560 vis = fgGetDouble("/environment/metar/min-visibility-m");
561 pressure = fgGetDouble("/environment/metar/pressure-inhg");
562 temp = fgGetDouble("/environment/metar/temperature-degc");
563 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
565 // Set the cloud layers by copying over the METAR versions.
566 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
568 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
569 vector<SGPropertyNode_ptr>::const_iterator layer;
570 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
572 const char *cl = "/environment/clouds/layer[%i]";
576 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
577 snprintf(s, 128, cl, i);
578 strncat(s, "/coverage", 128);
579 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
581 snprintf(s, 128, cl, i);
582 strncat(s, "/elevation-ft", 128);
583 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
585 snprintf(s, 128, cl, i);
586 strncat(s, "/thickness-ft", 128);
587 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
589 snprintf(s, 128, cl, i);
590 strncat(s, "/span-m", 128);
591 fgSetDouble(s, 40000.0);
595 fgSetupWind(dir_from, dir_to, speed, gust);
596 fgDefaultWeatherValue("visibility-m", vis);
597 set_temp_at_altitude(temp, station_elevation_ft);
598 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
599 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
601 // We've now successfully loaded a METAR into the configuration
605 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
606 const char * requiredname,
609 double currentval = fgGetDouble(currentname);
610 double requiredval = fgGetDouble(requiredname);
611 return interpolate_val(currentval, requiredval, dt);
614 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
618 double dval = EnvironmentUpdatePeriodSec * dt;
620 if (fabs(currentval - requiredval) < dval) return requiredval;
621 if (currentval < requiredval) return (currentval + dval);
622 if (currentval > requiredval) return (currentval - dval);
627 FGMetarEnvironmentCtrl::init ()
629 const SGPropertyNode *longitude
630 = fgGetNode( "/position/longitude-deg", true );
631 const SGPropertyNode *latitude
632 = fgGetNode( "/position/latitude-deg", true );
634 metar_loaded = false;
635 bool found_metar = false;
636 long max_age = metar_max_age->getLongValue();
637 // Don't check max age during init so that we don't loop over a lot
638 // of airports metar if there is a problem.
639 // The update() calls will find a correct metar if things went wrong here
640 metar_max_age->setLongValue(0);
642 while ( !found_metar && (_error_count < 3) ) {
643 const FGAirport* a = globals->get_airports()
644 ->search( longitude->getDoubleValue(),
645 latitude->getDoubleValue(),
648 FGMetarResult result = fetch_data( a->getId() );
649 if ( result.m != NULL ) {
650 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
654 search_elapsed = 0.0;
656 update_metar_properties( result.m );
661 // mark as no metar so it doesn't show up in subsequent
663 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
665 globals->get_airports()->no_metar( a->getId() );
669 metar_max_age->setLongValue(max_age);
673 FGMetarEnvironmentCtrl::reinit ()
686 FGMetarEnvironmentCtrl::update(double delta_time_sec)
689 _dt += delta_time_sec;
690 if (_error_count >= 3)
693 FGMetarResult result;
695 static const SGPropertyNode *longitude
696 = fgGetNode( "/position/longitude-deg", true );
697 static const SGPropertyNode *latitude
698 = fgGetNode( "/position/latitude-deg", true );
699 search_elapsed += delta_time_sec;
700 fetch_elapsed += delta_time_sec;
701 interpolate_elapsed += delta_time_sec;
703 // if time for a new search request, push it onto the request
705 if ( search_elapsed > search_interval_sec ) {
706 const FGAirport* a = globals->get_airports()
707 ->search( longitude->getDoubleValue(),
708 latitude->getDoubleValue(),
711 if ( !last_apt || last_apt->getId() != a->getId()
712 || fetch_elapsed > same_station_interval_sec )
714 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
716 request_queue.push( a->getId() );
719 search_elapsed = 0.0;
722 search_elapsed = 0.0;
723 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
724 << same_station_interval_sec - fetch_elapsed );
727 SG_LOG( SG_GENERAL, SG_WARN,
728 "Unable to find any airports with metar" );
730 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
731 // Interpolate the current configuration closer to the actual METAR
734 interpolate_elapsed = 0.0;
737 #if !defined(ENABLE_THREADS)
738 // No loader thread running so manually fetch the data
740 while ( !request_queue.empty() ) {
741 id = request_queue.front();
746 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
747 result = fetch_data( id );
748 result_queue.push( result );
750 #endif // ENABLE_THREADS
752 // process any results from the loader.
753 while ( !result_queue.empty() ) {
754 result = result_queue.front();
756 if ( result.m != NULL ) {
757 update_metar_properties( result.m );
762 // mark as no metar so it doesn't show up in subsequent
763 // searches, and signal an immediate re-search.
764 SG_LOG( SG_GENERAL, SG_WARN,
765 "no metar at station = " << result.icao );
766 globals->get_airports()->no_metar( result.icao );
767 search_elapsed = 9999.0;
771 env->update(delta_time_sec);
776 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
778 env->setEnvironment(environment);
782 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
784 FGMetarResult result;
787 // if the last error was more than three seconds ago,
788 // then pretent nothing happened.
797 // fetch station elevation if exists
798 const FGAirport* a = globals->get_airports()->search( icao );
800 station_elevation_ft = a->getElevation();
803 // fetch current metar data
805 string host = proxy_host->getStringValue();
806 string auth = proxy_auth->getStringValue();
807 string port = proxy_port->getStringValue();
808 result.m = new FGMetar( icao, host, port, auth);
810 long max_age = metar_max_age->getLongValue();
811 long age = result.m->getAge_min();
812 if (max_age && age > max_age) {
813 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
817 if (++_stale_count > 10) {
819 throw sg_io_exception("More than 10 stale METAR messages in a row."
820 " Check your system time!");
825 } catch (const sg_io_exception& e) {
826 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
827 << e.getFormattedMessage().c_str() );
828 #if defined(ENABLE_THREADS)
829 if (_error_count++ >= 3) {
830 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
845 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
851 fgSetString("/environment/metar/real-metar", m->getData());
852 // don't update with real weather when we use a custom weather scenario
853 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
854 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
856 fgSetString("/environment/metar/last-metar", m->getData());
857 fgSetString("/environment/metar/station-id", m->getId());
858 fgSetDouble("/environment/metar/min-visibility-m",
859 m->getMinVisibility().getVisibility_m() );
860 fgSetDouble("/environment/metar/max-visibility-m",
861 m->getMaxVisibility().getVisibility_m() );
863 const SGMetarVisibility *dirvis = m->getDirVisibility();
864 for (i = 0; i < 8; i++, dirvis++) {
865 const char *min = "/environment/metar/visibility[%d]/min-m";
866 const char *max = "/environment/metar/visibility[%d]/max-m";
868 d = dirvis->getVisibility_m();
870 snprintf(s, 128, min, i);
872 snprintf(s, 128, max, i);
876 fgSetInt("/environment/metar/base-wind-range-from",
877 m->getWindRangeFrom() );
878 fgSetInt("/environment/metar/base-wind-range-to",
879 m->getWindRangeTo() );
880 fgSetDouble("/environment/metar/base-wind-speed-kt",
881 m->getWindSpeed_kt() );
882 fgSetDouble("/environment/metar/gust-wind-speed-kt",
883 m->getGustSpeed_kt() );
884 fgSetDouble("/environment/metar/temperature-degc",
885 m->getTemperature_C() );
886 fgSetDouble("/environment/metar/dewpoint-degc",
887 m->getDewpoint_C() );
888 fgSetDouble("/environment/metar/rel-humidity-norm",
889 m->getRelHumidity() );
890 fgSetDouble("/environment/metar/pressure-inhg",
891 m->getPressure_inHg() );
893 vector<SGMetarCloud> cv = m->getClouds();
894 vector<SGMetarCloud>::const_iterator cloud;
896 const char *cl = "/environment/metar/clouds/layer[%i]";
897 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
898 const char *coverage_string[5] =
899 { "clear", "few", "scattered", "broken", "overcast" };
900 const double thickness[5] = { 0, 65, 600,750, 1000};
903 snprintf(s, 128, cl, i);
904 strncat(s, "/coverage", 128);
905 q = cloud->getCoverage();
906 fgSetString(s, coverage_string[q] );
908 snprintf(s, 128, cl, i);
909 strncat(s, "/elevation-ft", 128);
910 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
912 snprintf(s, 128, cl, i);
913 strncat(s, "/thickness-ft", 128);
914 fgSetDouble(s, thickness[q]);
916 snprintf(s, 128, cl, i);
917 strncat(s, "/span-m", 128);
918 fgSetDouble(s, 40000.0);
921 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
922 snprintf(s, 128, cl, i);
923 strncat(s, "/coverage", 128);
924 fgSetString(s, "clear");
926 snprintf(s, 128, cl, i);
927 strncat(s, "/elevation-ft", 128);
928 fgSetDouble(s, -9999);
930 snprintf(s, 128, cl, i);
931 strncat(s, "/thickness-ft", 128);
934 snprintf(s, 128, cl, i);
935 strncat(s, "/span-m", 128);
936 fgSetDouble(s, 40000.0);
939 fgSetDouble("/environment/metar/rain-norm", m->getRain());
940 fgSetDouble("/environment/metar/hail-norm", m->getHail());
941 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
942 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
946 #if defined(ENABLE_THREADS)
948 FGMetarEnvironmentCtrl::thread_stop()
950 request_queue.push( string() ); // ask thread to terminate
955 FGMetarEnvironmentCtrl::MetarThread::run()
959 string icao = fetcher->request_queue.pop();
962 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
963 FGMetarResult result = fetcher->fetch_data( icao );
964 fetcher->result_queue.push( result );
967 #endif // ENABLE_THREADS
970 // end of environment_ctrl.cxx