0bb1494452
Attached is a patch to the airport data storage that I would like committed after review if acceptable. Currently the storage of airports mapped by ID is by locally created objects - about 12 Meg or so created on the stack if I am not mistaken. I've changed this to creating the airports on the heap, and storing pointers to them - see FGAirportList.add(...) in src/Airports/simple.cxx. I believe that this is probably better practice, and it's certainly cured some strange problems I was seeing when accessing the airport data with some gps unit code. Changes resulting from this have cascaded through a few files which access the data - 11 files are modified in all. Melchior and Durk - you might want to test this and shout if there are problems since the metar and traffic code are probably the biggest users of the airport data. I've also added a fuzzy search function that returns the next matching airport code in ASCII sequence in order to support gps units that have autocompletion of partially entered codes. More generally, the simple airport class seems to have grown a lot with the fairly recent addition of the parking, runway preference and schedule time code. It is no longer just an encapsulation of the global airport data file, and has grown to 552 bytes in size when unpopulated (about 1/2 a K!). My personal opinion is that we should look to just store the basic data in apt.dat for all global airports in a simple airport class, plus globally needed data (metar available?), and then have the traffic, AI and ATC subsystems create more advanced airports for themselves as needed in the area of interest. Once a significant number of airports worldwide have ground networks and parking defined, it will be impractical and unnecessary to store them all in memory. That's just a thought for the future though.
480 lines
16 KiB
C++
480 lines
16 KiB
C++
// Build a cloud layer based on metar
|
|
//
|
|
// Written by Harald JOHNSEN, started April 2005.
|
|
//
|
|
// Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
|
|
//
|
|
//
|
|
|
|
#include <Main/fg_props.hxx>
|
|
|
|
#include <simgear/constants.h>
|
|
#include <simgear/sound/soundmgr_openal.hxx>
|
|
#include <simgear/scene/sky/sky.hxx>
|
|
#include <simgear/environment/visual_enviro.hxx>
|
|
#include <simgear/scene/sky/cloudfield.hxx>
|
|
#include <simgear/scene/sky/newcloud.hxx>
|
|
#include <simgear/math/sg_random.h>
|
|
#include <Main/globals.hxx>
|
|
#include <Airports/simple.hxx>
|
|
#include <Main/util.hxx>
|
|
|
|
#include "environment_ctrl.hxx"
|
|
#include "environment_mgr.hxx"
|
|
#include "fgmetar.hxx"
|
|
#include "fgclouds.hxx"
|
|
|
|
extern SGSky *thesky;
|
|
|
|
|
|
FGClouds::FGClouds(FGEnvironmentCtrl * controller) :
|
|
station_elevation_ft(0.0),
|
|
_controller( controller ),
|
|
snd_lightning(NULL),
|
|
last_scenario( "none" ),
|
|
last_env_config( new SGPropertyNode() ),
|
|
last_env_clouds( new SGPropertyNode() )
|
|
{
|
|
update_event = 0;
|
|
fgSetString("/environment/weather-scenario", last_scenario.c_str());
|
|
}
|
|
FGClouds::~FGClouds() {
|
|
}
|
|
|
|
int FGClouds::get_update_event(void) const {
|
|
return update_event;
|
|
}
|
|
void FGClouds::set_update_event(int count) {
|
|
update_event = count;
|
|
build();
|
|
}
|
|
|
|
void FGClouds::init(void) {
|
|
if( snd_lightning == NULL ) {
|
|
snd_lightning = new SGSoundSample(globals->get_fg_root().c_str(), "Sounds/thunder.wav", true);
|
|
snd_lightning->set_max_dist(7000.0f);
|
|
snd_lightning->set_reference_dist(3000.0f);
|
|
SGSoundMgr *soundMgr = globals->get_soundmgr();
|
|
soundMgr->add( snd_lightning, "thunder" );
|
|
sgEnviro.set_soundMgr( soundMgr );
|
|
}
|
|
}
|
|
|
|
SGNewCloud *FGClouds::buildCloud(SGPropertyNode *cloud_def_root, string name) {
|
|
SGPropertyNode *cld_def=NULL;
|
|
|
|
cld_def = cloud_def_root->getChild(name.c_str());
|
|
string base_name = name.substr(0,2);
|
|
if( !cld_def ) {
|
|
if( name[2] == '-' ) {
|
|
cld_def = cloud_def_root->getChild(base_name.c_str());
|
|
}
|
|
if( !cld_def )
|
|
return NULL;
|
|
}
|
|
string familly = cld_def->getStringValue("familly", base_name.c_str());
|
|
SGNewCloud *cld = new SGNewCloud(familly);
|
|
for(int i = 0; i < cld_def->nChildren() ; i++) {
|
|
SGPropertyNode *abox = cld_def->getChild(i);
|
|
if( strcmp(abox->getName(), "box") == 0) {
|
|
double x = abox->getDoubleValue("x");
|
|
double y = abox->getDoubleValue("y");
|
|
double z = abox->getDoubleValue("z");
|
|
double size = abox->getDoubleValue("size");
|
|
int type = abox->getIntValue("type", SGNewCloud::CLbox_standard);
|
|
cld->addContainer(x, y, z, size, (SGNewCloud::CLbox_type) type);
|
|
}
|
|
}
|
|
cld->genSprites();
|
|
return cld;
|
|
}
|
|
|
|
void FGClouds::buildLayer(SGCloudField *layer, string name, double alt, double coverage) {
|
|
struct {
|
|
string name;
|
|
double count;
|
|
} tCloudVariety[20];
|
|
int CloudVarietyCount = 0;
|
|
double totalCount = 0.0;
|
|
|
|
SGPropertyNode *cloud_def_root = fgGetNode("/environment/cloudlayers/clouds", false);
|
|
SGPropertyNode *layer_def_root = fgGetNode("/environment/cloudlayers/layers", false);
|
|
|
|
layer->clear();
|
|
// when we don't generate clouds the layer is rendered in 2D
|
|
if( coverage == 0.0 )
|
|
return;
|
|
if( layer_def_root == NULL || cloud_def_root == NULL)
|
|
return;
|
|
if( name == "ci" || name == "sc" || name == "st")
|
|
return;
|
|
|
|
SGPropertyNode *layer_def=NULL;
|
|
|
|
layer_def = layer_def_root->getChild(name.c_str());
|
|
if( !layer_def ) {
|
|
if( name[2] == '-' ) {
|
|
string base_name = name.substr(0,2);
|
|
layer_def = layer_def_root->getChild(base_name.c_str());
|
|
}
|
|
if( !layer_def )
|
|
return;
|
|
}
|
|
|
|
double grid_x_size = layer_def->getDoubleValue("grid-x-size", 1000.0);
|
|
double grid_y_size = layer_def->getDoubleValue("grid-y-size", 1000.0);
|
|
double grid_x_rand = layer_def->getDoubleValue("grid-x-rand", grid_x_size);
|
|
double grid_y_rand = layer_def->getDoubleValue("grid-y-rand", grid_y_size);
|
|
double grid_z_rand = layer_def->getDoubleValue("grid-z-rand");
|
|
|
|
for(int i = 0; i < layer_def->nChildren() ; i++) {
|
|
SGPropertyNode *acloud = layer_def->getChild(i);
|
|
if( strcmp(acloud->getName(), "cloud") == 0) {
|
|
string cloud_name = acloud->getStringValue("name");
|
|
tCloudVariety[CloudVarietyCount].name = cloud_name;
|
|
double count = acloud->getDoubleValue("count", 1.0);
|
|
tCloudVariety[CloudVarietyCount].count = count;
|
|
int variety = 0;
|
|
cloud_name = cloud_name + "-%d";
|
|
char variety_name[50];
|
|
do {
|
|
variety++;
|
|
snprintf(variety_name, sizeof(variety_name), cloud_name.c_str(), variety);
|
|
} while( cloud_def_root->getChild(variety_name, 0, false) );
|
|
|
|
totalCount += count;
|
|
if( CloudVarietyCount < 20 )
|
|
CloudVarietyCount++;
|
|
}
|
|
}
|
|
totalCount = 1.0 / totalCount;
|
|
double currCoverage = 0.0;
|
|
|
|
for(double px = 0.0; px < SGCloudField::fieldSize; px += grid_x_size) {
|
|
for(double py = 0.0; py < SGCloudField::fieldSize; py += grid_y_size) {
|
|
double x = px + grid_x_rand * (sg_random() - 0.5);
|
|
double y = py + grid_y_rand * (sg_random() - 0.5);
|
|
double z = alt + grid_z_rand * (sg_random() - 0.5);
|
|
double choice = sg_random();
|
|
currCoverage += coverage;
|
|
if( currCoverage < 1.0 )
|
|
continue;
|
|
currCoverage -= 1.0;
|
|
|
|
for(int i = 0; i < CloudVarietyCount ; i ++) {
|
|
choice -= tCloudVariety[i].count * totalCount;
|
|
if( choice <= 0.0 ) {
|
|
SGNewCloud *cld = buildCloud(cloud_def_root, tCloudVariety[i].name);
|
|
sgVec3 pos={x,z,y};
|
|
if( cld )
|
|
layer->addCloud(pos, cld);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// TODO:call this after real metar updates
|
|
void FGClouds::buildMETAR(void) {
|
|
SGPropertyNode *metar_root = fgGetNode("/environment", true);
|
|
|
|
double wind_speed_kt = metar_root->getDoubleValue("wind-speed-kt");
|
|
double temperature_degc = metar_root->getDoubleValue("temperature-sea-level-degc");
|
|
double dewpoint_degc = metar_root->getDoubleValue("dewpoint-sea-level-degc");
|
|
double pressure_mb = metar_root->getDoubleValue("pressure-sea-level-inhg") * SG_INHG_TO_PA / 100.0;
|
|
|
|
double dewp = pow(10.0, 7.5 * dewpoint_degc / (237.7 + dewpoint_degc));
|
|
double temp = pow(10.0, 7.5 * temperature_degc / (237.7 + temperature_degc));
|
|
double rel_humidity = dewp * 100 / temp;
|
|
|
|
// formule d'Epsy, base d'un cumulus
|
|
double cumulus_base = 122.0 * (temperature_degc - dewpoint_degc);
|
|
double stratus_base = 100.0 * (100.0 - rel_humidity) * SG_FEET_TO_METER;
|
|
|
|
bool cu_seen = false;
|
|
|
|
for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
|
|
SGPropertyNode *cloud_root = fgGetNode("/environment/clouds/layer", iLayer, true);
|
|
|
|
double alt_ft = cloud_root->getDoubleValue("elevation-ft");
|
|
double alt_m = alt_ft * SG_FEET_TO_METER;
|
|
string coverage = cloud_root->getStringValue("coverage");
|
|
double coverage_norm = 0.0;
|
|
if( coverage == "few" )
|
|
coverage_norm = 2.0/8.0; // <1-2
|
|
else if( coverage == "scattered" )
|
|
coverage_norm = 4.0/8.0; // 3-4
|
|
else if( coverage == "broken" )
|
|
coverage_norm = 6.0/8.0; // 5-7
|
|
else if( coverage == "overcast" )
|
|
coverage_norm = 8.0/8.0; // 8
|
|
|
|
string layer_type = "nn";
|
|
if( coverage == "cirrus" ) {
|
|
layer_type = "ci";
|
|
} else if( alt_ft > 16500 ) {
|
|
// layer_type = "ci|cs|cc";
|
|
layer_type = "ci";
|
|
} else if( alt_ft > 6500 ) {
|
|
// layer_type = "as|ac|ns";
|
|
layer_type = "ac";
|
|
if( pressure_mb < 1005.0 && coverage_norm >= 5.5 )
|
|
layer_type = "ns";
|
|
} else {
|
|
// layer_type = "st|cu|cb|sc";
|
|
// +/- 20% from stratus probable base
|
|
if( stratus_base * 0.80 < alt_m && stratus_base * 1.40 > alt_m )
|
|
layer_type = "st";
|
|
// +/- 20% from cumulus probable base
|
|
else if( cumulus_base * 0.80 < alt_m && cumulus_base * 1.20 > alt_m )
|
|
layer_type = "cu";
|
|
else {
|
|
// above formulae is far from perfect
|
|
if ( alt_ft < 2000 )
|
|
layer_type = "st";
|
|
else if( alt_ft < 4500 )
|
|
layer_type = "cu";
|
|
else
|
|
layer_type = "sc";
|
|
}
|
|
}
|
|
|
|
SGCloudField *layer3D = thesky->get_cloud_layer(iLayer)->get_layer3D();
|
|
buildLayer(layer3D, layer_type, alt_m, coverage_norm);
|
|
}
|
|
}
|
|
|
|
// copy from FGMetarEnvironmentCtrl until better
|
|
void
|
|
FGClouds::update_metar_properties( FGMetar *m )
|
|
{
|
|
int i;
|
|
double d;
|
|
char s[128];
|
|
|
|
fgSetString("/environment/metar/station-id", m->getId());
|
|
fgSetDouble("/environment/metar/min-visibility-m",
|
|
m->getMinVisibility().getVisibility_m() );
|
|
fgSetDouble("/environment/metar/max-visibility-m",
|
|
m->getMaxVisibility().getVisibility_m() );
|
|
|
|
SGMetarVisibility *dirvis = m->getDirVisibility();
|
|
for (i = 0; i < 8; i++, dirvis++) {
|
|
const char *min = "/environment/metar/visibility[%d]/min-m";
|
|
const char *max = "/environment/metar/visibility[%d]/max-m";
|
|
|
|
d = dirvis->getVisibility_m();
|
|
|
|
snprintf(s, 128, min, i);
|
|
fgSetDouble(s, d);
|
|
snprintf(s, 128, max, i);
|
|
fgSetDouble(s, d);
|
|
}
|
|
|
|
fgSetInt("/environment/metar/base-wind-range-from",
|
|
m->getWindRangeFrom() );
|
|
fgSetInt("/environment/metar/base-wind-range-to",
|
|
m->getWindRangeTo() );
|
|
fgSetDouble("/environment/metar/base-wind-speed-kt",
|
|
m->getWindSpeed_kt() );
|
|
fgSetDouble("/environment/metar/gust-wind-speed-kt",
|
|
m->getGustSpeed_kt() );
|
|
fgSetDouble("/environment/metar/temperature-degc",
|
|
m->getTemperature_C() );
|
|
fgSetDouble("/environment/metar/dewpoint-degc",
|
|
m->getDewpoint_C() );
|
|
fgSetDouble("/environment/metar/rel-humidity-norm",
|
|
m->getRelHumidity() );
|
|
fgSetDouble("/environment/metar/pressure-inhg",
|
|
m->getPressure_inHg() );
|
|
|
|
vector<SGMetarCloud> cv = m->getClouds();
|
|
vector<SGMetarCloud>::iterator cloud;
|
|
|
|
const char *cl = "/environment/clouds/layer[%i]";
|
|
for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
|
|
const char *coverage_string[5] =
|
|
{ "clear", "few", "scattered", "broken", "overcast" };
|
|
const double thickness[5] = { 0, 65, 600,750, 1000};
|
|
int q;
|
|
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/coverage", 128);
|
|
q = cloud->getCoverage();
|
|
fgSetString(s, coverage_string[q] );
|
|
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/elevation-ft", 128);
|
|
fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
|
|
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/thickness-ft", 128);
|
|
fgSetDouble(s, thickness[q]);
|
|
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/span-m", 128);
|
|
fgSetDouble(s, 40000.0);
|
|
}
|
|
|
|
for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/coverage", 128);
|
|
fgSetString(s, "clear");
|
|
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/elevation-ft", 128);
|
|
fgSetDouble(s, -9999);
|
|
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/thickness-ft", 128);
|
|
fgSetDouble(s, 0);
|
|
|
|
snprintf(s, 128, cl, i);
|
|
strncat(s, "/span-m", 128);
|
|
fgSetDouble(s, 40000.0);
|
|
}
|
|
|
|
fgSetDouble("/environment/metar/rain-norm", m->getRain());
|
|
fgSetDouble("/environment/metar/hail-norm", m->getHail());
|
|
fgSetDouble("/environment/metar/snow-norm", m->getSnow());
|
|
fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
|
|
}
|
|
|
|
void
|
|
FGClouds::update_env_config ()
|
|
{
|
|
fgSetupWind( fgGetDouble("/environment/metar/base-wind-range-from"),
|
|
fgGetDouble("/environment/metar/base-wind-range-to"),
|
|
fgGetDouble("/environment/metar/base-wind-speed-kt"),
|
|
fgGetDouble("/environment/metar/gust-wind-speed-kt") );
|
|
|
|
fgDefaultWeatherValue( "visibility-m",
|
|
fgGetDouble("/environment/metar/min-visibility-m") );
|
|
#if 0
|
|
set_temp_at_altitude( fgGetDouble("/environment/metar/temperature-degc"),
|
|
station_elevation_ft );
|
|
set_dewpoint_at_altitude( fgGetDouble("/environment/metar/dewpoint-degc"),
|
|
station_elevation_ft );
|
|
#endif
|
|
fgDefaultWeatherValue( "pressure-sea-level-inhg",
|
|
fgGetDouble("/environment/metar/pressure-inhg") );
|
|
}
|
|
|
|
|
|
void FGClouds::setLayer( int iLayer, float alt_ft, string coverage, string layer_type ) {
|
|
double coverage_norm = 0.0;
|
|
if( coverage == "few" )
|
|
coverage_norm = 2.0/8.0; // <1-2
|
|
else if( coverage == "scattered" )
|
|
coverage_norm = 4.0/8.0; // 3-4
|
|
else if( coverage == "broken" )
|
|
coverage_norm = 6.0/8.0; // 5-7
|
|
else if( coverage == "overcast" )
|
|
coverage_norm = 8.0/8.0; // 8
|
|
|
|
SGCloudField *layer3D = thesky->get_cloud_layer(iLayer)->get_layer3D();
|
|
buildLayer(layer3D, layer_type, station_elevation_ft + alt_ft * SG_FEET_TO_METER, coverage_norm);
|
|
}
|
|
|
|
void FGClouds::buildScenario( string scenario ) {
|
|
string fakeMetar="";
|
|
string station = fgGetString("/environment/metar/station-id", "XXXX");
|
|
|
|
// fetch station elevation if exists
|
|
if( station == "XXXX" )
|
|
station_elevation_ft = fgGetDouble("/position/ground-elev-m", 0.0);
|
|
else {
|
|
const FGAirport* a = globals->get_airports()->search( station );
|
|
station_elevation_ft = (a ? a->getElevation() : 0.0);
|
|
}
|
|
|
|
for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
|
|
thesky->get_cloud_layer(iLayer)->get_layer3D()->clear();
|
|
}
|
|
|
|
station += " 011000Z ";
|
|
if( scenario == "Fair weather" ) {
|
|
fakeMetar = "15003KT 12SM SCT033 FEW200 20/08 Q1015 NOSIG";
|
|
setLayer(0, 3300.0, "scattered", "cu");
|
|
} else if( scenario == "Thunderstorm" ) {
|
|
fakeMetar = "15012KT 08SM TSRA SCT040 BKN070 20/12 Q0995";
|
|
setLayer(0, 4000.0, "scattered", "cb");
|
|
setLayer(1, 7000.0, "scattered", "ns");
|
|
} else
|
|
return;
|
|
FGMetar *m = new FGMetar( station + fakeMetar );
|
|
update_metar_properties( m );
|
|
update_env_config();
|
|
// propagate aloft tables
|
|
_controller->reinit();
|
|
|
|
fgSetString("/environment/metar/last-metar", m->getData());
|
|
// TODO:desactivate real metar updates
|
|
if( scenario == "Fair weather" ) {
|
|
fgSetString("/environment/clouds/layer[1]/coverage", "cirrus");
|
|
}
|
|
}
|
|
|
|
|
|
void FGClouds::build(void) {
|
|
string scenario = fgGetString("/environment/weather-scenario", "METAR");
|
|
|
|
if( scenario == last_scenario)
|
|
return;
|
|
if( last_scenario == "none" ) {
|
|
// save clouds and weather conditions
|
|
SGPropertyNode *param = fgGetNode("/environment/config", true);
|
|
copyProperties( param, last_env_config );
|
|
param = fgGetNode("/environment/clouds", true);
|
|
copyProperties( param, last_env_clouds );
|
|
}
|
|
if( scenario == "METAR" ) {
|
|
string realMetar = fgGetString("/environment/metar/real-metar", "");
|
|
if( realMetar != "" ) {
|
|
fgSetString("/environment/metar/last-metar", realMetar.c_str());
|
|
FGMetar *m = new FGMetar( realMetar );
|
|
update_metar_properties( m );
|
|
update_env_config();
|
|
// propagate aloft tables
|
|
_controller->reinit();
|
|
}
|
|
buildMETAR();
|
|
}
|
|
else if( scenario == "none" ) {
|
|
// restore clouds and weather conditions
|
|
SGPropertyNode *param = fgGetNode("/environment/config", true);
|
|
copyProperties( last_env_config, param );
|
|
param = fgGetNode("/environment/clouds", true);
|
|
copyProperties( last_env_clouds, param );
|
|
fgSetDouble("/environment/metar/rain-norm", 0.0);
|
|
fgSetDouble("/environment/metar/snow-norm", 0.0);
|
|
// update_env_config();
|
|
// propagate aloft tables
|
|
_controller->reinit();
|
|
buildMETAR();
|
|
}
|
|
else
|
|
buildScenario( scenario );
|
|
last_scenario = scenario;
|
|
|
|
// ...
|
|
if( snd_lightning == NULL )
|
|
init();
|
|
}
|