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