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"
30 #include <boost/foreach.hpp>
31 #include <boost/algorithm/string/case_conv.hpp>
33 #include <simgear/structure/exception.hxx>
34 #include <simgear/misc/strutils.hxx>
35 #include <simgear/props/tiedpropertylist.hxx>
36 #include <simgear/io/HTTPMemoryRequest.hxx>
37 #include <simgear/timing/sg_time.hxx>
38 #include <simgear/structure/event_mgr.hxx>
39 #include <simgear/structure/commands.hxx>
41 #include "metarproperties.hxx"
42 #include "metarairportfilter.hxx"
43 #include "fgmetar.hxx"
44 #include <Network/HTTPClient.hxx>
45 #include <Main/fg_props.hxx>
47 namespace Environment {
50 /* -------------------------------------------------------------------------------- */
52 class MetarDataHandler {
54 virtual void handleMetarData( const std::string & data ) = 0;
55 virtual void handleMetarFailure() = 0;
58 class MetarRequester {
60 virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id ) = 0;
63 /* -------------------------------------------------------------------------------- */
65 class LiveMetarProperties : public MetarProperties, MetarDataHandler {
67 LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge );
68 virtual ~LiveMetarProperties();
69 virtual void update( double dt );
71 virtual double getTimeToLive() const { return _timeToLive; }
72 virtual void resetTimeToLive()
73 { _timeToLive = 0.00; _pollingTimer = 0.0; }
75 // implementation of MetarDataHandler
76 virtual void handleMetarData( const std::string & data );
77 virtual void handleMetarFailure();
79 static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
80 static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
85 MetarRequester * _metarRequester;
90 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
92 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge ) :
93 MetarProperties( rootNode ),
96 _metarRequester(metarRequester),
100 _tiedProperties.Tie("time-to-live", &_timeToLive );
101 _tiedProperties.Tie("failure", &_failure);
104 LiveMetarProperties::~LiveMetarProperties()
106 _tiedProperties.Untie();
109 void LiveMetarProperties::update( double dt )
113 if( _timeToLive <= 0.0 ) {
116 std::string stationId = getStationId();
117 if( stationId.empty() ) return;
118 if( _pollingTimer > 0.0 ) return;
119 _metarRequester->requestMetar( this, stationId );
120 _pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
124 void LiveMetarProperties::handleMetarData( const std::string & data )
126 SG_LOG( SG_ENVIRONMENT, SG_DEBUG, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
127 _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
129 SGSharedPtr<FGMetar> m;
131 m = new FGMetar(data.c_str());
133 catch( sg_io_exception ) {
134 SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar: " << data );
139 if (_maxAge && (m->getAge_min() > _maxAge)) {
140 // METAR is older than max-age, ignore
141 SG_LOG( SG_ENVIRONMENT, SG_DEBUG, "Ignoring outdated METAR for " << getStationId());
149 void LiveMetarProperties::handleMetarFailure()
154 /* -------------------------------------------------------------------------------- */
156 class BasicRealWxController : public RealWxController
159 BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
160 virtual ~BasicRealWxController ();
162 virtual void init ();
163 virtual void reinit ();
164 virtual void shutdown ();
167 * Create a metar-property binding at the specified property path,
168 * and initiate a request for the specified station-ID (which may be
169 * empty). If the property path is already mapped, the station ID
172 void addMetarAtPath(const string& propPath, const string& icao);
174 void removeMetarAtPath(const string& propPath);
176 typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
177 MetarPropertiesList::iterator findMetarAtPath(const string &propPath);
181 void update( double dt );
183 void checkNearbyMetar();
185 long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
187 SGPropertyNode_ptr _rootNode;
188 SGPropertyNode_ptr _ground_elevation_n;
189 SGPropertyNode_ptr _max_age_n;
193 simgear::TiedPropertyList _tiedProperties;
194 MetarPropertiesList _metarProperties;
195 MetarRequester* _requester;
199 static bool commandRequestMetar(const SGPropertyNode* arg)
201 SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
206 BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
211 string icao(arg->getStringValue("station"));
212 boost::to_upper(icao);
213 string path = arg->getStringValue("path");
214 self->addMetarAtPath(path, icao);
218 static bool commandClearMetar(const SGPropertyNode* arg)
220 SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
225 BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
230 string path = arg->getStringValue("path");
231 self->removeMetarAtPath(path);
235 /* -------------------------------------------------------------------------------- */
238 ~/enabled: bool Enables/Disables the realwx controller
239 ~/metar[1..n]: string Target property path for metar data
242 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
244 _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
245 _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
248 _requester(metarRequester)
251 globals->get_commands()->addCommand("request-metar", commandRequestMetar);
252 globals->get_commands()->addCommand("clear-metar", commandClearMetar);
255 BasicRealWxController::~BasicRealWxController()
257 globals->get_commands()->removeCommand("request-metar");
258 globals->get_commands()->removeCommand("clear-metar");
261 void BasicRealWxController::init()
265 // at least instantiate MetarProperties for /environment/metar
266 SGPropertyNode_ptr metarNode = fgGetNode( _rootNode->getStringValue("metar", "/environment/metar"), true );
267 _metarProperties.push_back( new LiveMetarProperties(metarNode,
269 getMetarMaxAgeMin()));
271 BOOST_FOREACH( SGPropertyNode_ptr n, _rootNode->getChildren("metar") ) {
272 SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
273 addMetarAtPath(metarNode->getPath(), "");
277 update(0); // fetch data ASAP
279 globals->get_event_mgr()->addTask("checkNearbyMetar", this,
280 &BasicRealWxController::checkNearbyMetar, 60 );
283 void BasicRealWxController::reinit()
287 update(0); // fetch data ASAP
290 void BasicRealWxController::shutdown()
292 globals->get_event_mgr()->removeTask("checkNearbyMetar");
295 void BasicRealWxController::bind()
297 _tiedProperties.setRoot( _rootNode );
298 _tiedProperties.Tie( "enabled", &_enabled );
301 void BasicRealWxController::unbind()
303 _tiedProperties.Untie();
306 void BasicRealWxController::update( double dt )
309 bool firstIteration = !_wasEnabled;
310 // clock tick for every METAR in stock
311 BOOST_FOREACH(LiveMetarProperties* p, _metarProperties) {
312 // first round? All received METARs are outdated
313 if( firstIteration ) p->resetTimeToLive();
323 void BasicRealWxController::addMetarAtPath(const string& propPath, const string& icao)
325 // check for duplicate entries
326 MetarPropertiesList::iterator it = findMetarAtPath(propPath);
327 if( it != _metarProperties.end() ) {
328 SG_LOG( SG_ENVIRONMENT, SG_INFO, "Reusing metar properties at " << propPath << " for " << icao);
330 if ((*it)->getStationId() != icao) {
331 (*it)->setStationId(icao);
332 (*it)->resetTimeToLive();
337 SGPropertyNode_ptr metarNode = fgGetNode(propPath, true);
338 SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << propPath << " for " << icao);
339 LiveMetarProperties_ptr p(new LiveMetarProperties( metarNode, _requester, getMetarMaxAgeMin() ));
340 _metarProperties.push_back(p);
341 p->setStationId(icao);
344 void BasicRealWxController::removeMetarAtPath(const string &propPath)
346 MetarPropertiesList::iterator it = findMetarAtPath( propPath );
347 if( it != _metarProperties.end() ) {
348 SG_LOG(SG_ENVIRONMENT, SG_INFO, "removing metar properties at " << propPath);
349 LiveMetarProperties_ptr p(*it);
350 _metarProperties.erase(it);
351 // final ref will drop, and delete the MetarProperties, when we return
353 SG_LOG(SG_ENVIRONMENT, SG_WARN, "no metar properties at " << propPath);
357 BasicRealWxController::MetarPropertiesList::iterator BasicRealWxController::findMetarAtPath(const string &propPath)
359 // don not compare unprocessed property path
360 // /foo/bar[0]/baz equals /foo/bar/baz
361 SGPropertyNode_ptr n = fgGetNode(propPath,false);
362 if( false == n.valid() ) // trivial: node does not exist
363 return _metarProperties.end();
365 MetarPropertiesList::iterator it = _metarProperties.begin();
366 while( it != _metarProperties.end() &&
367 (*it)->get_root_node()->getPath() != n->getPath() )
373 void BasicRealWxController::checkNearbyMetar()
376 const SGGeod & pos = globals->get_aircraft_position();
378 // check nearest airport
379 SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
381 FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
382 if( nearestAirport == NULL ) {
383 SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM" );
387 SG_LOG(SG_ENVIRONMENT, SG_DEBUG,
388 "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
390 // if it has changed, invalidate the associated METAR
391 if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
392 SG_LOG(SG_ENVIRONMENT, SG_INFO,
393 "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" <<
394 _metarProperties[0]->getStationId() <<
395 "', new: '" << nearestAirport->ident() << "'" );
396 _metarProperties[0]->setStationId( nearestAirport->ident() );
397 _metarProperties[0]->resetTimeToLive();
400 catch( sg_exception & ) {
406 /* -------------------------------------------------------------------------------- */
408 class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester {
410 NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
412 // implementation of MetarRequester
413 virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id );
415 virtual ~NoaaMetarRealWxController()
422 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
423 BasicRealWxController(rootNode, this )
427 void NoaaMetarRealWxController::requestMetar
429 MetarDataHandler* metarDataHandler,
430 const std::string& id
433 static const std::string NOAA_BASE_URL =
434 "http://weather.noaa.gov/pub/data/observations/metar/stations/";
435 class NoaaMetarGetRequest:
436 public simgear::HTTP::MemoryRequest
439 NoaaMetarGetRequest( MetarDataHandler* metarDataHandler,
440 const std::string& stationId ):
441 MemoryRequest(NOAA_BASE_URL + stationId + ".TXT"),
442 _metarDataHandler(metarDataHandler)
444 std::ostringstream buf;
445 buf << globals->get_time_params()->get_cur_time();
446 requestHeader("X-TIME") = buf.str();
449 virtual void onDone()
451 if( responseCode() != 200 )
457 "metar download failed:" << url() << ": reason:" << responseReason()
462 _metarDataHandler->handleMetarData
464 simgear::strutils::simplify(responseBody())
468 virtual void onFail()
470 SG_LOG(SG_ENVIRONMENT, SG_INFO, "metar download failure");
471 _metarDataHandler->handleMetarFailure();
475 MetarDataHandler * _metarDataHandler;
478 string upperId = boost::to_upper_copy(id);
484 "NoaaMetarRealWxController::update(): "
485 "spawning load request for station-id '" << upperId << "'"
487 FGHTTPClient* http = static_cast<FGHTTPClient*>(globals->get_subsystem("http"));
489 http->makeRequest(new NoaaMetarGetRequest(metarDataHandler, upperId));
493 /* -------------------------------------------------------------------------------- */
495 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
497 return new NoaaMetarRealWxController( rootNode );
500 RealWxController::~RealWxController()
504 /* -------------------------------------------------------------------------------- */
506 } // namespace Environment