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