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 ),
351 MaxCloudInterpolationDeltaFt( 4000.0 ),
358 #if defined(ENABLE_THREADS)
359 thread = new MetarThread(this);
360 thread->setProcessorAffinity(1);
362 #endif // ENABLE_THREADS
365 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
367 #if defined(ENABLE_THREADS)
369 #endif // ENABLE_THREADS
376 // use a "command" to set station temp at station elevation
377 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
379 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
380 node->setFloatValue( temp_degc );
381 node = args.getNode("altitude-ft", 0, true);
382 node->setFloatValue( altitude_ft );
383 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
387 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
389 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
390 node->setFloatValue( dewpoint_degc );
391 node = args.getNode("altitude-ft", 0, true);
392 node->setFloatValue( altitude_ft );
393 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
398 FGMetarEnvironmentCtrl::update_env_config ()
409 // If we aren't in the METAR scenario, don't attempt to interpolate.
410 if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR")) return;
413 // Generate interpolated values between the METAR and the current
416 // Pick up the METAR wind values and convert them into a vector.
418 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
419 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
421 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
422 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
424 // Convert the current wind values and convert them into a vector
426 double current_speed =
427 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
428 double current_heading = fgGetDouble(
429 "/environment/config/boundary/entry/wind-from-heading-deg");
431 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
432 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
434 // Determine the maximum component-wise value that the wind can change.
435 // First we determine the fraction in the X and Y component, then
436 // factor by the maximum wind change.
437 double x = fabs(current[0] - metar[0]);
438 double y = fabs(current[1] - metar[1]);
440 // only interpolate if we have a difference
442 double dx = x / (x + y);
445 double maxdx = dx * MaxWindChangeKtsSec;
446 double maxdy = dy * MaxWindChangeKtsSec;
448 // Interpolate each component separately.
449 current[0] = interpolate_val(current[0], metar[0], maxdx);
450 current[1] = interpolate_val(current[1], metar[1], maxdy);
453 // Now convert back to polar coordinates.
454 if ((current[0] == 0.0) && (current[1] == 0.0)) {
455 // Special case where there is no wind (otherwise atan2 barfs)
457 dir_from = current_heading;
460 // Some real wind to convert back from. Work out the speed
461 // and direction value in degrees.
462 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
463 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
465 // Normalize the direction.
469 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
472 // Now handle the visibility. We convert both visibility values
473 // to X-values, then interpolate from there, then back to real values.
474 // The length_scale is fixed to 1000m, so the visibility changes by
475 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
476 // whichever is more.
478 fgGetDouble("/environment/config/boundary/entry/visibility-m");
479 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
480 double currentxval = log(1000.0 + currentvis);
481 double metarxval = log(1000.0 + metarvis);
483 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
485 // Now convert back from an X-value to a straightforward visibility.
486 vis = exp(currentxval) - 1000.0;
488 pressure = interpolate_prop(
489 "/environment/config/boundary/entry/pressure-sea-level-inhg",
490 "/environment/metar/pressure-inhg",
491 MaxPressureChangeInHgSec);
493 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
494 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
495 temp = fgGetDouble("/environment/metar/temperature-degc");
496 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
498 // Set the cloud layers by interpolating over the METAR versions.
499 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
501 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
502 vector<SGPropertyNode_ptr>::const_iterator layer;
503 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
505 const char *cl = "/environment/clouds/layer[%i]";
506 double aircraft_alt = fgGetDouble("/position/altitude-ft");
510 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
514 // In the case of clouds, we want to avoid writing if nothing has
515 // changed, as these properties are tied to the renderer and will
516 // cause the clouds to be updated, reseting the texture locations.
518 // We don't interpolate the coverage values as no-matter how we
519 // do it, it will be quite a sudden change of texture. Better to
520 // have a single change than four or five.
521 snprintf(s, 128, cl, i);
522 strncat(s, "/coverage", 128);
523 const char* coverage = (*layer)->getStringValue("coverage", "clear");
524 if (strncmp(fgGetString(s), coverage, 128) != 0)
525 fgSetString(s, coverage);
527 snprintf(s, 128, cl, i);
528 strncat(s, "/elevation-ft", 128);
529 double current_alt = fgGetDouble(s);
530 double required_alt = (*layer)->getDoubleValue("elevation-ft");
532 if (current_alt < -9000 || required_alt < -9000 ||
533 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
534 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
535 // We don't interpolate any layers that are
536 // - too far above us to be visible
537 // - too far below us to be visible
538 // - with too large a difference to make interpolation sensible
539 // - to or from -9999 (used as a placeholder)
540 // - any values that are too high above us,
541 snprintf(s, 128, cl, i);
542 strncat(s, "/elevation-ft", 128);
543 if (current_alt != required_alt)
544 fgSetDouble(s, required_alt);
546 snprintf(s, 128, cl, i);
547 strncat(s, "/thickness-ft", 128);
548 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
549 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
552 // Interpolate the other values in the usual way
553 if (current_alt != required_alt) {
554 current_alt = interpolate_val(current_alt,
556 MaxCloudAltitudeChangeFtSec);
557 fgSetDouble(s, current_alt);
560 snprintf(s, 128, cl, i);
561 strncat(s, "/thickness-ft", 128);
562 currentval = fgGetDouble(s);
563 requiredval = (*layer)->getDoubleValue("thickness-ft");
565 if (currentval != requiredval) {
566 currentval = interpolate_val(currentval,
568 MaxCloudThicknessChangeFtSec);
569 fgSetDouble(s, currentval);
575 // We haven't already loaded a METAR, so apply it immediately.
576 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
577 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
578 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
579 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
580 vis = fgGetDouble("/environment/metar/min-visibility-m");
581 pressure = fgGetDouble("/environment/metar/pressure-inhg");
582 temp = fgGetDouble("/environment/metar/temperature-degc");
583 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
585 // Set the cloud layers by copying over the METAR versions.
586 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds", true);
588 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
589 vector<SGPropertyNode_ptr>::const_iterator layer;
590 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
592 const char *cl = "/environment/clouds/layer[%i]";
596 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
597 snprintf(s, 128, cl, i);
598 strncat(s, "/coverage", 128);
599 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
601 snprintf(s, 128, cl, i);
602 strncat(s, "/elevation-ft", 128);
603 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
605 snprintf(s, 128, cl, i);
606 strncat(s, "/thickness-ft", 128);
607 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
609 snprintf(s, 128, cl, i);
610 strncat(s, "/span-m", 128);
611 fgSetDouble(s, 40000.0);
614 // Force an update of the 3D clouds
615 fgSetDouble("/environment/rebuild-layers", 1.0);
618 fgSetupWind(dir_from, dir_to, speed, gust);
619 fgDefaultWeatherValue("visibility-m", vis);
620 set_temp_at_altitude(temp, station_elevation_ft);
621 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
622 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
624 // We've now successfully loaded a METAR into the configuration
628 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
629 const char * requiredname,
632 double currentval = fgGetDouble(currentname);
633 double requiredval = fgGetDouble(requiredname);
634 return interpolate_val(currentval, requiredval, dt);
637 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
641 double dval = EnvironmentUpdatePeriodSec * dt;
643 if (fabs(currentval - requiredval) < dval) return requiredval;
644 if (currentval < requiredval) return (currentval + dval);
645 if (currentval > requiredval) return (currentval - dval);
650 FGMetarEnvironmentCtrl::init ()
652 const SGPropertyNode *longitude
653 = fgGetNode( "/position/longitude-deg", true );
654 const SGPropertyNode *latitude
655 = fgGetNode( "/position/latitude-deg", true );
657 metar_loaded = false;
658 bool found_metar = false;
659 long max_age = metar_max_age->getLongValue();
660 // Don't check max age during init so that we don't loop over a lot
661 // of airports metar if there is a problem.
662 // The update() calls will find a correct metar if things went wrong here
663 metar_max_age->setLongValue(0);
665 while ( !found_metar && (_error_count < 3) ) {
666 const FGAirport* a = globals->get_airports()
667 ->search( longitude->getDoubleValue(),
668 latitude->getDoubleValue(),
672 FGMetarResult result = fetch_data( a->getId() );
673 if ( result.m != NULL ) {
674 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
678 search_elapsed = 0.0;
680 update_metar_properties( result.m );
685 // mark as no metar so it doesn't show up in subsequent
687 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
689 globals->get_airports()->no_metar( a->getId() );
693 metar_max_age->setLongValue(max_age);
697 FGMetarEnvironmentCtrl::reinit ()
701 metar_loaded = false;
707 FGMetarEnvironmentCtrl::update(double delta_time_sec)
710 _dt += delta_time_sec;
711 if (_error_count >= 3)
714 FGMetarResult result;
716 static const SGPropertyNode *longitude
717 = fgGetNode( "/position/longitude-deg", true );
718 static const SGPropertyNode *latitude
719 = fgGetNode( "/position/latitude-deg", true );
720 search_elapsed += delta_time_sec;
721 fetch_elapsed += delta_time_sec;
722 interpolate_elapsed += delta_time_sec;
724 // if time for a new search request, push it onto the request
726 if ( search_elapsed > search_interval_sec ) {
727 const FGAirport* a = globals->get_airports()
728 ->search( longitude->getDoubleValue(),
729 latitude->getDoubleValue(),
733 if ( !last_apt || last_apt->getId() != a->getId()
734 || fetch_elapsed > same_station_interval_sec )
736 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
738 request_queue.push( a->getId() );
741 search_elapsed = 0.0;
744 search_elapsed = 0.0;
745 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
746 << same_station_interval_sec - fetch_elapsed );
749 SG_LOG( SG_GENERAL, SG_WARN,
750 "Unable to find any airports with metar" );
752 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
753 // Interpolate the current configuration closer to the actual METAR
756 interpolate_elapsed = 0.0;
759 #if !defined(ENABLE_THREADS)
760 // No loader thread running so manually fetch the data
762 while ( !request_queue.empty() ) {
763 id = request_queue.front();
768 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
769 result = fetch_data( id );
770 result_queue.push( result );
772 #endif // ENABLE_THREADS
774 // process any results from the loader.
775 while ( !result_queue.empty() ) {
776 result = result_queue.front();
778 if ( result.m != NULL ) {
779 update_metar_properties( result.m );
784 // mark as no metar so it doesn't show up in subsequent
785 // searches, and signal an immediate re-search.
786 SG_LOG( SG_GENERAL, SG_WARN,
787 "no metar at station = " << result.icao );
788 globals->get_airports()->no_metar( result.icao );
789 search_elapsed = 9999.0;
793 env->update(delta_time_sec);
798 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
800 env->setEnvironment(environment);
804 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
806 FGMetarResult result;
809 // if the last error was more than three seconds ago,
810 // then pretent nothing happened.
819 // fetch station elevation if exists
820 const FGAirport* a = globals->get_airports()->search( icao );
822 station_elevation_ft = a->getElevation();
825 // fetch current metar data
827 string host = proxy_host->getStringValue();
828 string auth = proxy_auth->getStringValue();
829 string port = proxy_port->getStringValue();
830 result.m = new FGMetar( icao, host, port, auth);
832 long max_age = metar_max_age->getLongValue();
833 long age = result.m->getAge_min();
834 if (max_age && age > max_age) {
835 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
839 if (++_stale_count > 10) {
841 throw sg_io_exception("More than 10 stale METAR messages in a row."
842 " Check your system time!");
847 } catch (const sg_io_exception& e) {
848 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
849 << e.getFormattedMessage().c_str() );
850 #if defined(ENABLE_THREADS)
851 if (_error_count++ >= 3) {
852 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
867 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
873 fgSetString("/environment/metar/real-metar", m->getData());
874 // don't update with real weather when we use a custom weather scenario
875 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
876 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
878 fgSetString("/environment/metar/last-metar", m->getData());
879 fgSetString("/environment/metar/station-id", m->getId());
880 fgSetDouble("/environment/metar/min-visibility-m",
881 m->getMinVisibility().getVisibility_m() );
882 fgSetDouble("/environment/metar/max-visibility-m",
883 m->getMaxVisibility().getVisibility_m() );
885 const SGMetarVisibility *dirvis = m->getDirVisibility();
886 for (i = 0; i < 8; i++, dirvis++) {
887 const char *min = "/environment/metar/visibility[%d]/min-m";
888 const char *max = "/environment/metar/visibility[%d]/max-m";
890 d = dirvis->getVisibility_m();
892 snprintf(s, 128, min, i);
894 snprintf(s, 128, max, i);
898 fgSetInt("/environment/metar/base-wind-range-from",
899 m->getWindRangeFrom() );
900 fgSetInt("/environment/metar/base-wind-range-to",
901 m->getWindRangeTo() );
902 fgSetDouble("/environment/metar/base-wind-speed-kt",
903 m->getWindSpeed_kt() );
904 fgSetDouble("/environment/metar/gust-wind-speed-kt",
905 m->getGustSpeed_kt() );
906 fgSetDouble("/environment/metar/temperature-degc",
907 m->getTemperature_C() );
908 fgSetDouble("/environment/metar/dewpoint-degc",
909 m->getDewpoint_C() );
910 fgSetDouble("/environment/metar/rel-humidity-norm",
911 m->getRelHumidity() );
912 fgSetDouble("/environment/metar/pressure-inhg",
913 m->getPressure_inHg() );
915 vector<SGMetarCloud> cv = m->getClouds();
916 vector<SGMetarCloud>::const_iterator cloud;
918 const char *cl = "/environment/metar/clouds/layer[%i]";
919 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
920 const char *coverage_string[5] =
921 { "clear", "few", "scattered", "broken", "overcast" };
922 const double thickness[5] = { 0, 65, 600,750, 1000};
925 snprintf(s, 128, cl, i);
926 strncat(s, "/coverage", 128);
927 q = cloud->getCoverage();
928 fgSetString(s, coverage_string[q] );
930 snprintf(s, 128, cl, i);
931 strncat(s, "/elevation-ft", 128);
932 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
934 snprintf(s, 128, cl, i);
935 strncat(s, "/thickness-ft", 128);
936 fgSetDouble(s, thickness[q]);
938 snprintf(s, 128, cl, i);
939 strncat(s, "/span-m", 128);
940 fgSetDouble(s, 40000.0);
943 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
944 snprintf(s, 128, cl, i);
945 strncat(s, "/coverage", 128);
946 fgSetString(s, "clear");
948 snprintf(s, 128, cl, i);
949 strncat(s, "/elevation-ft", 128);
950 fgSetDouble(s, -9999);
952 snprintf(s, 128, cl, i);
953 strncat(s, "/thickness-ft", 128);
956 snprintf(s, 128, cl, i);
957 strncat(s, "/span-m", 128);
958 fgSetDouble(s, 40000.0);
961 fgSetDouble("/environment/metar/rain-norm", m->getRain());
962 fgSetDouble("/environment/metar/hail-norm", m->getHail());
963 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
964 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
968 #if defined(ENABLE_THREADS)
970 FGMetarEnvironmentCtrl::thread_stop()
972 request_queue.push( string() ); // ask thread to terminate
977 FGMetarEnvironmentCtrl::MetarThread::run()
981 string icao = fetcher->request_queue.pop();
984 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
985 FGMetarResult result = fetcher->fetch_data( icao );
986 fetcher->result_queue.push( result );
989 #endif // ENABLE_THREADS
992 // end of environment_ctrl.cxx