]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
db093b736b0a03e5a0dd6ef028fb6a5059fa48f6
[flightgear.git] / src / Environment / environment_ctrl.cxx
1 // environment_ctrl.cxx -- manager for natural environment information.
2 //
3 // Written by David Megginson, started February 2002.
4 //
5 // Copyright (C) 2002  David Megginson - david@megginson.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <stdlib.h>
28 #include <math.h>
29 #include <algorithm>
30
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/structure/commands.hxx>
33 #include <simgear/structure/exception.hxx>
34
35 #include <Airports/simple.hxx>
36 #include <Main/fg_props.hxx>
37 #include <Main/util.hxx>
38
39 #include "environment_mgr.hxx"
40 #include "environment_ctrl.hxx"
41
42 using std::sort;
43
44 class metar_filter : public FGAirportSearchFilter {
45     virtual bool pass(FGAirport *a) { return a->getMetar(); }
46 } metar_only;
47
48 \f
49 ////////////////////////////////////////////////////////////////////////
50 // Implementation of FGEnvironmentCtrl abstract base class.
51 ////////////////////////////////////////////////////////////////////////
52
53 FGEnvironmentCtrl::FGEnvironmentCtrl ()
54   : _environment(0),
55     _lon_deg(0),
56     _lat_deg(0),
57     _elev_ft(0)
58 {
59 }
60
61 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
62 {
63 }
64
65 void
66 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
67 {
68   _environment = environment;
69 }
70
71 void
72 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
73 {
74   _lon_deg = lon_deg;
75 }
76
77 void
78 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
79 {
80   _lat_deg = lat_deg;
81 }
82
83 void
84 FGEnvironmentCtrl::setElevationFt (double elev_ft)
85 {
86   _elev_ft = elev_ft;
87 }
88
89 void
90 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
91 {
92   _lon_deg = lon_deg;
93   _lat_deg = lat_deg;
94   _elev_ft = elev_ft;
95 }
96
97
98 \f
99 ////////////////////////////////////////////////////////////////////////
100 // Implementation of FGUserDefEnvironmentCtrl.
101 ////////////////////////////////////////////////////////////////////////
102
103 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
104   : _base_wind_speed_node(0),
105     _gust_wind_speed_node(0),
106     _current_wind_speed_kt(0),
107     _delta_wind_speed_kt(0)
108 {
109 }
110
111 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
112 {
113 }
114
115 void
116 FGUserDefEnvironmentCtrl::init ()
117 {
118                                 // Fill in some defaults.
119   if (!fgHasNode("/environment/params/base-wind-speed-kt"))
120     fgSetDouble("/environment/params/base-wind-speed-kt",
121                 fgGetDouble("/environment/wind-speed-kt"));
122   if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
123     fgSetDouble("/environment/params/gust-wind-speed-kt",
124                 fgGetDouble("/environment/params/base-wind-speed-kt"));
125
126   _base_wind_speed_node =
127     fgGetNode("/environment/params/base-wind-speed-kt", true);
128   _gust_wind_speed_node =
129     fgGetNode("/environment/params/gust-wind-speed-kt", true);
130
131   _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
132   _delta_wind_speed_kt = 0.1;
133 }
134
135 void
136 FGUserDefEnvironmentCtrl::update (double dt)
137 {
138   double base_wind_speed = _base_wind_speed_node->getDoubleValue();
139   double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
140
141   if (gust_wind_speed < base_wind_speed) {
142       gust_wind_speed = base_wind_speed;
143       _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
144   }
145
146   if (base_wind_speed == gust_wind_speed) {
147     _current_wind_speed_kt = base_wind_speed;
148   } else {
149     int rn = rand() % 128;
150     int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
151     double gust = _current_wind_speed_kt - base_wind_speed;
152     double incr = gust / 50;
153
154     if (rn == 0)
155       _delta_wind_speed_kt = - _delta_wind_speed_kt;
156     else if (rn < 4)
157       _delta_wind_speed_kt -= incr * sign;
158     else if (rn < 16)
159       _delta_wind_speed_kt += incr * sign;
160
161     _current_wind_speed_kt += _delta_wind_speed_kt;
162
163     if (_current_wind_speed_kt < base_wind_speed) {
164       _current_wind_speed_kt = base_wind_speed;
165       _delta_wind_speed_kt = 0.01;
166     } else if (_current_wind_speed_kt > gust_wind_speed) {
167       _current_wind_speed_kt = gust_wind_speed;
168       _delta_wind_speed_kt = -0.01;
169     }
170   }
171   
172   if (_environment != 0)
173     _environment->set_wind_speed_kt(_current_wind_speed_kt);
174 }
175
176
177 \f
178 ////////////////////////////////////////////////////////////////////////
179 // Implementation of FGInterpolateEnvironmentCtrl.
180 ////////////////////////////////////////////////////////////////////////
181
182 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
183 {
184 }
185
186 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
187 {
188     unsigned int i;
189     for (i = 0; i < _boundary_table.size(); i++)
190         delete _boundary_table[i];
191     for (i = 0; i < _aloft_table.size(); i++)
192         delete _aloft_table[i];
193 }
194
195
196
197 void
198 FGInterpolateEnvironmentCtrl::init ()
199 {
200     read_table(fgGetNode("/environment/config/boundary", true),
201                _boundary_table);
202     read_table(fgGetNode("/environment/config/aloft", true),
203                _aloft_table);
204 }
205
206 void
207 FGInterpolateEnvironmentCtrl::reinit ()
208 {
209     unsigned int i;
210     for (i = 0; i < _boundary_table.size(); i++)
211         delete _boundary_table[i];
212     for (i = 0; i < _aloft_table.size(); i++)
213         delete _aloft_table[i];
214     _boundary_table.clear();
215     _aloft_table.clear();
216     init();
217 }
218
219 void
220 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
221                                           vector<bucket *> &table)
222 {
223     for (int i = 0; i < node->nChildren(); i++) {
224         const SGPropertyNode * child = node->getChild(i);
225         if ( strcmp(child->getName(), "entry") == 0
226              && child->getStringValue("elevation-ft", "")[0] != '\0'
227              && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
228         {
229             bucket * b = new bucket;
230             if (i > 0)
231                 b->environment.copy(table[i-1]->environment);
232             b->environment.read(child);
233             b->altitude_ft = b->environment.get_elevation_ft();
234             table.push_back(b);
235         }
236     }
237     sort(table.begin(), table.end(), bucket::lessThan);
238 }
239
240 void
241 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
242 {
243                                 // FIXME
244     double altitude_ft = fgGetDouble("/position/altitude-ft");
245     double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
246     double boundary_transition =
247         fgGetDouble("/environment/config/boundary-transition-ft", 500);
248
249     // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
250
251     int length = _boundary_table.size();
252
253     if (length > 0) {
254                                 // boundary table
255         double boundary_limit = _boundary_table[length-1]->altitude_ft;
256         if (boundary_limit >= altitude_agl_ft) {
257             do_interpolate(_boundary_table, altitude_agl_ft,
258                            _environment);
259             return;
260         } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
261                                 // both tables
262             do_interpolate(_boundary_table, altitude_agl_ft, &env1);
263             do_interpolate(_aloft_table, altitude_ft, &env2);
264             double fraction =
265                 (altitude_agl_ft - boundary_limit) / boundary_transition;
266             interpolate(&env1, &env2, fraction, _environment);
267             return;
268         }
269     }
270
271                                 // aloft table
272     do_interpolate(_aloft_table, altitude_ft, _environment);
273 }
274
275 void
276 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
277                                               double altitude_ft,
278                                               FGEnvironment * environment)
279 {
280     int length = table.size();
281     if (length == 0)
282         return;
283
284                                 // Boundary conditions
285     if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
286         environment->copy(table[0]->environment);
287         return;
288     } else if (table[length-1]->altitude_ft <= altitude_ft) {
289         environment->copy(table[length-1]->environment);
290         return;
291     }
292         
293                                 // Search the interpolation table
294     for (int i = 0; i < length - 1; i++) {
295         if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
296                 FGEnvironment * env1 = &(table[i]->environment);
297                 FGEnvironment * env2 = &(table[i+1]->environment);
298                 double fraction;
299                 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
300                     fraction = 1.0;
301                 else
302                     fraction =
303                         ((altitude_ft - table[i]->altitude_ft) /
304                          (table[i+1]->altitude_ft - table[i]->altitude_ft));
305                 interpolate(env1, env2, fraction, environment);
306
307                 return;
308         }
309     }
310 }
311
312 bool
313 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
314 {
315     return (altitude_ft < b.altitude_ft);
316 }
317
318 bool
319 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
320 {
321     return (a->altitude_ft) < (b->altitude_ft);
322 }
323
324 \f
325 ////////////////////////////////////////////////////////////////////////
326 // Implementation of FGMetarEnvironmentCtrl.
327 ////////////////////////////////////////////////////////////////////////
328
329 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
330     : env( new FGInterpolateEnvironmentCtrl ),
331       _icao( "" ),
332       metar_loaded( false ),
333       search_interval_sec( 60.0 ),        // 1 minute
334       same_station_interval_sec( 900.0 ), // 15 minutes
335       search_elapsed( 9999.0 ),
336       fetch_elapsed( 9999.0 ),
337       last_apt( 0 ),
338       proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
339       proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
340       proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
341       metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
342
343       // Interpolation constant definitions.
344       EnvironmentUpdatePeriodSec( 0.2 ),
345       MaxWindChangeKtsSec( 0.2 ),
346       MaxVisChangePercentSec( 0.05 ),
347       MaxPressureChangeInHgSec( 0.0033 ),
348       MaxCloudAltitudeChangeFtSec( 20.0 ),
349       MaxCloudThicknessChangeFtSec( 50.0 ),
350       MaxCloudInterpolationHeightFt( 5000.0 ),
351       MaxCloudInterpolationDeltaFt( 4000.0 ),
352
353       _error_count( 0 ),
354       _stale_count( 0 ),
355       _dt( 0.0 ),
356       _error_dt( 0.0 )
357 {
358 #if defined(ENABLE_THREADS)
359     thread = new MetarThread(this);
360     thread->setProcessorAffinity(1);
361     thread->start();
362 #endif // ENABLE_THREADS
363 }
364
365 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
366 {
367 #if defined(ENABLE_THREADS)
368    thread_stop();
369 #endif // ENABLE_THREADS
370
371    delete env;
372    env = NULL;
373 }
374
375
376 // use a "command" to set station temp at station elevation
377 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
378     SGPropertyNode args;
379     SGPropertyNode *node = args.getNode("temp-degc", 0, true);
380     node->setFloatValue( temp_degc );
381     node = args.getNode("altitude-ft", 0, true);
382     node->setFloatValue( altitude_ft );
383     globals->get_commands()->execute("set-outside-air-temp-degc", &args);
384 }
385
386
387 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
388     SGPropertyNode args;
389     SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
390     node->setFloatValue( dewpoint_degc );
391     node = args.getNode("altitude-ft", 0, true);
392     node->setFloatValue( altitude_ft );
393     globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
394 }
395
396
397 void
398 FGMetarEnvironmentCtrl::update_env_config ()
399 {
400     double dir_from;
401     double dir_to;
402     double speed;
403     double gust;
404     double vis;
405     double pressure;
406     double temp;
407     double dewpoint;
408     
409     // If we aren't in the METAR scenario, don't attempt to interpolate.
410     if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR")) return;
411
412     if (metar_loaded) {
413         // Generate interpolated values between the METAR and the current
414         // configuration.
415
416         // Pick up the METAR wind values and convert them into a vector.
417         double metar[2];
418         double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
419         double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
420
421         metar[0] = metar_speed * sin((metar_heading / 180.0) * M_PI);
422         metar[1] = metar_speed * cos((metar_heading / 180.0) * M_PI);
423
424         // Convert the current wind values and convert them into a vector
425         double current[2];
426         double current_speed =
427                 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
428         double current_heading = fgGetDouble(
429                 "/environment/config/boundary/entry/wind-from-heading-deg");
430
431         current[0] = current_speed * sin((current_heading / 180.0) * M_PI);
432         current[1] = current_speed * cos((current_heading / 180.0) * M_PI);
433
434         // Determine the maximum component-wise value that the wind can change.
435         // First we determine the fraction in the X and Y component, then
436         // factor by the maximum wind change.
437         double x = fabs(current[0] - metar[0]);
438         double y = fabs(current[1] - metar[1]);
439         double dx = x / (x + y);
440         double dy = 1 - dx;
441
442         double maxdx = dx * MaxWindChangeKtsSec;
443         double maxdy = dy * MaxWindChangeKtsSec;
444
445         // Interpolate each component separately.
446         current[0] = interpolate_val(current[0], metar[0], maxdx);
447         current[1] = interpolate_val(current[1], metar[1], maxdy);
448
449         // Now convert back to polar coordinates.
450         if ((current[0] == 0.0) && (current[1] == 0.0)) {
451             // Special case where there is no wind (otherwise atan2 barfs)
452             speed = 0.0;
453             dir_from = current_heading;
454
455         } else {
456             // Some real wind to convert back from. Work out the speed
457             // and direction value in degrees.
458             speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
459             dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
460
461             // Normalize the direction.
462             if (dir_from < 0.0)
463                 dir_from += 360.0;
464
465             SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
466         }
467
468         // Now handle the visibility. We convert both visibility values
469         // to X-values, then interpolate from there, then back to real values.
470         // The length_scale is fixed to 1000m, so the visibility changes by
471         // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
472         // whichever is more.
473         double currentvis =
474                 fgGetDouble("/environment/config/boundary/entry/visibility-m");
475         double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
476         double currentxval = log(1000.0 + currentvis);
477         double metarxval = log(1000.0 + metarvis);
478
479         currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
480
481         // Now convert back from an X-value to a straightforward visibility.
482         vis = exp(currentxval) - 1000.0;
483
484         pressure = interpolate_prop(
485                 "/environment/config/boundary/entry/pressure-sea-level-inhg",
486                 "/environment/metar/pressure-inhg",
487                 MaxPressureChangeInHgSec);
488
489         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
490         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
491         temp     = fgGetDouble("/environment/metar/temperature-degc");
492         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
493
494         // Set the cloud layers by interpolating over the METAR versions.
495         SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds");
496
497         vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
498         vector<SGPropertyNode_ptr>::const_iterator layer;
499         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
500
501         const char *cl = "/environment/clouds/layer[%i]";
502         double aircraft_alt = fgGetDouble("/position/altitude-ft");
503         char s[128];
504         int i;
505         bool rebuild_clouds = false;
506
507         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
508             double currentval;
509             double requiredval;
510
511             // In the case of clouds, we want to avoid writing if nothing has
512             // changed, as these properties are tied to the renderer and will
513             // cause the clouds to be updated, reseting the texture locations.
514
515             // We don't interpolate the coverage values as no-matter how we
516             // do it, it will be quite a sudden change of texture. Better to
517             // have a single change than four or five.
518             snprintf(s, 128, cl, i);
519             strncat(s, "/coverage", 128);
520             const char* coverage = (*layer)->getStringValue("coverage", "clear");
521             if (strncmp(fgGetString(s), coverage, 128) != 0) {
522                 fgSetString(s, coverage);
523                 rebuild_clouds = true;
524             }
525
526             snprintf(s, 128, cl, i);
527             strncat(s, "/elevation-ft", 128);
528             double current_alt = fgGetDouble(s);
529             double required_alt = (*layer)->getDoubleValue("elevation-ft");
530
531             if (current_alt < -9000 || required_alt < -9000 ||
532                 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
533                 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
534                 // We don't interpolate any layers that are
535                 //  - too far above us to be visible
536                 //  - too far below us to be visible
537                 //  - with too large a difference to make interpolation sensible
538                 //  - to or from -9999 (used as a placeholder)
539                 //  - any values that are too high above us,
540                 snprintf(s, 128, cl, i);
541                 strncat(s, "/elevation-ft", 128);
542                 if (current_alt != required_alt)
543                     fgSetDouble(s, required_alt);
544
545                 snprintf(s, 128, cl, i);
546                 strncat(s, "/thickness-ft", 128);
547                 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
548                     fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
549
550             } else {
551                 // Interpolate the other values in the usual way
552                 if (current_alt != required_alt) {
553                     current_alt = interpolate_val(current_alt,
554                                                   required_alt,
555                                                   MaxCloudAltitudeChangeFtSec);
556                     fgSetDouble(s, current_alt);
557                 }
558
559                 snprintf(s, 128, cl, i);
560                 strncat(s, "/thickness-ft", 128);
561                 currentval = fgGetDouble(s);
562                 requiredval = (*layer)->getDoubleValue("thickness-ft");
563
564                 if (currentval != requiredval) {
565                     currentval = interpolate_val(currentval,
566                                                  requiredval,
567                                                  MaxCloudThicknessChangeFtSec);
568                     fgSetDouble(s, currentval);
569                 }
570             }
571         }
572         
573         if (rebuild_clouds) {
574             // Force an update of the 3D clouds
575             fgSetDouble("/environment/rebuild-layers", 1.0);
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     const SGPropertyNode *longitude
656         = fgGetNode( "/position/longitude-deg", true );
657     const SGPropertyNode *latitude
658         = fgGetNode( "/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         const FGAirport* a = globals->get_airports()
670                    ->search( longitude->getDoubleValue(),
671                              latitude->getDoubleValue(),
672                              360.0,
673                              metar_only );
674         if ( a ) {  
675             FGMetarResult result = fetch_data( a->getId() );
676             if ( result.m != NULL ) {
677                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
678                         << a->getId());
679                 last_apt = a;
680                 _icao = a->getId();
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 = "
691                         << a->getId() );
692                 globals->get_airports()->no_metar( a->getId() );
693             }
694         }
695     }
696     metar_max_age->setLongValue(max_age);
697 }
698
699 void
700 FGMetarEnvironmentCtrl::reinit ()
701 {
702     _error_count = 0;
703     _error_dt = 0.0;
704     metar_loaded = false;
705
706     env->reinit();
707 }
708
709 void
710 FGMetarEnvironmentCtrl::update(double delta_time_sec)
711 {
712
713     _dt += delta_time_sec;
714     if (_error_count >= 3)
715        return;
716
717     FGMetarResult result;
718
719     static const SGPropertyNode *longitude
720         = fgGetNode( "/position/longitude-deg", true );
721     static const SGPropertyNode *latitude
722         = fgGetNode( "/position/latitude-deg", true );
723     search_elapsed += delta_time_sec;
724     fetch_elapsed += delta_time_sec;
725     interpolate_elapsed += delta_time_sec;
726
727     // if time for a new search request, push it onto the request
728     // queue
729     if ( search_elapsed > search_interval_sec ) {
730         const FGAirport* a = globals->get_airports()
731                    ->search( longitude->getDoubleValue(),
732                              latitude->getDoubleValue(),
733                              360.0,
734                              metar_only );
735         if ( a ) {
736             if ( !last_apt || last_apt->getId() != a->getId()
737                  || fetch_elapsed > same_station_interval_sec )
738             {
739                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
740                         << a->getId());
741                 request_queue.push( a->getId() );
742                 last_apt = a;
743                 _icao = a->getId();
744                 search_elapsed = 0.0;
745                 fetch_elapsed = 0.0;
746             } else {
747                 search_elapsed = 0.0;
748                 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
749                         << same_station_interval_sec - fetch_elapsed );
750             }
751         } else {
752             SG_LOG( SG_GENERAL, SG_WARN,
753                     "Unable to find any airports with metar" );
754         }
755     } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
756         // Interpolate the current configuration closer to the actual METAR
757         update_env_config();
758         env->reinit();
759         interpolate_elapsed = 0.0;
760     }
761
762 #if !defined(ENABLE_THREADS)
763     // No loader thread running so manually fetch the data
764     string id = "";
765     while ( !request_queue.empty() ) {
766         id = request_queue.front();
767         request_queue.pop();
768     }
769
770     if ( !id.empty() ) {
771         SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
772         result = fetch_data( id );
773         result_queue.push( result );
774     }
775 #endif // ENABLE_THREADS
776
777     // process any results from the loader.
778     while ( !result_queue.empty() ) {
779         result = result_queue.front();
780         result_queue.pop();
781         if ( result.m != NULL ) {
782             update_metar_properties( result.m );
783             delete result.m;
784             update_env_config();
785             env->reinit();
786         } else {
787             // mark as no metar so it doesn't show up in subsequent
788             // searches, and signal an immediate re-search.
789             SG_LOG( SG_GENERAL, SG_WARN,
790                     "no metar at station = " << result.icao );
791             globals->get_airports()->no_metar( result.icao );
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