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