You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
872 lines
22 KiB
872 lines
22 KiB
/***************************************************************************
|
|
metar_parser.cpp - Metar Parser
|
|
Based on code originally in weatherlib.cpp.
|
|
-------------------
|
|
begin : Wed June 7 2004
|
|
copyright : (C) 2004 by John Ratke
|
|
: (C) 2002-2004 Nadeem Hasan <nhasan@kde.org>
|
|
: (C) 2002-2004 Ian Geiser <geiseri@kde.org>
|
|
email : jratke@comcast.net
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program 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 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <tqdatetime.h>
|
|
#include <kdebug.h>
|
|
#include <math.h>
|
|
|
|
#include "metar_parser.h"
|
|
#include "stationdatabase.h"
|
|
#include "sun.h"
|
|
|
|
// Temperature conversion macros
|
|
#define TEMP_C_TO_F(x) ( ((x) * 9/5) + 32 )
|
|
#define TEMP_F_TO_C(x) ( ((x) - 32) * 5/9 )
|
|
|
|
MetarParser::MetarParser(StationDatabase *stationDB,
|
|
KLocale::MeasureSystem units,
|
|
TQDate date,
|
|
TQTime time,
|
|
unsigned int localUTCOffset) :
|
|
m_stationDb(stationDB), m_units(units), m_date(date), m_time(time), m_localUTCOffset(localUTCOffset)
|
|
{
|
|
CoverRegExp = TQRegExp("^(FEW|SCT|BKN|OVC|SKC|CLR|CAVOK)([0-9]{3})?(?:TCU|CB)?$");
|
|
CurrentRegExp = TQRegExp("^(\\+|-|VC)?([A-Z]{2,4})$");
|
|
WindRegExp = TQRegExp("^([0-9]{3}|VRB)([0-9]{2,3})(?:G([0-9]{2,3}))?(KT|KMH|MPS)$");
|
|
VisRegExp = TQRegExp("^([0-9]{1,2})SM$");
|
|
VisFracRegExp = TQRegExp("^1/(2|4)SM$");
|
|
TempRegExp = TQRegExp("^(M)?([0-9]{2})/(?:(M)?([0-9]{2}))?$");
|
|
TimeRegExp = TQRegExp("^([0-9]{2}:[0-9]{2})$");
|
|
DateRegExp = TQRegExp("^([0-9]{4}/[0-9]{2}/[0-9]{2})$");
|
|
PressRegExp = TQRegExp("^([AQ])([0-9]{4})$");
|
|
TempTenRegExp = TQRegExp("^T([01][0-9]{3})([01][0-9]{3})$");
|
|
}
|
|
|
|
void MetarParser::reset()
|
|
{
|
|
// Initialize the WeatherInfo structure
|
|
weatherInfo.theWeather = TQString();
|
|
weatherInfo.clouds = 0;
|
|
weatherInfo.windMPH = 0;
|
|
weatherInfo.tempC = 0;
|
|
weatherInfo.dewC = 0;
|
|
weatherInfo.heavy = false;
|
|
weatherInfo.qsCoverList.clear();
|
|
weatherInfo.qsCurrentList.clear();
|
|
weatherInfo.qsDate = m_date;
|
|
weatherInfo.qsTime = m_time;
|
|
weatherInfo.qsPressure = TQString();
|
|
weatherInfo.qsTemperature = TQString();
|
|
weatherInfo.qsDewPoint = TQString();
|
|
weatherInfo.qsRelHumidity = TQString();
|
|
weatherInfo.qsVisibility = TQString();
|
|
weatherInfo.qsWindSpeed = TQString();
|
|
weatherInfo.qsWindChill = TQString();
|
|
weatherInfo.qsHeatIndex = TQString();
|
|
weatherInfo.qsWindDirection = TQString();
|
|
weatherInfo.stationNeedsMaintenance = false;
|
|
}
|
|
|
|
struct WeatherInfo MetarParser::processData(const TQString &stationID, const TQString &metar)
|
|
{
|
|
reset();
|
|
|
|
weatherInfo.reportLocation = stationID;
|
|
|
|
kdDebug(12006) << "Processing data: " << metar << endl;
|
|
|
|
// Split at whitespace into tokens
|
|
TQStringList dataList = TQStringList::split(TQRegExp("\\s+"), metar);
|
|
bool found = false;
|
|
bool beforeRemark = true;
|
|
|
|
for (TQStringList::ConstIterator it = dataList.begin();
|
|
it != dataList.end(); ++it)
|
|
{
|
|
// Don't try to parse the ICAO location code
|
|
if ((!found) && (*it == weatherInfo.reportLocation.upper().stripWhiteSpace()))
|
|
{
|
|
found = true;
|
|
continue;
|
|
}
|
|
|
|
kdDebug(12006) << "Processing Token: " << *it << endl;
|
|
|
|
if (*it == "RMK")
|
|
{
|
|
beforeRemark = false;
|
|
continue;
|
|
}
|
|
|
|
if (beforeRemark)
|
|
{
|
|
if (parseDate(*it))
|
|
continue;
|
|
if (parseTime(*it))
|
|
continue;
|
|
if (parseWindSpeed(*it))
|
|
continue;
|
|
if (parseVisibility(it)) // Note, pass in iterator.
|
|
continue;
|
|
if (parseTemperature(*it))
|
|
continue;
|
|
if (parsePressure(*it))
|
|
continue;
|
|
if (parseCover(*it))
|
|
continue;
|
|
if (parseCurrent(*it))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (parseTemperatureTenths(*it))
|
|
continue;
|
|
if (parseStationNeedsMaintenance(*it))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
calcTemperatureVariables();
|
|
calcWindChill();
|
|
calcCurrentIcon();
|
|
|
|
return weatherInfo;
|
|
}
|
|
|
|
/** Parse the current cover type */
|
|
bool MetarParser::parseCover(const TQString &s)
|
|
{
|
|
if (CoverRegExp.search(s) > -1)
|
|
{
|
|
kdDebug(12006) << "Cover: " << TQString(CoverRegExp.capturedTexts().join("-"))
|
|
<< endl;
|
|
|
|
TQString sCode = CoverRegExp.cap(1);
|
|
float height = CoverRegExp.cap(2).toFloat(); // initially in 100's of feet
|
|
TQString sClouds;
|
|
TQString skycondition;
|
|
|
|
height *= 100;
|
|
if (m_units == KLocale::Metric)
|
|
{
|
|
height = height * 0.3048;
|
|
// using plural i18n form for proper translations
|
|
sClouds = i18n("1 meter", "%n meters", (int)height);
|
|
}
|
|
else
|
|
{
|
|
// using plural i18n form for proper translations
|
|
sClouds = i18n("1 foot", "%n feet", (int)height);
|
|
}
|
|
|
|
if (sCode == "FEW")
|
|
{
|
|
skycondition = i18n( "Few clouds at %1" ).arg(sClouds);
|
|
weatherInfo.clouds += 2;
|
|
}
|
|
else if (sCode == "SCT")
|
|
{
|
|
skycondition = i18n( "Scattered clouds at %1" ).arg(sClouds);
|
|
weatherInfo.clouds += 4;
|
|
}
|
|
else if (sCode == "BKN")
|
|
{
|
|
skycondition = i18n( "Broken clouds at %1" ).arg(sClouds);
|
|
weatherInfo.clouds += 8;
|
|
}
|
|
else if (sCode == "OVC")
|
|
{
|
|
skycondition = i18n( "Overcast clouds at %1" ).arg(sClouds);
|
|
weatherInfo.clouds += 64;
|
|
}
|
|
else if ((sCode == "CLR") || (sCode == "SKC") || (sCode == "CAVOK"))
|
|
{
|
|
skycondition = i18n("Clear skies");
|
|
weatherInfo.clouds = 0;
|
|
}
|
|
|
|
kdDebug(12006) << "*** Clouds: " << weatherInfo.clouds << endl;
|
|
weatherInfo.qsCoverList << skycondition;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Parse the current weather conditions */
|
|
bool MetarParser::parseCurrent(const TQString &s)
|
|
{
|
|
if (CurrentRegExp.search(s) > -1)
|
|
{
|
|
TQString sIntensity = CurrentRegExp.cap(1);
|
|
TQString sCode = CurrentRegExp.cap(2);
|
|
TQString intensity, descriptor, phenomena, currentWeather;
|
|
|
|
kdDebug(12006) << "Current: " << TQString(CurrentRegExp.capturedTexts().join("-")) << endl;
|
|
|
|
// Decode the intensity
|
|
if (sIntensity == "+")
|
|
{
|
|
intensity = i18n("Heavy");
|
|
weatherInfo.heavy = true;
|
|
}
|
|
else if (sIntensity == "-")
|
|
{
|
|
intensity = i18n("Light");
|
|
weatherInfo.heavy = false;
|
|
}
|
|
|
|
// Decode the descriptor
|
|
if (sCode.contains("MI"))
|
|
descriptor = i18n("Shallow");
|
|
else if (sCode.contains("PR"))
|
|
descriptor = i18n("Partial");
|
|
else if (sCode.contains("BC"))
|
|
descriptor = i18n("Patches");
|
|
else if (sCode.contains("DR"))
|
|
descriptor = i18n("Low Drifting");
|
|
else if (sCode.contains("BL"))
|
|
descriptor = i18n("Blowing");
|
|
else if (sCode.contains("SH"))
|
|
{
|
|
descriptor = i18n("Showers");
|
|
weatherInfo.theWeather = "shower";
|
|
}
|
|
else if (sCode.contains("TS"))
|
|
{
|
|
descriptor = i18n("Thunder Storm");
|
|
weatherInfo.theWeather = "tstorm";
|
|
}
|
|
else if (sCode.contains("FZ"))
|
|
{
|
|
descriptor = i18n("Freezing");
|
|
}
|
|
|
|
// Decode weather phenomena
|
|
if (sCode.contains("DZ"))
|
|
{
|
|
phenomena = i18n("Drizzle");
|
|
weatherInfo.theWeather = "light_rain";
|
|
}
|
|
else if (sCode.contains("RA"))
|
|
{
|
|
phenomena = i18n("Rain");
|
|
weatherInfo.theWeather = "shower";
|
|
}
|
|
else if (sCode.contains("SN"))
|
|
{
|
|
phenomena = i18n("Snow");
|
|
weatherInfo.theWeather = "snow";
|
|
}
|
|
else if (sCode.contains("SG"))
|
|
{
|
|
phenomena = i18n("Snow Grains");
|
|
weatherInfo.theWeather = "snow4";
|
|
}
|
|
else if (sCode.contains("IC"))
|
|
{
|
|
phenomena = i18n("Ice Crystals");
|
|
weatherInfo.theWeather = "hail";
|
|
}
|
|
else if (sCode.contains("PE"))
|
|
{
|
|
phenomena = i18n("Ice Pellets");
|
|
weatherInfo.theWeather = "hail";
|
|
}
|
|
else if (s.contains("GR"))
|
|
{
|
|
phenomena = i18n("Hail");
|
|
weatherInfo.theWeather = "hail";
|
|
}
|
|
else if (sCode.contains("GS"))
|
|
{
|
|
phenomena = i18n("Small Hail Pellets");
|
|
weatherInfo.theWeather = "hail";
|
|
}
|
|
else if (s.contains("UP"))
|
|
{
|
|
phenomena = i18n("Unknown Precipitation");
|
|
weatherInfo.theWeather = iconName("shower1");
|
|
}
|
|
else if (sCode.contains("BR"))
|
|
{
|
|
phenomena = i18n("Mist");
|
|
// Mist has lower priority than say rain or snow
|
|
if ( weatherInfo.theWeather.isEmpty() )
|
|
{
|
|
weatherInfo.theWeather = "mist";
|
|
}
|
|
}
|
|
else if (sCode.contains("FG"))
|
|
{
|
|
phenomena = i18n("Fog");
|
|
// Fog has lower priority than say rain or snow
|
|
if ( weatherInfo.theWeather.isEmpty() )
|
|
{
|
|
weatherInfo.theWeather = "fog";
|
|
}
|
|
}
|
|
else if (sCode.contains("FU"))
|
|
phenomena = i18n("Smoke");
|
|
else if (sCode.contains("VA"))
|
|
phenomena = i18n("Volcanic Ash");
|
|
else if (sCode.contains("DU"))
|
|
phenomena = i18n("Widespread Dust");
|
|
else if (sCode.contains("SA"))
|
|
phenomena = i18n("Sand");
|
|
else if (sCode.contains("HZ"))
|
|
phenomena = i18n("Haze");
|
|
else if (sCode.contains("PY"))
|
|
phenomena = i18n("Spray");
|
|
else if (sCode.contains("PO"))
|
|
phenomena = i18n("Dust/Sand Swirls");
|
|
else if (sCode.contains("SQ"))
|
|
phenomena = i18n("Sudden Winds");
|
|
else if (sCode.contains("FC"))
|
|
{
|
|
if (sIntensity == "+")
|
|
currentWeather = i18n("Tornado");
|
|
else
|
|
phenomena = i18n("Funnel Cloud");
|
|
}
|
|
else if (sCode.contains("SS"))
|
|
phenomena = i18n("Sand Storm");
|
|
else if (sCode.contains("DS"))
|
|
phenomena = i18n("Dust Storm");
|
|
|
|
if (currentWeather.isEmpty()) currentWeather = i18n("%1 is the intensity, %2 is the descriptor and %3 is the phenomena", "%1 %2 %3").arg(intensity).arg(descriptor).arg(phenomena);
|
|
|
|
if (!currentWeather.isEmpty())
|
|
weatherInfo.qsCurrentList << currentWeather;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Parse out the current temperature */
|
|
bool MetarParser::parseTemperature(const TQString &s)
|
|
{
|
|
if (TempRegExp.search(s) > -1)
|
|
{
|
|
kdDebug(12006) << "Temp: " << TQString(TempRegExp.capturedTexts().join("-"))
|
|
<< endl;
|
|
|
|
float fTemp = TempRegExp.cap(2).toFloat();
|
|
if (TempRegExp.cap(1) == "M" && fTemp != 0 )
|
|
fTemp *= -1;
|
|
float fDew = TempRegExp.cap(4).toFloat();
|
|
if (TempRegExp.cap(3) == "M" && fDew != 0 )
|
|
fDew *= -1;
|
|
|
|
weatherInfo.tempC = fTemp;
|
|
weatherInfo.dewC = fDew;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MetarParser::parseTemperatureTenths(const TQString &s)
|
|
{
|
|
if (TempTenRegExp.search(s) > -1)
|
|
{
|
|
kdDebug(12006) << "Temp Tenths: " << TQString(TempTenRegExp.capturedTexts().join("-"))
|
|
<< endl;
|
|
|
|
float temperature = TempTenRegExp.cap( 1 ).toFloat() / 10;
|
|
float dewPoint = TempTenRegExp.cap( 2 ).toFloat() / 10;
|
|
|
|
if ( temperature >= 100 )
|
|
{
|
|
temperature -= 100;
|
|
temperature *= -1;
|
|
}
|
|
if ( dewPoint >= 100 )
|
|
{
|
|
dewPoint -= 100;
|
|
dewPoint *= -1;
|
|
}
|
|
|
|
weatherInfo.tempC = temperature;
|
|
weatherInfo.dewC = dewPoint;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MetarParser::calcTemperatureVariables()
|
|
{
|
|
#define E(t) ::pow(10, 7.5*t/(237.7+t))
|
|
float fRelHumidity = E(weatherInfo.dewC)/E(weatherInfo.tempC) * 100;
|
|
if (fRelHumidity > 100.0) fRelHumidity = 100.0;
|
|
|
|
weatherInfo.qsRelHumidity.sprintf("%.1f", fRelHumidity);
|
|
removeTrailingDotZero(weatherInfo.qsRelHumidity);
|
|
weatherInfo.qsRelHumidity += "%";
|
|
|
|
float fHeatIndex = 0;
|
|
float tempF = TEMP_C_TO_F(weatherInfo.tempC);
|
|
if (tempF >= 80)
|
|
{
|
|
#define SQR(a) ((a)*(a))
|
|
fHeatIndex = -42.379 + (2.04901523*tempF)
|
|
+ (10.14333127*fRelHumidity)
|
|
- (0.22475541*tempF*fRelHumidity)
|
|
- (0.00683783*SQR(tempF))
|
|
- (0.05481717*SQR(fRelHumidity))
|
|
+ (0.00122874*SQR(tempF)*fRelHumidity)
|
|
+ (0.00085282*tempF*SQR(fRelHumidity))
|
|
- (0.00000199*SQR(tempF)*SQR(fRelHumidity));
|
|
|
|
if ( fHeatIndex <= tempF )
|
|
fHeatIndex = 0;
|
|
}
|
|
|
|
TQString unit;
|
|
if (m_units == KLocale::Metric)
|
|
{
|
|
unit = i18n("°C");
|
|
weatherInfo.qsTemperature.sprintf("%.1f", weatherInfo.tempC);
|
|
weatherInfo.qsDewPoint.sprintf("%.1f", weatherInfo.dewC);
|
|
if (fHeatIndex >= 80)
|
|
weatherInfo.qsHeatIndex.sprintf("%.1f", TEMP_F_TO_C(fHeatIndex));
|
|
}
|
|
else
|
|
{
|
|
unit = i18n("°F");
|
|
weatherInfo.qsTemperature.sprintf("%.1f", tempF);
|
|
weatherInfo.qsDewPoint.sprintf("%.1f", TEMP_C_TO_F(weatherInfo.dewC));
|
|
if (fHeatIndex >= 80)
|
|
weatherInfo.qsHeatIndex.sprintf("%.1f", (fHeatIndex));
|
|
}
|
|
|
|
removeTrailingDotZero(weatherInfo.qsTemperature);
|
|
removeTrailingDotZero(weatherInfo.qsDewPoint);
|
|
removeTrailingDotZero(weatherInfo.qsHeatIndex);
|
|
|
|
weatherInfo.qsTemperature += unit;
|
|
weatherInfo.qsDewPoint += unit;
|
|
if (!weatherInfo.qsHeatIndex.isEmpty())
|
|
weatherInfo.qsHeatIndex += unit;
|
|
}
|
|
|
|
void MetarParser::removeTrailingDotZero(TQString &string)
|
|
{
|
|
if ( string.right( 2 ) == ".0" )
|
|
{
|
|
string = string.left( string.length() - 2 );
|
|
}
|
|
}
|
|
|
|
/** Parse out the current date. */
|
|
bool MetarParser::parseDate(const TQString &s)
|
|
{
|
|
if (DateRegExp.search(s) > -1)
|
|
{
|
|
kdDebug(12006) << "Date: " << TQString(DateRegExp.capturedTexts().join("-"))
|
|
<< endl;
|
|
TQString dateString = DateRegExp.cap(1);
|
|
TQString day, month, year;
|
|
|
|
day = dateString.mid(8,2);
|
|
month = dateString.mid(5,2);
|
|
year = dateString.mid(0,4);
|
|
|
|
TQDate theDate(year.toInt(), month.toInt(), day.toInt());
|
|
|
|
|
|
weatherInfo.qsDate = theDate;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Parse out the current time. */
|
|
bool MetarParser::parseTime(const TQString &s)
|
|
{
|
|
if (TimeRegExp.search(s) > -1)
|
|
{
|
|
kdDebug(12006) << "Time: " << TQString(TimeRegExp.capturedTexts().join("-"))
|
|
<< endl;
|
|
|
|
TQString hour, minute, dateString;
|
|
|
|
dateString = TimeRegExp.cap(1);
|
|
hour = dateString.mid(0,2);
|
|
minute = dateString.mid(3,2);
|
|
TQTime theTime(hour.toInt(), minute.toInt());
|
|
|
|
weatherInfo.qsTime = theTime;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Parse out the current visibility */
|
|
bool MetarParser::parseVisibility(TQStringList::ConstIterator it)
|
|
{
|
|
float fVisibility = 0;
|
|
|
|
if (VisRegExp.search(*it) > -1)
|
|
{
|
|
fVisibility = VisRegExp.cap(1).toFloat();
|
|
|
|
kdDebug(12006) << "Visibility: " << TQString(VisRegExp.capturedTexts().join("-"))
|
|
<< endl;
|
|
|
|
}
|
|
else if (VisFracRegExp.search(*it) > -1)
|
|
{
|
|
// got a fractional visibility, go back to previous string in the list
|
|
// and get the whole part.
|
|
fVisibility = (*(it--)).toFloat();
|
|
// shouldn't be necessary?
|
|
//it++;
|
|
fVisibility += ( 1 / VisFracRegExp.cap(1).toFloat() );
|
|
}
|
|
|
|
if (fVisibility > 0)
|
|
{
|
|
if (m_units == KLocale::Metric)
|
|
{
|
|
fVisibility *= 1.6;
|
|
weatherInfo.qsVisibility.setNum(fVisibility);
|
|
weatherInfo.qsVisibility += i18n("km");
|
|
}
|
|
else
|
|
{
|
|
weatherInfo.qsVisibility.setNum(fVisibility);
|
|
weatherInfo.qsVisibility += i18n("m");
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Parse out the current pressure. */
|
|
bool MetarParser::parsePressure( const TQString &s)
|
|
{
|
|
if (PressRegExp.search(s) > -1)
|
|
{
|
|
TQString type = PressRegExp.cap(1);
|
|
float fPressure = PressRegExp.cap(2).toFloat();
|
|
|
|
kdDebug(12006) << "Pressure: " << TQString(PressRegExp.capturedTexts().join("-"))
|
|
<< endl;
|
|
|
|
if (m_units == KLocale::Metric)
|
|
{
|
|
if (type == "A")
|
|
fPressure *= (33.8639/100);
|
|
weatherInfo.qsPressure.setNum(fPressure, 'f', 0);
|
|
weatherInfo.qsPressure += i18n(" hPa");
|
|
}
|
|
else
|
|
{
|
|
if (type == "Q")
|
|
fPressure /= 33.8639;
|
|
else
|
|
fPressure /= 100;
|
|
weatherInfo.qsPressure.setNum(fPressure, 'f', 2);
|
|
weatherInfo.qsPressure += i18n("\" Hg");
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct wind_info
|
|
{
|
|
unsigned int number;
|
|
TQString name;
|
|
};
|
|
|
|
static const struct wind_info wind_direction[] =
|
|
{
|
|
{ 0, i18n("N") }, // North is 0 to 11, and so on
|
|
{ 12, i18n("NNE") },
|
|
{ 33, i18n("NE") },
|
|
{ 57, i18n("ENE") },
|
|
{ 79, i18n("E") },
|
|
{ 102, i18n("ESE") },
|
|
{ 124, i18n("SE") },
|
|
{ 147, i18n("SSE") },
|
|
{ 169, i18n("S") },
|
|
{ 192, i18n("SSW") },
|
|
{ 214, i18n("SW") },
|
|
{ 237, i18n("WSW") },
|
|
{ 259, i18n("W") },
|
|
{ 282, i18n("WNW") },
|
|
{ 304, i18n("NW") },
|
|
{ 327, i18n("NNW") },
|
|
{ 349, i18n("N") },
|
|
{ 360, i18n("N") }
|
|
};
|
|
|
|
|
|
TQString MetarParser::parseWindDirection(const unsigned int direction)
|
|
{
|
|
unsigned int i = 0;
|
|
|
|
for (i = 0; i < (sizeof(wind_direction) / sizeof(wind_info)) - 1; i++)
|
|
{
|
|
if (direction >= wind_direction[i].number &&
|
|
direction < wind_direction[i + 1].number)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return wind_direction[i].name;
|
|
}
|
|
|
|
/** Parse the wind speed */
|
|
bool MetarParser::parseWindSpeed(const TQString &s)
|
|
{
|
|
if (WindRegExp.search(s) > -1)
|
|
{
|
|
unsigned int direction = WindRegExp.cap(1).toInt();
|
|
float windSpeed = WindRegExp.cap(2).toFloat();
|
|
float gustSpeed = WindRegExp.cap(3).toFloat();
|
|
TQString sWindUnit = WindRegExp.cap(4);
|
|
|
|
kdDebug(12006) << "Wind: " << WindRegExp.capturedTexts().join("-")
|
|
<< endl;
|
|
|
|
if (m_units == KLocale::Metric)
|
|
{
|
|
if (sWindUnit == "KT")
|
|
{
|
|
windSpeed = (windSpeed * 3.6 / 1.94);
|
|
gustSpeed = (gustSpeed * 3.6 / 1.94);
|
|
}
|
|
else if (sWindUnit == "MPS")
|
|
{
|
|
windSpeed = (windSpeed * 3.6);
|
|
gustSpeed = (gustSpeed * 3.6);
|
|
}
|
|
weatherInfo.windMPH = (windSpeed / 1.61);
|
|
weatherInfo.qsWindSpeed = i18n("1 km/h", "%n km/h", (int) windSpeed);
|
|
}
|
|
else
|
|
{
|
|
if (sWindUnit == "KT")
|
|
{
|
|
windSpeed = (windSpeed * 2.24 / 1.94);
|
|
gustSpeed = (gustSpeed * 2.24 / 1.94);
|
|
}
|
|
else if (sWindUnit == "KMH")
|
|
{
|
|
windSpeed = (windSpeed / 1.61);
|
|
gustSpeed = (gustSpeed / 1.61);
|
|
}
|
|
else if (sWindUnit == "MPS")
|
|
{
|
|
windSpeed = (windSpeed * 2.24);
|
|
gustSpeed = (gustSpeed * 2.24);
|
|
}
|
|
weatherInfo.windMPH = windSpeed;
|
|
weatherInfo.qsWindSpeed = i18n("1 MPH", "%n MPH", (int) windSpeed);
|
|
}
|
|
|
|
if (gustSpeed >= 1)
|
|
{
|
|
if (m_units == KLocale::Metric)
|
|
{
|
|
weatherInfo.qsCurrentList << i18n("Wind gusts up to 1 km/h",
|
|
"Wind gusts up to %n km/h", (int) gustSpeed);
|
|
}
|
|
else
|
|
{
|
|
weatherInfo.qsCurrentList << i18n("Wind gusts up to 1 MPH",
|
|
"Wind gusts up to %n MPH", (int) gustSpeed);
|
|
}
|
|
}
|
|
|
|
if ((WindRegExp.cap(1) != "VRB") && (windSpeed >= 1))
|
|
{
|
|
weatherInfo.qsWindDirection = parseWindDirection(direction);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MetarParser::parseStationNeedsMaintenance(const TQString &s)
|
|
{
|
|
if (s == "$")
|
|
{
|
|
weatherInfo.stationNeedsMaintenance = true;
|
|
kdDebug(12006) << "Station Needs Maintenance" << endl;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MetarParser::calcCurrentIcon()
|
|
{
|
|
// Default to overcast clouds
|
|
if ( weatherInfo.clouds == -1 )
|
|
weatherInfo.clouds = 64;
|
|
|
|
if (weatherInfo.theWeather.isEmpty())
|
|
{
|
|
if (weatherInfo.clouds == 0)
|
|
weatherInfo.theWeather = iconName("sunny");
|
|
else if (weatherInfo.clouds > 0 && weatherInfo.clouds <= 2)
|
|
weatherInfo.theWeather = iconName("cloudy1");
|
|
else if ( weatherInfo.clouds > 2 && weatherInfo.clouds <= 4)
|
|
weatherInfo.theWeather = iconName("cloudy2");
|
|
else if ( weatherInfo.clouds > 4 && weatherInfo.clouds <= 8)
|
|
weatherInfo.theWeather = iconName("cloudy3");
|
|
else if ( weatherInfo.clouds > 8 && weatherInfo.clouds < 63)
|
|
weatherInfo.theWeather = iconName( "cloudy4" );
|
|
else
|
|
weatherInfo.theWeather = "cloudy5";
|
|
}
|
|
else if (weatherInfo.theWeather == "tstorm")
|
|
{
|
|
if ( weatherInfo.heavy )
|
|
weatherInfo.clouds = 30;
|
|
|
|
if (weatherInfo.clouds >= 0 && weatherInfo.clouds <= 10)
|
|
weatherInfo.theWeather = iconName("tstorm1");
|
|
else if ( weatherInfo.clouds > 10 && weatherInfo.clouds <= 20)
|
|
weatherInfo.theWeather = iconName("tstorm2");
|
|
else
|
|
weatherInfo.theWeather = "tstorm3";
|
|
}
|
|
else if (weatherInfo.theWeather == "shower")
|
|
{
|
|
if ( weatherInfo.heavy )
|
|
weatherInfo.clouds = 30;
|
|
|
|
if (weatherInfo.clouds >= 0 && weatherInfo.clouds <= 10)
|
|
weatherInfo.theWeather = iconName("shower1");
|
|
else if ( weatherInfo.clouds > 10 && weatherInfo.clouds <= 20)
|
|
weatherInfo.theWeather = iconName("shower2");
|
|
else
|
|
weatherInfo.theWeather = "shower3";
|
|
}
|
|
else if (weatherInfo.theWeather == "snow")
|
|
{
|
|
if ( weatherInfo.heavy )
|
|
weatherInfo.clouds = 30;
|
|
|
|
if (weatherInfo.clouds >= 0 && weatherInfo.clouds <= 8)
|
|
weatherInfo.theWeather = iconName("snow1");
|
|
else if ( weatherInfo.clouds > 8 && weatherInfo.clouds <= 16)
|
|
weatherInfo.theWeather = iconName("snow2");
|
|
else if (weatherInfo.clouds > 16 && weatherInfo.clouds <= 24)
|
|
weatherInfo.theWeather = iconName("snow3");
|
|
else
|
|
weatherInfo.theWeather = "snow5";
|
|
}
|
|
else if (isNight(weatherInfo.reportLocation) && weatherInfo.theWeather == "mist")
|
|
weatherInfo.theWeather = "mist_night";
|
|
else if (isNight(weatherInfo.reportLocation) && weatherInfo.theWeather == "fog")
|
|
weatherInfo.theWeather = "fog_night";
|
|
else if ( weatherInfo.theWeather == "mist" || weatherInfo.theWeather == "fog" )
|
|
{
|
|
if ( weatherInfo.clouds >= 63 )
|
|
weatherInfo.theWeather = "cloudy5";
|
|
}
|
|
|
|
kdDebug(12006) << "Clouds: " << weatherInfo.clouds << ", Icon: "
|
|
<< weatherInfo.theWeather << endl;
|
|
}
|
|
|
|
void MetarParser::calcWindChill()
|
|
{
|
|
float windChill = 35.74 + (0.6215 * TEMP_C_TO_F(weatherInfo.tempC))
|
|
- (35.75 * ::pow(weatherInfo.windMPH, 0.16))
|
|
+ (0.4275 * TEMP_C_TO_F(weatherInfo.tempC) * ::pow(weatherInfo.windMPH, 0.16));
|
|
|
|
kdDebug(12006) << "Wind Chill: " << windChill << endl;
|
|
|
|
if (windChill < 48)
|
|
{
|
|
if (m_units == KLocale::Metric)
|
|
{
|
|
weatherInfo.qsWindChill.setNum(TEMP_F_TO_C(windChill), 'f', 1);
|
|
removeTrailingDotZero(weatherInfo.qsWindChill);
|
|
weatherInfo.qsWindChill += i18n("°C");
|
|
}
|
|
else
|
|
{
|
|
weatherInfo.qsWindChill.setNum(windChill, 'f', 1);
|
|
removeTrailingDotZero(weatherInfo.qsWindChill);
|
|
weatherInfo.qsWindChill += i18n("°F");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MetarParser::isNight(const TQString &stationID) const
|
|
{
|
|
TQString upperStationID = stationID.upper();
|
|
TQString latitude = m_stationDb->stationLatitudeFromID(upperStationID);
|
|
TQString longitude = m_stationDb->stationLongitudeFromID(upperStationID);
|
|
|
|
if ( latitude.compare( i18n("Unknown Station" ) ) == 0 ||
|
|
longitude.compare( i18n("Unknown Station" ) ) == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Sun theSun( latitude, longitude , m_date, m_localUTCOffset );
|
|
|
|
TQTime currently = m_time;
|
|
|
|
TQTime civilStart = theSun.computeCivilTwilightStart();
|
|
TQTime civilEnd = theSun.computeCivilTwilightEnd();
|
|
|
|
kdDebug (12006) << "station, current, lat, lon, start, end, offset: " <<
|
|
upperStationID << " " << currently << " " << latitude << " " <<
|
|
longitude << " " << civilStart << " " << civilEnd << " " <<
|
|
m_localUTCOffset << endl;
|
|
|
|
if (civilStart != civilEnd)
|
|
{
|
|
if (civilEnd < civilStart)
|
|
/* Handle daylight past midnight in local time */
|
|
/* for weather stations located at other timezones */
|
|
return (currently < civilStart && currently > civilEnd);
|
|
else
|
|
return (currently < civilStart || currently > civilEnd);
|
|
}
|
|
else
|
|
{
|
|
// Midnight Sun & Polar Night - In summer, the Sun is always
|
|
// over the horizon line ... so use latitude & today date to
|
|
// set isNight() value.
|
|
return ((m_date.daysInYear() >= 80 || m_date.daysInYear() <= 264) && latitude.contains("S"));
|
|
}
|
|
}
|
|
}
|
|
|
|
TQString MetarParser::iconName( const TQString &icon ) const
|
|
{
|
|
TQString _iconName = icon;
|
|
|
|
if ( isNight( weatherInfo.reportLocation ) )
|
|
_iconName += "_night";
|
|
|
|
return _iconName;
|
|
}
|
|
|