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