]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
1d70724afdc9fb3c804b798f8011ffd979341ae8
[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 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->setProcessorAffinity(1);
360     thread->start();
361 #endif // ENABLE_THREADS
362 }
363
364 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
365 {
366 #if defined(ENABLE_THREADS)
367    thread_stop();
368 #endif // ENABLE_THREADS
369
370    delete env;
371    env = NULL;
372 }
373
374
375 // use a "command" to set station temp at station elevation
376 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
377     SGPropertyNode args;
378     SGPropertyNode *node = args.getNode("temp-degc", 0, true);
379     node->setFloatValue( temp_degc );
380     node = args.getNode("altitude-ft", 0, true);
381     node->setFloatValue( altitude_ft );
382     globals->get_commands()->execute("set-outside-air-temp-degc", &args);
383 }
384
385
386 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
387     SGPropertyNode args;
388     SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
389     node->setFloatValue( dewpoint_degc );
390     node = args.getNode("altitude-ft", 0, true);
391     node->setFloatValue( altitude_ft );
392     globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
393 }
394
395
396 void
397 FGMetarEnvironmentCtrl::update_env_config ()
398 {
399     double dir_from;
400     double dir_to;
401     double speed;
402     double gust;
403     double vis;
404     double pressure;
405     double temp;
406     double dewpoint;
407
408     if (metar_loaded) {
409         // Generate interpolated values between the METAR and the current
410         // configuration.
411
412         // Pick up the METAR wind values and convert them into a vector.
413         double metar[2];
414         double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
415         double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
416
417         metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
418         metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
419
420         // Convert the current wind values and convert them into a vector
421         double current[2];
422         double current_speed =
423                 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
424         double current_heading = fgGetDouble(
425                 "/environment/config/boundary/entry/wind-from-heading-deg");
426
427         current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
428         current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
429
430         // Determine the maximum component-wise value that the wind can change.
431         // First we determine the fraction in the X and Y component, then
432         // factor by the maximum wind change.
433         double x = fabs(current[0] - metar[0]);
434         double y = fabs(current[1] - metar[1]);
435         double dx = x / (x + y);
436         double dy = 1 - dx;
437
438         double maxdx = dx * MaxWindChangeKtsSec;
439         double maxdy = dy * MaxWindChangeKtsSec;
440
441         // Interpolate each component separately.
442         current[0] = interpolate_val(current[0], metar[0], maxdx);
443         current[1] = interpolate_val(current[1], metar[1], maxdy);
444
445         // Now convert back to polar coordinates.
446         if ((current[0] == 0.0) && (current[1] == 0.0)) {
447             // Special case where there is no wind (otherwise atan2 barfs)
448             speed = 0.0;
449             dir_from = current_heading;
450
451         } else {
452             // Some real wind to convert back from. Work out the speed
453             // and direction value in degrees.
454             speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
455             dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
456
457             // Normalize the direction.
458             if (dir_from < 0.0)
459                 dir_from += 360.0;
460
461             SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
462         }
463
464         // Now handle the visibility. We convert both visibility values
465         // to X-values, then interpolate from there, then back to real values.
466         // The length_scale is fixed to 1000m, so the visibility changes by
467         // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
468         // whichever is more.
469         double currentvis =
470                 fgGetDouble("/environment/config/boundary/entry/visibility-m");
471         double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
472         double currentxval = log(1000.0 + currentvis);
473         double metarxval = log(1000.0 + metarvis);
474
475         currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
476
477         // Now convert back from an X-value to a straightforward visibility.
478         vis = exp(currentxval) - 1000.0;
479
480         pressure = interpolate_prop(
481                 "/environment/config/boundary/entry/pressure-sea-level-inhg",
482                 "/environment/metar/pressure-inhg",
483                 MaxPressureChangeInHgSec);
484
485         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
486         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
487         temp     = fgGetDouble("/environment/metar/temperature-degc");
488         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
489
490         // Set the cloud layers by interpolating over the METAR versions.
491         SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
492
493         vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
494         vector<SGPropertyNode_ptr>::const_iterator layer;
495         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
496
497         const char *cl = "/environment/clouds/layer[%i]";
498         double aircraft_alt = fgGetDouble("/position/altitude-ft");
499         char s[128];
500         int i;
501
502         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
503             double currentval;
504             double requiredval;
505
506             // In the case of clouds, we want to avoid writing if nothing has
507             // changed, as these properties are tied to the renderer and will
508             // cause the clouds to be updated, reseting the texture locations.
509
510             // We don't interpolate the coverage values as no-matter how we
511             // do it, it will be quite a sudden change of texture. Better to
512             // have a single change than four or five.
513             snprintf(s, 128, cl, i);
514             strncat(s, "/coverage", 128);
515             const char* coverage = (*layer)->getStringValue("coverage", "clear");
516             if (strncmp(fgGetString(s), coverage, 128) != 0)
517                 fgSetString(s, coverage);
518
519             snprintf(s, 128, cl, i);
520             strncat(s, "/elevation-ft", 128);
521             double current_alt = fgGetDouble(s);
522             double required_alt = (*layer)->getDoubleValue("elevation-ft");
523
524             if (current_alt < -9000 || required_alt < -9000
525                     || fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt) {
526                 // We don't interpolate any values that are too high above us,
527                 // or too far below us to be visible. Nor do we interpolate
528                 // values to or from -9999, which is used as a placeholder
529                 // when there isn't actually a cloud layer present.
530                 snprintf(s, 128, cl, i);
531                 strncat(s, "/elevation-ft", 128);
532                 if (current_alt != required_alt)
533                     fgSetDouble(s, required_alt);
534
535                 snprintf(s, 128, cl, i);
536                 strncat(s, "/thickness-ft", 128);
537                 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
538                     fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
539
540             } else {
541                 // Interpolate the other values in the usual way
542                 if (current_alt != required_alt) {
543                     current_alt = interpolate_val(current_alt,
544                                                   required_alt,
545                                                   MaxCloudAltitudeChangeFtSec);
546                     fgSetDouble(s, current_alt);
547                 }
548
549                 snprintf(s, 128, cl, i);
550                 strncat(s, "/thickness-ft", 128);
551                 currentval = fgGetDouble(s);
552                 requiredval = (*layer)->getDoubleValue("thickness-ft");
553
554                 if (currentval != requiredval) {
555                     currentval = interpolate_val(currentval,
556                                                  requiredval,
557                                                  MaxCloudThicknessChangeFtSec);
558                     fgSetDouble(s, currentval);
559                 }
560             }
561         }
562
563     } else {
564         // We haven't already loaded a METAR, so apply it immediately.
565         dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
566         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
567         speed    = fgGetDouble("/environment/metar/base-wind-speed-kt");
568         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
569         vis      = fgGetDouble("/environment/metar/min-visibility-m");
570         pressure = fgGetDouble("/environment/metar/pressure-inhg");
571         temp     = fgGetDouble("/environment/metar/temperature-degc");
572         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
573
574         // Set the cloud layers by copying over the METAR versions.
575         SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
576
577         vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
578         vector<SGPropertyNode_ptr>::const_iterator layer;
579         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
580
581         const char *cl = "/environment/clouds/layer[%i]";
582         char s[128];
583         int i;
584
585         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
586             snprintf(s, 128, cl, i);
587             strncat(s, "/coverage", 128);
588             fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
589
590             snprintf(s, 128, cl, i);
591             strncat(s, "/elevation-ft", 128);
592             fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
593
594             snprintf(s, 128, cl, i);
595             strncat(s, "/thickness-ft", 128);
596             fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
597
598             snprintf(s, 128, cl, i);
599             strncat(s, "/span-m", 128);
600             fgSetDouble(s, 40000.0);
601         }
602     }
603
604     fgSetupWind(dir_from, dir_to, speed, gust);
605     fgDefaultWeatherValue("visibility-m", vis);
606     set_temp_at_altitude(temp, station_elevation_ft);
607     set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
608     fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
609
610     // We've now successfully loaded a METAR into the configuration
611     metar_loaded = true;
612 }
613
614 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
615                                                 const char * requiredname,
616                                                 double dt)
617 {
618     double currentval = fgGetDouble(currentname);
619     double requiredval = fgGetDouble(requiredname);
620     return interpolate_val(currentval, requiredval, dt);
621 }
622
623 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
624                                                double requiredval,
625                                                double dt)
626 {
627     double dval = EnvironmentUpdatePeriodSec * dt;
628
629     if (fabs(currentval - requiredval) < dval) return requiredval;
630     if (currentval < requiredval) return (currentval + dval);
631     if (currentval > requiredval) return (currentval - dval);
632     return requiredval;
633 }
634
635 void
636 FGMetarEnvironmentCtrl::init ()
637 {
638     const SGPropertyNode *longitude
639         = fgGetNode( "/position/longitude-deg", true );
640     const SGPropertyNode *latitude
641         = fgGetNode( "/position/latitude-deg", true );
642
643     metar_loaded = false;
644     bool found_metar = false;
645     long max_age = metar_max_age->getLongValue();
646     // Don't check max age during init so that we don't loop over a lot
647     // of airports metar if there is a problem.
648     // The update() calls will find a correct metar if things went wrong here
649     metar_max_age->setLongValue(0);
650
651     while ( !found_metar && (_error_count < 3) ) {
652         const FGAirport* a = globals->get_airports()
653                    ->search( longitude->getDoubleValue(),
654                              latitude->getDoubleValue(),
655                              360.0,
656                              metar_only );
657         if ( a ) {  
658             FGMetarResult result = fetch_data( a->getId() );
659             if ( result.m != NULL ) {
660                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
661                         << a->getId());
662                 last_apt = a;
663                 _icao = a->getId();
664                 search_elapsed = 0.0;
665                 fetch_elapsed = 0.0;
666                 update_metar_properties( result.m );
667                 update_env_config();
668                 env->init();
669                 found_metar = true;
670             } else {
671                 // mark as no metar so it doesn't show up in subsequent
672                 // searches.
673                 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
674                         << a->getId() );
675                 globals->get_airports()->no_metar( a->getId() );
676             }
677         }
678     }
679     metar_max_age->setLongValue(max_age);
680 }
681
682 void
683 FGMetarEnvironmentCtrl::reinit ()
684 {
685     _error_count = 0;
686     _error_dt = 0.0;
687
688 #if 0
689     update_env_config();
690 #endif
691
692     env->reinit();
693 }
694
695 void
696 FGMetarEnvironmentCtrl::update(double delta_time_sec)
697 {
698
699     _dt += delta_time_sec;
700     if (_error_count >= 3)
701        return;
702
703     FGMetarResult result;
704
705     static const SGPropertyNode *longitude
706         = fgGetNode( "/position/longitude-deg", true );
707     static const SGPropertyNode *latitude
708         = fgGetNode( "/position/latitude-deg", true );
709     search_elapsed += delta_time_sec;
710     fetch_elapsed += delta_time_sec;
711     interpolate_elapsed += delta_time_sec;
712
713     // if time for a new search request, push it onto the request
714     // queue
715     if ( search_elapsed > search_interval_sec ) {
716         const FGAirport* a = globals->get_airports()
717                    ->search( longitude->getDoubleValue(),
718                              latitude->getDoubleValue(),
719                              360.0,
720                              metar_only );
721         if ( a ) {
722             if ( !last_apt || last_apt->getId() != a->getId()
723                  || fetch_elapsed > same_station_interval_sec )
724             {
725                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
726                         << a->getId());
727                 request_queue.push( a->getId() );
728                 last_apt = a;
729                 _icao = a->getId();
730                 search_elapsed = 0.0;
731                 fetch_elapsed = 0.0;
732             } else {
733                 search_elapsed = 0.0;
734                 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
735                         << same_station_interval_sec - fetch_elapsed );
736             }
737         } else {
738             SG_LOG( SG_GENERAL, SG_WARN,
739                     "Unable to find any airports with metar" );
740         }
741     } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
742         // Interpolate the current configuration closer to the actual METAR
743         update_env_config();
744         env->reinit();
745         interpolate_elapsed = 0.0;
746     }
747
748 #if !defined(ENABLE_THREADS)
749     // No loader thread running so manually fetch the data
750     string id = "";
751     while ( !request_queue.empty() ) {
752         id = request_queue.front();
753         request_queue.pop();
754     }
755
756     if ( !id.empty() ) {
757         SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
758         result = fetch_data( id );
759         result_queue.push( result );
760     }
761 #endif // ENABLE_THREADS
762
763     // process any results from the loader.
764     while ( !result_queue.empty() ) {
765         result = result_queue.front();
766         result_queue.pop();
767         if ( result.m != NULL ) {
768             update_metar_properties( result.m );
769             delete result.m;
770             update_env_config();
771             env->reinit();
772         } else {
773             // mark as no metar so it doesn't show up in subsequent
774             // searches, and signal an immediate re-search.
775             SG_LOG( SG_GENERAL, SG_WARN,
776                     "no metar at station = " << result.icao );
777             globals->get_airports()->no_metar( result.icao );
778             search_elapsed = 9999.0;
779         }
780     }
781
782     env->update(delta_time_sec);
783 }
784
785
786 void
787 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
788 {
789     env->setEnvironment(environment);
790 }
791
792 FGMetarResult
793 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
794 {
795     FGMetarResult result;
796     result.icao = icao;
797
798     // if the last error was more than three seconds ago,
799     // then pretent nothing happened.
800     if (_error_dt < 3) {
801         _error_dt += _dt;
802
803     } else {
804         _error_dt = 0.0;
805         _error_count = 0;
806     }
807
808     // fetch station elevation if exists
809     const FGAirport* a = globals->get_airports()->search( icao );
810     if ( a ) {
811         station_elevation_ft = a->getElevation();
812     }
813
814     // fetch current metar data
815     try {
816         string host = proxy_host->getStringValue();
817         string auth = proxy_auth->getStringValue();
818         string port = proxy_port->getStringValue();
819         result.m = new FGMetar( icao, host, port, auth);
820
821         long max_age = metar_max_age->getLongValue();
822         long age = result.m->getAge_min();
823         if (max_age &&  age > max_age) {
824             SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
825             delete result.m;
826             result.m = NULL;
827
828             if (++_stale_count > 10) {
829                 _error_count = 1000;
830                 throw sg_io_exception("More than 10 stale METAR messages in a row."
831                         " Check your system time!");
832             }
833         } else
834             _stale_count = 0;
835
836     } catch (const sg_io_exception& e) {
837         SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
838                 << e.getFormattedMessage().c_str() );
839 #if defined(ENABLE_THREADS)
840         if (_error_count++ >= 3) {
841            SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
842            thread_stop();
843         }
844 #endif
845
846         result.m = NULL;
847     }
848
849     _dt = 0;
850
851     return result;
852 }
853
854
855 void
856 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
857 {
858     int i;
859     double d;
860     char s[128];
861
862     fgSetString("/environment/metar/real-metar", m->getData());
863         // don't update with real weather when we use a custom weather scenario
864         const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
865         if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
866                 return;
867     fgSetString("/environment/metar/last-metar", m->getData());
868     fgSetString("/environment/metar/station-id", m->getId());
869     fgSetDouble("/environment/metar/min-visibility-m",
870                 m->getMinVisibility().getVisibility_m() );
871     fgSetDouble("/environment/metar/max-visibility-m",
872                 m->getMaxVisibility().getVisibility_m() );
873
874     const SGMetarVisibility *dirvis = m->getDirVisibility();
875     for (i = 0; i < 8; i++, dirvis++) {
876         const char *min = "/environment/metar/visibility[%d]/min-m";
877         const char *max = "/environment/metar/visibility[%d]/max-m";
878
879         d = dirvis->getVisibility_m();
880
881         snprintf(s, 128, min, i);
882         fgSetDouble(s, d);
883         snprintf(s, 128, max, i);
884         fgSetDouble(s, d);
885     }
886
887     fgSetInt("/environment/metar/base-wind-range-from",
888              m->getWindRangeFrom() );
889     fgSetInt("/environment/metar/base-wind-range-to",
890              m->getWindRangeTo() );
891     fgSetDouble("/environment/metar/base-wind-speed-kt",
892                 m->getWindSpeed_kt() );
893     fgSetDouble("/environment/metar/gust-wind-speed-kt",
894                 m->getGustSpeed_kt() );
895     fgSetDouble("/environment/metar/temperature-degc",
896                 m->getTemperature_C() );
897     fgSetDouble("/environment/metar/dewpoint-degc",
898                 m->getDewpoint_C() );
899     fgSetDouble("/environment/metar/rel-humidity-norm",
900                 m->getRelHumidity() );
901     fgSetDouble("/environment/metar/pressure-inhg",
902                 m->getPressure_inHg() );
903
904     vector<SGMetarCloud> cv = m->getClouds();
905     vector<SGMetarCloud>::const_iterator cloud;
906
907     const char *cl = "/environment/metar/clouds/layer[%i]";
908     for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
909         const char *coverage_string[5] = 
910             { "clear", "few", "scattered", "broken", "overcast" };
911         const double thickness[5] = { 0, 65, 600,750, 1000};
912         int q;
913
914         snprintf(s, 128, cl, i);
915         strncat(s, "/coverage", 128);
916         q = cloud->getCoverage();
917         fgSetString(s, coverage_string[q] );
918
919         snprintf(s, 128, cl, i);
920         strncat(s, "/elevation-ft", 128);
921         fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
922
923         snprintf(s, 128, cl, i);
924         strncat(s, "/thickness-ft", 128);
925         fgSetDouble(s, thickness[q]);
926
927         snprintf(s, 128, cl, i);
928         strncat(s, "/span-m", 128);
929         fgSetDouble(s, 40000.0);
930     }
931
932     for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
933         snprintf(s, 128, cl, i);
934         strncat(s, "/coverage", 128);
935         fgSetString(s, "clear");
936
937         snprintf(s, 128, cl, i);
938         strncat(s, "/elevation-ft", 128);
939         fgSetDouble(s, -9999);
940
941         snprintf(s, 128, cl, i);
942         strncat(s, "/thickness-ft", 128);
943         fgSetDouble(s, 0);
944
945         snprintf(s, 128, cl, i);
946         strncat(s, "/span-m", 128);
947         fgSetDouble(s, 40000.0);
948     }
949
950     fgSetDouble("/environment/metar/rain-norm", m->getRain());
951     fgSetDouble("/environment/metar/hail-norm", m->getHail());
952     fgSetDouble("/environment/metar/snow-norm", m->getSnow());
953     fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
954 }
955
956
957 #if defined(ENABLE_THREADS)
958 void
959 FGMetarEnvironmentCtrl::thread_stop()
960 {
961     request_queue.push( string() );     // ask thread to terminate
962     thread->join();
963 }
964
965 void
966 FGMetarEnvironmentCtrl::MetarThread::run()
967 {
968     while ( true )
969     {
970         string icao = fetcher->request_queue.pop();
971         if (icao.empty())
972             return;
973         SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
974         FGMetarResult result = fetcher->fetch_data( icao );
975         fetcher->result_queue.push( result );
976     }
977 }
978 #endif // ENABLE_THREADS
979
980
981 // end of environment_ctrl.cxx