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 ),
339 metar_loaded( false ),
340 search_interval_sec( 60.0 ), // 1 minute
341 same_station_interval_sec( 900.0 ), // 15 minutes
342 search_elapsed( 9999.0 ),
343 fetch_elapsed( 9999.0 ),
345 proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
346 proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
347 proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
348 metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
350 // Interpolation constant definitions.
351 EnvironmentUpdatePeriodSec( 0.2 ),
352 MaxWindChangeKtsSec( 0.2 ),
353 MaxVisChangePercentSec( 0.05 ),
354 MaxPressureChangeInHgSec( 0.0033 ),
355 MaxCloudAltitudeChangeFtSec( 20.0 ),
356 MaxCloudThicknessChangeFtSec( 50.0 ),
357 MaxCloudInterpolationHeightFt( 5000.0 ),
358 MaxCloudInterpolationDeltaFt( 4000.0 ),
365 #if defined(ENABLE_THREADS)
366 thread = new MetarThread(this);
367 thread->setProcessorAffinity(1);
369 #endif // ENABLE_THREADS
372 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
374 #if defined(ENABLE_THREADS)
376 #endif // ENABLE_THREADS
383 // use a "command" to set station temp at station elevation
384 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
386 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
387 node->setFloatValue( temp_degc );
388 node = args.getNode("altitude-ft", 0, true);
389 node->setFloatValue( altitude_ft );
390 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
394 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
396 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
397 node->setFloatValue( dewpoint_degc );
398 node = args.getNode("altitude-ft", 0, true);
399 node->setFloatValue( altitude_ft );
400 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
405 FGMetarEnvironmentCtrl::update_env_config ()
416 // If we aren't in the METAR scenario, don't attempt to interpolate.
417 if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR")) return;
420 // Generate interpolated values between the METAR and the current
423 // Pick up the METAR wind values and convert them into a vector.
425 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
426 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
428 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
429 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
431 // Convert the current wind values and convert them into a vector
433 double current_speed =
434 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
435 double current_heading = fgGetDouble(
436 "/environment/config/boundary/entry/wind-from-heading-deg");
438 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
439 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
441 // Determine the maximum component-wise value that the wind can change.
442 // First we determine the fraction in the X and Y component, then
443 // factor by the maximum wind change.
444 double x = fabs(current[0] - metar[0]);
445 double y = fabs(current[1] - metar[1]);
446 double dx = x / (x + y);
449 double maxdx = dx * MaxWindChangeKtsSec;
450 double maxdy = dy * MaxWindChangeKtsSec;
452 // Interpolate each component separately.
453 current[0] = interpolate_val(current[0], metar[0], maxdx);
454 current[1] = interpolate_val(current[1], metar[1], maxdy);
456 // Now convert back to polar coordinates.
457 if ((current[0] == 0.0) && (current[1] == 0.0)) {
458 // Special case where there is no wind (otherwise atan2 barfs)
460 dir_from = current_heading;
463 // Some real wind to convert back from. Work out the speed
464 // and direction value in degrees.
465 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
466 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
468 // Normalize the direction.
472 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
475 // Now handle the visibility. We convert both visibility values
476 // to X-values, then interpolate from there, then back to real values.
477 // The length_scale is fixed to 1000m, so the visibility changes by
478 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
479 // whichever is more.
481 fgGetDouble("/environment/config/boundary/entry/visibility-m");
482 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
483 double currentxval = log(1000.0 + currentvis);
484 double metarxval = log(1000.0 + metarvis);
486 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
488 // Now convert back from an X-value to a straightforward visibility.
489 vis = exp(currentxval) - 1000.0;
491 pressure = interpolate_prop(
492 "/environment/config/boundary/entry/pressure-sea-level-inhg",
493 "/environment/metar/pressure-inhg",
494 MaxPressureChangeInHgSec);
496 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
497 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
498 temp = fgGetDouble("/environment/metar/temperature-degc");
499 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
501 // Set the cloud layers by interpolating over the METAR versions.
502 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
504 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
505 vector<SGPropertyNode_ptr>::const_iterator layer;
506 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
508 const char *cl = "/environment/clouds/layer[%i]";
509 double aircraft_alt = fgGetDouble("/position/altitude-ft");
513 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
517 // In the case of clouds, we want to avoid writing if nothing has
518 // changed, as these properties are tied to the renderer and will
519 // cause the clouds to be updated, reseting the texture locations.
521 // We don't interpolate the coverage values as no-matter how we
522 // do it, it will be quite a sudden change of texture. Better to
523 // have a single change than four or five.
524 snprintf(s, 128, cl, i);
525 strncat(s, "/coverage", 128);
526 const char* coverage = (*layer)->getStringValue("coverage", "clear");
527 if (strncmp(fgGetString(s), coverage, 128) != 0)
528 fgSetString(s, coverage);
530 snprintf(s, 128, cl, i);
531 strncat(s, "/elevation-ft", 128);
532 double current_alt = fgGetDouble(s);
533 double required_alt = (*layer)->getDoubleValue("elevation-ft");
535 if (current_alt < -9000 || required_alt < -9000 ||
536 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
537 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
538 // We don't interpolate any layers that are
539 // - too far above us to be visible
540 // - too far below us to be visible
541 // - with too large a difference to make interpolation sensible
542 // - to or from -9999 (used as a placeholder)
543 // - any values that are too high above us,
544 snprintf(s, 128, cl, i);
545 strncat(s, "/elevation-ft", 128);
546 if (current_alt != required_alt)
547 fgSetDouble(s, required_alt);
549 snprintf(s, 128, cl, i);
550 strncat(s, "/thickness-ft", 128);
551 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
552 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
555 // Interpolate the other values in the usual way
556 if (current_alt != required_alt) {
557 current_alt = interpolate_val(current_alt,
559 MaxCloudAltitudeChangeFtSec);
560 fgSetDouble(s, current_alt);
563 snprintf(s, 128, cl, i);
564 strncat(s, "/thickness-ft", 128);
565 currentval = fgGetDouble(s);
566 requiredval = (*layer)->getDoubleValue("thickness-ft");
568 if (currentval != requiredval) {
569 currentval = interpolate_val(currentval,
571 MaxCloudThicknessChangeFtSec);
572 fgSetDouble(s, currentval);
578 // We haven't already loaded a METAR, so apply it immediately.
579 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
580 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
581 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
582 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
583 vis = fgGetDouble("/environment/metar/min-visibility-m");
584 pressure = fgGetDouble("/environment/metar/pressure-inhg");
585 temp = fgGetDouble("/environment/metar/temperature-degc");
586 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
588 // Set the cloud layers by copying over the METAR versions.
589 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds", true);
591 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
592 vector<SGPropertyNode_ptr>::const_iterator layer;
593 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
595 const char *cl = "/environment/clouds/layer[%i]";
599 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
600 snprintf(s, 128, cl, i);
601 strncat(s, "/coverage", 128);
602 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
604 snprintf(s, 128, cl, i);
605 strncat(s, "/elevation-ft", 128);
606 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
608 snprintf(s, 128, cl, i);
609 strncat(s, "/thickness-ft", 128);
610 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
612 snprintf(s, 128, cl, i);
613 strncat(s, "/span-m", 128);
614 fgSetDouble(s, 40000.0);
617 // Force an update of the 3D clouds
618 fgSetDouble("/environment/rebuild-layers", 1.0);
621 fgSetupWind(dir_from, dir_to, speed, gust);
622 fgDefaultWeatherValue("visibility-m", vis);
623 set_temp_at_altitude(temp, station_elevation_ft);
624 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
625 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
627 // We've now successfully loaded a METAR into the configuration
631 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
632 const char * requiredname,
635 double currentval = fgGetDouble(currentname);
636 double requiredval = fgGetDouble(requiredname);
637 return interpolate_val(currentval, requiredval, dt);
640 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
644 double dval = EnvironmentUpdatePeriodSec * dt;
646 if (fabs(currentval - requiredval) < dval) return requiredval;
647 if (currentval < requiredval) return (currentval + dval);
648 if (currentval > requiredval) return (currentval - dval);
653 FGMetarEnvironmentCtrl::init ()
655 SGGeod pos = SGGeod::fromDeg(
656 fgGetDouble("/position/longitude-deg", true),
657 fgGetDouble( "/position/latitude-deg", true));
659 metar_loaded = false;
660 bool found_metar = false;
661 long max_age = metar_max_age->getLongValue();
662 // Don't check max age during init so that we don't loop over a lot
663 // of airports metar if there is a problem.
664 // The update() calls will find a correct metar if things went wrong here
665 metar_max_age->setLongValue(0);
667 while ( !found_metar && (_error_count < 3) ) {
668 AirportWithMetar filter;
669 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
674 FGMetarResult result = fetch_data(a);
675 if ( result.m != NULL ) {
676 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
679 search_elapsed = 0.0;
681 update_metar_properties( result.m );
686 // mark as no metar so it doesn't show up in subsequent
688 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = " << a->ident() );
691 } // of airprot-with-metar search iteration
693 metar_max_age->setLongValue(max_age);
697 FGMetarEnvironmentCtrl::reinit ()
701 metar_loaded = false;
707 FGMetarEnvironmentCtrl::update(double delta_time_sec)
709 _dt += delta_time_sec;
710 if (_error_count >= 3)
713 FGMetarResult result;
715 static const SGPropertyNode *longitude
716 = fgGetNode( "/position/longitude-deg", true );
717 static const SGPropertyNode *latitude
718 = fgGetNode( "/position/latitude-deg", true );
719 SGGeod pos = SGGeod::fromDeg(longitude->getDoubleValue(),
720 latitude->getDoubleValue());
722 search_elapsed += delta_time_sec;
723 fetch_elapsed += delta_time_sec;
724 interpolate_elapsed += delta_time_sec;
726 // if time for a new search request, push it onto the request
728 if ( search_elapsed > search_interval_sec ) {
729 AirportWithMetar filter;
730 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
732 if ( !last_apt || last_apt->ident() != a->ident()
733 || fetch_elapsed > same_station_interval_sec )
735 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
737 request_queue.push(a);
739 search_elapsed = 0.0;
742 search_elapsed = 0.0;
743 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
744 << same_station_interval_sec - fetch_elapsed );
748 SG_LOG( SG_GENERAL, SG_WARN,
749 "Unable to find any airports with metar" );
751 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
752 // Interpolate the current configuration closer to the actual METAR
755 interpolate_elapsed = 0.0;
758 #if !defined(ENABLE_THREADS)
759 // No loader thread running so manually fetch the data
760 FGAirport* apt = NULL;
761 while ( !request_queue.empty() ) {
762 apt = request_queue.front();
767 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << apt->ident() );
768 result = fetch_data( apt );
769 result_queue.push( result );
771 #endif // ENABLE_THREADS
773 // process any results from the loader.
774 while ( !result_queue.empty() ) {
775 result = result_queue.front();
777 if ( result.m != NULL ) {
778 update_metar_properties( result.m );
783 // mark as no metar so it doesn't show up in subsequent
784 // searches, and signal an immediate re-search.
785 SG_LOG( SG_GENERAL, SG_WARN,
786 "no metar at station = " << result.airport->ident() );
787 result.airport->setMetar(false);
788 search_elapsed = 9999.0;
792 env->update(delta_time_sec);
797 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
799 env->setEnvironment(environment);
803 FGMetarEnvironmentCtrl::fetch_data(FGAirport* apt)
805 FGMetarResult result;
806 result.airport = apt;
808 // if the last error was more than three seconds ago,
809 // then pretent nothing happened.
818 station_elevation_ft = apt->getElevation();
820 // fetch current metar data
822 string host = proxy_host->getStringValue();
823 string auth = proxy_auth->getStringValue();
824 string port = proxy_port->getStringValue();
825 result.m = new FGMetar( apt->ident(), host, port, auth);
827 long max_age = metar_max_age->getLongValue();
828 long age = result.m->getAge_min();
829 if (max_age && age > max_age) {
830 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
834 if (++_stale_count > 10) {
836 throw sg_io_exception("More than 10 stale METAR messages in a row."
837 " Check your system time!");
842 } catch (const sg_io_exception& e) {
843 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
844 << e.getFormattedMessage().c_str() );
845 #if defined(ENABLE_THREADS)
846 if (_error_count++ >= 3) {
847 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
862 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
868 fgSetString("/environment/metar/real-metar", m->getData());
869 // don't update with real weather when we use a custom weather scenario
870 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
871 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
873 fgSetString("/environment/metar/last-metar", m->getData());
874 fgSetString("/environment/metar/station-id", m->getId());
875 fgSetDouble("/environment/metar/min-visibility-m",
876 m->getMinVisibility().getVisibility_m() );
877 fgSetDouble("/environment/metar/max-visibility-m",
878 m->getMaxVisibility().getVisibility_m() );
880 const SGMetarVisibility *dirvis = m->getDirVisibility();
881 for (i = 0; i < 8; i++, dirvis++) {
882 const char *min = "/environment/metar/visibility[%d]/min-m";
883 const char *max = "/environment/metar/visibility[%d]/max-m";
885 d = dirvis->getVisibility_m();
887 snprintf(s, 128, min, i);
889 snprintf(s, 128, max, i);
893 fgSetInt("/environment/metar/base-wind-range-from",
894 m->getWindRangeFrom() );
895 fgSetInt("/environment/metar/base-wind-range-to",
896 m->getWindRangeTo() );
897 fgSetDouble("/environment/metar/base-wind-speed-kt",
898 m->getWindSpeed_kt() );
899 fgSetDouble("/environment/metar/gust-wind-speed-kt",
900 m->getGustSpeed_kt() );
901 fgSetDouble("/environment/metar/temperature-degc",
902 m->getTemperature_C() );
903 fgSetDouble("/environment/metar/dewpoint-degc",
904 m->getDewpoint_C() );
905 fgSetDouble("/environment/metar/rel-humidity-norm",
906 m->getRelHumidity() );
907 fgSetDouble("/environment/metar/pressure-inhg",
908 m->getPressure_inHg() );
910 vector<SGMetarCloud> cv = m->getClouds();
911 vector<SGMetarCloud>::const_iterator cloud;
913 const char *cl = "/environment/metar/clouds/layer[%i]";
914 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
915 const char *coverage_string[5] =
916 { "clear", "few", "scattered", "broken", "overcast" };
917 const double thickness[5] = { 0, 65, 600,750, 1000};
920 snprintf(s, 128, cl, i);
921 strncat(s, "/coverage", 128);
922 q = cloud->getCoverage();
923 fgSetString(s, coverage_string[q] );
925 snprintf(s, 128, cl, i);
926 strncat(s, "/elevation-ft", 128);
927 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
929 snprintf(s, 128, cl, i);
930 strncat(s, "/thickness-ft", 128);
931 fgSetDouble(s, thickness[q]);
933 snprintf(s, 128, cl, i);
934 strncat(s, "/span-m", 128);
935 fgSetDouble(s, 40000.0);
938 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
939 snprintf(s, 128, cl, i);
940 strncat(s, "/coverage", 128);
941 fgSetString(s, "clear");
943 snprintf(s, 128, cl, i);
944 strncat(s, "/elevation-ft", 128);
945 fgSetDouble(s, -9999);
947 snprintf(s, 128, cl, i);
948 strncat(s, "/thickness-ft", 128);
951 snprintf(s, 128, cl, i);
952 strncat(s, "/span-m", 128);
953 fgSetDouble(s, 40000.0);
956 fgSetDouble("/environment/metar/rain-norm", m->getRain());
957 fgSetDouble("/environment/metar/hail-norm", m->getHail());
958 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
959 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
963 #if defined(ENABLE_THREADS)
965 FGMetarEnvironmentCtrl::thread_stop()
967 request_queue.push(NULL); // ask thread to terminate
972 FGMetarEnvironmentCtrl::MetarThread::run()
976 FGAirport* apt = fetcher->request_queue.pop();
979 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << apt->ident() );
980 FGMetarResult result = fetcher->fetch_data( apt );
981 fetcher->result_queue.push( result );
984 #endif // ENABLE_THREADS
987 // end of environment_ctrl.cxx