//
// 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),
_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;
{
struct tm now;
time_t now_sec = time(0);
-#if defined( _MSC_VER ) || defined ( __MINGW32__ )
+#ifdef _WIN32
now = *gmtime(&now_sec);
#else
gmtime_r(&now_sec, &now);
_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 ", 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;
-
- // 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 ",
- sg_location(_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.
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;
m++, dir = 225;
else
dir = 180;
- }
+ }
if (i == 0)
i = 50, modifier = SGMetarVisibility::LESS_THAN;
else if (i == 9999)
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;
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')
static const struct Token special[] = {
{ "NSW", "no significant weather" },
- { "VCSH", "showers in the vicinity" },
- { "VCTS", "thunderstorm in the vicinity" },
+/* { "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" },
+ { "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
+ { "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;
}
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;
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" },
+ { "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" },
+ { "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" },
+ { "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()
{
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;
return false;
if (i == 3) {
- cl._coverage = 0;
+ cl._coverage = SGMetarCloud::COVERAGE_CLEAR;
_clouds.push_back(cl);
} else {
_cavok = true;
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;
} 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///'
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')
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;
{
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) {
}
-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)
{