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