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