]> git.mxchange.org Git - simgear.git/blobdiff - simgear/environment/metar.cxx
Melchior FRANZ:
[simgear.git] / simgear / environment / metar.cxx
index a3e01623b8aab4a2e530edf769d81db4458825af..09ccbb7ab4881b6a51ae78602cb3976687c07c5a 100644 (file)
 
 /**
  * @file metar.cxx
- * Interface for encoded SGMetar aviation weather data.
+ * Interface for encoded Meteorological Aerodrome Reports (METAR).
  */
 
 #include <string>
+#include <time.h>
 
 #include <simgear/io/sg_socket.hxx>
 #include <simgear/debug/logstream.hxx>
@@ -36,7 +37,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"
  * @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();
+ * 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,17 +77,21 @@ 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)
 {
-       int i;
-       if (isalpha(m[0]) && isalpha(m[1]) && isalpha(m[2]) && isalpha(m[3]) && !m[4]) {
-               for (i = 0; i < 4; i++)
+       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) + 1];
-               strcpy(_data, m);
+               _data = new char[m.length() + 2];       // make room for " \0"
+               strcpy(_data, m.c_str());
+               _url = _data;
        }
        normalizeData();
 
@@ -87,13 +99,16 @@ SGMetar::SGMetar(const char *m) :
        _icao[0] = '\0';
 
        // NOAA preample
-       scanPreambleDate();
+       if (!scanPreambleDate())
+               useCurrentDate();
        scanPreambleTime();
 
        // METAR header
        scanType();
-       if (!scanId() || !scanDate())
-               throw sg_io_exception("metar data incomplete");
+       if (!scanId() || !scanDate()) {
+               delete[] _data;
+               throw sg_io_exception("metar data bogus (" + _url + ')');
+       }
        scanModifier();
 
        // base set
@@ -116,8 +131,12 @@ SGMetar::SGMetar(const char *m) :
        scanRemainder();
        scanRemark();
 
-       if (_grpcount < 4)
-               throw sg_io_exception("metar data invalid");
+       if (_grpcount < 4) {
+               delete[] _data;
+               throw sg_io_exception("metar data incomplete (" + _url + ')');
+       }
+
+       _url = "";
 }
 
 
@@ -133,6 +152,16 @@ SGMetar::~SGMetar()
 }
 
 
+void SGMetar::useCurrentDate()
+{
+       struct tm now;
+       time_t now_sec = time(0);
+       gmtime_r(&now_sec, &now);
+       _year = now.tm_year + 1900;
+       _month = now.tm_mon + 1;
+}
+
+
 /**
   * If called with "KSFO" loads data from
   * @code
@@ -140,34 +169,54 @@ 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 host = proxy.empty() ? "weather.noaa.gov" : proxy;
        string path = "/pub/data/observations/metar/stations/";
+
        path += string(id) + ".TXT";
-       string get = string("GET ") + path + " HTTP/1.0\r\n\r\n";
+       _url = "http://weather.noaa.gov" + 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;
-               string err = "failed to load metar data from http://" + host + path;
-               throw sg_io_exception(err);
+               throw sg_io_exception("cannot connect to " + host);
        }
 
+       string get = "GET ";
+       if (!proxy.empty())
+               get += "http://weather.noaa.gov";
+
+       sprintf(buf, "%ld", time);
+       get += path + " HTTP/1.0\015\012X-Time: " + buf + "\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: ", 9))
+                       _x_proxy = true;
+       }
        if (i) {
                i = sock->readline(buf, buflen);
                if (i)
@@ -177,15 +226,20 @@ char *SGMetar::loadData(const char *id)
        sock->close();
        delete sock;
 
-       char *metar = new char[strlen(buf) + 1];
-       strcpy(metar, buf);
+       char *b = buf;
+       scanBoundary(&b);
+       if (*b == '<')
+               throw sg_io_exception("no metar data available from " + _url);
+
+       char *metar = new char[strlen(b) + 2];  // make room for " \0"
+       strcpy(metar, b);
        return metar;
 }
 
 
 /**
-  * 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()
 {
@@ -193,6 +247,9 @@ void SGMetar::normalizeData()
        for (src = dest = _data; (*dest++ = *src++); )
                while (*src == ' ' && src[1] == ' ')
                        src++;
+       for (dest--; isspace(*--dest); ) ;
+       *++dest = ' ';
+       *++dest = '\0';
 }
 
 
@@ -256,8 +313,9 @@ bool SGMetar::scanType()
 bool SGMetar::scanId()
 {
        char *m = _m;
-       if (!(isupper(*m++) && isupper(*m++) && isupper(*m++) && isupper(*m++)))
-               return false;
+       for (int i = 0; i < 4; m++, i++)
+               if (!(isalpha(*m) || isdigit(*m)))
+                       return false;
        if (!scanBoundary(&m))
                return false;
        strncpy(_icao, _m, 4);
@@ -359,7 +417,7 @@ bool SGMetar::scanWind()
        if (gust != NaN)
                _gust_speed = gust * factor;
        _grpcount++;
-       return false;
+       return true;
 }
 
 
@@ -387,6 +445,12 @@ bool SGMetar::scanVariability()
 bool SGMetar::scanVisibility()
 // TODO: if only directed vis are given, do still set min/max
 {
+       if (!strncmp(_m, "//// ", 5)) {         // spec compliant?
+               _m += 5;
+               _grpcount++;
+               return true;
+       }
+
        char *m = _m;
        double distance;
        int i, dir = -1;
@@ -604,14 +668,15 @@ bool SGMetar::scanWeather()
        }
 
        string pre, post;
+       int intensity = 0;
        if (*m == '-')
-               m++, pre = "light ";
+               m++, pre = "light ", intensity = 1;
        else if (*m == '+')
-               m++, pre = "heavy ";
+               m++, pre = "heavy ", intensity = 3;
        else if (!strncmp(m, "VC", 2))
                m += 2, post = "in the vicinity ";
        else
-               pre = "moderate ";
+               pre = "moderate ", intensity = 2;
 
        int i;
        for (i = 0; i < 3; i++) {
@@ -623,6 +688,12 @@ bool SGMetar::scanWeather()
                if (!(a = scanToken(&m, phenomenon)))
                        break;
                weather += string(a->text) + " ";
+               if (!strcmp(a->id, "RA"))
+                       _rain = intensity;
+               else if (!strcmp(a->id, "HA"))
+                       _hail = intensity;
+               else if (!strcmp(a->id, "SN"))
+                       _snow = intensity;
        }
        if (!weather.length())
                return false;
@@ -675,8 +746,13 @@ bool SGMetar::scanSkyCondition()
                m += i;
                if (!scanBoundary(&m))
                        return false;
-               cl._coverage = 0;
-               _clouds.push_back(cl);
+
+               if (i == 3) {
+                       cl._coverage = 0;
+                       _clouds.push_back(cl);
+               } else {
+                       _cavok = true;
+               }
                _m = m;
                return true;
        }
@@ -776,8 +852,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;
 }
 
@@ -868,10 +944,11 @@ 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
@@ -1098,4 +1175,24 @@ const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
        return longest;
 }
 
+
+void SGMetarCloud::set(double alt, int cov)
+{
+       _altitude = alt;
+       if (cov != -1)
+               _coverage = cov;
+}
+
+
+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