]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
4a5bd263339cac721458b46a1c72b94de163169b
[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 (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");
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
611     fgSetupWind(dir_from, dir_to, speed, gust);
612     fgDefaultWeatherValue("visibility-m", vis);
613     set_temp_at_altitude(temp, station_elevation_ft);
614     set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
615     fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
616
617     // We've now successfully loaded a METAR into the configuration
618     metar_loaded = true;
619 }
620
621 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
622                                                 const char * requiredname,
623                                                 double dt)
624 {
625     double currentval = fgGetDouble(currentname);
626     double requiredval = fgGetDouble(requiredname);
627     return interpolate_val(currentval, requiredval, dt);
628 }
629
630 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
631                                                double requiredval,
632                                                double dt)
633 {
634     double dval = EnvironmentUpdatePeriodSec * dt;
635
636     if (fabs(currentval - requiredval) < dval) return requiredval;
637     if (currentval < requiredval) return (currentval + dval);
638     if (currentval > requiredval) return (currentval - dval);
639     return requiredval;
640 }
641
642 void
643 FGMetarEnvironmentCtrl::init ()
644 {
645     const SGPropertyNode *longitude
646         = fgGetNode( "/position/longitude-deg", true );
647     const SGPropertyNode *latitude
648         = fgGetNode( "/position/latitude-deg", true );
649
650     metar_loaded = false;
651     bool found_metar = false;
652     long max_age = metar_max_age->getLongValue();
653     // Don't check max age during init so that we don't loop over a lot
654     // of airports metar if there is a problem.
655     // The update() calls will find a correct metar if things went wrong here
656     metar_max_age->setLongValue(0);
657
658     while ( !found_metar && (_error_count < 3) ) {
659         const FGAirport* a = globals->get_airports()
660                    ->search( longitude->getDoubleValue(),
661                              latitude->getDoubleValue(),
662                              360.0,
663                              metar_only );
664         if ( a ) {  
665             FGMetarResult result = fetch_data( a->getId() );
666             if ( result.m != NULL ) {
667                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
668                         << a->getId());
669                 last_apt = a;
670                 _icao = a->getId();
671                 search_elapsed = 0.0;
672                 fetch_elapsed = 0.0;
673                 update_metar_properties( result.m );
674                 update_env_config();
675                 env->init();
676                 found_metar = true;
677             } else {
678                 // mark as no metar so it doesn't show up in subsequent
679                 // searches.
680                 SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = "
681                         << a->getId() );
682                 globals->get_airports()->no_metar( a->getId() );
683             }
684         }
685     }
686     metar_max_age->setLongValue(max_age);
687 }
688
689 void
690 FGMetarEnvironmentCtrl::reinit ()
691 {
692     _error_count = 0;
693     _error_dt = 0.0;
694     metar_loaded = false;
695
696     env->reinit();
697 }
698
699 void
700 FGMetarEnvironmentCtrl::update(double delta_time_sec)
701 {
702
703     _dt += delta_time_sec;
704     if (_error_count >= 3)
705        return;
706
707     FGMetarResult result;
708
709     static const SGPropertyNode *longitude
710         = fgGetNode( "/position/longitude-deg", true );
711     static const SGPropertyNode *latitude
712         = fgGetNode( "/position/latitude-deg", true );
713     search_elapsed += delta_time_sec;
714     fetch_elapsed += delta_time_sec;
715     interpolate_elapsed += delta_time_sec;
716
717     // if time for a new search request, push it onto the request
718     // queue
719     if ( search_elapsed > search_interval_sec ) {
720         const FGAirport* a = globals->get_airports()
721                    ->search( longitude->getDoubleValue(),
722                              latitude->getDoubleValue(),
723                              360.0,
724                              metar_only );
725         if ( a ) {
726             if ( !last_apt || last_apt->getId() != a->getId()
727                  || fetch_elapsed > same_station_interval_sec )
728             {
729                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
730                         << a->getId());
731                 request_queue.push( a->getId() );
732                 last_apt = a;
733                 _icao = a->getId();
734                 search_elapsed = 0.0;
735                 fetch_elapsed = 0.0;
736             } else {
737                 search_elapsed = 0.0;
738                 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
739                         << same_station_interval_sec - fetch_elapsed );
740             }
741         } else {
742             SG_LOG( SG_GENERAL, SG_WARN,
743                     "Unable to find any airports with metar" );
744         }
745     } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
746         // Interpolate the current configuration closer to the actual METAR
747         update_env_config();
748         env->reinit();
749         interpolate_elapsed = 0.0;
750     }
751
752 #if !defined(ENABLE_THREADS)
753     // No loader thread running so manually fetch the data
754     string id = "";
755     while ( !request_queue.empty() ) {
756         id = request_queue.front();
757         request_queue.pop();
758     }
759
760     if ( !id.empty() ) {
761         SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << id );
762         result = fetch_data( id );
763         result_queue.push( result );
764     }
765 #endif // ENABLE_THREADS
766
767     // process any results from the loader.
768     while ( !result_queue.empty() ) {
769         result = result_queue.front();
770         result_queue.pop();
771         if ( result.m != NULL ) {
772             update_metar_properties( result.m );
773             delete result.m;
774             update_env_config();
775             env->reinit();
776         } else {
777             // mark as no metar so it doesn't show up in subsequent
778             // searches, and signal an immediate re-search.
779             SG_LOG( SG_GENERAL, SG_WARN,
780                     "no metar at station = " << result.icao );
781             globals->get_airports()->no_metar( result.icao );
782             search_elapsed = 9999.0;
783         }
784     }
785
786     env->update(delta_time_sec);
787 }
788
789
790 void
791 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
792 {
793     env->setEnvironment(environment);
794 }
795
796 FGMetarResult
797 FGMetarEnvironmentCtrl::fetch_data( const string &icao )
798 {
799     FGMetarResult result;
800     result.icao = icao;
801
802     // if the last error was more than three seconds ago,
803     // then pretent nothing happened.
804     if (_error_dt < 3) {
805         _error_dt += _dt;
806
807     } else {
808         _error_dt = 0.0;
809         _error_count = 0;
810     }
811
812     // fetch station elevation if exists
813     const FGAirport* a = globals->get_airports()->search( icao );
814     if ( a ) {
815         station_elevation_ft = a->getElevation();
816     }
817
818     // fetch current metar data
819     try {
820         string host = proxy_host->getStringValue();
821         string auth = proxy_auth->getStringValue();
822         string port = proxy_port->getStringValue();
823         result.m = new FGMetar( icao, host, port, auth);
824
825         long max_age = metar_max_age->getLongValue();
826         long age = result.m->getAge_min();
827         if (max_age &&  age > max_age) {
828             SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
829             delete result.m;
830             result.m = NULL;
831
832             if (++_stale_count > 10) {
833                 _error_count = 1000;
834                 throw sg_io_exception("More than 10 stale METAR messages in a row."
835                         " Check your system time!");
836             }
837         } else
838             _stale_count = 0;
839
840     } catch (const sg_io_exception& e) {
841         SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
842                 << e.getFormattedMessage().c_str() );
843 #if defined(ENABLE_THREADS)
844         if (_error_count++ >= 3) {
845            SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
846            thread_stop();
847         }
848 #endif
849
850         result.m = NULL;
851     }
852
853     _dt = 0;
854
855     return result;
856 }
857
858
859 void
860 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
861 {
862     int i;
863     double d;
864     char s[128];
865
866     fgSetString("/environment/metar/real-metar", m->getData());
867         // don't update with real weather when we use a custom weather scenario
868         const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
869         if( strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
870                 return;
871     fgSetString("/environment/metar/last-metar", m->getData());
872     fgSetString("/environment/metar/station-id", m->getId());
873     fgSetDouble("/environment/metar/min-visibility-m",
874                 m->getMinVisibility().getVisibility_m() );
875     fgSetDouble("/environment/metar/max-visibility-m",
876                 m->getMaxVisibility().getVisibility_m() );
877
878     const SGMetarVisibility *dirvis = m->getDirVisibility();
879     for (i = 0; i < 8; i++, dirvis++) {
880         const char *min = "/environment/metar/visibility[%d]/min-m";
881         const char *max = "/environment/metar/visibility[%d]/max-m";
882
883         d = dirvis->getVisibility_m();
884
885         snprintf(s, 128, min, i);
886         fgSetDouble(s, d);
887         snprintf(s, 128, max, i);
888         fgSetDouble(s, d);
889     }
890
891     fgSetInt("/environment/metar/base-wind-range-from",
892              m->getWindRangeFrom() );
893     fgSetInt("/environment/metar/base-wind-range-to",
894              m->getWindRangeTo() );
895     fgSetDouble("/environment/metar/base-wind-speed-kt",
896                 m->getWindSpeed_kt() );
897     fgSetDouble("/environment/metar/gust-wind-speed-kt",
898                 m->getGustSpeed_kt() );
899     fgSetDouble("/environment/metar/temperature-degc",
900                 m->getTemperature_C() );
901     fgSetDouble("/environment/metar/dewpoint-degc",
902                 m->getDewpoint_C() );
903     fgSetDouble("/environment/metar/rel-humidity-norm",
904                 m->getRelHumidity() );
905     fgSetDouble("/environment/metar/pressure-inhg",
906                 m->getPressure_inHg() );
907
908     vector<SGMetarCloud> cv = m->getClouds();
909     vector<SGMetarCloud>::const_iterator cloud;
910
911     const char *cl = "/environment/metar/clouds/layer[%i]";
912     for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
913         const char *coverage_string[5] = 
914             { "clear", "few", "scattered", "broken", "overcast" };
915         const double thickness[5] = { 0, 65, 600,750, 1000};
916         int q;
917
918         snprintf(s, 128, cl, i);
919         strncat(s, "/coverage", 128);
920         q = cloud->getCoverage();
921         fgSetString(s, coverage_string[q] );
922
923         snprintf(s, 128, cl, i);
924         strncat(s, "/elevation-ft", 128);
925         fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
926
927         snprintf(s, 128, cl, i);
928         strncat(s, "/thickness-ft", 128);
929         fgSetDouble(s, thickness[q]);
930
931         snprintf(s, 128, cl, i);
932         strncat(s, "/span-m", 128);
933         fgSetDouble(s, 40000.0);
934     }
935
936     for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
937         snprintf(s, 128, cl, i);
938         strncat(s, "/coverage", 128);
939         fgSetString(s, "clear");
940
941         snprintf(s, 128, cl, i);
942         strncat(s, "/elevation-ft", 128);
943         fgSetDouble(s, -9999);
944
945         snprintf(s, 128, cl, i);
946         strncat(s, "/thickness-ft", 128);
947         fgSetDouble(s, 0);
948
949         snprintf(s, 128, cl, i);
950         strncat(s, "/span-m", 128);
951         fgSetDouble(s, 40000.0);
952     }
953
954     fgSetDouble("/environment/metar/rain-norm", m->getRain());
955     fgSetDouble("/environment/metar/hail-norm", m->getHail());
956     fgSetDouble("/environment/metar/snow-norm", m->getSnow());
957     fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
958 }
959
960
961 #if defined(ENABLE_THREADS)
962 void
963 FGMetarEnvironmentCtrl::thread_stop()
964 {
965     request_queue.push( string() );     // ask thread to terminate
966     thread->join();
967 }
968
969 void
970 FGMetarEnvironmentCtrl::MetarThread::run()
971 {
972     while ( true )
973     {
974         string icao = fetcher->request_queue.pop();
975         if (icao.empty())
976             return;
977         SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << icao );
978         FGMetarResult result = fetcher->fetch_data( icao );
979         fetcher->result_queue.push( result );
980     }
981 }
982 #endif // ENABLE_THREADS
983
984
985 // end of environment_ctrl.cxx