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);
359 thread->setProcessorAffinity(1);
361 #endif // ENABLE_THREADS
364 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
366 #if defined(ENABLE_THREADS)
368 #endif // ENABLE_THREADS
375 // use a "command" to set station temp at station elevation
376 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
378 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
379 node->setFloatValue( temp_degc );
380 node = args.getNode("altitude-ft", 0, true);
381 node->setFloatValue( altitude_ft );
382 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
386 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
388 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
389 node->setFloatValue( dewpoint_degc );
390 node = args.getNode("altitude-ft", 0, true);
391 node->setFloatValue( altitude_ft );
392 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
397 FGMetarEnvironmentCtrl::update_env_config ()
409 // Generate interpolated values between the METAR and the current
412 // Pick up the METAR wind values and convert them into a vector.
414 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
415 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
417 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
418 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
420 // Convert the current wind values and convert them into a vector
422 double current_speed =
423 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
424 double current_heading = fgGetDouble(
425 "/environment/config/boundary/entry/wind-from-heading-deg");
427 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
428 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
430 // Determine the maximum component-wise value that the wind can change.
431 // First we determine the fraction in the X and Y component, then
432 // factor by the maximum wind change.
433 double x = fabs(current[0] - metar[0]);
434 double y = fabs(current[1] - metar[1]);
435 double dx = x / (x + y);
438 double maxdx = dx * MaxWindChangeKtsSec;
439 double maxdy = dy * MaxWindChangeKtsSec;
441 // Interpolate each component separately.
442 current[0] = interpolate_val(current[0], metar[0], maxdx);
443 current[1] = interpolate_val(current[1], metar[1], maxdy);
445 // Now convert back to polar coordinates.
446 if ((current[0] == 0.0) && (current[1] == 0.0)) {
447 // Special case where there is no wind (otherwise atan2 barfs)
449 dir_from = current_heading;
452 // Some real wind to convert back from. Work out the speed
453 // and direction value in degrees.
454 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
455 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
457 // Normalize the direction.
461 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
464 // Now handle the visibility. We convert both visibility values
465 // to X-values, then interpolate from there, then back to real values.
466 // The length_scale is fixed to 1000m, so the visibility changes by
467 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
468 // whichever is more.
470 fgGetDouble("/environment/config/boundary/entry/visibility-m");
471 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
472 double currentxval = log(1000.0 + currentvis);
473 double metarxval = log(1000.0 + metarvis);
475 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
477 // Now convert back from an X-value to a straightforward visibility.
478 vis = exp(currentxval) - 1000.0;
480 pressure = interpolate_prop(
481 "/environment/config/boundary/entry/pressure-sea-level-inhg",
482 "/environment/metar/pressure-inhg",
483 MaxPressureChangeInHgSec);
485 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
486 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
487 temp = fgGetDouble("/environment/metar/temperature-degc");
488 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
490 // Set the cloud layers by interpolating over the METAR versions.
491 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
493 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
494 vector<SGPropertyNode_ptr>::const_iterator layer;
495 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
497 const char *cl = "/environment/clouds/layer[%i]";
498 double aircraft_alt = fgGetDouble("/position/altitude-ft");
502 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
506 // In the case of clouds, we want to avoid writing if nothing has
507 // changed, as these properties are tied to the renderer and will
508 // cause the clouds to be updated, reseting the texture locations.
510 // We don't interpolate the coverage values as no-matter how we
511 // do it, it will be quite a sudden change of texture. Better to
512 // have a single change than four or five.
513 snprintf(s, 128, cl, i);
514 strncat(s, "/coverage", 128);
515 const char* coverage = (*layer)->getStringValue("coverage", "clear");
516 if (strncmp(fgGetString(s), coverage, 128) != 0)
517 fgSetString(s, coverage);
519 snprintf(s, 128, cl, i);
520 strncat(s, "/elevation-ft", 128);
521 double current_alt = fgGetDouble(s);
522 double required_alt = (*layer)->getDoubleValue("elevation-ft");
524 if (current_alt < -9000 || required_alt < -9000
525 || fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt) {
526 // We don't interpolate any values that are too high above us,
527 // or too far below us to be visible. Nor do we interpolate
528 // values to or from -9999, which is used as a placeholder
529 // when there isn't actually a cloud layer present.
530 snprintf(s, 128, cl, i);
531 strncat(s, "/elevation-ft", 128);
532 if (current_alt != required_alt)
533 fgSetDouble(s, required_alt);
535 snprintf(s, 128, cl, i);
536 strncat(s, "/thickness-ft", 128);
537 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
538 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
541 // Interpolate the other values in the usual way
542 if (current_alt != required_alt) {
543 current_alt = interpolate_val(current_alt,
545 MaxCloudAltitudeChangeFtSec);
546 fgSetDouble(s, current_alt);
549 snprintf(s, 128, cl, i);
550 strncat(s, "/thickness-ft", 128);
551 currentval = fgGetDouble(s);
552 requiredval = (*layer)->getDoubleValue("thickness-ft");
554 if (currentval != requiredval) {
555 currentval = interpolate_val(currentval,
557 MaxCloudThicknessChangeFtSec);
558 fgSetDouble(s, currentval);
564 // We haven't already loaded a METAR, so apply it immediately.
565 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
566 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
567 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
568 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
569 vis = fgGetDouble("/environment/metar/min-visibility-m");
570 pressure = fgGetDouble("/environment/metar/pressure-inhg");
571 temp = fgGetDouble("/environment/metar/temperature-degc");
572 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
574 // Set the cloud layers by copying over the METAR versions.
575 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
577 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
578 vector<SGPropertyNode_ptr>::const_iterator layer;
579 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
581 const char *cl = "/environment/clouds/layer[%i]";
585 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
586 snprintf(s, 128, cl, i);
587 strncat(s, "/coverage", 128);
588 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
590 snprintf(s, 128, cl, i);
591 strncat(s, "/elevation-ft", 128);
592 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
594 snprintf(s, 128, cl, i);
595 strncat(s, "/thickness-ft", 128);
596 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
598 snprintf(s, 128, cl, i);
599 strncat(s, "/span-m", 128);
600 fgSetDouble(s, 40000.0);
604 fgSetupWind(dir_from, dir_to, speed, gust);
605 fgDefaultWeatherValue("visibility-m", vis);
606 set_temp_at_altitude(temp, station_elevation_ft);
607 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
608 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
610 // We've now successfully loaded a METAR into the configuration
614 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
615 const char * requiredname,
618 double currentval = fgGetDouble(currentname);
619 double requiredval = fgGetDouble(requiredname);
620 return interpolate_val(currentval, requiredval, dt);
623 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
627 double dval = EnvironmentUpdatePeriodSec * dt;
629 if (fabs(currentval - requiredval) < dval) return requiredval;
630 if (currentval < requiredval) return (currentval + dval);
631 if (currentval > requiredval) return (currentval - dval);
636 FGMetarEnvironmentCtrl::init ()
638 const SGPropertyNode *longitude
639 = fgGetNode( "/position/longitude-deg", true );
640 const SGPropertyNode *latitude
641 = fgGetNode( "/position/latitude-deg", true );
643 metar_loaded = false;
644 bool found_metar = false;
645 long max_age = metar_max_age->getLongValue();
646 // Don't check max age during init so that we don't loop over a lot
647 // of airports metar if there is a problem.
648 // The update() calls will find a correct metar if things went wrong here
649 metar_max_age->setLongValue(0);
651 while ( !found_metar && (_error_count < 3) ) {
652 const FGAirport* a = globals->get_airports()
653 ->search( longitude->getDoubleValue(),
654 latitude->getDoubleValue(),
657 FGMetarResult result = fetch_data( a->getId() );
658 if ( result.m != NULL ) {
659 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
663 search_elapsed = 0.0;
665 update_metar_properties( result.m );
670 // mark as no metar so it doesn't show up in subsequent
672 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
674 globals->get_airports()->no_metar( a->getId() );
678 metar_max_age->setLongValue(max_age);
682 FGMetarEnvironmentCtrl::reinit ()
695 FGMetarEnvironmentCtrl::update(double delta_time_sec)
698 _dt += delta_time_sec;
699 if (_error_count >= 3)
702 FGMetarResult result;
704 static const SGPropertyNode *longitude
705 = fgGetNode( "/position/longitude-deg", true );
706 static const SGPropertyNode *latitude
707 = fgGetNode( "/position/latitude-deg", true );
708 search_elapsed += delta_time_sec;
709 fetch_elapsed += delta_time_sec;
710 interpolate_elapsed += delta_time_sec;
712 // if time for a new search request, push it onto the request
714 if ( search_elapsed > search_interval_sec ) {
715 const FGAirport* a = globals->get_airports()
716 ->search( longitude->getDoubleValue(),
717 latitude->getDoubleValue(),
720 if ( !last_apt || last_apt->getId() != a->getId()
721 || fetch_elapsed > same_station_interval_sec )
723 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
725 request_queue.push( a->getId() );
728 search_elapsed = 0.0;
731 search_elapsed = 0.0;
732 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
733 << same_station_interval_sec - fetch_elapsed );
736 SG_LOG( SG_GENERAL, SG_WARN,
737 "Unable to find any airports with metar" );
739 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
740 // Interpolate the current configuration closer to the actual METAR
743 interpolate_elapsed = 0.0;
746 #if !defined(ENABLE_THREADS)
747 // No loader thread running so manually fetch the data
749 while ( !request_queue.empty() ) {
750 id = request_queue.front();
755 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
756 result = fetch_data( id );
757 result_queue.push( result );
759 #endif // ENABLE_THREADS
761 // process any results from the loader.
762 while ( !result_queue.empty() ) {
763 result = result_queue.front();
765 if ( result.m != NULL ) {
766 update_metar_properties( result.m );
771 // mark as no metar so it doesn't show up in subsequent
772 // searches, and signal an immediate re-search.
773 SG_LOG( SG_GENERAL, SG_WARN,
774 "no metar at station = " << result.icao );
775 globals->get_airports()->no_metar( result.icao );
776 search_elapsed = 9999.0;
780 env->update(delta_time_sec);
785 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
787 env->setEnvironment(environment);
791 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
793 FGMetarResult result;
796 // if the last error was more than three seconds ago,
797 // then pretent nothing happened.
806 // fetch station elevation if exists
807 const FGAirport* a = globals->get_airports()->search( icao );
809 station_elevation_ft = a->getElevation();
812 // fetch current metar data
814 string host = proxy_host->getStringValue();
815 string auth = proxy_auth->getStringValue();
816 string port = proxy_port->getStringValue();
817 result.m = new FGMetar( icao, host, port, auth);
819 long max_age = metar_max_age->getLongValue();
820 long age = result.m->getAge_min();
821 if (max_age && age > max_age) {
822 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
826 if (++_stale_count > 10) {
828 throw sg_io_exception("More than 10 stale METAR messages in a row."
829 " Check your system time!");
834 } catch (const sg_io_exception& e) {
835 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
836 << e.getFormattedMessage().c_str() );
837 #if defined(ENABLE_THREADS)
838 if (_error_count++ >= 3) {
839 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
854 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
860 fgSetString("/environment/metar/real-metar", m->getData());
861 // don't update with real weather when we use a custom weather scenario
862 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
863 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
865 fgSetString("/environment/metar/last-metar", m->getData());
866 fgSetString("/environment/metar/station-id", m->getId());
867 fgSetDouble("/environment/metar/min-visibility-m",
868 m->getMinVisibility().getVisibility_m() );
869 fgSetDouble("/environment/metar/max-visibility-m",
870 m->getMaxVisibility().getVisibility_m() );
872 const SGMetarVisibility *dirvis = m->getDirVisibility();
873 for (i = 0; i < 8; i++, dirvis++) {
874 const char *min = "/environment/metar/visibility[%d]/min-m";
875 const char *max = "/environment/metar/visibility[%d]/max-m";
877 d = dirvis->getVisibility_m();
879 snprintf(s, 128, min, i);
881 snprintf(s, 128, max, i);
885 fgSetInt("/environment/metar/base-wind-range-from",
886 m->getWindRangeFrom() );
887 fgSetInt("/environment/metar/base-wind-range-to",
888 m->getWindRangeTo() );
889 fgSetDouble("/environment/metar/base-wind-speed-kt",
890 m->getWindSpeed_kt() );
891 fgSetDouble("/environment/metar/gust-wind-speed-kt",
892 m->getGustSpeed_kt() );
893 fgSetDouble("/environment/metar/temperature-degc",
894 m->getTemperature_C() );
895 fgSetDouble("/environment/metar/dewpoint-degc",
896 m->getDewpoint_C() );
897 fgSetDouble("/environment/metar/rel-humidity-norm",
898 m->getRelHumidity() );
899 fgSetDouble("/environment/metar/pressure-inhg",
900 m->getPressure_inHg() );
902 vector<SGMetarCloud> cv = m->getClouds();
903 vector<SGMetarCloud>::const_iterator cloud;
905 const char *cl = "/environment/metar/clouds/layer[%i]";
906 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
907 const char *coverage_string[5] =
908 { "clear", "few", "scattered", "broken", "overcast" };
909 const double thickness[5] = { 0, 65, 600,750, 1000};
912 snprintf(s, 128, cl, i);
913 strncat(s, "/coverage", 128);
914 q = cloud->getCoverage();
915 fgSetString(s, coverage_string[q] );
917 snprintf(s, 128, cl, i);
918 strncat(s, "/elevation-ft", 128);
919 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
921 snprintf(s, 128, cl, i);
922 strncat(s, "/thickness-ft", 128);
923 fgSetDouble(s, thickness[q]);
925 snprintf(s, 128, cl, i);
926 strncat(s, "/span-m", 128);
927 fgSetDouble(s, 40000.0);
930 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
931 snprintf(s, 128, cl, i);
932 strncat(s, "/coverage", 128);
933 fgSetString(s, "clear");
935 snprintf(s, 128, cl, i);
936 strncat(s, "/elevation-ft", 128);
937 fgSetDouble(s, -9999);
939 snprintf(s, 128, cl, i);
940 strncat(s, "/thickness-ft", 128);
943 snprintf(s, 128, cl, i);
944 strncat(s, "/span-m", 128);
945 fgSetDouble(s, 40000.0);
948 fgSetDouble("/environment/metar/rain-norm", m->getRain());
949 fgSetDouble("/environment/metar/hail-norm", m->getHail());
950 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
951 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
955 #if defined(ENABLE_THREADS)
957 FGMetarEnvironmentCtrl::thread_stop()
959 request_queue.push( string() ); // ask thread to terminate
964 FGMetarEnvironmentCtrl::MetarThread::run()
968 string icao = fetcher->request_queue.pop();
971 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
972 FGMetarResult result = fetcher->fetch_data( icao );
973 fetcher->result_queue.push( result );
976 #endif // ENABLE_THREADS
979 // end of environment_ctrl.cxx