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 "Environment/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)
371 #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 ()
411 // If we aren't in the METAR scenario, don't attempt to interpolate.
412 if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR")) return;
415 // Generate interpolated values between the METAR and the current
418 // Pick up the METAR wind values and convert them into a vector.
420 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
421 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
423 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
424 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
426 // Convert the current wind values and convert them into a vector
428 double current_speed =
429 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
430 double current_heading = fgGetDouble(
431 "/environment/config/boundary/entry/wind-from-heading-deg");
433 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
434 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
436 // Determine the maximum component-wise value that the wind can change.
437 // First we determine the fraction in the X and Y component, then
438 // factor by the maximum wind change.
439 double x = fabs(current[0] - metar[0]);
440 double y = fabs(current[1] - metar[1]);
442 // only interpolate if we have a difference
444 double dx = x / (x + y);
447 double maxdx = dx * MaxWindChangeKtsSec;
448 double maxdy = dy * MaxWindChangeKtsSec;
450 // Interpolate each component separately.
451 current[0] = interpolate_val(current[0], metar[0], maxdx);
452 current[1] = interpolate_val(current[1], metar[1], maxdy);
455 // Now convert back to polar coordinates.
456 if ((current[0] == 0.0) && (current[1] == 0.0)) {
457 // Special case where there is no wind (otherwise atan2 barfs)
459 dir_from = current_heading;
462 // Some real wind to convert back from. Work out the speed
463 // and direction value in degrees.
464 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
465 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
467 // Normalize the direction.
471 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
474 // Now handle the visibility. We convert both visibility values
475 // to X-values, then interpolate from there, then back to real values.
476 // The length_scale is fixed to 1000m, so the visibility changes by
477 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
478 // whichever is more.
480 fgGetDouble("/environment/config/boundary/entry/visibility-m");
481 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
482 double currentxval = log(1000.0 + currentvis);
483 double metarxval = log(1000.0 + metarvis);
485 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
487 // Now convert back from an X-value to a straightforward visibility.
488 vis = exp(currentxval) - 1000.0;
490 pressure = interpolate_prop(
491 "/environment/config/boundary/entry/pressure-sea-level-inhg",
492 "/environment/metar/pressure-inhg",
493 MaxPressureChangeInHgSec);
495 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
496 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
497 temp = fgGetDouble("/environment/metar/temperature-degc");
498 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
500 // Set the cloud layers by interpolating over the METAR versions.
501 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
503 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
504 vector<SGPropertyNode_ptr>::const_iterator layer;
505 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
507 const char *cl = "/environment/clouds/layer[%i]";
508 double aircraft_alt = fgGetDouble("/position/altitude-ft");
512 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
516 // In the case of clouds, we want to avoid writing if nothing has
517 // changed, as these properties are tied to the renderer and will
518 // cause the clouds to be updated, reseting the texture locations.
520 // We don't interpolate the coverage values as no-matter how we
521 // do it, it will be quite a sudden change of texture. Better to
522 // have a single change than four or five.
523 snprintf(s, 128, cl, i);
524 strncat(s, "/coverage", 128);
525 const char* coverage = (*layer)->getStringValue("coverage", "clear");
526 if (strncmp(fgGetString(s), coverage, 128) != 0)
527 fgSetString(s, coverage);
529 snprintf(s, 128, cl, i);
530 strncat(s, "/elevation-ft", 128);
531 double current_alt = fgGetDouble(s);
532 double required_alt = (*layer)->getDoubleValue("elevation-ft");
534 if (current_alt < -9000 || required_alt < -9000 ||
535 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
536 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
537 // We don't interpolate any layers that are
538 // - too far above us to be visible
539 // - too far below us to be visible
540 // - with too large a difference to make interpolation sensible
541 // - to or from -9999 (used as a placeholder)
542 // - any values that are too high above us,
543 snprintf(s, 128, cl, i);
544 strncat(s, "/elevation-ft", 128);
545 if (current_alt != required_alt)
546 fgSetDouble(s, required_alt);
548 snprintf(s, 128, cl, i);
549 strncat(s, "/thickness-ft", 128);
550 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
551 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
554 // Interpolate the other values in the usual way
555 if (current_alt != required_alt) {
556 current_alt = interpolate_val(current_alt,
558 MaxCloudAltitudeChangeFtSec);
559 fgSetDouble(s, current_alt);
562 snprintf(s, 128, cl, i);
563 strncat(s, "/thickness-ft", 128);
564 currentval = fgGetDouble(s);
565 requiredval = (*layer)->getDoubleValue("thickness-ft");
567 if (currentval != requiredval) {
568 currentval = interpolate_val(currentval,
570 MaxCloudThicknessChangeFtSec);
571 fgSetDouble(s, currentval);
577 // We haven't already loaded a METAR, so apply it immediately.
578 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
579 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
580 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
581 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
582 vis = fgGetDouble("/environment/metar/min-visibility-m");
583 pressure = fgGetDouble("/environment/metar/pressure-inhg");
584 temp = fgGetDouble("/environment/metar/temperature-degc");
585 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
587 // Set the cloud layers by copying over the METAR versions.
588 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds", true);
590 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
591 vector<SGPropertyNode_ptr>::const_iterator layer;
592 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
594 const char *cl = "/environment/clouds/layer[%i]";
598 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
599 snprintf(s, 128, cl, i);
600 strncat(s, "/coverage", 128);
601 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
603 snprintf(s, 128, cl, i);
604 strncat(s, "/elevation-ft", 128);
605 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
607 snprintf(s, 128, cl, i);
608 strncat(s, "/thickness-ft", 128);
609 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
611 snprintf(s, 128, cl, i);
612 strncat(s, "/span-m", 128);
613 fgSetDouble(s, 40000.0);
616 // Force an update of the 3D clouds
617 fgSetDouble("/environment/rebuild-layers", 1.0);
620 fgSetupWind(dir_from, dir_to, speed, gust);
621 fgDefaultWeatherValue("visibility-m", vis);
622 set_temp_at_altitude(temp, station_elevation_ft);
623 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
624 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
626 // We've now successfully loaded a METAR into the configuration
630 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
631 const char * requiredname,
634 double currentval = fgGetDouble(currentname);
635 double requiredval = fgGetDouble(requiredname);
636 return interpolate_val(currentval, requiredval, dt);
639 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
643 double dval = EnvironmentUpdatePeriodSec * dt;
645 if (fabs(currentval - requiredval) < dval) return requiredval;
646 if (currentval < requiredval) return (currentval + dval);
647 if (currentval > requiredval) return (currentval - dval);
652 FGMetarEnvironmentCtrl::init ()
654 SGGeod pos = SGGeod::fromDeg(
655 fgGetDouble("/position/longitude-deg", true),
656 fgGetDouble( "/position/latitude-deg", true));
658 metar_loaded = false;
659 bool found_metar = false;
660 long max_age = metar_max_age->getLongValue();
661 // Don't check max age during init so that we don't loop over a lot
662 // of airports metar if there is a problem.
663 // The update() calls will find a correct metar if things went wrong here
664 metar_max_age->setLongValue(0);
666 while ( !found_metar && (_error_count < 3) ) {
667 AirportWithMetar filter;
668 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
673 FGMetarResult result = fetch_data(a);
674 if ( result.m != NULL ) {
675 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 = " << a->ident() );
690 } // of airprot-with-metar search iteration
692 metar_max_age->setLongValue(max_age);
696 FGMetarEnvironmentCtrl::reinit ()
700 metar_loaded = false;
706 FGMetarEnvironmentCtrl::update(double delta_time_sec)
708 _dt += delta_time_sec;
709 if (_error_count >= 3)
712 FGMetarResult result;
714 static const SGPropertyNode *longitude
715 = fgGetNode( "/position/longitude-deg", true );
716 static const SGPropertyNode *latitude
717 = fgGetNode( "/position/latitude-deg", true );
718 SGGeod pos = SGGeod::fromDeg(longitude->getDoubleValue(),
719 latitude->getDoubleValue());
721 search_elapsed += delta_time_sec;
722 fetch_elapsed += delta_time_sec;
723 interpolate_elapsed += delta_time_sec;
725 // if time for a new search request, push it onto the request
727 if ( search_elapsed > search_interval_sec ) {
728 AirportWithMetar filter;
729 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
731 if ( !last_apt || last_apt->ident() != a->ident()
732 || fetch_elapsed > same_station_interval_sec )
734 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
736 request_queue.push(a);
738 search_elapsed = 0.0;
741 search_elapsed = 0.0;
742 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
743 << same_station_interval_sec - fetch_elapsed );
747 SG_LOG( SG_GENERAL, SG_WARN,
748 "Unable to find any airports with metar" );
750 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
751 // Interpolate the current configuration closer to the actual METAR
754 interpolate_elapsed = 0.0;
757 #if !defined(ENABLE_THREADS)
758 // No loader thread running so manually fetch the data
759 FGAirport* apt = NULL;
760 while ( !request_queue.empty() ) {
761 apt = request_queue.front();
766 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << apt->ident() );
767 result = fetch_data( apt );
768 result_queue.push( result );
770 #endif // ENABLE_THREADS
772 // process any results from the loader.
773 while ( !result_queue.empty() ) {
774 result = result_queue.front();
776 if ( result.m != NULL ) {
777 update_metar_properties( result.m );
782 // mark as no metar so it doesn't show up in subsequent
783 // searches, and signal an immediate re-search.
784 SG_LOG( SG_GENERAL, SG_WARN,
785 "no metar at station = " << result.airport->ident() );
786 result.airport->setMetar(false);
787 search_elapsed = 9999.0;
791 env->update(delta_time_sec);
796 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
798 env->setEnvironment(environment);
802 FGMetarEnvironmentCtrl::fetch_data(FGAirport* apt)
804 FGMetarResult result;
805 result.airport = apt;
807 // if the last error was more than three seconds ago,
808 // then pretent nothing happened.
817 station_elevation_ft = apt->getElevation();
819 // fetch current metar data
821 string host = proxy_host->getStringValue();
822 string auth = proxy_auth->getStringValue();
823 string port = proxy_port->getStringValue();
824 result.m = new FGMetar( apt->ident(), host, port, auth);
826 long max_age = metar_max_age->getLongValue();
827 long age = result.m->getAge_min();
828 if (max_age && age > max_age) {
829 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
833 if (++_stale_count > 10) {
835 throw sg_io_exception("More than 10 stale METAR messages in a row."
836 " Check your system time!");
841 } catch (const sg_io_exception& e) {
842 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
843 << e.getFormattedMessage().c_str() );
844 #if defined(ENABLE_THREADS)
845 if (_error_count++ >= 3) {
846 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
861 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
867 fgSetString("/environment/metar/real-metar", m->getData());
868 // don't update with real weather when we use a custom weather scenario
869 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
870 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
872 fgSetString("/environment/metar/last-metar", m->getData());
873 fgSetString("/environment/metar/station-id", m->getId());
874 fgSetDouble("/environment/metar/min-visibility-m",
875 m->getMinVisibility().getVisibility_m() );
876 fgSetDouble("/environment/metar/max-visibility-m",
877 m->getMaxVisibility().getVisibility_m() );
879 const SGMetarVisibility *dirvis = m->getDirVisibility();
880 for (i = 0; i < 8; i++, dirvis++) {
881 const char *min = "/environment/metar/visibility[%d]/min-m";
882 const char *max = "/environment/metar/visibility[%d]/max-m";
884 d = dirvis->getVisibility_m();
886 snprintf(s, 128, min, i);
888 snprintf(s, 128, max, i);
892 fgSetInt("/environment/metar/base-wind-range-from",
893 m->getWindRangeFrom() );
894 fgSetInt("/environment/metar/base-wind-range-to",
895 m->getWindRangeTo() );
896 fgSetDouble("/environment/metar/base-wind-speed-kt",
897 m->getWindSpeed_kt() );
898 fgSetDouble("/environment/metar/gust-wind-speed-kt",
899 m->getGustSpeed_kt() );
900 fgSetDouble("/environment/metar/temperature-degc",
901 m->getTemperature_C() );
902 fgSetDouble("/environment/metar/dewpoint-degc",
903 m->getDewpoint_C() );
904 fgSetDouble("/environment/metar/rel-humidity-norm",
905 m->getRelHumidity() );
906 fgSetDouble("/environment/metar/pressure-inhg",
907 m->getPressure_inHg() );
909 vector<SGMetarCloud> cv = m->getClouds();
910 vector<SGMetarCloud>::const_iterator cloud;
912 const char *cl = "/environment/metar/clouds/layer[%i]";
913 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
914 const char *coverage_string[5] =
915 { "clear", "few", "scattered", "broken", "overcast" };
916 const double thickness[5] = { 0, 65, 600,750, 1000};
919 snprintf(s, 128, cl, i);
920 strncat(s, "/coverage", 128);
921 q = cloud->getCoverage();
922 fgSetString(s, coverage_string[q] );
924 snprintf(s, 128, cl, i);
925 strncat(s, "/elevation-ft", 128);
926 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
928 snprintf(s, 128, cl, i);
929 strncat(s, "/thickness-ft", 128);
930 fgSetDouble(s, thickness[q]);
932 snprintf(s, 128, cl, i);
933 strncat(s, "/span-m", 128);
934 fgSetDouble(s, 40000.0);
937 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
938 snprintf(s, 128, cl, i);
939 strncat(s, "/coverage", 128);
940 fgSetString(s, "clear");
942 snprintf(s, 128, cl, i);
943 strncat(s, "/elevation-ft", 128);
944 fgSetDouble(s, -9999);
946 snprintf(s, 128, cl, i);
947 strncat(s, "/thickness-ft", 128);
950 snprintf(s, 128, cl, i);
951 strncat(s, "/span-m", 128);
952 fgSetDouble(s, 40000.0);
955 fgSetDouble("/environment/metar/rain-norm", m->getRain());
956 fgSetDouble("/environment/metar/hail-norm", m->getHail());
957 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
958 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
962 #if defined(ENABLE_THREADS)
964 FGMetarEnvironmentCtrl::thread_stop()
966 request_queue.push(NULL); // ask thread to terminate
971 FGMetarEnvironmentCtrl::MetarThread::run()
975 FGAirport* apt = fetcher->request_queue.pop();
978 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << apt->ident() );
979 FGMetarResult result = fetcher->fetch_data( apt );
980 fetcher->result_queue.push( result );
983 #endif // ENABLE_THREADS
986 // end of environment_ctrl.cxx