]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
677969e705a0550dedb1b390b8d7c1a12543ba2e
[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
506         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
507             double currentval;
508             double requiredval;
509
510             // In the case of clouds, we want to avoid writing if nothing has
511             // changed, as these properties are tied to the renderer and will
512             // cause the clouds to be updated, reseting the texture locations.
513
514             // We don't interpolate the coverage values as no-matter how we
515             // do it, it will be quite a sudden change of texture. Better to
516             // have a single change than four or five.
517             snprintf(s, 128, cl, i);
518             strncat(s, "/coverage", 128);
519             const char* coverage = (*layer)->getStringValue("coverage", "clear");
520             if (strncmp(fgGetString(s), coverage, 128) != 0)
521                 fgSetString(s, coverage);
522
523             snprintf(s, 128, cl, i);
524             strncat(s, "/elevation-ft", 128);
525             double current_alt = fgGetDouble(s);
526             double required_alt = (*layer)->getDoubleValue("elevation-ft");
527
528             if (current_alt < -9000 || required_alt < -9000 ||
529                 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
530                 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
531                 // We don't interpolate any layers that are
532                 //  - too far above us to be visible
533                 //  - too far below us to be visible
534                 //  - with too large a difference to make interpolation sensible
535                 //  - to or from -9999 (used as a placeholder)
536                 //  - any values that are too high above us,
537                 snprintf(s, 128, cl, i);
538                 strncat(s, "/elevation-ft", 128);
539                 if (current_alt != required_alt)
540                     fgSetDouble(s, required_alt);
541
542                 snprintf(s, 128, cl, i);
543                 strncat(s, "/thickness-ft", 128);
544                 if (fgGetDouble(s) != (*layer)->getDoubleValue("thickness-ft"))
545                     fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
546
547             } else {
548                 // Interpolate the other values in the usual way
549                 if (current_alt != required_alt) {
550                     current_alt = interpolate_val(current_alt,
551                                                   required_alt,
552                                                   MaxCloudAltitudeChangeFtSec);
553                     fgSetDouble(s, current_alt);
554                 }
555
556                 snprintf(s, 128, cl, i);
557                 strncat(s, "/thickness-ft", 128);
558                 currentval = fgGetDouble(s);
559                 requiredval = (*layer)->getDoubleValue("thickness-ft");
560
561                 if (currentval != requiredval) {
562                     currentval = interpolate_val(currentval,
563                                                  requiredval,
564                                                  MaxCloudThicknessChangeFtSec);
565                     fgSetDouble(s, currentval);
566                 }
567             }
568         }
569
570     } else {
571         // We haven't already loaded a METAR, so apply it immediately.
572         dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
573         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
574         speed    = fgGetDouble("/environment/metar/base-wind-speed-kt");
575         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
576         vis      = fgGetDouble("/environment/metar/min-visibility-m");
577         pressure = fgGetDouble("/environment/metar/pressure-inhg");
578         temp     = fgGetDouble("/environment/metar/temperature-degc");
579         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
580
581         // Set the cloud layers by copying over the METAR versions.
582         SGPropertyNode * clouds = fgGetNode("/environment/metar/clouds", true);
583
584         vector<SGPropertyNode_ptr> layers = clouds->getChildren("layer");
585         vector<SGPropertyNode_ptr>::const_iterator layer;
586         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
587
588         const char *cl = "/environment/clouds/layer[%i]";
589         char s[128];
590         int i;
591
592         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
593             snprintf(s, 128, cl, i);
594             strncat(s, "/coverage", 128);
595             fgSetString(s, (*layer)->getStringValue("coverage", "clear"));
596
597             snprintf(s, 128, cl, i);
598             strncat(s, "/elevation-ft", 128);
599             fgSetDouble(s, (*layer)->getDoubleValue("elevation-ft"));
600
601             snprintf(s, 128, cl, i);
602             strncat(s, "/thickness-ft", 128);
603             fgSetDouble(s, (*layer)->getDoubleValue("thickness-ft"));
604
605             snprintf(s, 128, cl, i);
606             strncat(s, "/span-m", 128);
607             fgSetDouble(s, 40000.0);
608         }
609
610         // Force an update of the 3D clouds
611         fgSetDouble("/environment/rebuild-layers", 1.0);
612     }
613
614     fgSetupWind(dir_from, dir_to, speed, gust);
615     fgDefaultWeatherValue("visibility-m", vis);
616     set_temp_at_altitude(temp, station_elevation_ft);
617     set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
618     fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
619
620     // We've now successfully loaded a METAR into the configuration
621     metar_loaded = true;
622 }
623
624 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
625                                                 const char * requiredname,
626                                                 double dt)
627 {
628     double currentval = fgGetDouble(currentname);
629     double requiredval = fgGetDouble(requiredname);
630     return interpolate_val(currentval, requiredval, dt);
631 }
632
633 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
634                                                double requiredval,
635                                                double dt)
636 {
637     double dval = EnvironmentUpdatePeriodSec * dt;
638
639     if (fabs(currentval - requiredval) < dval) return requiredval;
640     if (currentval < requiredval) return (currentval + dval);
641     if (currentval > requiredval) return (currentval - dval);
642     return requiredval;
643 }
644
645 void
646 FGMetarEnvironmentCtrl::init ()
647 {
648     const SGPropertyNode *longitude
649         = fgGetNode( "/position/longitude-deg", true );
650     const SGPropertyNode *latitude
651         = fgGetNode( "/position/latitude-deg", true );
652
653     metar_loaded = false;
654     bool found_metar = false;
655     long max_age = metar_max_age->getLongValue();
656     // Don't check max age during init so that we don't loop over a lot
657     // of airports metar if there is a problem.
658     // The update() calls will find a correct metar if things went wrong here
659     metar_max_age->setLongValue(0);
660
661     while ( !found_metar && (_error_count < 3) ) {
662         const FGAirport* a = globals->get_airports()
663                    ->search( longitude->getDoubleValue(),
664                              latitude->getDoubleValue(),
665                              360.0,
666                              metar_only );
667         if ( a ) {  
668             FGMetarResult result = fetch_data( a->getId() );
669             if ( result.m != NULL ) {
670                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
671                         << a->getId());
672                 last_apt = a;
673                 _icao = a->getId();
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 = "
684                         << a->getId() );
685                 globals->get_airports()->no_metar( a->getId() );
686             }
687         }
688     }
689     metar_max_age->setLongValue(max_age);
690 }
691
692 void
693 FGMetarEnvironmentCtrl::reinit ()
694 {
695     _error_count = 0;
696     _error_dt = 0.0;
697     metar_loaded = false;
698
699     env->reinit();
700 }
701
702 void
703 FGMetarEnvironmentCtrl::update(double delta_time_sec)
704 {
705
706     _dt += delta_time_sec;
707     if (_error_count >= 3)
708        return;
709
710     FGMetarResult result;
711
712     static const SGPropertyNode *longitude
713         = fgGetNode( "/position/longitude-deg", true );
714     static const SGPropertyNode *latitude
715         = fgGetNode( "/position/latitude-deg", true );
716     search_elapsed += delta_time_sec;
717     fetch_elapsed += delta_time_sec;
718     interpolate_elapsed += delta_time_sec;
719
720     // if time for a new search request, push it onto the request
721     // queue
722     if ( search_elapsed > search_interval_sec ) {
723         const FGAirport* a = globals->get_airports()
724                    ->search( longitude->getDoubleValue(),
725                              latitude->getDoubleValue(),
726                              360.0,
727                              metar_only );
728         if ( a ) {
729             if ( !last_apt || last_apt->getId() != a->getId()
730                  || fetch_elapsed > same_station_interval_sec )
731             {
732                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
733                         << a->getId());
734                 request_queue.push( a->getId() );
735                 last_apt = a;
736                 _icao = a->getId();
737                 search_elapsed = 0.0;
738                 fetch_elapsed = 0.0;
739             } else {
740                 search_elapsed = 0.0;
741                 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
742                         << same_station_interval_sec - fetch_elapsed );
743             }
744         } else {
745             SG_LOG( SG_GENERAL, SG_WARN,
746                     "Unable to find any airports with metar" );
747         }
748     } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
749         // Interpolate the current configuration closer to the actual METAR
750         update_env_config();
751         env->reinit();
752         interpolate_elapsed = 0.0;
753     }
754
755 #if !defined(ENABLE_THREADS)
756     // No loader thread running so manually fetch the data
757     string id = "";
758     while ( !request_queue.empty() ) {
759         id = request_queue.front();
760         request_queue.pop();
761     }
762
763     if ( !id.empty() ) {
764         SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
765         result = fetch_data( id );
766         result_queue.push( result );
767     }
768 #endif // ENABLE_THREADS
769
770     // process any results from the loader.
771     while ( !result_queue.empty() ) {
772         result = result_queue.front();
773         result_queue.pop();
774         if ( result.m != NULL ) {
775             update_metar_properties( result.m );
776             delete result.m;
777             update_env_config();
778             env->reinit();
779         } else {
780             // mark as no metar so it doesn't show up in subsequent
781             // searches, and signal an immediate re-search.
782             SG_LOG( SG_GENERAL, SG_WARN,
783                     "no metar at station = " << result.icao );
784             globals->get_airports()->no_metar( result.icao );
785             search_elapsed = 9999.0;
786         }
787     }
788
789     env->update(delta_time_sec);
790 }
791
792
793 void
794 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
795 {
796     env->setEnvironment(environment);
797 }
798
799 FGMetarResult
800 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
801 {
802     FGMetarResult result;
803     result.icao = icao;
804
805     // if the last error was more than three seconds ago,
806     // then pretent nothing happened.
807     if (_error_dt < 3) {
808         _error_dt += _dt;
809
810     } else {
811         _error_dt = 0.0;
812         _error_count = 0;
813     }
814
815     // fetch station elevation if exists
816     const FGAirport* a = globals->get_airports()->search( icao );
817     if ( a ) {
818         station_elevation_ft = a->getElevation();
819     }
820
821     // fetch current metar data
822     try {
823         string host = proxy_host->getStringValue();
824         string auth = proxy_auth->getStringValue();
825         string port = proxy_port->getStringValue();
826         result.m = new FGMetar( icao, host, port, auth);
827
828         long max_age = metar_max_age->getLongValue();
829         long age = result.m->getAge_min();
830         if (max_age &&  age > max_age) {
831             SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
832             delete result.m;
833             result.m = NULL;
834
835             if (++_stale_count > 10) {
836                 _error_count = 1000;
837                 throw sg_io_exception("More than 10 stale METAR messages in a row."
838                         " Check your system time!");
839             }
840         } else
841             _stale_count = 0;
842
843     } catch (const sg_io_exception& e) {
844         SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
845                 << e.getFormattedMessage().c_str() );
846 #if defined(ENABLE_THREADS)
847         if (_error_count++ >= 3) {
848            SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
849            thread_stop();
850         }
851 #endif
852
853         result.m = NULL;
854     }
855
856     _dt = 0;
857
858     return result;
859 }
860
861
862 void
863 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
864 {
865     int i;
866     double d;
867     char s[128];
868
869     fgSetString("/environment/metar/real-metar", m->getData());
870         // don't update with real weather when we use a custom weather scenario
871         const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
872         if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
873                 return;
874     fgSetString("/environment/metar/last-metar", m->getData());
875     fgSetString("/environment/metar/station-id", m->getId());
876     fgSetDouble("/environment/metar/min-visibility-m",
877                 m->getMinVisibility().getVisibility_m() );
878     fgSetDouble("/environment/metar/max-visibility-m",
879                 m->getMaxVisibility().getVisibility_m() );
880
881     const SGMetarVisibility *dirvis = m->getDirVisibility();
882     for (i = 0; i < 8; i++, dirvis++) {
883         const char *min = "/environment/metar/visibility[%d]/min-m";
884         const char *max = "/environment/metar/visibility[%d]/max-m";
885
886         d = dirvis->getVisibility_m();
887
888         snprintf(s, 128, min, i);
889         fgSetDouble(s, d);
890         snprintf(s, 128, max, i);
891         fgSetDouble(s, d);
892     }
893
894     fgSetInt("/environment/metar/base-wind-range-from",
895              m->getWindRangeFrom() );
896     fgSetInt("/environment/metar/base-wind-range-to",
897              m->getWindRangeTo() );
898     fgSetDouble("/environment/metar/base-wind-speed-kt",
899                 m->getWindSpeed_kt() );
900     fgSetDouble("/environment/metar/gust-wind-speed-kt",
901                 m->getGustSpeed_kt() );
902     fgSetDouble("/environment/metar/temperature-degc",
903                 m->getTemperature_C() );
904     fgSetDouble("/environment/metar/dewpoint-degc",
905                 m->getDewpoint_C() );
906     fgSetDouble("/environment/metar/rel-humidity-norm",
907                 m->getRelHumidity() );
908     fgSetDouble("/environment/metar/pressure-inhg",
909                 m->getPressure_inHg() );
910
911     vector<SGMetarCloud> cv = m->getClouds();
912     vector<SGMetarCloud>::const_iterator cloud;
913
914     const char *cl = "/environment/metar/clouds/layer[%i]";
915     for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
916         const char *coverage_string[5] = 
917             { "clear", "few", "scattered", "broken", "overcast" };
918         const double thickness[5] = { 0, 65, 600,750, 1000};
919         int q;
920
921         snprintf(s, 128, cl, i);
922         strncat(s, "/coverage", 128);
923         q = cloud->getCoverage();
924         fgSetString(s, coverage_string[q] );
925
926         snprintf(s, 128, cl, i);
927         strncat(s, "/elevation-ft", 128);
928         fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
929
930         snprintf(s, 128, cl, i);
931         strncat(s, "/thickness-ft", 128);
932         fgSetDouble(s, thickness[q]);
933
934         snprintf(s, 128, cl, i);
935         strncat(s, "/span-m", 128);
936         fgSetDouble(s, 40000.0);
937     }
938
939     for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
940         snprintf(s, 128, cl, i);
941         strncat(s, "/coverage", 128);
942         fgSetString(s, "clear");
943
944         snprintf(s, 128, cl, i);
945         strncat(s, "/elevation-ft", 128);
946         fgSetDouble(s, -9999);
947
948         snprintf(s, 128, cl, i);
949         strncat(s, "/thickness-ft", 128);
950         fgSetDouble(s, 0);
951
952         snprintf(s, 128, cl, i);
953         strncat(s, "/span-m", 128);
954         fgSetDouble(s, 40000.0);
955     }
956
957     fgSetDouble("/environment/metar/rain-norm", m->getRain());
958     fgSetDouble("/environment/metar/hail-norm", m->getHail());
959     fgSetDouble("/environment/metar/snow-norm", m->getSnow());
960     fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
961 }
962
963
964 #if defined(ENABLE_THREADS)
965 void
966 FGMetarEnvironmentCtrl::thread_stop()
967 {
968     request_queue.push( string() );     // ask thread to terminate
969     thread->join();
970 }
971
972 void
973 FGMetarEnvironmentCtrl::MetarThread::run()
974 {
975     while ( true )
976     {
977         string icao = fetcher->request_queue.pop();
978         if (icao.empty())
979             return;
980         SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
981         FGMetarResult result = fetcher->fetch_data( icao );
982         fetcher->result_queue.push( result );
983     }
984 }
985 #endif // ENABLE_THREADS
986
987
988 // end of environment_ctrl.cxx