1 // realwx_ctrl.cxx -- Process real weather data
3 // Written by David Megginson, started February 2002.
4 // Rewritten by Torsten Dreyer, August 2010, August 2011
6 // Copyright (C) 2002 David Megginson - david@megginson.com
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include "realwx_ctrl.hxx"
28 #include "metarproperties.hxx"
29 #include "metarairportfilter.hxx"
30 #include "fgmetar.hxx"
32 #include <Main/fg_props.hxx>
34 #include <boost/foreach.hpp>
36 #include <simgear/structure/exception.hxx>
37 #include <simgear/misc/strutils.hxx>
38 #include <simgear/props/tiedpropertylist.hxx>
39 #include <simgear/io/HTTPClient.hxx>
40 #include <simgear/io/HTTPRequest.hxx>
41 #include <simgear/timing/sg_time.hxx>
42 #include <simgear/structure/event_mgr.hxx>
46 namespace Environment {
47 /* -------------------------------------------------------------------------------- */
49 class FGHTTPClient : public simgear::HTTP::Client {
54 FGHTTPClient::FGHTTPClient()
56 string proxyHost(fgGetString("/sim/presets/proxy/host"));
57 int proxyPort(fgGetInt("/sim/presets/proxy/port"));
58 string proxyAuth(fgGetString("/sim/presets/proxy/auth"));
60 if (!proxyHost.empty()) {
61 setProxy(proxyHost, proxyPort, proxyAuth);
65 /* -------------------------------------------------------------------------------- */
67 class MetarDataHandler {
69 virtual void handleMetarData( const std::string & data ) = 0;
72 class MetarRequester {
74 virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id ) = 0;
77 /* -------------------------------------------------------------------------------- */
79 class LiveMetarProperties : public MetarProperties, MetarDataHandler {
81 LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
82 virtual ~LiveMetarProperties();
83 virtual void update( double dt );
85 virtual double getTimeToLive() const { return _timeToLive; }
86 virtual void setTimeToLive( double value ) { _timeToLive = value; }
88 // implementation of MetarDataHandler
89 virtual void handleMetarData( const std::string & data );
91 static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
92 static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
97 MetarRequester * _metarRequester;
100 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
102 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
103 MetarProperties( rootNode ),
106 _metarRequester(metarRequester)
108 _tiedProperties.Tie("time-to-live", &_timeToLive );
111 LiveMetarProperties::~LiveMetarProperties()
113 _tiedProperties.Untie();
116 void LiveMetarProperties::update( double dt )
120 if( _timeToLive < 0.0 ) {
122 std::string stationId = getStationId();
123 if( stationId.empty() ) return;
124 if( _pollingTimer > 0.0 ) return;
125 _metarRequester->requestMetar( this, stationId );
126 _pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
130 void LiveMetarProperties::handleMetarData( const std::string & data )
132 SG_LOG( SG_ENVIRONMENT, SG_INFO, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
133 _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
137 /* -------------------------------------------------------------------------------- */
139 class BasicRealWxController : public RealWxController
142 BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
143 virtual ~BasicRealWxController ();
145 virtual void init ();
146 virtual void reinit ();
147 virtual void shutdown ();
152 void update( double dt );
154 void checkNearbyMetar();
156 long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
158 SGPropertyNode_ptr _rootNode;
159 SGPropertyNode_ptr _ground_elevation_n;
160 SGPropertyNode_ptr _max_age_n;
164 simgear::TiedPropertyList _tiedProperties;
165 typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
166 MetarPropertiesList _metarProperties;
170 /* -------------------------------------------------------------------------------- */
173 ~/enabled: bool Enables/Disables the realwx controller
174 ~/metar[1..n]: string Target property path for metar data
177 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
179 _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
180 _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
184 // at least instantiate MetarProperties for /environment/metar
185 _metarProperties.push_back( new LiveMetarProperties(
186 fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ), metarRequester ));
188 BOOST_FOREACH( SGPropertyNode_ptr n, rootNode->getChildren("metar") ) {
189 SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
191 // check for duplicate entries
192 bool existingElement = false;
193 BOOST_FOREACH( LiveMetarProperties_ptr p, _metarProperties ) {
194 if( p->get_root_node()->getPath() == metarNode->getPath() ) {
195 existingElement = true;
200 if( existingElement )
203 SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << metarNode->getPath() );
204 _metarProperties.push_back( new LiveMetarProperties( metarNode, metarRequester ));
208 BasicRealWxController::~BasicRealWxController()
212 void BasicRealWxController::init()
215 update(0); // fetch data ASAP
217 globals->get_event_mgr()->addTask("checkNearbyMetar", this,
218 &BasicRealWxController::checkNearbyMetar, 60 );
221 void BasicRealWxController::reinit()
226 void BasicRealWxController::shutdown()
228 globals->get_event_mgr()->removeTask("checkNearbyMetar");
231 void BasicRealWxController::bind()
233 _tiedProperties.setRoot( _rootNode );
234 _tiedProperties.Tie( "enabled", &_enabled );
237 void BasicRealWxController::unbind()
239 _tiedProperties.Untie();
242 void BasicRealWxController::update( double dt )
245 bool firstIteration = !__enabled; // first iteration after being enabled?
247 // clock tick for every METAR in stock
248 BOOST_FOREACH(LiveMetarProperties* p, _metarProperties) {
249 // first round? All received METARs are outdated
250 if( firstIteration ) p->setTimeToLive( 0.0 );
260 void BasicRealWxController::checkNearbyMetar()
263 const SGGeod & pos = globals->get_aircraft_position();
265 // check nearest airport
266 SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
268 FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
269 if( nearestAirport == NULL ) {
270 SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM" );
274 SG_LOG(SG_ENVIRONMENT, SG_DEBUG,
275 "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
277 // if it has changed, invalidate the associated METAR
278 if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
279 SG_LOG(SG_ENVIRONMENT, SG_INFO,
280 "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" <<
281 _metarProperties[0]->getStationId() <<
282 "', new: '" << nearestAirport->ident() << "'" );
283 _metarProperties[0]->setStationId( nearestAirport->ident() );
284 _metarProperties[0]->setTimeToLive( 0.0 );
287 catch( sg_exception & ) {
293 /* -------------------------------------------------------------------------------- */
295 class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester {
297 NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
298 virtual ~NoaaMetarRealWxController();
299 virtual void update( double dt );
301 // implementation of MetarRequester
302 virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id );
308 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
309 BasicRealWxController(rootNode, this )
313 NoaaMetarRealWxController::~NoaaMetarRealWxController()
317 void NoaaMetarRealWxController::update( double dt )
320 BasicRealWxController::update( dt );
323 void NoaaMetarRealWxController::requestMetar( MetarDataHandler * metarDataHandler, const std::string & id )
325 class NoaaMetarGetRequest : public simgear::HTTP::Request
328 NoaaMetarGetRequest(MetarDataHandler* metarDataHandler, const string& stationId ) :
329 Request("http://weather.noaa.gov/pub/data/observations/metar/stations/" + stationId + ".TXT"),
331 _metarDataHandler(metarDataHandler)
335 virtual string_list requestHeaders() const
338 reply.push_back("X-TIME");
342 virtual std::string header(const std::string& name) const
346 if( name == "X-TIME" ) {
347 std::ostringstream buf;
348 buf << globals->get_time_params()->get_cur_time();
355 virtual void responseHeader(const string& key, const string& value)
357 if (key == "x-metarproxy") {
362 virtual void gotBodyData(const char* s, int n)
364 _metar += string(s, n);
367 virtual void responseComplete()
369 if (responseCode() == 200) {
370 _metarDataHandler->handleMetarData( simgear::strutils::simplify(_metar) );
372 SG_LOG(SG_ENVIRONMENT, SG_WARN, "metar download failed:" << url() << ": reason:" << responseReason());
376 bool fromMetarProxy() const
377 { return _fromProxy; }
381 MetarDataHandler * _metarDataHandler;
385 SG_LOG(SG_ENVIRONMENT, SG_INFO,
386 "NoaaMetarRealWxController::update(): spawning load request for station-id '" << id << "'" );
387 _http.makeRequest(new NoaaMetarGetRequest(metarDataHandler, id));
390 /* -------------------------------------------------------------------------------- */
392 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
394 // string dataSource = rootNode->getStringValue("data-source", "noaa" );
395 // if( dataSource == "nwx" ) {
396 // return new NwxMetarRealWxController( rootNode );
398 return new NoaaMetarRealWxController( rootNode );
402 /* -------------------------------------------------------------------------------- */
404 } // namespace Environment