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]);
441 double dx = x / (x + y);
444 double maxdx = dx * MaxWindChangeKtsSec;
445 double maxdy = dy * MaxWindChangeKtsSec;
447 // Interpolate each component separately.
448 current[0] = interpolate_val(current[0], metar[0], maxdx);
449 current[1] = interpolate_val(current[1], metar[1], maxdy);
451 // Now convert back to polar coordinates.
452 if ((current[0] == 0.0) && (current[1] == 0.0)) {
453 // Special case where there is no wind (otherwise atan2 barfs)
455 dir_from = current_heading;
458 // Some real wind to convert back from. Work out the speed
459 // and direction value in degrees.
460 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
461 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
463 // Normalize the direction.
467 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
470 // Now handle the visibility. We convert both visibility values
471 // to X-values, then interpolate from there, then back to real values.
472 // The length_scale is fixed to 1000m, so the visibility changes by
473 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
474 // whichever is more.
476 fgGetDouble("/environment/config/boundary/entry/visibility-m");
477 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
478 double currentxval = log(1000.0 + currentvis);
479 double metarxval = log(1000.0 + metarvis);
481 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
483 // Now convert back from an X-value to a straightforward visibility.
484 vis = exp(currentxval) - 1000.0;
486 pressure = interpolate_prop(
487 "/environment/config/boundary/entry/pressure-sea-level-inhg",
488 "/environment/metar/pressure-inhg",
489 MaxPressureChangeInHgSec);
491 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
492 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
493 temp = fgGetDouble("/environment/metar/temperature-degc");
494 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
496 // Set the cloud layers by interpolating over the METAR versions.
497 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
499 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
500 vector<SGPropertyNode_ptr>::const_iterator layer;
501 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
503 const char *cl = "/environment/clouds/layer[%i]";
504 double aircraft_alt = fgGetDouble("/position/altitude-ft");
508 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
512 // In the case of clouds, we want to avoid writing if nothing has
513 // changed, as these properties are tied to the renderer and will
514 // cause the clouds to be updated, reseting the texture locations.
516 // We don't interpolate the coverage values as no-matter how we
517 // do it, it will be quite a sudden change of texture. Better to
518 // have a single change than four or five.
519 snprintf(s, 128, cl, i);
520 strncat(s, "/coverage", 128);
521 const char* coverage = (*layer)->getStringValue("coverage", "clear");
522 if (strncmp(fgGetString(s), coverage, 128) != 0)
523 fgSetString(s, coverage);
525 snprintf(s, 128, cl, i);
526 strncat(s, "/elevation-ft", 128);
527 double current_alt = fgGetDouble(s);
528 double required_alt = (*layer)->getDoubleValue("elevation-ft");
530 if (current_alt < -9000 || required_alt < -9000 ||
531 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
532 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
533 // We don't interpolate any layers that are
534 // - too far above us to be visible
535 // - too far below us to be visible
536 // - with too large a difference to make interpolation sensible
537 // - to or from -9999 (used as a placeholder)
538 // - any values that are too high above us,
539 snprintf(s, 128, cl, i);
540 strncat(s, "/elevation-ft", 128);
541 if (current_alt != required_alt)
542 fgSetDouble(s, required_alt);
544 snprintf(s, 128, cl, i);
545 strncat(s, "/thickness-ft", 128);
546 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
547 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
550 // Interpolate the other values in the usual way
551 if (current_alt != required_alt) {
552 current_alt = interpolate_val(current_alt,
554 MaxCloudAltitudeChangeFtSec);
555 fgSetDouble(s, current_alt);
558 snprintf(s, 128, cl, i);
559 strncat(s, "/thickness-ft", 128);
560 currentval = fgGetDouble(s);
561 requiredval = (*layer)->getDoubleValue("thickness-ft");
563 if (currentval != requiredval) {
564 currentval = interpolate_val(currentval,
566 MaxCloudThicknessChangeFtSec);
567 fgSetDouble(s, currentval);
573 // We haven't already loaded a METAR, so apply it immediately.
574 dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
575 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
576 speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
577 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
578 vis = fgGetDouble("/environment/metar/min-visibility-m");
579 pressure = fgGetDouble("/environment/metar/pressure-inhg");
580 temp = fgGetDouble("/environment/metar/temperature-degc");
581 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
583 // Set the cloud layers by copying over the METAR versions.
584 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds", true);
586 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
587 vector<SGPropertyNode_ptr>::const_iterator layer;
588 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
590 const char *cl = "/environment/clouds/layer[%i]";
594 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
595 snprintf(s, 128, cl, i);
596 strncat(s, "/coverage", 128);
597 fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
599 snprintf(s, 128, cl, i);
600 strncat(s, "/elevation-ft", 128);
601 fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
603 snprintf(s, 128, cl, i);
604 strncat(s, "/thickness-ft", 128);
605 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
607 snprintf(s, 128, cl, i);
608 strncat(s, "/span-m", 128);
609 fgSetDouble(s, 40000.0);
612 // Force an update of the 3D clouds
613 fgSetDouble("/environment/rebuild-layers", 1.0);
616 fgSetupWind(dir_from, dir_to, speed, gust);
617 fgDefaultWeatherValue("visibility-m", vis);
618 set_temp_at_altitude(temp, station_elevation_ft);
619 set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
620 fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
622 // We've now successfully loaded a METAR into the configuration
626 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
627 const char * requiredname,
630 double currentval = fgGetDouble(currentname);
631 double requiredval = fgGetDouble(requiredname);
632 return interpolate_val(currentval, requiredval, dt);
635 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
639 double dval = EnvironmentUpdatePeriodSec * dt;
641 if (fabs(currentval - requiredval) < dval) return requiredval;
642 if (currentval < requiredval) return (currentval + dval);
643 if (currentval > requiredval) return (currentval - dval);
648 FGMetarEnvironmentCtrl::init ()
650 SGGeod pos = SGGeod::fromDeg(
651 fgGetDouble("/position/longitude-deg", true),
652 fgGetDouble( "/position/latitude-deg", true));
654 metar_loaded = false;
655 bool found_metar = false;
656 long max_age = metar_max_age->getLongValue();
657 // Don't check max age during init so that we don't loop over a lot
658 // of airports metar if there is a problem.
659 // The update() calls will find a correct metar if things went wrong here
660 metar_max_age->setLongValue(0);
662 while ( !found_metar && (_error_count < 3) ) {
663 AirportWithMetar filter;
664 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
669 FGMetarResult result = fetch_data(a);
670 if ( result.m != NULL ) {
671 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
674 search_elapsed = 0.0;
676 update_metar_properties( result.m );
681 // mark as no metar so it doesn't show up in subsequent
683 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = " << a->ident() );
686 } // of airprot-with-metar search iteration
688 metar_max_age->setLongValue(max_age);
692 FGMetarEnvironmentCtrl::reinit ()
696 metar_loaded = false;
702 FGMetarEnvironmentCtrl::update(double delta_time_sec)
704 _dt += delta_time_sec;
705 if (_error_count >= 3)
708 FGMetarResult result;
710 static const SGPropertyNode *longitude
711 = fgGetNode( "/position/longitude-deg", true );
712 static const SGPropertyNode *latitude
713 = fgGetNode( "/position/latitude-deg", true );
714 SGGeod pos = SGGeod::fromDeg(longitude->getDoubleValue(),
715 latitude->getDoubleValue());
717 search_elapsed += delta_time_sec;
718 fetch_elapsed += delta_time_sec;
719 interpolate_elapsed += delta_time_sec;
721 // if time for a new search request, push it onto the request
723 if ( search_elapsed > search_interval_sec ) {
724 AirportWithMetar filter;
725 FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
727 if ( !last_apt || last_apt->ident() != a->ident()
728 || fetch_elapsed > same_station_interval_sec )
730 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
732 request_queue.push(a);
734 search_elapsed = 0.0;
737 search_elapsed = 0.0;
738 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
739 << same_station_interval_sec - fetch_elapsed );
743 SG_LOG( SG_GENERAL, SG_WARN,
744 "Unable to find any airports with metar" );
746 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
747 // Interpolate the current configuration closer to the actual METAR
750 interpolate_elapsed = 0.0;
753 #if !defined(ENABLE_THREADS)
754 // No loader thread running so manually fetch the data
755 FGAirport* apt = NULL;
756 while ( !request_queue.empty() ) {
757 apt = request_queue.front();
762 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << apt->ident() );
763 result = fetch_data( apt );
764 result_queue.push( result );
766 #endif // ENABLE_THREADS
768 // process any results from the loader.
769 while ( !result_queue.empty() ) {
770 result = result_queue.front();
772 if ( result.m != NULL ) {
773 update_metar_properties( result.m );
778 // mark as no metar so it doesn't show up in subsequent
779 // searches, and signal an immediate re-search.
780 SG_LOG( SG_GENERAL, SG_WARN,
781 "no metar at station = " << result.airport->ident() );
782 result.airport->setMetar(false);
783 search_elapsed = 9999.0;
787 env->update(delta_time_sec);
792 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
794 env->setEnvironment(environment);
798 FGMetarEnvironmentCtrl::fetch_data(FGAirport* apt)
800 FGMetarResult result;
801 result.airport = apt;
803 // if the last error was more than three seconds ago,
804 // then pretent nothing happened.
813 station_elevation_ft = apt->getElevation();
815 // fetch current metar data
817 string host = proxy_host->getStringValue();
818 string auth = proxy_auth->getStringValue();
819 string port = proxy_port->getStringValue();
820 result.m = new FGMetar( apt->ident(), host, port, auth);
822 long max_age = metar_max_age->getLongValue();
823 long age = result.m->getAge_min();
824 if (max_age && age > max_age) {
825 SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
829 if (++_stale_count > 10) {
831 throw sg_io_exception("More than 10 stale METAR messages in a row."
832 " Check your system time!");
837 } catch (const sg_io_exception& e) {
838 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
839 << e.getFormattedMessage().c_str() );
840 #if defined(ENABLE_THREADS)
841 if (_error_count++ >= 3) {
842 SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
857 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
863 fgSetString("/environment/metar/real-metar", m->getData());
864 // don't update with real weather when we use a custom weather scenario
865 const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
866 if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
868 fgSetString("/environment/metar/last-metar", m->getData());
869 fgSetString("/environment/metar/station-id", m->getId());
870 fgSetDouble("/environment/metar/min-visibility-m",
871 m->getMinVisibility().getVisibility_m() );
872 fgSetDouble("/environment/metar/max-visibility-m",
873 m->getMaxVisibility().getVisibility_m() );
875 const SGMetarVisibility *dirvis = m->getDirVisibility();
876 for (i = 0; i < 8; i++, dirvis++) {
877 const char *min = "/environment/metar/visibility[%d]/min-m";
878 const char *max = "/environment/metar/visibility[%d]/max-m";
880 d = dirvis->getVisibility_m();
882 snprintf(s, 128, min, i);
884 snprintf(s, 128, max, i);
888 fgSetInt("/environment/metar/base-wind-range-from",
889 m->getWindRangeFrom() );
890 fgSetInt("/environment/metar/base-wind-range-to",
891 m->getWindRangeTo() );
892 fgSetDouble("/environment/metar/base-wind-speed-kt",
893 m->getWindSpeed_kt() );
894 fgSetDouble("/environment/metar/gust-wind-speed-kt",
895 m->getGustSpeed_kt() );
896 fgSetDouble("/environment/metar/temperature-degc",
897 m->getTemperature_C() );
898 fgSetDouble("/environment/metar/dewpoint-degc",
899 m->getDewpoint_C() );
900 fgSetDouble("/environment/metar/rel-humidity-norm",
901 m->getRelHumidity() );
902 fgSetDouble("/environment/metar/pressure-inhg",
903 m->getPressure_inHg() );
905 vector<SGMetarCloud> cv = m->getClouds();
906 vector<SGMetarCloud>::const_iterator cloud;
908 const char *cl = "/environment/metar/clouds/layer[%i]";
909 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
910 const char *coverage_string[5] =
911 { "clear", "few", "scattered", "broken", "overcast" };
912 const double thickness[5] = { 0, 65, 600,750, 1000};
915 snprintf(s, 128, cl, i);
916 strncat(s, "/coverage", 128);
917 q = cloud->getCoverage();
918 fgSetString(s, coverage_string[q] );
920 snprintf(s, 128, cl, i);
921 strncat(s, "/elevation-ft", 128);
922 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
924 snprintf(s, 128, cl, i);
925 strncat(s, "/thickness-ft", 128);
926 fgSetDouble(s, thickness[q]);
928 snprintf(s, 128, cl, i);
929 strncat(s, "/span-m", 128);
930 fgSetDouble(s, 40000.0);
933 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
934 snprintf(s, 128, cl, i);
935 strncat(s, "/coverage", 128);
936 fgSetString(s, "clear");
938 snprintf(s, 128, cl, i);
939 strncat(s, "/elevation-ft", 128);
940 fgSetDouble(s, -9999);
942 snprintf(s, 128, cl, i);
943 strncat(s, "/thickness-ft", 128);
946 snprintf(s, 128, cl, i);
947 strncat(s, "/span-m", 128);
948 fgSetDouble(s, 40000.0);
951 fgSetDouble("/environment/metar/rain-norm", m->getRain());
952 fgSetDouble("/environment/metar/hail-norm", m->getHail());
953 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
954 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
958 #if defined(ENABLE_THREADS)
960 FGMetarEnvironmentCtrl::thread_stop()
962 request_queue.push(NULL); // ask thread to terminate
967 FGMetarEnvironmentCtrl::MetarThread::run()
971 FGAirport* apt = fetcher->request_queue.pop();
974 SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << apt->ident() );
975 FGMetarResult result = fetcher->fetch_data( apt );
976 fetcher->result_queue.push( result );
979 #endif // ENABLE_THREADS
982 // end of environment_ctrl.cxx