]> git.mxchange.org Git - simgear.git/blobdiff - simgear/environment/metar.cxx
Consolidate the different ReaderWriterOptions classes.
[simgear.git] / simgear / environment / metar.cxx
index 8966b77179f921a0e51d2750fc9c2aceeafc1845..75109b75651d03eb413a08b9975696ec02315384 100644 (file)
@@ -16,7 +16,7 @@
 //
 // 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 Meteorological Aerodrome Reports (METAR).
  */
+#ifdef HAVE_CONFIG_H
+#  include <simgear_config.h>
+#endif
 
 #include <string>
 #include <time.h>
+#include <cstring>
 
-#include <simgear/io/sg_socket.hxx>
 #include <simgear/debug/logstream.hxx>
 #include <simgear/structure/exception.hxx>
 
 
 #define NaN SGMetarNaN
 
+using std::string;
+using std::map;
+using std::vector;
+
 /**
- * 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 takes a Metar string
  * 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.
  *
  * @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", "proxy.provider.foo", "3128", "proxy-password");
- * double d = n.getDewpoint_C();
+
  * @endcode
  */
-SGMetar::SGMetar(const string& m, const string& proxy, const string& port,
-               const string& auth, const time_t time) :
+SGMetar::SGMetar(const string& m) :
        _grpcount(0),
        _x_proxy(false),
        _year(-1),
@@ -83,16 +82,10 @@ SGMetar::SGMetar(const string& m, const string& proxy, const string& port,
        _snow(false),
        _cavok(false)
 {
-       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, proxy, port, auth, time);
-       } else {
-               _data = new char[m.length() + 2];       // make room for " \0"
-               strcpy(_data, m.c_str());
-               _url = _data;
-       }
+       _data = new char[m.length() + 2];       // make room for " \0"
+       strcpy(_data, m.c_str());
+       _url = _data;
+               
        normalizeData();
 
        _m = _data;
@@ -107,7 +100,7 @@ SGMetar::SGMetar(const string& m, const string& proxy, const string& port,
        scanType();
        if (!scanId() || !scanDate()) {
                delete[] _data;
-               throw sg_io_exception("metar data bogus (" + _url + ')');
+               throw sg_io_exception("metar data bogus ", sg_location(_url));
        }
        scanModifier();
 
@@ -133,7 +126,7 @@ SGMetar::SGMetar(const string& m, const string& proxy, const string& port,
 
        if (_grpcount < 4) {
                delete[] _data;
-               throw sg_io_exception("metar data incomplete (" + _url + ')');
+               throw sg_io_exception("metar data incomplete ", sg_location(_url));
        }
 
        _url = "";
@@ -156,7 +149,7 @@ void SGMetar::useCurrentDate()
 {
        struct tm now;
        time_t now_sec = time(0);
-#ifdef _MSC_VER
+#ifdef _WIN32
        now = *gmtime(&now_sec);
 #else
        gmtime_r(&now_sec, &now);
@@ -165,82 +158,6 @@ void SGMetar::useCurrentDate()
        _month = now.tm_mon + 1;
 }
 
-
-/**
-  * If called with "KSFO" loads data from
-  * @code
-  * http://weather.noaa.gov/pub/data/observations/metar/stations/KSFO.TXT.
-  * @endcode
-  * Throws sg_io_exception on failure. Gives up after waiting longer than 10 seconds.
-  *
-  * @param id four-letter ICAO Metar station code, e.g. "KSFO".
-  * @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, const string& proxy, const string& port,
-               const string& auth, time_t time)
-{
-       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://weather.noaa.gov" + path;
-
-       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("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;
-
-       // skip HTTP header
-       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)
-                       sock->readline(&buf[i], buflen);
-       }
-
-       sock->close();
-       delete sock;
-
-       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, and add
   * a trailing space. This makes scanning for things like "ALL RWY" easier.
@@ -461,11 +378,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;
@@ -481,7 +400,7 @@ bool SGMetar::scanVisibility()
                                m++, dir = 225;
                        else
                                dir = 180;
-               }
+                }
                if (i == 0)
                        i = 50, modifier = SGMetarVisibility::LESS_THAN;
                else if (i == 9999)
@@ -489,7 +408,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;
 
@@ -588,7 +506,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')
@@ -608,61 +526,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;
@@ -672,32 +591,35 @@ bool SGMetar::scanWeather()
        }
 
        string pre, post;
-       int intensity = 0;
+    struct Weather w;
        if (*m == '-')
-               m++, pre = "light ", intensity = 1;
+               m++, pre = "light ", w.intensity = LIGHT;
        else if (*m == '+')
-               m++, pre = "heavy ", intensity = 3;
+               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 ", intensity = 2;
+               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 = intensity;
+                       _rain = w.intensity;
                else if (!strcmp(a->id, "HA"))
-                       _hail = intensity;
+                       _hail = w.intensity;
                else if (!strcmp(a->id, "SN"))
-                       _snow = intensity;
+                       _snow = w.intensity;
        }
        if (!weather.length())
                return false;
@@ -707,35 +629,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 <iostream>
 // (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]?
 bool SGMetar::scanSkyCondition()
 {
@@ -743,8 +667,17 @@ 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;
@@ -752,7 +685,7 @@ bool SGMetar::scanSkyCondition()
                        return false;
 
                if (i == 3) {
-                       cl._coverage = 0;
+            cl._coverage = SGMetarCloud::COVERAGE_CLEAR;
                        _clouds.push_back(cl);
                } else {
                        _cavok = true;
@@ -764,13 +697,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;
@@ -783,7 +716,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///'
@@ -832,7 +765,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')
@@ -957,11 +890,13 @@ bool SGMetar::scanRunwayReport()
                        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))
@@ -970,7 +905,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)
@@ -996,6 +931,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;
@@ -1075,13 +1011,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 }
 };
 
 
@@ -1167,7 +1103,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) {
@@ -1180,13 +1116,29 @@ const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
 }
 
 
-void SGMetarCloud::set(double alt, int cov)
+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)
 {