]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment_ctrl.cxx
At startup, fetch METAR for closest airport that has one, which is not necessarily...
[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         : _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_aloft, double dir, double speed, double gust )
395 {
396         setupWindBranch( "boundary", dir, speed, gust );
397         if( setup_aloft )
398                 setupWindBranch( "aloft", dir, speed, gust );
399 }
400
401 double FGMetarCtrl::interpolate_val(double currentval, double requiredval, double dt)
402 {
403         double dval = EnvironmentUpdatePeriodSec * dt;
404
405         if (fabs(currentval - requiredval) < dval) return requiredval;
406         if (currentval < requiredval) return (currentval + dval);
407         if (currentval > requiredval) return (currentval - dval);
408         return requiredval;
409 }
410
411 void
412 FGMetarCtrl::init ()
413 {
414         first_update = true;
415         wind_interpolation_required = true;
416 }
417
418 void
419 FGMetarCtrl::reinit ()
420 {
421         init();
422 }
423
424 static inline double convert_to_360( double d )
425 {
426         if( d < 0.0 ) return d + 360.0;
427         if( d >= 360.0 ) return d - 360.0;
428         return d;
429 }
430
431 static inline double convert_to_180( double d )
432 {
433         return d > 180.0 ? d - 360.0 : d;
434 }
435
436 void
437 FGMetarCtrl::update(double dt)
438 {
439         if( dt <= 0 || !metar_valid ||!enabled)
440                 return;
441
442         windModulator->update(dt);
443         // Interpolate the current configuration closer to the actual METAR
444
445         bool reinit_required = false;
446         bool layer_rebuild_required = false;
447
448         if (first_update) {
449                 double dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
450                 double speed = base_wind_speed_n->getDoubleValue();
451                 double gust = gust_wind_speed_n->getDoubleValue();
452                 setupWind(setup_winds_aloft, dir, speed, gust);
453
454                 double metarvis = min_visibility_n->getDoubleValue();
455                 fgDefaultWeatherValue("visibility-m", metarvis);
456
457                 double metarpressure = pressure_n->getDoubleValue();
458                 fgDefaultWeatherValue("pressure-sea-level-inhg", metarpressure);
459
460                 // We haven't already loaded a METAR, so apply it immediately.
461                 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
462                 vector<SGPropertyNode_ptr>::const_iterator layer;
463                 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
464
465                 int i;
466                 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
467                         SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
468
469                         target->setStringValue("coverage",
470                                         (*layer)->getStringValue("coverage", "clear"));
471                         target->setDoubleValue("elevation-ft",
472                                         (*layer)->getDoubleValue("elevation-ft"));
473                         target->setDoubleValue("thickness-ft",
474                                         (*layer)->getDoubleValue("thickness-ft"));
475                         target->setDoubleValue("span-m", 40000.0);
476                 }
477
478                 first_update = false;
479                 reinit_required = true;
480                 layer_rebuild_required = true;
481
482         } else {
483                 if( wind_interpolation_required ) {
484                         // Generate interpolated values between the METAR and the current
485                         // configuration.
486
487                         // Pick up the METAR wind values and convert them into a vector.
488                         double metar[2];
489                         double metar_speed = base_wind_speed_n->getDoubleValue();
490                         double metar_heading = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
491
492                         metar[0] = metar_speed * sin(metar_heading * SG_DEGREES_TO_RADIANS );
493                         metar[1] = metar_speed * cos(metar_heading * SG_DEGREES_TO_RADIANS);
494
495                         // Convert the current wind values and convert them into a vector
496                         double current[2];
497                         double speed = boundary_wind_speed_n->getDoubleValue();
498                         double dir_from = boundary_wind_from_heading_n->getDoubleValue();;
499
500                         current[0] = speed * sin(dir_from * SG_DEGREES_TO_RADIANS );
501                         current[1] = speed * cos(dir_from * SG_DEGREES_TO_RADIANS );
502
503                         // Determine the maximum component-wise value that the wind can change.
504                         // First we determine the fraction in the X and Y component, then
505                         // factor by the maximum wind change.
506                         double x = fabs(current[0] - metar[0]);
507                         double y = fabs(current[1] - metar[1]);
508
509                         // only interpolate if we have a difference
510                         if (x + y > 0.01 ) {
511                                 double dx = x / (x + y);
512                                 double dy = 1 - dx;
513
514                                 double maxdx = dx * MaxWindChangeKtsSec;
515                                 double maxdy = dy * MaxWindChangeKtsSec;
516
517                                 // Interpolate each component separately.
518                                 current[0] = interpolate_val(current[0], metar[0], maxdx);
519                                 current[1] = interpolate_val(current[1], metar[1], maxdy);
520
521                                 // Now convert back to polar coordinates.
522                                 if ((fabs(current[0]) > 0.1) || (fabs(current[1]) > 0.1)) {
523                                         // Some real wind to convert back from. Work out the speed
524                                         // and direction value in degrees.
525                                         speed = sqrt((current[0] * current[0]) + (current[1] * current[1]));
526                                         dir_from = (atan2(current[0], current[1]) * SG_RADIANS_TO_DEGREES );
527
528                                         // Normalize the direction.
529                                         if (dir_from < 0.0)
530                                                 dir_from += 360.0;
531
532                                         SG_LOG( SG_GENERAL, SG_DEBUG, "Wind : " << dir_from << "@" << speed);
533                                 } else {
534                                         // Special case where there is no wind (otherwise atan2 barfs)
535                                         speed = 0.0;
536                                 }
537                                 double gust = gust_wind_speed_n->getDoubleValue();
538                                 setupWind(setup_winds_aloft, dir_from, speed, gust);
539                                 reinit_required = true;
540                         } else { 
541                                 wind_interpolation_required = false;
542                         }
543                 } else { // if(wind_interpolation_required)
544                         // interpolation of wind vector is finished, apply wind
545                         // variations and gusts for the boundary layer only
546
547
548                         bool wind_modulated = false;
549
550                         // start with the main wind direction
551                         double wind_dir = base_wind_dir_n->getDoubleValue()+magnetic_variation_n->getDoubleValue();
552                         double min = convert_to_180(base_wind_range_from_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
553                         double max = convert_to_180(base_wind_range_to_n->getDoubleValue()+magnetic_variation_n->getDoubleValue());
554                         if( max > min ) {
555                                 // if variable winds configured, modulate the wind direction
556                                 double f = windModulator->get_direction_offset_norm();
557                                 wind_dir = min+(max-min)*f;
558                                 double old = convert_to_180(boundary_wind_from_heading_n->getDoubleValue());
559                                 wind_dir = convert_to_360(fgGetLowPass(old, wind_dir, dt ));
560                                 wind_modulated = true;
561                         }
562                         
563                         // start with main wind speed
564                         double wind_speed = base_wind_speed_n->getDoubleValue();
565                         max = gust_wind_speed_n->getDoubleValue();
566                         if( max > wind_speed ) {
567                                 // if gusts are configured, modulate wind magnitude
568                                 double f = windModulator->get_magnitude_factor_norm();
569                                 wind_speed = wind_speed+(max-wind_speed)*f;
570                                 wind_speed = fgGetLowPass(boundary_wind_speed_n->getDoubleValue(), wind_speed, dt );
571                                 wind_modulated = true;
572                         }
573                         if( wind_modulated ) {
574                                 setupWind(false, wind_dir, wind_speed, max);
575                                 reinit_required = true;
576                         }
577                 }
578
579                 // Now handle the visibility. We convert both visibility values
580                 // to X-values, then interpolate from there, then back to real values.
581                 // The length_scale is fixed to 1000m, so the visibility changes by
582                 // by MaxVisChangePercentSec or 1000m X MaxVisChangePercentSec,
583                 // whichever is more.
584                 double vis = boundary_visibility_n->getDoubleValue();;
585                 double metarvis = min_visibility_n->getDoubleValue();
586                 if( vis != metarvis ) {
587                         double currentxval = log(1000.0 + vis);
588                         double metarxval = log(1000.0 + metarvis);
589
590                         currentxval = interpolate_val(currentxval, metarxval, MaxVisChangePercentSec);
591
592                         // Now convert back from an X-value to a straightforward visibility.
593                         vis = exp(currentxval) - 1000.0;
594                         fgDefaultWeatherValue("visibility-m", vis);
595                         reinit_required = true;
596                 }
597
598                 double pressure = boundary_sea_level_pressure_n->getDoubleValue();
599                 double metarpressure = pressure_n->getDoubleValue();
600                 if( pressure != metarpressure ) {
601                         pressure = interpolate_val( pressure, metarpressure, MaxPressureChangeInHgSec );
602                         fgDefaultWeatherValue("pressure-sea-level-inhg", pressure);
603                         reinit_required = true;
604                 }
605
606                 // Set the cloud layers by interpolating over the METAR versions.
607                 vector<SGPropertyNode_ptr> layers = clouds_n->getChildren("layer");
608                 vector<SGPropertyNode_ptr>::const_iterator layer;
609                 vector<SGPropertyNode_ptr>::const_iterator layers_end = layers.end();
610
611                 double aircraft_alt = fgGetDouble("/position/altitude-ft");
612                 int i;
613
614                 for (i = 0, layer = layers.begin(); layer != layers_end; ++layer, i++) {
615                         SGPropertyNode *target = environment_clouds_n->getChild("layer", i, true);
616
617                         // In the case of clouds, we want to avoid writing if nothing has
618                         // changed, as these properties are tied to the renderer and will
619                         // cause the clouds to be updated, reseting the texture locations.
620
621                         // We don't interpolate the coverage values as no-matter how we
622                         // do it, it will be quite a sudden change of texture. Better to
623                         // have a single change than four or five.
624                         const char *coverage = (*layer)->getStringValue("coverage", "clear");
625                         SGPropertyNode *cov = target->getNode("coverage", true);
626                         if (strcmp(cov->getStringValue(), coverage) != 0) {
627                                 cov->setStringValue(coverage);
628                                 layer_rebuild_required = true;
629                         }
630
631                         double required_alt = (*layer)->getDoubleValue("elevation-ft");
632                         double current_alt = target->getDoubleValue("elevation-ft");
633                         double required_thickness = (*layer)->getDoubleValue("thickness-ft");
634                         SGPropertyNode *thickness = target->getNode("thickness-ft", true);
635
636                         if (current_alt < -9000 || required_alt < -9000 ||
637                                 fabs(aircraft_alt - required_alt) > MaxCloudInterpolationHeightFt ||
638                                 fabs(current_alt - required_alt) > MaxCloudInterpolationDeltaFt) {
639                                 // We don't interpolate any layers that are
640                                 //  - too far above us to be visible
641                                 //  - too far below us to be visible
642                                 //  - with too large a difference to make interpolation sensible
643                                 //  - to or from -9999 (used as a placeholder)
644                                 //  - any values that are too high above us,
645                                 if (current_alt != required_alt)
646                                         target->setDoubleValue("elevation-ft", required_alt);
647
648                                 if (thickness->getDoubleValue() != required_thickness)
649                                         thickness->setDoubleValue(required_thickness);
650
651                         } else {
652                                 // Interpolate the other values in the usual way
653                                 if (current_alt != required_alt) {
654                                         current_alt = interpolate_val(current_alt, required_alt, MaxCloudAltitudeChangeFtSec);
655                                         target->setDoubleValue("elevation-ft", current_alt);
656                                 }
657
658                                 double current_thickness = thickness->getDoubleValue();
659
660                                 if (current_thickness != required_thickness) {
661                                         current_thickness = interpolate_val(current_thickness,
662                                                                                                  required_thickness,
663                                                                                                  MaxCloudThicknessChangeFtSec);
664                                         thickness->setDoubleValue(current_thickness);
665                                 }
666                         }
667                 }
668         }
669
670         set_temp_at_altitude(temperature_n->getDoubleValue(), station_elevation_ft);
671         set_dewpoint_at_altitude(dewpoint_n->getDoubleValue(), station_elevation_ft);
672         //TODO: check if temperature/dewpoint have changed. This requires reinit.
673
674         // Force an update of the 3D clouds
675         if( layer_rebuild_required )
676                 fgSetInt("/environment/rebuild-layers", 1 );
677
678         // Reinitializing of the environment controller required
679         if( reinit_required )
680                 _environmentCtrl->reinit();
681 }
682
683 const char * FGMetarCtrl::get_metar(void) const
684 {
685         return metar.c_str();
686 }
687
688 static const char *coverage_string[] = { "clear", "few", "scattered", "broken", "overcast" };
689 static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
690
691 void FGMetarCtrl::set_metar( const char * metar_string )
692 {
693         int i;
694
695         metar = metar_string;
696
697         SGSharedPtr<FGMetar> m;
698         try {
699                 m = new FGMetar( metar_string );
700         }
701         catch( sg_io_exception ) {
702                 fprintf( stderr, "can't get metar: %s\n", metar_string );
703                 metar_valid = false;
704                 return;
705         }
706
707         wind_interpolation_required = true;
708
709         min_visibility_n->setDoubleValue( m->getMinVisibility().getVisibility_m() );
710         max_visibility_n->setDoubleValue( m->getMaxVisibility().getVisibility_m() );
711
712         const SGMetarVisibility *dirvis = m->getDirVisibility();
713         for (i = 0; i < 8; i++, dirvis++) {
714                 SGPropertyNode *vis = metar_base_n->getChild("visibility", i, true);
715                 double v = dirvis->getVisibility_m();
716
717                 vis->setDoubleValue("min-m", v);
718                 vis->setDoubleValue("max-m", v);
719         }
720
721         base_wind_dir_n->setIntValue( m->getWindDir() );
722         base_wind_range_from_n->setIntValue( m->getWindRangeFrom() );
723         base_wind_range_to_n->setIntValue( m->getWindRangeTo() );
724         base_wind_speed_n->setDoubleValue( m->getWindSpeed_kt() );
725         gust_wind_speed_n->setDoubleValue( m->getGustSpeed_kt() );
726         temperature_n->setDoubleValue( m->getTemperature_C() );
727         dewpoint_n->setDoubleValue( m->getDewpoint_C() );
728         humidity_n->setDoubleValue( m->getRelHumidity() );
729         pressure_n->setDoubleValue( m->getPressure_inHg() );
730
731
732         // get station elevation to compute cloud base
733         double station_elevation_ft = 0;
734         {
735                 // 1. check the id given in the metar
736                 FGAirport* a = FGAirport::findByIdent(m->getId());
737
738                 // 2. if unknown, find closest airport with metar to current position
739                 if( a == NULL ) {
740                         SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
741                         a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
742                 }
743
744                 // 3. otherwise use ground elevation
745                 if( a != NULL ) {
746                         station_elevation_ft = a->getElevation();
747                         station_id_n->setStringValue( a->ident());
748                 } else {
749                         station_elevation_ft = ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET;
750                         station_id_n->setStringValue( m->getId());
751                 }
752         }
753
754         station_elevation_n->setDoubleValue( station_elevation_ft );
755
756         vector<SGMetarCloud> cv = m->getClouds();
757         vector<SGMetarCloud>::const_iterator cloud, cloud_end = cv.end();
758
759         int layer_cnt = environment_clouds_n->getChildren("layer").size();
760         for (i = 0, cloud = cv.begin(); i < layer_cnt; i++) {
761
762
763                 const char *coverage = "clear";
764                 double elevation = -9999.0;
765                 double thickness = 0.0;
766                 const double span = 40000.0;
767
768                 if (cloud != cloud_end) {
769                         int c = cloud->getCoverage();
770                         coverage = coverage_string[c];
771                         elevation = cloud->getAltitude_ft() + station_elevation_ft;
772                         thickness = thickness_value[c];
773                         ++cloud;
774                 }
775
776                 SGPropertyNode *layer = clouds_n->getChild("layer", i, true );
777
778                 // if the coverage has changed, a rebuild of the layer is needed
779                 if( strcmp(layer->getStringValue("coverage"), coverage ) ) {
780                         layer->setStringValue("coverage", coverage);
781                 }
782                 layer->setDoubleValue("elevation-ft", elevation);
783                 layer->setDoubleValue("thickness-ft", thickness);
784                 layer->setDoubleValue("span-m", span);
785         }
786
787         rain_n->setDoubleValue(m->getRain());
788         hail_n->setDoubleValue(m->getHail());
789         snow_n->setDoubleValue(m->getSnow());
790         snow_cover_n->setBoolValue(m->getSnowCover());
791         metar_valid = true;
792 }
793
794 #if defined(ENABLE_THREADS)
795 /**
796  * This class represents the thread of execution responsible for
797  * fetching the metar data.
798  */
799 class MetarThread : public OpenThreads::Thread {
800 public:
801         MetarThread( FGMetarFetcher * f ) : metar_fetcher(f) {}
802         ~MetarThread() {}
803
804         /**
805          * Fetche the metar data from the NOAA.
806          */
807         void run();
808
809 private:
810         FGMetarFetcher * metar_fetcher;
811 };
812
813 void MetarThread::run()
814 {
815         for( ;; ) {
816                 string airport_id = metar_fetcher->request_queue.pop();
817
818                 if( airport_id.size() == 0 )
819                         break;
820
821                 if( metar_fetcher->_error_count > 3 ) {
822                         SG_LOG( SG_GENERAL, SG_WARN, "Too many erros fetching METAR, thread stopped permanently.");
823                         break;
824                 }
825
826                 metar_fetcher->fetch( airport_id );
827         }
828 }
829 #endif
830
831 FGMetarFetcher::FGMetarFetcher() : 
832 #if defined(ENABLE_THREADS)
833         metar_thread(NULL),
834 #endif
835         fetch_timer(0.0),
836         search_timer(0.0),
837         error_timer(0.0),
838         _stale_count(0),
839         _error_count(0),
840         enabled(false)
841 {
842         longitude_n = fgGetNode( "/position/longitude-deg", true );
843         latitude_n  = fgGetNode( "/position/latitude-deg", true );
844         enable_n    = fgGetNode( "/environment/params/real-world-weather-fetch", true );
845
846         proxy_host_n = fgGetNode("/sim/presets/proxy/host", true);
847         proxy_port_n = fgGetNode("/sim/presets/proxy/port", true);
848         proxy_auth_n = fgGetNode("/sim/presets/proxy/authentication", true);
849         max_age_n    = fgGetNode("/environment/params/metar-max-age-min", true);
850
851         output_n         = fgGetNode("/environment/metar/data", true );
852 #if defined(ENABLE_THREADS)
853         metar_thread = new MetarThread(this);
854 // FIXME: do we really need setProcessorAffinity()?
855 //      metar_thread->setProcessorAffinity(1);
856         metar_thread->start();
857 #endif // ENABLE_THREADS
858 }
859
860
861 FGMetarFetcher::~FGMetarFetcher()
862 {
863 #if defined(ENABLE_THREADS)
864         request_queue.push("");
865         metar_thread->join();
866         delete metar_thread;
867 #endif // ENABLE_THREADS
868 }
869
870 void FGMetarFetcher::init ()
871 {
872         fetch_timer = 0.0;
873         search_timer = 0.0;
874         error_timer = 0.0;
875         _stale_count = 0;
876         _error_count = 0;
877         current_airport_id.clear();
878         /* Torsten Dreyer:
879            hack to stop startup.nas complaining if metar arrives after nasal-dir-initialized
880            is fired. Immediately fetch and wait for the METAR before continuing. This gets the
881            /environment/metar/xxx properties filled before nasal-dir is initialized.
882            Maybe the runway selection should happen here to make startup.nas obsolete?
883         */
884         const char * startup_airport = fgGetString("/sim/startup/options/airport");
885         if( *startup_airport ) {
886                 FGAirport * a = FGAirport::getByIdent( startup_airport );
887                 if( a ) {
888                         SGGeod pos = SGGeod::fromDeg(a->getLongitude(), a->getLatitude());
889                         a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
890                         current_airport_id = a->getId();
891                         fetch( current_airport_id );
892                 }
893         }
894 }
895
896 void FGMetarFetcher::reinit ()
897 {
898         init();
899 }
900
901 /* search for closest airport with metar every xx seconds */
902 static const int search_interval_sec = 60;
903
904 /* fetch metar for airport, even if airport has not changed every xx seconds */
905 static const int fetch_interval_sec = 900;
906
907 /* reset error counter after xxx seconds */
908 static const int error_timer_sec = 3;
909
910 void FGMetarFetcher::update (double delta_time_sec)
911 {
912         fetch_timer -= delta_time_sec;
913         search_timer -= delta_time_sec;
914         error_timer -= delta_time_sec;
915
916         if( error_timer <= 0.0 ) {
917                 error_timer = error_timer_sec;
918                 _error_count = 0;
919         }
920
921         if( enable_n->getBoolValue() == false ) {
922                 enabled = false;
923                 return;
924         }
925
926         // we were just enabled, reset all timers to 
927         // trigger immediate metar fetch
928         if( !enabled ) {
929                 search_timer = 0.0;
930                 fetch_timer = 0.0;
931                 error_timer = error_timer_sec;
932                 enabled = true;
933         }
934
935         FGAirport * a = NULL;
936
937         if( search_timer <= 0.0 ) {
938                 // search timer expired, search closest airport with metar
939                 SGGeod pos = SGGeod::fromDeg(longitude_n->getDoubleValue(), latitude_n->getDoubleValue());
940                 a = FGAirport::findClosest(pos, 10000.0, &airportWithMetarFilter);
941                 search_timer = search_interval_sec;
942         }
943
944         if( a == NULL )
945                 return;
946
947
948         if( a->ident() != current_airport_id || fetch_timer <= 0 ) {
949                 // fetch timer expired or airport has changed, schedule a fetch
950                 current_airport_id = a->ident();
951                 fetch_timer = fetch_interval_sec;
952 #if defined(ENABLE_THREADS)
953                 // push this airport id into the queue for the worker thread
954                 request_queue.push( current_airport_id );
955 #else
956                 // if there is no worker thread, immediately fetch the data
957                 fetch( current_airport_id );
958 #endif
959         }
960 }
961
962 void FGMetarFetcher::fetch( const string & id )
963 {
964         if( enable_n->getBoolValue() == false ) 
965                 return;
966
967         SGSharedPtr<FGMetar> result = NULL;
968
969         // fetch current metar data
970         try {
971                 string host = proxy_host_n->getStringValue();
972                 string auth = proxy_auth_n->getStringValue();
973                 string port = proxy_port_n->getStringValue();
974
975                 result = new FGMetar( id, host, port, auth);
976
977                 long max_age = max_age_n->getLongValue();
978                 long age = result->getAge_min();
979
980                 if (max_age && age > max_age) {
981                         SG_LOG( SG_GENERAL, SG_WARN, "METAR data too old (" << age << " min).");
982                         if (++_stale_count > 10) {
983                                 _error_count = 1000;
984                                 throw sg_io_exception("More than 10 stale METAR messages in a row." " Check your system time!");
985                         }
986                 } else {
987                         _stale_count = 0;
988                 }
989
990         } catch (const sg_io_exception& e) {
991                 SG_LOG( SG_GENERAL, SG_WARN, "Error fetching live weather data: " << e.getFormattedMessage().c_str() );
992                 result = NULL;
993                 // remove METAR flag from the airport
994                 FGAirport * a = FGAirport::findByIdent( id );
995                 if( a ) a->setMetar( false );
996                 // immediately schedule a new search
997                 search_timer = 0.0;
998         }
999
1000         // write the metar to the property node, the rest is done by the methods tied to this property
1001         // don't write the metar data, if real-weather-fetch has been disabled in the meantime
1002         if( result != NULL && enable_n->getBoolValue() == true ) 
1003                 output_n->setStringValue( result->getData() );
1004 }
1005
1006 // end of environment_ctrl.cxx
1007