]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
Make comparisons against OSG version less flakey
[flightgear.git] / src / Environment / environment_ctrl.cxx
1 // environment_ctrl.cxx -- manager for natural environment information.
2 //
3 // Written by David Megginson, started February 2002.
4 //
5 // Copyright (C) 2002  David Megginson - david@megginson.com
6 //
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.
11 //
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.
16 //
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.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <stdlib.h>
28 #include <math.h>
29 #include <algorithm>
30
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/structure/commands.hxx>
33 #include <simgear/structure/exception.hxx>
34
35 #include <Airports/simple.hxx>
36 #include <Main/fg_props.hxx>
37 #include <Main/util.hxx>
38
39 #include "environment_mgr.hxx"
40 #include "environment_ctrl.hxx"
41
42 SG_USING_STD(sort);
43
44
45 \f
46 ////////////////////////////////////////////////////////////////////////
47 // Implementation of FGEnvironmentCtrl abstract base class.
48 ////////////////////////////////////////////////////////////////////////
49
50 FGEnvironmentCtrl::FGEnvironmentCtrl ()
51   : _environment(0),
52     _lon_deg(0),
53     _lat_deg(0),
54     _elev_ft(0)
55 {
56 }
57
58 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
59 {
60 }
61
62 void
63 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
64 {
65   _environment = environment;
66 }
67
68 void
69 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
70 {
71   _lon_deg = lon_deg;
72 }
73
74 void
75 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
76 {
77   _lat_deg = lat_deg;
78 }
79
80 void
81 FGEnvironmentCtrl::setElevationFt (double elev_ft)
82 {
83   _elev_ft = elev_ft;
84 }
85
86 void
87 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
88 {
89   _lon_deg = lon_deg;
90   _lat_deg = lat_deg;
91   _elev_ft = elev_ft;
92 }
93
94
95 \f
96 ////////////////////////////////////////////////////////////////////////
97 // Implementation of FGUserDefEnvironmentCtrl.
98 ////////////////////////////////////////////////////////////////////////
99
100 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
101   : _base_wind_speed_node(0),
102     _gust_wind_speed_node(0),
103     _current_wind_speed_kt(0),
104     _delta_wind_speed_kt(0)
105 {
106 }
107
108 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
109 {
110 }
111
112 void
113 FGUserDefEnvironmentCtrl::init ()
114 {
115                                 // Fill in some defaults.
116   if (!fgHasNode("/environment/params/base-wind-speed-kt"))
117     fgSetDouble("/environment/params/base-wind-speed-kt",
118                 fgGetDouble("/environment/wind-speed-kt"));
119   if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
120     fgSetDouble("/environment/params/gust-wind-speed-kt",
121                 fgGetDouble("/environment/params/base-wind-speed-kt"));
122
123   _base_wind_speed_node =
124     fgGetNode("/environment/params/base-wind-speed-kt", true);
125   _gust_wind_speed_node =
126     fgGetNode("/environment/params/gust-wind-speed-kt", true);
127
128   _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
129   _delta_wind_speed_kt = 0.1;
130 }
131
132 void
133 FGUserDefEnvironmentCtrl::update (double dt)
134 {
135   double base_wind_speed = _base_wind_speed_node->getDoubleValue();
136   double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
137
138   if (gust_wind_speed < base_wind_speed) {
139       gust_wind_speed = base_wind_speed;
140       _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
141   }
142
143   if (base_wind_speed == gust_wind_speed) {
144     _current_wind_speed_kt = base_wind_speed;
145   } else {
146     int rn = rand() % 128;
147     int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
148     double gust = _current_wind_speed_kt - base_wind_speed;
149     double incr = gust / 50;
150
151     if (rn == 0)
152       _delta_wind_speed_kt = - _delta_wind_speed_kt;
153     else if (rn < 4)
154       _delta_wind_speed_kt -= incr * sign;
155     else if (rn < 16)
156       _delta_wind_speed_kt += incr * sign;
157
158     _current_wind_speed_kt += _delta_wind_speed_kt;
159
160     if (_current_wind_speed_kt < base_wind_speed) {
161       _current_wind_speed_kt = base_wind_speed;
162       _delta_wind_speed_kt = 0.01;
163     } else if (_current_wind_speed_kt > gust_wind_speed) {
164       _current_wind_speed_kt = gust_wind_speed;
165       _delta_wind_speed_kt = -0.01;
166     }
167   }
168   
169   if (_environment != 0)
170     _environment->set_wind_speed_kt(_current_wind_speed_kt);
171 }
172
173
174 \f
175 ////////////////////////////////////////////////////////////////////////
176 // Implementation of FGInterpolateEnvironmentCtrl.
177 ////////////////////////////////////////////////////////////////////////
178
179 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
180 {
181 }
182
183 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
184 {
185     unsigned int i;
186     for (i = 0; i < _boundary_table.size(); i++)
187         delete _boundary_table[i];
188     for (i = 0; i < _aloft_table.size(); i++)
189         delete _aloft_table[i];
190 }
191
192
193
194 void
195 FGInterpolateEnvironmentCtrl::init ()
196 {
197     read_table(fgGetNode("/environment/config/boundary", true),
198                _boundary_table);
199     read_table(fgGetNode("/environment/config/aloft", true),
200                _aloft_table);
201 }
202
203 void
204 FGInterpolateEnvironmentCtrl::reinit ()
205 {
206     unsigned int i;
207     for (i = 0; i < _boundary_table.size(); i++)
208         delete _boundary_table[i];
209     for (i = 0; i < _aloft_table.size(); i++)
210         delete _aloft_table[i];
211     _boundary_table.clear();
212     _aloft_table.clear();
213     init();
214 }
215
216 void
217 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
218                                           vector<bucket *> &table)
219 {
220     for (int i = 0; i < node->nChildren(); i++) {
221         const SGPropertyNode * child = node->getChild(i);
222         if ( strcmp(child->getName(), "entry") == 0
223              && child->getStringValue("elevation-ft", "")[0] != '\0'
224              && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
225         {
226             bucket * b = new bucket;
227             if (i > 0)
228                 b->environment.copy(table[i-1]->environment);
229             b->environment.read(child);
230             b->altitude_ft = b->environment.get_elevation_ft();
231             table.push_back(b);
232         }
233     }
234     sort(table.begin(), table.end(), bucket::lessThan);
235 }
236
237 void
238 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
239 {
240                                 // FIXME
241     double altitude_ft = fgGetDouble("/position/altitude-ft");
242     double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
243     double boundary_transition =
244         fgGetDouble("/environment/config/boundary-transition-ft", 500);
245
246     // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
247
248     int length = _boundary_table.size();
249
250     if (length > 0) {
251                                 // boundary table
252         double boundary_limit = _boundary_table[length-1]->altitude_ft;
253         if (boundary_limit >= altitude_agl_ft) {
254             do_interpolate(_boundary_table, altitude_agl_ft,
255                            _environment);
256             return;
257         } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
258                                 // both tables
259             do_interpolate(_boundary_table, altitude_agl_ft, &env1);
260             do_interpolate(_aloft_table, altitude_ft, &env2);
261             double fraction =
262                 (altitude_agl_ft - boundary_limit) / boundary_transition;
263             interpolate(&env1, &env2, fraction, _environment);
264             return;
265         }
266     }
267
268                                 // aloft table
269     do_interpolate(_aloft_table, altitude_ft, _environment);
270 }
271
272 void
273 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
274                                               double altitude_ft,
275                                               FGEnvironment * environment)
276 {
277     int length = table.size();
278     if (length == 0)
279         return;
280
281                                 // Boundary conditions
282     if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
283         environment->copy(table[0]->environment);
284         return;
285     } else if (table[length-1]->altitude_ft <= altitude_ft) {
286         environment->copy(table[length-1]->environment);
287         return;
288     }
289         
290                                 // Search the interpolation table
291     for (int i = 0; i < length - 1; i++) {
292         if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
293                 FGEnvironment * env1 = &(table[i]->environment);
294                 FGEnvironment * env2 = &(table[i+1]->environment);
295                 double fraction;
296                 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
297                     fraction = 1.0;
298                 else
299                     fraction =
300                         ((altitude_ft - table[i]->altitude_ft) /
301                          (table[i+1]->altitude_ft - table[i]->altitude_ft));
302                 interpolate(env1, env2, fraction, environment);
303
304                 return;
305         }
306     }
307 }
308
309 bool
310 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
311 {
312     return (altitude_ft < b.altitude_ft);
313 }
314
315 bool
316 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
317 {
318     return (a->altitude_ft) < (b->altitude_ft);
319 }
320
321 \f
322 ////////////////////////////////////////////////////////////////////////
323 // Implementation of FGMetarEnvironmentCtrl.
324 ////////////////////////////////////////////////////////////////////////
325
326 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
327     : env( new FGInterpolateEnvironmentCtrl ),
328       _icao( "" ),
329       metar_loaded( false ),
330       search_interval_sec( 60.0 ),        // 1 minute
331       same_station_interval_sec( 900.0 ), // 15 minutes
332       search_elapsed( 9999.0 ),
333       fetch_elapsed( 9999.0 ),
334       last_apt( 0 ),
335       proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
336       proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
337       proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
338       metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
339
340       // Interpolation constant definitions.
341       EnvironmentUpdatePeriodSec( 0.2 ),
342       MaxWindChangeKtsSec( 0.2 ),
343       MaxVisChangePercentSec( 0.05 ),
344       MaxPressureChangeInHgSec( 0.0033 ),
345       MaxCloudAltitudeChangeFtSec( 20.0 ),
346       MaxCloudThicknessChangeFtSec( 50.0 ),
347       MaxCloudInterpolationHeightFt( 5000.0 ),
348
349       _error_count( 0 ),
350       _stale_count( 0 ),
351       _dt( 0.0 ),
352       _error_dt( 0.0 )
353 {
354 #if defined(ENABLE_THREADS)
355     thread = new MetarThread(this);
356     thread->start( 1 );
357 #endif // ENABLE_THREADS
358 }
359
360 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
361 {
362 #if defined(ENABLE_THREADS)
363    thread_stop();
364 #endif // ENABLE_THREADS
365
366    delete env;
367    env = NULL;
368 }
369
370
371 // use a "command" to set station temp at station elevation
372 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
373     SGPropertyNode args;
374     SGPropertyNode *node = args.getNode("temp-degc", 0, true);
375     node->setFloatValue( temp_degc );
376     node = args.getNode("altitude-ft", 0, true);
377     node->setFloatValue( altitude_ft );
378     globals->get_commands()->execute("set-outside-air-temp-degc", &args);
379 }
380
381
382 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
383     SGPropertyNode args;
384     SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
385     node->setFloatValue( dewpoint_degc );
386     node = args.getNode("altitude-ft", 0, true);
387     node->setFloatValue( altitude_ft );
388     globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
389 }
390
391
392 void
393 FGMetarEnvironmentCtrl::update_env_config ()
394 {
395     double dir_from;
396     double dir_to;
397     double speed;
398     double gust;
399     double vis;
400     double pressure;
401     double temp;
402     double dewpoint;
403
404     if (metar_loaded) {
405         // Generate interpolated values between the METAR and the current
406         // configuration.
407
408         // Pick up the METAR wind values and convert them into a vector.
409         double metar[2];
410         double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
411         double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
412
413         metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
414         metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
415
416         // Convert the current wind values and convert them into a vector
417         double current[2];
418         double current_speed =
419                 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
420         double current_heading = fgGetDouble(
421                 "/environment/config/boundary/entry/wind-from-heading-deg");
422
423         current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
424         current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
425
426         // Determine the maximum component-wise value that the wind can change.
427         // First we determine the fraction in the X and Y component, then
428         // factor by the maximum wind change.
429         double x = fabs(current[0] - metar[0]);
430         double y = fabs(current[1] - metar[1]);
431         double dx = x / (x + y);
432         double dy = 1 - dx;
433
434         double maxdx = dx * MaxWindChangeKtsSec;
435         double maxdy = dy * MaxWindChangeKtsSec;
436
437         // Interpolate each component separately.
438         current[0] = interpolate_val(current[0], metar[0], maxdx);
439         current[1] = interpolate_val(current[1], metar[1], maxdy);
440
441         // Now convert back to polar coordinates.
442         if ((current[0] == 0.0) && (current[1] == 0.0)) {
443             // Special case where there is no wind (otherwise atan2 barfs)
444             speed = 0.0;
445             dir_from = current_heading;
446
447         } else {
448             // Some real wind to convert back from. Work out the speed
449             // and direction value in degrees.
450             speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
451             dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
452
453             // Normalize the direction.
454             if (dir_from < 0.0)
455                 dir_from += 360.0;
456
457             SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
458         }
459
460         // Now handle the visibility. We convert both visibility values
461         // to X-values, then interpolate from there, then back to real values.
462         // The length_scale is fixed to 1000m, so the visibility changes by
463         // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
464         // whichever is more.
465         double currentvis =
466                 fgGetDouble("/environment/config/boundary/entry/visibility-m");
467         double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
468         double currentxval = log(1000.0 + currentvis);
469         double metarxval = log(1000.0 + metarvis);
470
471         currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
472
473         // Now convert back from an X-value to a straightforward visibility.
474         vis = exp(currentxval) - 1000.0;
475
476         pressure = interpolate_prop(
477                 "/environment/config/boundary/entry/pressure-sea-level-inhg",
478                 "/environment/metar/pressure-inhg",
479                 MaxPressureChangeInHgSec);
480
481         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
482         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
483         temp     = fgGetDouble("/environment/metar/temperature-degc");
484         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
485
486         // Set the cloud layers by interpolating over the METAR versions.
487         SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
488
489         vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
490         vector<SGPropertyNode_ptr>::const_iterator layer;
491         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
492
493         const char *cl = "/environment/clouds/layer[%i]";
494         double aircraft_alt = fgGetDouble("/position/altitude-ft");
495         char s[128];
496         int i;
497
498         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
499             double currentval;
500             double requiredval;
501
502             // In the case of clouds, we want to avoid writing if nothing has
503             // changed, as these properties are tied to the renderer and will
504             // cause the clouds to be updated, reseting the texture locations.
505
506             // We don't interpolate the coverage values as no-matter how we
507             // do it, it will be quite a sudden change of texture. Better to
508             // have a single change than four or five.
509             snprintf(s, 128, cl, i);
510             strncat(s, "/coverage", 128);
511             const char* coverage = (*layer)->getStringValue("coverage", "clear");
512             if (strncmp(fgGetString(s), coverage, 128) != 0)
513                 fgSetString(s, coverage);
514
515             snprintf(s, 128, cl, i);
516             strncat(s, "/elevation-ft", 128);
517             double current_alt = fgGetDouble(s);
518             double required_alt = (*layer)->getDoubleValue("elevation-ft");
519
520             if (current_alt < -9000 || required_alt < -9000
521                     || fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt) {
522                 // We don't interpolate any values that are too high above us,
523                 // or too far below us to be visible. Nor do we interpolate
524                 // values to or from -9999, which is used as a placeholder
525                 // when there isn't actually a cloud layer present.
526                 snprintf(s, 128, cl, i);
527                 strncat(s, "/elevation-ft", 128);
528                 if (current_alt != required_alt)
529                     fgSetDouble(s, required_alt);
530
531                 snprintf(s, 128, cl, i);
532                 strncat(s, "/thickness-ft", 128);
533                 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
534                     fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
535
536             } else {
537                 // Interpolate the other values in the usual way
538                 if (current_alt != required_alt) {
539                     current_alt = interpolate_val(current_alt,
540                                                   required_alt,
541                                                   MaxCloudAltitudeChangeFtSec);
542                     fgSetDouble(s, current_alt);
543                 }
544
545                 snprintf(s, 128, cl, i);
546                 strncat(s, "/thickness-ft", 128);
547                 currentval = fgGetDouble(s);
548                 requiredval = (*layer)->getDoubleValue("thickness-ft");
549
550                 if (currentval != requiredval) {
551                     currentval = interpolate_val(currentval,
552                                                  requiredval,
553                                                  MaxCloudThicknessChangeFtSec);
554                     fgSetDouble(s, currentval);
555                 }
556             }
557         }
558
559     } else {
560         // We haven't already loaded a METAR, so apply it immediately.
561         dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
562         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
563         speed    = fgGetDouble("/environment/metar/base-wind-speed-kt");
564         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
565         vis      = fgGetDouble("/environment/metar/min-visibility-m");
566         pressure = fgGetDouble("/environment/metar/pressure-inhg");
567         temp     = fgGetDouble("/environment/metar/temperature-degc");
568         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
569
570         // Set the cloud layers by copying over the METAR versions.
571         SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
572
573         vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
574         vector<SGPropertyNode_ptr>::const_iterator layer;
575         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
576
577         const char *cl = "/environment/clouds/layer[%i]";
578         char s[128];
579         int i;
580
581         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
582             snprintf(s, 128, cl, i);
583             strncat(s, "/coverage", 128);
584             fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
585
586             snprintf(s, 128, cl, i);
587             strncat(s, "/elevation-ft", 128);
588             fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
589
590             snprintf(s, 128, cl, i);
591             strncat(s, "/thickness-ft", 128);
592             fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
593
594             snprintf(s, 128, cl, i);
595             strncat(s, "/span-m", 128);
596             fgSetDouble(s, 40000.0);
597         }
598     }
599
600     fgSetupWind(dir_from, dir_to, speed, gust);
601     fgDefaultWeatherValue("visibility-m", vis);
602     set_temp_at_altitude(temp, station_elevation_ft);
603     set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
604     fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
605
606     // We've now successfully loaded a METAR into the configuration
607     metar_loaded = true;
608 }
609
610 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
611                                                 const char * requiredname,
612                                                 double dt)
613 {
614     double currentval = fgGetDouble(currentname);
615     double requiredval = fgGetDouble(requiredname);
616     return interpolate_val(currentval, requiredval, dt);
617 }
618
619 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
620                                                double requiredval,
621                                                double dt)
622 {
623     double dval = EnvironmentUpdatePeriodSec * dt;
624
625     if (fabs(currentval - requiredval) < dval) return requiredval;
626     if (currentval < requiredval) return (currentval + dval);
627     if (currentval > requiredval) return (currentval - dval);
628     return requiredval;
629 }
630
631 void
632 FGMetarEnvironmentCtrl::init ()
633 {
634     const SGPropertyNode *longitude
635         = fgGetNode( "/position/longitude-deg", true );
636     const SGPropertyNode *latitude
637         = fgGetNode( "/position/latitude-deg", true );
638
639     metar_loaded = false;
640     bool found_metar = false;
641     long max_age = metar_max_age->getLongValue();
642     // Don't check max age during init so that we don't loop over a lot
643     // of airports metar if there is a problem.
644     // The update() calls will find a correct metar if things went wrong here
645     metar_max_age->setLongValue(0);
646
647     while ( !found_metar && (_error_count < 3) ) {
648         const FGAirport* a = globals->get_airports()
649                    ->search( longitude->getDoubleValue(),
650                              latitude->getDoubleValue(),
651                              true );
652         if ( a ) {  
653             FGMetarResult result = fetch_data( a->getId() );
654             if ( result.m != NULL ) {
655                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
656                         << a->getId());
657                 last_apt = a;
658                 _icao = a->getId();
659                 search_elapsed = 0.0;
660                 fetch_elapsed = 0.0;
661                 update_metar_properties( result.m );
662                 update_env_config();
663                 env->init();
664                 found_metar = true;
665             } else {
666                 // mark as no metar so it doesn't show up in subsequent
667                 // searches.
668                 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
669                         << a->getId() );
670                 globals->get_airports()->no_metar( a->getId() );
671             }
672         }
673     }
674     metar_max_age->setLongValue(max_age);
675 }
676
677 void
678 FGMetarEnvironmentCtrl::reinit ()
679 {
680     _error_count = 0;
681     _error_dt = 0.0;
682
683 #if 0
684     update_env_config();
685 #endif
686
687     env->reinit();
688 }
689
690 void
691 FGMetarEnvironmentCtrl::update(double delta_time_sec)
692 {
693
694     _dt += delta_time_sec;
695     if (_error_count >= 3)
696        return;
697
698     FGMetarResult result;
699
700     static const SGPropertyNode *longitude
701         = fgGetNode( "/position/longitude-deg", true );
702     static const SGPropertyNode *latitude
703         = fgGetNode( "/position/latitude-deg", true );
704     search_elapsed += delta_time_sec;
705     fetch_elapsed += delta_time_sec;
706     interpolate_elapsed += delta_time_sec;
707
708     // if time for a new search request, push it onto the request
709     // queue
710     if ( search_elapsed > search_interval_sec ) {
711         const FGAirport* a = globals->get_airports()
712                    ->search( longitude->getDoubleValue(),
713                              latitude->getDoubleValue(),
714                              true );
715         if ( a ) {
716             if ( !last_apt || last_apt->getId() != a->getId()
717                  || fetch_elapsed > same_station_interval_sec )
718             {
719                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
720                         << a->getId());
721                 request_queue.push( a->getId() );
722                 last_apt = a;
723                 _icao = a->getId();
724                 search_elapsed = 0.0;
725                 fetch_elapsed = 0.0;
726             } else {
727                 search_elapsed = 0.0;
728                 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
729                         << same_station_interval_sec - fetch_elapsed );
730             }
731         } else {
732             SG_LOG( SG_GENERAL, SG_WARN,
733                     "Unable to find any airports with metar" );
734         }
735     } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
736         // Interpolate the current configuration closer to the actual METAR
737         update_env_config();
738         env->reinit();
739         interpolate_elapsed = 0.0;
740     }
741
742 #if !defined(ENABLE_THREADS)
743     // No loader thread running so manually fetch the data
744     string id = "";
745     while ( !request_queue.empty() ) {
746         id = request_queue.front();
747         request_queue.pop();
748     }
749
750     if ( !id.empty() ) {
751         SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
752         result = fetch_data( id );
753         result_queue.push( result );
754     }
755 #endif // ENABLE_THREADS
756
757     // process any results from the loader.
758     while ( !result_queue.empty() ) {
759         result = result_queue.front();
760         result_queue.pop();
761         if ( result.m != NULL ) {
762             update_metar_properties( result.m );
763             delete result.m;
764             update_env_config();
765             env->reinit();
766         } else {
767             // mark as no metar so it doesn't show up in subsequent
768             // searches, and signal an immediate re-search.
769             SG_LOG( SG_GENERAL, SG_WARN,
770                     "no metar at station = " << result.icao );
771             globals->get_airports()->no_metar( result.icao );
772             search_elapsed = 9999.0;
773         }
774     }
775
776     env->update(delta_time_sec);
777 }
778
779
780 void
781 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
782 {
783     env->setEnvironment(environment);
784 }
785
786 FGMetarResult
787 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
788 {
789     FGMetarResult result;
790     result.icao = icao;
791
792     // if the last error was more than three seconds ago,
793     // then pretent nothing happened.
794     if (_error_dt < 3) {
795         _error_dt += _dt;
796
797     } else {
798         _error_dt = 0.0;
799         _error_count = 0;
800     }
801
802     // fetch station elevation if exists
803     const FGAirport* a = globals->get_airports()->search( icao );
804     if ( a ) {
805         station_elevation_ft = a->getElevation();
806     }
807
808     // fetch current metar data
809     try {
810         string host = proxy_host->getStringValue();
811         string auth = proxy_auth->getStringValue();
812         string port = proxy_port->getStringValue();
813         result.m = new FGMetar( icao, host, port, auth);
814
815         long max_age = metar_max_age->getLongValue();
816         long age = result.m->getAge_min();
817         if (max_age &&  age > max_age) {
818             SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
819             delete result.m;
820             result.m = NULL;
821
822             if (++_stale_count > 10) {
823                 _error_count = 1000;
824                 throw sg_io_exception("More than 10 stale METAR messages in a row."
825                         " Check your system time!");
826             }
827         } else
828             _stale_count = 0;
829
830     } catch (const sg_io_exception& e) {
831         SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
832                 << e.getFormattedMessage().c_str() );
833 #if defined(ENABLE_THREADS)
834         if (_error_count++ >= 3) {
835            SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
836            thread_stop();
837         }
838 #endif
839
840         result.m = NULL;
841     }
842
843     _dt = 0;
844
845     return result;
846 }
847
848
849 void
850 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
851 {
852     int i;
853     double d;
854     char s[128];
855
856     fgSetString("/environment/metar/real-metar", m->getData());
857         // don't update with real weather when we use a custom weather scenario
858         const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
859         if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
860                 return;
861     fgSetString("/environment/metar/last-metar", m->getData());
862     fgSetString("/environment/metar/station-id", m->getId());
863     fgSetDouble("/environment/metar/min-visibility-m",
864                 m->getMinVisibility().getVisibility_m() );
865     fgSetDouble("/environment/metar/max-visibility-m",
866                 m->getMaxVisibility().getVisibility_m() );
867
868     const SGMetarVisibility *dirvis = m->getDirVisibility();
869     for (i = 0; i < 8; i++, dirvis++) {
870         const char *min = "/environment/metar/visibility[%d]/min-m";
871         const char *max = "/environment/metar/visibility[%d]/max-m";
872
873         d = dirvis->getVisibility_m();
874
875         snprintf(s, 128, min, i);
876         fgSetDouble(s, d);
877         snprintf(s, 128, max, i);
878         fgSetDouble(s, d);
879     }
880
881     fgSetInt("/environment/metar/base-wind-range-from",
882              m->getWindRangeFrom() );
883     fgSetInt("/environment/metar/base-wind-range-to",
884              m->getWindRangeTo() );
885     fgSetDouble("/environment/metar/base-wind-speed-kt",
886                 m->getWindSpeed_kt() );
887     fgSetDouble("/environment/metar/gust-wind-speed-kt",
888                 m->getGustSpeed_kt() );
889     fgSetDouble("/environment/metar/temperature-degc",
890                 m->getTemperature_C() );
891     fgSetDouble("/environment/metar/dewpoint-degc",
892                 m->getDewpoint_C() );
893     fgSetDouble("/environment/metar/rel-humidity-norm",
894                 m->getRelHumidity() );
895     fgSetDouble("/environment/metar/pressure-inhg",
896                 m->getPressure_inHg() );
897
898     vector<SGMetarCloud> cv = m->getClouds();
899     vector<SGMetarCloud>::const_iterator cloud;
900
901     const char *cl = "/environment/metar/clouds/layer[%i]";
902     for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
903         const char *coverage_string[5] = 
904             { "clear", "few", "scattered", "broken", "overcast" };
905         const double thickness[5] = { 0, 65, 600,750, 1000};
906         int q;
907
908         snprintf(s, 128, cl, i);
909         strncat(s, "/coverage", 128);
910         q = cloud->getCoverage();
911         fgSetString(s, coverage_string[q] );
912
913         snprintf(s, 128, cl, i);
914         strncat(s, "/elevation-ft", 128);
915         fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
916
917         snprintf(s, 128, cl, i);
918         strncat(s, "/thickness-ft", 128);
919         fgSetDouble(s, thickness[q]);
920
921         snprintf(s, 128, cl, i);
922         strncat(s, "/span-m", 128);
923         fgSetDouble(s, 40000.0);
924     }
925
926     for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
927         snprintf(s, 128, cl, i);
928         strncat(s, "/coverage", 128);
929         fgSetString(s, "clear");
930
931         snprintf(s, 128, cl, i);
932         strncat(s, "/elevation-ft", 128);
933         fgSetDouble(s, -9999);
934
935         snprintf(s, 128, cl, i);
936         strncat(s, "/thickness-ft", 128);
937         fgSetDouble(s, 0);
938
939         snprintf(s, 128, cl, i);
940         strncat(s, "/span-m", 128);
941         fgSetDouble(s, 40000.0);
942     }
943
944     fgSetDouble("/environment/metar/rain-norm", m->getRain());
945     fgSetDouble("/environment/metar/hail-norm", m->getHail());
946     fgSetDouble("/environment/metar/snow-norm", m->getSnow());
947     fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
948 }
949
950
951 #if defined(ENABLE_THREADS)
952 void
953 FGMetarEnvironmentCtrl::thread_stop()
954 {
955     request_queue.push( string() );     // ask thread to terminate
956     thread->join();
957 }
958
959 void
960 FGMetarEnvironmentCtrl::MetarThread::run()
961 {
962     while ( true )
963     {
964         string icao = fetcher->request_queue.pop();
965         if (icao.empty())
966             return;
967         SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
968         FGMetarResult result = fetcher->fetch_data( icao );
969         fetcher->result_queue.push( result );
970     }
971 }
972 #endif // ENABLE_THREADS
973
974
975 // end of environment_ctrl.cxx