]> git.mxchange.org Git - flightgear.git/blob - src/Environment/environment.cxx
Port over remaining Point3D usage to the more type and unit safe SG* classes.
[flightgear.git] / src / Environment / environment.cxx
1 // environment.cxx -- routines to model the natural environment
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
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #include <math.h>
29
30 #include <simgear/constants.h>
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/math/interpolater.hxx>
33 #include <simgear/props/props.hxx>
34 #include <simgear/environment/visual_enviro.hxx>
35
36 #include <Main/fg_props.hxx>
37
38 #include "environment.hxx"
39
40
41 \f
42 ////////////////////////////////////////////////////////////////////////
43 // Atmosphere model.
44 ////////////////////////////////////////////////////////////////////////
45
46 // Calculated based on the ISA standard day, as found at e.g.
47 // http://www.av8n.com/physics/altimetry.htm
48
49 // Each line of data has 3 elements:
50 //   Elevation (ft), 
51 //   temperature factor (dimensionless ratio of absolute temp), 
52 //   pressure factor (dimensionless ratio)
53 static double atmosphere_data[][3] = {
54  {  -3000.00,   1.021,  1.1133 },
55  {      0.00,   1.000,  1.0000 },
56  {   2952.76,   0.980,  0.8978 },
57  {   5905.51,   0.959,  0.8042 },
58  {   8858.27,   0.939,  0.7187 },
59  {  11811.02,   0.919,  0.6407 },
60  {  14763.78,   0.898,  0.5697 },
61  {  17716.54,   0.878,  0.5052 },
62  {  20669.29,   0.858,  0.4468 },
63  {  23622.05,   0.838,  0.3940 },
64  {  26574.80,   0.817,  0.3463 },
65  {  29527.56,   0.797,  0.3034 },
66  {  32480.31,   0.777,  0.2649 },
67  {  35433.07,   0.756,  0.2305 },
68  {  38385.83,   0.752,  0.2000 },
69  {  41338.58,   0.752,  0.1736 },
70  {  44291.34,   0.752,  0.1506 },
71  {  47244.09,   0.752,  0.1307 },
72  {  50196.85,   0.752,  0.1134 },
73  {  53149.61,   0.752,  0.0984 },
74  {  56102.36,   0.752,  0.0854 },
75  {  59055.12,   0.752,  0.0741 },
76  {  62007.87,   0.752,  0.0643 },
77  {  65000.00,   0.752,  0.0557 },
78  {  68000.00,   0.754,  0.0482 },
79  {  71000.00,   0.758,  0.0418 },
80  {  74000.00,   0.761,  0.0362 },
81  {  77000.00,   0.764,  0.0314 },
82  {  80000.00,   0.767,  0.0273 },
83  {  83000.00,   0.770,  0.0237 },
84  {  86000.00,   0.773,  0.0206 },
85  {  89000.00,   0.777,  0.0179 },
86  {  92000.00,   0.780,  0.0156 },
87  {  95000.00,   0.783,  0.0135 },
88  {  98000.00,   0.786,  0.0118 },
89  { 101000.00,   0.789,  0.0103 },
90  { -1, -1, -1 }
91 };
92
93 static SGInterpTable * _temperature_degc_table = 0;
94 static SGInterpTable * _pressure_inhg_table = 0;
95
96 static void
97 _setup_tables ()
98 {
99   if (_temperature_degc_table != 0)
100       return;
101
102   _temperature_degc_table = new SGInterpTable;
103   _pressure_inhg_table = new SGInterpTable;
104
105   for (int i = 0; atmosphere_data[i][0] != -1; i++) {
106     _temperature_degc_table->addEntry(atmosphere_data[i][0],
107                                       atmosphere_data[i][1]);
108     _pressure_inhg_table->addEntry(atmosphere_data[i][0],
109                                    atmosphere_data[i][2]);
110   }
111 }
112
113
114 \f
115 ////////////////////////////////////////////////////////////////////////
116 // Implementation of FGEnvironment.
117 ////////////////////////////////////////////////////////////////////////
118
119 void FGEnvironment::_init()
120 {
121     elevation_ft = 0;
122     visibility_m = 32000;
123     temperature_sea_level_degc = 15;
124     temperature_degc = 15;
125     dewpoint_sea_level_degc = 5; // guess
126     dewpoint_degc = 5;
127     pressure_sea_level_inhg = 29.92;
128     pressure_inhg = 29.92;
129     turbulence_magnitude_norm = 0;
130     turbulence_rate_hz = 1;
131     wind_from_heading_deg = 0;
132     wind_speed_kt = 0;
133     wind_from_north_fps = 0;
134     wind_from_east_fps = 0;
135     wind_from_down_fps = 0;
136     altitude_half_to_sun_m = 1000;
137     altitude_tropo_top_m = 10000;
138     _setup_tables();
139     _recalc_density();
140     _recalc_relative_humidity();
141     
142 }
143
144 FGEnvironment::FGEnvironment()
145 {
146     _init();
147 }
148
149 FGEnvironment::FGEnvironment (const FGEnvironment &env)
150 {
151     _init();
152     copy(env);
153 }
154
155 FGEnvironment::~FGEnvironment()
156 {
157 }
158
159 void
160 FGEnvironment::copy (const FGEnvironment &env)
161 {
162     elevation_ft = env.elevation_ft;
163     visibility_m = env.visibility_m;
164     temperature_sea_level_degc = env.temperature_sea_level_degc;
165     temperature_degc = env.temperature_degc;
166     dewpoint_sea_level_degc = env.dewpoint_sea_level_degc;
167     dewpoint_degc = env.dewpoint_degc;
168     pressure_sea_level_inhg = env.pressure_sea_level_inhg;
169     wind_from_heading_deg = env.wind_from_heading_deg;
170     wind_speed_kt = env.wind_speed_kt;
171     wind_from_north_fps = env.wind_from_north_fps;
172     wind_from_east_fps = env.wind_from_east_fps;
173     wind_from_down_fps = env.wind_from_down_fps;
174     turbulence_magnitude_norm = env.turbulence_magnitude_norm;
175     turbulence_rate_hz = env.turbulence_rate_hz;
176 }
177
178 static inline bool
179 maybe_copy_value (FGEnvironment * env, const SGPropertyNode * node,
180                   const char * name, void (FGEnvironment::*setter)(double))
181 {
182     const SGPropertyNode * child = node->getNode(name);
183                                 // fragile: depends on not being typed
184                                 // as a number
185     if (child != 0 && child->hasValue() &&
186         child->getStringValue()[0] != '\0') {
187         (env->*setter)(child->getDoubleValue());
188         return true;
189     } else {
190         return false;
191     }
192 }
193
194 void
195 FGEnvironment::read (const SGPropertyNode * node)
196 {
197     maybe_copy_value(this, node, "visibility-m",
198                      &FGEnvironment::set_visibility_m);
199
200     if (!maybe_copy_value(this, node, "temperature-sea-level-degc",
201                           &FGEnvironment::set_temperature_sea_level_degc))
202         maybe_copy_value(this, node, "temperature-degc",
203                          &FGEnvironment::set_temperature_degc);
204
205     if (!maybe_copy_value(this, node, "dewpoint-sea-level-degc",
206                           &FGEnvironment::set_dewpoint_sea_level_degc))
207         maybe_copy_value(this, node, "dewpoint-degc",
208                          &FGEnvironment::set_dewpoint_degc);
209
210     if (!maybe_copy_value(this, node, "pressure-sea-level-inhg",
211                           &FGEnvironment::set_pressure_sea_level_inhg))
212         maybe_copy_value(this, node, "pressure-inhg",
213                          &FGEnvironment::set_pressure_inhg);
214
215     maybe_copy_value(this, node, "wind-from-heading-deg",
216                      &FGEnvironment::set_wind_from_heading_deg);
217
218     maybe_copy_value(this, node, "wind-speed-kt",
219                      &FGEnvironment::set_wind_speed_kt);
220
221     maybe_copy_value(this, node, "elevation-ft",
222                      &FGEnvironment::set_elevation_ft);
223
224     maybe_copy_value(this, node, "turbulence/magnitude-norm",
225                      &FGEnvironment::set_turbulence_magnitude_norm);
226
227     maybe_copy_value(this, node, "turbulence/rate-hz",
228                      &FGEnvironment::set_turbulence_rate_hz);
229 }
230
231
232 double
233 FGEnvironment::get_visibility_m () const
234 {
235   return visibility_m;
236 }
237
238 double
239 FGEnvironment::get_temperature_sea_level_degc () const
240 {
241   return temperature_sea_level_degc;
242 }
243
244 double
245 FGEnvironment::get_temperature_degc () const
246 {
247   return temperature_degc;
248 }
249
250 double
251 FGEnvironment::get_temperature_degf () const
252 {
253   return (temperature_degc * 9.0 / 5.0) + 32.0;
254 }
255
256 double
257 FGEnvironment::get_dewpoint_sea_level_degc () const
258 {
259   return dewpoint_sea_level_degc;
260 }
261
262 double
263 FGEnvironment::get_dewpoint_degc () const
264 {
265   return dewpoint_degc;
266 }
267
268 double
269 FGEnvironment::get_pressure_sea_level_inhg () const
270 {
271   return pressure_sea_level_inhg;
272 }
273
274 double
275 FGEnvironment::get_pressure_inhg () const
276 {
277   return pressure_inhg;
278 }
279
280 double
281 FGEnvironment::get_density_slugft3 () const
282 {
283   return density_slugft3;
284 }
285
286 double
287 FGEnvironment::get_relative_humidity () const
288 {
289   return relative_humidity;
290 }
291
292 double
293 FGEnvironment::get_density_tropo_avg_kgm3 () const
294 {
295   return density_tropo_avg_kgm3;
296 }
297
298 double
299 FGEnvironment::get_altitude_half_to_sun_m () const
300 {
301   return altitude_half_to_sun_m;
302 }
303
304 double
305 FGEnvironment::get_altitude_tropo_top_m () const
306 {
307   return altitude_tropo_top_m;
308 }
309
310 double
311 FGEnvironment::get_wind_from_heading_deg () const
312 {
313   return wind_from_heading_deg;
314 }
315
316 double
317 FGEnvironment::get_wind_speed_kt () const
318 {
319   return wind_speed_kt;
320 }
321
322 double
323 FGEnvironment::get_wind_from_north_fps () const
324 {
325   return wind_from_north_fps;
326 }
327
328 double
329 FGEnvironment::get_wind_from_east_fps () const
330 {
331   return wind_from_east_fps;
332 }
333
334 double
335 FGEnvironment::get_wind_from_down_fps () const
336 {
337   return wind_from_down_fps;
338 }
339
340 double
341 FGEnvironment::get_turbulence_magnitude_norm () const
342 {
343   if( sgEnviro.get_turbulence_enable_state() )
344     if (fgGetBool("/environment/params/real-world-weather-fetch") == true)
345       return sgEnviro.get_cloud_turbulence();
346   return turbulence_magnitude_norm;
347 }
348
349 double
350 FGEnvironment::get_turbulence_rate_hz () const
351 {
352   return turbulence_rate_hz;
353 }
354
355 double
356 FGEnvironment::get_elevation_ft () const
357 {
358   return elevation_ft;
359 }
360
361 void
362 FGEnvironment::set_visibility_m (double v)
363 {
364   visibility_m = v;
365 }
366
367 void
368 FGEnvironment::set_temperature_sea_level_degc (double t)
369 {
370   temperature_sea_level_degc = t;
371   if (dewpoint_sea_level_degc > t)
372       dewpoint_sea_level_degc = t;
373   _recalc_alt_temperature();
374   _recalc_density();
375 }
376
377 void
378 FGEnvironment::set_temperature_degc (double t)
379 {
380   temperature_degc = t;
381   _recalc_sl_temperature();
382   _recalc_density();
383   _recalc_relative_humidity();
384 }
385
386 void
387 FGEnvironment::set_dewpoint_sea_level_degc (double t)
388 {
389   dewpoint_sea_level_degc = t;
390   if (temperature_sea_level_degc < t)
391       temperature_sea_level_degc = t;
392   _recalc_alt_dewpoint();
393   _recalc_density();
394 }
395
396 void
397 FGEnvironment::set_dewpoint_degc (double t)
398 {
399   dewpoint_degc = t;
400   _recalc_sl_dewpoint();
401   _recalc_density();
402   _recalc_relative_humidity();
403 }
404
405 void
406 FGEnvironment::set_pressure_sea_level_inhg (double p)
407 {
408   pressure_sea_level_inhg = p;
409   _recalc_alt_pressure();
410   _recalc_density();
411 }
412
413 void
414 FGEnvironment::set_pressure_inhg (double p)
415 {
416   pressure_inhg = p;
417   _recalc_sl_pressure();
418   _recalc_density();
419 }
420
421 void
422 FGEnvironment::set_wind_from_heading_deg (double h)
423 {
424   wind_from_heading_deg = h;
425   _recalc_ne();
426 }
427
428 void
429 FGEnvironment::set_wind_speed_kt (double s)
430 {
431   wind_speed_kt = s;
432   _recalc_ne();
433 }
434
435 void
436 FGEnvironment::set_wind_from_north_fps (double n)
437 {
438   wind_from_north_fps = n;
439   _recalc_hdgspd();
440 }
441
442 void
443 FGEnvironment::set_wind_from_east_fps (double e)
444 {
445   wind_from_east_fps = e;
446   _recalc_hdgspd();
447 }
448
449 void
450 FGEnvironment::set_wind_from_down_fps (double d)
451 {
452   wind_from_down_fps = d;
453   _recalc_hdgspd();
454 }
455
456 void
457 FGEnvironment::set_turbulence_magnitude_norm (double t)
458 {
459   turbulence_magnitude_norm = t;
460 }
461
462 void
463 FGEnvironment::set_turbulence_rate_hz (double r)
464 {
465   turbulence_rate_hz = r;
466 }
467
468 void
469 FGEnvironment::set_elevation_ft (double e)
470 {
471   elevation_ft = e;
472   _recalc_alt_temperature();
473   _recalc_alt_dewpoint();
474   _recalc_alt_pressure();
475   _recalc_density();
476   _recalc_relative_humidity();
477 }
478
479 void
480 FGEnvironment::set_altitude_half_to_sun_m (double alt)
481 {
482  altitude_half_to_sun_m = alt;
483  _recalc_density_tropo_avg_kgm3();
484 }
485
486 void
487 FGEnvironment::set_altitude_tropo_top_m (double alt)
488 {
489  altitude_tropo_top_m = alt;
490  _recalc_density_tropo_avg_kgm3();
491 }
492
493
494 void
495 FGEnvironment::_recalc_hdgspd ()
496 {
497   double angle_rad;
498
499   if (wind_from_east_fps == 0) {
500     angle_rad = (wind_from_north_fps >= 0 ? SGD_PI_2 : -SGD_PI_2);
501   } else {
502     angle_rad = atan(wind_from_north_fps/wind_from_east_fps);
503   }
504   wind_from_heading_deg = angle_rad * SGD_RADIANS_TO_DEGREES;
505   if (wind_from_east_fps >= 0)
506     wind_from_heading_deg = 90 - wind_from_heading_deg;
507   else
508     wind_from_heading_deg = 270 - wind_from_heading_deg;
509
510 #if 0
511   // FIXME: Windspeed can become negative with these formulas.
512   //        which can cause problems for animations that rely
513   //        on the windspeed property.
514   if (angle_rad == 0)
515     wind_speed_kt = fabs(wind_from_east_fps
516                          * SG_METER_TO_NM * SG_FEET_TO_METER * 3600);
517   else
518     wind_speed_kt = (wind_from_north_fps / sin(angle_rad))
519       * SG_METER_TO_NM * SG_FEET_TO_METER * 3600;
520 #else
521   wind_speed_kt = sqrt(wind_from_north_fps * wind_from_north_fps +
522                        wind_from_east_fps * wind_from_east_fps) 
523                   * SG_METER_TO_NM * SG_FEET_TO_METER * 3600;
524 #endif
525 }
526
527 void
528 FGEnvironment::_recalc_ne ()
529 {
530   double speed_fps =
531     wind_speed_kt * SG_NM_TO_METER * SG_METER_TO_FEET * (1.0/3600);
532
533   wind_from_north_fps = speed_fps *
534     cos(wind_from_heading_deg * SGD_DEGREES_TO_RADIANS);
535   wind_from_east_fps = speed_fps *
536     sin(wind_from_heading_deg * SGD_DEGREES_TO_RADIANS);
537 }
538
539 void
540 FGEnvironment::_recalc_sl_temperature ()
541 {
542   // If we're in the stratosphere, leave sea-level temp alone
543   if (elevation_ft < 38000) {
544     temperature_sea_level_degc = (temperature_degc + 273.15)
545         / _temperature_degc_table->interpolate(elevation_ft)
546       - 273.15;
547   }
548 }
549
550 void
551 FGEnvironment::_recalc_alt_temperature ()
552 {
553   if (elevation_ft < 38000) {
554     temperature_degc = (temperature_sea_level_degc + 273.15) *
555         _temperature_degc_table->interpolate(elevation_ft) - 273.15;
556   } else {
557     temperature_degc = -56.49;  // Stratosphere is constant
558   }
559 }
560
561 void
562 FGEnvironment::_recalc_sl_dewpoint ()
563 {
564                                 // 0.2degC/1000ft
565                                 // FIXME: this will work only for low
566                                 // elevations
567   dewpoint_sea_level_degc = dewpoint_degc + (elevation_ft * .0002);
568   if (dewpoint_sea_level_degc > temperature_sea_level_degc)
569     dewpoint_sea_level_degc = temperature_sea_level_degc;
570 }
571
572 void
573 FGEnvironment::_recalc_alt_dewpoint ()
574 {
575                                 // 0.2degC/1000ft
576                                 // FIXME: this will work only for low
577                                 // elevations
578   dewpoint_degc = dewpoint_sea_level_degc + (elevation_ft * .0002);
579   if (dewpoint_degc > temperature_degc)
580     dewpoint_degc = temperature_degc;
581 }
582
583 void
584 FGEnvironment::_recalc_sl_pressure ()
585 {
586   pressure_sea_level_inhg =
587     pressure_inhg / _pressure_inhg_table->interpolate(elevation_ft);
588 }
589
590 void
591 FGEnvironment::_recalc_alt_pressure ()
592 {
593   pressure_inhg =
594     pressure_sea_level_inhg * _pressure_inhg_table->interpolate(elevation_ft);
595 }
596
597 void
598 FGEnvironment::_recalc_density ()
599 {
600   double pressure_psf = pressure_inhg * 70.7487;
601   
602   // adjust for humidity
603   // calculations taken from USA Today (oops!) at
604   // http://www.usatoday.com/weather/basics/density-calculations.htm
605   double temperature_degk = temperature_degc + 273.15;
606   double pressure_mb = pressure_inhg * 33.86;
607   double vapor_pressure_mb =
608     6.11 * pow(10.0, 7.5 * dewpoint_degc / (237.7 + dewpoint_degc));
609   double virtual_temperature_degk = temperature_degk / (1 - (vapor_pressure_mb / pressure_mb) * (1.0 - 0.622));
610   double virtual_temperature_degr = virtual_temperature_degk * 1.8;
611
612   density_slugft3 = pressure_psf / (virtual_temperature_degr * 1718);
613   _recalc_density_tropo_avg_kgm3();
614 }
615
616 // This is used to calculate the average density on the path 
617 // of sunlight to the observer for calculating sun-color
618 void
619 FGEnvironment::_recalc_density_tropo_avg_kgm3 ()
620 {
621   double pressure_mb = pressure_inhg * 33.86;
622   double vaporpressure = 6.11 * pow(10.0, ((7.5 * dewpoint_degc) / (237.7 + dewpoint_degc)));
623
624   double virtual_temp = (temperature_degc + 273.15) / (1 - 0.379 * (vaporpressure/pressure_mb));
625
626   double density_half = (100 * pressure_mb * exp(-altitude_half_to_sun_m / 8000))
627       / (287.05 * virtual_temp);
628   double density_tropo = (100 * pressure_mb * exp((-1 * altitude_tropo_top_m) / 8000))
629       / ( 287.05 * virtual_temp);
630
631   density_tropo_avg_kgm3 = ((density_slugft3 * 515.379) + density_half + density_tropo) / 3;
632 }
633
634 void
635 FGEnvironment::_recalc_relative_humidity ()
636 {
637   double vaporpressure = 6.11 * pow(10.0, ((7.5 * dewpoint_degc) / ( 237.7 + dewpoint_degc)));
638   double sat_vaporpressure = 6.11 * pow(10.0, ((7.5 * temperature_degc)
639       / ( 237.7 + temperature_degc)) );
640   relative_humidity = 100 * vaporpressure / sat_vaporpressure ;
641 }
642
643
644
645 ////////////////////////////////////////////////////////////////////////
646 // Functions.
647 ////////////////////////////////////////////////////////////////////////
648
649 static inline double
650 do_interp (double a, double b, double fraction)
651 {
652     double retval = (a + ((b - a) * fraction));
653     return retval;
654 }
655
656 static inline double
657 do_interp_deg (double a, double b, double fraction)
658 {
659     a = fmod(a, 360);
660     b = fmod(b, 360);
661     if (fabs(b-a) > 180) {
662         if (a < b)
663             a += 360;
664         else
665             b += 360;
666     }
667     return fmod(do_interp(a, b, fraction), 360);
668 }
669
670 void
671 interpolate (const FGEnvironment * env1, const FGEnvironment * env2,
672              double fraction, FGEnvironment * result)
673 {
674     result->set_visibility_m
675         (do_interp(env1->get_visibility_m(),
676                    env2->get_visibility_m(),
677                    fraction));
678
679     result->set_temperature_sea_level_degc
680         (do_interp(env1->get_temperature_sea_level_degc(),
681                    env2->get_temperature_sea_level_degc(),
682                    fraction));
683
684     result->set_dewpoint_degc
685         (do_interp(env1->get_dewpoint_sea_level_degc(),
686                    env2->get_dewpoint_sea_level_degc(),
687                    fraction));
688
689     result->set_pressure_sea_level_inhg
690         (do_interp(env1->get_pressure_sea_level_inhg(),
691                    env2->get_pressure_sea_level_inhg(),
692                    fraction));
693
694     result->set_wind_from_heading_deg
695         (do_interp_deg(env1->get_wind_from_heading_deg(),
696                        env2->get_wind_from_heading_deg(),
697                        fraction));
698
699     result->set_wind_speed_kt
700         (do_interp(env1->get_wind_speed_kt(),
701                    env2->get_wind_speed_kt(),
702                    fraction));
703
704     result->set_elevation_ft
705         (do_interp(env1->get_elevation_ft(),
706                    env2->get_elevation_ft(),
707                    fraction));
708
709     result->set_turbulence_magnitude_norm
710         (do_interp(env1->get_turbulence_magnitude_norm(),
711                    env2->get_turbulence_magnitude_norm(),
712                    fraction));
713
714     result->set_turbulence_rate_hz
715         (do_interp(env1->get_turbulence_rate_hz(),
716                    env2->get_turbulence_rate_hz(),
717                    fraction));
718 }
719
720 // end of environment.cxx