]> git.mxchange.org Git - flightgear.git/blob - src/Environment/realwx_ctrl.cxx
Don't free METAR objects, it makes NoaaMetarGetRequest::onDone crash
[flightgear.git] / src / Environment / realwx_ctrl.cxx
1 // realwx_ctrl.cxx -- Process real weather data
2 //
3 // Written by David Megginson, started February 2002.
4 // Rewritten by Torsten Dreyer, August 2010, August 2011
5 //
6 // Copyright (C) 2002  David Megginson - david@megginson.com
7 //
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.
12 //
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.
17 //
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.
21 //
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include "realwx_ctrl.hxx"
28
29 #include <algorithm>
30 #include <boost/foreach.hpp>
31 #include <boost/algorithm/string/case_conv.hpp>
32
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>
40
41 #include "metarproperties.hxx"
42 #include "metarairportfilter.hxx"
43 #include "fgmetar.hxx"
44 #include <Network/HTTPClient.hxx>
45 #include <Main/fg_props.hxx>
46
47 namespace Environment {
48
49
50 /* -------------------------------------------------------------------------------- */
51
52 class MetarDataHandler {
53 public:
54     virtual void handleMetarData( const std::string & data ) = 0;
55     virtual void handleMetarFailure() = 0;
56 };
57
58 class MetarRequester {
59 public:
60     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id ) = 0;
61 };
62
63 /* -------------------------------------------------------------------------------- */
64
65 class LiveMetarProperties : public MetarProperties, MetarDataHandler {
66 public:
67     LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge );
68     virtual ~LiveMetarProperties();
69     virtual void update( double dt );
70
71     virtual double getTimeToLive() const { return _timeToLive; }
72     virtual void resetTimeToLive()
73     { _timeToLive = 0.00; _pollingTimer = 0.0; }
74
75     // implementation of MetarDataHandler
76     virtual void handleMetarData( const std::string & data );
77     virtual void handleMetarFailure();
78   
79     static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
80     static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
81
82 private:
83     double _timeToLive;
84     double _pollingTimer;
85     MetarRequester * _metarRequester;
86     int _maxAge;
87     bool _failure;
88 };
89
90 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
91
92 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge ) :
93     MetarProperties( rootNode ),
94     _timeToLive(0.0),
95     _pollingTimer(0.0),
96     _metarRequester(metarRequester),
97     _maxAge(maxAge),
98     _failure(false)
99 {
100     _tiedProperties.Tie("time-to-live", &_timeToLive );
101     _tiedProperties.Tie("failure", &_failure);
102 }
103
104 LiveMetarProperties::~LiveMetarProperties()
105 {
106     _tiedProperties.Untie();
107 }
108
109 void LiveMetarProperties::update( double dt )
110 {
111     _timeToLive -= dt;
112     _pollingTimer -= dt;
113     if( _timeToLive <= 0.0 ) {
114         _timeToLive = 0.0;
115         invalidate();
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;
121     }
122 }
123
124 void LiveMetarProperties::handleMetarData( const std::string & data )
125 {
126     SG_LOG( SG_ENVIRONMENT, SG_DEBUG, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
127     _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
128     
129     SGSharedPtr<FGMetar> m;
130     try {
131         m = new FGMetar(data.c_str());
132     }
133     catch( sg_io_exception ) {
134         SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar: " << data );
135         _failure = true;
136         return;
137     }
138
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());
142         return;
143     }
144   
145     _failure = false;
146     setMetar( m );
147 }
148
149 void LiveMetarProperties::handleMetarFailure()
150 {
151   _failure = true;
152 }
153   
154 /* -------------------------------------------------------------------------------- */
155
156 class BasicRealWxController : public RealWxController
157 {
158 public:
159     BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
160     virtual ~BasicRealWxController ();
161
162     virtual void init ();
163     virtual void reinit ();
164     virtual void shutdown ();
165     
166     /**
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
170      * will be updated.
171      */
172     void addMetarAtPath(const string& propPath, const string& icao);
173   
174     void removeMetarAtPath(const string& propPath);
175
176     typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
177     MetarPropertiesList::iterator findMetarAtPath(const string &propPath);
178 protected:
179     void bind();
180     void unbind();
181     void update( double dt );
182
183     void checkNearbyMetar();
184
185     long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
186
187     SGPropertyNode_ptr _rootNode;
188     SGPropertyNode_ptr _ground_elevation_n;
189     SGPropertyNode_ptr _max_age_n;
190
191     bool _enabled;
192     bool _wasEnabled;
193     simgear::TiedPropertyList _tiedProperties;
194     MetarPropertiesList _metarProperties;
195     MetarRequester* _requester;
196
197 };
198
199 static bool commandRequestMetar(const SGPropertyNode* arg)
200 {
201   SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
202   if (!envMgr) {
203     return false;
204   }
205   
206   BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
207   if (!self) {
208     return false;
209   }
210   
211   string icao(arg->getStringValue("station"));
212   boost::to_upper(icao);
213   string path = arg->getStringValue("path");
214   self->addMetarAtPath(path, icao);
215   return true;
216 }
217   
218 static bool commandClearMetar(const SGPropertyNode* arg)
219 {
220   SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
221   if (!envMgr) {
222     return false;
223   }
224   
225   BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
226   if (!self) {
227     return false;
228   }
229   
230   string path = arg->getStringValue("path");
231   self->removeMetarAtPath(path);
232   return true;
233 }
234   
235 /* -------------------------------------------------------------------------------- */
236 /*
237 Properties
238  ~/enabled: bool              Enables/Disables the realwx controller
239  ~/metar[1..n]: string        Target property path for metar data
240  */
241
242 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
243   _rootNode(rootNode),
244   _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
245   _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
246   _enabled(true),
247   _wasEnabled(false),
248   _requester(metarRequester)
249 {
250     
251     globals->get_commands()->addCommand("request-metar", commandRequestMetar);
252     globals->get_commands()->addCommand("clear-metar", commandClearMetar);
253 }
254
255 BasicRealWxController::~BasicRealWxController()
256 {
257     globals->get_commands()->removeCommand("request-metar");
258     globals->get_commands()->removeCommand("clear-metar");
259 }
260
261 void BasicRealWxController::init()
262 {
263     _wasEnabled = false;
264     
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,
268                                                         _requester,
269                                                         getMetarMaxAgeMin()));
270     
271     BOOST_FOREACH( SGPropertyNode_ptr n, _rootNode->getChildren("metar") ) {
272         SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
273         addMetarAtPath(metarNode->getPath(), "");
274     }
275
276     checkNearbyMetar();
277     update(0); // fetch data ASAP
278     
279     globals->get_event_mgr()->addTask("checkNearbyMetar", this,
280                                       &BasicRealWxController::checkNearbyMetar, 60 );
281 }
282
283 void BasicRealWxController::reinit()
284 {
285     _wasEnabled = false;
286     checkNearbyMetar();
287     update(0); // fetch data ASAP
288 }
289     
290 void BasicRealWxController::shutdown()
291 {
292     globals->get_event_mgr()->removeTask("checkNearbyMetar");
293 }
294
295 void BasicRealWxController::bind()
296 {
297     _tiedProperties.setRoot( _rootNode );
298     _tiedProperties.Tie( "enabled", &_enabled );
299 }
300
301 void BasicRealWxController::unbind()
302 {
303     _tiedProperties.Untie();
304 }
305
306 void BasicRealWxController::update( double dt )
307 {  
308   if( _enabled ) {
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();
314       p->update(dt);
315     }
316
317     _wasEnabled = true;
318   } else {
319     _wasEnabled = false;
320   }
321 }
322
323 void BasicRealWxController::addMetarAtPath(const string& propPath, const string& icao)
324 {
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);
329     // already exists
330     if ((*it)->getStationId() != icao) {
331       (*it)->setStationId(icao);
332       (*it)->resetTimeToLive();
333     }
334     return;
335   }
336
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);
342 }
343
344 void BasicRealWxController::removeMetarAtPath(const string &propPath)
345 {
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); // delay deletion until this goes out of scope when we return; no longer needed?
350     // TODO: this doesn't actually delete it, because doing so invalidates the
351     // BasicRealWxController::update iterator, causing a crash on Go To Airport
352     // _metarProperties.erase(it);
353   } else {
354     SG_LOG(SG_ENVIRONMENT, SG_WARN, "no metar properties at " << propPath);
355   }
356 }
357
358 BasicRealWxController::MetarPropertiesList::iterator BasicRealWxController::findMetarAtPath(const string &propPath)
359 {
360   // don not compare unprocessed property path
361   // /foo/bar[0]/baz equals /foo/bar/baz
362   SGPropertyNode_ptr n = fgGetNode(propPath,false);
363   if( false == n.valid() ) // trivial: node does not exist
364     return _metarProperties.end();
365
366   MetarPropertiesList::iterator it = _metarProperties.begin();
367   while( it != _metarProperties.end() &&
368          (*it)->get_root_node()->getPath() != n->getPath() )
369     ++it;
370
371   return it;
372 }
373   
374 void BasicRealWxController::checkNearbyMetar()
375 {
376     try {
377       const SGGeod & pos = globals->get_aircraft_position();
378
379       // check nearest airport
380       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
381
382       FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
383       if( nearestAirport == NULL ) {
384           SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM"  );
385           return;
386       }
387
388       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, 
389           "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
390
391       // if it has changed, invalidate the associated METAR
392       if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
393           SG_LOG(SG_ENVIRONMENT, SG_INFO, 
394               "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" << 
395               _metarProperties[0]->getStationId() <<
396               "', new: '" << nearestAirport->ident() << "'" );
397           _metarProperties[0]->setStationId( nearestAirport->ident() );
398           _metarProperties[0]->resetTimeToLive();
399       }
400     }
401     catch( sg_exception & ) {
402       return;
403     }
404     
405 }
406
407 /* -------------------------------------------------------------------------------- */
408
409 class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester {
410 public:
411     NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
412
413     // implementation of MetarRequester
414     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id );
415
416     virtual ~NoaaMetarRealWxController()
417     {
418     }
419 private:
420     
421 };
422
423 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
424   BasicRealWxController(rootNode, this )
425 {
426 }
427
428 void NoaaMetarRealWxController::requestMetar
429 (
430   MetarDataHandler* metarDataHandler,
431   const std::string& id
432 )
433 {
434   static const std::string NOAA_BASE_URL =
435     "http://weather.noaa.gov/pub/data/observations/metar/stations/";
436   class NoaaMetarGetRequest:
437     public simgear::HTTP::MemoryRequest
438   {
439     public:
440       NoaaMetarGetRequest( MetarDataHandler* metarDataHandler,
441                            const std::string& stationId ):
442         MemoryRequest(NOAA_BASE_URL + stationId + ".TXT"),
443         _metarDataHandler(metarDataHandler)
444       {
445         std::ostringstream buf;
446         buf <<  globals->get_time_params()->get_cur_time();
447         requestHeader("X-TIME") = buf.str();
448       }
449
450       virtual void onDone()
451       {
452         if( responseCode() != 200 )
453         {
454           SG_LOG
455           (
456             SG_ENVIRONMENT,
457             SG_WARN,
458             "metar download failed:" << url() << ": reason:" << responseReason()
459           );
460           return;
461         }
462
463         _metarDataHandler->handleMetarData
464         (
465           simgear::strutils::simplify(responseBody())
466         );
467       }
468
469       virtual void onFail()
470       {
471         SG_LOG(SG_ENVIRONMENT, SG_INFO, "metar download failure");
472         _metarDataHandler->handleMetarFailure();
473       }
474
475     private:
476       MetarDataHandler * _metarDataHandler;
477   };
478
479   string upperId = boost::to_upper_copy(id);
480
481   SG_LOG
482   (
483     SG_ENVIRONMENT,
484     SG_INFO,
485     "NoaaMetarRealWxController::update(): "
486     "spawning load request for station-id '" << upperId << "'"
487   );
488   FGHTTPClient* http = static_cast<FGHTTPClient*>(globals->get_subsystem("http"));
489   if (http) {
490       http->makeRequest(new NoaaMetarGetRequest(metarDataHandler, upperId));
491   }
492 }
493
494 /* -------------------------------------------------------------------------------- */
495     
496 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
497 {
498   return new NoaaMetarRealWxController( rootNode );
499 }
500     
501 RealWxController::~RealWxController()
502 {
503 }
504
505 /* -------------------------------------------------------------------------------- */
506
507 } // namespace Environment