1
0
Fork 0

METAR: enable reports from multiple stations

This patch enables multiple MetarProperties instances tied to the
property tree. For each node's value of /environment/realwx/metar
one MetarProperties instance is created and tied to the property
tree named there. (See FGDATA/Environment/environment.xml for details)

At least one instance will be created to provice backward compatibility
to the existing live-data weather system. This instance (tied to
/environment/metar) fetches a METAR for the nearest airport at a regular
schedule. All other instances fetch a report for airports named in the
property station-id. It re-reads the report every 15 minutes, the
remaining time until the next fetch will be scheduled is in the property
time-to-live. This property can be written to, to extend the live of
this report or schedule an immediate reload by setting it's value to zero.

This patch also provides magnetic variation for the station's location.
This commit is contained in:
Torsten Dreyer 2011-01-07 13:11:06 +01:00
parent 5cdfe3d7a5
commit 9337584df0
3 changed files with 256 additions and 56 deletions

View file

@ -31,6 +31,9 @@
#include "metarairportfilter.hxx"
#include <simgear/scene/sky/cloud.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/magvar/magvar.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Main/fg_props.hxx>
using std::string;
@ -39,6 +42,72 @@ namespace Environment {
static vector<string> 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_ALL, SG_DEBUG, "Recalculating magvar for lon=" << lon << ", lat=" << lat << ", alt=" << alt );
_lon = lon;
_lat = lat;
_alt = alt;
lon *= SGD_DEGREES_TO_RADIANS;
lat *= SGD_DEGREES_TO_RADIANS;
alt *= SG_FEET_TO_METER;
_time.update( lon, lat, 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 ) ),
@ -64,7 +133,8 @@ MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
_rain(0.0),
_hail(0.0),
_snow(0.0),
_snow_cover(false)
_snow_cover(false),
_magneticVariation(new MagneticVariation())
{
// Hack to avoid static initialization order problems on OSX
if( coverage_string.size() == 0 ) {
@ -78,10 +148,12 @@ MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
_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 );
@ -107,6 +179,7 @@ MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
MetarProperties::~MetarProperties()
{
delete _magneticVariation;
}
@ -320,4 +393,19 @@ void MetarProperties::set_metar( const char * metar )
_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 );
}
} // namespace Environment

View file

@ -29,6 +29,8 @@
namespace Environment {
class MagneticVariation;
class MetarProperties : public SGReferenced
{
public:
@ -36,12 +38,19 @@ public:
virtual ~MetarProperties();
SGPropertyNode_ptr get_root_node() const { return _rootNode; }
virtual bool isValid() const { return _metarValidNode->getBoolValue(); }
virtual const std::string & getStationId() const { return _station_id; }
virtual void setStationId( const std::string & value );
virtual void setMetar( const std::string & value ) { set_metar( value.c_str() ); }
private:
const char * get_metar() const { return _metar.c_str(); }
void set_metar( const char * metar );
const char * get_station_id() const { return _station_id.c_str(); }
void set_station_id( const char * value );
const char * get_decoded() const { return _decoded.c_str(); }
double get_magnetic_variation_deg() const;
double get_magnetic_dip_deg() const;
SGPropertyNode_ptr _rootNode;
SGPropertyNode_ptr _metarValidNode;
@ -71,9 +80,15 @@ private:
double _snow;
bool _snow_cover;
std::string _decoded;
protected:
TiedPropertyList _tiedProperties;
MagneticVariation * _magneticVariation;
};
inline void MetarProperties::set_station_id( const char * value )
{
_station_id = value;
}
} // namespace
#endif // __METARPROPERTIES_HXX

View file

