Remove using std:: from the metar header, remove HTTP support, add very basic unit-test harness.

This commit is contained in:
James Turner 2011-10-21 09:35:37 +01:00
parent 7984f055e2
commit 63a8209a83
4 changed files with 118 additions and 141 deletions

View File

@ -5,3 +5,12 @@ set(HEADERS metar.hxx precipitation.hxx)
set(SOURCES metar.cxx precipitation.cxx) set(SOURCES metar.cxx precipitation.cxx)
simgear_component(environment environment "${SOURCES}" "${HEADERS}") simgear_component(environment environment "${SOURCES}" "${HEADERS}")
add_executable(test_metar test_metar.cxx)
target_link_libraries(test_metar
sgenvironment sgstructure sgmisc sgdebug
${CMAKE_THREAD_LIBS_INIT}
${ZLIB_LIBRARY}
${RT_LIBRARY})
add_test(metar ${EXECUTABLE_OUTPUT_PATH}/test_metar)

View File

@ -32,7 +32,6 @@
#include <time.h> #include <time.h>
#include <cstring> #include <cstring>
#include <simgear/io/sg_socket.hxx>
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
@ -40,32 +39,28 @@
#define NaN SGMetarNaN #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 * The constructor takes a Metar string
* 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" * The constructor throws sg_io_exceptions on failure. The "METAR"
* keyword has no effect (apart from incrementing the group counter * keyword has no effect (apart from incrementing the group counter
* @a grpcount) and can be left away. A keyword "SPECI" is * @a grpcount) and can be left away. A keyword "SPECI" is
* likewise accepted. * likewise accepted.
* *
* @param m ICAO station id or metar string * @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: * @par Examples:
* @code * @code
* SGMetar *m = new SGMetar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013"); * SGMetar *m = new SGMetar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013");
* double t = m->getTemperature_F(); * double t = m->getTemperature_F();
* delete m; * delete m;
*
* SGMetar n("KSFO", "proxy.provider.foo", "3128", "proxy-password");
* double d = n.getDewpoint_C();
* @endcode * @endcode
*/ */
SGMetar::SGMetar(const string& m, const string& proxy, const string& port, SGMetar::SGMetar(const string& m) :
const string& auth, const time_t time) :
_grpcount(0), _grpcount(0),
_x_proxy(false), _x_proxy(false),
_year(-1), _year(-1),
@ -87,16 +82,10 @@ SGMetar::SGMetar(const string& m, const string& proxy, const string& port,
_snow(false), _snow(false),
_cavok(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" _data = new char[m.length() + 2]; // make room for " \0"
strcpy(_data, m.c_str()); strcpy(_data, m.c_str());
_url = _data; _url = _data;
}
normalizeData(); normalizeData();
_m = _data; _m = _data;
@ -169,85 +158,6 @@ void SGMetar::useCurrentDate()
_month = now.tm_mon + 1; _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 metar_server = "weather.noaa.gov";
string host = proxy.empty() ? metar_server : proxy;
string path = "/pub/data/observations/metar/stations/";
path += string(id) + ".TXT";
_url = "http://" + metar_server + 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://" + metar_server;
sprintf(buf, "%ld", time);
get += path + " HTTP/1.0\015\012X-Time: " + buf + "\015\012";
get += "Host: " + metar_server + "\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: ", 13))
_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 * Replace any number of subsequent spaces by just one space, and add
* a trailing space. This makes scanning for things like "ALL RWY" easier. * a trailing space. This makes scanning for things like "ALL RWY" easier.

View File

