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