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 FGPositioned::Filter
46 virtual bool pass(FGPositioned* aPos) const
48 if ((aPos->type() < FGPositioned::AIRPORT) || (aPos->type() > FGPositioned::SEAPORT)) {
52 FGAirport* apt = static_cast<FGAirport*>(aPos);
53 return apt->getMetar();
57 ////////////////////////////////////////////////////////////////////////
58 // Implementation of FGEnvironmentCtrl abstract base class.
59 ////////////////////////////////////////////////////////////////////////
61 FGEnvironmentCtrl::FGEnvironmentCtrl ()
69 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
74 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
76 _environment = environment;
80 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
86 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
92 FGEnvironmentCtrl::setElevationFt (double elev_ft)
98 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
107 ////////////////////////////////////////////////////////////////////////
108 // Implementation of FGUserDefEnvironmentCtrl.
109 ////////////////////////////////////////////////////////////////////////
111 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
112 : _base_wind_speed_node(0),
113 _gust_wind_speed_node(0),
114 _current_wind_speed_kt(0),
115 _delta_wind_speed_kt(0)
119 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
124 FGUserDefEnvironmentCtrl::init ()
126 // Fill in some defaults.
127 if (!fgHasNode("/environment/params/base-wind-speed-kt"))
128 fgSetDouble("/environment/params/base-wind-speed-kt",
129 fgGetDouble("/environment/wind-speed-kt"));
130 if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
131 fgSetDouble("/environment/params/gust-wind-speed-kt",
132 fgGetDouble("/environment/params/base-wind-speed-kt"));
134 _base_wind_speed_node =
135 fgGetNode("/environment/params/base-wind-speed-kt", true);
136 _gust_wind_speed_node =
137 fgGetNode("/environment/params/gust-wind-speed-kt", true);
139 _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
140 _delta_wind_speed_kt = 0.1;
144 FGUserDefEnvironmentCtrl::update (double dt)
146 double base_wind_speed = _base_wind_speed_node->getDoubleValue();
147 double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
149 if (gust_wind_speed < base_wind_speed) {
150 gust_wind_speed = base_wind_speed;
151 _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
154 if (base_wind_speed == gust_wind_speed) {
155 _current_wind_speed_kt = base_wind_speed;
157 int rn = rand() % 128;
158 int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
159 double gust = _current_wind_speed_kt - base_wind_speed;
160 double incr = gust / 50;
163 _delta_wind_speed_kt = - _delta_wind_speed_kt;
165 _delta_wind_speed_kt -= incr * sign;
167 _delta_wind_speed_kt += incr * sign;
169 _current_wind_speed_kt += _delta_wind_speed_kt;
171 if (_current_wind_speed_kt < base_wind_speed) {
172 _current_wind_speed_kt = base_wind_speed;
173 _delta_wind_speed_kt = 0.01;
174 } else if (_current_wind_speed_kt > gust_wind_speed) {
175 _current_wind_speed_kt = gust_wind_speed;
176 _delta_wind_speed_kt = -0.01;
180 if (_environment != 0)
181 _environment->set_wind_speed_kt(_current_wind_speed_kt);
186 ////////////////////////////////////////////////////////////////////////
187 // Implementation of FGInterpolateEnvironmentCtrl.
188 ////////////////////////////////////////////////////////////////////////
190 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
194 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
197 for (i = 0; i < _boundary_table.size(); i++)
198 delete _boundary_table[i];
199 for (i = 0; i < _aloft_table.size(); i++)
200 delete _aloft_table[i];
206 FGInterpolateEnvironmentCtrl::init ()
208 read_table(fgGetNode("/environment/config/boundary", true),
210 read_table(fgGetNode("/environment/config/aloft", true),
215 FGInterpolateEnvironmentCtrl::reinit ()
218 for (i = 0; i < _boundary_table.size(); i++)
219 delete _boundary_table[i];
220 for (i = 0; i < _aloft_table.size(); i++)
221 delete _aloft_table[i];
222 _boundary_table.clear();
223 _aloft_table.clear();
228 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
229 vector<bucket *> &table)
231 for (int i = 0; i < node->nChildren(); i++) {
232 const SGPropertyNode * child = node->getChild(i);
233 if ( strcmp(child->getName(), "entry") == 0
234 && child->getStringValue("elevation-ft", "")[0] != '\0'
235 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
237 bucket * b = new bucket;
239 b->environment.copy(table[i-1]->environment);
240 b->environment.read(child);
241 b->altitude_ft = b->environment.get_elevation_ft();
245 sort(table.begin(), table.end(), bucket::lessThan);
249 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
252 double altitude_ft = fgGetDouble("/position/altitude-ft");
253 double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
254 double boundary_transition =
255 fgGetDouble("/environment/config/boundary-transition-ft", 500);
257 // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
259 int length = _boundary_table.size();
263 double boundary_limit = _boundary_table[length-1]->altitude_ft;
264 if (boundary_limit >= altitude_agl_ft) {
265 do_interpolate(_boundary_table, altitude_agl_ft,
268 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
270 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
271 do_interpolate(_aloft_table, altitude_ft, &env2);
273 (altitude_agl_ft - boundary_limit) / boundary_transition;
274 interpolate(&env1, &env2, fraction, _environment);
280 do_interpolate(_aloft_table, altitude_ft, _environment);
284 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
286 FGEnvironment * environment)
288 int length = table.size();
292 // Boundary conditions
293 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
294 environment->copy(table[0]->environment);
296 } else if (table[length-1]->altitude_ft <= altitude_ft) {
297 environment->copy(table[length-1]->environment);
301 // Search the interpolation table
302 for (int i = 0; i < length - 1; i++) {
303 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
304 FGEnvironment * env1 = &(table[i]->environment);
305 FGEnvironment * env2 = &(table[i+1]->environment);
307 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
311 ((altitude_ft - table[i]->altitude_ft) /
312 (table[i+1]->altitude_ft - table[i]->altitude_ft));
313 interpolate(env1, env2, fraction, environment);
321 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
323 return (altitude_ft < b.altitude_ft);
327 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
329 return (a->altitude_ft) < (b->altitude_ft);
333 ////////////////////////////////////////////////////////////////////////
334 // Implementation of FGMetarEnvironmentCtrl.
335 ////////////////////////////////////////////////////////////////////////
337 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
338 : env( new FGInterpolateEnvironmentCtrl ),
340 metar_loaded( false ),
341 search_interval_sec( 60.0 ), // 1 minute
342 same_station_interval_sec( 900.0 ), // 15 minutes
343 search_elapsed( 9999.0 ),
344 fetch_elapsed( 9999.0 ),
346 proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
347 proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
348 proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
349 metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
351 // Interpolation constant definitions.
352 EnvironmentUpdatePeriodSec( 0.2 ),
353 MaxWindChangeKtsSec( 0.2 ),
354 MaxVisChangePercentSec( 0.05 ),
355 MaxPressureChangeInHgSec( 0.0033 ),
356 MaxCloudAltitudeChangeFtSec( 20.0 ),
357 MaxCloudThicknessChangeFtSec( 50.0 ),
358 MaxCloudInterpolationHeightFt( 5000.0 ),
359 MaxCloudInterpolationDeltaFt( 4000.0 ),
366 #if defined(ENABLE_THREADS)
367 thread = new MetarThread(this);
368 thread->setProcessorAffinity(1);
370 #endif // ENABLE_THREADS
373 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
375 #if defined(ENABLE_THREADS)
377 #endif // ENABLE_THREADS
384 // use a "command" to set station temp at station elevation
385 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
387 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
388 node->setFloatValue( temp_degc );
389 node = args.getNode("altitude-ft", 0, true);
390 node->setFloatValue( altitude_ft );
391 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
395 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
397 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
398 node->setFloatValue( dewpoint_degc );
399 node = args.getNode("altitude-ft", 0, true);
400 node->setFloatValue( altitude_ft );
401 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
406 FGMetarEnvironmentCtrl::update_env_config ()
417 // If we aren't in the METAR scenario, don't attempt to interpolate.
418 if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR")) return;
421 // Generate interpolated values between the METAR and the current
424 // Pick up the METAR wind values and convert them into a vector.
426 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
427 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
429 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
430 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
432 // Convert the current wind values and convert them into a vector
434 double current_speed =
435 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
436 double current_heading = fgGetDouble(
437 "/environment/config/boundary/entry/wind-from-heading-deg");
439 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
440 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
442 // Determine the maximum component-wise value that the wind can change.
443 // First we determine the fraction in the X and Y component, then
444 // factor by the maximum wind change.
445 double x = fabs(current[0] - metar[0]);
446 double y = fabs(current[1] - metar[1]);
447 double dx = x / (x + y);
450 double maxdx = dx * MaxWindChangeKtsSec;
451 double maxdy = dy * MaxWindChangeKtsSec;
453 // Interpolate each component separately.
454 current[0] = interpolate_val(current[0], metar[0], maxdx);
455 current[1] = interpolate_val(current[1], metar[1], maxdy);
457 // Now convert back to polar coordinates.
458 if ((current[0] == 0.0) && (current[1] == 0.0)) {
459 // Special case where there is no wind (otherwise atan2 barfs)
461 dir_from = current_heading;
464 // Some real wind to convert back from. Work out the speed
465 // and direction value in degrees.
466 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
467 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
469 // Normalize the direction.
473 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
476 // Now handle the visibility. We convert both visibility values
477 // to X-values, then interpolate from there, then back to real values.
478 // The length_scale is fixed to 1000m, so the visibility changes by
479 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
480 // whichever is more.
482 fgGetDouble("/environment/config/boundary/entry/visibility-m");
483 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
484 double currentxval = log(1000.0 + currentvis);
485 double metarxval = log(1000.0 + metarvis);
487 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
489 // Now convert back from an X-value to a straightforward visibility.
490 vis = exp(currentxval) - 1000.0;
492 pressure = interpolate_prop(
493 "/environment/config/boundary/entry/pressure-sea-level-inhg",
494 "/environment/metar/pressure-inhg",
495 MaxPressureChangeInHgSec);
497 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
498 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
499 temp = fgGetDouble("/environment/metar/temperature-degc");
500 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
502 // Set the cloud layers by interpolating over the METAR versions.
503 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
505 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
506 vector<SGPropertyNode_ptr>::const_iterator layer;
507 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
509 const char *cl = "/environment/clouds/layer[%i]";
510 double aircraft_alt = fgGetDouble("/position/altitude-ft");
514 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
518 // In the case of clouds, we want to avoid writing if nothing has
519 // changed, as these properties are tied to the renderer and will
520 // cause the clouds to be updated, reseting the texture locations.
522 // We don't interpolate the coverage values as no-matter how we
523 // do it, it will be quite a sudden change of texture. Better to
524 // have a single change than four or five.
525 snprintf(s, 128, cl, i);
526 strncat(s, "/coverage", 128);
527 const char* coverage = (*layer)->getStringValue("coverage", "clear");
528 if (strncmp(fgGetString(s), coverage, 128) != 0)
529 fgSetString(s, coverage);
531 snprintf(s, 128, cl, i);
532 strncat(s, "/elevation-ft", 128);
533 double current_alt = fgGetDouble(s);
534 double required_alt = (*layer)->getDoubleValue("elevation-ft");
536 if (current_alt < -9000 || required_alt < -9000 ||
537 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
538 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
539 // We don't interpolate any layers that are
540 // - too far above us to be visible
541 // - too far below us to be visible
542 // - with too large a difference to make interpolation sensible
543 // - to or from -9999 (used as a placeholder)
544 // - any values that are too high above us,
545 snprintf(s, 128, cl, i);
546 strncat(s, "/elevation-ft", 128);
547 if (current_alt != required_alt)
548 fgSetDouble(s, required_alt);
550 snprintf(s, 128, cl, i);
551 strncat(s, "/thickness-ft", 128);
552 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
553 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
556 // Interpolate the other values in the usual way
557 if (current_alt != required_alt) {
558 current_alt = interpolate_val(current_alt,
560 MaxCloudAltitudeChangeFtSec);
561 fgSetDouble(s, current_alt);
564 snprintf(s, 128, cl, i);
565 strncat(s, "/thickness-ft", 128);
566 currentval = fgGetDouble(s);
567 requiredval = (*layer)->getDoubleValue("thickness-ft");
569 if (currentval != requiredval) {
570 currentval = interpolate_val(currentval,
572 MaxCloudThicknessChangeFtSec);
573 fgSetDouble(s, currentval);
579 // We haven't already loaded a METAR, so apply it immediately.
580 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
581 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
582 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
583 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
584 vis = fgGetDouble("/environment/metar/min-visibility-m");
585 pressure = fgGetDouble("/environment/metar/pressure-inhg");
586 temp = fgGetDouble("/environment/metar/temperature-degc");
587 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
589 // Set the cloud layers by copying over the METAR versions.
590 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds", true);
592 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
593 vector<SGPropertyNode_ptr>::const_iterator layer;
594 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
596 const char *cl = "/environment/clouds/layer[%i]";
600 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
601 snprintf(s, 128, cl, i);
602 strncat(s, "/coverage", 128);
603 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
605 snprintf(s, 128, cl, i);
606 strncat(s, "/elevation-ft", 128);
607 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
609 snprintf(s, 128, cl, i);
610 strncat(s, "/thickness-ft", 128);
611 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
613 snprintf(s, 128, cl, i);
614 strncat(s, "/span-m", 128);
615 fgSetDouble(s, 40000.0);
618 // Force an update of the 3D clouds
619 fgSetDouble("/environment/rebuild-layers", 1.0);
622 fgSetupWind(dir_from, dir_to, speed, gust);
623 fgDefaultWeatherValue("visibility-m", vis);
624 set_temp_at_altitude(temp, station_elevation_ft);
625 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
626 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
628 // We've now successfully loaded a METAR into the configuration
632 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
633 const char * requiredname,
636 double currentval = fgGetDouble(currentname);
637 double requiredval = fgGetDouble(requiredname);
638 return interpolate_val(currentval, requiredval, dt);
641 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
645 double dval = EnvironmentUpdatePeriodSec * dt;
647 if (fabs(currentval - requiredval) < dval) return requiredval;
648 if (currentval < requiredval) return (currentval + dval);
649 if (currentval > requiredval) return (currentval - dval);
654 FGMetarEnvironmentCtrl::init ()
656 SGGeod pos = SGGeod::fromDeg(
657 fgGetDouble("/position/longitude-deg", true),
658 fgGetDouble( "/position/latitude-deg", true));
660 metar_loaded = false;
661 bool found_metar = false;
662 long max_age = metar_max_age->getLongValue();
663 // Don't check max age during init so that we don't loop over a lot
664 // of airports metar if there is a problem.
665 // The update() calls will find a correct metar if things went wrong here
666 metar_max_age->setLongValue(0);
668 while ( !found_metar && (_error_count < 3) ) {
669 AirportWithMetar filter;
670 FGPositionedRef a = FGPositioned::findClosest(pos, 10000.0, &filter);
675 FGMetarResult result = fetch_data(a->ident());
676 if ( result.m != NULL ) {
677 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
681 search_elapsed = 0.0;
683 update_metar_properties( result.m );
688 // mark as no metar so it doesn't show up in subsequent
690 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = " << a->ident() );
691 static_cast<FGAirport*>(a.ptr())->setMetar(false);
693 } // of airprot-with-metar search iteration
695 metar_max_age->setLongValue(max_age);
699 FGMetarEnvironmentCtrl::reinit ()
703 metar_loaded = false;
709 FGMetarEnvironmentCtrl::update(double delta_time_sec)
711 _dt += delta_time_sec;
712 if (_error_count >= 3)
715 FGMetarResult result;
717 static const SGPropertyNode *longitude
718 = fgGetNode( "/position/longitude-deg", true );
719 static const SGPropertyNode *latitude
720 = fgGetNode( "/position/latitude-deg", true );
721 SGGeod pos = SGGeod::fromDeg(longitude->getDoubleValue(),
722 latitude->getDoubleValue());
724 search_elapsed += delta_time_sec;
725 fetch_elapsed += delta_time_sec;
726 interpolate_elapsed += delta_time_sec;
728 // if time for a new search request, push it onto the request
730 if ( search_elapsed > search_interval_sec ) {
731 AirportWithMetar filter;
732 FGPositionedRef a = FGPositioned::findClosest(pos, 10000.0, &filter);
734 if ( !last_apt || last_apt->ident() != a->ident()
735 || fetch_elapsed > same_station_interval_sec )
737 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
739 request_queue.push( a->ident() );
742 search_elapsed = 0.0;
745 search_elapsed = 0.0;
746 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
747 << same_station_interval_sec - fetch_elapsed );
751 SG_LOG( SG_GENERAL, SG_WARN,
752 "Unable to find any airports with metar" );
754 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
755 // Interpolate the current configuration closer to the actual METAR
758 interpolate_elapsed = 0.0;
761 #if !defined(ENABLE_THREADS)
762 // No loader thread running so manually fetch the data
764 while ( !request_queue.empty() ) {
765 id = request_queue.front();
770 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
771 result = fetch_data( id );
772 result_queue.push( result );
774 #endif // ENABLE_THREADS
776 // process any results from the loader.
777 while ( !result_queue.empty() ) {
778 result = result_queue.front();
780 if ( result.m != NULL ) {
781 update_metar_properties( result.m );
786 // mark as no metar so it doesn't show up in subsequent
787 // searches, and signal an immediate re-search.
788 SG_LOG( SG_GENERAL, SG_WARN,
789 "no metar at station = " << result.icao );
790 const FGAirport* apt = globals->get_airports()->search(result.icao);
791 const_cast<FGAirport*>(apt)->setMetar(false);
792 search_elapsed = 9999.0;
796 env->update(delta_time_sec);
801 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
803 env->setEnvironment(environment);
807 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
809 FGMetarResult result;
812 // if the last error was more than three seconds ago,
813 // then pretent nothing happened.
822 // fetch station elevation if exists
823 const FGAirport* a = globals->get_airports()->search( icao );
825 station_elevation_ft = a->getElevation();
828 // fetch current metar data
830 string host = proxy_host->getStringValue();
831 string auth = proxy_auth->getStringValue();
832 string port = proxy_port->getStringValue();
833 result.m = new FGMetar( icao, host, port, auth);
835 long max_age = metar_max_age->getLongValue();
836 long age = result.m->getAge_min();
837 if (max_age && age > max_age) {
838 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
842 if (++_stale_count > 10) {
844 throw sg_io_exception("More than 10 stale METAR messages in a row."
845 " Check your system time!");
850 } catch (const sg_io_exception& e) {
851 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
852 << e.getFormattedMessage().c_str() );
853 #if defined(ENABLE_THREADS)
854 if (_error_count++ >= 3) {
855 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
870 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
876 fgSetString("/environment/metar/real-metar", m->getData());
877 // don't update with real weather when we use a custom weather scenario
878 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
879 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
881 fgSetString("/environment/metar/last-metar", m->getData());
882 fgSetString("/environment/metar/station-id", m->getId());
883 fgSetDouble("/environment/metar/min-visibility-m",
884 m->getMinVisibility().getVisibility_m() );
885 fgSetDouble("/environment/metar/max-visibility-m",
886 m->getMaxVisibility().getVisibility_m() );
888 const SGMetarVisibility *dirvis = m->getDirVisibility();
889 for (i = 0; i < 8; i++, dirvis++) {
890 const char *min = "/environment/metar/visibility[%d]/min-m";
891 const char *max = "/environment/metar/visibility[%d]/max-m";
893 d = dirvis->getVisibility_m();
895 snprintf(s, 128, min, i);
897 snprintf(s, 128, max, i);
901 fgSetInt("/environment/metar/base-wind-range-from",
902 m->getWindRangeFrom() );
903 fgSetInt("/environment/metar/base-wind-range-to",
904 m->getWindRangeTo() );
905 fgSetDouble("/environment/metar/base-wind-speed-kt",
906 m->getWindSpeed_kt() );
907 fgSetDouble("/environment/metar/gust-wind-speed-kt",
908 m->getGustSpeed_kt() );
909 fgSetDouble("/environment/metar/temperature-degc",
910 m->getTemperature_C() );
911 fgSetDouble("/environment/metar/dewpoint-degc",
912 m->getDewpoint_C() );
913 fgSetDouble("/environment/metar/rel-humidity-norm",
914 m->getRelHumidity() );
915 fgSetDouble("/environment/metar/pressure-inhg",
916 m->getPressure_inHg() );
918 vector<SGMetarCloud> cv = m->getClouds();
919 vector<SGMetarCloud>::const_iterator cloud;
921 const char *cl = "/environment/metar/clouds/layer[%i]";
922 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
923 const char *coverage_string[5] =
924 { "clear", "few", "scattered", "broken", "overcast" };
925 const double thickness[5] = { 0, 65, 600,750, 1000};
928 snprintf(s, 128, cl, i);
929 strncat(s, "/coverage", 128);
930 q = cloud->getCoverage();
931 fgSetString(s, coverage_string[q] );
933 snprintf(s, 128, cl, i);
934 strncat(s, "/elevation-ft", 128);
935 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
937 snprintf(s, 128, cl, i);
938 strncat(s, "/thickness-ft", 128);
939 fgSetDouble(s, thickness[q]);
941 snprintf(s, 128, cl, i);
942 strncat(s, "/span-m", 128);
943 fgSetDouble(s, 40000.0);
946 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
947 snprintf(s, 128, cl, i);
948 strncat(s, "/coverage", 128);
949 fgSetString(s, "clear");
951 snprintf(s, 128, cl, i);
952 strncat(s, "/elevation-ft", 128);
953 fgSetDouble(s, -9999);
955 snprintf(s, 128, cl, i);
956 strncat(s, "/thickness-ft", 128);
959 snprintf(s, 128, cl, i);
960 strncat(s, "/span-m", 128);
961 fgSetDouble(s, 40000.0);
964 fgSetDouble("/environment/metar/rain-norm", m->getRain());
965 fgSetDouble("/environment/metar/hail-norm", m->getHail());
966 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
967 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
971 #if defined(ENABLE_THREADS)
973 FGMetarEnvironmentCtrl::thread_stop()
975 request_queue.push( string() ); // ask thread to terminate
980 FGMetarEnvironmentCtrl::MetarThread::run()
984 string icao = fetcher->request_queue.pop();
987 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
988 FGMetarResult result = fetcher->fetch_data( icao );
989 fetcher->result_queue.push( result );
992 #endif // ENABLE_THREADS
995 // end of environment_ctrl.cxx