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