kopia lustrzana https://github.com/jamescoxon/dl-fldigi
Weather report
* Added return http document error tests * Removed "Weather:\n" prefix from returned data string * Corrected English / Metric conversions * Added wind in Meters/Sec to MPH KPH conversion * Added condition parsing from raw data string * Added web lookup for METAR station of interestpull/1/head
rodzic
5a080d5df7
commit
7c60aa866e
|
@ -3253,6 +3253,12 @@ static void cb_btn_wx_mbars(Fl_Check_Button* o, void*) {
|
|||
progdefaults.changed = true;
|
||||
}
|
||||
|
||||
Fl_Button *btn_metar_search=(Fl_Button *)0;
|
||||
|
||||
static void cb_btn_metar_search(Fl_Button*, void*) {
|
||||
get_METAR_station();
|
||||
}
|
||||
|
||||
Fl_Group *tabQRZ=(Fl_Group *)0;
|
||||
|
||||
Fl_Round_Button *btnQRZWEBnotavailable=(Fl_Round_Button *)0;
|
||||
|
@ -7196,7 +7202,7 @@ d frequency"));
|
|||
{ Fl_Group* o = new Fl_Group(5, 60, 490, 300, _("Weather query specification"));
|
||||
o->box(FL_ENGRAVED_FRAME);
|
||||
o->align(Fl_Align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE));
|
||||
{ Fl_Input* o = inpWXsta = new Fl_Input(197, 92, 43, 24, _("METAR station ID code"));
|
||||
{ Fl_Input* o = inpWXsta = new Fl_Input(70, 92, 43, 24, _("METAR station ID code"));
|
||||
inpWXsta->tooltip(_("for example KMDQ for \nHuntsville-Madison Executive Airport, AL"));
|
||||
inpWXsta->callback((Fl_Callback*)cb_inpWXsta);
|
||||
inpWXsta->align(Fl_Align(FL_ALIGN_RIGHT));
|
||||
|
@ -7265,6 +7271,9 @@ d frequency"));
|
|||
{ Fl_Box* o = new Fl_Box(45, 269, 156, 19, _("Barometric pressure"));
|
||||
o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
|
||||
} // Fl_Box* o
|
||||
{ btn_metar_search = new Fl_Button(286, 92, 130, 24, _("Search on web"));
|
||||
btn_metar_search->callback((Fl_Callback*)cb_btn_metar_search);
|
||||
} // Fl_Button* btn_metar_search
|
||||
o->end();
|
||||
} // Fl_Group* o
|
||||
tabWX->end();
|
||||
|
|
|
@ -114,7 +114,8 @@ for (size_t i = 0; i < sizeof(qrzbweb)/sizeof(*qrzbweb); i++)
|
|||
qrzbweb[i]->value(b == qrzbweb[i]);} {}
|
||||
}
|
||||
|
||||
Function {ConfigureDialog()} {} {
|
||||
Function {ConfigureDialog()} {open
|
||||
} {
|
||||
code {static const char szShifts[] = "23|85|160|170|182|200|240|350|425|_850|Custom";
|
||||
static const char szBauds[] = "45|45.45|50|56|75|100|110|150|200|300";
|
||||
static const char szSelBits[] = "5 (baudot)|7 (ascii)|8 (ascii)";
|
||||
|
@ -4026,7 +4027,7 @@ progdefaults.changed = true;}
|
|||
callback {progdefaults.wx_sta = o->value();
|
||||
progdefaults.changed = true;}
|
||||
tooltip {for example KMDQ for
|
||||
Huntsville-Madison Executive Airport, AL} xywh {197 92 43 24} align 8
|
||||
Huntsville-Madison Executive Airport, AL} xywh {70 92 43 24} align 8
|
||||
code0 {o->value(progdefaults.wx_sta.c_str());}
|
||||
}
|
||||
Fl_Check_Button btn_wx_full {
|
||||
|
@ -4104,6 +4105,12 @@ progdefaults.changed = true;}
|
|||
label {Barometric pressure}
|
||||
xywh {45 269 156 19} align 20
|
||||
}
|
||||
Fl_Button btn_metar_search {
|
||||
label {Search on web}
|
||||
callback {get_METAR_station();}
|
||||
xywh {286 92 130 24}
|
||||
code0 {\#include "weather.h"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -444,6 +444,8 @@ extern Fl_Check_Button *btn_wx_mph;
|
|||
extern Fl_Check_Button *btn_wx_kph;
|
||||
extern Fl_Check_Button *btn_wx_inches;
|
||||
extern Fl_Check_Button *btn_wx_mbars;
|
||||
#include "weather.h"
|
||||
extern Fl_Button *btn_metar_search;
|
||||
extern Fl_Group *tabQRZ;
|
||||
extern Fl_Round_Button *btnQRZWEBnotavailable;
|
||||
extern Fl_Round_Button *btnQRZonline;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <string>
|
||||
|
||||
extern void getwx(std::string& wx, const char *metar = NULL);
|
||||
extern void get_METAR_station();
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -54,12 +54,164 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
/*======================================================================
|
||||
*
|
||||
* WEATHER The weather group
|
||||
* iiddppooxx
|
||||
* ii is intensity group
|
||||
* ii Description
|
||||
* - light
|
||||
* moderate
|
||||
* + heavy
|
||||
* VC in the vicinity
|
||||
*
|
||||
* dd is the descriptor group
|
||||
* dd Description
|
||||
* MI shallow
|
||||
* PR partial
|
||||
* BC patches
|
||||
* DR low drifting
|
||||
* BL blowing
|
||||
* SH shower
|
||||
* TS thunderstorm
|
||||
* FZ freezing
|
||||
*
|
||||
* pp is the precipitation group
|
||||
* pp Description
|
||||
* DZ drizzle
|
||||
* RA rain
|
||||
* SN snow
|
||||
* SG snow grains
|
||||
* IC ice crystals
|
||||
* PE ice pellets
|
||||
* GR hail
|
||||
* GS small hail/snow pellets
|
||||
* UP unknown
|
||||
*
|
||||
* oo is the obscuration group
|
||||
* oo Description
|
||||
* BR mist
|
||||
* FG fog
|
||||
* FU smoke
|
||||
* VA volcanic ash
|
||||
* DU dust
|
||||
* SA sand
|
||||
* HZ haze
|
||||
* PY spray
|
||||
*
|
||||
* xx is the misc group
|
||||
* xx Description
|
||||
* PO dust whirls
|
||||
* SQ squalls
|
||||
* FC funnel cloud/tornado/waterspout
|
||||
* SS duststorm
|
||||
*
|
||||
* CLOUDS
|
||||
* The cloud levels
|
||||
* ccchhhtt
|
||||
* ccc is the coverage
|
||||
* CLR or SKC = clear
|
||||
* FEW = 1/8 coverage
|
||||
* SCT = 2,3,4/8 coverage
|
||||
* BKN = 5,6,7/8 coverage
|
||||
* OVC = overcast
|
||||
* VV = vertical visibility for obscuration
|
||||
* hhh is the height of base in 30m or 100ft increments. ie 30 = 3000 feet
|
||||
* tt is an optional type
|
||||
* CU = cumulus
|
||||
* CB = cumulonumbus
|
||||
* TCU = towering cumulus
|
||||
* CI = cirrus
|
||||
*/
|
||||
|
||||
struct wxpairs {const char *grp; const char *name;};
|
||||
|
||||
static wxpairs precip[] = {
|
||||
{"DZ", "drizzle"},
|
||||
{"RA", "rain"},
|
||||
{"SN", "snow"},
|
||||
{"SG", "snow grains"},
|
||||
{"IC", "ice crystals"},
|
||||
{"PE", "ice pellets"},
|
||||
{"GR", "hail"},
|
||||
{"GS", "small hail / show pellets"},
|
||||
{"UP", "unknown"},
|
||||
{NULL, NULL} };
|
||||
|
||||
static wxpairs intensity[] = {
|
||||
{"-", "light"},
|
||||
{"+", "heavy"},
|
||||
{"VC", "in the vicinity"},
|
||||
{NULL, NULL} };
|
||||
|
||||
static wxpairs descriptor[] = {
|
||||
{"MI", "shallow "},
|
||||
{"PR", "partial"},
|
||||
{"BC", "patches"},
|
||||
{"DR", "low drifting"},
|
||||
{"BL", "blowing"},
|
||||
{"SH", "shower"},
|
||||
{"TS", "thunderstorm"},
|
||||
{"FZ", "freezing"},
|
||||
{NULL, NULL} };
|
||||
|
||||
static wxpairs obscure[] = {
|
||||
{"BR", "mist"},
|
||||
{"FG", "fog"},
|
||||
{"FU", "smoke"},
|
||||
{"VA", "volcanic ash"},
|
||||
{"DU", "dust"},
|
||||
{"SA", "sand"},
|
||||
{"HZ", "haze"},
|
||||
{"PY", "spray"},
|
||||
{NULL, NULL} };
|
||||
|
||||
static wxpairs misc[] = {
|
||||
{"PO", "dust whirls"},
|
||||
{"SQ", "squalls"},
|
||||
{"FC", "funnel cloud/tornado/waterspout"},
|
||||
{"SS", "duststorm"},
|
||||
{NULL, NULL} };
|
||||
|
||||
static wxpairs clouds[] = {
|
||||
{"CLR", "clear skies"},
|
||||
{"SKC", "clear skies"},
|
||||
{"FEW", "few clouds"},
|
||||
{"SCT", "scattered clouds"},
|
||||
{"BKN", "broken cloud cover"},
|
||||
{"OVC", "overcast"},
|
||||
{NULL, NULL} };
|
||||
|
||||
static wxpairs cloud_type[] = {
|
||||
{"CU", "cumulus"},
|
||||
{"CB", "cumulonumbus"},
|
||||
{"TCU", "towering cumulus"},
|
||||
{"CI", "cirrus"},
|
||||
{NULL, NULL} };
|
||||
|
||||
static void wx_busy_cursor(void*)
|
||||
{
|
||||
Fl::first_window()->cursor(FL_CURSOR_WAIT);
|
||||
}
|
||||
|
||||
static void wx_default_cursor(void*)
|
||||
{
|
||||
Fl::first_window()->cursor(FL_CURSOR_DEFAULT);
|
||||
}
|
||||
|
||||
void getwx(string& wx, const char *metar)
|
||||
{
|
||||
string url;
|
||||
string text;
|
||||
string field;
|
||||
string wxsta;
|
||||
string name = "";
|
||||
string condx = "";
|
||||
string temperature = "";
|
||||
string winds = "";
|
||||
string baro = "";
|
||||
|
||||
size_t p, p1, p2, p3;
|
||||
|
||||
if (!metar)
|
||||
wxsta = progdefaults.wx_sta;
|
||||
|
@ -68,58 +220,56 @@ void getwx(string& wx, const char *metar)
|
|||
|
||||
wx.clear();
|
||||
|
||||
for (size_t n = 0; n < wxsta.length(); n++) wxsta[n] = toupper(wxsta[n]);
|
||||
for (size_t n = 0; n < wxsta.length(); n++)
|
||||
wxsta[n] = toupper(wxsta[n] & 0x7F);
|
||||
|
||||
url.assign("http://weather.noaa.gov/pub/data/observations/metar/decoded/")
|
||||
.append(wxsta).append(".TXT");
|
||||
|
||||
int ret = fetch_http(url, text, 5.0);
|
||||
|
||||
if (ret == -1) return;
|
||||
|
||||
size_t p, p1, p2, p3;
|
||||
p2 = text.find(wxsta);
|
||||
if (p2 == string::npos) return;
|
||||
p1 = text.rfind("\n", p2) + 1;
|
||||
p3 = text.find("ob:", p2);
|
||||
if (p3 == string::npos) return;
|
||||
|
||||
string wx_full = text.substr(p1, p3 - p1);
|
||||
if (progdefaults.wx_full) {
|
||||
wx.assign("Weather:\n").append(wx_full);
|
||||
if (!fetch_http_gui(url, text, 5.0, wx_busy_cursor, 0, wx_default_cursor, 0)) {
|
||||
LOG_WARN("%s", "url not available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
wx.assign("Weather:\n");
|
||||
|
||||
if (progdefaults.wx_station_name) { // parse noun name
|
||||
if (text[p2-1] == '(') { // have valid line
|
||||
wx.append(text.substr(p1, p2 - p1 -2)).append("\n");
|
||||
}
|
||||
p2 = text.find(wxsta);
|
||||
if (p2 == string::npos) {
|
||||
LOG_WARN("%s", "station not found\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (progdefaults.wx_condx) {
|
||||
size_t p4, p5, p6;
|
||||
wx.append("Condx: ");
|
||||
if ((p4 = wx_full.find("Weather: ")) != string::npos) {
|
||||
p5 = p4 + 9;
|
||||
p6 = wx_full.find("\n", p5);
|
||||
if (p6 != string::npos)
|
||||
wx.append(wx_full.substr(p5, p6 - p5));
|
||||
}
|
||||
if ((p4 = wx_full.find("Sky conditions: ")) != string::npos) {
|
||||
p5 = p4 + 16;
|
||||
p6 = wx_full.find("\n", p5);
|
||||
if (p6 != string::npos)
|
||||
wx.append(wx_full.substr(p5, p6 - p5));
|
||||
}
|
||||
wx.append("\n");
|
||||
p1 = text.rfind("\n", p2);
|
||||
if (p1 == string::npos) {
|
||||
LOG_WARN("%s", "station not found\n");
|
||||
return;
|
||||
}
|
||||
p1++;
|
||||
|
||||
p3 = text.find("ob:", p2);
|
||||
if (p3 == string::npos) {
|
||||
LOG_WARN("%s", "observations not available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
string wx_full = text.substr(p1, p3 - p1);
|
||||
if (progdefaults.wx_full) {
|
||||
wx.assign(wx_full);//text.substr(p1));
|
||||
return;
|
||||
}
|
||||
//printf("%s\n",wx_full.c_str());
|
||||
|
||||
if (text[p2-1] == '(') // have valid line
|
||||
name = text.substr(p1, p2 - p1 -2);
|
||||
|
||||
p = text.find(wxsta, p3 + 1);
|
||||
text.erase(0, p + 1 + wxsta.length());
|
||||
p = text.find("\n");
|
||||
if (p != string::npos) text.erase(p);
|
||||
//printf("%s\n", text.c_str());
|
||||
|
||||
while(text.length()) {
|
||||
// parse field contents
|
||||
bool parsed = false;
|
||||
while(text.length()) { // each ob: field is separated by a space or end of file
|
||||
parsed = false;
|
||||
p = text.find(" ");
|
||||
if (p != string::npos) {
|
||||
field = text.substr(0, p);
|
||||
|
@ -128,71 +278,153 @@ void getwx(string& wx, const char *metar)
|
|||
field = text;
|
||||
text.clear();
|
||||
}
|
||||
// parse field contents
|
||||
if (field == "RMK") break;
|
||||
// parse for general weather
|
||||
// iiddppooxx
|
||||
if (field == "AUTO") ;
|
||||
|
||||
else if ((progdefaults.wx_mph || progdefaults.wx_kph) &&
|
||||
field.rfind("KT") == field.length() - 2) { // wind dir / speed
|
||||
int knots;
|
||||
sscanf(field.substr(3,2).c_str(), "%d", &knots);
|
||||
wx.append("Wind: ").append(field.substr(0,3)).append(" at ");
|
||||
char ctemp[10];
|
||||
if (progdefaults.wx_mph) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d mph ", (int)(knots * 528.0 / 600.0));
|
||||
wx.append(ctemp);
|
||||
else if ((p = field.rfind("KT")) != string::npos) {
|
||||
if (p == field.length() - 2) { // wind dir / speed
|
||||
int knots;
|
||||
sscanf(field.substr(3,2).c_str(), "%d", &knots);
|
||||
winds.clear();
|
||||
winds.append(field.substr(0,3)).append(" at ");
|
||||
char ctemp[10];
|
||||
if (progdefaults.wx_mph) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d mph ", (int)ceil(knots * 600.0 / 528.0 ));
|
||||
winds.append(ctemp);
|
||||
}
|
||||
if (progdefaults.wx_kph) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d kph ", (int)ceil(knots * 600.0 * 1.6094 / 528.0));
|
||||
winds.append(ctemp);
|
||||
}
|
||||
}
|
||||
if (progdefaults.wx_kph) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d kph ", (int)(knots * 528.0 * 1.8288 / 600.0));
|
||||
wx.append(ctemp);
|
||||
}
|
||||
wx.append("\n");
|
||||
}
|
||||
|
||||
else if ((p = field.find("/") ) != string::npos &&
|
||||
(progdefaults.wx_fahrenheit || progdefaults.wx_celsius) ) { // temperature / dewpoint
|
||||
else if ((p = field.rfind("MPS")) != string::npos) {
|
||||
if (p == field.length() - 3) { // wind dir / speed in meters / second
|
||||
int mps;
|
||||
sscanf(field.substr(3,2).c_str(), "%d", &mps);
|
||||
winds.clear();
|
||||
winds.append(field.substr(0,3)).append(" at ");
|
||||
char ctemp[10];
|
||||
if (progdefaults.wx_mph) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d mph ", (int)ceil(mps * 2.2369));
|
||||
winds.append(ctemp);
|
||||
}
|
||||
if (progdefaults.wx_kph) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d kph ", (int)ceil(mps * 3.6));
|
||||
winds.append(ctemp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((p = field.find("/") ) != string::npos) { // temperature / dewpoint
|
||||
string cent = field.substr(0, p);
|
||||
if (cent[0] == 'M') cent[0] = '-';
|
||||
int tempC, tempF;
|
||||
sscanf(cent.c_str(), "%d", &tempC);
|
||||
tempF = (int)(tempC * 2.12 + 32);
|
||||
wx.append("Temp: ");
|
||||
tempF = (int)(tempC * 1.8 + 32);
|
||||
temperature.clear();
|
||||
char ctemp[10];
|
||||
if (progdefaults.wx_fahrenheit) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d F ", tempF);
|
||||
wx.append(ctemp);
|
||||
temperature.append(ctemp);
|
||||
}
|
||||
if (progdefaults.wx_celsius) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%d C", tempC);
|
||||
wx.append(ctemp);
|
||||
temperature.append(ctemp);
|
||||
}
|
||||
wx.append("\n");
|
||||
}
|
||||
else if ((field[0] == 'A' && field.length() == 5) || field[0] == 'Q') {
|
||||
float inches;
|
||||
sscanf(field.substr(1).c_str(), "%f", &inches);
|
||||
if (field[0] == 'A')
|
||||
inches /= 100.0;
|
||||
else
|
||||
inches /= 33.87;
|
||||
baro.clear();
|
||||
char ctemp[20];
|
||||
if (progdefaults.wx_inches) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%.2f in Hg ", inches);
|
||||
baro.append(ctemp);
|
||||
}
|
||||
if (progdefaults.wx_mbars) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%.0f mbar", inches * 33.87);
|
||||
baro.append(ctemp);
|
||||
}
|
||||
} if (!parsed) {
|
||||
for (wxpairs *pp = precip; pp->grp != NULL; pp++) {
|
||||
if (field.find(pp->grp) != string::npos) { // found a precip group
|
||||
wxpairs *ii, *dd, *oo, *xx;
|
||||
for (ii = intensity; ii->grp != NULL; ii++)
|
||||
if (field.find(ii->grp) != string::npos) break;
|
||||
for (dd = descriptor; dd->grp != NULL; dd++)
|
||||
if (field.find(dd->grp) != string::npos) break;
|
||||
for (oo = obscure; oo->grp != NULL; oo++)
|
||||
if (field.find(oo->grp) != string::npos) break;
|
||||
for (xx = misc; xx->grp != NULL; xx++)
|
||||
if (field.find(xx->grp) != string::npos) break;
|
||||
if (ii->grp != NULL) condx.append(ii->name).append(" ");
|
||||
if (dd->grp != NULL) condx.append(dd->name).append(" ");
|
||||
condx.append(pp->name);
|
||||
if (oo->grp != NULL) condx.append(", ").append(oo->name);
|
||||
if (xx->grp != NULL) condx.append(", ").append(xx->name);
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
} if (!parsed) {
|
||||
wxpairs *oo;
|
||||
for (oo = obscure; oo->grp != NULL; oo++)
|
||||
if (field.find(oo->grp) != string::npos) break;
|
||||
if (oo->grp != NULL) {
|
||||
condx.append(" ").append(oo->name);
|
||||
parsed = true;
|
||||
}
|
||||
} if (!parsed) {
|
||||
// parse for cloud cover
|
||||
// use only the first occurance of sky cover report; it is lowest altitude
|
||||
// cloud cover is reported multiple times for sounding stations
|
||||
for (wxpairs *cc = clouds; cc->grp != NULL; cc++) {
|
||||
if (field.find(cc->grp) != string::npos) {
|
||||
if (condx.find(cc->name) != string::npos) break;
|
||||
if (condx.empty())
|
||||
condx.append(cc->name);
|
||||
else
|
||||
condx.append(", ").append(cc->name);
|
||||
wxpairs *ct;
|
||||
for (ct = cloud_type; ct->grp != NULL; ct++) {
|
||||
if (field.find(ct->grp) != string::npos) {
|
||||
if (ct->grp != NULL)
|
||||
condx.append(" ").append(ct->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
parsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (progdefaults.wx_inches || progdefaults.wx_mbars) {
|
||||
if ((field[0] == 'A' && field.length() == 5) || field[0] == 'Q') {
|
||||
float inches;
|
||||
sscanf(field.substr(1).c_str(), "%f", &inches);
|
||||
if (field[0] == 'A')
|
||||
inches /= 100.0;
|
||||
else
|
||||
inches /= 33.87;
|
||||
wx.append("Baro: ");
|
||||
char ctemp[20];
|
||||
if (progdefaults.wx_inches) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%.2f in Hg ", inches);
|
||||
wx.append(ctemp);
|
||||
}
|
||||
if (progdefaults.wx_mbars) {
|
||||
snprintf(ctemp, sizeof(ctemp), "%.0f mbar", inches * 33.87);
|
||||
wx.append(ctemp);
|
||||
}
|
||||
wx.append("\n");
|
||||
}
|
||||
}
|
||||
if (progdefaults.wx_station_name && !name.empty()) { // parse noun name
|
||||
wx.append("Sta: ").append(name).append("\n");
|
||||
}
|
||||
if (progdefaults.wx_condx && !condx.empty()) {
|
||||
wx.append("Cond: ").append(condx).append("\n");
|
||||
}
|
||||
if ((progdefaults.wx_mph || progdefaults.wx_kph) && !winds.empty()){
|
||||
wx.append("Wind: ").append(winds).append("\n");
|
||||
}
|
||||
if ((progdefaults.wx_fahrenheit || progdefaults.wx_celsius) && !temperature.empty() ) {
|
||||
wx.append("Temp: ").append(temperature).append("\n");
|
||||
}
|
||||
if ((progdefaults.wx_inches || progdefaults.wx_mbars) && !baro.empty()) {
|
||||
wx.append("Baro: ").append(baro).append("\n");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
//======================================================================
|
||||
|
||||
void get_METAR_station()
|
||||
{
|
||||
cb_mnuVisitURL(0, (void*)string("http://www.rap.ucar.edu/weather/surface/stations.txt").c_str());
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue