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.
29 #include <simgear/debug/logstream.hxx>
30 #include <simgear/structure/commands.hxx>
31 #include <simgear/structure/exception.hxx>
33 #include <Airports/simple.hxx>
34 #include <Main/fg_props.hxx>
35 #include <Main/util.hxx>
37 #include "fgmetar.hxx"
38 #include "environment_mgr.hxx"
39 #include "environment_ctrl.hxx"
43 class AirportWithMetar : public FGAirport::AirportFilter
46 virtual bool passAirport(FGAirport* aApt) const
48 return aApt->getMetar();
52 ////////////////////////////////////////////////////////////////////////
53 // Implementation of FGEnvironmentCtrl abstract base class.
54 ////////////////////////////////////////////////////////////////////////
56 FGEnvironmentCtrl::FGEnvironmentCtrl ()
64 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
69 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
71 _environment = environment;
75 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
81 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
87 FGEnvironmentCtrl::setElevationFt (double elev_ft)
93 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
102 ////////////////////////////////////////////////////////////////////////
103 // Implementation of FGUserDefEnvironmentCtrl.
104 ////////////////////////////////////////////////////////////////////////
106 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
107 : _base_wind_speed_node(0),
108 _gust_wind_speed_node(0),
109 _current_wind_speed_kt(0),
110 _delta_wind_speed_kt(0)
114 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
119 FGUserDefEnvironmentCtrl::init ()
121 // Fill in some defaults.
122 if (!fgHasNode("/environment/params/base-wind-speed-kt"))
123 fgSetDouble("/environment/params/base-wind-speed-kt",
124 fgGetDouble("/environment/wind-speed-kt"));
125 if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
126 fgSetDouble("/environment/params/gust-wind-speed-kt",
127 fgGetDouble("/environment/params/base-wind-speed-kt"));
129 _base_wind_speed_node =
130 fgGetNode("/environment/params/base-wind-speed-kt", true);
131 _gust_wind_speed_node =
132 fgGetNode("/environment/params/gust-wind-speed-kt", true);
134 _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
135 _delta_wind_speed_kt = 0.1;
139 FGUserDefEnvironmentCtrl::update (double dt)
141 double base_wind_speed = _base_wind_speed_node->getDoubleValue();
142 double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
144 if (gust_wind_speed < base_wind_speed) {
145 gust_wind_speed = base_wind_speed;
146 _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
149 if (base_wind_speed == gust_wind_speed) {
150 _current_wind_speed_kt = base_wind_speed;
152 int rn = rand() % 128;
153 int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
154 double gust = _current_wind_speed_kt - base_wind_speed;
155 double incr = gust / 50;
158 _delta_wind_speed_kt = - _delta_wind_speed_kt;
160 _delta_wind_speed_kt -= incr * sign;
162 _delta_wind_speed_kt += incr * sign;
164 _current_wind_speed_kt += _delta_wind_speed_kt;
166 if (_current_wind_speed_kt < base_wind_speed) {
167 _current_wind_speed_kt = base_wind_speed;
168 _delta_wind_speed_kt = 0.01;
169 } else if (_current_wind_speed_kt > gust_wind_speed) {
170 _current_wind_speed_kt = gust_wind_speed;
171 _delta_wind_speed_kt = -0.01;
175 if (_environment != 0)
176 _environment->set_wind_speed_kt(_current_wind_speed_kt);
181 ////////////////////////////////////////////////////////////////////////
182 // Implementation of FGInterpolateEnvironmentCtrl.
183 ////////////////////////////////////////////////////////////////////////
185 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
189 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
192 for (i = 0; i < _boundary_table.size(); i++)
193 delete _boundary_table[i];
194 for (i = 0; i < _aloft_table.size(); i++)
195 delete _aloft_table[i];
201 FGInterpolateEnvironmentCtrl::init ()
203 read_table(fgGetNode("/environment/config/boundary", true),
205 read_table(fgGetNode("/environment/config/aloft", true),
210 FGInterpolateEnvironmentCtrl::reinit ()
213 for (i = 0; i < _boundary_table.size(); i++)
214 delete _boundary_table[i];
215 for (i = 0; i < _aloft_table.size(); i++)
216 delete _aloft_table[i];
217 _boundary_table.clear();
218 _aloft_table.clear();
223 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
224 vector<bucket *> &table)
226 for (int i = 0; i < node->nChildren(); i++) {
227 const SGPropertyNode * child = node->getChild(i);
228 if ( strcmp(child->getName(), "entry") == 0
229 && child->getStringValue("elevation-ft", "")[0] != '\0'
230 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
232 bucket * b = new bucket;
234 b->environment.copy(table[i-1]->environment);
235 b->environment.read(child);
236 b->altitude_ft = b->environment.get_elevation_ft();
240 sort(table.begin(), table.end(), bucket::lessThan);
244 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
247 double altitude_ft = fgGetDouble("/position/altitude-ft");
248 double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
249 double boundary_transition =
250 fgGetDouble("/environment/config/boundary-transition-ft", 500);
252 // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
254 int length = _boundary_table.size();
258 double boundary_limit = _boundary_table[length-1]->altitude_ft;
259 if (boundary_limit >= altitude_agl_ft) {
260 do_interpolate(_boundary_table, altitude_agl_ft,
263 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
265 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
266 do_interpolate(_aloft_table, altitude_ft, &env2);
268 (altitude_agl_ft - boundary_limit) / boundary_transition;
269 interpolate(&env1, &env2, fraction, _environment);
275 do_interpolate(_aloft_table, altitude_ft, _environment);
279 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
281 FGEnvironment * environment)
283 int length = table.size();
287 // Boundary conditions
288 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
289 environment->copy(table[0]->environment);
291 } else if (table[length-1]->altitude_ft <= altitude_ft) {
292 environment->copy(table[length-1]->environment);
296 // Search the interpolation table
297 for (int i = 0; i < length - 1; i++) {
298 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
299 FGEnvironment * env1 = &(table[i]->environment);
300 FGEnvironment * env2 = &(table[i+1]->environment);
302 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
306 ((altitude_ft - table[i]->altitude_ft) /
307 (table[i+1]->altitude_ft - table[i]->altitude_ft));
308 interpolate(env1, env2, fraction, environment);
316 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
318 return (altitude_ft < b.altitude_ft);
322 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
324 return (a->altitude_ft) < (b->altitude_ft);
328 ////////////////////////////////////////////////////////////////////////
329 // Implementation of FGMetarEnvironmentCtrl.
330 ////////////////////////////////////////////////////////////////////////
332 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
333 : env( new FGInterpolateEnvironmentCtrl ),
334 metar_loaded( false ),
335 search_interval_sec( 60.0 ), // 1 minute
336 same_station_interval_sec( 900.0 ), // 15 minutes
337 search_elapsed( 9999.0 ),
338 fetch_elapsed( 9999.0 ),
340 proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
341 proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
342 proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
343 metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
345 // Interpolation constant definitions.
346 EnvironmentUpdatePeriodSec( 0.2 ),
347 MaxWindChangeKtsSec( 0.2 ),
348 MaxVisChangePercentSec( 0.05 ),
349 MaxPressureChangeInHgSec( 0.0033 ),
350 MaxCloudAltitudeChangeFtSec( 20.0 ),
351 MaxCloudThicknessChangeFtSec( 50.0 ),
352 MaxCloudInterpolationHeightFt( 5000.0 ),
353 MaxCloudInterpolationDeltaFt( 4000.0 ),
360 #if defined(ENABLE_THREADS)
361 thread = new MetarThread(this);
362 thread->setProcessorAffinity(1);
364 #endif // ENABLE_THREADS
367 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
369 #if defined(ENABLE_THREADS)
372 #endif // ENABLE_THREADS
378 // use a "command" to set station temp at station elevation
379 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
381 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
382 node->setFloatValue( temp_degc );
383 node = args.getNode("altitude-ft", 0, true);
384 node->setFloatValue( altitude_ft );
385 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
389 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
391 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
392 node->setFloatValue( dewpoint_degc );
393 node = args.getNode("altitude-ft", 0, true);
394 node->setFloatValue( altitude_ft );
395 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
400 FGMetarEnvironmentCtrl::update_env_config ()
402 // If we aren't in the METAR scenario, don't attempt to interpolate.
403 if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR"))
415 const SGPropertyNode *metar_clouds = fgGetNode("/environment/metar/clouds", true);
416 SGPropertyNode *clouds = fgGetNode("/environment/clouds", true);
419 // Generate interpolated values between the METAR and the current
422 // Pick up the METAR wind values and convert them into a vector.
424 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
425 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
427 metar[0] = metar_speed * sin(metar_heading * M_PI / 180.0);
428 metar[1] = metar_speed * cos(metar_heading * M_PI / 180.0);
430 // Convert the current wind values and convert them into a vector
432 double current_speed =
433 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
434 double current_heading = fgGetDouble(
435 "/environment/config/boundary/entry/wind-from-heading-deg");
437 current[0] = current_speed * sin(current_heading * M_PI / 180.0);
438 current[1] = current_speed * cos(current_heading * M_PI / 180.0);
440 // Determine the maximum component-wise value that the wind can change.
441 // First we determine the fraction in the X and Y component, then
442 // factor by the maximum wind change.
443 double x = fabs(current[0] - metar[0]);
444 double y = fabs(current[1] - metar[1]);
446 // only interpolate if we have a difference
448 double dx = x / (x + y);
451 double maxdx = dx * MaxWindChangeKtsSec;
452 double maxdy = dy * MaxWindChangeKtsSec;
454 // Interpolate each component separately.
455 current[0] = interpolate_val(current[0], metar[0], maxdx);
456 current[1] = interpolate_val(current[1], metar[1], maxdy);
459 // Now convert back to polar coordinates.
460 if ((current[0] == 0.0) && (current[1] == 0.0)) {
461 // Special case where there is no wind (otherwise atan2 barfs)
463 dir_from = current_heading;
466 // Some real wind to convert back from. Work out the speed
467 // and direction value in degrees.
468 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
469 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
471 // Normalize the direction.
475 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
478 // Now handle the visibility. We convert both visibility values
479 // to X-values, then interpolate from there, then back to real values.
480 // The length_scale is fixed to 1000m, so the visibility changes by
481 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
482 // whichever is more.
484 fgGetDouble("/environment/config/boundary/entry/visibility-m");
485 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
486 double currentxval = log(1000.0 + currentvis);
487 double metarxval = log(1000.0 + metarvis);
489 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
491 // Now convert back from an X-value to a straightforward visibility.
492 vis = exp(currentxval) - 1000.0;
494 pressure = interpolate_prop(
495 "/environment/config/boundary/entry/pressure-sea-level-inhg",
496 "/environment/metar/pressure-inhg",
497 MaxPressureChangeInHgSec);
499 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
500 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
501 temp = fgGetDouble("/environment/metar/temperature-degc");
502 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
504 // Set the cloud layers by interpolating over the METAR versions.
505 vector<SGPropertyNode_ptr> layers = metar_clouds->getChildren("layer");
506 vector<SGPropertyNode_ptr>::const_iterator layer;
507 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
509 double aircraft_alt = fgGetDouble("/position/altitude-ft");
512 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
513 SGPropertyNode *target = clouds->getChild("layer", i, true);
515 // In the case of clouds, we want to avoid writing if nothing has
516 // changed, as these properties are tied to the renderer and will
517 // cause the clouds to be updated, reseting the texture locations.
519 // We don't interpolate the coverage values as no-matter how we
520 // do it, it will be quite a sudden change of texture. Better to
521 // have a single change than four or five.
522 const char *coverage = (*layer)->getStringValue("coverage", "clear");
523 SGPropertyNode *cov = target->getNode("coverage", true);
524 if (strcmp(cov->getStringValue(), coverage) != 0)
525 cov->setStringValue(coverage);
527 double required_alt = (*layer)->getDoubleValue("elevation-ft");
528 double current_alt = target->getDoubleValue("elevation-ft");
529 double required_thickness = (*layer)->getDoubleValue("thickness-ft");
530 SGPropertyNode *thickness = target->getNode("thickness-ft", true);
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 if (current_alt != required_alt)
542 target->setDoubleValue("elevation-ft", required_alt);
544 if (thickness->getDoubleValue() != required_thickness)
545 thickness->setDoubleValue(required_thickness);
548 // Interpolate the other values in the usual way
549 if (current_alt != required_alt) {
550 current_alt = interpolate_val(current_alt,
552 MaxCloudAltitudeChangeFtSec);
553 target->setDoubleValue("elevation-ft", current_alt);
556 double current_thickness = thickness->getDoubleValue();
558 if (current_thickness != required_thickness) {
559 current_thickness = interpolate_val(current_thickness,
561 MaxCloudThicknessChangeFtSec);
562 thickness->setDoubleValue(current_thickness);
568 // We haven't already loaded a METAR, so apply it immediately.
569 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
570 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
571 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
572 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
573 vis = fgGetDouble("/environment/metar/min-visibility-m");
574 pressure = fgGetDouble("/environment/metar/pressure-inhg");
575 temp = fgGetDouble("/environment/metar/temperature-degc");
576 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
578 vector<SGPropertyNode_ptr> layers = metar_clouds->getChildren("layer");
579 vector<SGPropertyNode_ptr>::const_iterator layer;
580 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
583 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
584 SGPropertyNode *target = clouds->getChild("layer", i, true);
586 target->setStringValue("coverage",
587 (*layer)->getStringValue("coverage", "clear"));
588 target->setDoubleValue("elevation-ft",
589 (*layer)->getDoubleValue("elevation-ft"));
590 target->setDoubleValue("thickness-ft",
591 (*layer)->getDoubleValue("thickness-ft"));
592 target->setDoubleValue("span-m", 40000.0);
595 // Force an update of the 3D clouds
596 fgSetDouble("/environment/rebuild-layers", 1.0);
599 fgSetupWind(dir_from, dir_to, speed, gust);
600 fgDefaultWeatherValue("visibility-m", vis);
601 set_temp_at_altitude(temp, station_elevation_ft);
602 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
603 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
605 // We've now successfully loaded a METAR into the configuration
609 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
610 const char * requiredname,
613 double currentval = fgGetDouble(currentname);
614 double requiredval = fgGetDouble(requiredname);
615 return interpolate_val(currentval, requiredval, dt);
618 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
622 double dval = EnvironmentUpdatePeriodSec * dt;
624 if (fabs(currentval - requiredval) < dval) return requiredval;
625 if (currentval < requiredval) return (currentval + dval);
626 if (currentval > requiredval) return (currentval - dval);
631 FGMetarEnvironmentCtrl::init ()
633 SGGeod pos = SGGeod::fromDeg(
634 fgGetDouble("/position/longitude-deg", true),
635 fgGetDouble( "/position/latitude-deg", true));
637 metar_loaded = false;
638 bool found_metar = false;
639 long max_age = metar_max_age->getLongValue();
640 // Don't check max age during init so that we don't loop over a lot
641 // of airports metar if there is a problem.
642 // The update() calls will find a correct metar if things went wrong here
643 metar_max_age->setLongValue(0);
645 while ( !found_metar && (_error_count < 3) ) {
646 AirportWithMetar filter;
647 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
652 FGMetarResult result = fetch_data(a);
653 if ( result.m != NULL ) {
654 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
657 search_elapsed = 0.0;
659 update_metar_properties( result.m );
664 // mark as no metar so it doesn't show up in subsequent
666 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = " << a->ident() );
669 } // of airprot-with-metar search iteration
671 metar_max_age->setLongValue(max_age);
675 FGMetarEnvironmentCtrl::reinit ()
679 metar_loaded = false;
685 FGMetarEnvironmentCtrl::update(double delta_time_sec)
687 _dt += delta_time_sec;
688 if (_error_count >= 3)
691 FGMetarResult result;
693 static const SGPropertyNode *longitude
694 = fgGetNode( "/position/longitude-deg", true );
695 static const SGPropertyNode *latitude
696 = fgGetNode( "/position/latitude-deg", true );
697 SGGeod pos = SGGeod::fromDeg(longitude->getDoubleValue(),
698 latitude->getDoubleValue());
700 search_elapsed += delta_time_sec;
701 fetch_elapsed += delta_time_sec;
702 interpolate_elapsed += delta_time_sec;
704 // if time for a new search request, push it onto the request
706 if ( search_elapsed > search_interval_sec ) {
707 AirportWithMetar filter;
708 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
710 if ( !last_apt || last_apt->ident() != a->ident()
711 || fetch_elapsed > same_station_interval_sec )
713 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
715 request_queue.push(a);
717 search_elapsed = 0.0;
720 search_elapsed = 0.0;
721 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
722 << same_station_interval_sec - fetch_elapsed );
726 SG_LOG( SG_GENERAL, SG_WARN,
727 "Unable to find any airports with metar" );
729 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
730 // Interpolate the current configuration closer to the actual METAR
733 interpolate_elapsed = 0.0;
736 #if !defined(ENABLE_THREADS)
737 // No loader thread running so manually fetch the data
738 FGAirport* apt = NULL;
739 while ( !request_queue.empty() ) {
740 apt = request_queue.front();
745 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << apt->ident() );
746 result = fetch_data( apt );
747 result_queue.push( result );
749 #endif // ENABLE_THREADS
751 // process any results from the loader.
752 while ( !result_queue.empty() ) {
753 result = result_queue.front();
755 if ( result.m != NULL ) {
756 update_metar_properties( result.m );
761 // mark as no metar so it doesn't show up in subsequent
762 // searches, and signal an immediate re-search.
763 SG_LOG( SG_GENERAL, SG_WARN,
764 "no metar at station = " << result.airport->ident() );
765 result.airport->setMetar(false);
766 search_elapsed = 9999.0;
770 env->update(delta_time_sec);
775 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
777 env->setEnvironment(environment);
781 FGMetarEnvironmentCtrl::fetch_data(FGAirport* apt)
783 FGMetarResult result;
784 result.airport = apt;
786 // if the last error was more than three seconds ago,
787 // then pretent nothing happened.
796 station_elevation_ft = apt->getElevation();
798 // fetch current metar data
800 string host = proxy_host->getStringValue();
801 string auth = proxy_auth->getStringValue();
802 string port = proxy_port->getStringValue();
803 result.m = new FGMetar( apt->ident(), host, port, auth);
805 long max_age = metar_max_age->getLongValue();
806 long age = result.m->getAge_min();
807 if (max_age && age > max_age) {
808 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
812 if (++_stale_count > 10) {
814 throw sg_io_exception("More than 10 stale METAR messages in a row."
815 " Check your system time!");
820 } catch (const sg_io_exception& e) {
821 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
822 << e.getFormattedMessage().c_str() );
823 #if defined(ENABLE_THREADS)
824 if (_error_count++ >= 3) {
825 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
840 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
844 fgSetString("/environment/metar/real-metar", m->getData());
845 // don't update with real weather when we use a custom weather scenario
846 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
847 if (strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
849 fgSetString("/environment/metar/last-metar", m->getData());
850 fgSetString("/environment/metar/station-id", m->getId());
851 fgSetDouble("/environment/metar/min-visibility-m",
852 m->getMinVisibility().getVisibility_m() );
853 fgSetDouble("/environment/metar/max-visibility-m",
854 m->getMaxVisibility().getVisibility_m() );
856 SGPropertyNode *metar = fgGetNode("/environment/metar", true);
857 const SGMetarVisibility *dirvis = m->getDirVisibility();
859 for (i = 0; i < 8; i++, dirvis++) {
860 SGPropertyNode *vis = metar->getChild("visibility", i, true);
861 double v = dirvis->getVisibility_m();
863 vis->setDoubleValue("min-m", v);
864 vis->setDoubleValue("max-m", v);
867 fgSetInt("/environment/metar/base-wind-range-from",
868 m->getWindRangeFrom() );
869 fgSetInt("/environment/metar/base-wind-range-to",
870 m->getWindRangeTo() );
871 fgSetDouble("/environment/metar/base-wind-speed-kt",
872 m->getWindSpeed_kt() );
873 fgSetDouble("/environment/metar/gust-wind-speed-kt",
874 m->getGustSpeed_kt() );
875 fgSetDouble("/environment/metar/temperature-degc",
876 m->getTemperature_C() );
877 fgSetDouble("/environment/metar/dewpoint-degc",
878 m->getDewpoint_C() );
879 fgSetDouble("/environment/metar/rel-humidity-norm",
880 m->getRelHumidity() );
881 fgSetDouble("/environment/metar/pressure-inhg",
882 m->getPressure_inHg() );
884 vector<SGMetarCloud> cv = m->getClouds();
885 vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
887 SGPropertyNode *metar_clouds = fgGetNode("/environment/metar/clouds", true);
889 for (i = 0, cloud = cv.begin(); i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
890 const char *coverage_string[5] = { "clear", "few", "scattered", "broken", "overcast" };
891 const double thickness_value[5] = { 0, 65, 600, 750, 1000 };
893 const char *coverage = "clear";
894 double elevation = -9999.0;
895 double thickness = 0.0;
896 const double span = 40000.0;
898 if (cloud != cloud_end) {
899 int c = cloud->getCoverage();
900 coverage = coverage_string[c];
901 elevation = cloud->getAltitude_ft() + station_elevation_ft;
902 thickness = thickness_value[c];
906 SGPropertyNode *layer;
907 layer = metar_clouds->getChild("layer", i, true);
908 layer->setStringValue("coverage", coverage);
909 layer->setDoubleValue("elevation-ft", elevation);
910 layer->setDoubleValue("thickness-ft", thickness);
911 layer->setDoubleValue("span-m", span);
913 fgSetDouble("/environment/metar/rain-norm", m->getRain());
914 fgSetDouble("/environment/metar/hail-norm", m->getHail());
915 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
916 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
920 #if defined(ENABLE_THREADS)
922 FGMetarEnvironmentCtrl::thread_stop()
924 request_queue.push(NULL); // ask thread to terminate
929 FGMetarEnvironmentCtrl::MetarThread::run()
933 FGAirport* apt = fetcher->request_queue.pop();
936 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << apt->ident() );
937 FGMetarResult result = fetcher->fetch_data( apt );
938 fetcher->result_queue.push( result );
941 #endif // ENABLE_THREADS
944 // end of environment_ctrl.cxx