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