]> git.mxchange.org Git - flightgear.git/blob - src/Environment/realwx_ctrl.cxx
Fix #535: METAR from command line ignored
[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 #include "metarproperties.hxx"
29 #include "metarairportfilter.hxx"
30 #include "fgmetar.hxx"
31
32 #include <Main/fg_props.hxx>
33
34 #include <boost/foreach.hpp>
35
36 #include <simgear/structure/exception.hxx>
37 #include <simgear/misc/strutils.hxx>
38 #include <simgear/props/tiedpropertylist.hxx>
39 #include <simgear/io/HTTPClient.hxx>
40 #include <simgear/io/HTTPRequest.hxx>
41 #include <simgear/timing/sg_time.hxx>
42 #include <simgear/structure/event_mgr.hxx>
43
44 #include <algorithm>
45
46 namespace Environment {
47 /* -------------------------------------------------------------------------------- */
48
49 class FGHTTPClient : public simgear::HTTP::Client {
50 public:
51     FGHTTPClient();
52 };
53
54 FGHTTPClient::FGHTTPClient()
55 {
56     string proxyHost(fgGetString("/sim/presets/proxy/host"));
57     int proxyPort(fgGetInt("/sim/presets/proxy/port"));
58     string proxyAuth(fgGetString("/sim/presets/proxy/auth"));
59     
60     if (!proxyHost.empty()) {
61         setProxy(proxyHost, proxyPort, proxyAuth);
62     }
63 }
64
65 /* -------------------------------------------------------------------------------- */
66
67 class MetarDataHandler {
68 public:
69     virtual void handleMetarData( const std::string & data ) = 0;
70 };
71
72 class MetarRequester {
73 public:
74     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id ) = 0;
75 };
76
77 /* -------------------------------------------------------------------------------- */
78
79 class LiveMetarProperties : public MetarProperties, MetarDataHandler {
80 public:
81     LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
82     virtual ~LiveMetarProperties();
83     virtual void update( double dt );
84
85     virtual double getTimeToLive() const { return _timeToLive; }
86     virtual void setTimeToLive( double value ) { _timeToLive = value; }
87
88     // implementation of MetarDataHandler
89     virtual void handleMetarData( const std::string & data );
90
91     static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
92     static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
93
94 private:
95     double _timeToLive;
96     double _pollingTimer;
97     MetarRequester * _metarRequester;
98 };
99
100 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
101
102 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
103     MetarProperties( rootNode ),
104     _timeToLive(0.0),
105     _pollingTimer(0.0),
106     _metarRequester(metarRequester)
107 {
108     _tiedProperties.Tie("time-to-live", &_timeToLive );
109 }
110
111 LiveMetarProperties::~LiveMetarProperties()
112 {
113     _tiedProperties.Untie();
114 }
115
116 void LiveMetarProperties::update( double dt )
117 {
118     _timeToLive -= dt;
119     _pollingTimer -= dt;
120     if( _timeToLive < 0.0 ) {
121         _timeToLive = 0.0;
122         std::string stationId = getStationId();
123         if( stationId.empty() ) return;
124         if( _pollingTimer > 0.0 ) return;
125         _metarRequester->requestMetar( this, stationId );
126         _pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
127     }
128 }
129
130 void LiveMetarProperties::handleMetarData( const std::string & data )
131 {
132     SG_LOG( SG_ENVIRONMENT, SG_INFO, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
133     _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
134     setMetar( data );
135 }
136
137 /* -------------------------------------------------------------------------------- */
138
139 class BasicRealWxController : public RealWxController
140 {
141 public:
142     BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
143     virtual ~BasicRealWxController ();
144
145     virtual void init ();
146     virtual void reinit ();
147     virtual void shutdown ();
148     
149 protected:
150     void bind();
151     void unbind();
152     void update( double dt );
153
154     void checkNearbyMetar();
155
156     long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
157
158     SGPropertyNode_ptr _rootNode;
159     SGPropertyNode_ptr _ground_elevation_n;
160     SGPropertyNode_ptr _max_age_n;
161
162     bool _enabled;
163     bool __enabled;
164     simgear::TiedPropertyList _tiedProperties;
165     typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
166     MetarPropertiesList _metarProperties;
167
168 };
169
170 /* -------------------------------------------------------------------------------- */
171 /*
172 Properties
173  ~/enabled: bool              Enables/Disables the realwx controller
174  ~/metar[1..n]: string        Target property path for metar data
175  */
176
177 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
178   _rootNode(rootNode),
179   _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
180   _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
181   _enabled(true),
182   __enabled(false)
183 {
184     // at least instantiate MetarProperties for /environment/metar
185     _metarProperties.push_back( new LiveMetarProperties( 
186             fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ), metarRequester ));
187
188     BOOST_FOREACH( SGPropertyNode_ptr n, rootNode->getChildren("metar") ) {
189        SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
190
191        // check for duplicate entries
192        bool existingElement = false;
193        BOOST_FOREACH( LiveMetarProperties_ptr p, _metarProperties ) {
194          if( p->get_root_node()->getPath() == metarNode->getPath() ) {
195            existingElement = true;
196            break;
197          }
198        }
199
200        if( existingElement )
201          continue;
202
203        SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << metarNode->getPath() );
204         _metarProperties.push_back( new LiveMetarProperties( metarNode, metarRequester ));
205     }
206 }
207
208 BasicRealWxController::~BasicRealWxController()
209 {
210 }
211
212 void BasicRealWxController::init()
213 {
214     __enabled = false;
215     update(0); // fetch data ASAP
216     
217     globals->get_event_mgr()->addTask("checkNearbyMetar", this,
218                                       &BasicRealWxController::checkNearbyMetar, 60 );
219 }
220
221 void BasicRealWxController::reinit()
222 {
223     __enabled = false;
224 }
225     
226 void BasicRealWxController::shutdown()
227 {
228     globals->get_event_mgr()->removeTask("checkNearbyMetar");
229 }
230
231 void BasicRealWxController::bind()
232 {
233     _tiedProperties.setRoot( _rootNode );
234     _tiedProperties.Tie( "enabled", &_enabled );
235 }
236
237 void BasicRealWxController::unbind()
238 {
239     _tiedProperties.Untie();
240 }
241
242 void BasicRealWxController::update( double dt )
243 {
244   if( _enabled ) {
245     bool firstIteration = !__enabled; // first iteration after being enabled?
246
247     // clock tick for every METAR in stock
248     BOOST_FOREACH(LiveMetarProperties* p, _metarProperties) {
249       // first round? All received METARs are outdated
250       if( firstIteration ) p->setTimeToLive( 0.0 );
251       p->update(dt);
252     }
253
254     __enabled = true;
255   } else {
256     __enabled = false;
257   }
258 }
259
260 void BasicRealWxController::checkNearbyMetar()
261 {
262     try {
263       const SGGeod & pos = globals->get_aircraft_position();
264
265       // check nearest airport
266       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
267
268       FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
269       if( nearestAirport == NULL ) {
270           SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM"  );
271           return;
272       }
273
274       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, 
275           "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
276
277       // if it has changed, invalidate the associated METAR
278       if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
279           SG_LOG(SG_ENVIRONMENT, SG_INFO, 
280               "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" << 
281               _metarProperties[0]->getStationId() <<
282               "', new: '" << nearestAirport->ident() << "'" );
283           _metarProperties[0]->setStationId( nearestAirport->ident() );
284           _metarProperties[0]->setTimeToLive( 0.0 );
285       }
286     }
287     catch( sg_exception & ) {
288       return;
289     }
290     
291 }
292
293 /* -------------------------------------------------------------------------------- */
294
295 class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester {
296 public:
297     NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
298     virtual ~NoaaMetarRealWxController();
299     virtual void update( double dt );
300
301     // implementation of MetarRequester
302     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id );
303
304 private:
305     FGHTTPClient _http;
306 };
307
308 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
309   BasicRealWxController(rootNode, this )
310 {
311 }
312
313 NoaaMetarRealWxController::~NoaaMetarRealWxController()
314 {
315 }
316
317 void NoaaMetarRealWxController::update( double dt )
318 {
319     _http.update();
320     BasicRealWxController::update( dt );
321 }
322
323 void NoaaMetarRealWxController::requestMetar( MetarDataHandler * metarDataHandler, const std::string & id )
324 {
325     class NoaaMetarGetRequest : public simgear::HTTP::Request
326     {
327     public:
328         NoaaMetarGetRequest(MetarDataHandler* metarDataHandler, const string& stationId ) :
329               Request("http://weather.noaa.gov/pub/data/observations/metar/stations/" + stationId + ".TXT"),
330               _fromProxy(false),
331               _metarDataHandler(metarDataHandler)
332           {
333           }
334
335           virtual string_list requestHeaders() const
336           {
337               string_list reply;
338               reply.push_back("X-TIME");
339               return reply;
340           }
341
342           virtual std::string header(const std::string& name) const
343           {
344               string reply;
345
346               if( name == "X-TIME" ) {
347                   std::ostringstream buf;
348                   buf <<  globals->get_time_params()->get_cur_time();
349                   reply = buf.str();
350               }
351
352               return reply;
353           }
354
355           virtual void responseHeader(const string& key, const string& value)
356           {
357               if (key == "x-metarproxy") {
358                   _fromProxy = true;
359               }
360           }
361
362           virtual void gotBodyData(const char* s, int n)
363           {
364               _metar += string(s, n);
365           }
366
367           virtual void responseComplete()
368           {
369               if (responseCode() == 200) {
370                   _metarDataHandler->handleMetarData( _metar);
371               } else {
372                   SG_LOG(SG_ENVIRONMENT, SG_WARN, "metar download failed:" << url() << ": reason:" << responseReason());
373               }
374           }
375
376           bool fromMetarProxy() const
377           { return _fromProxy; }
378     private:  
379         string _metar;
380         bool _fromProxy;
381         MetarDataHandler * _metarDataHandler;
382     };
383
384
385     SG_LOG(SG_ENVIRONMENT, SG_INFO, 
386         "NoaaMetarRealWxController::update(): spawning load request for station-id '" << id << "'" );
387     _http.makeRequest(new NoaaMetarGetRequest(metarDataHandler, id));
388 }
389
390 /* -------------------------------------------------------------------------------- */
391
392 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
393 {
394 //    string dataSource = rootNode->getStringValue("data-source", "noaa" );
395 //    if( dataSource == "nwx" ) {
396 //      return new NwxMetarRealWxController( rootNode );
397 //    } else {
398       return new NoaaMetarRealWxController( rootNode );
399 //    }
400 }
401
402 /* -------------------------------------------------------------------------------- */
403
404 } // namespace Environment