1 // metar interface class demo
3 // Written by Melchior FRANZ, started December 2003.
5 // Copyright (C) 2003 Melchior FRANZ - mfranz@aon.at
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.
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.
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.
31 #include <boost/algorithm/string.hpp>
33 #include <simgear/debug/logstream.hxx>
34 #include <simgear/environment/metar.hxx>
35 #include <simgear/structure/exception.hxx>
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>
43 using namespace simgear;
45 class MetarRequest : public HTTP::Request
53 MetarRequest(const std::string& stationId) :
54 HTTP::Request("http://weather.noaa.gov/pub/data/observations/metar/stations/" + boost::to_upper_copy(stationId) + ".TXT"),
63 virtual void responseHeader(const string& key, const string& value)
65 if (key == "x-metarproxy") {
70 virtual void gotBodyData(const char* s, int n)
72 metarData += string(s, n);
75 virtual void responseComplete()
77 if (responseCode() == 200) {
80 SG_LOG(SG_ENVIRONMENT, SG_WARN, "metar download failed:" << url() << ": reason:" << responseReason());
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
110 const char *azimuthName(double d)
112 const char *dir[] = {
113 "N", "NNE", "NE", "ENE",
114 "E", "ESE", "SE", "SSE",
115 "S", "SSW", "SW", "WSW",
116 "W", "WNW", "NW", "NNW"
123 return dir[int(d / 22.5)];
127 // round double to 10^g
128 double rnd(double r, int g = 0)
130 double f = pow(10.0, g);
131 return f * floor(r / f + 0.5);
135 ostream& operator<<(ostream& s, const SGMetarVisibility& v)
138 int m = v.getModifier();
140 if (m == SGMetarVisibility::GREATER_THAN)
142 else if (m == SGMetarVisibility::LESS_THAN)
148 double dist = rnd(v.getVisibility_m(), 1);
150 buf << rnd(dist, 1) << " m";
152 buf << rnd(dist / 1000.0, -1) << " km";
154 const char *dir = "";
156 if ((i = v.getDirection()) != -1) {
157 dir = azimuthName(i);
160 buf << "\t\t\t\t\t" << mod << rnd(v.getVisibility_sm(), -1) << " US-miles " << dir;
161 return s << buf.str();
165 void printReport(SGMetar *m)
167 #define NaN SGMetarNaN
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)";
182 cout << "METAR Report" << s << endl;
183 cout << "============" << endl;
184 cout << "Airport-Id:\t\t" << m->getId() << endl;
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;
196 SGMetarVisibility minvis = m->getMinVisibility();
197 SGMetarVisibility maxvis = m->getMaxVisibility();
198 double min = minvis.getVisibility_m();
199 double max = maxvis.getVisibility_m();
202 cout << "min. Visibility:\t" << minvis << endl;
203 cout << "max. Visibility:\t" << maxvis << endl;
205 cout << "Visibility:\t\t" << minvis << endl;
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;
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;
225 d = m->getWindSpeed_kmh();
226 cout << "Wind:\t\t\t";
228 cout << "none" << endl;
230 if ((i = m->getWindDir()) == -1)
231 cout << "from variable directions";
233 cout << "from the " << azimuthName(i) << " (" << i << "°)";
234 cout << " at " << rnd(d, -1) << " km/h";
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";
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";
249 int from = m->getWindRangeFrom();
250 int to = m->getWindRangeTo();
252 cout << "\t\t\tvariable from " << azimuthName(from);
253 cout << " to " << azimuthName(to);
254 cout << " (" << from << "°--" << to << "°)" << endl;
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;
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;
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;
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();
287 const char *coverage_string[5] = {
288 "clear skies", "few clouds", "scattered clouds", "broken clouds", "sky overcast"
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");
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 << ')';
302 cout << "\t\t\t" << rnd(cloud->getAltitude_m(), 1) << " m";
308 map<string, SGMetarRunway> rm = m->getRunways();
309 map<string, SGMetarRunway>::iterator runway;
310 for (runway = rm.begin(); runway != rm.end(); runway++) {
312 if (!strcmp(runway->first.c_str(), "ALL"))
313 cout << "All runways:\t\t";
315 cout << "Runway " << runway->first << ":\t\t";
316 SGMetarRunway rwy = runway->second;
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);
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);
335 if (! surface.empty()) {
336 vector<string>::iterator rwysurf = surface.begin();
337 for (i = 0; rwysurf != surface.end(); rwysurf++, i++) {
345 // assemble visibility string
346 SGMetarVisibility minvis = rwy.getMinVisibility();
347 SGMetarVisibility maxvis = rwy.getMaxVisibility();
348 if ((d = minvis.getVisibility_m()) != NaN) {
350 cout << endl << "\t\t\t";
353 if (maxvis.getVisibility_m() != d) {
354 cout << endl << "\t\t\t" << maxvis << endl;
358 if (rwy.getWindShear()) {
360 cout << endl << "\t\t\t";
361 cout << "critical wind shear" << endl;
370 void printArgs(SGMetar *m, double airport_elevation)
372 #define NaN SGMetarNaN
378 sprintf(buf, "--airport=%s ", m->getId());
382 sprintf(buf, "--start-date-gmt=%4d:%02d:%02d:%02d:%02d:00 ",
383 m->getYear(), m->getMonth(), m->getDay(),
384 m->getHour(), m->getMinute());
388 const char *coverage_string[5] = {
389 "clear", "few", "scattered", "broken", "overcast"
391 vector<SGMetarCloud> cv = m->getClouds();
392 vector<SGMetarCloud>::iterator cloud;
393 for (i = 0, cloud = cv.begin(); i < 5; i++) {
395 double altitude = -99999;
396 if (cloud != cv.end()) {
397 coverage = cloud->getCoverage();
398 altitude = coverage ? cloud->getAltitude_ft() + airport_elevation : -99999;
401 sprintf(buf, "--prop:/environment/clouds/layer[%d]/coverage=%s ", i, coverage_string[coverage]);
403 sprintf(buf, "--prop:/environment/clouds/layer[%d]/elevation-ft=%.0lf ", i, altitude);
405 sprintf(buf, "--prop:/environment/clouds/layer[%d]/thickness-ft=500 ", i);
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);
423 sprintf(&buf[pos], "elevation-ft=%.0lf", elevation);
425 sprintf(&buf[pos], "turbulence-norm=%.0lf", 0.0);
428 if (visibility != NaN) {
429 sprintf(&buf[pos], "visibility-m=%.0lf", visibility);
432 if (temperature != NaN) {
433 sprintf(&buf[pos], "temperature-degc=%.0lf", temperature);
436 if (dewpoint != NaN) {
437 sprintf(&buf[pos], "dewpoint-degc=%.0lf", dewpoint);
440 if (pressure != NaN) {
441 sprintf(&buf[pos], "pressure-sea-level-inhg=%.0lf", pressure);
444 if (wind_dir != NaN) {
445 sprintf(&buf[pos], "wind-from-heading-deg=%d", wind_dir);
448 if (wind_speed != NaN) {
449 sprintf(&buf[pos], "wind-speed-kt=%.0lf", wind_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);
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);
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;
482 void getproxy(string& host, string& port)
487 const char *p = getenv("http_proxy");
492 if (!strncmp(p, "http://", 7))
501 for (t = s + strlen(s); t > s; t--)
502 if (!isspace(t[-1]) && t[-1] != '/')
518 "Usage: metar [-v] [-e elevation] [-r|-c] <list of ICAO airport ids or METAR strings>\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"
528 " http_proxy set proxy in the form \"http://host:port/\"\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"
540 int main(int argc, char *argv[])
543 bool verbose = false;
544 double elevation = 0.0;
551 string proxy_host, proxy_port;
552 getproxy(proxy_host, proxy_port);
554 Socket::initSockets();
557 http.setProxy(proxy_host, atoi(proxy_port.c_str()));
559 for (int i = 1; i < argc; i++) {
560 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
562 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
564 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--report"))
566 else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--command-line"))
568 else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--elevation")) {
570 cerr << "-e option used without elevation" << endl;
573 elevation = strtod(argv[i], 0);
575 static bool shown = false;
576 if (verbose && !shown) {
577 cerr << "Proxy host: '" << proxy_host << "'" << endl;
578 cerr << "Proxy port: '" << proxy_port << "'" << endl << endl;
583 MetarRequest* mr = new MetarRequest(argv[i]);
584 HTTP::Request_ptr own(mr);
585 http.makeRequest(mr);
587 // spin until the request completes, fails or times out
588 SGTimeStamp start(SGTimeStamp::now());
589 while (start.elapsedMSec() < 8000) {
591 if (mr->complete || mr->failed) {
594 SGTimeStamp::sleepForMSec(1);
598 throw sg_io_exception("metar download failed (or timed out)");
600 SGMetar *m = new SGMetar(mr->metarData);
602 //SGMetar *m = new SGMetar("2004/01/11 01:20\nLOWG 110120Z AUTO VRB01KT 0050 1600N R35/0600 FG M06/M06 Q1019 88//////\n");
605 cerr << G"INPUT: " << m->getData() << ""N << endl;
607 const char *unused = m->getUnusedData();
609 cerr << R"UNUSED: " << unused << ""N << endl;
615 printArgs(m, elevation);
618 } catch (const sg_io_exception& e) {
619 cerr << R"ERROR: " << e.getFormattedMessage().c_str() << ""N << endl << endl;