diff --git a/src/Environment/Makefile.am b/src/Environment/Makefile.am index 9efb9d7bd..fb7607919 100644 --- a/src/Environment/Makefile.am +++ b/src/Environment/Makefile.am @@ -4,8 +4,19 @@ noinst_LIBRARIES = libEnvironment.a -libEnvironment_a_SOURCES = environment.cxx environment.hxx \ - environment_mgr.cxx environment_mgr.hxx \ - environment_ctrl.cxx environment_ctrl.hxx +libEnvironment_a_SOURCES = \ + environment.cxx environment.hxx \ + environment_mgr.cxx environment_mgr.hxx \ + environment_ctrl.cxx environment_ctrl.hxx \ + metar.cxx metar.hxx + +bin_PROGRAMS = metar + +metar_SOURCES = metar-main.cxx metar.cxx metar.hxx + +metar_LDADD = \ + -lsgio -lsgbucket -lsgmisc -lsgstructure -lsgdebug \ + -lplibnet -lplibul \ + -lz $(base_LIBS) INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src diff --git a/src/Environment/metar-main.cxx b/src/Environment/metar-main.cxx new file mode 100644 index 000000000..273320573 --- /dev/null +++ b/src/Environment/metar-main.cxx @@ -0,0 +1,482 @@ +// metar interface class demo +// +// Written by Melchior FRANZ, started December 2003. +// +// Copyright (C) 2003 Melchior FRANZ - mfranz@aon.at +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA +// +// $Id$ + +#include +#include + +#include +#include + +#include "metar.hxx" + +using std::ostringstream; + +// text color +#if defined(__linux__) || defined( __sun__ ) ||defined(__CYGWIN__) || defined( __FreeBSD__ ) +# define R "\033[31;1m" // red +# define G "\033[32;1m" // green +# define Y "\033[33;1m" // yellow +# define B "\033[34;1m" // blue +# define M "\033[35;1m" // magenta +# define C "\033[36;1m" // cyan +# define W "\033[37;1m" // white +# define N "\033[m" // normal +#else +# define R "" +# define G "" +# define Y "" +# define B "" +# define M "" +# define C "" +# define W "" +# define N "" +#endif + + +const char *azimuthName(double d); +double rnd(double number, int digits); +void printReport(Metar *m); +void printVisibility(FGMetarVisibility *v); +void printArgs(Metar *m, double airport_elevation); + + +const char *azimuthName(double d) +{ + const char *dir[] = { + "N", "NNE", "NE", "ENE", + "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", + "W", "WNW", "NW", "NNW" + }; + d += 11.25; + while (d < 0) + d += 360; + while (d >= 360) + d -= 360; + return dir[int(d / 22.5)]; +} + + +// round double to 10^g +double rnd(double r, int g = 0) +{ + double f = pow(10.0, g); + return f * rint(r / f); +} + + +ostream& operator<<(ostream& s, FGMetarVisibility& v) +{ + ostringstream buf; + int m = v.getModifier(); + const char *mod; + if (m == FGMetarVisibility::GREATER_THAN) + mod = ">="; + else if (m == FGMetarVisibility::LESS_THAN) + mod = "<"; + else + mod = ""; + buf << mod; + + double dist = rnd(v.getVisibility_m(), 1); + if (dist < 1000.0) + buf << rnd(dist, 1) << " m"; + else + buf << rnd(dist / 1000.0, -1) << " km"; + + const char *dir = ""; + int i; + if ((i = v.getDirection()) != -1) { + dir = azimuthName(i); + buf << " " << dir; + } + buf << "\t\t\t\t\t" << mod << rnd(v.getVisibility_sm(), -1) << " US-miles " << dir; + return s << buf.str(); +} + + +void printReport(Metar *m) +{ +#define NaN FGMetarNaN + const char *s; + char buf[256]; + double d; + int i, lineno; + + if ((i = m->getReportType()) == Metar::AUTO) + s = "\t\t(automatically generated)"; + else if (i == Metar::COR) + s = "\t\t(manually corrected)"; + else if (i == Metar::RTD) + s = "\t\t(routine delayed)"; + else + s = ""; + + cout << "METAR Report" << s << endl; + cout << "============" << endl; + cout << "Airport-Id:\t\t" << m->getId() << endl; + + + // date/time + int year = m->getYear(); + int month = m->getMonth(); + cout << "Report time:\t\t"; + if (year != -1 && month != -1) + cout << year << '/' << month << '/' << m->getDay(); + cout << ' ' << m->getHour() << ':'; + cout << std::setw(2) << std::setfill('0') << m->getMinute() << " UTC" << endl; + + + // visibility + FGMetarVisibility minvis = m->getMinVisibility(); + FGMetarVisibility maxvis = m->getMaxVisibility(); + double min = minvis.getVisibility_m(); + double max = maxvis.getVisibility_m(); + if (min != NaN) { + if (max != NaN) { + cout << "min. Visibility:\t" << minvis << endl; + cout << "max. Visibility:\t" << maxvis << endl; + } else + cout << "Visibility:\t\t" << minvis << endl; + } + + + // directed visibility + FGMetarVisibility *dirvis = m->getDirVisibility(); + for (i = 0; i < 8; i++, dirvis++) + if (dirvis->getVisibility_m() != NaN) + cout << "\t\t\t" << *dirvis << endl; + + + // vertical visibility + FGMetarVisibility vertvis = m->getVertVisibility(); + if ((d = vertvis.getVisibility_ft()) != NaN) + cout << "Vert. visibility:\t" << vertvis << endl; + else if (vertvis.getModifier() == FGMetarVisibility::NOGO) + cout << "Vert. visibility:\timpossible to determine" << endl; + + + // wind + d = m->getWindSpeed_kmh(); + cout << "Wind:\t\t\t"; + if (d < .1) + cout << "none" << endl; + else { + if ((i = m->getWindDir()) == -1) + cout << "from variable directions"; + else + cout << "from the " << azimuthName(i) << " (" << i << "°)"; + cout << " at " << rnd(d, -1) << " km/h"; + + cout << "\t\t" << rnd(m->getWindSpeed_kt(), -1) << " kt"; + cout << " = " << rnd(m->getWindSpeed_mph(), -1) << " mph"; + cout << " = " << rnd(m->getWindSpeed_mps(), -1) << " m/s"; + cout << endl; + + if ((d = m->getGustSpeed_kmh()) != NaN) { + cout << "\t\t\twith gusts at " << rnd(d, -1) << " km/h"; + cout << "\t\t\t" << rnd(m->getGustSpeed_kt(), -1) << " kt"; + cout << " = " << rnd(m->getGustSpeed_mph(), -1) << " mph"; + cout << " = " << rnd(m->getGustSpeed_mps(), -1) << " m/s"; + cout << endl; + } + + int from = m->getWindRangeFrom(); + int to = m->getWindRangeTo(); + if (from != to) { + cout << "\t\t\tvariable from " << azimuthName(from); + cout << " to " << azimuthName(to); + cout << " (" << from << "°--" << to << "°)" << endl; + } + } + + + // temperature/humidity/air pressure + if ((d = m->getTemperature_C()) != NaN) { + cout << "Temperature:\t\t" << d << "°C\t\t\t\t\t"; + cout << rnd(m->getTemperature_F(), -1) << "°F" << endl; + + if ((d = m->getDewpoint_C()) != NaN) { + cout << "Dewpoint:\t\t" << d << "°C\t\t\t\t\t"; + cout << rnd(m->getDewpoint_F(), -1) << "°F" << endl; + cout << "Rel. Humidity:\t\t" << rnd(m->getRelHumidity()) << "%" << endl; + } + } + if ((d = m->getPressure_hPa()) != NaN) { + cout << "Pressure:\t\t" << rnd(d) << " hPa\t\t\t\t"; + cout << rnd(m->getPressure_inHg(), -2) << " in. Hg" << endl; + } + + + // weather phenomena + vector wv = m->getWeather(); + vector::iterator weather; + for (i = 0, weather = wv.begin(); weather != wv.end(); weather++, i++) { + cout << (i ? ", " : "Weather:\t\t") << weather->c_str(); + } + if (i) + cout << endl; + + + // cloud layers + const char *coverage_string[5] = { + "clear skies", "few clouds", "scattered clouds", "broken clouds", "sky overcast" + }; + vector cv = m->getClouds(); + vector::iterator cloud; + for (lineno = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, lineno++) { + cout << (lineno ? "\t\t\t" : "Sky condition:\t\t"); + + if ((i = cloud->getCoverage()) != -1) + cout << coverage_string[i]; + if ((d = cloud->getAltitude_ft()) != NaN) + cout << " at " << rnd(d, 1) << " ft"; + if ((s = cloud->getTypeLongString())) + cout << " (" << s << ')'; + if (d != NaN) + cout << "\t\t\t" << rnd(cloud->getAltitude_m(), 1) << " m"; + cout << endl; + } + + + // runways + map rm = m->getRunways(); + map::iterator runway; + for (runway = rm.begin(); runway != rm.end(); runway++) { + lineno = 0; + if (!strcmp(runway->first.c_str(), "ALL")) + cout << "All runways:\t\t"; + else + cout << "Runway " << runway->first << ":\t\t"; + FGMetarRunway rwy = runway->second; + + // assemble surface string + vector surface; + if ((s = rwy.getDeposit()) && strlen(s)) + surface.push_back(s); + if ((s = rwy.getExtentString()) && strlen(s)) + surface.push_back(s); + if ((d = rwy.getDepth()) != NaN) { + sprintf(buf, "%.0lf mm", d * 1000.0); + surface.push_back(buf); + } + if ((s = rwy.getFrictionString()) && strlen(s)) + surface.push_back(s); + if ((d = rwy.getFriction()) != NaN) { + sprintf(buf, "friction: %.2lf", d); + surface.push_back(buf); + } + + if (surface.size()) { + vector::iterator rwysurf = surface.begin(); + for (i = 0; rwysurf != surface.end(); rwysurf++, i++) { + if (i) + cout << ", "; + cout << *rwysurf; + } + lineno++; + } + + // assemble visibility string + FGMetarVisibility minvis = rwy.getMinVisibility(); + FGMetarVisibility maxvis = rwy.getMaxVisibility(); + if ((d = minvis.getVisibility_m()) != NaN) { + if (lineno++) + cout << endl << "\t\t\t"; + cout << minvis; + } + if (maxvis.getVisibility_m() != d) { + cout << endl << "\t\t\t" << maxvis << endl; + lineno++; + } + + if (rwy.getWindShear()) { + if (lineno++) + cout << endl << "\t\t\t"; + cout << "critical wind shear" << endl; + } + cout << endl; + } + cout << endl; +#undef NaN +} + + +void printArgs(Metar *m, double airport_elevation) +{ +#define NaN FGMetarNaN + vector args; + char buf[256]; + int i; + + // ICAO id + sprintf(buf, "--airport=%s ", m->getId()); + args.push_back(buf); + + // report time + sprintf(buf, "--start-date-gmt=%4d:%02d:%02d:%02d:%02d:00 ", + m->getYear(), m->getMonth(), m->getDay(), + m->getHour(), m->getMinute()); + args.push_back(buf); + + // cloud layers + const char *coverage_string[5] = { + "clear", "few", "scattered", "broken", "overcast" + }; + vector cv = m->getClouds(); + vector::iterator cloud; + for (i = 0, cloud = cv.begin(); i < 5; i++) { + int coverage = 0; + double altitude = -99999; + if (cloud != cv.end()) { + coverage = cloud->getCoverage(); + altitude = coverage ? cloud->getAltitude_ft() + airport_elevation : -99999; + cloud++; + } + sprintf(buf, "--prop:/environment/clouds/layer[%d]/coverage=%s ", i, coverage_string[coverage]); + args.push_back(buf); + sprintf(buf, "--prop:/environment/clouds/layer[%d]/elevation-ft=%.0lf ", i, altitude); + args.push_back(buf); + sprintf(buf, "--prop:/environment/clouds/layer[%d]/thickness-ft=500 ", i); + args.push_back(buf); + } + + // environment (temperature, dewpoint, visibility, pressure) + // metar sets don't provide aloft information; we have to + // set the same values for all boundary levels + int wind_dir = m->getWindDir(); + double visibility = m->getMinVisibility().getVisibility_m(); + double dewpoint = m->getDewpoint_C(); + double temperature = m->getTemperature_C(); + double pressure = m->getPressure_inHg(); + double wind_speed = m->getWindSpeed_kt(); + double elevation = -100; + for (i = 0; i < 3; i++, elevation += 2000.0) { + sprintf(buf, "--prop:/environment/config/boundary/entry[%d]/", i); + int pos = strlen(buf); + + sprintf(&buf[pos], "elevation-ft=%.0lf", elevation); + args.push_back(buf); + sprintf(&buf[pos], "turbulence-norm=%.0lf", 0.0); + args.push_back(buf); + + if (visibility != NaN) { + sprintf(&buf[pos], "visibility-m=%.0lf", visibility); + args.push_back(buf); + } + if (temperature != NaN) { + sprintf(&buf[pos], "temperature-degc=%.0lf", temperature); + args.push_back(buf); + } + if (dewpoint != NaN) { + sprintf(&buf[pos], "dewpoint-degc=%.0lf", dewpoint); + args.push_back(buf); + } + if (pressure != NaN) { + sprintf(&buf[pos], "pressure-sea-level-inhg=%.0lf", pressure); + args.push_back(buf); + } + if (wind_dir != NaN) { + sprintf(&buf[pos], "wind-from-heading-deg=%d", wind_dir); + args.push_back(buf); + } + if (wind_speed != NaN) { + sprintf(&buf[pos], "wind-speed-kt=%.0lf", wind_speed); + args.push_back(buf); + } + } + + // wind dir@speed + int range_from = m->getWindRangeFrom(); + int range_to = m->getWindRangeTo(); + double gust_speed = m->getGustSpeed_kt(); + if (wind_speed != NaN && wind_dir != -1) { + strcpy(buf, "--wind="); + if (range_from != -1 && range_to != -1) + sprintf(&buf[strlen(buf)], "%d:%d", range_from, range_to); + else + sprintf(&buf[strlen(buf)], "%d", wind_dir); + sprintf(&buf[strlen(buf)], "@%.0lf", wind_speed); + if (gust_speed != NaN) + sprintf(&buf[strlen(buf)], ":%.0lf", gust_speed); + args.push_back(buf); + } + + + // output everything + cout << "fgfs" << endl; + vector::iterator arg; + for (i = 0, arg = args.begin(); arg != args.end(); i++, arg++) { + cout << "\t" << *arg << endl; + } + cout << endl; +#undef NaN +} + + + + + +const char *metar_list[] = { + "LOWW", "VHHH", "ULLI", "EHTW", "EFHK", "CYXU", 0, // note the trailing zero + "CYGK", "CYOW", "CYQY", "CYTZ", "CYXU", "EBBR", "EDDB", "EDDK", "EDVE", "EFHF", + "EFHK", "EGLC", "EGLL", "EHTW", "EIDW", "ENGM", "GMMN", "KART", "KBFI", "KBOS", + "KCCR", "KCEZ", "KCOF", "KDAL", "KDEN", "KDSM", "KEDW", "KEMT", "KENW", "KHON", + "KIGM", "KJFK", "KLAX", "KMCI", "KMKE", "KMLB", "KMSY", "KNBC", "KOAK", "KORD", + "KPNE", "KSAC", "KSAN", "KSEA", "KSFO", "KSJC", "KSMF", "KSMO", "KSNS", "KSQL", + "KSUN", "LBSF", "LEMD", "LFPG", "LFPO", "LGAT", "LHBP", "LIPQ", "LIRA", "LKPR", + "LLJR", "LOWG", "LOWI", "LOWK", "LOWL", "LOWS", "LOWW", "LOWZ", "LOXA", "LOXT", + "LOXZ", "LSZH", "LYBE", "NZWP", "ORBS", "PHNL", "ULLI", "VHHH", "WMKB", "YSSY", + 0 +}; + + +int main(int argc, char *argv[]) +{ + const char **src = metar_list; + if (argc > 1) + src = (const char **)&argv[1]; + + for (int i = 0; src[i]; i++) { + const char *icao = src[i]; + + try { + Metar *m = new Metar(icao); + //Metar *m = new Metar("2004/01/11 01:20\nLOWG 110120Z AUTO VRB01KT 0050 1600N R35/0600 FG M06/M06 Q1019 88//////\n"); + + printf(G"INPUT: %s\n"N, m->getData()); + const char *unused = m->getUnusedData(); + if (*unused) + printf(R"UNUSED: %s\n"N, unused); + + printReport(m); + //printArgs(m, 0.0); + + delete m; + } catch (const sg_io_exception& e) { + fprintf(stderr, R"ERROR: %s\n\n"N, e.getFormattedMessage().c_str()); + } + } + return 0; +} + + diff --git a/src/Environment/metar.cxx b/src/Environment/metar.cxx new file mode 100644 index 000000000..c99157fd7 --- /dev/null +++ b/src/Environment/metar.cxx @@ -0,0 +1,1101 @@ +// metar interface class +// +// Written by Melchior FRANZ, started December 2003. +// +// Copyright (C) 2003 Melchior FRANZ - mfranz@aon.at +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA +// +// $Id$ + +/** + * @file metar.cxx + * Interface for encoded Metar aviation weather data. + */ + +#include + +#include +#include +#include + +#include "metar.hxx" + +#define NaN FGMetarNaN + +/** + * The constructor takes a Metar string, or a four-letter ICAO code. In the + * latter case the metar string is downloaded from + * http://weather.noaa.gov/pub/data/observations/metar/stations/. + * The constructor throws sg_io_exceptions on failure. The "METAR" + * keyword has no effect (apart from incrementing the group counter + * @a grpcount) and can be left away. A keyword "SPECI" is + * likewise accepted. + * + * @par Examples: + * @code + * Metar *m = new Metar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013"); + * double t = m->getTemperature(); + * delete m; + * + * Metar n("KSFO"); + * double d = n.getDewpoint_C(); + * @endcode + */ +Metar::Metar(const char *m) : + _grpcount(0), + _year(-1), + _month(-1), + _day(-1), + _hour(-1), + _minute(-1), + _report_type(-1), + _wind_dir(-1), + _wind_speed(NaN), + _gust_speed(NaN), + _wind_range_from(-1), + _wind_range_to(-1), + _temp(NaN), + _dewp(NaN), + _pressure(NaN) +{ + int i; + if (isalpha(m[0]) && isalpha(m[1]) && isalpha(m[2]) && isalpha(m[3]) && !m[4]) { + for (i = 0; i < 4; i++) + _icao[i] = toupper(m[i]); + _icao[4] = '\0'; + _data = loadData(_icao); + } else { + _data = new char[strlen(m) + 1]; + strcpy(_data, m); + } + normalizeData(); + + _m = _data; + _icao[0] = '\0'; + + // NOAA preample + scanPreambleDate(); + scanPreambleTime(); + + // METAR header + scanType(); + if (!scanId() || !scanDate()) + throw sg_io_exception("metar data incomplete"); + scanModifier(); + + // base set + scanWind(); + scanVariability(); + while (scanVisibility()) ; + while (scanRwyVisRange()) ; + while (scanWeather()) ; + while (scanSkyCondition()) ; + scanTemperature(); + scanPressure(); + while (scanSkyCondition()) ; + while (scanRunwayReport()) ; + scanWindShear(); + + // appendix + while (scanColorState()) ; + scanTrendForecast(); + while (scanRunwayReport()) ; + scanRemainder(); + scanRemark(); + + if (_grpcount < 4) + throw sg_io_exception("metar data invalid"); +} + + +/** + * Clears lists and maps to discourage access after destruction. + */ +Metar::~Metar() +{ + _clouds.clear(); + _runways.clear(); + _weather.clear(); + delete[] _data; +} + + +/** + * If called with "KSFO" loads data from + * @code + * http://weather.noaa.gov/pub/data/observations/metar/stations/KSFO.TXT. + * @endcode + * Throws sg_io_exception on failure. Gives up after waiting longer than 10 seconds. + * + * @param id four-letter ICAO Metar station code, e.g. "KSFO". + * @return pointer to Metar data string, allocated by new char[]. + */ +char *Metar::loadData(const char *id) +{ + string host = "weather.noaa.gov"; + string path = "/pub/data/observations/metar/stations/"; + path += string(id) + ".TXT"; + string get = string("GET ") + path + " HTTP/1.0\r\n\r\n"; + + SGSocket *sock = new SGSocket(host, "80", "tcp"); + sock->set_timeout(10000); + if (!sock->open(SG_IO_OUT)) { + delete sock; + string err = "failed to load metar data from http://" + host + path; + throw sg_io_exception(err); + } + + sock->writestring(get.c_str()); + + int i; + const int buflen = 512; + char buf[2 * buflen]; + + // skip HTTP header + while ((i = sock->readline(buf, buflen))) + if (i <= 2 && isspace(buf[0]) && (!buf[1] || isspace(buf[1]))) + break; + if (i) { + i = sock->readline(buf, buflen); + if (i) + sock->readline(&buf[i], buflen); + } + + sock->close(); + delete sock; + + char *metar = new char[strlen(buf) + 1]; + strcpy(metar, buf); + return metar; +} + + +/** + * Replace any number of subsequent spaces by just one space. + * This makes scanning for things like "ALL RWY" easier. + */ +void Metar::normalizeData() +{ + char *src, *dest; + for (src = dest = _data; (*dest++ = *src++); ) + while (*src == ' ' && src[1] == ' ') + src++; +} + + +// \d\d\d\d/\d\d/\d\d +bool Metar::scanPreambleDate() +{ + char *m = _m; + int year, month, day; + if (!scanNumber(&m, &year, 4)) + return false; + if (*m++ != '/') + return false; + if (!scanNumber(&m, &month, 2)) + return false; + if (*m++ != '/') + return false; + if (!scanNumber(&m, &day, 2)) + return false; + if (!scanBoundary(&m)) + return false; + _year = year; + _month = month; + _day = day; + _m = m; + return true; +} + + +// \d\d:\d\d +bool Metar::scanPreambleTime() +{ + char *m = _m; + int hour, minute; + if (!scanNumber(&m, &hour, 2)) + return false; + if (*m++ != ':') + return false; + if (!scanNumber(&m, &minute, 2)) + return false; + if (!scanBoundary(&m)) + return false; + _hour = hour; + _minute = minute; + _m = m; + return true; +} + + +// (METAR|SPECI) +bool Metar::scanType() +{ + if (strncmp(_m, "METAR ", 6) && strncmp(_m, "SPECI ", 6)) + return false; + _m += 6; + _grpcount++; + return true; +} + + +// [A-Z]{4} +bool Metar::scanId() +{ + char *m = _m; + if (!(isupper(*m++) && isupper(*m++) && isupper(*m++) && isupper(*m++))) + return false; + if (!scanBoundary(&m)) + return false; + strncpy(_icao, _m, 4); + _icao[4] = '\0'; + _m = m; + _grpcount++; + return true; +} + + +// \d{6}Z +bool Metar::scanDate() +{ + char *m = _m; + int day, hour, minute; + if (!scanNumber(&m, &day, 2)) + return false; + if (!scanNumber(&m, &hour, 2)) + return false; + if (!scanNumber(&m, &minute, 2)) + return false; + if (*m++ != 'Z') + return false; + if (!scanBoundary(&m)) + return false; + _day = day; + _hour = hour; + _minute = minute; + _m = m; + _grpcount++; + return true; +} + + +// (NIL|AUTO|COR|RTD) +bool Metar::scanModifier() +{ + char *m = _m; + int type; + if (!strncmp(m, "NIL", 3)) { + _m += strlen(_m); + return true; + } + if (!strncmp(m, "AUTO", 4)) // automatically generated + m += 4, type = AUTO; + else if (!strncmp(m, "COR", 3)) // manually corrected + m += 3, type = COR; + else if (!strncmp(m, "RTD", 3)) // routine delayed + m += 3, type = RTD; + else + return false; + if (!scanBoundary(&m)) + return false; + _report_type = type; + _m = m; + _grpcount++; + return true; +} + + +// (\d{3}|VRB)\d{1,3}(G\d{2,3})?(KT|KMH|MPS) +bool Metar::scanWind() +{ + char *m = _m; + int dir; + if (!strncmp(m, "VRB", 3)) + m += 3, dir = -1; + else if (!scanNumber(&m, &dir, 3)) + return false; + + int i; + if (!scanNumber(&m, &i, 2, 3)) + return false; + double speed = i; + + double gust = NaN; + if (*m == 'G') { + m++; + if (!scanNumber(&m, &i, 2, 3)) + return false; + gust = i; + } + double factor; + if (!strncmp(m, "KT", 2)) + m += 2, factor = SG_KT_TO_MPS; + else if (!strncmp(m, "KMH", 3)) + m += 3, factor = SG_KMH_TO_MPS; + else if (!strncmp(m, "KPH", 3)) // ?? + m += 3, factor = SG_KMH_TO_MPS; + else if (!strncmp(m, "MPS", 3)) + m += 3, factor = 1.0; + else + return false; + if (!scanBoundary(&m)) + return false; + _m = m; + _wind_dir = dir; + _wind_speed = speed * factor; + if (gust != NaN) + _gust_speed = gust * factor; + _grpcount++; + return false; +} + + +// \d{3}V\d{3} +bool Metar::scanVariability() +{ + char *m = _m; + int from, to; + if (!scanNumber(&m, &from, 3)) + return false; + if (*m++ != 'V') + return false; + if (!scanNumber(&m, &to, 3)) + return false; + if (!scanBoundary(&m)) + return false; + _m = m; + _wind_range_from = from; + _wind_range_to = to; + _grpcount++; + return true; +} + + +bool Metar::scanVisibility() +// TODO: if only directed vis are given, do still set min/max +{ + char *m = _m; + double distance; + int i, dir = -1; + int modifier = FGMetarVisibility::EQUALS; +// \d{4}(N|NE|E|SE|S|SW|W|NW)? + if (scanNumber(&m, &i, 4)) { + if (*m == 'E') + m++, dir = 90; + else if (*m == 'W') + m++, dir = 270; + else if (*m == 'N') { + m++; + if (*m == 'E') + m++, dir = 45; + else if (*m == 'W') + m++, dir = 315; + else + dir = 0; + } else if (*m == 'S') { + m++; + if (*m == 'E') + m++, dir = 135; + else if (*m == 'W') + m++, dir = 225; + else + dir = 180; + } + if (i == 0) + i = 50, modifier = FGMetarVisibility::LESS_THAN; + else if (i == 9999) + i++, modifier = FGMetarVisibility::GREATER_THAN; + distance = i; + } else { +// M?(\d{1,2}|\d{1,2}/\d{1,2}|\d{1,2} \d{1,2}/\d{1,2})(SM|KM) + modifier = 0; + if (*m == 'M') + m++, modifier = FGMetarVisibility::LESS_THAN; + + if (!scanNumber(&m, &i, 1, 2)) + return false; + distance = i; + + if (*m == '/') { + m++; + if (!scanNumber(&m, &i, 1, 2)) + return false; + distance /= i; + } else if (*m == ' ') { + m++; + int denom; + if (!scanNumber(&m, &i, 1, 2)) + return false; + if (*m++ != '/') + return false; + if (!scanNumber(&m, &denom, 1, 2)) + return false; + distance += (double)i / denom; + } + + if (!strncmp(m, "SM", 2)) + distance *= SG_SM_TO_METER, m += 2; + else if (!strncmp(m, "KM", 2)) + distance *= 1000, m += 2; + else + return false; + } + if (!scanBoundary(&m)) + return false; + + FGMetarVisibility *v; + if (dir != -1) + v = &_dir_visibility[dir / 45]; + else if (_min_visibility._distance == NaN) + v = &_min_visibility; + else + v = &_max_visibility; + + v->_distance = distance; + v->_modifier = modifier; + v->_direction = dir; + _m = m; + _grpcount++; + return true; +} + + +// R\d\d[LCR]?/([PM]?\d{4}V)?[PM]?\d{4}(FT)?[DNU]? +bool Metar::scanRwyVisRange() +{ + char *m = _m; + int i; + FGMetarRunway r; + if (*m++ != 'R') + return false; + if (!scanNumber(&m, &i, 2)) + return false; + if (*m == 'L' || *m == 'C' || *m == 'R') + m++; + + char id[4]; + strncpy(id, _m + 1, i = m - _m - 1); + id[i] = '\0'; + + if (*m++ != '/') + return false; + + int from, to; + if (*m == 'P') + m++, r._min_visibility._modifier = FGMetarVisibility::GREATER_THAN; + else if (*m == 'M') + m++, r._min_visibility._modifier = FGMetarVisibility::LESS_THAN; + if (!scanNumber(&m, &from, 4)) + return false; + if (*m == 'V') { + m++; + if (*m == 'P') + m++, r._max_visibility._modifier = FGMetarVisibility::GREATER_THAN; + else if (*m == 'M') + m++, r._max_visibility._modifier = FGMetarVisibility::LESS_THAN; + if (!scanNumber(&m, &to, 4)) + return false; + } else + to = from; + + if (!strncmp(m, "FT", 2)) { + from = int(from * SG_FEET_TO_METER); + to = int(to * SG_FEET_TO_METER); + m += 2; + } + r._min_visibility._distance = from; + r._max_visibility._distance = to; + + if (*m == '/') // this is not in the spec! + *m++; + if (*m == 'D') + m++, r._min_visibility._tendency = FGMetarVisibility::DECREASING; + else if (*m == 'N') + m++, r._min_visibility._tendency = FGMetarVisibility::STABLE; + else if (*m == 'U') + m++, r._min_visibility._tendency = FGMetarVisibility::INCREASING; + + if (!scanBoundary(&m)) + return false; + _m = m; + + _runways[id]._min_visibility = r._min_visibility; + _runways[id]._max_visibility = r._max_visibility; + _grpcount++; + return true; +} + + +static const struct Token special[] = { + "NSW", "no significant weather", + "VCSH", "showers in the vicinity", + "VCTS", "thunderstorm in the vicinity", + 0, 0 +}; + + +static const struct Token description[] = { + "SH", "showers of", + "TS", "thunderstorm with", + "BC", "patches of", + "BL", "blowing", + "DR", "low drifting", + "FZ", "freezing", + "MI", "shallow", + "PR", "partial", + 0, 0 +}; + + +static const struct Token phenomenon[] = { + "DZ", "drizzle", + "GR", "hail", + "GS", "small hail and/or snow pellets", + "IC", "ice crystals", + "PE", "ice pellets", + "RA", "rain", + "SG", "snow grains", + "SN", "snow", + "UP", "unknown precipitation", + "BR", "mist", + "DU", "widespread dust", + "FG", "fog", + "FGBR", "fog bank", + "FU", "smoke", + "HZ", "haze", + "PY", "spray", + "SA", "sand", + "VA", "volcanic ash", + "DS", "duststorm", + "FC", "funnel cloud/tornado waterspout", + "PO", "well-developed dust/sand whirls", + "SQ", "squalls", + "SS", "sandstorm", + "UP", "unknown", // ... due to failed automatic acquisition + 0, 0 +}; + + +// (+|-|VC)?(NSW|MI|PR|BC|DR|BL|SH|TS|FZ)?((DZ|RA|SN|SG|IC|PE|GR|GS|UP){0,3})(BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS){0,3} +bool Metar::scanWeather() +{ + char *m = _m; + string weather; + const struct Token *a; + if ((a = scanToken(&m, special))) { + if (!scanBoundary(&m)) + return false; + _weather.push_back(a->text); + _m = m; + return true; + } + + string pre, post; + if (*m == '-') + m++, pre = "light "; + else if (*m == '+') + m++, pre = "heavy "; + else if (!strncmp(m, "VC", 2)) + m += 2, post = "in the vicinity "; + else + pre = "moderate "; + + int i; + for (i = 0; i < 3; i++) { + if (!(a = scanToken(&m, description))) + break; + weather += string(a->text) + " "; + } + for (i = 0; i < 3; i++) { + if (!(a = scanToken(&m, phenomenon))) + break; + weather += string(a->text) + " "; + } + if (!weather.length()) + return false; + if (!scanBoundary(&m)) + return false; + _m = m; + weather = pre + weather + post; + weather.erase(weather.length() - 1); + _weather.push_back(weather); + _grpcount++; + return true; +} + + +static const struct Token cloud_types[] = { + "AC", "altocumulus", + "ACC", "altocumulus castellanus", + "ACSL", "altocumulus standing lenticular", + "AS", "altostratus", + "CB", "cumulonimbus", + "CBMAM", "cumulonimbus mammatus", + "CC", "cirrocumulus", + "CCSL", "cirrocumulus standing lenticular", + "CI", "cirrus", + "CS", "cirrostratus", + "CU", "cumulus", + "CUFRA", "cumulus fractus", + "NS", "nimbostratus", + "SAC", "stratoaltocumulus", // guessed + "SC", "stratocumulus", + "SCSL", "stratocumulus standing lenticular", + "ST", "stratus", + "STFRA", "stratus fractus", + "TCU", "towering cumulus", + 0, 0 +}; + + +// (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]? +bool Metar::scanSkyCondition() +{ + char *m = _m; + int i; + FGMetarCloud cl; + + if (!strncmp(m, "CLR", i = 3) // clear + || !strncmp(m, "SKC", i = 3) // sky clear + || !strncmp(m, "NSC", i = 3) // no significant clouds + || !strncmp(m, "CAVOK", i = 5)) { // ceiling and visibility OK (implies 9999) + m += i; + if (!scanBoundary(&m)) + return false; + cl._coverage = 0; + _clouds.push_back(cl); + _m = m; + return true; + } + + if (!strncmp(m, "VV", i = 2)) // vertical visibility + ; + else if (!strncmp(m, "FEW", i = 3)) + cl._coverage = 1; + else if (!strncmp(m, "SCT", i = 3)) + cl._coverage = 2; + else if (!strncmp(m, "BKN", i = 3)) + cl._coverage = 3; + else if (!strncmp(m, "OVC", i = 3)) + cl._coverage = 4; + else + return false; + m += i; + + if (!strncmp(m, "///", 3)) // vis not measurable (e.g. because of heavy snowing) + m += 3, i = -1; + else if (scanBoundary(&m)) { + _m = m; + return true; // ignore single OVC/BKN/... + } else if (!scanNumber(&m, &i, 3)) + i = -1; + + if (cl._coverage == -1) { + if (!scanBoundary(&m)) + return false; + if (i == -1) // 'VV///' + _vert_visibility._modifier = FGMetarVisibility::NOGO; + else + _vert_visibility._distance = i * 100 * SG_FEET_TO_METER; + _m = m; + return true; + } + + if (i != -1) + cl._altitude = i * 100 * SG_FEET_TO_METER; + + const struct Token *a; + if ((a = scanToken(&m, cloud_types))) { + cl._type = a->id; + cl._type_long = a->text; + } + if (!scanBoundary(&m)) + return false; + _clouds.push_back(cl); + _m = m; + _grpcount++; + return true; +} + + +// M?[0-9]{2}/(M?[0-9]{2})? (spec) +// (M?[0-9]{2}|XX)/(M?[0-9]{2}|XX)? (Namibia) +bool Metar::scanTemperature() +{ + char *m = _m; + int sign = 1, temp, dew; + if (!strncmp(m, "XX/XX", 5)) { // not spec compliant! + _m += 5; + return scanBoundary(&_m); + } + + if (*m == 'M') + m++, sign = -1; + if (!scanNumber(&m, &temp, 2)) + return false; + temp *= sign; + + if (*m++ != '/') + return false; + if (!scanBoundary(&m)) { + if (!strncmp(m, "XX", 2)) // not spec compliant! + m += 2, sign = 0; + else { + sign = 1; + if (*m == 'M') + m++, sign = -1; + if (!scanNumber(&m, &dew, 2)) + return false; + } + if (!scanBoundary(&m)) + return false; + if (sign) + _dewp = sign * dew; + } + _temp = temp; + _m = m; + _grpcount++; + return true; +} + + +double Metar::getRelHumidity() const +{ + if (_temp == NaN || _dewp == NaN) + return NaN; + double dewp = pow(10, 7.5 * _dewp / (237.7 + _dewp)); + double temp = pow(10, 7.5 * _temp / (237.7 + _temp)); + return dewp * 100 / temp; +} + + +// [AQ]\d{4} (spec) +// [AQ]\d{2}(\d{2}|//) (Namibia) +bool Metar::scanPressure() +{ + char *m = _m; + double factor; + int press, i; + + if (*m == 'A') + factor = SG_INHG_TO_PA / 100; + else if (*m == 'Q') + factor = 100; + else + return false; + m++; + if (!scanNumber(&m, &press, 2)) + return false; + press *= 100; + if (!strncmp(m, "//", 2)) // not spec compliant! + m += 2; + else if (scanNumber(&m, &i, 2)) + press += i; + else + return false; + if (!scanBoundary(&m)) + return false; + _pressure = press * factor; + _m = m; + _grpcount++; + return true; +} + + +static const char *runway_deposit[] = { + "clear and dry", + "damp", + "wet or puddles", + "frost", + "dry snow", + "wet snow", + "slush", + "ice", + "compacted snow", + "frozen ridges" +}; + + +static const char *runway_deposit_extent[] = { + 0, "1-10%", "11-25%", 0, 0, "26-50%", 0, 0, 0, "51-100%" +}; + + +static const char *runway_friction[] = { + 0, + "poor braking action", + "poor/medium braking action", + "medium braking action", + "medium/good braking action", + "good braking action", + 0, 0, 0, + "friction: unreliable measurement" +}; + + +// \d\d(CLRD|[\d/]{4})(\d\d|//) +bool Metar::scanRunwayReport() +{ + char *m = _m; + int i; + char id[4]; + FGMetarRunway r; + + if (!scanNumber(&m, &i, 2)) + return false; + if (i == 88) + strcpy(id, "ALL"); + else if (i == 99) + strcpy(id, "REP"); // repetition of previous report + else if (i >= 50) { + i -= 50; + id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = 'R', id[3] = '\0'; + } else + id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = '\0'; + + if (!strncmp(m, "CLRD", 4)) { + m += 4; // runway cleared + r._deposit = "cleared"; + } else { + if (scanNumber(&m, &i, 1)) { + r._deposit = runway_deposit[i]; + } else if (*m == '/') + m++; + else + return false; + if (*m == '1' || *m == '2' || *m == '5' || *m == '9') { // extent of deposit + r._extent = *m - '0'; + r._extent_string = runway_deposit_extent[*m - '0']; + } else if (*m != '/') + return false; + m++; + i = -1; + if (!strncmp(m, "//", 2)) + m += 2; + else if (!scanNumber(&m, &i, 2)) + return false; + + if (i == 0) + r._depth = 0.5; // < 1 mm deep (let's say 0.5 :-) + else if (i > 0 && i <= 90) + r._depth = i / 1000.0; // i mm deep + else if (i >= 92 && i <= 98) + r._depth = (i - 90) / 20.0; + else if (i == 99) + r._comment = "runway not in use"; + else if (i == -1) // no depth given ("//") + ; + else + return false; + } + i = -1; + if (m[0] == '/' && m[1] == '/') + m += 2; + else if (!scanNumber(&m, &i, 2)) + return false; + if (i >= 1 && i < 90) { + r._friction = i / 100.0; + } else if ((i >= 91 && i <= 95) || i == 99) { + r._friction_string = runway_friction[i - 90]; + } + if (!scanBoundary(&m)) + return false; + + _runways[id]._deposit = r._deposit; + _runways[id]._extent = r._extent; + _runways[id]._extent_string = r._extent_string; + _runways[id]._depth = r._depth; + _runways[id]._friction = r._friction; + _runways[id]._friction_string = r._friction_string; + _runways[id]._comment = r._comment; + _m = m; + _grpcount++; + return true; +} + + +// WS (ALL RWYS?|RWY ?\d\d[LCR]?)? +bool Metar::scanWindShear() +{ + char *m = _m; + if (strncmp(m, "WS", 2)) + return false; + m += 2; + if (!scanBoundary(&m)) + return false; + + if (!strncmp(m, "ALL", 3)) { + m += 3; + if (!scanBoundary(&m)) + return false; + if (strncmp(m, "RWY", 3)) + return false; + m += 3; + if (*m == 'S') + m++; + if (!scanBoundary(&m)) + return false; + _runways["ALL"]._wind_shear = true; + _m = m; + return true; + } + + char id[4], *mm; + int i, cnt; + for (cnt = 0;; cnt++) { // ?? + if (strncmp(m, "RWY", 3)) + break; + m += 3; + scanBoundary(&m); + mm = m; + if (!scanNumber(&m, &i, 2)) + return false; + if (*m == 'L' || *m == 'C' || *m == 'R') + m++; + strncpy(id, mm, i = m - mm); + id[i] = '\0'; + if (!scanBoundary(&m)) + return false; + _runways[id]._wind_shear = true; + } + if (!cnt) + _runways["ALL"]._wind_shear = true; + _m = m; + return true; +} + + +bool Metar::scanTrendForecast() +{ + char *m = _m; + if (strncmp(m, "NOSIG", 5)) + return false; + + m += 5; + if (!scanBoundary(&m)) + return false; + _m = m; + return true; +} + + +// (BLU|WHT|GRN|YLO|AMB|RED) +static const struct Token colors[] = { + "BLU", "Blue", // 2500 ft, 8.0 km + "WHT", "White", // 1500 ft, 5.0 km + "GRN", "Green", // 700 ft, 3.7 km + "YLO", "Yellow", // 300 ft, 1.6 km + "AMB", "Amber", // 200 ft, 0.8 km + "RED", "Red", // <200 ft, <0.8 km + 0, 0 +}; + + +bool Metar::scanColorState() +{ + char *m = _m; + const struct Token *a; + if (!(a = scanToken(&m, colors))) + return false; + if (!scanBoundary(&m)) + return false; + //printf(Y"Code %s\n"N, a->text); + _m = m; + return true; +} + + +bool Metar::scanRemark() +{ + if (strncmp(_m, "RMK", 3)) + return false; + _m += 3; + if (!scanBoundary(&_m)) + return false; + + while (*_m) { + if (!scanRunwayReport()) { + while (*_m && !isspace(*_m)) + _m++; + scanBoundary(&_m); + } + } + return true; +} + + +bool Metar::scanRemainder() +{ + char *m = _m; + if (!(strncmp(m, "NOSIG", 5))) { + m += 5; + if (scanBoundary(&m)) + _m = m; //_comment.push_back("No significant tendency"); + } + + if (!scanBoundary(&m)) + return false; + _m = m; + return true; +} + + +bool Metar::scanBoundary(char **s) +{ + if (**s && !isspace(**s)) + return false; + while (isspace(**s)) + (*s)++; + return true; +} + + +int Metar::scanNumber(char **src, int *num, int min, int max) +{ + int i; + char *s = *src; + *num = 0; + for (i = 0; i < min; i++) { + if (!isdigit(*s)) + return 0; + else + *num = *num * 10 + *s++ - '0'; + } + for (; i < max && isdigit(*s); i++) + *num = *num * 10 + *s++ - '0'; + *src = s; + return i; +} + + +// find longest match of str in list +const struct Token *Metar::scanToken(char **str, const struct Token *list) +{ + const struct Token *longest = 0; + int maxlen = 0, len; + char *s; + for (int i = 0; (s = list[i].id); i++) { + len = strlen(s); + if (!strncmp(s, *str, len) && len > maxlen) { + maxlen = len; + longest = &list[i]; + } + } + *str += maxlen; + return longest; +} + +#undef NaN diff --git a/src/Environment/metar.hxx b/src/Environment/metar.hxx new file mode 100644 index 000000000..8dcb55705 --- /dev/null +++ b/src/Environment/metar.hxx @@ -0,0 +1,261 @@ +// metar interface class +// +// Written by Melchior FRANZ, started December 2003. +// +// Copyright (C) 2003 Melchior FRANZ - mfranz@aon.at +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA +// +// $Id$ + +#ifndef _METAR_HXX +#define _METAR_HXX + +#include +#include +#include + +#include + +SG_USING_STD(vector); +SG_USING_STD(map); +SG_USING_STD(string); + +const double FGMetarNaN = -1E20; +#define NaN FGMetarNaN + +struct Token { + char *id; + char *text; +}; + + +class Metar; + +class FGMetarVisibility { + friend class Metar; +public: + FGMetarVisibility() : + _distance(NaN), + _direction(-1), + _modifier(EQUALS), + _tendency(NONE) {} + + enum Modifier { + NOGO, + EQUALS, + LESS_THAN, + GREATER_THAN + }; + + enum Tendency { + NONE, + STABLE, + INCREASING, + DECREASING + }; + + inline double getVisibility_m() const { return _distance; } + inline double getVisibility_ft() const { return _distance == NaN ? NaN : _distance * SG_METER_TO_FEET; } + inline double getVisibility_sm() const { return _distance == NaN ? NaN : _distance * SG_METER_TO_SM; } + inline int getDirection() const { return _direction; } + inline int getModifier() const { return _modifier; } + inline int getTendency() const { return _tendency; } + +protected: + double _distance; + int _direction; + int _modifier; + int _tendency; +}; + + +// runway condition (surface and visibility) +class FGMetarRunway { + friend class Metar; +public: + FGMetarRunway() : + _deposit(0), + _extent(-1), + _extent_string(0), + _depth(NaN), + _friction(NaN), + _friction_string(0), + _comment(0), + _wind_shear(false) {} + + inline const char *getDeposit() const { return _deposit; } + inline double getExtent() const { return _extent; } + inline const char *getExtentString() const { return _extent_string; } + inline double getDepth() const { return _depth; } + inline double getFriction() const { return _friction; } + inline const char *getFrictionString() const { return _friction_string; } + inline const char *getComment() const { return _comment; } + inline const bool getWindShear() const { return _wind_shear; } + inline FGMetarVisibility getMinVisibility() const { return _min_visibility; } + inline FGMetarVisibility getMaxVisibility() const { return _max_visibility; } + +protected: + FGMetarVisibility _min_visibility; + FGMetarVisibility _max_visibility; + const char *_deposit; + int _extent; + const char *_extent_string; + double _depth; + double _friction; + const char *_friction_string; + const char *_comment; + bool _wind_shear; +}; + + +// cloud layer +class FGMetarCloud { + friend class Metar; +public: + FGMetarCloud() : + _coverage(-1), + _altitude(NaN), + _type(0), + _type_long(0) {} + + inline int getCoverage() const { return _coverage; } + inline double getAltitude_m() const { return _altitude; } + inline double getAltitude_ft() const { return _altitude == NaN ? NaN : _altitude * SG_METER_TO_FEET; } + inline char *getTypeString() const { return _type; } + inline char *getTypeLongString() const { return _type_long; } + +protected: + int _coverage; // quarters: 0 -> clear ... 4 -> overcast + double _altitude; // 1000 m + char *_type; // CU + char *_type_long; // cumulus +}; + + +class Metar { +public: + Metar(const char *m); + Metar(const string m) { Metar(m.c_str()); } + ~Metar(); + + enum ReportType { + NONE, + AUTO, + COR, + RTD + }; + + inline const char *getData() const { return _data; } + inline const char *getUnusedData() const { return _m; } + inline const char *getId() const { return _icao; } + inline int getYear() const { return _year; } + inline int getMonth() const { return _month; } + inline int getDay() const { return _day; } + inline int getHour() const { return _hour; } + inline int getMinute() const { return _minute; } + inline int getReportType() const { return _report_type; } + + inline int getWindDir() const { return _wind_dir; } + inline double getWindSpeed_mps() const { return _wind_speed; } + inline double getWindSpeed_kmh() const { return _wind_speed == NaN ? NaN : _wind_speed * 3.6; } + inline double getWindSpeed_kt() const { return _wind_speed == NaN ? NaN : _wind_speed * SG_MPS_TO_KT; } + inline double getWindSpeed_mph() const { return _wind_speed == NaN ? NaN : _wind_speed * SG_MPS_TO_MPH; } + + inline double getGustSpeed_mps() const { return _gust_speed; } + inline double getGustSpeed_kmh() const { return _gust_speed == NaN ? NaN : _gust_speed * 3.6; } + inline double getGustSpeed_kt() const { return _gust_speed == NaN ? NaN : _gust_speed * SG_MPS_TO_KT; } + inline double getGustSpeed_mph() const { return _gust_speed == NaN ? NaN : _gust_speed * SG_MPS_TO_MPH; } + + inline int getWindRangeFrom() const { return _wind_range_from; } + inline int getWindRangeTo() const { return _wind_range_to; } + + inline FGMetarVisibility& getMinVisibility() { return _min_visibility; } + inline FGMetarVisibility& getMaxVisibility() { return _max_visibility; } + inline FGMetarVisibility& getVertVisibility() { return _vert_visibility; } + inline FGMetarVisibility *getDirVisibility() { return _dir_visibility; } + + inline double getTemperature_C() const { return _temp; } + inline double getTemperature_F() const { return _temp == NaN ? NaN : 1.8 * _temp + 32; } + inline double getDewpoint_C() const { return _dewp; } + inline double getDewpoint_F() const { return _dewp == NaN ? NaN : 1.8 * _dewp + 32; } + inline double getPressure_hPa() const { return _pressure == NaN ? NaN : _pressure / 100; } + inline double getPressure_inHg() const { return _pressure == NaN ? NaN : _pressure * SG_PA_TO_INHG; } + + double getRelHumidity() const; + + inline vector& getClouds() { return _clouds; } + inline map& getRunways() { return _runways; } + inline vector& getWeather() { return _weather; } + +protected: + int _grpcount; + char *_data; + char *_m; + char _icao[5]; + int _year; + int _month; + int _day; + int _hour; + int _minute; + int _report_type; + int _wind_dir; + double _wind_speed; + double _gust_speed; + int _wind_range_from; + int _wind_range_to; + double _temp; + double _dewp; + double _pressure; + + FGMetarVisibility _min_visibility; + FGMetarVisibility _max_visibility; + FGMetarVisibility _vert_visibility; + FGMetarVisibility _dir_visibility[8]; + vector _clouds; + map _runways; + vector _weather; + + bool scanPreambleDate(); + bool scanPreambleTime(); + + bool scanType(); + bool scanId(); + bool scanDate(); + bool scanModifier(); + bool scanWind(); + bool scanVariability(); + bool scanVisibility(); + bool scanRwyVisRange(); + bool scanSkyCondition(); + bool scanWeather(); + bool scanTemperature(); + bool scanPressure(); + bool scanRunwayReport(); + bool scanWindShear(); + bool scanTrendForecast(); + bool scanColorState(); + bool scanRemark(); + bool scanRemainder(); + + int scanNumber(char **str, int *num, int min, int max = 0); + bool scanBoundary(char **str); + const struct Token *scanToken(char **str, const struct Token *list); + char *loadData(const char *id); + void normalizeData(); +}; + +#undef NaN +#endif // _METAR_HXX