Remove using std:: from the metar header, remove HTTP support, add very basic unit-test harness.
This commit is contained in:
parent
7984f055e2
commit
63a8209a83
@ -5,3 +5,12 @@ set(HEADERS metar.hxx precipitation.hxx)
|
||||
set(SOURCES metar.cxx precipitation.cxx)
|
||||
|
||||
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)
|
@ -32,7 +32,6 @@
|
||||
#include <time.h>
|
||||
#include <cstring>
|
||||
|
||||
#include <simgear/io/sg_socket.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
@ -40,32 +39,28 @@
|
||||
|
||||
#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),
|
||||
@ -87,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;
|
||||
@ -169,85 +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 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
|
||||
* a trailing space. This makes scanning for things like "ALL RWY" easier.
|
||||
|
@ -29,18 +29,12 @@
|
||||
|
||||
#include <simgear/constants.h>
|
||||
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::string;
|
||||
|
||||
const double SGMetarNaN = -1E20;
|
||||
#define NaN SGMetarNaN
|
||||
|
||||
struct Token {
|
||||
const char *id;
|
||||
const char *text;
|
||||
};
|
||||
|
||||
const double SGMetarNaN = -1E20;
|
||||
|
||||
class SGMetar;
|
||||
|
||||
@ -48,7 +42,7 @@ class SGMetarVisibility {
|
||||
friend class SGMetar;
|
||||
public:
|
||||
SGMetarVisibility() :
|
||||
_distance(NaN),
|
||||
_distance(SGMetarNaN),
|
||||
_direction(-1),
|
||||
_modifier(EQUALS),
|
||||
_tendency(NONE) {}
|
||||
@ -70,8 +64,8 @@ public:
|
||||
void set(double dist, int dir = -1, int mod = -1, int tend = -1);
|
||||
|
||||
inline double getVisibility_m() const { return _distance; }
|
||||
inline double getVisibility_ft() const { return _distance == NaN ? NaN : _distance * SG_METER_TO_FEET; }
|
||||
inline double getVisibility_sm() const { return _distance == NaN ? NaN : _distance * SG_METER_TO_SM; }
|
||||
inline double getVisibility_ft() const { return _distance == SGMetarNaN ? SGMetarNaN : _distance * SG_METER_TO_FEET; }
|
||||
inline double getVisibility_sm() const { return _distance == SGMetarNaN ? SGMetarNaN : _distance * SG_METER_TO_SM; }
|
||||
inline int getDirection() const { return _direction; }
|
||||
inline int getModifier() const { return _modifier; }
|
||||
inline int getTendency() const { return _tendency; }
|
||||
@ -93,8 +87,8 @@ public:
|
||||
_deposit_string(0),
|
||||
_extent(-1),
|
||||
_extent_string(0),
|
||||
_depth(NaN),
|
||||
_friction(NaN),
|
||||
_depth(SGMetarNaN),
|
||||
_friction(SGMetarNaN),
|
||||
_friction_string(0),
|
||||
_comment(0),
|
||||
_wind_shear(false) {}
|
||||
@ -146,14 +140,14 @@ public:
|
||||
static const char * COVERAGE_BROKEN_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 );
|
||||
|
||||
inline Coverage getCoverage() const { return _coverage; }
|
||||
static Coverage getCoverage( const std::string & coverage );
|
||||
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 *getTypeLongString() const { return _type_long; }
|
||||
|
||||
@ -167,8 +161,7 @@ protected:
|
||||
|
||||
class SGMetar {
|
||||
public:
|
||||
SGMetar(const string& m, const string& proxy = "", const string& port = "",
|
||||
const string &auth = "", const time_t time = 0);
|
||||
SGMetar(const std::string& m);
|
||||
~SGMetar();
|
||||
|
||||
enum ReportType {
|
||||
@ -189,8 +182,8 @@ public:
|
||||
Weather() { intensity = NIL; vincinity = false; }
|
||||
Intensity intensity;
|
||||
bool vincinity;
|
||||
vector<string> descriptions;
|
||||
vector<string> phenomena;
|
||||
std::vector<std::string> descriptions;
|
||||
std::vector<std::string> phenomena;
|
||||
};
|
||||
|
||||
inline const char *getData() const { return _data; }
|
||||
@ -206,14 +199,14 @@ public:
|
||||
|
||||
inline int getWindDir() const { return _wind_dir; }
|
||||
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_kt() const { return _wind_speed == NaN ? NaN : _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_kmh() const { return _wind_speed == SGMetarNaN ? SGMetarNaN : _wind_speed * SG_MPS_TO_KMH; }
|
||||
inline double getWindSpeed_kt() const { return _wind_speed == SGMetarNaN ? SGMetarNaN : _wind_speed * SG_MPS_TO_KT; }
|
||||
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_kmh() const { return _gust_speed == NaN ? NaN : _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_mph() const { return _gust_speed == NaN ? NaN : _gust_speed * SG_MPS_TO_MPH; }
|
||||
inline double getGustSpeed_kmh() const { return _gust_speed == SGMetarNaN ? SGMetarNaN : _gust_speed * SG_MPS_TO_KMH; }
|
||||
inline double getGustSpeed_kt() const { return _gust_speed == SGMetarNaN ? SGMetarNaN : _gust_speed * SG_MPS_TO_KT; }
|
||||
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 getWindRangeTo() const { return _wind_range_to; }
|
||||
@ -224,11 +217,11 @@ public:
|
||||
inline const SGMetarVisibility *getDirVisibility() const { return _dir_visibility; }
|
||||
|
||||
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_F() const { return _dewp == NaN ? NaN : 1.8 * _dewp + 32; }
|
||||
inline double getPressure_hPa() const { return _pressure == NaN ? NaN : _pressure / 100; }
|
||||
inline double getPressure_inHg() const { return _pressure == NaN ? NaN : _pressure * SG_PA_TO_INHG; }
|
||||
inline double getDewpoint_F() const { return _dewp == SGMetarNaN ? SGMetarNaN : 1.8 * _dewp + 32; }
|
||||
inline double getPressure_hPa() const { return _pressure == SGMetarNaN ? SGMetarNaN : _pressure / 100; }
|
||||
inline double getPressure_inHg() const { return _pressure == SGMetarNaN ? SGMetarNaN : _pressure * SG_PA_TO_INHG; }
|
||||
|
||||
inline int getRain() const { return _rain; }
|
||||
inline int getHail() const { return _hail; }
|
||||
@ -237,13 +230,13 @@ public:
|
||||
|
||||
double getRelHumidity() const;
|
||||
|
||||
inline const vector<SGMetarCloud>& getClouds() const { return _clouds; }
|
||||
inline const map<string, SGMetarRunway>& getRunways() const { return _runways; }
|
||||
inline const vector<string>& getWeather() const { return _weather; }
|
||||
inline const vector<struct Weather> getWeather2() const { return _weather2; }
|
||||
inline const std::vector<SGMetarCloud>& getClouds() const { return _clouds; }
|
||||
inline const std::map<std::string, SGMetarRunway>& getRunways() const { return _runways; }
|
||||
inline const std::vector<std::string>& getWeather() const { return _weather; }
|
||||
inline const std::vector<struct Weather> getWeather2() const { return _weather2; }
|
||||
|
||||
protected:
|
||||
string _url;
|
||||
std::string _url;
|
||||
int _grpcount;
|
||||
bool _x_proxy;
|
||||
char *_data;
|
||||
@ -267,15 +260,15 @@ protected:
|
||||
int _hail;
|
||||
int _snow;
|
||||
bool _cavok;
|
||||
vector<struct Weather> _weather2;
|
||||
std::vector<struct Weather> _weather2;
|
||||
|
||||
SGMetarVisibility _min_visibility;
|
||||
SGMetarVisibility _max_visibility;
|
||||
SGMetarVisibility _vert_visibility;
|
||||
SGMetarVisibility _dir_visibility[8];
|
||||
vector<SGMetarCloud> _clouds;
|
||||
map<string, SGMetarRunway> _runways;
|
||||
vector<string> _weather;
|
||||
std::vector<SGMetarCloud> _clouds;
|
||||
std::map<std::string, SGMetarRunway> _runways;
|
||||
std::vector<std::string> _weather;
|
||||
|
||||
bool scanPreambleDate();
|
||||
bool scanPreambleTime();
|
||||
@ -303,10 +296,7 @@ protected:
|
||||
int scanNumber(char **str, int *num, int min, int max = 0);
|
||||
bool scanBoundary(char **str);
|
||||
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();
|
||||
};
|
||||
|
||||
#undef NaN
|
||||
#endif // _METAR_HXX
|
||||
|
68
simgear/environment/test_metar.cxx
Normal file
68
simgear/environment/test_metar.cxx
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user