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.
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/structure/commands.hxx>
33 #include <simgear/structure/exception.hxx>
35 #include <Airports/simple.hxx>
36 #include <Main/fg_props.hxx>
37 #include <Main/util.hxx>
39 #include "environment_mgr.hxx"
40 #include "environment_ctrl.hxx"
44 class metar_filter : public FGAirportSearchFilter {
45 virtual bool pass(FGAirport *a) { return a->getMetar(); }
49 ////////////////////////////////////////////////////////////////////////
50 // Implementation of FGEnvironmentCtrl abstract base class.
51 ////////////////////////////////////////////////////////////////////////
53 FGEnvironmentCtrl::FGEnvironmentCtrl ()
61 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
66 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
68 _environment = environment;
72 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
78 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
84 FGEnvironmentCtrl::setElevationFt (double elev_ft)
90 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
99 ////////////////////////////////////////////////////////////////////////
100 // Implementation of FGUserDefEnvironmentCtrl.
101 ////////////////////////////////////////////////////////////////////////
103 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
104 : _base_wind_speed_node(0),
105 _gust_wind_speed_node(0),
106 _current_wind_speed_kt(0),
107 _delta_wind_speed_kt(0)
111 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
116 FGUserDefEnvironmentCtrl::init ()
118 // Fill in some defaults.
119 if (!fgHasNode("/environment/params/base-wind-speed-kt"))
120 fgSetDouble("/environment/params/base-wind-speed-kt",
121 fgGetDouble("/environment/wind-speed-kt"));
122 if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
123 fgSetDouble("/environment/params/gust-wind-speed-kt",
124 fgGetDouble("/environment/params/base-wind-speed-kt"));
126 _base_wind_speed_node =
127 fgGetNode("/environment/params/base-wind-speed-kt", true);
128 _gust_wind_speed_node =
129 fgGetNode("/environment/params/gust-wind-speed-kt", true);
131 _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
132 _delta_wind_speed_kt = 0.1;
136 FGUserDefEnvironmentCtrl::update (double dt)
138 double base_wind_speed = _base_wind_speed_node->getDoubleValue();
139 double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
141 if (gust_wind_speed < base_wind_speed) {
142 gust_wind_speed = base_wind_speed;
143 _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
146 if (base_wind_speed == gust_wind_speed) {
147 _current_wind_speed_kt = base_wind_speed;
149 int rn = rand() % 128;
150 int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
151 double gust = _current_wind_speed_kt - base_wind_speed;
152 double incr = gust / 50;
155 _delta_wind_speed_kt = - _delta_wind_speed_kt;
157 _delta_wind_speed_kt -= incr * sign;
159 _delta_wind_speed_kt += incr * sign;
161 _current_wind_speed_kt += _delta_wind_speed_kt;
163 if (_current_wind_speed_kt < base_wind_speed) {
164 _current_wind_speed_kt = base_wind_speed;
165 _delta_wind_speed_kt = 0.01;
166 } else if (_current_wind_speed_kt > gust_wind_speed) {
167 _current_wind_speed_kt = gust_wind_speed;
168 _delta_wind_speed_kt = -0.01;
172 if (_environment != 0)
173 _environment->set_wind_speed_kt(_current_wind_speed_kt);
178 ////////////////////////////////////////////////////////////////////////
179 // Implementation of FGInterpolateEnvironmentCtrl.
180 ////////////////////////////////////////////////////////////////////////
182 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
186 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
189 for (i = 0; i < _boundary_table.size(); i++)
190 delete _boundary_table[i];
191 for (i = 0; i < _aloft_table.size(); i++)
192 delete _aloft_table[i];
198 FGInterpolateEnvironmentCtrl::init ()
200 read_table(fgGetNode("/environment/config/boundary", true),
202 read_table(fgGetNode("/environment/config/aloft", true),
207 FGInterpolateEnvironmentCtrl::reinit ()
210 for (i = 0; i < _boundary_table.size(); i++)
211 delete _boundary_table[i];
212 for (i = 0; i < _aloft_table.size(); i++)
213 delete _aloft_table[i];
214 _boundary_table.clear();
215 _aloft_table.clear();
220 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
221 vector<bucket *> &table)
223 for (int i = 0; i < node->nChildren(); i++) {
224 const SGPropertyNode * child = node->getChild(i);
225 if ( strcmp(child->getName(), "entry") == 0
226 && child->getStringValue("elevation-ft", "")[0] != '\0'
227 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
229 bucket * b = new bucket;
231 b->environment.copy(table[i-1]->environment);
232 b->environment.read(child);
233 b->altitude_ft = b->environment.get_elevation_ft();
237 sort(table.begin(), table.end(), bucket::lessThan);
241 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
244 double altitude_ft = fgGetDouble("/position/altitude-ft");
245 double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
246 double boundary_transition =
247 fgGetDouble("/environment/config/boundary-transition-ft", 500);
249 // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
251 int length = _boundary_table.size();
255 double boundary_limit = _boundary_table[length-1]->altitude_ft;
256 if (boundary_limit >= altitude_agl_ft) {
257 do_interpolate(_boundary_table, altitude_agl_ft,
260 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
262 do_interpolate(_boundary_table, altitude_agl_ft, &env1);
263 do_interpolate(_aloft_table, altitude_ft, &env2);
265 (altitude_agl_ft - boundary_limit) / boundary_transition;
266 interpolate(&env1, &env2, fraction, _environment);
272 do_interpolate(_aloft_table, altitude_ft, _environment);
276 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
278 FGEnvironment * environment)
280 int length = table.size();
284 // Boundary conditions
285 if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
286 environment->copy(table[0]->environment);
288 } else if (table[length-1]->altitude_ft <= altitude_ft) {
289 environment->copy(table[length-1]->environment);
293 // Search the interpolation table
294 for (int i = 0; i < length - 1; i++) {
295 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
296 FGEnvironment * env1 = &(table[i]->environment);
297 FGEnvironment * env2 = &(table[i+1]->environment);
299 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
303 ((altitude_ft - table[i]->altitude_ft) /
304 (table[i+1]->altitude_ft - table[i]->altitude_ft));
305 interpolate(env1, env2, fraction, environment);
313 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
315 return (altitude_ft < b.altitude_ft);
319 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
321 return (a->altitude_ft) < (b->altitude_ft);
325 ////////////////////////////////////////////////////////////////////////
326 // Implementation of FGMetarEnvironmentCtrl.
327 ////////////////////////////////////////////////////////////////////////
329 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
330 : env( new FGInterpolateEnvironmentCtrl ),
332 metar_loaded( false ),
333 search_interval_sec( 60.0 ), // 1 minute
334 same_station_interval_sec( 900.0 ), // 15 minutes
335 search_elapsed( 9999.0 ),
336 fetch_elapsed( 9999.0 ),
338 proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
339 proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
340 proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
341 metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
343 // Interpolation constant definitions.
344 EnvironmentUpdatePeriodSec( 0.2 ),
345 MaxWindChangeKtsSec( 0.2 ),
346 MaxVisChangePercentSec( 0.05 ),
347 MaxPressureChangeInHgSec( 0.0033 ),
348 MaxCloudAltitudeChangeFtSec( 20.0 ),
349 MaxCloudThicknessChangeFtSec( 50.0 ),
350 MaxCloudInterpolationHeightFt( 5000.0 ),
351 MaxCloudInterpolationDeltaFt( 4000.0 ),
358 #if defined(ENABLE_THREADS)
359 thread = new MetarThread(this);
360 thread->setProcessorAffinity(1);
362 #endif // ENABLE_THREADS
365 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
367 #if defined(ENABLE_THREADS)
369 #endif // ENABLE_THREADS
376 // use a "command" to set station temp at station elevation
377 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
379 SGPropertyNode *node = args.getNode("temp-degc", 0, true);
380 node->setFloatValue( temp_degc );
381 node = args.getNode("altitude-ft", 0, true);
382 node->setFloatValue( altitude_ft );
383 globals->get_commands()->execute("set-outside-air-temp-degc", &args);
387 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
389 SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
390 node->setFloatValue( dewpoint_degc );
391 node = args.getNode("altitude-ft", 0, true);
392 node->setFloatValue( altitude_ft );
393 globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
398 FGMetarEnvironmentCtrl::update_env_config ()
409 // If we aren't in the METAR scenario, don't attempt to interpolate.
410 if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR")) return;
413 // Generate interpolated values between the METAR and the current
416 // Pick up the METAR wind values and convert them into a vector.
418 double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
419 double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
421 metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
422 metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
424 // Convert the current wind values and convert them into a vector
426 double current_speed =
427 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
428 double current_heading = fgGetDouble(
429 "/environment/config/boundary/entry/wind-from-heading-deg");
431 current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
432 current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
434 // Determine the maximum component-wise value that the wind can change.
435 // First we determine the fraction in the X and Y component, then
436 // factor by the maximum wind change.
437 double x = fabs(current[0] - metar[0]);
438 double y = fabs(current[1] - metar[1]);
439 double dx = x / (x + y);
442 double maxdx = dx * MaxWindChangeKtsSec;
443 double maxdy = dy * MaxWindChangeKtsSec;
445 // Interpolate each component separately.
446 current[0] = interpolate_val(current[0], metar[0], maxdx);
447 current[1] = interpolate_val(current[1], metar[1], maxdy);
449 // Now convert back to polar coordinates.
450 if ((current[0] == 0.0) && (current[1] == 0.0)) {
451 // Special case where there is no wind (otherwise atan2 barfs)
453 dir_from = current_heading;
456 // Some real wind to convert back from. Work out the speed
457 // and direction value in degrees.
458 speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
459 dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
461 // Normalize the direction.
465 SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
468 // Now handle the visibility. We convert both visibility values
469 // to X-values, then interpolate from there, then back to real values.
470 // The length_scale is fixed to 1000m, so the visibility changes by
471 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
472 // whichever is more.
474 fgGetDouble("/environment/config/boundary/entry/visibility-m");
475 double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
476 double currentxval = log(1000.0 + currentvis);
477 double metarxval = log(1000.0 + metarvis);
479 currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
481 // Now convert back from an X-value to a straightforward visibility.
482 vis = exp(currentxval) - 1000.0;
484 pressure = interpolate_prop(
485 "/environment/config/boundary/entry/pressure-sea-level-inhg",
486 "/environment/metar/pressure-inhg",
487 MaxPressureChangeInHgSec);
489 dir_to = fgGetDouble("/environment/metar/base-wind-range-to");
490 gust = fgGetDouble("/environment/metar/gust-wind-speed-kt");
491 temp = fgGetDouble("/environment/metar/temperature-degc");
492 dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
494 // Set the cloud layers by interpolating over the METAR versions.
495 SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
497 vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
498 vector<SGPropertyNode_ptr>::const_iterator layer;
499 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
501 const char *cl = "/environment/clouds/layer[%i]";
502 double aircraft_alt = fgGetDouble("/position/altitude-ft");
505 bool rebuild_clouds = false;
507 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
511 // In the case of clouds, we want to avoid writing if nothing has
512 // changed, as these properties are tied to the renderer and will
513 // cause the clouds to be updated, reseting the texture locations.
515 // We don't interpolate the coverage values as no-matter how we
516 // do it, it will be quite a sudden change of texture. Better to
517 // have a single change than four or five.
518 snprintf(s, 128, cl, i);
519 strncat(s, "/coverage", 128);
520 const char* coverage = (*layer)->getStringValue("coverage", "clear");
521 if (strncmp(fgGetString(s), coverage, 128) != 0) {
522 fgSetString(s, coverage);
523 rebuild_clouds = true;
526 snprintf(s, 128, cl, i);
527 strncat(s, "/elevation-ft", 128);
528 double current_alt = fgGetDouble(s);
529 double required_alt = (*layer)->getDoubleValue("elevation-ft");
531 if (current_alt < -9000 || required_alt < -9000 ||
532 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
533 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
534 // We don't interpolate any layers that are
535 // - too far above us to be visible
536 // - too far below us to be visible
537 // - with too large a difference to make interpolation sensible
538 // - to or from -9999 (used as a placeholder)
539 // - any values that are too high above us,
540 snprintf(s, 128, cl, i);
541 strncat(s, "/elevation-ft", 128);
542 if (current_alt != required_alt)
543 fgSetDouble(s, required_alt);
545 snprintf(s, 128, cl, i);
546 strncat(s, "/thickness-ft", 128);
547 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
548 fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
551 // Interpolate the other values in the usual way
552 if (current_alt != required_alt) {
553 current_alt = interpolate_val(current_alt,
555 MaxCloudAltitudeChangeFtSec);
556 fgSetDouble(s, current_alt);
559 snprintf(s, 128, cl, i);
560 strncat(s, "/thickness-ft", 128);
561 currentval = fgGetDouble(s);
562 requiredval = (*layer)->getDoubleValue("thickness-ft");
564 if (currentval != requiredval) {
565 currentval = interpolate_val(currentval,
567 MaxCloudThicknessChangeFtSec);
568 fgSetDouble(s, currentval);
573 if (rebuild_clouds) {
574 // Force an update of the 3D clouds
575 fgSetDouble("/environment/rebuild-layers", 1.0);
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 const SGPropertyNode *longitude
656 = fgGetNode( "/position/longitude-deg", true );
657 const SGPropertyNode *latitude
658 = fgGetNode( "/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 const FGAirport* a = globals->get_airports()
670 ->search( longitude->getDoubleValue(),
671 latitude->getDoubleValue(),
675 FGMetarResult result = fetch_data( a->getId() );
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 = "
692 globals->get_airports()->no_metar( a->getId() );
696 metar_max_age->setLongValue(max_age);
700 FGMetarEnvironmentCtrl::reinit ()
704 metar_loaded = false;
710 FGMetarEnvironmentCtrl::update(double delta_time_sec)
713 _dt += delta_time_sec;
714 if (_error_count >= 3)
717 FGMetarResult result;
719 static const SGPropertyNode *longitude
720 = fgGetNode( "/position/longitude-deg", true );
721 static const SGPropertyNode *latitude
722 = fgGetNode( "/position/latitude-deg", true );
723 search_elapsed += delta_time_sec;
724 fetch_elapsed += delta_time_sec;
725 interpolate_elapsed += delta_time_sec;
727 // if time for a new search request, push it onto the request
729 if ( search_elapsed > search_interval_sec ) {
730 const FGAirport* a = globals->get_airports()
731 ->search( longitude->getDoubleValue(),
732 latitude->getDoubleValue(),
736 if ( !last_apt || last_apt->getId() != a->getId()
737 || fetch_elapsed > same_station_interval_sec )
739 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
741 request_queue.push( a->getId() );
744 search_elapsed = 0.0;
747 search_elapsed = 0.0;
748 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
749 << same_station_interval_sec - fetch_elapsed );
752 SG_LOG( SG_GENERAL, SG_WARN,
753 "Unable to find any airports with metar" );
755 } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
756 // Interpolate the current configuration closer to the actual METAR
759 interpolate_elapsed = 0.0;
762 #if !defined(ENABLE_THREADS)
763 // No loader thread running so manually fetch the data
765 while ( !request_queue.empty() ) {
766 id = request_queue.front();
771 SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
772 result = fetch_data( id );
773 result_queue.push( result );
775 #endif // ENABLE_THREADS
777 // process any results from the loader.
778 while ( !result_queue.empty() ) {
779 result = result_queue.front();
781 if ( result.m != NULL ) {
782 update_metar_properties( result.m );
787 // mark as no metar so it doesn't show up in subsequent
788 // searches, and signal an immediate re-search.
789 SG_LOG( SG_GENERAL, SG_WARN,
790 "no metar at station = " << result.icao );
791 globals->get_airports()->no_metar( result.icao );
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