]> git.mxchange.org Git - simgear.git/blobdiff - simgear/environment/metar.cxx
Merge branch 'maint'
[simgear.git] / simgear / environment / metar.cxx
index 8e67bc66c07531899b30092215531a634a82aa09..79401e39419d3680ae0495f1d68b34da42cfb987 100644 (file)
 //
 // 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 <simgear_config.h>
+#endif
 
 #include <string>
+#include <time.h>
 
 #include <simgear/io/sg_socket.hxx>
 #include <simgear/debug/logstream.hxx>
@@ -36,7 +40,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_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 +80,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 +102,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 +136,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 +155,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 +176,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";
-       _url = "http://" + host + path;
 
-       string get = string("GET ") + path + " HTTP/1.0\r\n\r\n";
+       path += string(id) + ".TXT";
+       _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;
-               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://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: ", 13))
+                       _x_proxy = true;
+       }
        if (i) {
                i = sock->readline(buf, buflen);
                if (i)
@@ -186,7 +236,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 +246,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 +255,9 @@ void SGMetar::normalizeData()
        for (src = dest = _data; (*dest++ = *src++); )
                while (*src == ' ' && src[1] == ' ')
                        src++;
+       for (dest--; isspace(*--dest); ) ;
+       *++dest = ' ';
+       *++dest = '\0';
 }
 
 
@@ -371,7 +425,7 @@ bool SGMetar::scanWind()
        if (gust != NaN)
                _gust_speed = gust * factor;
        _grpcount++;
-       return false;
+       return true;
 }
 
 
@@ -439,7 +493,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;
 
@@ -558,56 +611,56 @@ 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;
@@ -622,14 +675,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++) {
@@ -641,6 +695,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;
@@ -656,26 +716,26 @@ bool SGMetar::scanWeather()
 
 
 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 }
 };
 
 
@@ -686,6 +746,14 @@ 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, "NSC", i = 3)            // no significant clouds
@@ -693,8 +761,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;
        }
@@ -770,7 +843,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 +867,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 +959,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 +983,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 +1009,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 +1089,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 +1181,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 +1193,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