/**
* @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>
#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),
_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();
_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
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 = "";
}
}
+void SGMetar::useCurrentDate()
+{
+ struct tm now;
+ time_t now_sec = time(0);
+#ifdef _MSC_VER
+ 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
* @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)
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()
{
for (src = dest = _data; (*dest++ = *src++); )
while (*src == ' ' && src[1] == ' ')
src++;
+ for (dest--; isspace(*--dest); ) ;
+ *++dest = ' ';
+ *++dest = '\0';
}
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);
if (gust != NaN)
_gust_speed = gust * factor;
_grpcount++;
- return false;
+ return true;
}
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;
}
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++) {
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;
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;
}
{
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;
}
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 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