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.
782 lines
29 KiB
782 lines
29 KiB
/* |
|
SPDX-FileCopyrightText: 2009 Thilo-Alexander Ginkel <thilo@ginkel.com> |
|
|
|
Based upon BBC Weather Ion by Shawn Starr |
|
SPDX-FileCopyrightText: 2007-2009 Shawn Starr <shawn.starr@rogers.com> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
/* Ion for weather data from wetter.com */ |
|
|
|
// Sample URLs: |
|
// https://api.wetter.com/location/index/search/Heidelberg/project/weatherion/cs/9090dec6e783b96bd6a6ca9d451f3fee |
|
// https://api.wetter.com/forecast/weather/city/DE0004329/project/weatherion/cs/89f1264869cce5c6fd5a2db80051f3d8 |
|
|
|
#include "ion_wettercom.h" |
|
|
|
#include "ion_wettercomdebug.h" |
|
|
|
#include <KIO/Job> |
|
#include <KLocalizedString> |
|
#include <KUnitConversion/Converter> |
|
|
|
#include <QCryptographicHash> |
|
#include <QLocale> |
|
#include <QXmlStreamReader> |
|
|
|
/* |
|
* Initialization |
|
*/ |
|
|
|
WetterComIon::WetterComIon(QObject *parent, const QVariantList &args) |
|
: IonInterface(parent, args) |
|
|
|
{ |
|
#if defined(MIN_POLL_INTERVAL) |
|
setMinimumPollingInterval(MIN_POLL_INTERVAL); |
|
#endif |
|
setInitialized(true); |
|
} |
|
|
|
WetterComIon::~WetterComIon() |
|
{ |
|
cleanup(); |
|
} |
|
|
|
void WetterComIon::cleanup() |
|
{ |
|
// Clean up dynamically allocated forecasts |
|
QMutableHashIterator<QString, WeatherData> it(m_weatherData); |
|
while (it.hasNext()) { |
|
it.next(); |
|
WeatherData &item = it.value(); |
|
qDeleteAll(item.forecasts); |
|
item.forecasts.clear(); |
|
} |
|
} |
|
|
|
void WetterComIon::reset() |
|
{ |
|
cleanup(); |
|
m_sourcesToReset = sources(); |
|
updateAllSources(); |
|
} |
|
|
|
QMap<QString, IonInterface::ConditionIcons> WetterComIon::setupCommonIconMappings() const |
|
{ |
|
return QMap<QString, ConditionIcons>{ |
|
{QStringLiteral("3"), Overcast}, |
|
{QStringLiteral("30"), Overcast}, |
|
{QStringLiteral("4"), Haze}, |
|
{QStringLiteral("40"), Haze}, |
|
{QStringLiteral("45"), Haze}, |
|
{QStringLiteral("48"), Haze}, |
|
{QStringLiteral("49"), Haze}, |
|
{QStringLiteral("5"), Mist}, |
|
{QStringLiteral("50"), Mist}, |
|
{QStringLiteral("51"), Mist}, |
|
{QStringLiteral("53"), Mist}, |
|
{QStringLiteral("55"), Mist}, |
|
{QStringLiteral("56"), FreezingDrizzle}, |
|
{QStringLiteral("57"), FreezingDrizzle}, |
|
{QStringLiteral("6"), Rain}, |
|
{QStringLiteral("60"), LightRain}, |
|
{QStringLiteral("61"), LightRain}, |
|
{QStringLiteral("63"), Rain}, |
|
{QStringLiteral("65"), Rain}, |
|
{QStringLiteral("66"), FreezingRain}, |
|
{QStringLiteral("67"), FreezingRain}, |
|
{QStringLiteral("68"), RainSnow}, |
|
{QStringLiteral("69"), RainSnow}, |
|
{QStringLiteral("7"), Snow}, |
|
{QStringLiteral("70"), LightSnow}, |
|
{QStringLiteral("71"), LightSnow}, |
|
{QStringLiteral("73"), Snow}, |
|
{QStringLiteral("75"), Flurries}, |
|
{QStringLiteral("8"), Showers}, |
|
{QStringLiteral("81"), Showers}, |
|
{QStringLiteral("82"), Showers}, |
|
{QStringLiteral("83"), RainSnow}, |
|
{QStringLiteral("84"), RainSnow}, |
|
{QStringLiteral("85"), Snow}, |
|
{QStringLiteral("86"), Snow}, |
|
{QStringLiteral("9"), Thunderstorm}, |
|
{QStringLiteral("90"), Thunderstorm}, |
|
{QStringLiteral("96"), Thunderstorm}, |
|
{QStringLiteral("999"), NotAvailable}, |
|
}; |
|
} |
|
|
|
QMap<QString, IonInterface::ConditionIcons> WetterComIon::setupDayIconMappings() const |
|
{ |
|
QMap<QString, ConditionIcons> conditionList = setupCommonIconMappings(); |
|
|
|
conditionList.insert(QStringLiteral("0"), ClearDay); |
|
conditionList.insert(QStringLiteral("1"), FewCloudsDay); |
|
conditionList.insert(QStringLiteral("10"), FewCloudsDay); |
|
conditionList.insert(QStringLiteral("2"), PartlyCloudyDay); |
|
conditionList.insert(QStringLiteral("20"), PartlyCloudyDay); |
|
conditionList.insert(QStringLiteral("80"), ChanceShowersDay); |
|
conditionList.insert(QStringLiteral("95"), ChanceThunderstormDay); |
|
|
|
return conditionList; |
|
} |
|
|
|
QMap<QString, IonInterface::ConditionIcons> const &WetterComIon::dayIcons() const |
|
{ |
|
static QMap<QString, ConditionIcons> const val = setupDayIconMappings(); |
|
return val; |
|
} |
|
|
|
QMap<QString, IonInterface::ConditionIcons> WetterComIon::setupNightIconMappings() const |
|
{ |
|
QMap<QString, ConditionIcons> conditionList = setupCommonIconMappings(); |
|
|
|
conditionList.insert(QStringLiteral("0"), ClearNight); |
|
conditionList.insert(QStringLiteral("1"), FewCloudsNight); |
|
conditionList.insert(QStringLiteral("10"), FewCloudsNight); |
|
conditionList.insert(QStringLiteral("2"), PartlyCloudyNight); |
|
conditionList.insert(QStringLiteral("20"), PartlyCloudyNight); |
|
conditionList.insert(QStringLiteral("80"), ChanceShowersNight); |
|
conditionList.insert(QStringLiteral("95"), ChanceThunderstormNight); |
|
|
|
return conditionList; |
|
} |
|
|
|
QMap<QString, IonInterface::ConditionIcons> const &WetterComIon::nightIcons() const |
|
{ |
|
static QMap<QString, ConditionIcons> const val = setupNightIconMappings(); |
|
return val; |
|
} |
|
|
|
QHash<QString, QString> WetterComIon::setupCommonConditionMappings() const |
|
{ |
|
return QHash<QString, QString>{ |
|
{QStringLiteral("1"), i18nc("weather condition", "few clouds")}, |
|
{QStringLiteral("10"), i18nc("weather condition", "few clouds")}, |
|
{QStringLiteral("2"), i18nc("weather condition", "cloudy")}, |
|
{QStringLiteral("20"), i18nc("weather condition", "cloudy")}, |
|
{QStringLiteral("3"), i18nc("weather condition", "overcast")}, |
|
{QStringLiteral("30"), i18nc("weather condition", "overcast")}, |
|
{QStringLiteral("4"), i18nc("weather condition", "haze")}, |
|
{QStringLiteral("40"), i18nc("weather condition", "haze")}, |
|
{QStringLiteral("45"), i18nc("weather condition", "haze")}, |
|
{QStringLiteral("48"), i18nc("weather condition", "fog with icing")}, |
|
{QStringLiteral("49"), i18nc("weather condition", "fog with icing")}, |
|
{QStringLiteral("5"), i18nc("weather condition", "drizzle")}, |
|
{QStringLiteral("50"), i18nc("weather condition", "drizzle")}, |
|
{QStringLiteral("51"), i18nc("weather condition", "light drizzle")}, |
|
{QStringLiteral("53"), i18nc("weather condition", "drizzle")}, |
|
{QStringLiteral("55"), i18nc("weather condition", "heavy drizzle")}, |
|
{QStringLiteral("56"), i18nc("weather condition", "freezing drizzle")}, |
|
{QStringLiteral("57"), i18nc("weather condition", "heavy freezing drizzle")}, |
|
{QStringLiteral("6"), i18nc("weather condition", "rain")}, |
|
{QStringLiteral("60"), i18nc("weather condition", "light rain")}, |
|
{QStringLiteral("61"), i18nc("weather condition", "light rain")}, |
|
{QStringLiteral("63"), i18nc("weather condition", "moderate rain")}, |
|
{QStringLiteral("65"), i18nc("weather condition", "heavy rain")}, |
|
{QStringLiteral("66"), i18nc("weather condition", "light freezing rain")}, |
|
{QStringLiteral("67"), i18nc("weather condition", "freezing rain")}, |
|
{QStringLiteral("68"), i18nc("weather condition", "light rain snow")}, |
|
{QStringLiteral("69"), i18nc("weather condition", "heavy rain snow")}, |
|
{QStringLiteral("7"), i18nc("weather condition", "snow")}, |
|
{QStringLiteral("70"), i18nc("weather condition", "light snow")}, |
|
{QStringLiteral("71"), i18nc("weather condition", "light snow")}, |
|
{QStringLiteral("73"), i18nc("weather condition", "moderate snow")}, |
|
{QStringLiteral("75"), i18nc("weather condition", "heavy snow")}, |
|
{QStringLiteral("8"), i18nc("weather condition", "showers")}, |
|
{QStringLiteral("80"), i18nc("weather condition", "light showers")}, |
|
{QStringLiteral("81"), i18nc("weather condition", "showers")}, |
|
{QStringLiteral("82"), i18nc("weather condition", "heavy showers")}, |
|
{QStringLiteral("83"), i18nc("weather condition", "light snow rain showers")}, |
|
{QStringLiteral("84"), i18nc("weather condition", "heavy snow rain showers")}, |
|
{QStringLiteral("85"), i18nc("weather condition", "light snow showers")}, |
|
{QStringLiteral("86"), i18nc("weather condition", "snow showers")}, |
|
{QStringLiteral("9"), i18nc("weather condition", "thunderstorm")}, |
|
{QStringLiteral("90"), i18nc("weather condition", "thunderstorm")}, |
|
{QStringLiteral("95"), i18nc("weather condition", "light thunderstorm")}, |
|
{QStringLiteral("96"), i18nc("weather condition", "heavy thunderstorm")}, |
|
{QStringLiteral("999"), i18nc("weather condition", "n/a")}, |
|
}; |
|
} |
|
|
|
QHash<QString, QString> WetterComIon::setupDayConditionMappings() const |
|
{ |
|
QHash<QString, QString> conditionList = setupCommonConditionMappings(); |
|
conditionList.insert(QStringLiteral("0"), i18nc("weather condition", "sunny")); |
|
return conditionList; |
|
} |
|
|
|
QHash<QString, QString> const &WetterComIon::dayConditions() const |
|
{ |
|
static QHash<QString, QString> const val = setupDayConditionMappings(); |
|
return val; |
|
} |
|
|
|
QHash<QString, QString> WetterComIon::setupNightConditionMappings() const |
|
{ |
|
QHash<QString, QString> conditionList = setupCommonConditionMappings(); |
|
conditionList.insert(QStringLiteral("0"), i18nc("weather condition", "clear sky")); |
|
return conditionList; |
|
} |
|
|
|
QHash<QString, QString> const &WetterComIon::nightConditions() const |
|
{ |
|
static QHash<QString, QString> const val = setupNightConditionMappings(); |
|
return val; |
|
} |
|
|
|
QString WetterComIon::getWeatherCondition(const QHash<QString, QString> &conditionList, const QString &condition) const |
|
{ |
|
return conditionList[condition]; |
|
} |
|
|
|
bool WetterComIon::updateIonSource(const QString &source) |
|
{ |
|
// We expect the applet to send the source in the following tokenization: |
|
// ionname|validate|place_name|extra - Triggers validation of place |
|
// ionname|weather|place_name|extra - Triggers receiving weather of place |
|
|
|
const QStringList sourceAction = source.split(QLatin1Char('|')); |
|
|
|
if (sourceAction.size() < 3) { |
|
setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed")); |
|
return true; |
|
} |
|
|
|
if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() >= 3) { |
|
// Look for places to match |
|
findPlace(sourceAction[2], source); |
|
return true; |
|
} |
|
|
|
if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() >= 3) { |
|
if (sourceAction.count() >= 4) { |
|
if (sourceAction[2].isEmpty()) { |
|
setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed")); |
|
return true; |
|
} |
|
|
|
// Extra data format: placeCode;displayName |
|
const QStringList extraData = sourceAction[3].split(QLatin1Char(';')); |
|
|
|
if (extraData.count() != 2) { |
|
setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed")); |
|
return true; |
|
} |
|
|
|
m_place[sourceAction[2]].placeCode = extraData[0]; |
|
|
|
m_place[sourceAction[2]].displayName = extraData[1]; |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "About to retrieve forecast for source: " << sourceAction[2]; |
|
|
|
fetchForecast(sourceAction[2]); |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
setData(source, QStringLiteral("validate"), QStringLiteral("wettercom|malformed")); |
|
return true; |
|
} |
|
|
|
/* |
|
* Handling of place searches |
|
*/ |
|
|
|
void WetterComIon::findPlace(const QString &place, const QString &source) |
|
{ |
|
QCryptographicHash md5(QCryptographicHash::Md5); |
|
md5.addData(QByteArray(PROJECTNAME)); |
|
md5.addData(QByteArray(APIKEY)); |
|
md5.addData(place.toUtf8()); |
|
const QString encodedKey = QString::fromLatin1(md5.result().toHex()); |
|
|
|
const QUrl url(QStringLiteral(SEARCH_URL).arg(place, encodedKey)); |
|
|
|
KIO::TransferJob *getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); |
|
getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); // Disable displaying cookies |
|
m_searchJobXml.insert(getJob, new QXmlStreamReader); |
|
m_searchJobList.insert(getJob, source); |
|
|
|
connect(getJob, &KIO::TransferJob::data, this, &WetterComIon::setup_slotDataArrived); |
|
connect(getJob, &KJob::result, this, &WetterComIon::setup_slotJobFinished); |
|
} |
|
|
|
void WetterComIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data) |
|
{ |
|
QByteArray local = data; |
|
|
|
if (data.isEmpty() || !m_searchJobXml.contains(job)) { |
|
return; |
|
} |
|
|
|
m_searchJobXml[job]->addData(local); |
|
} |
|
|
|
void WetterComIon::setup_slotJobFinished(KJob *job) |
|
{ |
|
if (job->error() == KIO::ERR_SERVER_TIMEOUT) { |
|
setData(m_searchJobList[job], QStringLiteral("validate"), QStringLiteral("wettercom|timeout")); |
|
disconnectSource(m_searchJobList[job], this); |
|
m_searchJobList.remove(job); |
|
delete m_searchJobXml[job]; |
|
m_searchJobXml.remove(job); |
|
return; |
|
} |
|
|
|
QXmlStreamReader *reader = m_searchJobXml.value(job); |
|
|
|
if (reader) { |
|
parseSearchResults(m_searchJobList[job], *reader); |
|
} |
|
|
|
m_searchJobList.remove(job); |
|
|
|
delete m_searchJobXml[job]; |
|
m_searchJobXml.remove(job); |
|
} |
|
|
|
void WetterComIon::parseSearchResults(const QString &source, QXmlStreamReader &xml) |
|
{ |
|
QString name, code, quarter, state, country; |
|
|
|
while (!xml.atEnd()) { |
|
xml.readNext(); |
|
|
|
const QStringRef elementName = xml.name(); |
|
|
|
if (xml.isEndElement()) { |
|
if (elementName == QLatin1String("search")) { |
|
break; |
|
} else if (elementName == QLatin1String("item")) { |
|
// we parsed a place from the search result |
|
QString placeName; |
|
|
|
if (quarter.isEmpty()) { |
|
placeName = i18nc("Geographical location: city, state, ISO-country-code", "%1, %2, %3", name, state, country); |
|
} else { |
|
placeName = i18nc("Geographical location: quarter (city), state, ISO-country-code", "%1 (%2), %3, %4", quarter, name, state, country); |
|
} |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "Storing place data for place:" << placeName; |
|
|
|
PlaceInfo &place = m_place[placeName]; |
|
place.name = placeName; |
|
place.displayName = name; |
|
place.placeCode = code; |
|
m_locations.append(placeName); |
|
|
|
name.clear(); |
|
code.clear(); |
|
quarter.clear(); |
|
country.clear(); |
|
state.clear(); |
|
} |
|
} |
|
|
|
if (xml.isStartElement()) { |
|
if (elementName == QLatin1String("name")) { |
|
name = xml.readElementText(); |
|
} else if (elementName == QLatin1String("city_code")) { |
|
code = xml.readElementText(); |
|
} else if (elementName == QLatin1String("quarter")) { |
|
quarter = xml.readElementText(); |
|
} else if (elementName == QLatin1String("adm_1_code")) { |
|
country = xml.readElementText(); |
|
} else if (elementName == QLatin1String("adm_2_name")) { |
|
state = xml.readElementText(); |
|
} |
|
} |
|
} |
|
|
|
validate(source, xml.error() != QXmlStreamReader::NoError); |
|
} |
|
|
|
void WetterComIon::validate(const QString &source, bool parseError) |
|
{ |
|
if (!m_locations.count() || parseError) { |
|
const QString invalidPlace = source.section(QLatin1Char('|'), 2, 2); |
|
|
|
if (m_place[invalidPlace].name.isEmpty()) { |
|
setData(source, QStringLiteral("validate"), QVariant(QLatin1String("wettercom|invalid|multiple|") + invalidPlace)); |
|
} |
|
|
|
m_locations.clear(); |
|
|
|
return; |
|
} |
|
|
|
QString placeList; |
|
for (const QString &place : qAsConst(m_locations)) { |
|
// Extra data format: placeCode;displayName |
|
placeList.append(QLatin1String("|place|") + place + QLatin1String("|extra|") + m_place[place].placeCode + QLatin1Char(';') |
|
+ m_place[place].displayName); |
|
} |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "Returning place list:" << placeList; |
|
|
|
if (m_locations.count() > 1) { |
|
setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("wettercom|valid|multiple") + placeList)); |
|
} else { |
|
placeList[7] = placeList[7].toUpper(); |
|
setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("wettercom|valid|single") + placeList)); |
|
} |
|
|
|
m_locations.clear(); |
|
} |
|
|
|
/* |
|
* Handling of forecasts |
|
*/ |
|
|
|
void WetterComIon::fetchForecast(const QString &source) |
|
{ |
|
for (const QString &fetching : qAsConst(m_forecastJobList)) { |
|
if (fetching == source) { |
|
// already fetching! |
|
return; |
|
} |
|
} |
|
|
|
QCryptographicHash md5(QCryptographicHash::Md5); |
|
md5.addData(QByteArray(PROJECTNAME)); |
|
md5.addData(QByteArray(APIKEY)); |
|
md5.addData(m_place[source].placeCode.toUtf8()); |
|
const QString encodedKey = QString::fromLatin1(md5.result().toHex()); |
|
|
|
const QUrl url(QStringLiteral(FORECAST_URL).arg(m_place[source].placeCode, encodedKey)); |
|
|
|
KIO::TransferJob *getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); |
|
getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); |
|
m_forecastJobXml.insert(getJob, new QXmlStreamReader); |
|
m_forecastJobList.insert(getJob, source); |
|
|
|
connect(getJob, &KIO::TransferJob::data, this, &WetterComIon::forecast_slotDataArrived); |
|
connect(getJob, &KJob::result, this, &WetterComIon::forecast_slotJobFinished); |
|
} |
|
|
|
void WetterComIon::forecast_slotDataArrived(KIO::Job *job, const QByteArray &data) |
|
{ |
|
QByteArray local = data; |
|
|
|
if (data.isEmpty() || !m_forecastJobXml.contains(job)) { |
|
return; |
|
} |
|
|
|
m_forecastJobXml[job]->addData(local); |
|
} |
|
|
|
void WetterComIon::forecast_slotJobFinished(KJob *job) |
|
{ |
|
const QString source(m_forecastJobList.value(job)); |
|
setData(source, Data()); |
|
QXmlStreamReader *reader = m_forecastJobXml.value(job); |
|
|
|
if (reader) { |
|
parseWeatherForecast(source, *reader); |
|
} |
|
|
|
m_forecastJobList.remove(job); |
|
|
|
delete m_forecastJobXml[job]; |
|
m_forecastJobXml.remove(job); |
|
|
|
if (m_sourcesToReset.contains(source)) { |
|
m_sourcesToReset.removeAll(source); |
|
const QString weatherSource = QStringLiteral("wettercom|weather|%1|%2;%3").arg(source, m_place[source].placeCode, m_place[source].displayName); |
|
|
|
// so the weather engine updates it's data |
|
forceImmediateUpdateOfAllVisualizations(); |
|
|
|
// update the clients of our engine |
|
emit forceUpdate(this, weatherSource); |
|
} |
|
} |
|
|
|
void WetterComIon::parseWeatherForecast(const QString &source, QXmlStreamReader &xml) |
|
{ |
|
qCDebug(IONENGINE_WETTERCOM) << "About to parse forecast for source:" << source; |
|
|
|
WeatherData &weatherData = m_weatherData[source]; |
|
|
|
// Clear old forecasts when updating |
|
weatherData.forecasts.clear(); |
|
|
|
WeatherData::ForecastPeriod *forecastPeriod = new WeatherData::ForecastPeriod; |
|
WeatherData::ForecastInfo *forecast = new WeatherData::ForecastInfo; |
|
int summaryWeather = -1, summaryProbability = 0; |
|
int tempMax = -273, tempMin = 100, weather = -1, probability = 0; |
|
uint summaryUtcTime = 0, utcTime = 0, localTime = 0; |
|
QString date, time; |
|
|
|
weatherData.place = source; |
|
|
|
while (!xml.atEnd()) { |
|
xml.readNext(); |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "parsing xml elem: " << xml.name(); |
|
|
|
const QStringRef elementName = xml.name(); |
|
|
|
if (xml.isEndElement()) { |
|
if (elementName == QLatin1String("city")) { |
|
break; |
|
} |
|
if (elementName == QLatin1String("date")) { |
|
// we have parsed a complete day |
|
|
|
forecastPeriod->period = QDateTime::fromSecsSinceEpoch(summaryUtcTime, Qt::LocalTime); |
|
QString weatherString = QString::number(summaryWeather); |
|
forecastPeriod->iconName = getWeatherIcon(dayIcons(), weatherString); |
|
forecastPeriod->summary = getWeatherCondition(dayConditions(), weatherString); |
|
forecastPeriod->probability = summaryProbability; |
|
|
|
weatherData.forecasts.append(forecastPeriod); |
|
forecastPeriod = new WeatherData::ForecastPeriod; |
|
|
|
date.clear(); |
|
summaryWeather = -1; |
|
summaryProbability = 0; |
|
summaryUtcTime = 0; |
|
} else if (elementName == QLatin1String("time")) { |
|
// we have parsed one forecast |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "Parsed a forecast interval:" << date << time; |
|
|
|
// yep, that field is written to more often than needed... |
|
weatherData.timeDifference = localTime - utcTime; |
|
|
|
forecast->period = QDateTime::fromSecsSinceEpoch(utcTime, Qt::LocalTime); |
|
QString weatherString = QString::number(weather); |
|
forecast->tempHigh = tempMax; |
|
forecast->tempLow = tempMin; |
|
forecast->probability = probability; |
|
|
|
QTime localWeatherTime = QDateTime::fromSecsSinceEpoch(utcTime, Qt::LocalTime).time(); |
|
localWeatherTime = localWeatherTime.addSecs(weatherData.timeDifference); |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "localWeatherTime =" << localWeatherTime; |
|
|
|
// TODO use local sunset/sunrise time |
|
|
|
if (localWeatherTime.hour() < 20 && localWeatherTime.hour() > 6) { |
|
forecast->iconName = getWeatherIcon(dayIcons(), weatherString); |
|
forecast->summary = getWeatherCondition(dayConditions(), weatherString); |
|
forecastPeriod->dayForecasts.append(forecast); |
|
} else { |
|
forecast->iconName = getWeatherIcon(nightIcons(), weatherString); |
|
forecast->summary = getWeatherCondition(nightConditions(), weatherString); |
|
forecastPeriod->nightForecasts.append(forecast); |
|
} |
|
|
|
forecast = new WeatherData::ForecastInfo; |
|
|
|
tempMax = -273; |
|
tempMin = 100; |
|
weather = -1; |
|
probability = 0; |
|
utcTime = localTime = 0; |
|
time.clear(); |
|
} |
|
} |
|
|
|
if (xml.isStartElement()) { |
|
if (elementName == QLatin1String("date")) { |
|
date = xml.attributes().value(QStringLiteral("value")).toString(); |
|
} else if (elementName == QLatin1String("time")) { |
|
time = xml.attributes().value(QStringLiteral("value")).toString(); |
|
} else if (elementName == QLatin1String("tx")) { |
|
tempMax = qRound(xml.readElementText().toDouble()); |
|
qCDebug(IONENGINE_WETTERCOM) << "parsed t_max:" << tempMax; |
|
} else if (elementName == QLatin1String("tn")) { |
|
tempMin = qRound(xml.readElementText().toDouble()); |
|
qCDebug(IONENGINE_WETTERCOM) << "parsed t_min:" << tempMin; |
|
} else if (elementName == QLatin1Char('w')) { |
|
int tmp = xml.readElementText().toInt(); |
|
|
|
if (!time.isEmpty()) |
|
weather = tmp; |
|
else |
|
summaryWeather = tmp; |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "parsed weather condition:" << tmp; |
|
} else if (elementName == QLatin1String("name")) { |
|
weatherData.stationName = xml.readElementText(); |
|
qCDebug(IONENGINE_WETTERCOM) << "parsed station name:" << weatherData.stationName; |
|
} else if (elementName == QLatin1String("pc")) { |
|
int tmp = xml.readElementText().toInt(); |
|
|
|
if (!time.isEmpty()) |
|
probability = tmp; |
|
else |
|
summaryProbability = tmp; |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "parsed probability:" << probability; |
|
} else if (elementName == QLatin1String("text")) { |
|
weatherData.credits = xml.readElementText(); |
|
qCDebug(IONENGINE_WETTERCOM) << "parsed credits:" << weatherData.credits; |
|
} else if (elementName == QLatin1String("link")) { |
|
weatherData.creditsUrl = xml.readElementText(); |
|
qCDebug(IONENGINE_WETTERCOM) << "parsed credits url:" << weatherData.creditsUrl; |
|
} else if (elementName == QLatin1Char('d')) { |
|
localTime = xml.readElementText().toInt(); |
|
qCDebug(IONENGINE_WETTERCOM) << "parsed local time:" << localTime; |
|
} else if (elementName == QLatin1String("du")) { |
|
int tmp = xml.readElementText().toInt(); |
|
|
|
if (!time.isEmpty()) |
|
utcTime = tmp; |
|
else |
|
summaryUtcTime = tmp; |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "parsed UTC time:" << tmp; |
|
} |
|
} |
|
} |
|
|
|
delete forecast; |
|
delete forecastPeriod; |
|
|
|
updateWeather(source, xml.error() != QXmlStreamReader::NoError); |
|
} |
|
|
|
void WetterComIon::updateWeather(const QString &source, bool parseError) |
|
{ |
|
qCDebug(IONENGINE_WETTERCOM) << "Source:" << source; |
|
|
|
const PlaceInfo &placeInfo = m_place[source]; |
|
|
|
QString weatherSource = QStringLiteral("wettercom|weather|%1|%2;%3").arg(source, placeInfo.placeCode, placeInfo.displayName); |
|
|
|
const WeatherData &weatherData = m_weatherData[source]; |
|
|
|
Plasma::DataEngine::Data data; |
|
data.insert(QStringLiteral("Place"), placeInfo.displayName); |
|
|
|
if (!parseError && !weatherData.forecasts.isEmpty()) { |
|
data.insert(QStringLiteral("Station"), placeInfo.displayName); |
|
// data.insert("Condition Icon", "N/A"); |
|
// data.insert("Temperature", "N/A"); |
|
data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Celsius); |
|
|
|
int i = 0; |
|
for (const WeatherData::ForecastPeriod *forecastPeriod : weatherData.forecasts) { |
|
if (i > 0) { |
|
WeatherData::ForecastInfo weather = forecastPeriod->getWeather(); |
|
|
|
data.insert(QStringLiteral("Short Forecast Day %1").arg(i), |
|
QStringLiteral("%1|%2|%3|%4|%5|%6") |
|
.arg(QLocale().toString(weather.period.date().day()), weather.iconName, weather.summary) |
|
.arg(weather.tempHigh) |
|
.arg(weather.tempLow) |
|
.arg(weather.probability)); |
|
i++; |
|
} else { |
|
WeatherData::ForecastInfo dayWeather = forecastPeriod->getDayWeather(); |
|
|
|
data.insert(QStringLiteral("Short Forecast Day %1").arg(i), |
|
QStringLiteral("%1|%2|%3|%4|%5|%6") |
|
.arg(i18n("Day"), dayWeather.iconName, dayWeather.summary) |
|
.arg(dayWeather.tempHigh) |
|
.arg(dayWeather.tempLow) |
|
.arg(dayWeather.probability)); |
|
i++; |
|
|
|
if (forecastPeriod->hasNightWeather()) { |
|
WeatherData::ForecastInfo nightWeather = forecastPeriod->getNightWeather(); |
|
data.insert(QStringLiteral("Short Forecast Day %1").arg(i), |
|
QStringLiteral("%1 nt|%2|%3|%4|%5|%6") |
|
.arg(i18n("Night"), nightWeather.iconName, nightWeather.summary) |
|
.arg(nightWeather.tempHigh) |
|
.arg(nightWeather.tempLow) |
|
.arg(nightWeather.probability)); |
|
i++; |
|
} |
|
} |
|
} |
|
|
|
// Set number of forecasts per day/night supported |
|
data.insert(QStringLiteral("Total Weather Days"), i); |
|
|
|
data.insert(QStringLiteral("Credit"), weatherData.credits); // FIXME i18n? |
|
data.insert(QStringLiteral("Credit Url"), weatherData.creditsUrl); |
|
|
|
qCDebug(IONENGINE_WETTERCOM) << "updated weather data:" << weatherSource << data; |
|
} else { |
|
qCDebug(IONENGINE_WETTERCOM) << "Something went wrong when parsing weather data for source:" << source; |
|
} |
|
|
|
setData(weatherSource, data); |
|
} |
|
|
|
/* |
|
* WeatherData::ForecastPeriod convenience methods |
|
*/ |
|
|
|
WeatherData::ForecastPeriod::~ForecastPeriod() |
|
{ |
|
qDeleteAll(dayForecasts); |
|
qDeleteAll(nightForecasts); |
|
} |
|
|
|
WeatherData::ForecastInfo WeatherData::ForecastPeriod::getDayWeather() const |
|
{ |
|
WeatherData::ForecastInfo result; |
|
result.period = period; |
|
result.iconName = iconName; |
|
result.summary = summary; |
|
result.tempHigh = getMaxTemp(dayForecasts); |
|
result.tempLow = getMinTemp(dayForecasts); |
|
result.probability = probability; |
|
return result; |
|
} |
|
|
|
WeatherData::ForecastInfo WeatherData::ForecastPeriod::getNightWeather() const |
|
{ |
|
qCDebug(IONENGINE_WETTERCOM) << "nightForecasts.size() =" << nightForecasts.size(); |
|
|
|
// TODO do not just pick the first night forecast |
|
return *(nightForecasts.at(0)); |
|
} |
|
|
|
bool WeatherData::ForecastPeriod::hasNightWeather() const |
|
{ |
|
return !nightForecasts.isEmpty(); |
|
} |
|
|
|
WeatherData::ForecastInfo WeatherData::ForecastPeriod::getWeather() const |
|
{ |
|
WeatherData::ForecastInfo result = getDayWeather(); |
|
result.tempHigh = std::max(result.tempHigh, getMaxTemp(nightForecasts)); |
|
result.tempLow = std::min(result.tempLow, getMinTemp(nightForecasts)); |
|
return result; |
|
} |
|
|
|
int WeatherData::ForecastPeriod::getMaxTemp(const QVector<WeatherData::ForecastInfo *> &forecastInfos) const |
|
{ |
|
int result = -273; |
|
for (const WeatherData::ForecastInfo *forecast : forecastInfos) { |
|
result = std::max(result, forecast->tempHigh); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
int WeatherData::ForecastPeriod::getMinTemp(const QVector<WeatherData::ForecastInfo *> &forecastInfos) const |
|
{ |
|
int result = 100; |
|
for (const WeatherData::ForecastInfo *forecast : forecastInfos) { |
|
result = std::min(result, forecast->tempLow); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
K_PLUGIN_CLASS_WITH_JSON(WetterComIon, "ion-wettercom.json") |
|
|
|
#include "ion_wettercom.moc"
|
|
|