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