]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
Avoid division by zero.
[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 <algorithm>
28
29 #include <simgear/debug/logstream.hxx>
30 #include <simgear/structure/commands.hxx>
31 #include <simgear/structure/exception.hxx>
32
33 #include <Airports/simple.hxx>
34 #include <Main/fg_props.hxx>
35 #include <Main/util.hxx>
36
37 #include "fgmetar.hxx"
38 #include "environment_mgr.hxx"
39 #include "environment_ctrl.hxx"
40
41 using std::sort;
42
43 class AirportWithMetar : public FGAirport::AirportFilter
44 {
45 public:
46   virtual bool passAirport(FGAirport* aApt) const
47   {
48     return aApt->getMetar();
49   }
50 };
51 \f
52 ////////////////////////////////////////////////////////////////////////
53 // Implementation of FGEnvironmentCtrl abstract base class.
54 ////////////////////////////////////////////////////////////////////////
55
56 FGEnvironmentCtrl::FGEnvironmentCtrl ()
57   : _environment(0),
58     _lon_deg(0),
59     _lat_deg(0),
60     _elev_ft(0)
61 {
62 }
63
64 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
65 {
66 }
67
68 void
69 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
70 {
71   _environment = environment;
72 }
73
74 void
75 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
76 {
77   _lon_deg = lon_deg;
78 }
79
80 void
81 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
82 {
83   _lat_deg = lat_deg;
84 }
85
86 void
87 FGEnvironmentCtrl::setElevationFt (double elev_ft)
88 {
89   _elev_ft = elev_ft;
90 }
91
92 void
93 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
94 {
95   _lon_deg = lon_deg;
96   _lat_deg = lat_deg;
97   _elev_ft = elev_ft;
98 }
99
100
101 \f
102 ////////////////////////////////////////////////////////////////////////
103 // Implementation of FGUserDefEnvironmentCtrl.
104 ////////////////////////////////////////////////////////////////////////
105
106 FGUserDefEnvironmentCtrl::FGUserDefEnvironmentCtrl ()
107   : _base_wind_speed_node(0),
108     _gust_wind_speed_node(0),
109     _current_wind_speed_kt(0),
110     _delta_wind_speed_kt(0)
111 {
112 }
113
114 FGUserDefEnvironmentCtrl::~FGUserDefEnvironmentCtrl ()
115 {
116 }
117
118 void
119 FGUserDefEnvironmentCtrl::init ()
120 {
121                                 // Fill in some defaults.
122   if (!fgHasNode("/environment/params/base-wind-speed-kt"))
123     fgSetDouble("/environment/params/base-wind-speed-kt",
124                 fgGetDouble("/environment/wind-speed-kt"));
125   if (!fgHasNode("/environment/params/gust-wind-speed-kt"))
126     fgSetDouble("/environment/params/gust-wind-speed-kt",
127                 fgGetDouble("/environment/params/base-wind-speed-kt"));
128
129   _base_wind_speed_node =
130     fgGetNode("/environment/params/base-wind-speed-kt", true);
131   _gust_wind_speed_node =
132     fgGetNode("/environment/params/gust-wind-speed-kt", true);
133
134   _current_wind_speed_kt = _base_wind_speed_node->getDoubleValue();
135   _delta_wind_speed_kt = 0.1;
136 }
137
138 void
139 FGUserDefEnvironmentCtrl::update (double dt)
140 {
141   double base_wind_speed = _base_wind_speed_node->getDoubleValue();
142   double gust_wind_speed = _gust_wind_speed_node->getDoubleValue();
143
144   if (gust_wind_speed < base_wind_speed) {
145       gust_wind_speed = base_wind_speed;
146       _gust_wind_speed_node->setDoubleValue(gust_wind_speed);
147   }
148
149   if (base_wind_speed == gust_wind_speed) {
150     _current_wind_speed_kt = base_wind_speed;
151   } else {
152     int rn = rand() % 128;
153     int sign = (_delta_wind_speed_kt < 0 ? -1 : 1);
154     double gust = _current_wind_speed_kt - base_wind_speed;
155     double incr = gust / 50;
156
157     if (rn == 0)
158       _delta_wind_speed_kt = - _delta_wind_speed_kt;
159     else if (rn < 4)
160       _delta_wind_speed_kt -= incr * sign;
161     else if (rn < 16)
162       _delta_wind_speed_kt += incr * sign;
163
164     _current_wind_speed_kt += _delta_wind_speed_kt;
165
166     if (_current_wind_speed_kt < base_wind_speed) {
167       _current_wind_speed_kt = base_wind_speed;
168       _delta_wind_speed_kt = 0.01;
169     } else if (_current_wind_speed_kt > gust_wind_speed) {
170       _current_wind_speed_kt = gust_wind_speed;
171       _delta_wind_speed_kt = -0.01;
172     }
173   }
174   
175   if (_environment != 0)
176     _environment->set_wind_speed_kt(_current_wind_speed_kt);
177 }
178
179
180 \f
181 ////////////////////////////////////////////////////////////////////////
182 // Implementation of FGInterpolateEnvironmentCtrl.
183 ////////////////////////////////////////////////////////////////////////
184
185 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
186 {
187 }
188
189 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
190 {
191     unsigned int i;
192     for (i = 0; i < _boundary_table.size(); i++)
193         delete _boundary_table[i];
194     for (i = 0; i < _aloft_table.size(); i++)
195         delete _aloft_table[i];
196 }
197
198
199
200 void
201 FGInterpolateEnvironmentCtrl::init ()
202 {
203     read_table(fgGetNode("/environment/config/boundary", true),
204                _boundary_table);
205     read_table(fgGetNode("/environment/config/aloft", true),
206                _aloft_table);
207 }
208
209 void
210 FGInterpolateEnvironmentCtrl::reinit ()
211 {
212     unsigned int i;
213     for (i = 0; i < _boundary_table.size(); i++)
214         delete _boundary_table[i];
215     for (i = 0; i < _aloft_table.size(); i++)
216         delete _aloft_table[i];
217     _boundary_table.clear();
218     _aloft_table.clear();
219     init();
220 }
221
222 void
223 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node,
224                                           vector<bucket *> &table)
225 {
226     for (int i = 0; i < node->nChildren(); i++) {
227         const SGPropertyNode * child = node->getChild(i);
228         if ( strcmp(child->getName(), "entry") == 0
229              && child->getStringValue("elevation-ft", "")[0] != '\0'
230              && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
231         {
232             bucket * b = new bucket;
233             if (i > 0)
234                 b->environment.copy(table[i-1]->environment);
235             b->environment.read(child);
236             b->altitude_ft = b->environment.get_elevation_ft();
237             table.push_back(b);
238         }
239     }
240     sort(table.begin(), table.end(), bucket::lessThan);
241 }
242
243 void
244 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
245 {
246                                 // FIXME
247     double altitude_ft = fgGetDouble("/position/altitude-ft");
248     double altitude_agl_ft = fgGetDouble("/position/altitude-agl-ft");
249     double boundary_transition =
250         fgGetDouble("/environment/config/boundary-transition-ft", 500);
251
252     // double ground_elevation_ft = altitude_ft - altitude_agl_ft;
253
254     int length = _boundary_table.size();
255
256     if (length > 0) {
257                                 // boundary table
258         double boundary_limit = _boundary_table[length-1]->altitude_ft;
259         if (boundary_limit >= altitude_agl_ft) {
260             do_interpolate(_boundary_table, altitude_agl_ft,
261                            _environment);
262             return;
263         } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
264                                 // both tables
265             do_interpolate(_boundary_table, altitude_agl_ft, &env1);
266             do_interpolate(_aloft_table, altitude_ft, &env2);
267             double fraction =
268                 (altitude_agl_ft - boundary_limit) / boundary_transition;
269             interpolate(&env1, &env2, fraction, _environment);
270             return;
271         }
272     }
273
274                                 // aloft table
275     do_interpolate(_aloft_table, altitude_ft, _environment);
276 }
277
278 void
279 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table,
280                                               double altitude_ft,
281                                               FGEnvironment * environment)
282 {
283     int length = table.size();
284     if (length == 0)
285         return;
286
287                                 // Boundary conditions
288     if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
289         environment->copy(table[0]->environment);
290         return;
291     } else if (table[length-1]->altitude_ft <= altitude_ft) {
292         environment->copy(table[length-1]->environment);
293         return;
294     }
295         
296                                 // Search the interpolation table
297     for (int i = 0; i < length - 1; i++) {
298         if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
299                 FGEnvironment * env1 = &(table[i]->environment);
300                 FGEnvironment * env2 = &(table[i+1]->environment);
301                 double fraction;
302                 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
303                     fraction = 1.0;
304                 else
305                     fraction =
306                         ((altitude_ft - table[i]->altitude_ft) /
307                          (table[i+1]->altitude_ft - table[i]->altitude_ft));
308                 interpolate(env1, env2, fraction, environment);
309
310                 return;
311         }
312     }
313 }
314
315 bool
316 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
317 {
318     return (altitude_ft < b.altitude_ft);
319 }
320
321 bool
322 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
323 {
324     return (a->altitude_ft) < (b->altitude_ft);
325 }
326
327 \f
328 ////////////////////////////////////////////////////////////////////////
329 // Implementation of FGMetarEnvironmentCtrl.
330 ////////////////////////////////////////////////////////////////////////
331
332 FGMetarEnvironmentCtrl::FGMetarEnvironmentCtrl ()
333     : env( new FGInterpolateEnvironmentCtrl ),
334       metar_loaded( false ),
335       search_interval_sec( 60.0 ),        // 1 minute
336       same_station_interval_sec( 900.0 ), // 15 minutes
337       search_elapsed( 9999.0 ),
338       fetch_elapsed( 9999.0 ),
339       last_apt( 0 ),
340       proxy_host( fgGetNode("/sim/presets/proxy/host", true) ),
341       proxy_port( fgGetNode("/sim/presets/proxy/port", true) ),
342       proxy_auth( fgGetNode("/sim/presets/proxy/authentication", true) ),
343       metar_max_age( fgGetNode("/environment/params/metar-max-age-min", true) ),
344
345       // Interpolation constant definitions.
346       EnvironmentUpdatePeriodSec( 0.2 ),
347       MaxWindChangeKtsSec( 0.2 ),
348       MaxVisChangePercentSec( 0.05 ),
349       MaxPressureChangeInHgSec( 0.0033 ),
350       MaxCloudAltitudeChangeFtSec( 20.0 ),
351       MaxCloudThicknessChangeFtSec( 50.0 ),
352       MaxCloudInterpolationHeightFt( 5000.0 ),
353       MaxCloudInterpolationDeltaFt( 4000.0 ),
354
355       _error_count( 0 ),
356       _stale_count( 0 ),
357       _dt( 0.0 ),
358       _error_dt( 0.0 )
359 {
360 #if defined(ENABLE_THREADS)
361     thread = new MetarThread(this);
362     thread->setProcessorAffinity(1);
363     thread->start();
364 #endif // ENABLE_THREADS
365 }
366
367 FGMetarEnvironmentCtrl::~FGMetarEnvironmentCtrl ()
368 {
369 #if defined(ENABLE_THREADS)
370    thread_stop();
371    delete thread;
372 #endif // ENABLE_THREADS
373
374    delete env;
375 }
376
377
378 // use a "command" to set station temp at station elevation
379 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
380     SGPropertyNode args;
381     SGPropertyNode *node = args.getNode("temp-degc", 0, true);
382     node->setFloatValue( temp_degc );
383     node = args.getNode("altitude-ft", 0, true);
384     node->setFloatValue( altitude_ft );
385     globals->get_commands()->execute("set-outside-air-temp-degc", &args);
386 }
387
388
389 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
390     SGPropertyNode args;
391     SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
392     node->setFloatValue( dewpoint_degc );
393     node = args.getNode("altitude-ft", 0, true);
394     node->setFloatValue( altitude_ft );
395     globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
396 }
397
398
399 void
400 FGMetarEnvironmentCtrl::update_env_config ()
401 {
402     // If we aren't in the METAR scenario, don't attempt to interpolate.
403     if (strcmp(fgGetString("/environment/weather-scenario", "METAR"), "METAR"))
404         return;
405
406     double dir_from;
407     double dir_to;
408     double speed;
409     double gust;
410     double vis;
411     double pressure;
412     double temp;
413     double dewpoint;
414
415     const SGPropertyNode *metar_clouds = fgGetNode("/environment/metar/clouds", true);
416     SGPropertyNode *clouds = fgGetNode("/environment/clouds",  true);
417
418     if (metar_loaded) {
419         // Generate interpolated values between the METAR and the current
420         // configuration.
421
422         // Pick up the METAR wind values and convert them into a vector.
423         double metar[2];
424         double metar_speed = fgGetDouble("/environment/metar/base-wind-speed-kt");
425         double metar_heading = fgGetDouble("/environment/metar/base-wind-range-from");
426
427         metar[0] = metar_speed * sin(metar_heading * M_PI / 180.0);
428         metar[1] = metar_speed * cos(metar_heading * M_PI / 180.0);
429
430         // Convert the current wind values and convert them into a vector
431         double current[2];
432         double current_speed =
433                 fgGetDouble("/environment/config/boundary/entry/wind-speed-kt");
434         double current_heading = fgGetDouble(
435                 "/environment/config/boundary/entry/wind-from-heading-deg");
436
437         current[0] = current_speed * sin(current_heading * M_PI / 180.0);
438         current[1] = current_speed * cos(current_heading * M_PI / 180.0);
439
440         // Determine the maximum component-wise value that the wind can change.
441         // First we determine the fraction in the X and Y component, then
442         // factor by the maximum wind change.
443         double x = fabs(current[0] - metar[0]);
444         double y = fabs(current[1] - metar[1]);
445
446         // only interpolate if we have a difference
447         if (x + y > 0) {
448             double dx = x / (x + y);
449             double dy = 1 - dx;
450
451             double maxdx = dx * MaxWindChangeKtsSec;
452             double maxdy = dy * MaxWindChangeKtsSec;
453
454             // Interpolate each component separately.
455             current[0] = interpolate_val(current[0], metar[0], maxdx);
456             current[1] = interpolate_val(current[1], metar[1], maxdy);
457         }
458
459         // Now convert back to polar coordinates.
460         if ((current[0] == 0.0) && (current[1] == 0.0)) {
461             // Special case where there is no wind (otherwise atan2 barfs)
462             speed = 0.0;
463             dir_from = current_heading;
464
465         } else {
466             // Some real wind to convert back from. Work out the speed
467             // and direction value in degrees.
468             speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
469             dir_from = (atan2(current[0], current[1]) * 180.0 / M_PI);
470
471             // Normalize the direction.
472             if (dir_from < 0.0)
473                 dir_from += 360.0;
474
475             SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
476         }
477
478         // Now handle the visibility. We convert both visibility values
479         // to X-values, then interpolate from there, then back to real values.
480         // The length_scale is fixed to 1000m, so the visibility changes by
481         // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
482         // whichever is more.
483         double currentvis =
484                 fgGetDouble("/environment/config/boundary/entry/visibility-m");
485         double metarvis = fgGetDouble("/environment/metar/min-visibility-m");
486         double currentxval = log(1000.0 + currentvis);
487         double metarxval = log(1000.0 + metarvis);
488
489         currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
490
491         // Now convert back from an X-value to a straightforward visibility.
492         vis = exp(currentxval) - 1000.0;
493
494         pressure = interpolate_prop(
495                 "/environment/config/boundary/entry/pressure-sea-level-inhg",
496                 "/environment/metar/pressure-inhg",
497                 MaxPressureChangeInHgSec);
498
499         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
500         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
501         temp     = fgGetDouble("/environment/metar/temperature-degc");
502         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
503
504         // Set the cloud layers by interpolating over the METAR versions.
505         vector<SGPropertyNode_ptr> layers = metar_clouds->getChildren("layer");
506         vector<SGPropertyNode_ptr>::const_iterator layer;
507         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
508
509         double aircraft_alt = fgGetDouble("/position/altitude-ft");
510         int i;
511
512         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
513             SGPropertyNode *target = clouds->getChild("layer", i, true);
514
515             // In the case of clouds, we want to avoid writing if nothing has
516             // changed, as these properties are tied to the renderer and will
517             // cause the clouds to be updated, reseting the texture locations.
518
519             // We don't interpolate the coverage values as no-matter how we
520             // do it, it will be quite a sudden change of texture. Better to
521             // have a single change than four or five.
522             const char *coverage = (*layer)->getStringValue("coverage", "clear");
523             SGPropertyNode *cov = target->getNode("coverage", true);
524             if (strcmp(cov->getStringValue(), coverage) != 0)
525                 cov->setStringValue(coverage);
526
527             double required_alt = (*layer)->getDoubleValue("elevation-ft");
528             double current_alt = target->getDoubleValue("elevation-ft");
529             double required_thickness = (*layer)->getDoubleValue("thickness-ft");
530             SGPropertyNode *thickness = target->getNode("thickness-ft", true);
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                 if (current_alt != required_alt)
542                     target->setDoubleValue("elevation-ft", required_alt);
543
544                 if (thickness->getDoubleValue() != required_thickness)
545                     thickness->setDoubleValue(required_thickness);
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                     target->setDoubleValue("elevation-ft", current_alt);
554                 }
555
556                 double current_thickness = thickness->getDoubleValue();
557
558                 if (current_thickness != required_thickness) {
559                     current_thickness = interpolate_val(current_thickness,
560                                                  required_thickness,
561                                                  MaxCloudThicknessChangeFtSec);
562                     thickness->setDoubleValue(current_thickness);
563                 }
564             }
565         }
566
567     } else {
568         // We haven't already loaded a METAR, so apply it immediately.
569         dir_from = fgGetDouble("/environment/metar/base-wind-range-from");
570         dir_to   = fgGetDouble("/environment/metar/base-wind-range-to");
571         speed    = fgGetDouble("/environment/metar/base-wind-speed-kt");
572         gust     = fgGetDouble("/environment/metar/gust-wind-speed-kt");
573         vis      = fgGetDouble("/environment/metar/min-visibility-m");
574         pressure = fgGetDouble("/environment/metar/pressure-inhg");
575         temp     = fgGetDouble("/environment/metar/temperature-degc");
576         dewpoint = fgGetDouble("/environment/metar/dewpoint-degc");
577
578         vector<SGPropertyNode_ptr> layers = metar_clouds->getChildren("layer");
579         vector<SGPropertyNode_ptr>::const_iterator layer;
580         vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
581
582         int i;
583         for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
584             SGPropertyNode *target = clouds->getChild("layer", i, true);
585
586             target->setStringValue("coverage",
587                     (*layer)->getStringValue("coverage", "clear"));
588             target->setDoubleValue("elevation-ft",
589                     (*layer)->getDoubleValue("elevation-ft"));
590             target->setDoubleValue("thickness-ft",
591                     (*layer)->getDoubleValue("thickness-ft"));
592             target->setDoubleValue("span-m", 40000.0);
593         }
594
595         // Force an update of the 3D clouds
596         fgSetDouble("/environment/rebuild-layers", 1.0);
597     }
598
599     fgSetupWind(dir_from, dir_to, speed, gust);
600     fgDefaultWeatherValue("visibility-m", vis);
601     set_temp_at_altitude(temp, station_elevation_ft);
602     set_dewpoint_at_altitude(dewpoint, station_elevation_ft);
603     fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
604
605     // We've now successfully loaded a METAR into the configuration
606     metar_loaded = true;
607 }
608
609 double FGMetarEnvironmentCtrl::interpolate_prop(const char * currentname,
610                                                 const char * requiredname,
611                                                 double dt)
612 {
613     double currentval = fgGetDouble(currentname);
614     double requiredval = fgGetDouble(requiredname);
615     return interpolate_val(currentval, requiredval, dt);
616 }
617
618 double FGMetarEnvironmentCtrl::interpolate_val(double currentval,
619                                                double requiredval,
620                                                double dt)
621 {
622     double dval = EnvironmentUpdatePeriodSec * dt;
623
624     if (fabs(currentval - requiredval) < dval) return requiredval;
625     if (currentval < requiredval) return (currentval + dval);
626     if (currentval > requiredval) return (currentval - dval);
627     return requiredval;
628 }
629
630 void
631 FGMetarEnvironmentCtrl::init ()
632 {
633     SGGeod pos = SGGeod::fromDeg(
634       fgGetDouble("/position/longitude-deg", true), 
635       fgGetDouble( "/position/latitude-deg", true));
636
637     metar_loaded = false;
638     bool found_metar = false;
639     long max_age = metar_max_age->getLongValue();
640     // Don't check max age during init so that we don't loop over a lot
641     // of airports metar if there is a problem.
642     // The update() calls will find a correct metar if things went wrong here
643     metar_max_age->setLongValue(0);
644
645     while ( !found_metar && (_error_count < 3) ) {
646         AirportWithMetar filter;
647         FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
648         if (!a) {
649           break;
650         }
651         
652         FGMetarResult result = fetch_data(a);
653         if ( result.m != NULL ) {
654             SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
655                     << a->ident());
656             last_apt = a;
657             search_elapsed = 0.0;
658             fetch_elapsed = 0.0;
659             update_metar_properties( result.m );
660             update_env_config();
661             env->init();
662             found_metar = true;
663         } else {
664             // mark as no metar so it doesn't show up in subsequent
665             // searches.
666             SG_LOG( SG_GENERAL, SG_INFO, "no metar at metar = " << a->ident() );
667             a->setMetar(false);
668         }
669     } // of airprot-with-metar search iteration
670     
671     metar_max_age->setLongValue(max_age);
672 }
673
674 void
675 FGMetarEnvironmentCtrl::reinit ()
676 {
677     _error_count = 0;
678     _error_dt = 0.0;
679     metar_loaded = false;
680
681     env->reinit();
682 }
683
684 void
685 FGMetarEnvironmentCtrl::update(double delta_time_sec)
686 {
687     _dt += delta_time_sec;
688     if (_error_count >= 3)
689        return;
690
691     FGMetarResult result;
692
693     static const SGPropertyNode *longitude
694         = fgGetNode( "/position/longitude-deg", true );
695     static const SGPropertyNode *latitude
696         = fgGetNode( "/position/latitude-deg", true );
697     SGGeod pos = SGGeod::fromDeg(longitude->getDoubleValue(), 
698       latitude->getDoubleValue());
699         
700     search_elapsed += delta_time_sec;
701     fetch_elapsed += delta_time_sec;
702     interpolate_elapsed += delta_time_sec;
703
704     // if time for a new search request, push it onto the request
705     // queue
706     if ( search_elapsed > search_interval_sec ) {
707         AirportWithMetar filter;
708         FGAirport* a = FGAirport::findClosest(pos, 10000.0, &filter);
709         if (a) {
710           if ( !last_apt || last_apt->ident() != a->ident()
711                  || fetch_elapsed > same_station_interval_sec )
712             {
713                 SG_LOG( SG_GENERAL, SG_INFO, "closest station w/ metar = "
714                         << a->ident());
715                 request_queue.push(a);
716                 last_apt = a;
717                 search_elapsed = 0.0;
718                 fetch_elapsed = 0.0;
719             } else {
720                 search_elapsed = 0.0;
721                 SG_LOG( SG_GENERAL, SG_INFO, "same station, waiting = "
722                         << same_station_interval_sec - fetch_elapsed );
723             }
724
725         } else {
726           SG_LOG( SG_GENERAL, SG_WARN,
727                     "Unable to find any airports with metar" );
728         }
729     } else if ( interpolate_elapsed > EnvironmentUpdatePeriodSec ) {
730         // Interpolate the current configuration closer to the actual METAR
731         update_env_config();
732         env->reinit();
733         interpolate_elapsed = 0.0;
734     }
735
736 #if !defined(ENABLE_THREADS)
737     // No loader thread running so manually fetch the data
738     FGAirport* apt = NULL;
739     while ( !request_queue.empty() ) {
740         apt = request_queue.front();
741         request_queue.pop();
742     }
743
744     if (apt) {
745         SG_LOG( SG_GENERAL, SG_INFO, "inline fetching = " << apt->ident() );
746         result = fetch_data( apt );
747         result_queue.push( result );
748     }
749 #endif // ENABLE_THREADS
750
751     // process any results from the loader.
752     while ( !result_queue.empty() ) {
753         result = result_queue.front();
754         result_queue.pop();
755         if ( result.m != NULL ) {
756             update_metar_properties( result.m );
757             delete result.m;
758             update_env_config();
759             env->reinit();
760         } else {
761             // mark as no metar so it doesn't show up in subsequent
762             // searches, and signal an immediate re-search.
763             SG_LOG( SG_GENERAL, SG_WARN,
764                     "no metar at station = " << result.airport->ident() );
765             result.airport->setMetar(false);
766             search_elapsed = 9999.0;
767         }
768     }
769
770     env->update(delta_time_sec);
771 }
772
773
774 void
775 FGMetarEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
776 {
777     env->setEnvironment(environment);
778 }
779
780 FGMetarResult
781 FGMetarEnvironmentCtrl::fetch_data(FGAirport* apt)
782 {
783     FGMetarResult result;
784     result.airport = apt;
785
786     // if the last error was more than three seconds ago,
787     // then pretent nothing happened.
788     if (_error_dt < 3) {
789         _error_dt += _dt;
790
791     } else {
792         _error_dt = 0.0;
793         _error_count = 0;
794     }
795
796     station_elevation_ft = apt->getElevation();
797
798     // fetch current metar data
799     try {
800         string host = proxy_host->getStringValue();
801         string auth = proxy_auth->getStringValue();
802         string port = proxy_port->getStringValue();
803         result.m = new FGMetar( apt->ident(), host, port, auth);
804
805         long max_age = metar_max_age->getLongValue();
806         long age = result.m->getAge_min();
807         if (max_age &&  age > max_age) {
808             SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
809             delete result.m;
810             result.m = NULL;
811
812             if (++_stale_count > 10) {
813                 _error_count = 1000;
814                 throw sg_io_exception("More than 10 stale METAR messages in a row."
815                         " Check your system time!");
816             }
817         } else
818             _stale_count = 0;
819
820     } catch (const sg_io_exception& e) {
821         SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: "
822                 << e.getFormattedMessage().c_str() );
823 #if defined(ENABLE_THREADS)
824         if (_error_count++ >= 3) {
825            SG_LOG( SG_GENERAL, SG_WARN, "Stop fetching data permanently.");
826            thread_stop();
827         }
828 #endif
829
830         result.m = NULL;
831     }
832
833     _dt = 0;
834
835     return result;
836 }
837
838
839 void
840 FGMetarEnvironmentCtrl::update_metar_properties( const FGMetar *m )
841 {
842     int i;
843
844     fgSetString("/environment/metar/real-metar", m->getData());
845     // don't update with real weather when we use a custom weather scenario
846     const char *current_scenario = fgGetString("/environment/weather-scenario", "METAR");
847     if (strcmp(current_scenario, "METAR") && strcmp(current_scenario, "none"))
848         return;
849     fgSetString("/environment/metar/last-metar", m->getData());
850     fgSetString("/environment/metar/station-id", m->getId());
851     fgSetDouble("/environment/metar/min-visibility-m",
852                 m->getMinVisibility().getVisibility_m() );
853     fgSetDouble("/environment/metar/max-visibility-m",
854                 m->getMaxVisibility().getVisibility_m() );
855
856     SGPropertyNode *metar = fgGetNode("/environment/metar", true);
857     const SGMetarVisibility *dirvis = m->getDirVisibility();
858
859     for (i = 0; i < 8; i++, dirvis++) {
860         SGPropertyNode *vis = metar->getChild("visibility", i, true);
861         double v = dirvis->getVisibility_m();
862
863         vis->setDoubleValue("min-m", v);
864         vis->setDoubleValue("max-m", v);
865     }
866
867     fgSetInt("/environment/metar/base-wind-range-from",
868              m->getWindRangeFrom() );
869     fgSetInt("/environment/metar/base-wind-range-to",
870              m->getWindRangeTo() );
871     fgSetDouble("/environment/metar/base-wind-speed-kt",
872                 m->getWindSpeed_kt() );
873     fgSetDouble("/environment/metar/gust-wind-speed-kt",
874                 m->getGustSpeed_kt() );
875     fgSetDouble("/environment/metar/temperature-degc",
876                 m->getTemperature_C() );
877     fgSetDouble("/environment/metar/dewpoint-degc",
878                 m->getDewpoint_C() );
879     fgSetDouble("/environment/metar/rel-humidity-norm",
880                 m->getRelHumidity() );
881     fgSetDouble("/environment/metar/pressure-inhg",
882                 m->getPressure_inHg() );
883
884     vector<SGMetarCloud> cv = m->getClouds();
885     vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
886
887     SGPropertyNode *metar_clouds = fgGetNode("/environment/metar/clouds", true);
888
889     for (i = 0, cloud = cv.begin(); i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
890         const char *coverage_string[5] = { "clear", "few", "scattered", "broken", "overcast" };
891         const double thickness_value[5] = { 0, 65, 600, 750, 1000 };
892
893         const char *coverage = "clear";
894         double elevation = -9999.0;
895         double thickness = 0.0;
896         const double span = 40000.0;
897
898         if (cloud != cloud_end) {
899             int c = cloud->getCoverage();
900             coverage = coverage_string[c];
901             elevation = cloud->getAltitude_ft() + station_elevation_ft;
902             thickness = thickness_value[c];
903             ++cloud;
904         }
905
906         SGPropertyNode *layer;
907         layer = metar_clouds->getChild("layer", i, true);
908         layer->setStringValue("coverage", coverage);
909         layer->setDoubleValue("elevation-ft", elevation);
910         layer->setDoubleValue("thickness-ft", thickness);
911         layer->setDoubleValue("span-m", span);
912     }
913     fgSetDouble("/environment/metar/rain-norm", m->getRain());
914     fgSetDouble("/environment/metar/hail-norm", m->getHail());
915     fgSetDouble("/environment/metar/snow-norm", m->getSnow());
916     fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
917 }
918
919
920 #if defined(ENABLE_THREADS)
921 void
922 FGMetarEnvironmentCtrl::thread_stop()
923 {
924     request_queue.push(NULL);   // ask thread to terminate
925     thread->join();
926 }
927
928 void
929 FGMetarEnvironmentCtrl::MetarThread::run()
930 {
931     while ( true )
932     {
933         FGAirport* apt = fetcher->request_queue.pop();
934         if (!apt)
935             return;
936         SG_LOG( SG_GENERAL, SG_INFO, "Thread: fetch metar data = " << apt->ident() );
937         FGMetarResult result = fetcher->fetch_data( apt );
938         fetcher->result_queue.push( result );
939     }
940 }
941 #endif // ENABLE_THREADS
942
943
944 // end of environment_ctrl.cxx