@ -43,6 +43,42 @@
namespace Environment {
/* -------------------------------------------------------------------------------- */
class LiveMetarProperties : public MetarProperties {
public:
LiveMetarProperties( SGPropertyNode_ptr rootNode );
virtual ~LiveMetarProperties();
virtual void update( double dt );
virtual double getTimeToLive() const { return _timeToLive; }
virtual void setTimeToLive( double value ) { _timeToLive = value; }
private:
double _timeToLive;
};
typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode ) :
MetarProperties( rootNode ),
_timeToLive(0.0)
{
_tiedProperties.Tie("time-to-live", &_timeToLive );
}
LiveMetarProperties::~LiveMetarProperties()
{
}
void LiveMetarProperties::update( double dt )
{
_timeToLive -= dt;
if( _timeToLive < 0.0 ) _timeToLive = 0.0;
}
/* -------------------------------------------------------------------------------- */
class BasicRealWxController : public RealWxController
{
public:
@ -70,7 +106,8 @@ protected:
bool _enabled;
bool __enabled;
TiedPropertyList _tiedProperties;
MetarProperties _metarProperties;
; typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
MetarPropertiesList _metarProperties;
};
/* -------------------------------------------------------------------------------- */
@ -87,9 +124,18 @@ BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode ) :
_ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
_max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
_enabled(true),
__enabled(false),
_metarProperties( fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ) )
__enabled(false)
{
// at least instantiate MetarProperties for /environment/metar
_metarProperties.push_back( new LiveMetarProperties(
fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ) ) );
PropertyList metars = rootNode->getChildren("metar");
for( PropertyList::size_type i = 1; i < metars.size(); i++ ) {
SG_LOG( SG_ALL, SG_INFO, "Adding metar properties at " << metars[i]->getStringValue() );
_metarProperties.push_back( new LiveMetarProperties(
fgGetNode( metars[i]->getStringValue(), true )));
}
}
BasicRealWxController::~BasicRealWxController()
@ -121,7 +167,17 @@ void BasicRealWxController::unbind()
void BasicRealWxController::update( double dt )
{
if( _enabled ) {
update( !__enabled, dt );
bool firstIteration = !__enabled; // first iteration after being enabled?
// clock tick for every METAR in stock
for( MetarPropertiesList::iterator it = _metarProperties.begin();
it != _metarProperties.end(); it++ ) {
// first round? All received METARs are outdated
if( firstIteration ) (*it)->setTimeToLive( 0.0 );
(*it)->update(dt);
}
update( firstIteration, dt );
__enabled = true;
} else {
__enabled = false;
@ -144,21 +200,35 @@ public:
_proxyPort = fgGetNode("/sim/presets/proxy/port", true)->getStringValue();
_proxyAuth = fgGetNode("/sim/presets/proxy/authentication", true)->getStringValue();
}
MetarLoadRequest( const MetarLoadRequest & other ) {
_stationId = other._stationId;
_proxyHost = other._proxyAuth;
_proxyPort = other._proxyPort;
_proxyAuth = other._proxyAuth;
}
string _stationId;
string _proxyHost;
string _proxyPort;
string _proxyAuth;
private:
};
private:
double _metarTimeToLive;
double _positionTimeToLive;
double _minimumRequestInterval;
SGPropertyNode_ptr _metarDataNode;
SGPropertyNode_ptr _metarValidNode;
SGPropertyNode_ptr _metarStationIdNode;
class MetarLoadResponse {
public:
MetarLoadResponse( const string & stationId, const string metar ) {
_stationId = stationId;
_metar = metar;
}
MetarLoadResponse( const MetarLoadResponse & other ) {
_stationId = other._stationId;
_metar = other._metar;
}
string _stationId;
string _metar;
};
private:
double _positionTimeToLive;
double _requestTimer;
#if defined(ENABLE_THREADS)
class MetarLoadThread : public OpenThreads::Thread {
@ -166,13 +236,14 @@ private:
MetarLoadThread( long maxAge );
void requestMetar( const MetarLoadRequest & metarRequest, bool background = true );
bool hasMetar() { return _responseQueue.size() > 0; }
string getMetar() { return _responseQueue.pop(); }
MetarLoadResponse getMetar() { return _responseQueue.pop(); }
virtual void run();
private:
void fetch( const MetarLoadRequest & );
long _maxAge;
long _minRequestInterval;
SGBlockingQueue <MetarLoadRequest> _requestQueue;
SGBlockingQueue <string> _responseQueue;
SGBlockingQueue <MetarLoadResponse> _responseQueue;
};
MetarLoadThread * _metarLoadThread;
@ -181,12 +252,8 @@ private:
NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
BasicRealWxController(rootNode),
_metarTimeToLive(0.0),
_positionTimeToLive(0.0),
_minimumRequestInterval(0.0),
_metarDataNode(_metarProperties.get_root_node()->getNode("data",true)),
_metarValidNode(_metarProperties.get_root_node()->getNode("valid",true)),
_metarStationIdNode(_metarProperties.get_root_node()->getNode("station-id",true))
_requestTimer(0.0)
{
#if defined(ENABLE_THREADS)
_metarLoadThread = new MetarLoadThread(getMetarMaxAgeMin());
@ -208,22 +275,12 @@ NoaaMetarRealWxController::~NoaaMetarRealWxController()
void NoaaMetarRealWxController::update( bool first, double dt )
{
_metarTimeToLive -= dt;
_positionTimeToLive -= dt;
_minimumRequestInterval -= dt;
_requestTimer -= dt;
bool valid = _metarValidNode->getBoolValue();
string stationId = valid ? _metarStationIdNode->getStringValue() : "";
if( first ) _metarTimeToLive = 0.0;
if( _metarTimeToLive <= 0.0 ) {
valid = false;
_metarTimeToLive = 900;
_positionTimeToLive = 0;
}
if( _positionTimeToLive <= 0.0 || valid == false ) {
if( _positionTimeToLive <= 0.0 ) {
// check nearest airport
SG_LOG(SG_ALL, SG_INFO, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
_positionTimeToLive = 60.0;
SGGeod pos = SGGeod::fromDeg(_longitude_n->getDoubleValue(), _latitude_n->getDoubleValue());
@ -234,38 +291,64 @@ void NoaaMetarRealWxController::update( bool first, double dt )
return;
}
if( stationId != nearestAirport->ident() ) {
valid = false;
stationId = nearestAirport->ident();
SG_LOG(SG_ALL, SG_INFO,
"NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
// if it has changed, invalidate the associated METAR
if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
SG_LOG(SG_ALL, SG_INFO,
"NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" <<
_metarProperties[0]->getStationId() <<
"', new: '" << nearestAirport->ident() << "'" );
_metarProperties[0]->setStationId( nearestAirport->ident() );
_metarProperties[0]->setTimeToLive( 0.0 );
}
}
if( _requestTimer <= 0.0 ) {
_requestTimer = 10.0;
if( !valid ) {
if( _minimumRequestInterval <= 0 && stationId.length() > 0 ) {
MetarLoadRequest request( stationId );
// load the first metar in the foreground to make sure a metar is received
// before the automatic runway selection code runs. All subsequent calls
// run in the background
_metarLoadThread->requestMetar( request, !first );
_minimumRequestInterval = 10;
for( MetarPropertiesList::iterator it = _metarProperties.begin();
it != _metarProperties.end(); it++ ) {
if( (*it)->getTimeToLive() > 0.0 ) continue;
const std::string & stationId = (*it)->getStationId();
if( stationId.empty() ) continue;
SG_LOG(SG_ALL, SG_INFO,
"NoaaMetarRealWxController::update(): spawning load request for station-id '" << stationId << "'" );
MetarLoadRequest request( stationId );
// load the metar for the neares airport in the foreground if the fdm is uninitialized
// to make sure a metar is received
// before the automatic runway selection code runs. All subsequent calls
// run in the background
bool background = fgGetBool("/sim/fdm-initialized", false ) || it != _metarProperties.begin();
_metarLoadThread->requestMetar( request, background );
}
}
if( _metarLoadThread->hasMetar() ) {
string metar = _metarLoadThread->getMetar();
SG_LOG( SG_ALL, SG_ALERT, "NoaaMetarRwalWxController::update() received METAR " << metar );
_metarDataNode->setStringValue( metar );
// pick all the received responses from the result queue and update the associated
// property tree
while( _metarLoadThread->hasMetar() ) {
MetarLoadResponse metar = _metarLoadThread->getMetar();
SG_LOG( SG_ALL, SG_INFO, "NoaaMetarRwalWxController::update() received METAR for " << metar._stationId << ": " << metar._metar );
for( MetarPropertiesList::iterator it = _metarProperties.begin();
it != _metarProperties.end(); it++ ) {
if( (*it)->getStationId() != metar._stationId )
continue;
(*it)->setTimeToLive(900);
(*it)->setMetar( metar._metar );
}
}
}
/* -------------------------------------------------------------------------------- */
#if defined(ENABLE_THREADS)
NoaaMetarRealWxController::MetarLoadThread::MetarLoadThread( long maxAge ) :
_maxAge(maxAge)
_maxAge(maxAge),
_minRequestInterval(2000)
{
}
@ -287,13 +370,22 @@ void NoaaMetarRealWxController::MetarLoadThread::requestMetar( const MetarLoadRe
void NoaaMetarRealWxController::MetarLoadThread::run()
{
SGTimeStamp lastRun = SGTimeStamp::fromSec(0);
for( ;; ) {
SGTimeStamp dt = SGTimeStamp::now() - lastRun;
if( dt.getSeconds() * 1000 < _minRequestInterval )
microSleep( (_minRequestInterval - dt.getSeconds() * 1000 ) * 1000 );
lastRun = SGTimeStamp::now();
const MetarLoadRequest request = _requestQueue.pop();
if( request._stationId.size() == 0 )
break;
fetch( request );
}
}
@ -303,9 +395,13 @@ void NoaaMetarRealWxController::MetarLoadThread::fetch( const MetarLoadRequest &
try {
result = new FGMetar( request._stationId, request._proxyHost, request._proxyPort, request._proxyAuth );
_minRequestInterval = 2000;
} catch (const sg_io_exception& e) {
SG_LOG( SG_GENERAL, SG_WARN, "NoaaMetarRealWxController::fetchMetar(): can't get METAR for "
<< request._stationId << ":" << e.getFormattedMessage().c_str() );
_minRequestInterval += _minRequestInterval/2;
if( _minRequestInterval > 30000 )
_minRequestInterval = 30000;
return;
}
@ -325,7 +421,8 @@ void NoaaMetarRealWxController::MetarLoadThread::fetch( const MetarLoadRequest &
return;
}
_responseQueue.push( metar );
MetarLoadResponse response( request._stationId, metar );
_responseQueue.push( response );
}
#endif