/*************************************************************************** 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 : (C) 2002-2004 Ian Geiser 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. * * * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif // HAVE_CONFIG_H #include #include #include #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; }