]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
8e3f908e785ea5f3fae3ebc8bb1279a1fd4a857f
[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_ctrl.hxx"
39
40 using std::sort;
41
42 class AirportWithMetar : public FGAirport::AirportFilter {
43 public:
44         virtual bool passAirport(FGAirport* aApt) const {
45                 return aApt->getMetar();
46         }
47 };
48
49 static AirportWithMetar airportWithMetarFilter;
50 \f
51 ////////////////////////////////////////////////////////////////////////
52 // Implementation of FGEnvironmentCtrl abstract base class.
53 ////////////////////////////////////////////////////////////////////////
54
55 FGEnvironmentCtrl::FGEnvironmentCtrl ()
56         : _environment(0),
57         _lon_deg(0),
58         _lat_deg(0),
59         _elev_ft(0)
60 {
61 }
62
63 FGEnvironmentCtrl::~FGEnvironmentCtrl ()
64 {
65 }
66
67 void
68 FGEnvironmentCtrl::setEnvironment (FGEnvironment * environment)
69 {
70         _environment = environment;
71 }
72
73 void
74 FGEnvironmentCtrl::setLongitudeDeg (double lon_deg)
75 {
76         _lon_deg = lon_deg;
77 }
78
79 void
80 FGEnvironmentCtrl::setLatitudeDeg (double lat_deg)
81 {
82         _lat_deg = lat_deg;
83 }
84
85 void
86 FGEnvironmentCtrl::setElevationFt (double elev_ft)
87 {
88         _elev_ft = elev_ft;
89 }
90
91 void
92 FGEnvironmentCtrl::setPosition (double lon_deg, double lat_deg, double elev_ft)
93 {
94         _lon_deg = lon_deg;
95         _lat_deg = lat_deg;
96         _elev_ft = elev_ft;
97 }
98
99
100 \f
101 ////////////////////////////////////////////////////////////////////////
102 // Implementation of FGInterpolateEnvironmentCtrl.
103 ////////////////////////////////////////////////////////////////////////
104
105
106 FGInterpolateEnvironmentCtrl::FGInterpolateEnvironmentCtrl ()
107 {
108         altitude_n = fgGetNode("/position/altitude-ft", true);
109         altitude_agl_n = fgGetNode("/position/altitude-agl-ft", true);
110         boundary_transition_n = fgGetNode("/environment/config/boundary-transition-ft", false );
111         boundary_n = fgGetNode("/environment/config/boundary", true );
112         aloft_n = fgGetNode("/environment/config/aloft", true );
113 }
114
115 FGInterpolateEnvironmentCtrl::~FGInterpolateEnvironmentCtrl ()
116 {
117         unsigned int i;
118         for (i = 0; i < _boundary_table.size(); i++)
119                 delete _boundary_table[i];
120         for (i = 0; i < _aloft_table.size(); i++)
121                 delete _aloft_table[i];
122 }
123
124
125
126 void
127 FGInterpolateEnvironmentCtrl::init ()
128 {
129         read_table( boundary_n, _boundary_table);
130         read_table( aloft_n, _aloft_table);
131 }
132
133 void
134 FGInterpolateEnvironmentCtrl::reinit ()
135 {
136         init();
137 }
138
139 void
140 FGInterpolateEnvironmentCtrl::read_table (const SGPropertyNode * node, vector<bucket *> &table)
141 {
142         double last_altitude_ft = 0.0;
143         double sort_required = false;
144         int i;
145
146         for (i = 0; i < node->nChildren(); i++) {
147                 const SGPropertyNode * child = node->getChild(i);
148                 if ( strcmp(child->getName(), "entry") == 0
149                  && child->getStringValue("elevation-ft", "")[0] != '\0'
150                  && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
151         {
152                         bucket * b;
153                         if( i < table.size() ) {
154                                 // recycle existing bucket
155                                 b = table[i];
156                         } else {
157                                 // more nodes than buckets in table, add a new one
158                                 b = new bucket;
159                                 table.push_back(b);
160                         }
161                         if (i > 0)
162                                 b->environment.copy(table[i-1]->environment);
163                         b->environment.read(child);
164                         b->altitude_ft = b->environment.get_elevation_ft();
165
166                         // check, if altitudes are in ascending order
167                         if( b->altitude_ft < last_altitude_ft )
168                                 sort_required = true;
169                         last_altitude_ft = b->altitude_ft;
170                 }
171         }
172         // remove leftover buckets
173         vector<bucket*>::iterator it = table.begin() + i;
174         while( it != table.end() )
175                 table.erase( it );
176
177         if( sort_required )
178                 sort(table.begin(), table.end(), bucket::lessThan);
179 }
180
181 void
182 FGInterpolateEnvironmentCtrl::update (double delta_time_sec)
183 {
184         double altitude_ft = altitude_n->getDoubleValue();
185         double altitude_agl_ft = altitude_agl_n->getDoubleValue();
186         double boundary_transition = 
187                 boundary_transition_n == NULL ? 500 : boundary_transition_n->getDoubleValue();
188
189         int length = _boundary_table.size();
190
191         if (length > 0) {
192                                                                 // boundary table
193                 double boundary_limit = _boundary_table[length-1]->altitude_ft;
194                 if (boundary_limit >= altitude_agl_ft) {
195                         do_interpolate(_boundary_table, altitude_agl_ft, _environment);
196                         return;
197                 } else if ((boundary_limit + boundary_transition) >= altitude_agl_ft) {
198                         //TODO: this is 500ft above the top altitude of boundary layer
199                         //shouldn't this be +/-250 ft off of the top altitude?
200                                                                 // both tables
201                         do_interpolate(_boundary_table, altitude_agl_ft, &env1);
202                         do_interpolate(_aloft_table, altitude_ft, &env2);
203                         double fraction =
204                                 (altitude_agl_ft - boundary_limit) / boundary_transition;
205                         interpolate(&env1, &env2, fraction, _environment);
206                         return;
207                 }
208         }
209                                                                 // aloft table
210         do_interpolate(_aloft_table, altitude_ft, _environment);
211 }
212
213 void
214 FGInterpolateEnvironmentCtrl::do_interpolate (vector<bucket *> &table, double altitude_ft, FGEnvironment * environment)
215 {
216         int length = table.size();
217         if (length == 0)
218                 return;
219
220                                                                 // Boundary conditions
221         if ((length == 1) || (table[0]->altitude_ft >= altitude_ft)) {
222                 environment->copy(table[0]->environment);
223                 return;
224         } else if (table[length-1]->altitude_ft <= altitude_ft) {
225                 environment->copy(table[length-1]->environment);
226                 return;
227         }
228                                                                 // Search the interpolation table
229         for (int i = 0; i < length - 1; i++) {
230                 if ((i == length - 1) || (table[i]->altitude_ft <= altitude_ft)) {
231                                 FGEnvironment * env1 = &(table[i]->environment);
232                                 FGEnvironment * env2 = &(table[i+1]->environment);
233                                 double fraction;
234                                 if (table[i]->altitude_ft == table[i+1]->altitude_ft)
235                                         fraction = 1.0;
236                                 else 
237                                         fraction =
238                                                 ((altitude_ft - table[i]->altitude_ft) /
239                                                  (table[i+1]->altitude_ft - table[i]->altitude_ft));
240                                 interpolate(env1, env2, fraction, environment);
241
242                                 return;
243                 }
244         }
245 }
246
247 bool
248 FGInterpolateEnvironmentCtrl::bucket::operator< (const bucket &b) const
249 {
250         return (altitude_ft < b.altitude_ft);
251 }
252
253 bool
254 FGInterpolateEnvironmentCtrl::bucket::lessThan(bucket *a, bucket *b)
255 {
256         return (a->altitude_ft) < (b->altitude_ft);
257 }
258
259 \f
260 ////////////////////////////////////////////////////////////////////////
261 // Implementation of FGMetarCtrl.
262 ////////////////////////////////////////////////////////////////////////
263
264 FGMetarCtrl::FGMetarCtrl( SGSubsystem * environmentCtrl )
265         : _environmentCtrl(environmentCtrl),
266         station_elevation_ft(0.0),
267         metar_valid(false),
268         setup_winds_aloft(true),
269         wind_interpolation_required(true),
270         // Interpolation constant definitions.
271         EnvironmentUpdatePeriodSec( 0.2 ),
272         MaxWindChangeKtsSec( 0.2 ),
273         MaxVisChangePercentSec( 0.05 ),
274         MaxPressureChangeInHgSec( 0.0033 ),
275         MaxCloudAltitudeChangeFtSec( 20.0 ),
276         MaxCloudThicknessChangeFtSec( 50.0 ),
277         MaxCloudInterpolationHeightFt( 5000.0 ),
278         MaxCloudInterpolationDeltaFt( 4000.0 )
279 {
280         windModulator = new FGBasicWindModulator();
281
282         metar_base_n = fgGetNode( "/environment/metar", true );
283         station_id_n = metar_base_n->getNode("station-id", true );
284         station_elevation_n = metar_base_n->getNode("station-elevation-ft", true );
285         min_visibility_n = metar_base_n->getNode("min-visibility-m", true );
286         max_visibility_n = metar_base_n->getNode("max-visibility-m", true );
287         base_wind_range_from_n = metar_base_n->getNode("base-wind-range-from", true );
288         base_wind_range_to_n = metar_base_n->getNode("base-wind-range-to", true );
289         base_wind_speed_n = metar_base_n->getNode("base-wind-speed-kt", true );
290         base_wind_dir_n = metar_base_n->getNode("base-wind-dir-deg", true );
291         gust_wind_speed_n = metar_base_n->getNode("gust-wind-speed-kt", true );
292         temperature_n = metar_base_n->getNode("temperature-degc", true );
293         dewpoint_n = metar_base_n->getNode("dewpoint-degc", true );
294         humidity_n = metar_base_n->getNode("rel-humidity-norm", true );
295         pressure_n = metar_base_n->getNode("pressure-inhg", true );
296         clouds_n = metar_base_n->getNode("clouds", true );
297         rain_n = metar_base_n->getNode("rain-norm", true );
298         hail_n = metar_base_n->getNode("hail-norm", true );
299         snow_n = metar_base_n->getNode("snow-norm", true );
300         snow_cover_n = metar_base_n->getNode("snow-cover", true );
301         ground_elevation_n = fgGetNode( "/position/ground-elev-m", true );
302         longitude_n = fgGetNode( "/position/longitude-deg", true );
303         latitude_n = fgGetNode( "/position/latitude-deg", true );
304         environment_clouds_n = fgGetNode("/environment/clouds");
305
306         boundary_wind_speed_n = fgGetNode("/environment/config/boundary/entry/wind-speed-kt");
307         boundary_wind_from_heading_n = fgGetNode("/environment/config/boundary/entry/wind-from-heading-deg");
308         boundary_visibility_n = fgGetNode("/environment/config/boundary/entry/visibility-m");
309         boundary_sea_level_pressure_n = fgGetNode("/environment/config/boundary/entry/pressure-sea-level-inhg");
310 }
311
312 FGMetarCtrl::~FGMetarCtrl ()
313 {
314 }
315
316 void FGMetarCtrl::bind ()
317 {
318         fgTie("/environment/metar/valid", this, &FGMetarCtrl::get_valid );
319         fgTie("/environment/params/metar-updates-environment", this, &FGMetarCtrl::get_enabled, &FGMetarCtrl::set_enabled );
320         fgTie("/environment/params/metar-updates-winds-aloft", this, &FGMetarCtrl::get_setup_winds_aloft, &FGMetarCtrl::set_setup_winds_aloft );
321 }
322
323 void FGMetarCtrl::unbind ()
324 {
325         fgUntie("/environment/metar/valid");
326         fgUntie("/environment/params/metar-updates-environment");
327         fgUntie("/environment/params/metar-updates-winds-aloft");
328 }
329
330 // use a "command" to set station temp at station elevation
331 static void set_temp_at_altitude( float temp_degc, float altitude_ft ) {
332         SGPropertyNode args;
333         SGPropertyNode *node = args.getNode("temp-degc", 0, true);
334         node->setFloatValue( temp_degc );
335         node = args.getNode("altitude-ft", 0, true);
336         node->setFloatValue( altitude_ft );
337         globals->get_commands()->execute("set-outside-air-temp-degc", &args);
338 }
339
340 static void set_dewpoint_at_altitude( float dewpoint_degc, float altitude_ft ) {
341         SGPropertyNode args;
342         SGPropertyNode *node = args.getNode("dewpoint-degc", 0, true);
343         node->setFloatValue( dewpoint_degc );
344         node = args.getNode("altitude-ft", 0, true);
345         node->setFloatValue( altitude_ft );
346         globals->get_commands()->execute("set-dewpoint-temp-degc", &args);
347 }
348
349 /*
350  Setup the wind nodes for a branch in the /environment/config/<branchName>/entry nodes
351
352  Output properties:
353  wind-from-heading-deg
354  wind-speed-kt
355  turbulence/magnitude-norm
356
357  Input properties:
358  wind-heading-change-deg       how many degrees does the wind direction change at this level
359  wind-speed-change-rel         relative change of wind speed at this level 
360  turbulence/factor             factor for the calculated turbulence magnitude at this level
361  */
362 static void setupWindBranch( string branchName, double dir, double speed, double gust )
363 {
364         SGPropertyNode_ptr branch = fgGetNode("/environment/config", true)->getNode(branchName,true);
365         vector<SGPropertyNode_ptr> entries = branch->getChildren("entry");
366         for ( vector<SGPropertyNode_ptr>::iterator it = entries.begin(); it != entries.end(); it++) {
367
368                 // change wind direction as configured
369                 double layer_dir = dir + (*it)->getDoubleValue("wind-heading-change-deg", 0.0 );
370                 if( layer_dir >= 360.0 ) layer_dir -= 360.0;
371                 if( layer_dir < 0.0 ) layer_dir += 360.0;
372                 (*it)->setDoubleValue("wind-from-heading-deg", layer_dir);
373
374                 double layer_speed = speed*(1 + (*it)->getDoubleValue("wind-speed-change-rel", 0.0 ));
375                 (*it)->setDoubleValue("wind-speed-kt", layer_speed );
376
377                 // add some turbulence
378                 SGPropertyNode_ptr turbulence = (*it)->getNode("turbulence",true);
379
380                 double turbulence_norm = speed/50;
381                 if( gust > speed ) {
382                         turbulence_norm += (gust-speed)/25;
383                 }
384                 if( turbulence_norm > 1.0 ) turbulence_norm = 1.0;
385
386                 turbulence_norm *= turbulence->getDoubleValue("factor", 0.0 );
387                 turbulence->setDoubleValue( "magnitude-norm", turbulence_norm );
388         }
389 }
390
391 static void setupWind( bool setup_boundary, bool setup_aloft, double dir, double speed, double gust )
392 {
393         if( setup_boundary )
394                 setupWindBranch( "boundary", dir, speed, gust );
395
396         if( setup_aloft )
397                 setupWindBranch( "aloft", dir, speed, gust );
398 }
399
400 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
401 {
402         double dval = EnvironmentUpdatePeriodSec * dt;
403
404         if (fabs(currentval - requiredval) < dval) return requiredval;
405         if (currentval < requiredval) return (currentval + dval);
406         if (currentval > requiredval) return (currentval - dval);
407         return requiredval;
408 }
409
410 void
411 FGMetarCtrl::init ()
412 {
413         first_update = true;
414         wind_interpolation_required = true;
415 }
416
417 void
418 FGMetarCtrl::reinit ()
419 {
420         init();
421 }
422
423 static inline double convert_to_360( double d )
424 {
425         if( d < 0.0 ) return d + 360.0;
426         if( d >= 360.0 ) return d - 360.0;
427         return d;
428 }
429
430 static inline double convert_to_180( double d )
431 {
432         return d > 180.0 ? d - 360.0 : d;
433 }
434
435 void
436 FGMetarCtrl::update(double dt)
437 {
438         if( dt <= 0 || !metar_valid ||!enabled)
439                 return;
440
441         windModulator->update(dt);
442         // Interpolate the current configuration closer to the actual METAR
443
444         bool reinit_required = false;
445         bool layer_rebuild_required = false;
446
447         if (first_update) {
448                 double dir = base_wind_dir_n->getDoubleValue();
449                 double speed = base_wind_speed_n->getDoubleValue();
450                 double gust = gust_wind_speed_n->getDoubleValue();
451                 setupWind(true, setup_winds_aloft, dir, speed, gust);
452
453                 double metarvis = min_visibility_n->getDoubleValue();
454                 fgDefaultWeatherValue("visibility-m", metarvis);
455
456                 double metarpressure = pressure_n->getDoubleValue();
457                 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
458
459                 // We haven't already loaded a METAR, so apply it immediately.
460                 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
461                 vector<SGPropertyNode_ptr>::const_iterator layer;
462                 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
463
464                 int i;
465                 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
466                         SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
467
468                         target->setStringValue("coverage",
469                                         (*layer)->getStringValue("coverage", "clear"));
470                         target->setDoubleValue("elevation-ft",
471                                         (*layer)->getDoubleValue("elevation-ft"));
472                         target->setDoubleValue("thickness-ft",
473                                         (*layer)->getDoubleValue("thickness-ft"));
474                         target->setDoubleValue("span-m", 40000.0);
475                 }
476
477                 first_update = false;
478                 reinit_required = true;
479                 layer_rebuild_required = true;
480
481         } else {
482                 if( wind_interpolation_required ) {
483                         // Generate interpolated values between the METAR and the current
484                         // configuration.
485
486                         // Pick up the METAR wind values and convert them into a vector.
487                         double metar[2];
488                         double metar_speed = base_wind_speed_n->getDoubleValue();
489                         double metar_heading = base_wind_dir_n->getDoubleValue();
490
491                         metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
492                         metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
493
494                         // Convert the current wind values and convert them into a vector
495                         double current[2];
496                         double speed = boundary_wind_speed_n->getDoubleValue();
497                         double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
498
499                         current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
500                         current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
501
502                         // Determine the maximum component-wise value that the wind can change.
503                         // First we determine the fraction in the X and Y component, then
504                         // factor by the maximum wind change.
505                         double x = fabs(current[0] - metar[0]);
506                         double y = fabs(current[1] - metar[1]);
507
508                         // only interpolate if we have a difference
509                         if (x + y > 0.01 ) {
510                                 double dx = x / (x + y);
511                                 double dy = 1 - dx;
512
513                                 double maxdx = dx * MaxWindChangeKtsSec;
514                                 double maxdy = dy * MaxWindChangeKtsSec;
515
516                                 // Interpolate each component separately.
517                                 current[0] = interpolate_val(current[0], metar[0], maxdx);
518                                 current[1] = interpolate_val(current[1], metar[1], maxdy);
519
520                                 // Now convert back to polar coordinates.
521                                 if ((current[0] == 0.0) && (current[1] == 0.0)) {
522                                         // Special case where there is no wind (otherwise atan2 barfs)
523                                         speed = 0.0;
524                                 } else {
525                                         // Some real wind to convert back from. Work out the speed
526                                         // and direction value in degrees.
527                                         speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
528                                         dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
529
530                                         // Normalize the direction.
531                                         if (dir_from < 0.0)
532                                                 dir_from += 360.0;
533
534                                         SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
535                                 }
536                                 double gust = gust_wind_speed_n->getDoubleValue();
537                                 setupWind(true, setup_winds_aloft, dir_from, speed, gust);
538                                 reinit_required = true;
539                         } else { 
540                                 wind_interpolation_required = false;
541                         }
542                 } else { // if(wind_interpolation_required)
543                         // interpolation of wind vector is finished, apply wind
544                         // variations and gusts for the boundary layer only
545
546                         // start with the main wind direction
547                         double wind_dir = base_wind_dir_n->getDoubleValue();
548                         double min = convert_to_180(base_wind_range_from_n->getDoubleValue());
549                         double max = convert_to_180(base_wind_range_to_n->getDoubleValue());
550                         if( max > min ) {
551                                 // if variable winds configured, modulate the wind direction
552                                 double f = windModulator->get_direction_offset_norm();
553                                 wind_dir = min+(max-min)*f;
554                                 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
555                                 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
556                         }
557                         
558                         // start with main wind speed
559                         double wind_speed = base_wind_speed_n->getDoubleValue();
560                         max = gust_wind_speed_n->getDoubleValue();
561                         if( max > wind_speed ) {
562                                 // if gusts are configured, modulate wind magnitude
563                                 double f = windModulator->get_magnitude_factor_norm();
564                                 wind_speed = wind_speed+(max-wind_speed)*f;
565                                 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
566                         }
567                         setupWind(true, false, wind_dir, wind_speed, max);
568                         reinit_required = true;
569                 }
570
571                 // Now handle the visibility. We convert both visibility values
572                 // to X-values, then interpolate from there, then back to real values.
573                 // The length_scale is fixed to 1000m, so the visibility changes by
574                 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
575                 // whichever is more.
576                 double vis = boundary_visibility_n->getDoubleValue();;
577                 double metarvis = min_visibility_n->getDoubleValue();
578                 if( vis != metarvis ) {
579                         double currentxval = log(1000.0 + vis);
580                         double metarxval = log(1000.0 + metarvis);
581
582                         currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
583
584                         // Now convert back from an X-value to a straightforward visibility.
585                         vis = exp(currentxval) - 1000.0;
586                         fgDefaultWeatherValue("visibility-m", vis);
587                         reinit_required = true;
588                 }
589
590                 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
591                 double metarpressure = pressure_n->getDoubleValue();
592                 if( pressure != metarpressure ) {
593                         pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
594                         fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
595                         reinit_required = true;
596                 }
597
598                 // Set the cloud layers by interpolating over the METAR versions.
599                 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
600                 vector<SGPropertyNode_ptr>::const_iterator layer;
601                 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
602
603                 double aircraft_alt = fgGetDouble("/position/altitude-ft");
604                 int i;
605
606                 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
607                         SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
608
609                         // In the case of clouds, we want to avoid writing if nothing has
610                         // changed, as these properties are tied to the renderer and will
611                         // cause the clouds to be updated, reseting the texture locations.
612
613                         // We don't interpolate the coverage values as no-matter how we
614                         // do it, it will be quite a sudden change of texture. Better to
615                         // have a single change than four or five.
616                         const char *coverage = (*layer)->getStringValue("coverage", "clear");
617                         SGPropertyNode *cov = target->getNode("coverage", true);
618                         if (strcmp(cov->getStringValue(), coverage) != 0) {
619                                 cov->setStringValue(coverage);
620                                 layer_rebuild_required = true;
621                         }
622
623                         double required_alt = (*layer)->getDoubleValue("elevation-ft");
624                         double current_alt = target->getDoubleValue("elevation-ft");
625                         double required_thickness = (*layer)->getDoubleValue("thickness-ft");
626                         SGPropertyNode *thickness = target->getNode("thickness-ft", true);
627
628                         if (current_alt < -9000 || required_alt < -9000 ||
629                                 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
630                                 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
631                                 // We don't interpolate any layers that are
632                                 //  - too far above us to be visible
633                                 //  - too far below us to be visible
634                                 //  - with too large a difference to make interpolation sensible
635                                 //  - to or from -9999 (used as a placeholder)
636                                 //  - any values that are too high above us,
637                                 if (current_alt != required_alt)
638                                         target->setDoubleValue("elevation-ft", required_alt);
639
640                                 if (thickness->getDoubleValue() != required_thickness)
641                                         thickness->setDoubleValue(required_thickness);
642
643                         } else {
644                                 // Interpolate the other values in the usual way
645                                 if (current_alt != required_alt) {
646                                         current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
647                                         target->setDoubleValue("elevation-ft", current_alt);
648                                 }
649
650                                 double current_thickness = thickness->getDoubleValue();
651
652                                 if (current_thickness != required_thickness) {
653                                         current_thickness = interpolate_val(current_thickness,
654                                                                                                  required_thickness,
655                                                                                                  MaxCloudThicknessChangeFtSec);
656                                         thickness->setDoubleValue(current_thickness);
657                                 }
658                         }
659                 }
660         }
661
662         set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
663         set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
664         //TODO: check if temperature/dewpoint have changed. This requires reinit.
665
666         // Force an update of the 3D clouds
667         if( layer_rebuild_required )
668                 fgSetInt("/environment/rebuild-layers", 1 );
669
670         // Reinitializing of the environment controller required
671         if( reinit_required )
672                 _environmentCtrl->reinit();
673 }
674
675 const char * FGMetarCtrl::get_metar(void) const
676 {
677         return metar.c_str();
678 }
679
680 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
681 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
682
683 void FGMetarCtrl::set_metar( const char * metar_string )
684 {
685         int i;
686
687         metar = metar_string;
688
689         SGSharedPtr<FGMetar> m;
690         try {
691                 m = new FGMetar( metar_string );
692         }
693         catch( sg_io_exception ) {
694                 fprintf( stderr, "can't get metar: %s\n", metar_string );
695                 metar_valid = false;
696                 return;
697         }
698
699         wind_interpolation_required = true;
700
701         min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
702         max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
703
704         const SGMetarVisibility *dirvis = m->getDirVisibility();
705         for (i = 0; i < 8; i++, dirvis++) {
706                 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
707                 double v = dirvis->getVisibility_m();
708
709                 vis->setDoubleValue("min-m", v);
710                 vis->setDoubleValue("max-m", v);
711         }
712
713         base_wind_dir_n->setIntValue( m->getWindDir() );
714         base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
715         base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
716         base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
717         gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
718         temperature_n->setDoubleValue( m->getTemperature_C() );
719         dewpoint_n->setDoubleValue( m->getDewpoint_C() );
720         humidity_n->setDoubleValue( m->getRelHumidity() );
721         pressure_n->setDoubleValue( m->getPressure_inHg() );
722
723
724         // get station elevation to compute cloud base
725         double station_elevation_ft = 0;
726         {
727                 // 1. check the id given in the metar
728                 FGAirport* a = FGAirport::findByIdent(m->getId());
729
730                 // 2. if unknown, find closest airport with metar to current position
731                 if( a == NULL ) {
732                         SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
733                         a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
734                 }
735
736                 // 3. otherwise use ground elevation
737                 if( a != NULL ) {
738                         station_elevation_ft = a->getElevation();
739                         station_id_n->setStringValue( a->ident());
740                 } else {
741                         station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
742                         station_id_n->setStringValue( m->getId());
743                 }
744         }
745
746         station_elevation_n->setDoubleValue( station_elevation_ft );
747
748         vector<SGMetarCloud> cv = m->getClouds();
749         vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
750
751         int layer_cnt = environment_clouds_n->getChildren("layer").size();
752         for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
753
754
755                 const char *coverage = "clear";
756                 double elevation = -9999.0;
757                 double thickness = 0.0;
758                 const double span = 40000.0;
759
760                 if (cloud != cloud_end) {
761                         int c = cloud->getCoverage();
762                         coverage = coverage_string[c];
763                         elevation = cloud->getAltitude_ft() + station_elevation_ft;
764                         thickness = thickness_value[c];
765                         ++cloud;
766                 }
767
768                 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
769
770                 // if the coverage has changed, a rebuild of the layer is needed
771                 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
772                         layer->setStringValue("coverage", coverage);
773                 }
774                 layer->setDoubleValue("elevation-ft", elevation);
775                 layer->setDoubleValue("thickness-ft", thickness);
776                 layer->setDoubleValue("span-m", span);
777         }
778
779         rain_n->setDoubleValue(m->getRain());
780         hail_n->setDoubleValue(m->getHail());
781         snow_n->setDoubleValue(m->getSnow());
782         snow_cover_n->setBoolValue(m->getSnowCover());
783         metar_valid = true;
784 }
785
786 #if defined(ENABLE_THREADS)
787 /**
788  * This class represents the thread of execution responsible for
789  * fetching the metar data.
790  */
791 class MetarThread : public OpenThreads::Thread {
792 public:
793         MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
794         ~MetarThread() {}
795
796         /**
797          * Fetche the metar data from the NOAA.
798          */
799         void run();
800
801 private:
802         FGMetarFetcher * metar_fetcher;
803 };
804
805 void MetarThread::run()
806 {
807         for( ;; ) {
808                 string airport_id = metar_fetcher->request_queue.pop();
809
810                 if( airport_id.size() == 0 )
811                         break;
812
813                 if( metar_fetcher->_error_count > 3 ) {
814                         SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
815                         break;
816                 }
817
818                         metar_fetcher->fetch( airport_id );
819         }
820 }
821 #endif
822
823 FGMetarFetcher::FGMetarFetcher() : 
824 #if defined(ENABLE_THREADS)
825         metar_thread(NULL),
826 #endif
827         fetch_timer(0.0),
828         search_timer(0.0),
829         error_timer(0.0),
830         _stale_count(0),
831         _error_count(0)
832 {
833         longitude_n = fgGetNode( "/position/longitude-deg", true );
834         latitude_n  = fgGetNode( "/position/latitude-deg", true );
835         enable_n    = fgGetNode( "/environment/params/real-world-weather-fetch", true );
836
837         proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
838         proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
839         proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
840         max_age_n    = fgGetNode("/environment/params/metar-max-age-min", true);
841
842         output_n         = fgGetNode("/environment/metar/data", true );
843 #if defined(ENABLE_THREADS)
844         metar_thread = new MetarThread(this);
845 // FIXME: do we really need setProcessorAffinity()?
846 //      metar_thread->setProcessorAffinity(1);
847         metar_thread->start();
848 #endif // ENABLE_THREADS
849 }
850
851
852 FGMetarFetcher::~FGMetarFetcher()
853 {
854 #if defined(ENABLE_THREADS)
855         request_queue.push("");
856         metar_thread->join();
857         delete metar_thread;
858 #endif // ENABLE_THREADS
859 }
860
861 void FGMetarFetcher::init ()
862 {
863         fetch_timer = 0.0;
864         search_timer = 0.0;
865         error_timer = 0.0;
866         _stale_count = 0;
867         _error_count = 0;
868         current_airport_id.clear();
869 }
870
871 void FGMetarFetcher::reinit ()
872 {
873         init();
874 }
875
876 /* search for closest airport with metar every xx seconds */
877 static const int search_interval_sec = 60;
878
879 /* fetch metar for airport, even if airport has not changed every xx seconds */
880 static const int fetch_interval_sec = 900;
881
882 /* reset error counter after xxx seconds */
883 static const int error_timer_sec = 3;
884
885 void FGMetarFetcher::update (double delta_time_sec)
886 {
887         fetch_timer -= delta_time_sec;
888         search_timer -= delta_time_sec;
889         error_timer -= delta_time_sec;
890
891         if( error_timer <= 0.0 ) {
892                 error_timer = error_timer_sec;
893                 _error_count = 0;
894         }
895
896         if( enable_n->getBoolValue() == false ) 
897                 return;
898
899         FGAirport * a = NULL;
900
901         if( search_timer <= 0.0 ) {
902                 // search timer expired, search closest airport with metar
903                 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
904                 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
905                 search_timer = search_interval_sec;
906         }
907
908         if( a == NULL )
909                 return;
910
911
912         if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
913                 // fetch timer expired or airport has changed, schedule a fetch
914                 current_airport_id = a->ident();
915                 fetch_timer = fetch_interval_sec;
916 #if defined(ENABLE_THREADS)
917                 // push this airport id into the queue for the worker thread
918                 request_queue.push( current_airport_id );
919 #else
920                 // if there is no worker thread, immediately fetch the data
921                 fetch( current_airport_id );
922 #endif
923         }
924 }
925
926 void FGMetarFetcher::fetch( const string & id )
927 {
928         SGSharedPtr<FGMetar> result = NULL;
929
930         // fetch current metar data
931         try {
932                 string host = proxy_host_n->getStringValue();
933                 string auth = proxy_auth_n->getStringValue();
934                 string port = proxy_port_n->getStringValue();
935
936                 result = new FGMetar( id, host, port, auth);
937
938                 long max_age = max_age_n->getLongValue();
939                 long age = result->getAge_min();
940
941                 if (max_age && age > max_age) {
942                         SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
943                         if (++_stale_count > 10) {
944                                 _error_count = 1000;
945                                 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
946                         }
947                 } else {
948                         _stale_count = 0;
949                         }
950
951         } catch (const sg_io_exception& e) {
952                 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
953                 result = NULL;
954         }
955
956         // write the metar to the property node, the rest is done by the methods tied to this property
957         // don't write the metar data, if real-weather-fetch has been disabled in the meantime
958         if( result != NULL && enable_n->getBoolValue() == true ) 
959                 output_n->setStringValue( result->getData() );
960 }
961
962 // end of environment_ctrl.cxx
963