1 // Build a cloud layer based on metar
3 // Written by Harald JOHNSEN, started April 2005.
5 // Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
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.
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.
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, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
23 #include <Main/fg_props.hxx>
25 #include <simgear/constants.h>
26 #include <simgear/sound/soundmgr_openal.hxx>
27 #include <simgear/scene/sky/sky.hxx>
28 #include <simgear/environment/visual_enviro.hxx>
29 #include <simgear/scene/sky/cloudfield.hxx>
30 #include <simgear/scene/sky/newcloud.hxx>
31 #include <simgear/math/sg_random.h>
32 #include <Main/globals.hxx>
33 #include <Airports/simple.hxx>
34 #include <Main/util.hxx>
36 #include "environment_ctrl.hxx"
37 #include "environment_mgr.hxx"
38 #include "fgmetar.hxx"
39 #include "fgclouds.hxx"
44 FGClouds::FGClouds(FGEnvironmentCtrl * controller) :
45 station_elevation_ft(0.0),
46 _controller( controller ),
48 last_scenario( "none" ),
49 last_env_config( new SGPropertyNode() ),
50 last_env_clouds( new SGPropertyNode() )
53 fgSetString("/environment/weather-scenario", last_scenario.c_str());
55 FGClouds::~FGClouds() {
58 int FGClouds::get_update_event(void) const {
61 void FGClouds::set_update_event(int count) {
66 void FGClouds::init(void) {
67 if( snd_lightning == NULL ) {
68 snd_lightning = new SGSoundSample(globals->get_fg_root().c_str(), "Sounds/thunder.wav");
69 snd_lightning->set_max_dist(7000.0f);
70 snd_lightning->set_reference_dist(3000.0f);
71 SGSoundMgr *soundMgr = globals->get_soundmgr();
72 soundMgr->add( snd_lightning, "thunder" );
73 sgEnviro.set_soundMgr( soundMgr );
77 SGNewCloud *FGClouds::buildCloud(SGPropertyNode *cloud_def_root, const string& name) {
78 SGPropertyNode *cld_def=NULL;
80 cld_def = cloud_def_root->getChild(name.c_str());
81 string base_name = name.substr(0,2);
83 if( name[2] == '-' ) {
84 cld_def = cloud_def_root->getChild(base_name.c_str());
89 string familly = cld_def->getStringValue("familly", base_name.c_str());
90 SGNewCloud *cld = new SGNewCloud(familly);
91 for(int i = 0; i < cld_def->nChildren() ; i++) {
92 SGPropertyNode *abox = cld_def->getChild(i);
93 if( strcmp(abox->getName(), "box") == 0) {
94 double x = abox->getDoubleValue("x");
95 double y = abox->getDoubleValue("y");
96 double z = abox->getDoubleValue("z");
97 double size = abox->getDoubleValue("size");
98 int type = abox->getIntValue("type", SGNewCloud::CLbox_standard);
99 cld->addContainer(x, y, z, size, (SGNewCloud::CLbox_type) type);
106 void FGClouds::buildLayer(SGCloudField *layer, const string& name, double alt, double coverage) {
111 int CloudVarietyCount = 0;
112 double totalCount = 0.0;
114 SGPropertyNode *cloud_def_root = fgGetNode("/environment/cloudlayers/clouds", false);
115 SGPropertyNode *layer_def_root = fgGetNode("/environment/cloudlayers/layers", false);
118 // when we don't generate clouds the layer is rendered in 2D
119 if( coverage == 0.0 )
121 if( layer_def_root == NULL || cloud_def_root == NULL)
123 if( name == "ci" || name == "sc" || name == "st")
126 SGPropertyNode *layer_def=NULL;
128 layer_def = layer_def_root->getChild(name.c_str());
130 if( name[2] == '-' ) {
131 string base_name = name.substr(0,2);
132 layer_def = layer_def_root->getChild(base_name.c_str());
138 double grid_x_size = layer_def->getDoubleValue("grid-x-size", 1000.0);
139 double grid_y_size = layer_def->getDoubleValue("grid-y-size", 1000.0);
140 double grid_x_rand = layer_def->getDoubleValue("grid-x-rand", grid_x_size);
141 double grid_y_rand = layer_def->getDoubleValue("grid-y-rand", grid_y_size);
142 double grid_z_rand = layer_def->getDoubleValue("grid-z-rand");
144 for(int i = 0; i < layer_def->nChildren() ; i++) {
145 SGPropertyNode *acloud = layer_def->getChild(i);
146 if( strcmp(acloud->getName(), "cloud") == 0) {
147 string cloud_name = acloud->getStringValue("name");
148 tCloudVariety[CloudVarietyCount].name = cloud_name;
149 double count = acloud->getDoubleValue("count", 1.0);
150 tCloudVariety[CloudVarietyCount].count = count;
152 cloud_name = cloud_name + "-%d";
153 char variety_name[50];
156 snprintf(variety_name, sizeof(variety_name), cloud_name.c_str(), variety);
157 } while( cloud_def_root->getChild(variety_name, 0, false) );
160 if( CloudVarietyCount < 20 )
164 totalCount = 1.0 / totalCount;
165 double currCoverage = 0.0;
167 for(double px = 0.0; px < SGCloudField::fieldSize; px += grid_x_size) {
168 for(double py = 0.0; py < SGCloudField::fieldSize; py += grid_y_size) {
169 double x = px + grid_x_rand * (sg_random() - 0.5);
170 double y = py + grid_y_rand * (sg_random() - 0.5);
171 double z = alt + grid_z_rand * (sg_random() - 0.5);
172 double choice = sg_random();
173 currCoverage += coverage;
174 if( currCoverage < 1.0 )
178 for(int i = 0; i < CloudVarietyCount ; i ++) {
179 choice -= tCloudVariety[i].count * totalCount;
180 if( choice <= 0.0 ) {
181 SGNewCloud *cld = buildCloud(cloud_def_root, tCloudVariety[i].name);
184 layer->addCloud(pos, cld);
194 // TODO:call this after real metar updates
195 void FGClouds::buildMETAR(void) {
196 SGPropertyNode *metar_root = fgGetNode("/environment", true);
198 double wind_speed_kt = metar_root->getDoubleValue("wind-speed-kt");
199 double temperature_degc = metar_root->getDoubleValue("temperature-sea-level-degc");
200 double dewpoint_degc = metar_root->getDoubleValue("dewpoint-sea-level-degc");
201 double pressure_mb = metar_root->getDoubleValue("pressure-sea-level-inhg") * SG_INHG_TO_PA / 100.0;
203 double dewp = pow(10.0, 7.5 * dewpoint_degc / (237.7 + dewpoint_degc));
204 double temp = pow(10.0, 7.5 * temperature_degc / (237.7 + temperature_degc));
205 double rel_humidity = dewp * 100 / temp;
207 // formule d'Epsy, base d'un cumulus
208 double cumulus_base = 122.0 * (temperature_degc - dewpoint_degc);
209 double stratus_base = 100.0 * (100.0 - rel_humidity) * SG_FEET_TO_METER;
211 bool cu_seen = false;
213 for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
214 SGPropertyNode *cloud_root = fgGetNode("/environment/clouds/layer", iLayer, true);
216 double alt_ft = cloud_root->getDoubleValue("elevation-ft");
217 double alt_m = alt_ft * SG_FEET_TO_METER;
218 string coverage = cloud_root->getStringValue("coverage");
219 double coverage_norm = 0.0;
220 if( coverage == "few" )
221 coverage_norm = 2.0/8.0; // <1-2
222 else if( coverage == "scattered" )
223 coverage_norm = 4.0/8.0; // 3-4
224 else if( coverage == "broken" )
225 coverage_norm = 6.0/8.0; // 5-7
226 else if( coverage == "overcast" )
227 coverage_norm = 8.0/8.0; // 8
229 string layer_type = "nn";
230 if( coverage == "cirrus" ) {
232 } else if( alt_ft > 16500 ) {
233 // layer_type = "ci|cs|cc";
235 } else if( alt_ft > 6500 ) {
236 // layer_type = "as|ac|ns";
238 if( pressure_mb < 1005.0 && coverage_norm >= 5.5 )
241 // layer_type = "st|cu|cb|sc";
242 // +/- 20% from stratus probable base
243 if( stratus_base * 0.80 < alt_m && stratus_base * 1.40 > alt_m )
245 // +/- 20% from cumulus probable base
246 else if( cumulus_base * 0.80 < alt_m && cumulus_base * 1.20 > alt_m )
249 // above formulae is far from perfect
252 else if( alt_ft < 4500 )
259 SGCloudField *layer3D = thesky->get_cloud_layer(iLayer)->get_layer3D();
260 buildLayer(layer3D, layer_type, alt_m, coverage_norm);
264 // copy from FGMetarEnvironmentCtrl until better
266 FGClouds::update_metar_properties( const FGMetar *m )
272 fgSetString("/environment/metar/station-id", m->getId());
273 fgSetDouble("/environment/metar/min-visibility-m",
274 m->getMinVisibility().getVisibility_m() );
275 fgSetDouble("/environment/metar/max-visibility-m",
276 m->getMaxVisibility().getVisibility_m() );
278 const SGMetarVisibility *dirvis = m->getDirVisibility();
279 for (i = 0; i < 8; i++, dirvis++) {
280 const char *min = "/environment/metar/visibility[%d]/min-m";
281 const char *max = "/environment/metar/visibility[%d]/max-m";
283 d = dirvis->getVisibility_m();
285 snprintf(s, 128, min, i);
287 snprintf(s, 128, max, i);
291 fgSetInt("/environment/metar/base-wind-range-from",
292 m->getWindRangeFrom() );
293 fgSetInt("/environment/metar/base-wind-range-to",
294 m->getWindRangeTo() );
295 fgSetDouble("/environment/metar/base-wind-speed-kt",
296 m->getWindSpeed_kt() );
297 fgSetDouble("/environment/metar/gust-wind-speed-kt",
298 m->getGustSpeed_kt() );
299 fgSetDouble("/environment/metar/temperature-degc",
300 m->getTemperature_C() );
301 fgSetDouble("/environment/metar/dewpoint-degc",
302 m->getDewpoint_C() );
303 fgSetDouble("/environment/metar/rel-humidity-norm",
304 m->getRelHumidity() );
305 fgSetDouble("/environment/metar/pressure-inhg",
306 m->getPressure_inHg() );
308 vector<SGMetarCloud> cv = m->getClouds();
309 vector<SGMetarCloud>::const_iterator cloud;
311 const char *cl = "/environment/clouds/layer[%i]";
312 for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
313 const char *coverage_string[5] =
314 { "clear", "few", "scattered", "broken", "overcast" };
315 const double thickness[5] = { 0, 65, 600,750, 1000};
318 snprintf(s, 128, cl, i);
319 strncat(s, "/coverage", 128);
320 q = cloud->getCoverage();
321 fgSetString(s, coverage_string[q] );
323 snprintf(s, 128, cl, i);
324 strncat(s, "/elevation-ft", 128);
325 fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
327 snprintf(s, 128, cl, i);
328 strncat(s, "/thickness-ft", 128);
329 fgSetDouble(s, thickness[q]);
331 snprintf(s, 128, cl, i);
332 strncat(s, "/span-m", 128);
333 fgSetDouble(s, 40000.0);
336 for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
337 snprintf(s, 128, cl, i);
338 strncat(s, "/coverage", 128);
339 fgSetString(s, "clear");
341 snprintf(s, 128, cl, i);
342 strncat(s, "/elevation-ft", 128);
343 fgSetDouble(s, -9999);
345 snprintf(s, 128, cl, i);
346 strncat(s, "/thickness-ft", 128);
349 snprintf(s, 128, cl, i);
350 strncat(s, "/span-m", 128);
351 fgSetDouble(s, 40000.0);
354 fgSetDouble("/environment/metar/rain-norm", m->getRain());
355 fgSetDouble("/environment/metar/hail-norm", m->getHail());
356 fgSetDouble("/environment/metar/snow-norm", m->getSnow());
357 fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
361 FGClouds::update_env_config ()
363 fgSetupWind( fgGetDouble("/environment/metar/base-wind-range-from"),
364 fgGetDouble("/environment/metar/base-wind-range-to"),
365 fgGetDouble("/environment/metar/base-wind-speed-kt"),
366 fgGetDouble("/environment/metar/gust-wind-speed-kt") );
368 fgDefaultWeatherValue( "visibility-m",
369 fgGetDouble("/environment/metar/min-visibility-m") );
371 set_temp_at_altitude( fgGetDouble("/environment/metar/temperature-degc"),
372 station_elevation_ft );
373 set_dewpoint_at_altitude( fgGetDouble("/environment/metar/dewpoint-degc"),
374 station_elevation_ft );
376 fgDefaultWeatherValue( "pressure-sea-level-inhg",
377 fgGetDouble("/environment/metar/pressure-inhg") );
381 void FGClouds::setLayer( int iLayer, float alt_ft, const string& coverage, const string& layer_type ) {
382 double coverage_norm = 0.0;
383 if( coverage == "few" )
384 coverage_norm = 2.0/8.0; // <1-2
385 else if( coverage == "scattered" )
386 coverage_norm = 4.0/8.0; // 3-4
387 else if( coverage == "broken" )
388 coverage_norm = 6.0/8.0; // 5-7
389 else if( coverage == "overcast" )
390 coverage_norm = 8.0/8.0; // 8
392 SGCloudField *layer3D = thesky->get_cloud_layer(iLayer)->get_layer3D();
393 buildLayer(layer3D, layer_type, station_elevation_ft + alt_ft * SG_FEET_TO_METER, coverage_norm);
396 void FGClouds::buildScenario( const string& scenario ) {
398 string station = fgGetString("/environment/metar/station-id", "XXXX");
400 // fetch station elevation if exists
401 if( station == "XXXX" )
402 station_elevation_ft = fgGetDouble("/position/ground-elev-m", 0.0);
404 const FGAirport* a = globals->get_airports()->search( station );
405 station_elevation_ft = (a ? a->getElevation() : 0.0);
408 for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
409 thesky->get_cloud_layer(iLayer)->get_layer3D()->clear();
412 station += " 011000Z ";
413 if( scenario == "Fair weather" ) {
414 fakeMetar = "15003KT 12SM SCT033 FEW200 20/08 Q1015 NOSIG";
415 setLayer(0, 3300.0, "scattered", "cu");
416 } else if( scenario == "Thunderstorm" ) {
417 fakeMetar = "15012KT 08SM TSRA SCT040 BKN070 20/12 Q0995";
418 setLayer(0, 4000.0, "scattered", "cb");
419 setLayer(1, 7000.0, "scattered", "ns");
422 FGMetar *m = new FGMetar( station + fakeMetar );
423 update_metar_properties( m );
425 // propagate aloft tables
426 _controller->reinit();
428 fgSetString("/environment/metar/last-metar", m->getData());
429 // TODO:desactivate real metar updates
430 if( scenario == "Fair weather" ) {
431 fgSetString("/environment/clouds/layer[1]/coverage", "cirrus");
436 void FGClouds::build(void) {
437 string scenario = fgGetString("/environment/weather-scenario", "METAR");
439 if( scenario == last_scenario)
441 if( last_scenario == "none" ) {
442 // save clouds and weather conditions
443 SGPropertyNode *param = fgGetNode("/environment/config", true);
444 copyProperties( param, last_env_config );
445 param = fgGetNode("/environment/clouds", true);
446 copyProperties( param, last_env_clouds );
448 if( scenario == "METAR" ) {
449 string realMetar = fgGetString("/environment/metar/real-metar", "");
450 if( realMetar != "" ) {
451 fgSetString("/environment/metar/last-metar", realMetar.c_str());
452 FGMetar *m = new FGMetar( realMetar );
453 update_metar_properties( m );
455 // propagate aloft tables
456 _controller->reinit();
460 else if( scenario == "none" ) {
461 // restore clouds and weather conditions
462 SGPropertyNode *param = fgGetNode("/environment/config", true);
463 copyProperties( last_env_config, param );
464 param = fgGetNode("/environment/clouds", true);
465 copyProperties( last_env_clouds, param );
466 fgSetDouble("/environment/metar/rain-norm", 0.0);
467 fgSetDouble("/environment/metar/snow-norm", 0.0);
468 // update_env_config();
469 // propagate aloft tables
470 _controller->reinit();
474 buildScenario( scenario );
475 last_scenario = scenario;
478 if( snd_lightning == NULL )