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(), bucket::lessThan);
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);
316 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
318 return (a->altitude_ft) < (b->altitude_ft);
322 ////////////////////////////////////////////////////////////////////////
323 // Implementation of FGMetarEnvironmentCtrl.
324 ////////////////////////////////////////////////////////////////////////
326 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
327 : env( new FGInterpolateEnvironmentCtrl ),
329 metar_loaded( false ),
330 search_interval_sec( 60.0 ), // 1 minute
331 same_station_interval_sec( 900.0 ), // 15 minutes
332 search_elapsed( 9999.0 ),
333 fetch_elapsed( 9999.0 ),
335 proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
336 proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
337 proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
338 metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
340 // Interpolation constant definitions.
341 EnvironmentUpdatePeriodSec( 0.2 ),
342 MaxWindChangeKtsSec( 0.2 ),
343 MaxVisChangePercentSec( 0.05 ),
344 MaxPressureChangeInHgSec( 0.0033 ),
345 MaxCloudAltitudeChangeFtSec( 20.0 ),
346 MaxCloudThicknessChangeFtSec( 50.0 ),
347 MaxCloudInterpolationHeightFt( 5000.0 ),
354 #if defined(ENABLE_THREADS)
355 thread = new MetarThread(this);
357 #endif // ENABLE_THREADS
360 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
362 #if defined(ENABLE_THREADS)
364 #endif // ENABLE_THREADS
371 // use a "command" to set station temp at station elevation
372 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
374 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
375 node->setFloatValue( temp_degc );
376 node = args.getNode("altitude-ft", 0, true);
377 node->setFloatValue( altitude_ft );
378 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
382 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
384 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
385 node->setFloatValue( dewpoint_degc );
386 node = args.getNode("altitude-ft", 0, true);
387 node->setFloatValue( altitude_ft );
388 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
393 FGMetarEnvironmentCtrl::update_env_config ()
405 // Generate interpolated values between the METAR and the current
408 // Pick up the METAR wind values and convert them into a vector.
410 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
411 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
413 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
414 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
416 // Convert the current wind values and convert them into a vector
418 double current_speed =
419 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
420 double current_heading = fgGetDouble(
421 "/environment/config/boundary/entry/wind-from-heading-deg");
423 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
424 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
426 // Determine the maximum component-wise value that the wind can change.
427 // First we determine the fraction in the X and Y component, then
428 // factor by the maximum wind change.
429 double x = fabs(current[0] - metar[0]);
430 double y = fabs(current[1] - metar[1]);
431 double dx = x / (x + y);
434 double maxdx = dx * MaxWindChangeKtsSec;
435 double maxdy = dy * MaxWindChangeKtsSec;
437 // Interpolate each component separately.
438 current[0] = interpolate_val(current[0], metar[0], maxdx);
439 current[1] = interpolate_val(current[1], metar[1], maxdy);
441 // Now convert back to polar coordinates.
442 if ((current[0] == 0.0) && (current[1] == 0.0)) {
443 // Special case where there is no wind (otherwise atan2 barfs)
445 dir_from = current_heading;
448 // Some real wind to convert back from. Work out the speed
449 // and direction value in degrees.
450 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
451 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
453 // Normalize the direction.
457 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
460 // Now handle the visibility. We convert both visibility values
461 // to X-values, then interpolate from there, then back to real values.
462 // The length_scale is fixed to 1000m, so the visibility changes by
463 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
464 // whichever is more.
466 fgGetDouble("/environment/config/boundary/entry/visibility-m");
467 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
468 double currentxval = log(1000.0 + currentvis);
469 double metarxval = log(1000.0 + metarvis);
471 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
473 // Now convert back from an X-value to a straightforward visibility.
474 vis = exp(currentxval) - 1000.0;
476 pressure = interpolate_prop(
477 "/environment/config/boundary/entry/pressure-sea-level-inhg",
478 "/environment/metar/pressure-inhg",
479 MaxPressureChangeInHgSec);
481 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
482 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
483 temp = fgGetDouble("/environment/metar/temperature-degc");
484 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
486 // Set the cloud layers by interpolating over the METAR versions.
487 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
489 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
490 vector<SGPropertyNode_ptr>::const_iterator layer;
491 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
493 const char *cl = "/environment/clouds/layer[%i]";
494 double aircraft_alt = fgGetDouble("/position/altitude-ft");
498 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
502 // In the case of clouds, we want to avoid writing if nothing has
503 // changed, as these properties are tied to the renderer and will
504 // cause the clouds to be updated, reseting the texture locations.
506 // We don't interpolate the coverage values as no-matter how we
507 // do it, it will be quite a sudden change of texture. Better to
508 // have a single change than four or five.
509 snprintf(s, 128, cl, i);
510 strncat(s, "/coverage", 128);
511 const char* coverage = (*layer)->getStringValue("coverage", "clear");
512 if (strncmp(fgGetString(s), coverage, 128) != 0)
513 fgSetString(s, coverage);
515 snprintf(s, 128, cl, i);
516 strncat(s, "/elevation-ft", 128);
517 double current_alt = fgGetDouble(s);
518 double required_alt = (*layer)->getDoubleValue("elevation-ft");
520 if (current_alt < -9000 || required_alt < -9000
521 || fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt) {
522 // We don't interpolate any values that are too high above us,
523 // or too far below us to be visible. Nor do we interpolate
524 // values to or from -9999, which is used as a placeholder
525 // when there isn't actually a cloud layer present.
526 snprintf(s, 128, cl, i);
527 strncat(s, "/elevation-ft", 128);
528 if (current_alt != required_alt)
529 fgSetDouble(s, required_alt);
531 snprintf(s, 128, cl, i);
532 strncat(s, "/thickness-ft", 128);
533 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
534 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
537 // Interpolate the other values in the usual way
538 if (current_alt != required_alt) {
539 current_alt = interpolate_val(current_alt,
541 MaxCloudAltitudeChangeFtSec);
542 fgSetDouble(s, current_alt);
545 snprintf(s, 128, cl, i);
546 strncat(s, "/thickness-ft", 128);
547 currentval = fgGetDouble(s);
548 requiredval = (*layer)->getDoubleValue("thickness-ft");
550 if (currentval != requiredval) {
551 currentval = interpolate_val(currentval,
553 MaxCloudThicknessChangeFtSec);
554 fgSetDouble(s, currentval);
560 // We haven't already loaded a METAR, so apply it immediately.
561 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
562 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
563 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
564 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
565 vis = fgGetDouble("/environment/metar/min-visibility-m");
566 pressure = fgGetDouble("/environment/metar/pressure-inhg");
567 temp = fgGetDouble("/environment/metar/temperature-degc");
568 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
570 // Set the cloud layers by copying over the METAR versions.
571 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
573 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
574 vector<SGPropertyNode_ptr>::const_iterator layer;
575 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
577 const char *cl = "/environment/clouds/layer[%i]";
581 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
582 snprintf(s, 128, cl, i);
583 strncat(s, "/coverage", 128);
584 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
586 snprintf(s, 128, cl, i);
587 strncat(s, "/elevation-ft", 128);
588 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
590 snprintf(s, 128, cl, i);
591 strncat(s, "/thickness-ft", 128);
592 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
594 snprintf(s, 128, cl, i);
595 strncat(s, "/span-m", 128);
596 fgSetDouble(s, 40000.0);
600 fgSetupWind(dir_from, dir_to, speed, gust);
601 fgDefaultWeatherValue("visibility-m", vis);
602 set_temp_at_altitude(temp, station_elevation_ft);
603 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
604 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
606 // We've now successfully loaded a METAR into the configuration
610 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
611 const char * requiredname,
614 double currentval = fgGetDouble(currentname);
615 double requiredval = fgGetDouble(requiredname);
616 return interpolate_val(currentval, requiredval, dt);
619 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
623 double dval = EnvironmentUpdatePeriodSec * dt;
625 if (fabs(currentval - requiredval) < dval) return requiredval;
626 if (currentval < requiredval) return (currentval + dval);
627 if (currentval > requiredval) return (currentval - dval);
632 FGMetarEnvironmentCtrl::init ()
634 const SGPropertyNode *longitude
635 = fgGetNode( "/position/longitude-deg", true );
636 const SGPropertyNode *latitude
637 = fgGetNode( "/position/latitude-deg", true );
639 metar_loaded = false;
640 bool found_metar = false;
641 long max_age = metar_max_age->getLongValue();
642 // Don't check max age during init so that we don't loop over a lot
643 // of airports metar if there is a problem.
644 // The update() calls will find a correct metar if things went wrong here
645 metar_max_age->setLongValue(0);
647 while ( !found_metar && (_error_count < 3) ) {
648 const FGAirport* a = globals->get_airports()
649 ->search( longitude->getDoubleValue(),
650 latitude->getDoubleValue(),
653 FGMetarResult result = fetch_data( a->getId() );
654 if ( result.m != NULL ) {
655 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
659 search_elapsed = 0.0;
661 update_metar_properties( result.m );
666 // mark as no metar so it doesn't show up in subsequent
668 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
670 globals->get_airports()->no_metar( a->getId() );
674 metar_max_age->setLongValue(max_age);
678 FGMetarEnvironmentCtrl::reinit ()
691 FGMetarEnvironmentCtrl::update(double delta_time_sec)
694 _dt += delta_time_sec;
695 if (_error_count >= 3)
698 FGMetarResult result;
700 static const SGPropertyNode *longitude
701 = fgGetNode( "/position/longitude-deg", true );
702 static const SGPropertyNode *latitude
703 = fgGetNode( "/position/latitude-deg", true );
704 search_elapsed += delta_time_sec;
705 fetch_elapsed += delta_time_sec;
706 interpolate_elapsed += delta_time_sec;
708 // if time for a new search request, push it onto the request
710 if ( search_elapsed > search_interval_sec ) {
711 const FGAirport* a = globals->get_airports()
712 ->search( longitude->getDoubleValue(),
713 latitude->getDoubleValue(),
716 if ( !last_apt || last_apt->getId() != a->getId()
717 || fetch_elapsed > same_station_interval_sec )
719 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
721 request_queue.push( a->getId() );
724 search_elapsed = 0.0;
727 search_elapsed = 0.0;
728 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
729 << same_station_interval_sec - fetch_elapsed );
732 SG_LOG( SG_GENERAL, SG_WARN,
733 "Unable to find any airports with metar" );
735 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
736 // Interpolate the current configuration closer to the actual METAR
739 interpolate_elapsed = 0.0;
742 #if !defined(ENABLE_THREADS)
743 // No loader thread running so manually fetch the data
745 while ( !request_queue.empty() ) {
746 id = request_queue.front();
751 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
752 result = fetch_data( id );
753 result_queue.push( result );
755 #endif // ENABLE_THREADS
757 // process any results from the loader.
758 while ( !result_queue.empty() ) {
759 result = result_queue.front();
761 if ( result.m != NULL ) {
762 update_metar_properties( result.m );
767 // mark as no metar so it doesn't show up in subsequent
768 // searches, and signal an immediate re-search.
769 SG_LOG( SG_GENERAL, SG_WARN,
770 "no metar at station = " << result.icao );
771 globals->get_airports()->no_metar( result.icao );
772 search_elapsed = 9999.0;
776 env->update(delta_time_sec);
781 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
783 env->setEnvironment(environment);
787 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
789 FGMetarResult result;
792 // if the last error was more than three seconds ago,
793 // then pretent nothing happened.
802 // fetch station elevation if exists
803 const FGAirport* a = globals->get_airports()->search( icao );
805 station_elevation_ft = a->getElevation();
808 // fetch current metar data
810 string host = proxy_host->getStringValue();
811 string auth = proxy_auth->getStringValue();
812 string port = proxy_port->getStringValue();
813 result.m = new FGMetar( icao, host, port, auth);
815 long max_age = metar_max_age->getLongValue();
816 long age = result.m->getAge_min();
817 if (max_age && age > max_age) {
818 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
822 if (++_stale_count > 10) {
824 throw sg_io_exception("More than 10 stale METAR messages in a row."
825 " Check your system time!");
830 } catch (const sg_io_exception& e) {
831 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
832 << e.getFormattedMessage().c_str() );
833 #if defined(ENABLE_THREADS)
834 if (_error_count++ >= 3) {
835 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
850 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
856 fgSetString("/environment/metar/real-metar", m->getData());
857 // don't update with real weather when we use a custom weather scenario
858 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
859 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
861 fgSetString("/environment/metar/last-metar", m->getData());
862 fgSetString("/environment/metar/station-id", m->getId());
863 fgSetDouble("/environment/metar/min-visibility-m",
864 m->getMinVisibility().getVisibility_m() );
865 fgSetDouble("/environment/metar/max-visibility-m",
866 m->getMaxVisibility().getVisibility_m() );
868 const SGMetarVisibility *dirvis = m->getDirVisibility();
869 for (i = 0; i < 8; i++, dirvis++) {
870 const char *min = "/environment/metar/visibility[%d]/min-m";
871 const char *max = "/environment/metar/visibility[%d]/max-m";
873 d = dirvis->getVisibility_m();
875 snprintf(s, 128, min, i);
877 snprintf(s, 128, max, i);
881 fgSetInt("/environment/metar/base-wind-range-from",
882 m->getWindRangeFrom() );
883 fgSetInt("/environment/metar/base-wind-range-to",
884 m->getWindRangeTo() );
885 fgSetDouble("/environment/metar/base-wind-speed-kt",
886 m->getWindSpeed_kt() );
887 fgSetDouble("/environment/metar/gust-wind-speed-kt",
888 m->getGustSpeed_kt() );
889 fgSetDouble("/environment/metar/temperature-degc",
890 m->getTemperature_C() );
891 fgSetDouble("/environment/metar/dewpoint-degc",
892 m->getDewpoint_C() );
893 fgSetDouble("/environment/metar/rel-humidity-norm",
894 m->getRelHumidity() );
895 fgSetDouble("/environment/metar/pressure-inhg",
896 m->getPressure_inHg() );
898 vector<SGMetarCloud> cv = m->getClouds();
899 vector<SGMetarCloud>::const_iterator cloud;
901 const char *cl = "/environment/metar/clouds/layer[%i]";
902 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
903 const char *coverage_string[5] =
904 { "clear", "few", "scattered", "broken", "overcast" };
905 const double thickness[5] = { 0, 65, 600,750, 1000};
908 snprintf(s, 128, cl, i);
909 strncat(s, "/coverage", 128);
910 q = cloud->getCoverage();
911 fgSetString(s, coverage_string[q] );
913 snprintf(s, 128, cl, i);
914 strncat(s, "/elevation-ft", 128);
915 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
917 snprintf(s, 128, cl, i);
918 strncat(s, "/thickness-ft", 128);
919 fgSetDouble(s, thickness[q]);
921 snprintf(s, 128, cl, i);
922 strncat(s, "/span-m", 128);
923 fgSetDouble(s, 40000.0);
926 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
927 snprintf(s, 128, cl, i);
928 strncat(s, "/coverage", 128);
929 fgSetString(s, "clear");
931 snprintf(s, 128, cl, i);
932 strncat(s, "/elevation-ft", 128);
933 fgSetDouble(s, -9999);
935 snprintf(s, 128, cl, i);
936 strncat(s, "/thickness-ft", 128);
939 snprintf(s, 128, cl, i);
940 strncat(s, "/span-m", 128);
941 fgSetDouble(s, 40000.0);
944 fgSetDouble("/environment/metar/rain-norm", m->getRain());
945 fgSetDouble("/environment/metar/hail-norm", m->getHail());
946 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
947 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
951 #if defined(ENABLE_THREADS)
953 FGMetarEnvironmentCtrl::thread_stop()
955 request_queue.push( string() ); // ask thread to terminate
960 FGMetarEnvironmentCtrl::MetarThread::run()
964 string icao = fetcher->request_queue.pop();
967 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
968 FGMetarResult result = fetcher->fetch_data( icao );
969 fetcher->result_queue.push( result );
972 #endif // ENABLE_THREADS
975 // end of environment_ctrl.cxx