X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FEnvironment%2Fmetarproperties.cxx;h=1ebdd33b0da805cc6f8ad59ea6513560b7fca24b;hb=9745a6fce3de367cf29c37829b312090e84c2487;hp=f76ee6684086e5d9b0d8dd6252a76bb1387ccf59;hpb=5c6fe952598053fa63631fc0161d666f22a50f51;p=flightgear.git diff --git a/src/Environment/metarproperties.cxx b/src/Environment/metarproperties.cxx index f76ee6684..1ebdd33b0 100644 --- a/src/Environment/metarproperties.cxx +++ b/src/Environment/metarproperties.cxx @@ -20,17 +20,94 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // +#ifdef HAVE_CONFIG_H +# include +#endif + +#include // for strlen + #include "metarproperties.hxx" #include "fgmetar.hxx" #include "environment.hxx" #include "atmosphere.hxx" +#include "metarairportfilter.hxx" #include #include +#include +#include +#include +#include
using std::string; namespace Environment { +static vector coverage_string; + +/** + * @brief Helper class to wrap SGMagVar functionality and cache the variation and dip for + * a certain position. + */ +class MagneticVariation : public SGMagVar { +public: + /** + * Constructor + */ + MagneticVariation() : _lat(1), _lon(1), _alt(1) { + recalc( 0.0, 0.0, 0.0 ); + } + + /** + * @brief get the magnetic variation for a specific position at the current time + * @param lon the positions longitude in degrees + * @param lat the positions latitude in degrees + * @param alt the positions height above MSL (aka altitude) in feet + * @return the magnetic variation in degrees + */ + double get_variation_deg( double lon, double lat, double alt ); + + /** + * @brief get the magnetic dip for a specific position at the current time + * @param lon the positions longitude in degrees + * @param lat the positions latitude in degrees + * @param alt the positions height above MSL (aka altitude) in feet + * @return the magnetic dip in degrees + */ + double get_dip_deg( double lon, double lat, double alt ); +private: + void recalc( double lon, double lat, double alt ); + SGTime _time; + double _lat, _lon, _alt; +}; + +inline void MagneticVariation::recalc( double lon, double lat, double alt ) +{ + // calculation of magnetic variation is expensive. Cache the position + // and perform this calculation only if it has changed + if( _lon != lon || _lat != lat || _alt != alt ) { + SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "Recalculating magvar for lon=" << lon << ", lat=" << lat << ", alt=" << alt ); + _lon = lon; + _lat = lat; + _alt = alt; + + SGGeod location(SGGeod::fromDegFt(lon, lat, alt)); + _time.update( location, 0, 0 ); + update( lon, lat, alt, _time.getJD() ); + } +} + +inline double MagneticVariation::get_variation_deg( double lon, double lat, double alt ) +{ + recalc( lon, lat, alt ); + return get_magvar() * SGD_RADIANS_TO_DEGREES; +} + +inline double MagneticVariation::get_dip_deg( double lon, double lat, double alt ) +{ + recalc( lon, lat, alt ); + return get_magdip() * SGD_RADIANS_TO_DEGREES; +} + MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) : _rootNode(rootNode), _metarValidNode( rootNode->getNode( "valid", true ) ), @@ -56,24 +133,39 @@ MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) : _rain(0.0), _hail(0.0), _snow(0.0), - _snow_cover(false) + _snow_cover(false), + _day(0), + _hour(0), + _minute(0), + _cavok(false), + _magneticVariation(new MagneticVariation()) { + // Hack to avoid static initialization order problems on OSX + if( coverage_string.empty() ) { + coverage_string.push_back(SGCloudLayer::SG_CLOUD_CLEAR_STRING); + coverage_string.push_back(SGCloudLayer::SG_CLOUD_FEW_STRING); + coverage_string.push_back(SGCloudLayer::SG_CLOUD_SCATTERED_STRING); + coverage_string.push_back(SGCloudLayer::SG_CLOUD_BROKEN_STRING); + coverage_string.push_back(SGCloudLayer::SG_CLOUD_OVERCAST_STRING); + } // don't tie metar-valid, so listeners get triggered _metarValidNode->setBoolValue( false ); _tiedProperties.setRoot( _rootNode ); _tiedProperties.Tie("data", this, &MetarProperties::get_metar, &MetarProperties::set_metar ); - _tiedProperties.Tie("station-id", this, &MetarProperties::get_station_id ); + _tiedProperties.Tie("station-id", this, &MetarProperties::get_station_id, &MetarProperties::set_station_id ); _tiedProperties.Tie("station-elevation-ft", &_station_elevation ); _tiedProperties.Tie("station-latitude-deg", &_station_latitude ); _tiedProperties.Tie("station-longitude-deg", &_station_longitude ); + _tiedProperties.Tie("station-magnetic-variation-deg", this, &MetarProperties::get_magnetic_variation_deg ); + _tiedProperties.Tie("station-magnetic-dip-deg", this, &MetarProperties::get_magnetic_dip_deg ); _tiedProperties.Tie("min-visibility-m", &_min_visibility ); _tiedProperties.Tie("max-visibility-m", &_max_visibility ); _tiedProperties.Tie("base-wind-range-from", &_base_wind_range_from ); _tiedProperties.Tie("base-wind-range-to", &_base_wind_range_to ); - _tiedProperties.Tie("base-wind-speed-kt", &_wind_speed ); - _tiedProperties.Tie("base-wind-dir-deg", &_base_wind_dir ); - _tiedProperties.Tie("base-wind-from-north-fps", &_wind_from_north_fps ); - _tiedProperties.Tie("base-wind-from-east-fps", &_wind_from_east_fps ); + _tiedProperties.Tie("base-wind-speed-kt", this, &MetarProperties::get_wind_speed, &MetarProperties::set_wind_speed ); + _tiedProperties.Tie("base-wind-dir-deg", this, &MetarProperties::get_base_wind_dir, &MetarProperties::set_base_wind_dir ); + _tiedProperties.Tie("base-wind-from-north-fps", this, &MetarProperties::get_wind_from_north_fps, &MetarProperties::set_wind_from_north_fps ); + _tiedProperties.Tie("base-wind-from-east-fps",this, &MetarProperties::get_wind_from_east_fps, &MetarProperties::set_wind_from_east_fps ); _tiedProperties.Tie("gust-wind-speed-kt", &_gusts ); _tiedProperties.Tie("temperature-degc", &_temperature ); _tiedProperties.Tie("dewpoint-degc", &_dewpoint ); @@ -86,35 +178,65 @@ MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) : _tiedProperties.Tie("hail-norm", &_hail ); _tiedProperties.Tie("snow-norm", &_snow); _tiedProperties.Tie("snow-cover", &_snow_cover ); + _tiedProperties.Tie("day", &_day ); + _tiedProperties.Tie("hour", &_hour ); + _tiedProperties.Tie("minute", &_minute ); + _tiedProperties.Tie("decoded", this, &MetarProperties::get_decoded ); + _tiedProperties.Tie("cavok", &_cavok ); } MetarProperties::~MetarProperties() { + delete _magneticVariation; } -static const string coverage_string[] = { - SGCloudLayer::SG_CLOUD_CLEAR_STRING, - SGCloudLayer::SG_CLOUD_FEW_STRING, - SGCloudLayer::SG_CLOUD_SCATTERED_STRING, - SGCloudLayer::SG_CLOUD_BROKEN_STRING, - SGCloudLayer::SG_CLOUD_OVERCAST_STRING, -}; +void MetarProperties::invalidate() +{ + if( _metarValidNode->getBoolValue() ) + _metarValidNode->setBoolValue(false); +} static const double thickness_value[] = { 0, 65, 600, 750, 1000 }; -void MetarProperties::set_metar( const char * metar ) +const char* MetarProperties::get_metar() const +{ + if (!_metar) return ""; + return _metar->getData(); +} + +void MetarProperties::set_metar( const char * metarString ) { - _metar = metar; - SGSharedPtr m; + if ((metarString == NULL) || (strlen(metarString) == 0)) { + setMetar(m); + return; + } + try { - m = new FGMetar( _metar ); + m = new FGMetar( metarString ); } catch( sg_io_exception ) { - SG_LOG( SG_GENERAL, SG_WARN, "Can't parse metar: " << _metar ); + SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar: " << metarString ); _metarValidNode->setBoolValue(false); return; } + + setMetar(m); +} + +void MetarProperties::setMetar( SGSharedPtr m ) +{ + _metar = m; + _decoded.clear(); + if (!m) { + return; + } + + const vector weather = m->getWeather(); + for( vector::const_iterator it = weather.begin(); it != weather.end(); ++it ) { + if( false == _decoded.empty() ) _decoded.append(", "); + _decoded.append(*it); + } _min_visibility = m->getMinVisibility().getVisibility_m(); _max_visibility = m->getMaxVisibility().getVisibility_m(); @@ -128,14 +250,11 @@ void MetarProperties::set_metar( const char * metar ) vis->setDoubleValue("max-m", v); } - _base_wind_dir = m->getWindDir(); + set_base_wind_dir(m->getWindDir()); _base_wind_range_from = m->getWindRangeFrom(); _base_wind_range_to = m->getWindRangeTo(); - _wind_speed = m->getWindSpeed_kt(); + set_wind_speed(m->getWindSpeed_kt()); - double speed_fps = _wind_speed * SG_NM_TO_METER * SG_METER_TO_FEET / 3600.0; - _wind_from_north_fps = speed_fps * cos((double)_base_wind_dir * SGD_DEGREES_TO_RADIANS); - _wind_from_east_fps = speed_fps * sin((double)_base_wind_dir * SGD_DEGREES_TO_RADIANS); _gusts = m->getGustSpeed_kt(); _temperature = m->getTemperature_C(); _dewpoint = m->getDewpoint_C(); @@ -145,13 +264,15 @@ void MetarProperties::set_metar( const char * metar ) { // 1. check the id given in the metar FGAirport* a = FGAirport::findByIdent(m->getId()); -/* + // 2. if unknown, find closest airport with metar to current position if( a == NULL ) { - SGGeod pos = SGGeod::fromDeg(_longitude_n->getDoubleValue(), _latitude_n->getDoubleValue()); - a = FGAirport::findClosest(pos, 10000.0, &_airportWithMetarFilter); + SGGeod pos = SGGeod::fromDeg( + fgGetDouble( "/position/longitude-deg", 0.0 ), + fgGetDouble( "/position/latitude-deg", 0.0 ) ); + a = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() ); } -*/ + // 3. otherwise use ground elevation if( a != NULL ) { _station_elevation = a->getElevation(); @@ -160,12 +281,10 @@ void MetarProperties::set_metar( const char * metar ) _station_longitude = towerPosition.getLongitudeDeg(); _station_id = a->ident(); } else { - _station_elevation = 0.0; - _station_latitude = 0.0; - _station_longitude = 0.0; + _station_elevation = fgGetDouble("/position/ground-elev-m", 0.0 ) * SG_METER_TO_FEET; + _station_latitude = fgGetDouble( "/position/latitude-deg", 0.0 ); + _station_longitude = fgGetDouble( "/position/longitude-deg", 0.0 ); _station_id = "XXXX"; -// station_elevation_ft = _ground_elevation_n->getDoubleValue() * SG_METER_TO_FEET; -// _station_id = m->getId(); } } @@ -182,31 +301,181 @@ void MetarProperties::set_metar( const char * metar ) _sea_level_pressure = P_layer(0, elevation_m, fieldPressure, _temperature + atmodel::freezing, atmodel::ISA::lam0) / atmodel::inHg; } - vector cv = m->getClouds(); - vector::const_iterator cloud, cloud_end = cv.end(); + bool isBC = false; + bool isBR = false; + bool isFG = false; + bool isMI = false; + bool isHZ = false; + + { + for( unsigned i = 0; i < 3; i++ ) { + SGPropertyNode_ptr n = _rootNode->getChild("weather", i, true ); + vector weather = m->getWeather2(); + struct SGMetar::Weather * w = i < weather.size() ? &weather[i] : NULL; + n->getNode("intensity",true)->setIntValue( w != NULL ? w->intensity : 0 ); + n->getNode("vincinity",true)->setBoolValue( w != NULL ? w->vincinity : false ); + for( unsigned j = 0; j < 3; j++ ) { + + const string & phenomenon = w != NULL && j < w->phenomena.size() ? w->phenomena[j].c_str() : ""; + n->getChild( "phenomenon", j, true )->setStringValue( phenomenon ); + + const string & description = w != NULL && j < w->descriptions.size() ? w->descriptions[j].c_str() : ""; + n->getChild( "description", j, true )->setStringValue( description ); + + // need to know later, + // if its fog(FG) (might be shallow(MI) or patches(BC)) or haze (HZ) or mist(BR) + if( phenomenon == "FG" ) isFG = true; + if( phenomenon == "HZ" ) isHZ = true; + if( phenomenon == "BR" ) isBR = true; + if( description == "MI" ) isMI = true; + if( description == "BC" ) isBC = true; + } + } + } { static const char * LAYER = "layer"; SGPropertyNode_ptr cloudsNode = _rootNode->getNode("clouds", true ); const vector & metarClouds = m->getClouds(); - for( unsigned i = 0; i < 5; i++ ) { - SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, i, true ); - int coverage = i < metarClouds.size() ? metarClouds[i].getCoverage() : 0; - double elevation = i >= metarClouds.size() || coverage == 0 ? -9999.0 : metarClouds[i].getAltitude_ft() + _station_elevation; + unsigned layerOffset = 0; // Oh, this is ugly! + + // fog/mist/haze cloud layer does not work with 3d clouds yet :-( + bool setGroundCloudLayer = _rootNode->getBoolValue("set-ground-cloud-layer", false ) && + false == (fgGetBool("/sim/rendering/shader-effects", false ) && + fgGetBool("/sim/rendering/clouds3d-enable", false ) ); + + if( setGroundCloudLayer ) { + // create a cloud layer #0 starting at the ground if its fog, mist or haze + + // make sure layer actually starts at ground and set it's bottom at a constant + // value below the station's elevation + const double LAYER_BOTTOM_STATION_OFFSET = + fgGetDouble( "/environment/params/fog-mist-haze-layer/offset-from-station-elevation-ft", -200 ); + + SGMetarCloud::Coverage coverage = SGMetarCloud::COVERAGE_NIL; + double thickness = 0; + double alpha = 1.0; + + if( isFG ) { // fog + coverage = SGMetarCloud::getCoverage( isBC ? + fgGetString( "/environment/params/fog-mist-haze-layer/fog-bc-2dlayer-coverage", SGMetarCloud::COVERAGE_SCATTERED_STRING ) : + fgGetString( "/environment/params/fog-mist-haze-layer/fog-2dlayer-coverage", SGMetarCloud::COVERAGE_BROKEN_STRING ) + ); + + thickness = isMI ? + fgGetDouble("/environment/params/fog-mist-haze-layer/fog-shallow-thickness-ft",30) - LAYER_BOTTOM_STATION_OFFSET : // shallow fog, 10m/30ft + fgGetDouble("/environment/params/fog-mist-haze-layer/fog-thickness-ft",500) - LAYER_BOTTOM_STATION_OFFSET; // fog, 150m/500ft + alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/fog-2dlayer-alpha", 1.0); + } else if( isBR ) { // mist + coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING)); + thickness = fgGetDouble("/environment/params/fog-mist-haze-layer/mist-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET; + alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/mist-2dlayer-alpha",0.8); + } else if( isHZ ) { // haze + coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING)); + thickness = fgGetDouble("/environment/params/fog-mist-haze-layer/haze-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET; + alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/haze-2dlayer-alpha",0.6); + } + + if( coverage != SGMetarCloud::COVERAGE_NIL ) { + + // if there is a layer above the fog, limit the top to one foot below that layer's bottom + if( metarClouds.size() > 0 && metarClouds[0].getCoverage() != SGMetarCloud::COVERAGE_CLEAR ) + thickness = metarClouds[0].getAltitude_ft() - LAYER_BOTTOM_STATION_OFFSET - 1; + + SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, 0, true ); + layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) ); + layerNode->setStringValue( "coverage", coverage_string[coverage] ); + layerNode->setDoubleValue( "elevation-ft", _station_elevation + LAYER_BOTTOM_STATION_OFFSET ); + layerNode->setDoubleValue( "thickness-ft", thickness ); + layerNode->setDoubleValue( "visibility-m", _min_visibility ); + layerNode->setDoubleValue( "alpha", alpha ); + _min_visibility = _max_visibility = + fgGetDouble("/environment/params/fog-mist-haze-layer/visibility-above-layer-m",20000.0); // assume good visibility above the fog + layerOffset = 1; // shudder + } + } + + for( unsigned i = 0; i < 5-layerOffset; i++ ) { + SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, i+layerOffset, true ); + SGMetarCloud::Coverage coverage = i < metarClouds.size() ? metarClouds[i].getCoverage() : SGMetarCloud::COVERAGE_CLEAR; + double elevation = + i >= metarClouds.size() || coverage == SGMetarCloud::COVERAGE_CLEAR ? + -9999.0 : + metarClouds[i].getAltitude_ft() + _station_elevation; + + layerNode->setDoubleValue( "alpha", 1.0 ); layerNode->setStringValue( "coverage", coverage_string[coverage] ); layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) ); layerNode->setDoubleValue( "elevation-ft", elevation ); layerNode->setDoubleValue( "thickness-ft", thickness_value[coverage]); layerNode->setDoubleValue( "span-m", 40000 ); + layerNode->setDoubleValue( "visibility-m", 50.0 ); } - } _rain = m->getRain(); _hail = m->getHail(); _snow = m->getSnow(); _snow_cover = m->getSnowCover(); + _day = m->getDay(); + _hour = m->getHour(); + _minute = m->getMinute(); + _cavok = m->getCAVOK(); _metarValidNode->setBoolValue(true); } +void MetarProperties::setStationId( const std::string & value ) +{ + set_station_id(simgear::strutils::strip(value).c_str()); +} + +double MetarProperties::get_magnetic_variation_deg() const +{ + return _magneticVariation->get_variation_deg( _station_longitude, _station_latitude, _station_elevation ); +} + +double MetarProperties::get_magnetic_dip_deg() const +{ + return _magneticVariation->get_dip_deg( _station_longitude, _station_latitude, _station_elevation ); +} + +static inline void calc_wind_hs( double north_fps, double east_fps, int & heading_deg, double & speed_kt ) +{ + speed_kt = sqrt((north_fps)*(north_fps)+(east_fps)*(east_fps)) * 3600.0 / (SG_NM_TO_METER * SG_METER_TO_FEET); + heading_deg = SGMiscd::roundToInt( + SGMiscd::normalizeAngle2( atan2( east_fps, north_fps ) ) * SGD_RADIANS_TO_DEGREES ); +} + +void MetarProperties::set_wind_from_north_fps( double value ) +{ + _wind_from_north_fps = value; + calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed ); +} + +void MetarProperties::set_wind_from_east_fps( double value ) +{ + _wind_from_east_fps = value; + calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed ); +} + +static inline void calc_wind_ne( double heading_deg, double speed_kt, double & north_fps, double & east_fps ) +{ + double speed_fps = speed_kt * SG_NM_TO_METER * SG_METER_TO_FEET / 3600.0; + north_fps = speed_fps * cos(heading_deg * SGD_DEGREES_TO_RADIANS); + east_fps = speed_fps * sin(heading_deg * SGD_DEGREES_TO_RADIANS); +} + +void MetarProperties::set_base_wind_dir( double value ) +{ + _base_wind_dir = value; + calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps ); +} + +void MetarProperties::set_wind_speed( double value ) +{ + _wind_speed = value; + calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps ); +} + + } // namespace Environment