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