]> git.mxchange.org Git - flightgear.git/blob - src/Main/metar_main.cxx
Reduce amount of log output at level=debug.
[flightgear.git] / src / Main / metar_main.cxx
1 // metar interface class demo
2 //
3 // Written by Melchior FRANZ, started December 2003.
4 //
5 // Copyright (C) 2003  Melchior FRANZ - mfranz@aon.at
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #include <iomanip>
24 #include <sstream>
25 #include <iostream>
26 #include <string.h>
27 #include <time.h>
28 #include <cstdlib>
29 #include <cstdio>
30
31 #include <boost/algorithm/string.hpp>
32
33 #include <simgear/debug/logstream.hxx>
34 #include <simgear/environment/metar.hxx>
35 #include <simgear/structure/exception.hxx>
36
37 #include <simgear/io/HTTPClient.hxx>
38 #include <simgear/io/HTTPRequest.hxx>
39 #include <simgear/io/raw_socket.hxx>
40 #include <simgear/timing/timestamp.hxx>
41
42 using namespace std;
43 using namespace simgear;
44
45 class MetarRequest : public HTTP::Request
46 {
47 public:
48     bool complete;
49     bool failed;
50     string metarData;
51     bool fromProxy;
52     
53     MetarRequest(const std::string& stationId) : 
54         HTTP::Request("http://weather.noaa.gov/pub/data/observations/metar/stations/" + boost::to_upper_copy(stationId) + ".TXT"),
55         complete(false),
56         failed(false)
57     {
58         fromProxy = false;
59     }
60     
61 protected:
62     
63     virtual void responseHeader(const string& key, const string& value)
64     {
65         if (key == "x-metarproxy") {
66             fromProxy = true;
67         }
68     }
69
70     virtual void gotBodyData(const char* s, int n)
71     {
72         metarData += string(s, n);
73     }
74
75     virtual void responseComplete()
76     {
77         if (responseCode() == 200) {
78             complete = true;
79         } else {
80             SG_LOG(SG_ENVIRONMENT, SG_WARN, "metar download failed:" << url() << ": reason:" << responseReason());
81             failed = true;
82         }
83     }
84 };
85
86 // text color
87 #if defined(__linux__) || defined(__sun) || defined(__CYGWIN__) \
88     || defined( __FreeBSD__ ) || defined ( sgi )
89 #       define R "\033[31;1m"           // red
90 #       define G "\033[32;1m"           // green
91 #       define Y "\033[33;1m"           // yellow
92 #       define B "\033[34;1m"           // blue
93 #       define M "\033[35;1m"           // magenta
94 #       define C "\033[36;1m"           // cyan
95 #       define W "\033[37;1m"           // white
96 #       define N "\033[m"               // normal
97 #else
98 #       define R ""
99 #       define G ""
100 #       define Y ""
101 #       define B ""
102 #       define M ""
103 #       define C ""
104 #       define W ""
105 #       define N ""
106 #endif
107
108
109
110 const char *azimuthName(double d)
111 {
112         const char *dir[] = {
113                 "N", "NNE", "NE", "ENE",
114                 "E", "ESE", "SE", "SSE",
115                 "S", "SSW", "SW", "WSW",
116                 "W", "WNW", "NW", "NNW"
117         };
118         d += 11.25;
119         while (d < 0)
120                 d += 360;
121         while (d >= 360)
122                 d -= 360;
123         return dir[int(d / 22.5)];
124 }
125
126
127 // round double to 10^g
128 double rnd(double r, int g = 0)
129 {
130         double f = pow(10.0, g);
131         return f * floor(r / f + 0.5);
132 }
133
134
135 ostream& operator<<(ostream& s, const SGMetarVisibility& v)
136 {
137         ostringstream buf;
138         int m = v.getModifier();
139         const char *mod;
140         if (m == SGMetarVisibility::GREATER_THAN)
141                 mod = ">=";
142         else if (m == SGMetarVisibility::LESS_THAN)
143                 mod = "<";
144         else
145                 mod = "";
146         buf << mod;
147
148         double dist = rnd(v.getVisibility_m(), 1);
149         if (dist < 1000.0)
150                 buf << rnd(dist, 1) << " m";
151         else
152                 buf << rnd(dist / 1000.0, -1) << " km";
153
154         const char *dir = "";
155         int i;
156         if ((i = v.getDirection()) != -1) {
157                 dir = azimuthName(i);
158                 buf << " " << dir;
159         }
160         buf << "\t\t\t\t\t" << mod << rnd(v.getVisibility_sm(), -1) << " US-miles " << dir;
161         return s << buf.str();
162 }
163
164
165 void printReport(SGMetar *m)
166 {
167 #define NaN SGMetarNaN
168         const char *s;
169         char buf[256];
170         double d;
171         int i, lineno;
172
173         if ((i = m->getReportType()) == SGMetar::AUTO)
174                 s = "\t\t(automatically generated)";
175         else if (i == SGMetar::COR)
176                 s = "\t\t(manually corrected)";
177         else if (i == SGMetar::RTD)
178                 s = "\t\t(routine delayed)";
179         else
180                 s = "";
181
182         cout << "METAR Report" << s << endl;
183         cout << "============" << endl;
184         cout << "Airport-Id:\t\t" << m->getId() << endl;
185
186
187         // date/time
188         int year = m->getYear();
189         int month = m->getMonth();
190         cout << "Report time:\t\t" << year << '/' << month << '/' << m->getDay();
191         cout << ' ' << m->getHour() << ':';
192         cout << setw(2) << setfill('0') << m->getMinute() << " UTC" << endl;
193
194
195         // visibility
196         SGMetarVisibility minvis = m->getMinVisibility();
197         SGMetarVisibility maxvis = m->getMaxVisibility();
198         double min = minvis.getVisibility_m();
199         double max = maxvis.getVisibility_m();
200         if (min != NaN) {
201                 if (max != NaN) {
202                         cout << "min. Visibility:\t" << minvis << endl;
203                         cout << "max. Visibility:\t" << maxvis << endl;
204                 } else
205                         cout << "Visibility:\t\t" << minvis << endl;
206         }
207
208
209         // directed visibility
210         const SGMetarVisibility *dirvis = m->getDirVisibility();
211         for (i = 0; i < 8; i++, dirvis++)
212                 if (dirvis->getVisibility_m() != NaN)
213                         cout << "\t\t\t" << *dirvis << endl;
214
215
216         // vertical visibility
217         SGMetarVisibility vertvis = m->getVertVisibility();
218         if ((d = vertvis.getVisibility_ft()) != NaN)
219                 cout << "Vert. visibility:\t" << vertvis << endl;
220         else if (vertvis.getModifier() == SGMetarVisibility::NOGO)
221                 cout << "Vert. visibility:\timpossible to determine" << endl;
222
223
224         // wind
225         d = m->getWindSpeed_kmh();
226         cout << "Wind:\t\t\t";
227         if (d < .1)
228                 cout << "none" << endl;
229         else {
230                 if ((i = m->getWindDir()) == -1)
231                         cout << "from variable directions";
232                 else
233                         cout << "from the " << azimuthName(i) << " (" << i << "°)";
234                 cout << " at " << rnd(d, -1) << " km/h";
235
236                 cout << "\t\t" << rnd(m->getWindSpeed_kt(), -1) << " kt";
237                 cout << " = " << rnd(m->getWindSpeed_mph(), -1) << " mph";
238                 cout << " = " << rnd(m->getWindSpeed_mps(), -1) << " m/s";
239                 cout << endl;
240
241                 if ((d = m->getGustSpeed_kmh()) != NaN) {
242                         cout << "\t\t\twith gusts at " << rnd(d, -1) << " km/h";
243                         cout << "\t\t\t" << rnd(m->getGustSpeed_kt(), -1) << " kt";
244                         cout << " = " << rnd(m->getGustSpeed_mph(), -1) << " mph";
245                         cout << " = " << rnd(m->getGustSpeed_mps(), -1) << " m/s";
246                         cout << endl;
247                 }
248
249                 int from = m->getWindRangeFrom();
250                 int to = m->getWindRangeTo();
251                 if (from != to) {
252                         cout << "\t\t\tvariable from " << azimuthName(from);
253                         cout << " to " << azimuthName(to);
254                         cout << " (" << from << "°--" << to << "°)" << endl;
255                 }
256         }
257
258
259         // temperature/humidity/air pressure
260         if ((d = m->getTemperature_C()) != NaN) {
261                 cout << "Temperature:\t\t" << d << "°C\t\t\t\t\t";
262                 cout << rnd(m->getTemperature_F(), -1) << "°F" << endl;
263
264                 if ((d = m->getDewpoint_C()) != NaN) {
265                         cout << "Dewpoint:\t\t" << d << "°C\t\t\t\t\t";
266                         cout << rnd(m->getDewpoint_F(), -1) << "°F"  << endl;
267                         cout << "Rel. Humidity:\t\t" << rnd(m->getRelHumidity()) << "%" << endl;
268                 }
269         }
270         if ((d = m->getPressure_hPa()) != NaN) {
271                 cout << "Pressure:\t\t" << rnd(d) << " hPa\t\t\t\t";
272                 cout << rnd(m->getPressure_inHg(), -2) << " in. Hg" << endl;
273         }
274
275
276         // weather phenomena
277         vector<string> wv = m->getWeather();
278         vector<string>::iterator weather;
279         for (i = 0, weather = wv.begin(); weather != wv.end(); weather++, i++) {
280                 cout << (i ? ", " : "Weather:\t\t") << weather->c_str();
281         }
282         if (i)
283                 cout << endl;
284
285
286         // cloud layers
287         const char *coverage_string[5] = {
288                 "clear skies", "few clouds", "scattered clouds", "broken clouds", "sky overcast"
289         };
290         vector<SGMetarCloud> cv = m->getClouds();
291         vector<SGMetarCloud>::iterator cloud;
292         for (lineno = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, lineno++) {
293                 cout << (lineno ? "\t\t\t" : "Sky condition:\t\t");
294
295                 if ((i = cloud->getCoverage()) != -1)
296                         cout << coverage_string[i];
297                 if ((d = cloud->getAltitude_ft()) != NaN)
298                         cout << " at " << rnd(d, 1) << " ft";
299                 if ((s = cloud->getTypeLongString()))
300                         cout << " (" << s << ')';
301                 if (d != NaN)
302                         cout << "\t\t\t" << rnd(cloud->getAltitude_m(), 1) << " m";
303                 cout << endl;
304         }
305
306
307         // runways
308         map<string, SGMetarRunway> rm = m->getRunways();
309         map<string, SGMetarRunway>::iterator runway;
310         for (runway = rm.begin(); runway != rm.end(); runway++) {
311                 lineno = 0;
312                 if (!strcmp(runway->first.c_str(), "ALL"))
313                         cout << "All runways:\t\t";
314                 else
315                         cout << "Runway " << runway->first << ":\t\t";
316                 SGMetarRunway rwy = runway->second;
317
318                 // assemble surface string
319                 vector<string> surface;
320                 if ((s = rwy.getDepositString()) && strlen(s))
321                         surface.push_back(s);
322                 if ((s = rwy.getExtentString()) && strlen(s))
323                         surface.push_back(s);
324                 if ((d = rwy.getDepth()) != NaN) {
325                         sprintf(buf, "%.1lf mm", d * 1000.0);
326                         surface.push_back(buf);
327                 }
328                 if ((s = rwy.getFrictionString()) && strlen(s))
329                         surface.push_back(s);
330                 if ((d = rwy.getFriction()) != NaN) {
331                         sprintf(buf, "friction: %.2lf", d);
332                         surface.push_back(buf);
333                 }
334
335                 if (! surface.empty()) {
336                         vector<string>::iterator rwysurf = surface.begin();
337                         for (i = 0; rwysurf != surface.end(); rwysurf++, i++) {
338                                 if (i)
339                                         cout << ", ";
340                                 cout << *rwysurf;
341                         }
342                         lineno++;
343                 }
344
345                 // assemble visibility string
346                 SGMetarVisibility minvis = rwy.getMinVisibility();
347                 SGMetarVisibility maxvis = rwy.getMaxVisibility();
348                 if ((d = minvis.getVisibility_m()) != NaN) {
349                         if (lineno++)
350                                 cout << endl << "\t\t\t";
351                         cout << minvis;
352                 }
353                 if (maxvis.getVisibility_m() != d) {
354                         cout << endl << "\t\t\t" << maxvis << endl;
355                         lineno++;
356                 }
357
358                 if (rwy.getWindShear()) {
359                         if (lineno++)
360                                 cout << endl << "\t\t\t";
361                         cout << "critical wind shear" << endl;
362                 }
363                 cout << endl;
364         }
365         cout << endl;
366 #undef NaN
367 }
368
369
370 void printArgs(SGMetar *m, double airport_elevation)
371 {
372 #define NaN SGMetarNaN
373         vector<string> args;
374         char buf[256];
375         int i;
376
377         // ICAO id
378         sprintf(buf, "--airport=%s ", m->getId());
379         args.push_back(buf);
380
381         // report time
382         sprintf(buf, "--start-date-gmt=%4d:%02d:%02d:%02d:%02d:00 ",
383                         m->getYear(), m->getMonth(), m->getDay(),
384                         m->getHour(), m->getMinute());
385         args.push_back(buf);
386
387         // cloud layers
388         const char *coverage_string[5] = {
389                 "clear", "few", "scattered", "broken", "overcast"
390         };
391         vector<SGMetarCloud> cv = m->getClouds();
392         vector<SGMetarCloud>::iterator cloud;
393         for (i = 0, cloud = cv.begin(); i < 5; i++) {
394                 int coverage = 0;
395                 double altitude = -99999;
396                 if (cloud != cv.end()) {
397                         coverage = cloud->getCoverage();
398                         altitude = coverage ? cloud->getAltitude_ft() + airport_elevation : -99999;
399                         cloud++;
400                 }
401                 sprintf(buf, "--prop:/environment/clouds/layer[%d]/coverage=%s ", i, coverage_string[coverage]);
402                 args.push_back(buf);
403                 sprintf(buf, "--prop:/environment/clouds/layer[%d]/elevation-ft=%.0lf ", i, altitude);
404                 args.push_back(buf);
405                 sprintf(buf, "--prop:/environment/clouds/layer[%d]/thickness-ft=500 ", i);
406                 args.push_back(buf);
407         }
408
409         // environment (temperature, dewpoint, visibility, pressure)
410         // metar sets don't provide aloft information; we have to
411         // set the same values for all boundary levels
412         int wind_dir = m->getWindDir();
413         double visibility = m->getMinVisibility().getVisibility_m();
414         double dewpoint = m->getDewpoint_C();
415         double temperature = m->getTemperature_C();
416         double pressure = m->getPressure_inHg();
417         double wind_speed = m->getWindSpeed_kt();
418         double elevation = -100;
419         for (i = 0; i < 3; i++, elevation += 2000.0) {
420                 sprintf(buf, "--prop:/environment/config/boundary/entry[%d]/", i);
421                 int pos = strlen(buf);
422
423                 sprintf(&buf[pos], "elevation-ft=%.0lf", elevation);
424                 args.push_back(buf);
425                 sprintf(&buf[pos], "turbulence-norm=%.0lf", 0.0);
426                 args.push_back(buf);
427
428                 if (visibility != NaN) {
429                         sprintf(&buf[pos], "visibility-m=%.0lf", visibility);
430                         args.push_back(buf);
431                 }
432                 if (temperature != NaN) {
433                         sprintf(&buf[pos], "temperature-degc=%.0lf", temperature);
434                         args.push_back(buf);
435                 }
436                 if (dewpoint != NaN) {
437                         sprintf(&buf[pos], "dewpoint-degc=%.0lf", dewpoint);
438                         args.push_back(buf);
439                 }
440                 if (pressure != NaN) {
441                         sprintf(&buf[pos], "pressure-sea-level-inhg=%.0lf", pressure);
442                         args.push_back(buf);
443                 }
444                 if (wind_dir != NaN) {
445                         sprintf(&buf[pos], "wind-from-heading-deg=%d", wind_dir);
446                         args.push_back(buf);
447                 }
448                 if (wind_speed != NaN) {
449                         sprintf(&buf[pos], "wind-speed-kt=%.0lf", wind_speed);
450                         args.push_back(buf);
451                 }
452         }
453
454         // wind dir@speed
455         int range_from = m->getWindRangeFrom();
456         int range_to = m->getWindRangeTo();
457         double gust_speed = m->getGustSpeed_kt();
458         if (wind_speed != NaN && wind_dir != -1) {
459                 strcpy(buf, "--wind=");
460                 if (range_from != -1 && range_to != -1)
461                         sprintf(&buf[strlen(buf)], "%d:%d", range_from, range_to);
462                 else
463                         sprintf(&buf[strlen(buf)], "%d", wind_dir);
464                 sprintf(&buf[strlen(buf)], "@%.0lf", wind_speed);
465                 if (gust_speed != NaN)
466                         sprintf(&buf[strlen(buf)], ":%.0lf", gust_speed);
467                 args.push_back(buf);
468         }
469         
470
471         // output everything
472         //cout << "fgfs" << endl;
473         vector<string>::iterator arg;
474         for (i = 0, arg = args.begin(); arg != args.end(); i++, arg++) {
475                 cout << "\t" << *arg << endl;
476         }
477         cout << endl;
478 #undef NaN
479 }
480
481
482 void getproxy(string& host, string& port)
483 {
484         host = "";
485         port = "80";
486
487         const char *p = getenv("http_proxy");
488         if (!p)
489                 return;
490         while (isspace(*p))
491                 p++;
492         if (!strncmp(p, "http://", 7))
493                 p += 7;
494         if (!*p)
495                 return;
496
497         char s[256], *t;
498         strncpy(s, p, 255);
499         s[255] = '\0';
500
501         for (t = s + strlen(s); t > s; t--)
502                 if (!isspace(t[-1]) && t[-1] != '/')
503                         break;
504         *t = '\0';
505
506         t = strchr(s, ':');
507         if (t) {
508                 *t++ = '\0';
509                 port = t;
510         }
511         host = s;
512 }
513
514
515 void usage()
516 {
517         printf(
518                 "Usage: metar [-v] [-e elevation] [-r|-c] <list of ICAO airport ids or METAR strings>\n"
519                 "       metar -h\n"
520                 "\n"
521                 "       -h|--help            show this help\n"
522                 "       -v|--verbose         verbose output\n"
523                 "       -r|--report          print report (default)\n"
524                 "       -c|--command-line    print command line\n"
525                 "       -e E|--elevation E   set airport elevation to E meters\n"
526                 "                            (added to cloud bases in command line mode)\n"
527                 "Environment:\n"
528                 "       http_proxy           set proxy in the form \"http://host:port/\"\n"
529                 "\n"
530                 "Examples:\n"
531                 "       $ metar ksfo koak\n"
532                 "       $ metar -c ksfo -r ksfo\n"
533                 "       $ metar \"LOWL 161500Z 19004KT 160V240 9999 FEW035 SCT300 29/23 Q1006 NOSIG\"\n"
534                 "       $ fgfs  `metar -e 183 -c loww`\n"
535                 "       $ http_proxy=http://localhost:3128/ metar ksfo\n"
536                 "\n"
537         );
538 }
539
540 int main(int argc, char *argv[])
541 {
542         bool report = true;
543         bool verbose = false;
544         double elevation = 0.0;
545
546         if (argc <= 1) {
547                 usage();
548                 return 0;
549         }
550
551         string proxy_host, proxy_port;
552         getproxy(proxy_host, proxy_port);
553
554   Socket::initSockets();
555   
556     HTTP::Client http;
557     http.setProxy(proxy_host, atoi(proxy_port.c_str()));
558     
559         for (int i = 1; i < argc; i++) {
560                 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
561                         usage();
562                 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
563                         verbose = true;
564                 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--report"))
565                         report = true;
566                 else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--command-line"))
567                         report = false;
568                 else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--elevation")) {
569                         if (++i >= argc) {
570                                 cerr << "-e option used without elevation" << endl;
571                                 return 1;
572                         }
573                         elevation = strtod(argv[i], 0);
574                 } else {
575                         static bool shown = false;
576                         if (verbose && !shown) {
577                                 cerr << "Proxy host: '" << proxy_host << "'" << endl;
578                                 cerr << "Proxy port: '" << proxy_port << "'" << endl << endl;
579                                 shown = true;
580                         }
581
582                         try {
583                 MetarRequest* mr = new MetarRequest(argv[i]);
584                 HTTP::Request_ptr own(mr);
585                 http.makeRequest(mr);
586                 
587             // spin until the request completes, fails or times out
588                 SGTimeStamp start(SGTimeStamp::now());
589                 while (start.elapsedMSec() <  8000) {
590                     http.update();
591                     if (mr->complete || mr->failed) {
592                         break;
593                     }
594                     SGTimeStamp::sleepForMSec(1);
595                 }
596                 
597                 if (!mr->complete) {
598                     throw sg_io_exception("metar download failed (or timed out)");
599                 }
600                                 SGMetar *m = new SGMetar(mr->metarData);
601                                 
602                                 //SGMetar *m = new SGMetar("2004/01/11 01:20\nLOWG 110120Z AUTO VRB01KT 0050 1600N R35/0600 FG M06/M06 Q1019 88//////\n");
603
604                                 if (verbose) {
605                                         cerr << G"INPUT: " << m->getData() << ""N << endl;
606
607                                         const char *unused = m->getUnusedData();
608                                         if (*unused)
609                                                 cerr << R"UNUSED: " << unused << ""N << endl;
610                                 }
611
612                                 if (report)
613                                         printReport(m);
614                                 else
615                                         printArgs(m, elevation);
616
617                                 delete m;
618                         } catch (const sg_io_exception& e) {
619                                 cerr << R"ERROR: " << e.getFormattedMessage().c_str() << ""N << endl << endl;
620                         }
621                 }
622         }
623         return 0;
624 }
625
626