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