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