FlightGear next
climate.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: climate.cxx
3 * SPDX-FileComment: Köppen-Geiger climate interface class
4 * SPDX-FileCopyrightText: Copyright (C) 2020-2021 by Erik Hofman <erik@ehofman.com>
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include <cstring>
9
10#include <osgDB/ReadFile>
11
12#include <simgear/misc/sg_path.hxx>
13#include <simgear/math/SGVec3.hxx>
14#include <simgear/math/SGVec4.hxx>
15#include <simgear/timing/sg_time.hxx>
16#include <simgear/constants.h>
17
18#include "Main/fg_props.hxx"
19#include "Time/light.hxx"
20
21#include "environment.hxx"
22#include "atmosphere.hxx"
23#include "environment_mgr.hxx"
24#include "climate.hxx"
25
26// Based on "World Map of the Köppen-Geiger climate classification"
27// The map is provided with a spatial resolution of approx. 10x10km per pixel.
28// http://koeppen-geiger.vu-wien.ac.at/present.htm
29//
30// References:
31// * Teuling, A. J.: Technical note: Towards a continuous classification of
32// climate usingbivariate colour mapping, Hydrol. Earth Syst. Sci., 15,
33// 3071–3075, https://doi.org/10.5194/hess-15-3071-2011, 2011.
34//
35// * Lawrence, Mark G., 2005: The relationship between relative humidity and
36// the dewpoint temperature in moist air: A simple conversion and
37// applications. Bull. Amer. Meteor. Soc., 86, 225-233.
38// doi: http://dx.doi.org/10.1175/BAMS-86-2-225
39//
40// * A Simple Accurate Formula for Calculating Saturation Vapor Pressure of
41// Water and Ice. Jianhua Huang, J. Appl. Meteor. Climatol. (2018) 57 (6):
42// 1265–1272. https://doi.org/10.1175/JAMC-D-17-0334.1
43
44
45#define HOUR (0.5/24.0)
46#define MONTH (1.0/12.0)
47
49{
50 SGPath img_path = globals->get_fg_root() / "Geodata" / "koppen-geiger.png";
51
52 image = osgDB::readImageFile(img_path.utf8Str());
53 if (image)
54 {
55 _image_width = static_cast<double>( image->s() );
56 _image_height = static_cast<double>( image->t() );
57 _epsilon = 36.0/_image_width;
58 }
59}
60
62{
63 _monthNode = fgGetNode("/sim/time/utc/month", true);
64 _metarSnowLevelNode = fgGetNode("/environment/params/metar-updates-snow-level", true);
65
66 _positionLatitudeNode = fgGetNode("/position/latitude-deg", true);
67 _positionLongitudeNode= fgGetNode("/position/longitude-deg", true);
68 _gravityNode = fgGetNode("/environment/gravitational-acceleration-mps2", true);
69}
70
72{
73 _rootNode = fgGetNode("/environment/climate", true);
74 _rootNode->getNode("description", true)->setStringValue(_description[_code]);
75 _rootNode->getNode("classification", true)->setStringValue(_classification[_code]);
76
77 _tiedProperties.setRoot( _rootNode );
78
79 // environment properties
80 _tiedProperties.Tie( "environment-update", this, &FGClimate::getEnvironmentUpdate, &FGClimate::setEnvironmentUpdate);
81
82 // METAR
83 _tiedProperties.Tie("data", this, &FGClimate::get_metar);
84
85 // weather properties
86 _tiedProperties.Tie( "weather-update", &_weather_update);
87 _tiedProperties.Tie( "pressure-hpa", &_gl.pressure);
88 _tiedProperties.Tie( "pressure-sea-level-hpa", &_gl.pressure);
89 _tiedProperties.Tie( "relative-humidity", &_gl.relative_humidity);
90 _tiedProperties.Tie( "relative-humidity-sea-level", &_sl.relative_humidity);
91 _tiedProperties.Tie( "dewpoint-degc", &_gl.dewpoint);
92 _tiedProperties.Tie( "dewpoint-sea-level-degc", &_sl.dewpoint);
93 _tiedProperties.Tie( "temperature-degc", &_gl.temperature);
94 _tiedProperties.Tie( "temperature-sea-level-degc", &_sl.temperature);
95 _tiedProperties.Tie( "temperature-mean-degc", &_gl.temperature_mean);
96 _tiedProperties.Tie( "temperature-mean-sea-level-degc", &_sl.temperature_mean);
97 _tiedProperties.Tie( "temperature-seawater-degc", &_sl.temperature_water);
98 _tiedProperties.Tie( "precipitation-month-mm", &_gl.precipitation);
99 _tiedProperties.Tie( "precipitation-annual-mm", & _gl.precipitation_annual);
100 _tiedProperties.Tie( "snow-level-m", &_snow_level);
101 _tiedProperties.Tie( "wind-speed-mps", &_wind_speed);
102 _tiedProperties.Tie( "wind-direction-deg", &_wind_direction);
103 _tiedProperties.Tie( "visibility-m", &_visibility_m);
104 _tiedProperties.Tie( "elevation-m", &_elevation_m);
105 _tiedProperties.Tie( "mist-fact", &_mist);
106 _tiedProperties.Tie( "fog-fact", &_fog);
107}
108
110{
111 _tiedProperties.Untie();
112}
113
115{
116 _prev_lat = -99999.0;
117 _prev_lon = -99999.0;
118
119 _gl = ClimateTile();
120 _sl = ClimateTile();
121
122 _wind_direction = -99999.0;
123
124 _snow_level = -99999.0;
125 _snow_thickness = -99999.0;
126 _ice_cover = -99999.0;
127 _dust_cover = -99999.0;
128 _wetness = -99999.0;
129 _lichen_cover = -99999.0;
130 _inland_ice_cover = false;
131}
132
133// Set all environment parameters based on the koppen-classicfication
134// https://en.wikipedia.org/wiki/K%C3%B6ppen_climate_classification
135// http://vectormap.si.edu/Climate.htm
136void FGClimate::update(double dt)
137{
138 auto l = globals->get_subsystem<FGLight>();
139 if (l)
140 {
141 _sun_longitude_deg = l->get_sun_lon()*SGD_RADIANS_TO_DEGREES;
142 _sun_latitude_deg = l->get_sun_lat()*SGD_RADIANS_TO_DEGREES;
143 }
144
145 double latitude_deg = _positionLatitudeNode->getDoubleValue();
146 double longitude_deg = _positionLongitudeNode->getDoubleValue();
147
148 _adj_latitude_deg = latitude_deg - _sun_latitude_deg;
149 _adj_longitude_deg = _sun_longitude_deg - longitude_deg;
150 if (_adj_longitude_deg < 0.0) _adj_longitude_deg += 360.0;
151 else if (_adj_longitude_deg >= 360.0) _adj_longitude_deg -= 360.0;
152
153 double diff_pos = fabs(_prev_lat - _adj_latitude_deg) +
154 fabs(_prev_lon - _adj_longitude_deg);
155 if (diff_pos > _epsilon )
156 {
157 if (diff_pos > 1.0) reinit();
158
159 double north = latitude_deg >= 0.0 ? 1.0 : -1.0; // hemisphere
160 if (north) {
161 _is_autumn = (_monthNode->getIntValue() > 6) ? 1.0 : 0.0;
162 } else {
163 _is_autumn = (_monthNode->getIntValue() <= 6) ? 1.0 : 0.0;
164 }
165
166 _prev_lat = _adj_latitude_deg;
167 _prev_lon = _adj_longitude_deg;
168
169 update_day_factor();
170 update_season_factor();
171 update_daylight();
172 update_wind();
173
174 _code = 0; // Ocean
175 osg::Vec4f color;
176 if (image)
177 {
178 // from lat/lon to screen coordinates
179 double x = 180.0 + longitude_deg;
180 double y = 90.0 + latitude_deg;
181 double xs = x * _image_width/360.0;
182 double yt = y * _image_height/180.0;
183 double rxs = round(xs);
184 double ryt = round(yt);
185
186 int s = static_cast<int>(rxs);
187 int t = static_cast<int>(ryt);
188 color = image->getColor(s, t);
189
190 // convert from color shades to koppen-classicfication
191 _elevation_m = _gl.elevation_m = 5600.0*color[1];
192 _gl.precipitation_annual = 150.0 + 9000.0*color[2];
193 _code = static_cast<int>(floorf(255.0f*color[0]/4.0f));
194 if (_code >= MAX_CLIMATE_CLASSES)
195 {
196 SG_LOG(SG_ENVIRONMENT, SG_WARN, "Climate Koppen code exceeds the maximum");
197 _code = MAX_CLIMATE_CLASSES-1;
198 }
199
200 if (_code == 0) set_ocean();
201 else if (_code < 5) set_tropical();
202 else if (_code < 9) set_dry();
203 else if (_code < 18) set_temperate();
204 else if (_code < 30) set_continetal();
205 else if (_code < 32) set_polar();
206 else set_ocean();
207 }
208
209 _rootNode->getNode("description")->setStringValue(_description[_code]);
210 _rootNode->getNode("classification")->setStringValue(_classification[_code]);
211
212 // must be after the climate functions and after calculation of
213 // _gl.elevation_m
214 update_pressure();
215
216 // Mark G. Lawrence:
217 _gl.dewpoint = _gl.temperature - ((100.0 - _gl.relative_humidity)/5.0);
218
219 // calculate sea level parameters from ground level parameters
220 // adapted from metarproperties.cxx
221 FGEnvironment dummy;
222 dummy.set_is_isa(globals->get_subsystem<FGEnvironmentMgr>()->getEnvironment().get_is_isa());
223 dummy.set_live_update(false);
224 dummy.set_elevation_ft(_gl.elevation_m*SG_METER_TO_FEET);
225 dummy.set_dewpoint_degc(_gl.dewpoint);
227
228 dummy.set_live_update(true);
229 dummy.set_temperature_degc(_gl.temperature);
230
231 _sl.dewpoint = dummy.get_dewpoint_sea_level_degc();
232 _sl.relative_humidity = dummy.get_relative_humidity();
233 _sl.temperature = dummy.get_temperature_sea_level_degc();
235
236 dummy.set_temperature_degc(_gl.temperature_mean);
237 _sl.temperature_mean = dummy.get_temperature_sea_level_degc();
238
239 dummy.set_temperature_degc(_gl.temperature_water);
240 _sl.temperature_water = dummy.get_temperature_sea_level_degc();
241
242 set_environment();
243
244#if REPORT_TO_CONSOLE
245 report();
246#endif
247 }
248}
249
250// https://en.wikipedia.org/wiki/Sunrise_equation
251void FGClimate::update_daylight()
252{
253 double declination = _sun_latitude_deg*SGD_DEGREES_TO_RADIANS;
254 double latitude_deg = _positionLatitudeNode->getDoubleValue();
255 double latitude = latitude_deg*SGD_DEGREES_TO_RADIANS;
256
257 double fact = -tan(latitude) * tan(declination);
258 if (fact > 1.0) fact = 1.0;
259 else if (fact < -1.0) fact = -1.0;
260
261 _day_light = acos(fact)/SGD_PI;
262}
263
264// _day_noon returns 0.0 for night up to 1.0 for noon
265void FGClimate::update_day_factor()
266{
267 // noon is when lon == 180.0
268 _day_noon = fabs(_adj_longitude_deg - 180.0)/180.0;
269
270 double adj_lon = _adj_longitude_deg;
271 if (adj_lon >= 180.0) adj_lon -= 360.0;
272 _daytime = 1.0 - (adj_lon + 180.0)/360.0;
273}
274
275// _season_summer returns 0.0 for winter up to 1.0 for summer
276// _season_transistional returns 0.0 for early autumn/late spring up to
277// 1.0 for late autumn/early spring
278// to distinguish between the two use the _autumn flag.
279void FGClimate::update_season_factor()
280{
281 double latitude_deg = _positionLatitudeNode->getDoubleValue();
282 double sign = latitude_deg >= 0.0 ? 1.0 : -1.0; // hemisphere
283
284 _season_summer = (23.5 + sign*_sun_latitude_deg)/(2.0*23.5);
285
286 _seasons_year = (_is_autumn > 0.05) ? 1.0-0.5*_season_summer : 0.5*_season_summer;
287
288 _season_transistional = 2.0*(1.0 - _season_summer) - 1.0;
289 if (_season_transistional < 0.0) _season_transistional = 0.0;
290 else if (_season_transistional > 1.0) _season_transistional = 1.0;
291}
292
293// https://en.wikipedia.org/wiki/Density_of_air#Humid_air
294void FGClimate::update_pressure()
295{
296 // saturation vapor pressure for water, Jianhua Huang:
297 double Tc = _gl.temperature ? _gl.temperature : 1e-9;
298 double Psat = exp(34.494 - 4924.99/(Tc+237.1))/pow(Tc + 105.0, 1.57);
299
300 // vapor pressure of water
301 double Pv = 0.01*_gl.relative_humidity*Psat;
302
303 // pressure at elevation_itude
304 static const double R0 = 8.314462618; // Universal gas constant
305 static const double P0 = 101325.0; // Sea level standard atm. pressure
306 static const double T0 = 288.15; // Sea level standard temperature
307 static const double L = 0.0065; // Temperature lapse rate
308 static const double M = 0.0289654; // Molar mass of dry air
309
310 double h = _gl.elevation_m/1000.0;
311 double g = _gravityNode->getDoubleValue();
312 double P = P0*pow(1.0 - L*h/T0, g*M/(L*R0));
313
314 // partial pressure of dry air
315 double Pd = P - Pv;
316
317 // air pressure
318 _gl.pressure = 0.01*(Pd+Psat); // hPa
319}
320
321// https://sites.google.com/site/gitakrishnareach/home/global-wind-patterns
322// https://commons.wikimedia.org/wiki/File:Global_Annual_10m_Average_Wind_Speed.png
323void FGClimate::update_wind()
324{
325 double latitude_deg = _positionLatitudeNode->getDoubleValue();
326 if (latitude_deg > 60.0)
327 {
328 double val = 1.0 - (latitude_deg - 60.0)/30.0;
329 _set(_wind_direction, linear(val, 0.0, 90.0));
330 if (_code == 0) _wind_speed = linear(val, 6.0, 10.0);
331 }
332 else if (latitude_deg > 30.0)
333 {
334 double val = (latitude_deg - 30.0)/30.0;
335 _set(_wind_direction, linear(val, 180.0, 270.0));
336 if (_code == 0) _wind_speed = linear(val, 5.0, 10.0);
337 else _wind_speed = linear(1.0 - val, 3.0, 5.0);
338 }
339 else if (latitude_deg > 0)
340 {
341 double val = 1.0 - latitude_deg/30.0;
342 _set(_wind_direction, linear(val, 0.0, 90.0));
343 if (_code == 0) _wind_speed = triangular(val, 5.0, 7.0);
344 else _wind_speed = triangular(fabs(val - 0.5), 3.0, 5.0);
345 }
346 else if (latitude_deg > -30.0)
347 {
348 double val = -latitude_deg/30.0;
349 _set(_wind_direction, linear(val, 90.0, 180.0));
350 if (_code == 0) _wind_speed = triangular(val, 5.0, 7.0);
351 else _wind_speed = triangular(fabs(val - 0.5), 3.0, 5.0);
352 }
353 else if (latitude_deg > -60.0)
354 {
355 double val = 1.0 - (latitude_deg + 30.0)/30.0;
356 _set(_wind_direction, linear(val, -90.0, 0.0));
357 if (_code == 0) _wind_speed = linear(val, 5.0, 10.0);
358 else _wind_speed = linear(1.0 - val, 3.0, 6.0);
359 }
360 else
361 {
362 double val = (latitude_deg + 60.0)/30.0;
363 _wind_direction = linear(val, 90.0, 180.0);
364 if (_code == 0) _wind_speed = linear(1.0 - val, 5.0, 10.0);
365 }
366
367 if (_wind_direction < 0.0) _wind_direction += 360.0;
368}
369
370
371void FGClimate::set_ocean()
372{
373 double day = _day_noon;
374 double summer = _season_summer;
375
376 // temperature based on latitude, season and time of day
377 // the equator
378 double temp_equator_night = triangular(season(summer, MONTH), 17.5, 22.5);
379 double temp_equator_day = triangular(season(summer, MONTH), 27.5, 32.5);
380 double temp_equator_mean = linear(_day_light, temp_equator_night, temp_equator_day);
381 double temp_equator = linear(daytime(day, 3.0*HOUR), temp_equator_night, temp_equator_day);
382 double temp_sw_Am = triangular(season(summer, 2.0*MONTH), 22.0, 27.5);
383
384 // the poles
385 double temp_pole_night = sinusoidal(season(summer, MONTH), -30.0, 0.0);
386 double temp_pole_day = sinusoidal(season(summer, 1.5*MONTH), -22.5, 4.0);
387 double temp_pole_mean = linear(_day_light, temp_pole_night, temp_pole_day);
388 double temp_pole = linear(daytime(day, 3.0*HOUR), temp_pole_night, temp_pole_day);
389 double temp_sw_ET = long_low(season(summer, 2.0*MONTH), -27.5, -3.5);
390
391 // interpolate based on the viewers latitude
392 double latitude_deg = _positionLatitudeNode->getDoubleValue();
393 double fact_lat = pow(fabs(latitude_deg)/90.0, 2.5);
394 double ifact_lat = 1.0 - fact_lat;
395
396 _set(_gl.temperature, linear(ifact_lat, temp_pole, temp_equator));
397 _set(_gl.temperature_mean, linear(ifact_lat, temp_pole_mean, temp_equator_mean));
398 _set(_gl.temperature_water, linear(ifact_lat, temp_sw_ET, temp_sw_Am));
399
400 // high relative humidity around the equator and poles
401 _set(_gl.relative_humidity, triangular(fabs(fact_lat-0.5), 70.0, 87.0));
402
403 _set(_gl.precipitation_annual, 990.0); // global average
404 _set(_gl.precipitation, 100.0 - (_gl.precipitation_annual/25.0));
405
406 _gl.has_autumn = false;
407}
408
409// https://en.wikipedia.org/wiki/Tropical_rainforest_climate
410// https://en.wikipedia.org/wiki/Tropical_monsoon_climate
411void FGClimate::set_tropical()
412{
413 double day = _day_noon;
414
415 double summer = _season_summer;
416 double winter = -summer;
417
418 double hmin = sinusoidal(summer, 0.0, 0.36);
419 double hmax = sinusoidal(season(winter), 0.86, 1.0);
420 double humidity = linear(daytime(day, -9.0*HOUR), hmin, hmax);
421
422 // wind speed based on latitude (0.0 - 15 degrees)
423 double latitude_deg = _positionLatitudeNode->getDoubleValue();
424 double fact_lat = std::max(abs(latitude_deg), 15.0)/15.0;
425 double wind_speed = 3.0*fact_lat*fact_lat;
426
427 double temp_water = _gl.temperature_water;
428 double temp_night = _sl.temperature;
429 double temp_day = _sl.temperature;
430 double precipitation = _gl.precipitation;
431 double relative_humidity = _gl.relative_humidity;
432 switch(_code)
433 {
434 case 1: // Af: equatorial, fully humid
435 temp_night = triangular(summer, 20.0, 22.5);
436 temp_day = triangular(summer, 29.5, 32.5);
437 temp_water = triangular(season(summer, MONTH), 25.0, 27.5);
438 precipitation = sinusoidal(season(winter), 150.0, 280.0);
439 relative_humidity = triangular(humidity, 75.0, 85.0);
440 break;
441 case 2: // Am: equatorial, monsoonal
442 temp_night = triangular(season(summer, MONTH), 17.5, 22.5);
443 temp_day = triangular(season(summer, MONTH), 27.5, 32.5);
444 temp_water = triangular(season(summer, MONTH), 22.0, 27.5);
445 precipitation = linear(season(summer, MONTH), 45.0, 340.0);
446 relative_humidity = triangular(humidity, 75.0, 85.0);
447 wind_speed *= 2.0*_gl.precipitation/340.0;
448 break;
449 case 3: // As: equatorial, summer dry
450 temp_night = long_high(season(summer, .15*MONTH), 15.0, 22.5);
451 temp_day = triangular(season(summer, MONTH), 27.5, 35.0);
452 temp_water = triangular(season(summer, 2.0*MONTH), 21.5, 26.5);
453 precipitation = sinusoidal(season(summer, 2.0*MONTH), 35.0, 150.0);
454 relative_humidity = triangular(humidity, 60.0, 80.0);
455 wind_speed *= 2.0*_gl.precipitation/150.0;
456 break;
457 case 4: // Aw: equatorial, winter dry
458 temp_night = long_high(season(summer, 1.5*MONTH), 15.0, 22.5);
459 temp_day = triangular(season(summer, 2.0*MONTH), 27.5, 35.0);
460 temp_water = triangular(season(summer, 2.0*MONTH), 21.5, 28.5);
461 precipitation = sinusoidal(season(summer, 2.0*MONTH), 10.0, 230.0);
462 relative_humidity = triangular(humidity, 60.0, 80.0);
463 wind_speed *= 2.0*_gl.precipitation/230.0;
464 break;
465 default:
466 break;
467 }
468
469 _set(_gl.temperature, linear(daytime(day, 3.0*HOUR), temp_night, temp_day));
470 _set(_gl.temperature_mean, linear(_day_light, temp_night, temp_day));
471 _set(_gl.temperature_water, temp_water);
472
473 _set(_gl.relative_humidity, relative_humidity);
474 _set(_gl.precipitation, precipitation);
475
476 _gl.has_autumn = false;
477 _wind_speed = wind_speed;
478}
479
480// https://en.wikipedia.org/wiki/Desert_climate
481// https://en.wikipedia.org/wiki/Semi-arid_climate
482void FGClimate::set_dry()
483{
484 double day = _day_noon;
485
486 double summer = _season_summer;
487 double winter = -summer;
488
489 double hmin = sinusoidal(summer, 0.0, 0.36);
490 double hmax = sinusoidal(season(winter), 0.86, 1.0);
491 double humidity = linear(daytime(day, -9.0*HOUR), hmin, hmax);
492
493 double temp_water = _gl.temperature_water;
494 double temp_night = _sl.temperature;
495 double temp_day = _sl.temperature;
496 double precipitation = _gl.precipitation;
497 double relative_humidity = _gl.relative_humidity;
498 switch(_code)
499 {
500 case 5: // BSh: arid, steppe, hot arid
501 temp_night = long_high(season(summer, MONTH), 10.0, 22.0);
502 temp_day = triangular(season(summer, 2.0*MONTH), 27.5, 35.0);
503 temp_water = triangular(season(summer, 2.5*MONTH), 18.5, 28.5);
504 precipitation = long_low(season(summer, 2.0*MONTH), 8.0, 117.0);
505 relative_humidity = triangular(humidity, 20.0, 30.0);
506 break;
507 case 6: // BSk: arid, steppe, cold arid
508 temp_night = sinusoidal(season(summer, MONTH), -14.0, 12.0);
509 temp_day = sinusoidal(season(summer, MONTH), 0.0, 30.0);
510 temp_water = sinusoidal(season(summer, 2.0*MONTH), 5.0, 25.5);
511 precipitation = sinusoidal(season(summer, MONTH), 15.0, 34.0);
512 relative_humidity = sinusoidal(humidity, 48.0, 67.0);
513 break;
514 case 7: // BWh: arid, desert, hot arid
515 temp_night = sinusoidal(season(summer, 1.5*MONTH), 7.5, 22.0);
516 temp_day = even(season(summer, 1.5*MONTH), 22.5, 37.5);
517 temp_water = even(season(summer, 2.5*MONTH), 15.5, 33.5);
518 precipitation = monsoonal(season(summer, 2.0*MONTH), 3.0, 18.0);
519 relative_humidity = monsoonal(humidity, 25.0, 55.0);
520 break;
521 case 8: // BWk: arid, desert, cold arid
522 temp_night = sinusoidal(season(summer, MONTH), -15.0, 15.0);
523 temp_day = sinusoidal(season(summer, MONTH), -2.0, 30.0);
524 temp_water = sinusoidal(season(summer, 2.0*MONTH), 4.0, 26.5);
525 precipitation = linear(season(summer, MONTH), 4.0, 14.0);
526 relative_humidity = linear(humidity, 45.0, 61.0);
527 break;
528 default:
529 break;
530 }
531
532 _set(_gl.temperature, linear(daytime(day, 3.0*HOUR), temp_night, temp_day));
533 _set(_gl.temperature_mean, linear(_day_light, temp_night, temp_day));
534 _set(_gl.temperature_water, temp_water);
535
536 _set(_gl.relative_humidity, relative_humidity);
537 _set(_gl.precipitation, precipitation);
538 _gl.has_autumn = false;
539}
540
541// https://en.wikipedia.org/wiki/Temperate_climate
542void FGClimate::set_temperate()
543{
544 double day = _day_noon;
545
546 double summer = _season_summer;
547 double winter = -summer;
548
549 double hmin = sinusoidal(summer, 0.0, 0.36);
550 double hmax = sinusoidal(season(winter), 0.86, 1.0);
551 double humidity = linear(daytime(day, -9.0*HOUR), hmin, hmax);
552
553 double temp_water = _gl.temperature_water;
554 double temp_night = _sl.temperature;
555 double temp_day = _sl.temperature;
556 double precipitation = _gl.precipitation;
557 double relative_humidity = _gl.relative_humidity;
558 switch(_code)
559 {
560 case 9: // Cfa: warm temperature, fully humid hot summer
561 temp_night = sinusoidal(season(summer, 1.5*MONTH), -3.0, 20.0);
562 temp_day = sinusoidal(season(summer, 1.5*MONTH), 10.0, 33.0);
563 temp_water = sinusoidal(season(summer, 2.5*MONTH), 8.0, 28.5);
564 precipitation = sinusoidal(summer, 60.0, 140.0);
565 relative_humidity = sinusoidal(humidity, 65.0, 80.0);
566 break;
567 case 10: // Cfb: warm temperature, fully humid, warm summer
568 temp_night = sinusoidal(season(summer, 1.5*MONTH), -3.0, 10.0);
569 temp_day = sinusoidal(season(summer, 1.5*MONTH), 5.0, 25.0);
570 temp_water = sinusoidal(season(summer, 2.5*MONTH), 3.0, 20.5);
571 precipitation = sinusoidal(season(winter, 3.5*MONTH), 65.0, 90.0);
572 relative_humidity = sinusoidal(humidity, 68.0, 87.0);
573 break;
574 case 11: // Cfc: warm temperature, fully humid, cool summer
575 temp_night = long_low(season(summer, 1.5*MONTH), -3.0, 8.0);
576 temp_day = long_low(season(summer, 1.5*MONTH), 2.0, 14.0);
577 temp_water = long_low(season(summer, 2.5*MONTH), 3.0, 11.5);
578 precipitation = linear(season(winter), 90.0, 200.0);
579 relative_humidity = long_low(humidity, 70.0, 85.0);
580 break;
581 case 12: // Csa: warm temperature, summer dry, hot summer
582 temp_night = sinusoidal(season(summer, MONTH), 2.0, 16.0);
583 temp_day = sinusoidal(season(summer, MONTH), 12.0, 33.0);
584 temp_water = sinusoidal(season(summer, 2.0*MONTH), 10.0, 27.5);
585 precipitation = linear(season(winter), 25.0, 70.0);
586 relative_humidity = sinusoidal(humidity, 58.0, 72.0);
587 break;
588 case 13: // Csb: warm temperature, summer dry, warm summer
589 temp_night = linear(season(summer, 1.5*MONTH), -4.0, 10.0);
590 temp_day = linear(season(summer, 1.5*MONTH), 6.0, 27.0);
591 temp_water = linear(season(summer, 2.5*MONTH), 4.0, 21.5);
592 precipitation = linear(season(winter), 25.0, 120.0);
593 relative_humidity = linear(humidity, 50.0, 72.0);
594 break;
595 case 14: // Csc: warm temperature, summer dry, cool summer
596 temp_night = sinusoidal(season(summer, 0.5*MONTH), -4.0, 5.0);
597 temp_day = sinusoidal(season(summer, 0.5*MONTH), 5.0, 16.0);
598 temp_water = sinusoidal(season(summer, 1.5*MONTH), 3.0, 14.5);
599 precipitation = sinusoidal(season(winter, -MONTH), 60.0, 95.0);
600 relative_humidity = sinusoidal(humidity, 55.0, 75.0);
601 break;
602 case 15: // Cwa: warm temperature, winter dry, hot summer
603 temp_night = even(season(summer, MONTH), 4.0, 20.0);
604 temp_day = long_low(season(summer, MONTH), 15.0, 30.0);
605 temp_water = long_low(season(summer, 2.0*MONTH), 7.0, 24.5);
606 precipitation = long_low(season(summer, MONTH), 10.0, 320.0);
607 relative_humidity = sinusoidal(humidity, 60.0, 79.0);
608 break;
609 case 16: // Cwb: warm temperature, winter dry, warm summer
610 temp_night = even(season(summer, MONTH), 1.0, 13.0);
611 temp_day = long_low(season(summer, MONTH), 15.0, 27.0);
612 temp_water = even(season(summer, 2.0*MONTH), 5.0, 22.5);
613 precipitation = long_low(season(summer, MONTH), 10.0, 250.0);
614 relative_humidity = sinusoidal(humidity, 58.0, 72.0);
615 break;
616 case 17: // Cwc: warm temperature, winter dry, cool summer
617 temp_night = long_low(season(summer, MONTH), -9.0, 6.0);
618 temp_day = long_high(season(summer, MONTH), 6.0, 17.0);
619 temp_water = long_high(season(summer, 2.0*MONTH), 8.0, 15.5);
620 precipitation = long_low(season(summer, MONTH), 5.0, 200.0);
621 relative_humidity = long_high(humidity, 50.0, 58.0);
622 break;
623 default:
624 break;
625 }
626
627 _set(_gl.temperature, linear(daytime(day, 3.0*HOUR), temp_night, temp_day));
628 _set(_gl.temperature_mean, linear(_day_light, temp_night, temp_day));
629 _set(_gl.temperature_water, temp_water);
630
631 _set(_gl.relative_humidity, relative_humidity);
632 _set(_gl.precipitation, precipitation);
633
634 _gl.has_autumn = true;
635
636}
637
638// https://en.wikipedia.org/wiki/Continental_climate
639void FGClimate::set_continetal()
640{
641 double day = _day_noon;
642
643 double summer = _season_summer;
644 double winter = -summer;
645
646 double hmin = sinusoidal(summer, 0.0, 0.36);
647 double hmax = sinusoidal(season(winter), 0.86, 1.0);
648 double humidity = linear(daytime(day, -9.0*HOUR), hmin, hmax);
649
650 double temp_water = _gl.temperature_water;
651 double temp_day = _sl.temperature;
652 double temp_night = _sl.temperature;
653 double precipitation = _gl.precipitation;
654 double relative_humidity = _gl.relative_humidity;
655 switch(_code)
656 {
657 case 18: // Dfa: snow, fully humid, hot summer
658 temp_night = sinusoidal(season(summer, MONTH), -15.0, 13.0);
659 temp_day = sinusoidal(season(summer, MONTH), -5.0, 30.0);
660 temp_water = sinusoidal(season(summer, 2.0*MONTH), 0.0, 26.5);
661 precipitation = linear(season(summer, MONTH), 30.0, 70.0);
662 relative_humidity = sinusoidal(humidity, 68.0, 72.0);
663 break;
664 case 19: // Dfb: snow, fully humid, warm summer, warm summer
665 temp_night = sinusoidal(season(summer, MONTH), -17.5, 10.0);
666 temp_day = sinusoidal(season(summer, MONTH), -7.5, 25.0);
667 temp_water = sinusoidal(season(summer, 2.0*MONTH), -2.0, 22.5);
668 precipitation = linear(season(summer, MONTH), 30.0, 70.0);
669 relative_humidity = sinusoidal(humidity, 69.0, 81.0);
670 break;
671 case 20: // Dfc: snow, fully humid, cool summer, cool summer
672 temp_night = sinusoidal(season(summer, MONTH), -30.0, 4.0);
673 temp_day = sinusoidal(season(summer, MONTH), -20.0, 15.0);
674 temp_water = sinusoidal(season(summer, 2.0*MONTH), -10.0, 12.5);
675 precipitation = linear(season(summer, 1.5*MONTH), 22.0, 68.0);
676 relative_humidity = sinusoidal(humidity, 70.0, 88.0);
677 _wind_speed = 3.0;
678 break;
679 case 21: // Dfd: snow, fully humid, extremely continental
680 temp_night = sinusoidal(season(summer, MONTH), -45.0, 4.0);
681 temp_day = sinusoidal(season(summer, MONTH), -35.0, 10.0);
682 temp_water = sinusoidal(season(summer, 2.0*MONTH), -15.0, 8.5);
683 precipitation = long_low(season(summer, 1.5*MONTH), 7.5, 45.0);
684 relative_humidity = sinusoidal(humidity, 80.0, 90.0);
685 break;
686 case 22: // Dsa: snow, summer dry, hot summer
687 temp_night = sinusoidal(season(summer, 1.5*MONTH), -10.0, 10.0);
688 temp_day = sinusoidal(season(summer, 1.5*MONTH), 0.0, 30.0);
689 temp_water = sinusoidal(season(summer, 3.5*MONTH), 4.0, 24.5);
690 precipitation = long_high(season(winter, 2.0*MONTH), 5.0, 65.0);
691 relative_humidity = sinusoidal(humidity, 48.0, 58.08);
692 break;
693 case 23: // Dsb: snow, summer dry, warm summer
694 temp_night = sinusoidal(season(summer, 1.5*MONTH), -15.0, 6.0);
695 temp_day = sinusoidal(season(summer, 1.5*MONTH), -4.0, 25.0);
696 temp_water = sinusoidal(season(summer, 2.5*MONTH), 0.0, 19.5);
697 precipitation = long_high(season(winter, 2.0*MONTH), 12.0, 65.0);
698 relative_humidity = sinusoidal(humidity, 50.0, 68.0);
699 break;
700 case 24: // Dsc: snow, summer dry, cool summer
701 temp_night = sinusoidal(season(summer, MONTH), -27.5, 2.0);
702 temp_day = sinusoidal(season(summer, MONTH), -4.0, 15.0);
703 temp_water = sinusoidal(season(summer, 2.0*MONTH), 0.0, 12.5);
704 precipitation = long_low(season(summer, MONTH), 32.5, 45.0);
705 relative_humidity = sinusoidal(humidity, 50.0, 60.0);
706 break;
707 case 25: // Dsd: snow, summer dry, extremely continental
708 temp_night = sinusoidal(season(summer, MONTH), -11.5, -6.5);
709 temp_day = sinusoidal(season(summer, MONTH), 14.0, 27.0);
710 temp_water = sinusoidal(season(summer, 2.0*MONTH), 8.0, 20.5);
711 precipitation = long_low(season(summer, MONTH), 5.0, 90.0);
712 relative_humidity = sinusoidal(humidity, 48.0, 62.0);
713 break;
714 case 26: // Dwa: snow, winter dry, hot summer
715 temp_night = sinusoidal(season(summer, MONTH), -18.0, 16.5);
716 temp_day = sinusoidal(season(summer, MONTH), -5.0, 25.0);
717 temp_water = sinusoidal(season(summer, 2.0*MONTH), 0.0, 23.5);
718 precipitation = long_low(season(summer, 1.5*MONTH), 5.0, 180.0);
719 relative_humidity = sinusoidal(humidity, 60.0, 68.0);
720 break;
721 case 27: // Dwb: snow, winter dry, warm summer
722 temp_night = sinusoidal(season(summer, MONTH), -28.0, 10.0);
723 temp_day = sinusoidal(season(summer, MONTH), -12.5, 22.5);
724 temp_water = sinusoidal(season(summer, 2.0*MONTH), -5.0, 18.5);
725 precipitation = long_low(season(summer, 1.5*MONTH), 10.0, 140.0);
726 relative_humidity = sinusoidal(humidity, 60.0, 72.0);
727 break;
728 case 28: // Dwc: snow, winter dry, cool summer
729 temp_night = sinusoidal(season(summer, MONTH), -33.0, 5.0);
730 temp_day = sinusoidal(season(summer, MONTH), -20.0, 20.0);
731 temp_water = sinusoidal(season(summer, 2.0*MONTH), -10.0, 16.5);
732 precipitation = long_low(season(summer, 1.5*MONTH), 10.0, 110.0);
733 relative_humidity = sinusoidal(humidity, 60.0, 78.0);
734 break;
735 case 29: // Dwd: snow, winter dry, extremely continental
736 temp_night = sinusoidal(season(summer, MONTH), -57.5, 0.0);
737 temp_day = sinusoidal(season(summer, MONTH), -43.0, 15.0);
738 temp_water = sinusoidal(season(summer, 2.0*MONTH), -28.0, 11.5);
739 precipitation = sinusoidal(season(summer, 1.5*MONTH), 8.0, 63.0);
740 relative_humidity = 80.0;
741 break;
742 default:
743 break;
744 }
745
746 _set(_gl.temperature, linear(daytime(day, 3.0*HOUR), temp_night, temp_day));
747 _set(_gl.temperature_mean, linear(_day_light, temp_night, temp_day));
748 _set(_gl.temperature_water, temp_water);
749
750 _set(_gl.relative_humidity, relative_humidity);
751 _set(_gl.precipitation, precipitation);
752
753 _gl.has_autumn = true;
754}
755
756void FGClimate::set_polar()
757{
758 double day = _day_noon;
759
760 double summer = _season_summer;
761 double winter = -summer;
762
763 double hmin = sinusoidal(summer, 0.0, 0.36);
764 double hmax = sinusoidal(season(winter), 0.86, 1.0);
765 double humidity = linear(daytime(day, -9.0*HOUR), hmin, hmax);
766
767 // polar climate also occurs high in the mountains
768 double temp_water = _gl.temperature_water;
769 double temp_day = _sl.temperature;
770 double temp_night = _sl.temperature;
771 double precipitation = _gl.precipitation;
772 double relative_humidity = _gl.relative_humidity;
773 switch(_code)
774 {
775 case 30: // EF: polar frost
776 temp_night = long_low(season(summer, MONTH), -35.0, -6.0);
777 temp_day = long_low(season(summer, MONTH), -32.5, 0.0);
778 temp_water = long_low(season(summer, 2.0*MONTH), -27.5, -3.5);
779 precipitation = linear(season(summer, 2.5*MONTH), 50.0, 80.0);
780 relative_humidity = long_low(humidity, 65.0, 75.0);
781 _wind_speed = 5.5;
782 break;
783 case 31: // ET: polar tundra
784 temp_night = sinusoidal(season(summer, MONTH), -30.0, 0.0);
785 temp_day = sinusoidal(season(summer, 1.5*MONTH), -22.5, 8.0);
786 temp_water = sinusoidal(season(summer, 2.0*MONTH), -15.0, 5.0);
787 precipitation = sinusoidal(season(summer, 2.0*MONTH), 15.0, 45.0);
788 relative_humidity = sinusoidal(humidity, 60.0, 88.0);
789 _wind_speed = 4.0;
790 break;
791 default:
792 break;
793 }
794
795 _set(_gl.temperature, linear(daytime(day, 3.0*HOUR), temp_night, temp_day));
796 _set(_gl.temperature_mean, linear(_day_light, temp_night, temp_day));
797 _set(_gl.temperature_water, temp_water);
798
799 _set(_gl.relative_humidity, relative_humidity);
800 _set(_gl.precipitation, precipitation);
801
802 _gl.has_autumn = true;
803}
804
805void FGClimate::set_environment()
806{
807 double snow_fact, precipitation;
808
809 _set(_snow_level, 1000.0*_sl.temperature_mean/9.8);
810
811 // rough estimate
812 // Mist forms when the relative humidity is greater than about 70%
813 // Fog forms when the difference between air temperature and dew point
814 // is less than 2.5°C
815 double humidity, diff_temp_dewpoint;
816 if (1 || !_weather_update)
817 {
818 humidity = fgGetDouble("/environment/relative-humidity", 0.0);
819 diff_temp_dewpoint = fgGetDouble("/environment/temperature-degc", 0.0) -
820 fgGetDouble("/environment/dewpoint-degc", 0.0);
821 }
822 else
823 {
824 humidity = _gl.relative_humidity;
825 diff_temp_dewpoint = _gl.temperature - _gl.dewpoint;
826 }
827
828 _mist = pow(4.17*std::max(0.01*humidity-0.76, 0.0), 0.03);
829 _fog = exp(-1.842*diff_temp_dewpoint);
830
831 double val = 0.01*_mist + 0.99*_fog;
832 _visibility_m = _max_visibility_m*(1.0 - val);
833
834 // snow chance based on latitude, mean temperature and monthly precipitation
835 if (_gl.precipitation < 60.0) {
836 precipitation = 0.0;
837 }
838 else
839 {
840 precipitation = _gl.precipitation - 60.0;
841 precipitation = (precipitation > 240.0) ? 1.0 : precipitation/240.0;
842 }
843
844 if (_gl.temperature_mean > 5.0 || precipitation < 0.1) {
845 snow_fact = 0.0;
846 }
847 else
848 {
849 snow_fact = fabs(_gl.temperature_mean) - 5.0;
850 snow_fact = (snow_fact > 10.0) ? 1.0 : snow_fact/10.0;
851 snow_fact *= precipitation;
852 }
853
854 // Sea water will start to freeze at -2° water temperature.
855 if (_is_autumn < 0.95 || _sl.temperature_mean > -2.0) {
856 _set(_ice_cover, 0.0);
857 } else {
858 _set(_ice_cover, std::min(-(_sl.temperature_water+2.0)/10.0, 1.0));
859 }
860
861 // Inland water will start to freeze at 0⁰ water temperature.
862 if (_gl.temperature_mean > 0.5) {
863 _inland_ice_cover = false;
864 } else {
865 _inland_ice_cover = true;
866 }
867
868 // less than 20 mm/month of precipitation is considered dry.
869 if (_gl.precipitation < 20.0 || _gl.precipitation_annual < 240.0)
870 {
871 _set(_dust_cover, 0.3 - 0.3*sqrtf(_gl.precipitation/20.0));
872 _set(_snow_thickness, 0.0);
873 _set(_lichen_cover, 0.0);
874 _set(_wetness, 0.0);
875 }
876 else
877 {
878 _dust_cover = 0.0;
879
880 if (_gl.temperature_mean < 5.0)
881 {
882 double wetness = 12.0*_gl.precipitation/990.0;
883 wetness = pow(sin(atan(SGD_PI*wetness)), 2.0);
884 _snow_thickness = wetness;
885 } else {
886 _snow_thickness = 0.0;
887 }
888
889 if (_gl.temperature_mean > 0.0)
890 {
891 // https://weatherins.com/rain-guidelines/
892 _wetness = 9.0*std::max(_gl.precipitation-50.0, 0.0)/990.0;
893
894 double diff = std::max(_gl.temperature - _gl.dewpoint, 2.5)/2.5;
895 _wetness = std::min(_wetness/diff, 1.0);
896 } else {
897 _wetness = 0.0;
898 }
899
900 double cover = std::min(_gl.precipitation_annual, 990.0)/990.0;
901 _set(_lichen_cover, 0.5*pow(_wetness*cover, 1.5));
902 }
903
904 _rootNode->setDoubleValue("daytime-pct", _daytime*100.0);
905 _rootNode->setDoubleValue("daylight-pct", _day_light*100.0);
906 _rootNode->setDoubleValue("day-noon-pct", _day_noon*100.0);
907 _rootNode->setDoubleValue("season-summer-pct", _season_summer*100.0);
908 _rootNode->setDoubleValue("seasons-year-pct", _seasons_year*100.0);
909 _rootNode->setDoubleValue("season-autumn-pct", ((_gl.has_autumn && _is_autumn > 0.05) ? _season_transistional*100.0 : 0.0));
910
911#if 0
912 if (_weather_update)
913 {
914 fgSetDouble("/environment/relative-humidity", _gl.relative_humidity);
915 fgSetDouble("/environment/dewpoint-sea-level-degc", _sl.dewpoint);
916 fgSetDouble("/environment/dewpoint-degc", _gl.dewpoint);
917 fgSetDouble("/environment/temperature-sea-level-degc", _sl.temperature);
918 fgSetDouble("/environment/temperature-degc", _gl.temperature);
919 fgSetDouble("/environment/wind-speed-mps", _wind_speed);
920 }
921#endif
922
923 if (_environment_adjust)
924 {
925 if (!_metarSnowLevelNode->getBoolValue()) {
926 fgSetDouble("/environment/snow-level-m", _snow_level);
927 }
928 fgSetDouble("/environment/surface/snow-thickness-factor", _snow_thickness);
929 fgSetBool("/environment/surface/ice-cover", _inland_ice_cover);
930 fgSetDouble("/environment/sea/surface/ice-cover", _ice_cover);
931 fgSetDouble("/environment/surface/dust-cover-factor", _dust_cover);
932 fgSetDouble("/environment/surface/wetness-set", _wetness);
933 fgSetDouble("/environment/surface/lichen-cover-factor", _lichen_cover);
934
935 double season = 1.7*std::pow(_season_transistional, 4.0);
936 if (_gl.has_autumn && season > 0.01)
937 {
938 if (_is_autumn > 0.01) { // autumn
939 fgSetDouble("/environment/season", _is_autumn*season);
940 } else { // spring
941 fgSetDouble("/environment/season", 1.5+0.25*season);
942 }
943 }
944 else
945 {
946 fgSetDouble("/environment/season", 0.0);
947 }
948 }
949
950 // for material animation
951 double latitude_deg = _positionLatitudeNode->getDoubleValue();
952 double desert_pct = std::min( fabs(latitude_deg-23.5)/23.5, 1.0);
953 fgSetDouble("/sim/rendering/desert", 0.1-0.2*desert_pct);
954}
955
957{
958 _environment_adjust = value;
959
960 if (_metarSnowLevelNode)
961 {
962 if (value) {
963 update(0.0);
964 }
965 else
966 {
967 if (!_metarSnowLevelNode->getBoolValue()) {
968 fgSetDouble("/environment/snow-level-m", 8000.0);
969 }
970 fgSetDouble("/environment/surface/snow-thickness-factor", 0.0);
971 fgSetDouble("/environment/sea/surface/ice-cover", 0.0);
972 fgSetDouble("/environment/surface/dust-cover-factor", 0.0);
973 fgSetDouble("/environment/surface/wetness-set", 0.0);
974 fgSetDouble("/environment/surface/lichen-cover-factor", 0.0);
975 fgSetDouble("/environment/season", 0.0);
976 }
977 }
978}
979
980const char* FGClimate::get_metar() const
981{
982 std::stringstream m;
983
984 m << std::fixed << std::setprecision(0);
985 m << "XXXX ";
986
987 m << std::setw(2) << std::setfill('0') << fgGetInt("/sim/time/utc/day");
988 m << std::setw(2) << std::setfill('0') << fgGetInt("/sim/time/utc/hour");
989 m << std::setw(2) << std::setfill('0') << fgGetInt("/sim/time/utc/minute");
990 m << "Z AUTO ";
991
992 m << std::setw(3) << std::setfill('0') << _wind_direction;
993 m << std::setw(2) << std::setfill('0') << _wind_speed << "MPS ";
994
995 if (_gl.temperature < 0.0) {
996 m << "M" << std::setw(2) << std::setfill('0') << fabs(_gl.temperature);
997 } else {
998 m << std::setw(2) << std::setfill('0') << _gl.temperature;
999 }
1000
1001 m << "/";
1002
1003 if (_gl.dewpoint < 0.0) {
1004 m << "M" << std::setw(2) << std::setfill('0') << fabs(_gl.dewpoint);
1005 } else {
1006 m << std::setw(2) << std::setfill('0') << _gl.dewpoint;
1007 }
1008
1009 m << " Q" << _sl.pressure;
1010
1011 m << " NOSIG";
1012
1013 char *rv = std::strncpy((char*)&_metar, m.str().c_str(), 255);
1014 rv[255] = '\0';
1015
1016 return rv;
1017}
1018
1019// ---------------------------------------------------------------------------
1020
1021const std::string FGClimate::_classification[MAX_CLIMATE_CLASSES] = {
1022 "Ocean",
1023 "Af", "Am", "As", "Aw",
1024 "BSh", "BSk", "BWh", "BWk",
1025 "Cfa", "Cfb", "Cfc",
1026 "Csa", "Csb", "Csc",
1027 "Cwa", "Cwb", "Cwc",
1028 "Dfa", "Dfb", "Dfc", "Dfd",
1029 "Dsa", "Dsb", "Dsc", "Dsd",
1030 "Dwa", "Dwb", "Dwc", "Dwd",
1031 "EF", "ET"
1032};
1033
1034const std::string FGClimate::_description[MAX_CLIMATE_CLASSES] = {
1035 "Ocean",
1036 "Equatorial, fully humid",
1037 "Equatorial, monsoonal",
1038 "Equatorial, summer dry",
1039 "Equatorial, winter dry",
1040 "Arid, steppe, hot arid",
1041 "Arid, steppe, cold arid",
1042 "Arid, desert, hot arid",
1043 "Arid, desert, cold arid",
1044 "Warm temperature, fully humid hot summer",
1045 "Warm temperature, fully humid, warm summer",
1046 "Warm temperature, fully humid, cool summer",
1047 "Warm temperature, summer dry, hot summer",
1048 "Warm temperature, summer dry, warm summer",
1049 "Warm temperature, summer dry, cool summer",
1050 "Warm temperature, winter dry, hot summer",
1051 "Warm temperature, winter dry, warm summer",
1052 "Warm temperature, winter dry, cool summer",
1053 "Snow, fully humid, hot summer",
1054 "Snow, fully humid, warm summer",
1055 "Snow, fully humid, cool summer",
1056 "Snow, fully humid, extremely continetal",
1057 "Snow, summer dry, hot summer",
1058 "Snow, summer dry, warm summer",
1059 "Snow, summer dry, cool summer",
1060 "Snow, summer dry, extremely continetal",
1061 "Snow, winter dry, hot summer",
1062 "Snow, winter dry, warm summer",
1063 "Snow, winter dry, cool summer",
1064 "Snow, winter dry, extremely continetal",
1065 "Polar frost",
1066 "Polar tundra"
1067};
1068
1069
1070// interpolators
1071double FGClimate::daytime(double val, double offset)
1072{
1073 val = fmod(fabs(_daytime - offset), 1.0);
1074 val = (val > 0.5) ? 2.0 - 2.0*val : 2.0*val;
1075 return val;
1076}
1077
1078double FGClimate::season(double val, double offset)
1079{
1080 val = (val >= 0.0) ? fmod(fabs(_seasons_year - offset), 1.0)
1081 : fmod(fabs(1.0 - _seasons_year + offset), 1.0);
1082 val = (val > 0.5) ? 2.0 - 2.0*val : 2.0*val;
1083 return val;
1084}
1085
1086// val is the progress indicator between 0.0 and 1.0
1087double FGClimate::linear(double val, double min, double max)
1088{
1089 double diff = max-min;
1090 return min + val*diff;
1091}
1092
1093// google: y=1-abs(-1+2*x)
1094// low season is around 0.0 and 1.0, high season around 0.5
1095double FGClimate::triangular(double val, double min, double max)
1096{
1097 double diff = max-min;
1098 val = 1.0 - fabs(-1.0 + 2.0*val);
1099 return min + val*diff;
1100}
1101
1102
1103// google: y=0.5-0.5*cos(x)
1104// the short low season is round 0.0, the short high season around 1.0
1105double FGClimate::sinusoidal(double val, double min, double max)
1106{
1107 double diff = max-min;
1108 return min + diff*(0.5 - 0.5*cos(SGD_PI*val));
1109}
1110
1111// google: y=0.5-0.6366*atan(cos(x))
1112// the medium long low season is round 0.0, medium long high season round 1.0
1113double FGClimate::even(double val, double min, double max)
1114{
1115 double diff = max-min;
1116 return min + diff*(0.5 - 0.6366*atan(cos(SGD_PI*val)));
1117}
1118
1119// google: y=0.5-0.5*cos(x^1.5)
1120// 2.145 = pow(SGD_PI, 1.0/1.5)
1121// the long low season is around 0.0, the short high season around 1.0
1122double FGClimate::long_low(double val, double min, double max)
1123{
1124 double diff = max-min;
1125 return min + diff*(0.5 - 0.5*cos(pow(2.145*val, 1.5)));
1126}
1127
1128// google: y=0.5+0.5*cos(x^1.5)
1129// 2.14503 = pow(SGD_PI, 1.0/1.5)
1130// the long high season is around 0.0, the short low season around 1.0
1131double FGClimate::long_high(double val, double min, double max)
1132{
1133 double diff = max-min;
1134 return max - diff*(0.5 - 0.5*cos(pow(2.14503 - 2.14503*val, 1.5)));
1135}
1136
1137// goole: y=cos(atan(x*x))
1138// the monsoon is around 0.0
1139double FGClimate::monsoonal(double val, double min, double max)
1140{
1141 double diff = max-min;
1142 val = 2.0*SGD_2PI*(1.0-val);
1143 return min + diff*cos(atan(val*val));
1144}
1145
1147{
1148 double offs = 0.25;
1149
1150 printf("month:\t\t");
1151 for (int month=0; month<12; ++month) {
1152 printf("%5i", month+1);
1153 }
1154
1155 printf("\nlinear:\t\t");
1156 for (int month=0; month<12; ++month) {
1157 double val = (month > 6) ? (12 - month)/6.0 : month/6.0;
1158 _seasons_year = month/12.0;
1159 printf(" %-3.2f", linear(season(val, offs), 0.0, 1.0) );
1160 }
1161
1162 printf("\ntriangular:\t");
1163 for (int month=0; month<12; ++month) {
1164 double val = (month > 6) ? (12 - month)/6.0 : month/6.0;
1165 _seasons_year = month/12.0;
1166 printf(" %-3.2f", triangular(season(val, offs), 0.0, 1.0) );
1167 }
1168
1169 printf("\neven:\t\t");
1170 for (int month=0; month<12; ++month) {
1171 double val = (month > 6) ? (12 - month)/6.0 : month/6.0;
1172 _seasons_year = month/12.0;
1173 printf(" %-3.2f", sinusoidal(season(val, offs), 0.0, 1.0) );
1174 }
1175
1176 printf("\nlong:\t\t");
1177 for (int month=0; month<12; ++month) {
1178 double val = (month > 6) ? (12 - month)/6.0 : month/6.0;
1179 _seasons_year = month/12.0;
1180 printf(" %-3.2f", even(season(val, offs), 0.0, 1.0) );
1181 }
1182
1183 printf("\nlong low:\t");
1184 for (int month=0; month<12; ++month) {
1185 double val = (month > 6) ? (12 - month)/6.0 : month/6.0;
1186 _seasons_year = month/12.0;
1187 printf(" %-3.2f", long_low(season(val, offs), 0.0, 1.0) );
1188 }
1189
1190 printf("\nlong high:\t");
1191 for (int month=0; month<12; ++month) {
1192 double val = (month > 6) ? (12 - month)/6.0 : month/6.0;
1193 _seasons_year = month/12.0;
1194 printf(" %-3.2f", long_high(season(val, offs), 0.0, 1.0) );
1195 }
1196
1197 printf("\nmonsoonal:\t");
1198 for (int month=0; month<12; ++month) {
1199 double val = (month > 6) ? (12 - month)/6.0 : month/6.0;
1200 _seasons_year = month/12.0;
1201 printf(" %-3.2f", monsoonal(season(val, offs), 0.0, 1.0) );
1202 }
1203 printf("\n");
1204}
1205
1206#if REPORT_TO_CONSOLE
1207void FGClimate::report()
1208{
1209 struct tm *t = globals->get_time_params()->getGmt();
1210
1211 std::cout << "===============================================" << std::endl;
1212 std::cout << "Climate report for:" << std::endl;
1213 std::cout << " Date: " << sgTimeFormatTime(t) << " GMT" << std::endl;
1214 std::cout << " Sun latitude: " << _sun_latitude_deg << " deg."
1215 << std::endl;
1216 std::cout << " Sun longitude: " << _sun_longitude_deg << " deg."
1217 << std::endl;
1218 std::cout << " Viewer latitude: "
1219 << _positionLatitudeNode->getDoubleValue() << " deg."
1220 << " (adjusted: " << _adj_latitude_deg << ")" << std::endl;
1221 std::cout << " Viewer longitude: "
1222 << _positionLongitudeNode->getDoubleValue() << " deg."
1223 << " (adjusted: " << _adj_longitude_deg << ")" << std::endl;
1224 std::cout << std::endl;
1225 std::cout << " Köppen classification: " << _classification[_code]
1226 << std::endl;
1227 std::cout << " Description: " << _description[_code]
1228 << std::endl << std::endl;
1229 std::cout << " Daylight: " << _day_light*100.0 << " %"
1230 << std::endl;
1231 std::cout << " Daytime: "<< _daytime*24.0 << " hours" << std::endl;
1232 std::cout << " Daytime...(0.0 = night .. 1.0 = noon): " << _day_noon
1233 << std::endl;
1234 std::cout << " Season (0.0 = winter .. 1.0 = summer): " << _season_summer
1235 << std::endl;
1236 std::cout << " Year (0.25 = spring .. 0.75 = autumn): " << _seasons_year
1237 << std::endl << std::endl;
1238 std::cout << " Dewpoint: " << _gl.dewpoint << " deg. C."
1239 << std::endl;
1240 std::cout << " Ground temperature: " << _gl.temperature << " deg. C."
1241 << std::endl;
1242 std::cout << " Sea level temperature: " << _sl.temperature << " deg. C."
1243 << std::endl;
1244 std::cout << " Ground Mean temp.: " << _gl.temperature_mean
1245 << " deg. C." << std::endl;
1246 std::cout << " Sea level mean temp.: " << _sl.temperature_mean
1247 << " deg. C." << std::endl;
1248 std::cout << " Seawater temperature: " << _sl.temperature_water
1249 << " deg. C." << std::endl;
1250
1251 std::cout << " Months precipitation: " << _gl.precipitation << " mm"
1252 << std::endl;
1253 std::cout << " Annual precipitation: " << _gl.precipitation_annual << " mm"
1254 << std::endl;
1255 std::cout << " Relative humidity: " << _gl.relative_humidity << " %"
1256 << std::endl;
1257 std::cout << " Ground Air Pressure: " << _gl.pressure << " hPa"
1258 << std::endl;
1259 std::cout << " Sea level Air Pressure: " << _sl.pressure << " hPa"
1260 << std::endl;
1261 std::cout << " Wind speed: " << _wind_speed << " m/s = "
1262 << _wind_speed*SG_MPS_TO_KT << " kt" << std::endl;
1263 std::cout << " Wind direction: " << _wind_direction << " degrees"
1264 << std::endl;
1265 std::cout << " Snow level: " << _snow_level << " m." << std::endl;
1266 std::cout << " Inland water bodies ice cover: "
1267 << _inland_ice_cover ? "yes" : "no" << std::endl;
1268 std::cout << " Snow thickness.(0.0 = thin .. 1.0 = thick): "
1269 << _snow_thickness << std::endl;
1270 std::cout << " Ice cover......(0.0 = none .. 1.0 = thick): " << _ice_cover
1271 << std::endl;
1272 std::cout << " Dust cover.....(0.0 = none .. 1.0 = dusty): " << _dust_cover
1273 << std::endl;
1274 std::cout << " Wetness........(0.0 = dry .. 1.0 = wet): " << _wetness
1275 << std::endl;
1276 std::cout << " Lichen cover...(0.0 = none .. 1.0 = mossy): "
1277 << _lichen_cover << std::endl;
1278 std::cout << " Season (0.0 = summer .. 1.0 = late autumn): "
1279 << ((_gl.has_autumn && _is_autumn > 0.05) ? _season_transistional : 0.0)
1280 << std::endl << std::endl;
1281 std::cout << " METAR: " << get_metar() << std::endl;
1282 std::cout << "===============================================" << std::endl;
1283}
1284#endif // REPORT_TO_CONSOLE
double latitude
Definition ADA.cxx:53
#define min(X, Y)
void bind() override
Definition climate.cxx:71
void test()
Definition climate.cxx:1146
const char * get_metar() const
Definition climate.cxx:980
void setEnvironmentUpdate(bool value)
Definition climate.cxx:956
void init() override
Definition climate.cxx:61
bool getEnvironmentUpdate() const
Definition climate.hxx:98
void reinit() override
Definition climate.cxx:114
void unbind() override
Definition climate.cxx:109
void update(double dt) override
Definition climate.cxx:136
Manage environment information.
virtual FGEnvironment getEnvironment() const
Get the environment information for the plane's current position.
Model the natural environment.
virtual bool set_live_update(bool live_update)
virtual void set_dewpoint_degc(double d)
virtual double get_relative_humidity() const
virtual void set_is_isa(bool isa)
virtual double get_dewpoint_sea_level_degc() const
virtual bool get_is_isa() const
virtual double get_temperature_sea_level_degc() const
virtual void set_elevation_ft(double elevation_ft)
virtual double get_pressure_sea_level_inhg() const
virtual void set_pressure_inhg(double p)
virtual void set_temperature_degc(double t)
#define MONTH
Definition climate.cxx:46
#define HOUR
Definition climate.cxx:45
#define MAX_CLIMATE_CLASSES
Definition climate.hxx:41
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
Definition fg_props.cxx:532
FGGlobals * globals
Definition globals.cxx:142
#define M
const double P0(101325.0)
const double T0(15.+freezing)
const double g(9.80665)
const double inHg(101325.0/760 *1000 *inch)
const double mbar(100.)
float abs(float f)
Definition Airplane.cpp:21
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
Definition proptest.cpp:31
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
Definition proptest.cpp:24
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
void report(Airplane *a)