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