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