//
// 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 <simgear/io/sg_socket.hxx>
#include <simgear/debug/logstream.hxx>
* double d = n.getDewpoint_C();
* @endcode
*/
-SGMetar::SGMetar(const string& m, const string& proxy, const string& port, const string& auth) :
+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)
{
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);
+ _data = loadData(_icao, proxy, port, auth, time);
} else {
_data = new char[m.length() + 2]; // make room for " \0"
strcpy(_data, m.c_str());
_icao[0] = '\0';
// NOAA preample
- scanPreambleDate();
+ if (!scanPreambleDate())
+ useCurrentDate();
scanPreambleTime();
// METAR header
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();
if (_grpcount < 4) {
delete[] _data;
- throw sg_io_exception("metar data incomplete (" + _url + ')');
+ throw sg_io_exception("metar data incomplete ", sg_location(_url));
}
+
_url = "";
}
}
+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
* @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)
+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;
sock->set_timeout(10000);
if (!sock->open(SG_IO_OUT)) {
delete sock;
- throw sg_io_exception("cannot connect to " + host);
+ throw sg_io_exception("cannot connect to ", sg_location(host));
}
string get = "GET ";
if (!proxy.empty())
get += "http://weather.noaa.gov";
- get += path + " HTTP/1.0\r\n";
- sock->writestring(get.c_str());
- if (!auth.empty()) {
- get = "Proxy-Authorization: " + auth + "\r\n";
- sock->writestring(get.c_str());
- }
+ sprintf(buf, "%ld", time);
+ get += path + " HTTP/1.0\015\012X-Time: " + buf + "\015\012";
- sock->writestring("\r\n");
+ 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)
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);
if (gust != NaN)
_gust_speed = gust * factor;
_grpcount++;
- return false;
+ return true;
}
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;
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 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;
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 }
};
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
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;
}
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')
{
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 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))
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)
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;
// (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 }
};
{
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) {
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