X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fenvironment%2Fmetar.cxx;h=c15c4a7286074115f2598aee51703be58b2de24e;hb=778cc4c4352484a3d4d9191c7b19d51512565bda;hp=8e67bc66c07531899b30092215531a634a82aa09;hpb=70dbbf52f020a23f2590fa56d27b713f0d266a82;p=simgear.git diff --git a/simgear/environment/metar.cxx b/simgear/environment/metar.cxx index 8e67bc66..c15c4a72 100644 --- a/simgear/environment/metar.cxx +++ b/simgear/environment/metar.cxx @@ -16,16 +16,21 @@ // // 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 +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ /** * @file metar.cxx - * Interface for encoded SGMetar aviation weather data. + * Interface for encoded Meteorological Aerodrome Reports (METAR). */ +#ifdef HAVE_CONFIG_H +# include +#endif #include +#include +#include #include #include @@ -36,7 +41,7 @@ #define NaN SGMetarNaN /** - * The constructor takes a SGMetar string, or a four-letter ICAO code. In the + * 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" @@ -44,18 +49,25 @@ * @a grpcount) and can be left away. A keyword "SPECI" is * likewise accepted. * + * @param m ICAO station id or metar string + * @param proxy proxy host (optional; default: "") + * @param port proxy port (optional; default: "80") + * @param auth proxy authorization information (optional; default: "") + * * @par Examples: * @code * SGMetar *m = new SGMetar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013"); * double t = m->getTemperature_F(); * delete m; * - * SGMetar n("KSFO"); + * SGMetar n("KSFO", "proxy.provider.foo", "3128", "proxy-password"); * double d = n.getDewpoint_C(); * @endcode */ -SGMetar::SGMetar(const char *m) : +SGMetar::SGMetar(const string& m, const string& proxy, const string& port, + const string& auth, const time_t time) : _grpcount(0), + _x_proxy(false), _year(-1), _month(-1), _day(-1), @@ -69,16 +81,20 @@ SGMetar::SGMetar(const char *m) : _wind_range_to(-1), _temp(NaN), _dewp(NaN), - _pressure(NaN) + _pressure(NaN), + _rain(false), + _hail(false), + _snow(false), + _cavok(false) { - if (isalnum(m[0]) && isalnum(m[1]) && isalnum(m[2]) && isalnum(m[3]) && !m[4]) { + if (m.length() == 4 && isalnum(m[0]) && isalnum(m[1]) && isalnum(m[2]) && isalnum(m[3])) { for (int i = 0; i < 4; i++) _icao[i] = toupper(m[i]); _icao[4] = '\0'; - _data = loadData(_icao); + _data = loadData(_icao, proxy, port, auth, time); } else { - _data = new char[strlen(m) + 2]; // make room for " \0" - strcpy(_data, m); + _data = new char[m.length() + 2]; // make room for " \0" + strcpy(_data, m.c_str()); _url = _data; } normalizeData(); @@ -87,14 +103,15 @@ SGMetar::SGMetar(const char *m) : _icao[0] = '\0'; // NOAA preample - scanPreambleDate(); + if (!scanPreambleDate()) + useCurrentDate(); scanPreambleTime(); // METAR header scanType(); if (!scanId() || !scanDate()) { delete[] _data; - throw sg_io_exception("metar data incomplete (" + _url + ')'); + throw sg_io_exception("metar data bogus ", sg_location(_url)); } scanModifier(); @@ -120,8 +137,9 @@ SGMetar::SGMetar(const char *m) : if (_grpcount < 4) { delete[] _data; - throw sg_io_exception("metar data invalid (" + _url + ')'); + throw sg_io_exception("metar data incomplete ", sg_location(_url)); } + _url = ""; } @@ -138,6 +156,20 @@ SGMetar::~SGMetar() } +void SGMetar::useCurrentDate() +{ + struct tm now; + time_t now_sec = time(0); +#ifdef _WIN32 + now = *gmtime(&now_sec); +#else + gmtime_r(&now_sec, &now); +#endif + _year = now.tm_year + 1900; + _month = now.tm_mon + 1; +} + + /** * If called with "KSFO" loads data from * @code @@ -145,35 +177,56 @@ SGMetar::~SGMetar() * @endcode * Throws sg_io_exception on failure. Gives up after waiting longer than 10 seconds. * - * @param id four-letter ICAO SGMetar station code, e.g. "KSFO". - * @return pointer to SGMetar data string, allocated by new char[]. + * @param id four-letter ICAO Metar station code, e.g. "KSFO". + * @param proxy proxy host (optional; default: "") + * @param port proxy port (optional; default: "80") + * @param auth proxy authorization information (optional; default: "") + * @return pointer to Metar data string, allocated by new char[]. + * @see rfc2068.txt for proxy spec ("Proxy-Authorization") */ -char *SGMetar::loadData(const char *id) +char *SGMetar::loadData(const char *id, const string& proxy, const string& port, + const string& auth, time_t time) { - string host = "weather.noaa.gov"; + const int buflen = 512; + char buf[2 * buflen]; + + string metar_server = "weather.noaa.gov"; + string host = proxy.empty() ? metar_server : proxy; string path = "/pub/data/observations/metar/stations/"; - path += string(id) + ".TXT"; - _url = "http://" + host + path; - string get = string("GET ") + path + " HTTP/1.0\r\n\r\n"; + path += string(id) + ".TXT"; + _url = "http://" + metar_server + path; - SGSocket *sock = new SGSocket(host, "80", "tcp"); + SGSocket *sock = new SGSocket(host, port.empty() ? "80" : port, "tcp"); sock->set_timeout(10000); if (!sock->open(SG_IO_OUT)) { delete sock; - throw sg_io_exception("failed to load metar data from " + _url); + throw sg_io_exception("cannot connect to ", sg_location(host)); } + string get = "GET "; + if (!proxy.empty()) + get += "http://" + metar_server; + + sprintf(buf, "%ld", time); + get += path + " HTTP/1.0\015\012X-Time: " + buf + "\015\012"; + get += "Host: " + metar_server + "\015\012"; + + if (!auth.empty()) + get += "Proxy-Authorization: " + auth + "\015\012"; + + get += "\015\012"; sock->writestring(get.c_str()); int i; - const int buflen = 512; - char buf[2 * buflen]; // skip HTTP header - while ((i = sock->readline(buf, buflen))) + while ((i = sock->readline(buf, buflen))) { if (i <= 2 && isspace(buf[0]) && (!buf[1] || isspace(buf[1]))) break; + if (!strncmp(buf, "X-MetarProxy: ", 13)) + _x_proxy = true; + } if (i) { i = sock->readline(buf, buflen); if (i) @@ -186,7 +239,8 @@ char *SGMetar::loadData(const char *id) char *b = buf; scanBoundary(&b); if (*b == '<') - throw sg_io_exception("no metar data available from " + _url); + throw sg_io_exception("no metar data available from ", + sg_location(_url)); char *metar = new char[strlen(b) + 2]; // make room for " \0" strcpy(metar, b); @@ -195,8 +249,8 @@ char *SGMetar::loadData(const char *id) /** - * Replace any number of subsequent spaces by just one space. - * This makes scanning for things like "ALL RWY" easier. + * Replace any number of subsequent spaces by just one space, and add + * a trailing space. This makes scanning for things like "ALL RWY" easier. */ void SGMetar::normalizeData() { @@ -204,6 +258,9 @@ void SGMetar::normalizeData() for (src = dest = _data; (*dest++ = *src++); ) while (*src == ' ' && src[1] == ' ') src++; + for (dest--; isspace(*--dest); ) ; + *++dest = ' '; + *++dest = '\0'; } @@ -371,7 +428,7 @@ bool SGMetar::scanWind() if (gust != NaN) _gust_speed = gust * factor; _grpcount++; - return false; + return true; } @@ -411,11 +468,13 @@ bool SGMetar::scanVisibility() int modifier = SGMetarVisibility::EQUALS; // \d{4}(N|NE|E|SE|S|SW|W|NW)? if (scanNumber(&m, &i, 4)) { - if (*m == 'E') + if( strncmp( m, "NDV",3 ) == 0 ) { + m+=3; // tolerate NDV (no directional validation) + } else if (*m == 'E') { m++, dir = 90; - else if (*m == 'W') + } else if (*m == 'W') { m++, dir = 270; - else if (*m == 'N') { + } else if (*m == 'N') { m++; if (*m == 'E') m++, dir = 45; @@ -431,7 +490,7 @@ bool SGMetar::scanVisibility() m++, dir = 225; else dir = 180; - } + } if (i == 0) i = 50, modifier = SGMetarVisibility::LESS_THAN; else if (i == 9999) @@ -439,7 +498,6 @@ bool SGMetar::scanVisibility() 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 = SGMetarVisibility::LESS_THAN; @@ -538,7 +596,7 @@ bool SGMetar::scanRwyVisRange() r._max_visibility._distance = to; if (*m == '/') // this is not in the spec! - *m++; + m++; if (*m == 'D') m++, r._min_visibility._tendency = SGMetarVisibility::DECREASING; else if (*m == 'N') @@ -558,61 +616,62 @@ bool SGMetar::scanRwyVisRange() static const struct Token special[] = { - "NSW", "no significant weather", - "VCSH", "showers in the vicinity", - "VCTS", "thunderstorm in the vicinity", - 0, 0 + { "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 + { "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", - "SG", "fog", - "SGBR", "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 + { "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|SG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS){0,3} +// (+|-|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 SGMetar::scanWeather() { char *m = _m; string weather; const struct Token *a; + if ((a = scanToken(&m, special))) { if (!scanBoundary(&m)) return false; @@ -622,25 +681,35 @@ bool SGMetar::scanWeather() } string pre, post; + struct Weather w; if (*m == '-') - m++, pre = "light "; + m++, pre = "light ", w.intensity = LIGHT; else if (*m == '+') - m++, pre = "heavy "; + m++, pre = "heavy ", w.intensity = HEAVY; else if (!strncmp(m, "VC", 2)) - m += 2, post = "in the vicinity "; + m += 2, post = "in the vicinity ", w.vincinity=true; else - pre = "moderate "; + pre = "moderate ", w.intensity = MODERATE; int i; for (i = 0; i < 3; i++) { if (!(a = scanToken(&m, description))) break; + w.descriptions.push_back(a->id); weather += string(a->text) + " "; } + for (i = 0; i < 3; i++) { if (!(a = scanToken(&m, phenomenon))) break; + w.phenomena.push_back(a->id); weather += string(a->text) + " "; + if (!strcmp(a->id, "RA")) + _rain = w.intensity; + else if (!strcmp(a->id, "HA")) + _hail = w.intensity; + else if (!strcmp(a->id, "SN")) + _snow = w.intensity; } if (!weather.length()) return false; @@ -650,35 +719,37 @@ bool SGMetar::scanWeather() weather = pre + weather + post; weather.erase(weather.length() - 1); _weather.push_back(weather); + if( w.phenomena.size() > 0 ) + _weather2.push_back( w ); _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 + { "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 } }; - +#include // (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]? bool SGMetar::scanSkyCondition() { @@ -686,15 +757,29 @@ bool SGMetar::scanSkyCondition() int i; SGMetarCloud cl; + if (!strncmp(m, "//////", 6)) { + m += 6; + if (!scanBoundary(&m)) + return false; + _m = m; + return true; + } + if (!strncmp(m, "CLR", i = 3) // clear || !strncmp(m, "SKC", i = 3) // sky clear + || !strncmp(m, "NCD", i = 3) // nil cloud detected || !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); + + if (i == 3) { + cl._coverage = SGMetarCloud::COVERAGE_CLEAR; + _clouds.push_back(cl); + } else { + _cavok = true; + } _m = m; return true; } @@ -702,13 +787,13 @@ bool SGMetar::scanSkyCondition() if (!strncmp(m, "VV", i = 2)) // vertical visibility ; else if (!strncmp(m, "FEW", i = 3)) - cl._coverage = 1; + cl._coverage = SGMetarCloud::COVERAGE_FEW; else if (!strncmp(m, "SCT", i = 3)) - cl._coverage = 2; + cl._coverage = SGMetarCloud::COVERAGE_SCATTERED; else if (!strncmp(m, "BKN", i = 3)) - cl._coverage = 3; + cl._coverage = SGMetarCloud::COVERAGE_BROKEN; else if (!strncmp(m, "OVC", i = 3)) - cl._coverage = 4; + cl._coverage = SGMetarCloud::COVERAGE_OVERCAST; else return false; m += i; @@ -721,7 +806,7 @@ bool SGMetar::scanSkyCondition() } else if (!scanNumber(&m, &i, 3)) i = -1; - if (cl._coverage == -1) { + if (cl._coverage == SGMetarCloud::COVERAGE_NIL) { if (!scanBoundary(&m)) return false; if (i == -1) // 'VV///' @@ -770,7 +855,7 @@ bool SGMetar::scanTemperature() return false; if (!scanBoundary(&m)) { if (!strncmp(m, "XX", 2)) // not spec compliant! - m += 2, sign = 0; + m += 2, sign = 0, dew = temp; else { sign = 1; if (*m == 'M') @@ -794,8 +879,8 @@ double SGMetar::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)); + double dewp = pow(10.0, 7.5 * _dewp / (237.7 + _dewp)); + double temp = pow(10.0, 7.5 * _temp / (237.7 + _temp)); return dewp * 100 / temp; } @@ -886,19 +971,22 @@ bool SGMetar::scanRunwayReport() if (!strncmp(m, "CLRD", 4)) { m += 4; // runway cleared - r._deposit = "cleared"; + r._deposit_string = "cleared"; } else { if (scanNumber(&m, &i, 1)) { - r._deposit = runway_deposit[i]; + r._deposit = i; + r._deposit_string = 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)) @@ -907,7 +995,7 @@ bool SGMetar::scanRunwayReport() return false; if (i == 0) - r._depth = 0.5; // < 1 mm deep (let's say 0.5 :-) + r._depth = 0.0005; // < 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) @@ -933,6 +1021,7 @@ bool SGMetar::scanRunwayReport() return false; _runways[id]._deposit = r._deposit; + _runways[id]._deposit_string = r._deposit_string; _runways[id]._extent = r._extent; _runways[id]._extent_string = r._extent_string; _runways[id]._depth = r._depth; @@ -1012,13 +1101,13 @@ bool SGMetar::scanTrendForecast() // (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 + { "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 } }; @@ -1104,7 +1193,7 @@ const struct Token *SGMetar::scanToken(char **str, const struct Token *list) { const struct Token *longest = 0; int maxlen = 0, len; - char *s; + const char *s; for (int i = 0; (s = list[i].id); i++) { len = strlen(s); if (!strncmp(s, *str, len) && len > maxlen) { @@ -1116,4 +1205,40 @@ const struct Token *SGMetar::scanToken(char **str, const struct Token *list) return longest; } + +void SGMetarCloud::set(double alt, Coverage cov) +{ + _altitude = alt; + if (cov != -1) + _coverage = cov; +} + +SGMetarCloud::Coverage SGMetarCloud::getCoverage( const std::string & coverage ) +{ + if( coverage == "clear" ) return COVERAGE_CLEAR; + if( coverage == "few" ) return COVERAGE_FEW; + if( coverage == "scattered" ) return COVERAGE_SCATTERED; + if( coverage == "broken" ) return COVERAGE_BROKEN; + if( coverage == "overcast" ) return COVERAGE_OVERCAST; + return COVERAGE_NIL; +} + +const char * SGMetarCloud::COVERAGE_NIL_STRING = "nil"; +const char * SGMetarCloud::COVERAGE_CLEAR_STRING = "clear"; +const char * SGMetarCloud::COVERAGE_FEW_STRING = "few"; +const char * SGMetarCloud::COVERAGE_SCATTERED_STRING = "scattered"; +const char * SGMetarCloud::COVERAGE_BROKEN_STRING = "broken"; +const char * SGMetarCloud::COVERAGE_OVERCAST_STRING = "overcast"; + +void SGMetarVisibility::set(double dist, int dir, int mod, int tend) +{ + _distance = dist; + if (dir != -1) + _direction = dir; + if (mod != -1) + _modifier = mod; + if (tend != 1) + _tendency = tend; +} + #undef NaN