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:
parent
5cdfe3d7a5
commit
9337584df0
3 changed files with 256 additions and 56 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue