kopia lustrzana https://github.com/jamescoxon/dl-fldigi
424 wiersze
10 KiB
C++
424 wiersze
10 KiB
C++
// ----------------------------------------------------------------------------
|
|
// weather.cxx -- a part of fldigi
|
|
//
|
|
// Copyright (C) 2012
|
|
// Dave Freese, W1HKJ
|
|
//
|
|
// This file is part of fldigi.
|
|
//
|
|
// Fldigi is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Fldigi is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with fldigi. If not, see <http://www.gnu.org/licenses/>.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef __MINGW32__
|
|
# include "compat.h"
|
|
#endif
|
|
|
|
#include <sys/time.h>
|
|
#include "signal.h"
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
#include <cctype>
|
|
|
|
#include "threads.h"
|
|
|
|
#include "misc.h"
|
|
#include "configuration.h"
|
|
|
|
#include "main.h"
|
|
#include "confdialog.h"
|
|
#include "fl_digi.h"
|
|
#include "trx.h"
|
|
|
|
#include "xmlreader.h"
|
|
|
|
#include "qrunner.h"
|
|
#include "debug.h"
|
|
#include "network.h"
|
|
|
|
#include "weather.h"
|
|
|
|
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} };
|
|
|
|
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;
|
|
else
|
|
wxsta = metar;
|
|
|
|
wx.clear();
|
|
|
|
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");
|
|
|
|
if (!fetch_http_gui(url, text, 5.0)) {
|
|
LOG_WARN("%s", "url not available\n");
|
|
return;
|
|
}
|
|
|
|
p2 = text.find(wxsta);
|
|
if (p2 == string::npos) {
|
|
LOG_WARN("%s", "station not found\n");
|
|
return;
|
|
}
|
|
|
|
string eoh = progdefaults.wx_eoh;
|
|
if (eoh.empty()) eoh = "Connection: close";
|
|
|
|
p1 = text.find(eoh);
|
|
if (p1 != string::npos) {
|
|
p1 = text.find("\n",p1);
|
|
text.erase(0, p1);
|
|
while (text[0] == '\r' || text[0] == '\n') text.erase(0,1);
|
|
p1 = text.find("\n");
|
|
name = text.substr(0, p1);
|
|
}
|
|
|
|
p3 = text.find("ob:");
|
|
if (p3 == string::npos) {
|
|
LOG_WARN("%s", "observations not available\n");
|
|
return;
|
|
}
|
|
|
|
if (progdefaults.wx_full) {
|
|
wx.assign(text.substr(0, p3));
|
|
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);
|
|
|
|
// 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);
|
|
text.erase(0, p+1);
|
|
} else {
|
|
field = text;
|
|
text.clear();
|
|
}
|
|
if (field == "RMK") break;
|
|
// parse for general weather
|
|
// iiddppooxx
|
|
if (field == "AUTO") ;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
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 * 1.8 + 32);
|
|
temperature.clear();
|
|
char ctemp[10];
|
|
if (progdefaults.wx_fahrenheit) {
|
|
snprintf(ctemp, sizeof(ctemp), "%d F ", tempF);
|
|
temperature.append(ctemp);
|
|
}
|
|
if (progdefaults.wx_celsius) {
|
|
snprintf(ctemp, sizeof(ctemp), "%d C", tempC);
|
|
temperature.append(ctemp);
|
|
}
|
|
}
|
|
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_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());
|
|
}
|