]> git.mxchange.org Git - simgear.git/commitdiff
Move the new metar class from FlightGear to SimGear
authorehofman <ehofman>
Mon, 2 Feb 2004 10:12:31 +0000 (10:12 +0000)
committerehofman <ehofman>
Mon, 2 Feb 2004 10:12:31 +0000 (10:12 +0000)
configure.ac
simgear/Makefile.am
simgear/environment/.cvsignore [new file with mode: 0644]
simgear/environment/Makefile.am [new file with mode: 0644]
simgear/environment/metar.cxx [new file with mode: 0644]
simgear/environment/metar.hxx [new file with mode: 0644]

index 922f7cbcc80608b0cee3b4acfd241f25d985c7ef..c0aac66f4626dfab1b06fcfa471e48232c3356bc 100644 (file)
@@ -377,6 +377,7 @@ AC_CONFIG_FILES([ \
        simgear/magvar/Makefile \
        simgear/math/Makefile \
        simgear/metar/Makefile \
+       simgear/environment/Makefile \
        simgear/misc/Makefile \
        simgear/nasal/Makefile \
        simgear/props/Makefile \
index 26761fe836a723c16a5d5e2b368349dbc99f1634..77036479518010923bd4341a19efddf014db7383 100644 (file)
@@ -5,7 +5,7 @@ SGTHREAD_DIR =
 endif
 
 # METAR_DIRS =
-METAR_DIRS = metar
+METAR_DIRS = metar environment
 
 EXTRA_DIST = simgear_config.h.vc5 version.h.in
 
diff --git a/simgear/environment/.cvsignore b/simgear/environment/.cvsignore
new file mode 100644 (file)
index 0000000..8c8fabd
--- /dev/null
@@ -0,0 +1,4 @@
+.deps
+Makefile
+Makefile.in
+metar
diff --git a/simgear/environment/Makefile.am b/simgear/environment/Makefile.am
new file mode 100644 (file)
index 0000000..1948696
--- /dev/null
@@ -0,0 +1,9 @@
+includedir = @includedir@/environment
+
+lib_LIBRARIES = libsgenvironment.a
+
+include_HEADERS = metar.hxx
+
+libsgenvironment_a_SOURCES = metar.cxx
+
+INCLUDES = -I$(top_srcdir)
diff --git a/simgear/environment/metar.cxx b/simgear/environment/metar.cxx
new file mode 100644 (file)
index 0000000..a3e0162
--- /dev/null
@@ -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 SGMetar aviation weather data.
+ */
+
+#include <string>
+
+#include <simgear/io/sg_socket.hxx>
+#include <simgear/debug/logstream.hxx>
+#include <simgear/structure/exception.hxx>
+
+#include "metar.hxx"
+
+#define NaN SGMetarNaN
+
+/**
+ * The constructor takes a SGMetar 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
+ * SGMetar *m = new SGMetar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013");
+ * double t = m->getTemperature();
+ * delete m;
+ *
+ * SGMetar n("KSFO");
+ * double d = n.getDewpoint_C();
+ * @endcode
+ */
+SGMetar::SGMetar(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.
+  */
+SGMetar::~SGMetar()
+{
+       _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 SGMetar station code, e.g. "KSFO".
+  * @return pointer to SGMetar data string, allocated by new char[].
+  */
+char *SGMetar::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 SGMetar::normalizeData()
+{
+       char *src, *dest;
+       for (src = dest = _data; (*dest++ = *src++); )
+               while (*src == ' ' && src[1] == ' ')
+                       src++;
+}
+
+
+// \d\d\d\d/\d\d/\d\d
+bool SGMetar::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 SGMetar::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 SGMetar::scanType()
+{
+       if (strncmp(_m, "METAR ", 6) && strncmp(_m, "SPECI ", 6))
+               return false;
+       _m += 6;
+       _grpcount++;
+       return true;
+}
+
+
+// [A-Z]{4}
+bool SGMetar::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 SGMetar::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 SGMetar::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 SGMetar::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 SGMetar::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 SGMetar::scanVisibility()
+// TODO: if only directed vis are given, do still set min/max
+{
+       char *m = _m;
+       double distance;
+       int i, dir = -1;
+       int modifier = SGMetarVisibility::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 = SGMetarVisibility::LESS_THAN;
+               else if (i == 9999)
+                       i++, modifier = SGMetarVisibility::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 = SGMetarVisibility::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;
+
+       SGMetarVisibility *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 SGMetar::scanRwyVisRange()
+{
+       char *m = _m;
+       int i;
+       SGMetarRunway 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 = SGMetarVisibility::GREATER_THAN;
+       else if (*m == 'M')
+               m++, r._min_visibility._modifier = SGMetarVisibility::LESS_THAN;
+       if (!scanNumber(&m, &from, 4))
+               return false;
+       if (*m == 'V') {
+               m++;
+               if (*m == 'P')
+                       m++, r._max_visibility._modifier = SGMetarVisibility::GREATER_THAN;
+               else if (*m == 'M')
+                       m++, r._max_visibility._modifier = SGMetarVisibility::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 = SGMetarVisibility::DECREASING;
+       else if (*m == 'N')
+               m++, r._min_visibility._tendency = SGMetarVisibility::STABLE;
+       else if (*m == 'U')
+               m++, r._min_visibility._tendency = SGMetarVisibility::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",
+       "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
+};
+
+
+// (+|-|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}
+bool SGMetar::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 SGMetar::scanSkyCondition()
+{
+       char *m = _m;
+       int i;
+       SGMetarCloud 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 = SGMetarVisibility::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 SGMetar::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 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));
+       return dewp * 100 / temp;
+}
+
+
+// [AQ]\d{4}             (spec)
+// [AQ]\d{2}(\d{2}|//)   (Namibia)
+bool SGMetar::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 SGMetar::scanRunwayReport()
+{
+       char *m = _m;
+       int i;
+       char id[4];
+       SGMetarRunway 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 SGMetar::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 SGMetar::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 SGMetar::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 SGMetar::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 SGMetar::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 SGMetar::scanBoundary(char **s)
+{
+       if (**s && !isspace(**s))
+               return false;
+       while (isspace(**s))
+               (*s)++;
+       return true;
+}
+
+
+int SGMetar::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 *SGMetar::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/simgear/environment/metar.hxx b/simgear/environment/metar.hxx
new file mode 100644 (file)
index 0000000..275c7cb
--- /dev/null
@@ -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 <vector>
+#include <map>
+#include <string>
+
+#include <simgear/constants.h>
+
+SG_USING_STD(vector);
+SG_USING_STD(map);
+SG_USING_STD(string);
+
+const double SGMetarNaN = -1E20;
+#define NaN SGMetarNaN
+
+struct Token {
+       char    *id;
+       char    *text;
+};
+
+
+class SGMetar;
+
+class SGMetarVisibility {
+       friend class SGMetar;
+public:
+       SGMetarVisibility() :
+               _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 SGMetarRunway {
+       friend class SGMetar;
+public:
+       SGMetarRunway() :
+               _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 SGMetarVisibility        getMinVisibility()      const { return _min_visibility; }
+       inline SGMetarVisibility        getMaxVisibility()      const { return _max_visibility; }
+
+protected:
+       SGMetarVisibility _min_visibility;
+       SGMetarVisibility _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 SGMetarCloud {
+       friend class SGMetar;
+public:
+       SGMetarCloud() :
+               _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 SGMetar {
+public:
+       SGMetar(const char *m);
+       SGMetar(const string m) { SGMetar(m.c_str()); }
+       ~SGMetar();
+
+       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 SGMetarVisibility& getMinVisibility()    { return _min_visibility; }
+       inline SGMetarVisibility& getMaxVisibility()    { return _max_visibility; }
+       inline SGMetarVisibility& getVertVisibility()   { return _vert_visibility; }
+       inline SGMetarVisibility *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<SGMetarCloud>& getClouds()        { return _clouds; }
+       inline map<string, SGMetarRunway>& getRunways() { return _runways; }
+       inline vector<string>& 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;
+
+       SGMetarVisibility               _min_visibility;
+       SGMetarVisibility               _max_visibility;
+       SGMetarVisibility               _vert_visibility;
+       SGMetarVisibility               _dir_visibility[8];
+       vector<SGMetarCloud>            _clouds;
+       map<string, SGMetarRunway>      _runways;
+       vector<string>                  _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