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