@ -29,18 +29,12 @@
#include <simgear/constants.h> #include <simgear/constants.h>
using std::vector;
using std::map;
using std::string;
const double SGMetarNaN = -1E20;
#define NaN SGMetarNaN
struct Token { struct Token {
const char *id; const char *id;
const char *text; const char *text;
}; };
const double SGMetarNaN = -1E20;
class SGMetar; class SGMetar;
@ -48,7 +42,7 @@ class SGMetarVisibility {
friend class SGMetar; friend class SGMetar;
public: public:
SGMetarVisibility() : SGMetarVisibility() :
_distance(NaN), _distance(SGMetarNaN),
_direction(-1), _direction(-1),
_modifier(EQUALS), _modifier(EQUALS),
_tendency(NONE) {} _tendency(NONE) {}
@ -70,8 +64,8 @@ public:
void set(double dist, int dir = -1, int mod = -1, int tend = -1); void set(double dist, int dir = -1, int mod = -1, int tend = -1);
inline double getVisibility_m() const { return _distance; } inline double getVisibility_m() const { return _distance; }
inline double getVisibility_ft() const { return _distance == NaN ? NaN : _distance * SG_METER_TO_FEET; } inline double getVisibility_ft() const { return _distance == SGMetarNaN ? SGMetarNaN : _distance * SG_METER_TO_FEET; }
inline double getVisibility_sm() const { return _distance == NaN ? NaN : _distance * SG_METER_TO_SM; } inline double getVisibility_sm() const { return _distance == SGMetarNaN ? SGMetarNaN : _distance * SG_METER_TO_SM; }
inline int getDirection() const { return _direction; } inline int getDirection() const { return _direction; }
inline int getModifier() const { return _modifier; } inline int getModifier() const { return _modifier; }
inline int getTendency() const { return _tendency; } inline int getTendency() const { return _tendency; }
@ -93,8 +87,8 @@ public:
_deposit_string(0), _deposit_string(0),
_extent(-1), _extent(-1),
_extent_string(0), _extent_string(0),
_depth(NaN), _depth(SGMetarNaN),
_friction(NaN), _friction(SGMetarNaN),
_friction_string(0), _friction_string(0),
_comment(0), _comment(0),
_wind_shear(false) {} _wind_shear(false) {}
@ -146,14 +140,14 @@ public:
static const char * COVERAGE_BROKEN_STRING; static const char * COVERAGE_BROKEN_STRING;
static const char * COVERAGE_OVERCAST_STRING; static const char * COVERAGE_OVERCAST_STRING;
SGMetarCloud() : _coverage(COVERAGE_NIL), _altitude(NaN), _type(0), _type_long(0) {} SGMetarCloud() : _coverage(COVERAGE_NIL), _altitude(SGMetarNaN), _type(0), _type_long(0) {}
void set(double alt, Coverage cov = COVERAGE_NIL ); void set(double alt, Coverage cov = COVERAGE_NIL );
inline Coverage getCoverage() const { return _coverage; } inline Coverage getCoverage() const { return _coverage; }
static Coverage getCoverage( const std::string & coverage ); static Coverage getCoverage( const std::string & coverage );
inline double getAltitude_m() const { return _altitude; } inline double getAltitude_m() const { return _altitude; }
inline double getAltitude_ft() const { return _altitude == NaN ? NaN : _altitude * SG_METER_TO_FEET; } inline double getAltitude_ft() const { return _altitude == SGMetarNaN ? SGMetarNaN : _altitude * SG_METER_TO_FEET; }
inline const char *getTypeString() const { return _type; } inline const char *getTypeString() const { return _type; }
inline const char *getTypeLongString() const { return _type_long; } inline const char *getTypeLongString() const { return _type_long; }
@ -167,8 +161,7 @@ protected:
class SGMetar { class SGMetar {
public: public:
SGMetar(const string& m, const string& proxy = "", const string& port = "", SGMetar(const std::string& m);
const string &auth = "", const time_t time = 0);
~SGMetar(); ~SGMetar();
enum ReportType { enum ReportType {
@ -189,8 +182,8 @@ public:
Weather() { intensity = NIL; vincinity = false; } Weather() { intensity = NIL; vincinity = false; }
Intensity intensity; Intensity intensity;
bool vincinity; bool vincinity;
vector<string> descriptions; std::vector<std::string> descriptions;
vector<string> phenomena; std::vector<std::string> phenomena;
}; };
inline const char *getData() const { return _data; } inline const char *getData() const { return _data; }
@ -206,14 +199,14 @@ public:
inline int getWindDir() const { return _wind_dir; } inline int getWindDir() const { return _wind_dir; }
inline double getWindSpeed_mps() const { return _wind_speed; } inline double getWindSpeed_mps() const { return _wind_speed; }
inline double getWindSpeed_kmh() const { return _wind_speed == NaN ? NaN : _wind_speed * SG_MPS_TO_KMH; } inline double getWindSpeed_kmh() const { return _wind_speed == SGMetarNaN ? SGMetarNaN : _wind_speed * SG_MPS_TO_KMH; }
inline double getWindSpeed_kt() const { return _wind_speed == NaN ? NaN : _wind_speed * SG_MPS_TO_KT; } inline double getWindSpeed_kt() const { return _wind_speed == SGMetarNaN ? SGMetarNaN : _wind_speed * SG_MPS_TO_KT; }
inline double getWindSpeed_mph() const { return _wind_speed == NaN ? NaN : _wind_speed * SG_MPS_TO_MPH; } inline double getWindSpeed_mph() const { return _wind_speed == SGMetarNaN ? SGMetarNaN : _wind_speed * SG_MPS_TO_MPH; }
inline double getGustSpeed_mps() const { return _gust_speed; } inline double getGustSpeed_mps() const { return _gust_speed; }
inline double getGustSpeed_kmh() const { return _gust_speed == NaN ? NaN : _gust_speed * SG_MPS_TO_KMH; } inline double getGustSpeed_kmh() const { return _gust_speed == SGMetarNaN ? SGMetarNaN : _gust_speed * SG_MPS_TO_KMH; }
inline double getGustSpeed_kt() const { return _gust_speed == NaN ? NaN : _gust_speed * SG_MPS_TO_KT; } inline double getGustSpeed_kt() const { return _gust_speed == SGMetarNaN ? SGMetarNaN : _gust_speed * SG_MPS_TO_KT; }
inline double getGustSpeed_mph() const { return _gust_speed == NaN ? NaN : _gust_speed * SG_MPS_TO_MPH; } inline double getGustSpeed_mph() const { return _gust_speed == SGMetarNaN ? SGMetarNaN : _gust_speed * SG_MPS_TO_MPH; }
inline int getWindRangeFrom() const { return _wind_range_from; } inline int getWindRangeFrom() const { return _wind_range_from; }
inline int getWindRangeTo() const { return _wind_range_to; } inline int getWindRangeTo() const { return _wind_range_to; }
@ -224,11 +217,11 @@ public:
inline const SGMetarVisibility *getDirVisibility() const { return _dir_visibility; } inline const SGMetarVisibility *getDirVisibility() const { return _dir_visibility; }
inline double getTemperature_C() const { return _temp; } inline double getTemperature_C() const { return _temp; }
inline double getTemperature_F() const { return _temp == NaN ? NaN : 1.8 * _temp + 32; } inline double getTemperature_F() const { return _temp == SGMetarNaN ? SGMetarNaN : 1.8 * _temp + 32; }
inline double getDewpoint_C() const { return _dewp; } inline double getDewpoint_C() const { return _dewp; }
inline double getDewpoint_F() const { return _dewp == NaN ? NaN : 1.8 * _dewp + 32; } inline double getDewpoint_F() const { return _dewp == SGMetarNaN ? SGMetarNaN : 1.8 * _dewp + 32; }
inline double getPressure_hPa() const { return _pressure == NaN ? NaN : _pressure / 100; } inline double getPressure_hPa() const { return _pressure == SGMetarNaN ? SGMetarNaN : _pressure / 100; }
inline double getPressure_inHg() const { return _pressure == NaN ? NaN : _pressure * SG_PA_TO_INHG; } inline double getPressure_inHg() const { return _pressure == SGMetarNaN ? SGMetarNaN : _pressure * SG_PA_TO_INHG; }
inline int getRain() const { return _rain; } inline int getRain() const { return _rain; }
inline int getHail() const { return _hail; } inline int getHail() const { return _hail; }
@ -237,13 +230,13 @@ public:
double getRelHumidity() const; double getRelHumidity() const;
inline const vector<SGMetarCloud>& getClouds() const { return _clouds; } inline const std::vector<SGMetarCloud>& getClouds() const { return _clouds; }
inline const map<string, SGMetarRunway>& getRunways() const { return _runways; } inline const std::map<std::string, SGMetarRunway>& getRunways() const { return _runways; }
inline const vector<string>& getWeather() const { return _weather; } inline const std::vector<std::string>& getWeather() const { return _weather; }
inline const vector<struct Weather> getWeather2() const { return _weather2; } inline const std::vector<struct Weather> getWeather2() const { return _weather2; }
protected: protected:
string _url; std::string _url;
int _grpcount; int _grpcount;
bool _x_proxy; bool _x_proxy;
char *_data; char *_data;
@ -267,15 +260,15 @@ protected:
int _hail; int _hail;
int _snow; int _snow;
bool _cavok; bool _cavok;
vector<struct Weather> _weather2; std::vector<struct Weather> _weather2;
SGMetarVisibility _min_visibility; SGMetarVisibility _min_visibility;
SGMetarVisibility _max_visibility; SGMetarVisibility _max_visibility;
SGMetarVisibility _vert_visibility; SGMetarVisibility _vert_visibility;
SGMetarVisibility _dir_visibility[8]; SGMetarVisibility _dir_visibility[8];
vector<SGMetarCloud> _clouds; std::vector<SGMetarCloud> _clouds;
map<string, SGMetarRunway> _runways; std::map<std::string, SGMetarRunway> _runways;
vector<string> _weather; std::vector<std::string> _weather;
bool scanPreambleDate(); bool scanPreambleDate();
bool scanPreambleTime(); bool scanPreambleTime();
@ -303,10 +296,7 @@ protected:
int scanNumber(char **str, int *num, int min, int max = 0); int scanNumber(char **str, int *num, int min, int max = 0);
bool scanBoundary(char **str); bool scanBoundary(char **str);
const struct Token *scanToken(char **str, const struct Token *list); const struct Token *scanToken(char **str, const struct Token *list);
char *loadData(const char *id, const string& proxy, const string& port,
const string &auth, time_t time);
void normalizeData(); void normalizeData();
}; };
#undef NaN
#endif // _METAR_HXX #endif // _METAR_HXX

View File

@ -0,0 +1,68 @@
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include <simgear/compiler.h>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#ifdef _MSC_VER
# define random rand
#endif
#include <simgear/misc/sg_dir.hxx>
#include <simgear/structure/exception.hxx>
#include "metar.hxx"
using std::cout;
using std::cerr;
using std::endl;
using std::string;
#define COMPARE(a, b) \
if ((a) != (b)) { \
cerr << "failed:" << #a << " != " << #b << endl; \
cerr << "\tgot:" << a << endl; \
exit(1); \
}
#define VERIFY(a) \
if (!(a)) { \
cerr << "failed:" << #a << endl; \
exit(1); \
}
void test_basic()
{
SGMetar m1("2011/10/20 11:25 EHAM 201125Z 27012KT 240V300 9999 VCSH FEW025CB SCT048 10/05 Q1025 TEMPO VRB03KT");
COMPARE(m1.getYear(), 2011);
COMPARE(m1.getMonth(), 10);
COMPARE(m1.getDay(), 20);
COMPARE(m1.getHour(), 11);
COMPARE(m1.getMinute(), 25);
COMPARE(m1.getReportType(), -1); // should default to NIL?
COMPARE(m1.getWindDir(), 270);
COMPARE(m1.getWindSpeed_kt(), 12);
COMPARE(m1.getTemperature_C(), 10);
COMPARE(m1.getDewpoint_C(), 5);
COMPARE(m1.getPressure_hPa(), 1025);
}
int main(int argc, char* argv[])
{
try {
test_basic();
} catch (sg_exception& e) {
cerr << "got exception:" << e.getMessage() << endl;
return -1;
}
return 0;
}