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