]> git.mxchange.org Git - flightgear.git/blob - src/Environment/realwx_ctrl.cxx
METAR selection of runway works for reset.
[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/HTTPRequest.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 };
56
57 class MetarRequester {
58 public:
59     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id ) = 0;
60 };
61
62 /* -------------------------------------------------------------------------------- */
63
64 class LiveMetarProperties : public MetarProperties, MetarDataHandler {
65 public:
66     LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
67     virtual ~LiveMetarProperties();
68     virtual void update( double dt );
69
70     virtual double getTimeToLive() const { return _timeToLive; }
71     virtual void resetTimeToLive()
72     { _timeToLive = 0.00; _pollingTimer = 0.0; }
73
74     // implementation of MetarDataHandler
75     virtual void handleMetarData( const std::string & data );
76
77     static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
78     static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
79
80 private:
81     double _timeToLive;
82     double _pollingTimer;
83     MetarRequester * _metarRequester;
84 };
85
86 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
87
88 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
89     MetarProperties( rootNode ),
90     _timeToLive(0.0),
91     _pollingTimer(0.0),
92     _metarRequester(metarRequester)
93 {
94     _tiedProperties.Tie("time-to-live", &_timeToLive );
95 }
96
97 LiveMetarProperties::~LiveMetarProperties()
98 {
99     _tiedProperties.Untie();
100 }
101
102 void LiveMetarProperties::update( double dt )
103 {
104     _timeToLive -= dt;
105     _pollingTimer -= dt;
106     if( _timeToLive <= 0.0 ) {
107         _timeToLive = 0.0;
108         std::string stationId = getStationId();
109         if( stationId.empty() ) return;
110         if( _pollingTimer > 0.0 ) return;
111         _metarRequester->requestMetar( this, stationId );
112         _pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
113     }
114 }
115
116 void LiveMetarProperties::handleMetarData( const std::string & data )
117 {
118     SG_LOG( SG_ENVIRONMENT, SG_INFO, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
119     _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
120     setMetar( data );
121 }
122
123 /* -------------------------------------------------------------------------------- */
124
125 class BasicRealWxController : public RealWxController
126 {
127 public:
128     BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
129     virtual ~BasicRealWxController ();
130
131     virtual void init ();
132     virtual void reinit ();
133     virtual void shutdown ();
134     
135     /**
136      * Create a metar-property binding at the specified property path,
137      * and initiate a request for the specified station-ID (which may be
138      * empty). If the property path is already mapped, the station ID
139      * will be updated.
140      */
141     void addMetarAtPath(const string& propPath, const string& icao);
142   
143     void removeMetarAtPath(const string& propPath);
144 protected:
145     void bind();
146     void unbind();
147     void update( double dt );
148
149     void checkNearbyMetar();
150
151     long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
152
153     SGPropertyNode_ptr _rootNode;
154     SGPropertyNode_ptr _ground_elevation_n;
155     SGPropertyNode_ptr _max_age_n;
156
157     bool _enabled;
158     bool _wasEnabled;
159     simgear::TiedPropertyList _tiedProperties;
160     typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
161     MetarPropertiesList _metarProperties;
162     MetarRequester* _requester;
163
164 };
165
166 static bool commandRequestMetar(const SGPropertyNode* arg)
167 {
168   SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
169   if (!envMgr) {
170     return false;
171   }
172   
173   BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
174   if (!self) {
175     return false;
176   }
177   
178   string icao(arg->getStringValue("station"));
179   boost::to_upper(icao);
180   string path = arg->getStringValue("path");
181   self->addMetarAtPath(path, icao);
182   return true;
183 }
184   
185 static bool commandClearMetar(const SGPropertyNode* arg)
186 {
187   SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
188   if (!envMgr) {
189     return false;
190   }
191   
192   BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
193   if (!self) {
194     return false;
195   }
196   
197   string path = arg->getStringValue("path");
198   self->removeMetarAtPath(path);
199   return true;
200 }
201   
202 /* -------------------------------------------------------------------------------- */
203 /*
204 Properties
205  ~/enabled: bool              Enables/Disables the realwx controller
206  ~/metar[1..n]: string        Target property path for metar data
207  */
208
209 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
210   _rootNode(rootNode),
211   _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
212   _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
213   _enabled(true),
214   _wasEnabled(false),
215   _requester(metarRequester)
216 {
217     // at least instantiate MetarProperties for /environment/metar
218     _metarProperties.push_back( new LiveMetarProperties( 
219             fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ), metarRequester ));
220
221     BOOST_FOREACH( SGPropertyNode_ptr n, rootNode->getChildren("metar") ) {
222         SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
223         addMetarAtPath(metarNode->getPath(), "");
224     }
225   
226     SGCommandMgr::instance()->addCommand("request-metar", commandRequestMetar);
227     SGCommandMgr::instance()->addCommand("clear-metar", commandClearMetar);
228 }
229
230 BasicRealWxController::~BasicRealWxController()
231 {
232   //SGCommandMgr::instance()->removeCommand("request-metar");
233 }
234
235 void BasicRealWxController::init()
236 {
237     _wasEnabled = false;
238     
239     checkNearbyMetar();
240     update(0); // fetch data ASAP
241     
242     globals->get_event_mgr()->addTask("checkNearbyMetar", this,
243                                       &BasicRealWxController::checkNearbyMetar, 60 );
244 }
245
246 void BasicRealWxController::reinit()
247 {
248     _wasEnabled = false;
249     checkNearbyMetar();
250     update(0); // fetch data ASAP
251 }
252     
253 void BasicRealWxController::shutdown()
254 {
255     globals->get_event_mgr()->removeTask("checkNearbyMetar");
256 }
257
258 void BasicRealWxController::bind()
259 {
260     _tiedProperties.setRoot( _rootNode );
261     _tiedProperties.Tie( "enabled", &_enabled );
262 }
263
264 void BasicRealWxController::unbind()
265 {
266     _tiedProperties.Untie();
267 }
268
269 void BasicRealWxController::update( double dt )
270 {  
271   if( _enabled ) {
272     bool firstIteration = !_wasEnabled;
273     // clock tick for every METAR in stock
274     BOOST_FOREACH(LiveMetarProperties* p, _metarProperties) {
275       // first round? All received METARs are outdated
276       if( firstIteration ) p->resetTimeToLive();
277       p->update(dt);
278     }
279
280     _wasEnabled = true;
281   } else {
282     _wasEnabled = false;
283   }
284 }
285
286 void BasicRealWxController::addMetarAtPath(const string& propPath, const string& icao)
287 {
288   // check for duplicate entries
289   BOOST_FOREACH( LiveMetarProperties_ptr p, _metarProperties ) {
290     if( p->get_root_node()->getPath() == propPath ) {
291       // already exists
292       if (p->getStationId() != icao) {
293         p->setStationId(icao);
294         p->resetTimeToLive();
295       }
296       
297       return;
298     }
299   } // of exitsing metar properties iteration
300
301   SGPropertyNode_ptr metarNode = fgGetNode(propPath, true);
302   SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << propPath );
303   LiveMetarProperties_ptr p(new LiveMetarProperties( metarNode, _requester ));
304   _metarProperties.push_back(p);
305   p->setStationId(icao);
306 }
307
308 void BasicRealWxController::removeMetarAtPath(const string &propPath)
309 {
310   MetarPropertiesList::iterator it = _metarProperties.begin();
311   for (; it != _metarProperties.end(); ++it) {
312     LiveMetarProperties_ptr p(*it);
313     if( p->get_root_node()->getPath() == propPath ) {
314       _metarProperties.erase(it);
315       // final ref will drop, and delete the MetarProperties, when we return
316       return;
317     }
318   }
319   
320   SG_LOG(SG_ENVIRONMENT, SG_WARN, "no metar properties at " << propPath);
321 }
322   
323 void BasicRealWxController::checkNearbyMetar()
324 {
325     try {
326       const SGGeod & pos = globals->get_aircraft_position();
327
328       // check nearest airport
329       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
330
331       FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
332       if( nearestAirport == NULL ) {
333           SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM"  );
334           return;
335       }
336
337       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, 
338           "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
339
340       // if it has changed, invalidate the associated METAR
341       if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
342           SG_LOG(SG_ENVIRONMENT, SG_INFO, 
343               "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" << 
344               _metarProperties[0]->getStationId() <<
345               "', new: '" << nearestAirport->ident() << "'" );
346           _metarProperties[0]->setStationId( nearestAirport->ident() );
347           _metarProperties[0]->resetTimeToLive();
348       }
349     }
350     catch( sg_exception & ) {
351       return;
352     }
353     
354 }
355
356 /* -------------------------------------------------------------------------------- */
357
358 class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester {
359 public:
360     NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
361
362     // implementation of MetarRequester
363     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id );
364
365 private:
366     
367 };
368
369 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
370   BasicRealWxController(rootNode, this )
371 {
372 }
373
374 void NoaaMetarRealWxController::requestMetar( MetarDataHandler * metarDataHandler, const std::string & id )
375 {
376     class NoaaMetarGetRequest : public simgear::HTTP::Request
377     {
378     public:
379         NoaaMetarGetRequest(MetarDataHandler* metarDataHandler, const string& stationId ) :
380               Request("http://weather.noaa.gov/pub/data/observations/metar/stations/" + stationId + ".TXT"),
381               _fromProxy(false),
382               _metarDataHandler(metarDataHandler)
383           {
384           }
385
386           virtual string_list requestHeaders() const
387           {
388               string_list reply;
389               reply.push_back("X-TIME");
390               return reply;
391           }
392
393           virtual std::string header(const std::string& name) const
394           {
395               string reply;
396
397               if( name == "X-TIME" ) {
398                   std::ostringstream buf;
399                   buf <<  globals->get_time_params()->get_cur_time();
400                   reply = buf.str();
401               }
402
403               return reply;
404           }
405
406           virtual void responseHeader(const string& key, const string& value)
407           {
408               if (key == "x-metarproxy") {
409                   _fromProxy = true;
410               }
411           }
412
413           virtual void gotBodyData(const char* s, int n)
414           {
415               _metar += string(s, n);
416           }
417
418           virtual void responseComplete()
419           {
420               if (responseCode() == 200) {
421                   _metarDataHandler->handleMetarData( simgear::strutils::simplify(_metar) );
422               } else {
423                   SG_LOG(SG_ENVIRONMENT, SG_WARN, "metar download failed:" << url() << ": reason:" << responseReason());
424               }
425           }
426         
427         virtual void failed()
428         {
429             SG_LOG(SG_ENVIRONMENT, SG_INFO, "metar download failure");
430         }
431
432 //          bool fromMetarProxy() const
433 //          { return _fromProxy; }
434     private:  
435         string _metar;
436         bool _fromProxy;
437         MetarDataHandler * _metarDataHandler;
438     };
439
440     string upperId = boost::to_upper_copy(id);
441
442     SG_LOG(SG_ENVIRONMENT, SG_INFO,
443         "NoaaMetarRealWxController::update(): spawning load request for station-id '" << upperId << "'" );
444     FGHTTPClient* http = static_cast<FGHTTPClient*>(globals->get_subsystem("http"));
445     if (http) {
446         http->makeRequest(new NoaaMetarGetRequest(metarDataHandler, upperId));
447     }
448 }
449
450 /* -------------------------------------------------------------------------------- */
451
452 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
453 {
454   return new NoaaMetarRealWxController( rootNode );
455 }
456
457 /* -------------------------------------------------------------------------------- */
458
459 } // namespace Environment