diff --git a/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp b/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp index 8cf260932..dfe05669d 100644 --- a/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp +++ b/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp @@ -1,972 +1,972 @@ /*************************************************************************** * Copyright (C) 2007-2009 by Shawn Starr * * * * 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. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ /* Ion for BBC's Weather from the UK Met Office */ #include "ion_bbcukmet.h" #include "ion_bbcukmetdebug.h" #include #include #include #include #include #include #include #include WeatherData::WeatherData() : stationLatitude(qQNaN()) , stationLongitude(qQNaN()) , condition() , temperature_C(qQNaN()) , windSpeed_miles(qQNaN()) , humidity(qQNaN()) , pressure(qQNaN()) { } WeatherData::ForecastInfo::ForecastInfo() : tempHigh(qQNaN()) , tempLow(qQNaN()) , windSpeed(qQNaN()) { } // ctor, dtor UKMETIon::UKMETIon(QObject *parent, const QVariantList &args) : IonInterface(parent, args) { setInitialized(true); } UKMETIon::~UKMETIon() { deleteForecasts(); } void UKMETIon::reset() { deleteForecasts(); m_sourcesToReset = sources(); updateAllSources(); } void UKMETIon::deleteForecasts() { // Destroy each forecast stored in a QVector QHash::iterator it = m_weatherData.begin(), end = m_weatherData.end(); for (; it != end; ++it) { qDeleteAll(it.value().forecasts); it.value().forecasts.clear(); } } QMap UKMETIon::setupDayIconMappings() const { // ClearDay, FewCloudsDay, PartlyCloudyDay, Overcast, // Showers, ScatteredShowers, Thunderstorm, Snow, // FewCloudsNight, PartlyCloudyNight, ClearNight, // Mist, NotAvailable return QMap { { QStringLiteral("sunny"), ClearDay }, // { QStringLiteral("sunny night"), ClearNight }, { QStringLiteral("clear"), ClearDay }, { QStringLiteral("clear sky"), ClearDay }, { QStringLiteral("sunny intervals"), PartlyCloudyDay }, // { QStringLiteral("sunny intervals night"), ClearNight }, { QStringLiteral("light cloud"), PartlyCloudyDay }, { QStringLiteral("partly cloudy"), PartlyCloudyDay }, { QStringLiteral("cloudy"), PartlyCloudyDay }, { QStringLiteral("white cloud"), PartlyCloudyDay }, { QStringLiteral("grey cloud"), Overcast }, { QStringLiteral("thick cloud"), Overcast }, // { QStringLiteral("low level cloud"), NotAvailable }, // { QStringLiteral("medium level cloud"), NotAvailable }, // { QStringLiteral("sandstorm"), NotAvailable }, { QStringLiteral("drizzle"), LightRain }, { QStringLiteral("misty"), Mist }, { QStringLiteral("mist"), Mist }, { QStringLiteral("fog"), Mist }, { QStringLiteral("foggy"), Mist }, { QStringLiteral("tropical storm"), Thunderstorm }, { QStringLiteral("hazy"), NotAvailable }, { QStringLiteral("light shower"), Showers }, { QStringLiteral("light rain shower"), Showers }, { QStringLiteral("light showers"), Showers }, { QStringLiteral("light rain"), Showers }, { QStringLiteral("heavy rain"), Rain }, { QStringLiteral("heavy showers"), Rain }, { QStringLiteral("heavy shower"), Rain }, { QStringLiteral("heavy rain shower"), Rain }, { QStringLiteral("thundery shower"), Thunderstorm }, { QStringLiteral("thunderstorm"), Thunderstorm }, { QStringLiteral("cloudy with sleet"), RainSnow }, { QStringLiteral("sleet shower"), RainSnow }, { QStringLiteral("sleet showers"), RainSnow }, { QStringLiteral("sleet"), RainSnow }, { QStringLiteral("cloudy with hail"), Hail }, { QStringLiteral("hail shower"), Hail }, { QStringLiteral("hail showers"), Hail }, { QStringLiteral("hail"), Hail }, { QStringLiteral("light snow"), LightSnow }, { QStringLiteral("light snow shower"), Flurries }, { QStringLiteral("light snow showers"), Flurries }, { QStringLiteral("cloudy with light snow"), LightSnow }, { QStringLiteral("heavy snow"), Snow }, { QStringLiteral("heavy snow shower"), Snow }, { QStringLiteral("heavy snow showers"), Snow }, { QStringLiteral("cloudy with heavy snow"), Snow }, { QStringLiteral("na"), NotAvailable }, }; } QMap UKMETIon::setupNightIconMappings() const { return QMap { { QStringLiteral("clear"), ClearNight }, { QStringLiteral("clear sky"), ClearNight }, { QStringLiteral("clear intervals"), PartlyCloudyNight }, { QStringLiteral("sunny intervals"), PartlyCloudyDay }, // it's not really sunny { QStringLiteral("sunny"), ClearDay }, { QStringLiteral("light cloud"), PartlyCloudyNight }, { QStringLiteral("partly cloudy"), PartlyCloudyNight }, { QStringLiteral("cloudy"), PartlyCloudyNight }, { QStringLiteral("white cloud"), PartlyCloudyNight }, { QStringLiteral("grey cloud"), Overcast }, { QStringLiteral("thick cloud"), Overcast }, { QStringLiteral("drizzle"), LightRain }, { QStringLiteral("misty"), Mist }, { QStringLiteral("mist"), Mist }, { QStringLiteral("fog"), Mist }, { QStringLiteral("foggy"), Mist }, { QStringLiteral("tropical storm"), Thunderstorm }, { QStringLiteral("hazy"), NotAvailable }, { QStringLiteral("light shower"), Showers }, { QStringLiteral("light rain shower"), Showers }, { QStringLiteral("light showers"), Showers }, { QStringLiteral("light rain"), Showers }, { QStringLiteral("heavy rain"), Rain }, { QStringLiteral("heavy showers"), Rain }, { QStringLiteral("heavy shower"), Rain }, { QStringLiteral("heavy rain shower"), Rain }, { QStringLiteral("thundery shower"), Thunderstorm }, { QStringLiteral("thunderstorm"), Thunderstorm }, { QStringLiteral("cloudy with sleet"), RainSnow }, { QStringLiteral("sleet shower"), RainSnow }, { QStringLiteral("sleet showers"), RainSnow }, { QStringLiteral("sleet"), RainSnow }, { QStringLiteral("cloudy with hail"), Hail }, { QStringLiteral("hail shower"), Hail }, { QStringLiteral("hail showers"), Hail }, { QStringLiteral("hail"), Hail }, { QStringLiteral("light snow"), LightSnow }, { QStringLiteral("light snow shower"), Flurries }, { QStringLiteral("light snow showers"), Flurries }, { QStringLiteral("cloudy with light snow"), LightSnow }, { QStringLiteral("heavy snow"), Snow }, { QStringLiteral("heavy snow shower"), Snow }, { QStringLiteral("heavy snow showers"), Snow }, { QStringLiteral("cloudy with heavy snow"), Snow }, { QStringLiteral("na"), NotAvailable }, }; } QMap UKMETIon::setupWindIconMappings() const { return QMap { { QStringLiteral("northerly"), N }, { QStringLiteral("north north easterly"), NNE }, { QStringLiteral("north easterly"), NE }, { QStringLiteral("east north easterly"), ENE }, { QStringLiteral("easterly"), E }, { QStringLiteral("east south easterly"), ESE }, { QStringLiteral("south easterly"), SE }, { QStringLiteral("south south easterly"), SSE }, { QStringLiteral("southerly"), S }, { QStringLiteral("south south westerly"), SSW }, { QStringLiteral("south westerly"), SW }, { QStringLiteral("west south westerly"), WSW }, { QStringLiteral("westerly"), W }, { QStringLiteral("west north westerly"), WNW }, { QStringLiteral("north westerly"), NW }, { QStringLiteral("north north westerly"), NNW }, { QStringLiteral("calm"), VR }, }; } QMap const& UKMETIon::dayIcons() const { static QMap const dval = setupDayIconMappings(); return dval; } QMap const& UKMETIon::nightIcons() const { static QMap const nval = setupNightIconMappings(); return nval; } QMap const& UKMETIon::windIcons() const { static QMap const wval = setupWindIconMappings(); return wval; } // Get a specific Ion's data bool UKMETIon::updateIonSource(const QString& source) { // We expect the applet to send the source in the following tokenization: // ionname|validate|place_name - Triggers validation of place // ionname|weather|place_name - Triggers receiving weather of place const QStringList sourceAction = source.split(QLatin1Char('|')); // Guard: if the size of array is not 3 then we have bad data, return an error if (sourceAction.size() < 3) { setData(source, QStringLiteral("validate"), QStringLiteral("bbcukmet|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() >= 3) { if (sourceAction[2].isEmpty()) { setData(source, QStringLiteral("validate"), QStringLiteral("bbcukmet|malformed")); return true; } m_place[QStringLiteral("bbcukmet|") +sourceAction[2]].XMLurl = sourceAction[3]; getXMLData(sourceAction[0] + QLatin1Char('|') + sourceAction[2]); return true; } return false; } setData(source, QStringLiteral("validate"), QStringLiteral("bbcukmet|malformed")); return true; } // Gets specific city XML data void UKMETIon::getXMLData(const QString& source) { - foreach (const QString &fetching, m_obsJobList) { + for (const QString& fetching : qAsConst(m_obsJobList)) { if (fetching == source) { // already getting this source and awaiting the data return; } } const QUrl url(m_place[source].XMLurl); KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); // Disable displaying cookies m_obsJobXml.insert(getJob, new QXmlStreamReader); m_obsJobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &UKMETIon::observation_slotDataArrived); connect(getJob, &KJob::result, this, &UKMETIon::observation_slotJobFinished); } // Parses city list and gets the correct city based on ID number void UKMETIon::findPlace(const QString& place, const QString& source) { /* There's a page= parameter, results are limited to 10 by page */ const QUrl url(QLatin1String("http://www.bbc.com/locator/default/en-GB/search.json?search=")+place+ QLatin1String("&filter=international&postcode_unit=false&postcode_district=true")); KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); // Disable displaying cookies m_jobHtml.insert(getJob, new QByteArray()); m_jobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &UKMETIon::setup_slotDataArrived); connect(getJob, &KJob::result, this, &UKMETIon::setup_slotJobFinished); /* // Handle redirects for direct hit places. connect(getJob, SIGNAL(redirection(KIO::Job*,KUrl)), this, SLOT(setup_slotRedirected(KIO::Job*,KUrl))); */ } void UKMETIon::getFiveDayForecast(const QString& source) { XMLMapInfo& place = m_place[source]; QUrl xmlMap(place.forecastHTMLUrl); const QString stationID = xmlMap.path().section(QLatin1Char('/'), -1); place.XMLforecastURL = QStringLiteral("http://open.live.bbc.co.uk/weather/feeds/en/") + stationID + QStringLiteral("/3dayforecast.rss") + xmlMap.query(); const QUrl url(place.XMLforecastURL); KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); // Disable displaying cookies m_forecastJobXml.insert(getJob, new QXmlStreamReader); m_forecastJobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &UKMETIon::forecast_slotDataArrived); connect(getJob, &KJob::result, this, &UKMETIon::forecast_slotJobFinished); } void UKMETIon::readSearchHTMLData(const QString& source, const QByteArray& html) { int counter = 2; QJsonObject jsonDocumentObject = QJsonDocument::fromJson(html).object(); if (!jsonDocumentObject.isEmpty()) { - QJsonArray results = jsonDocumentObject.value(QStringLiteral("results")).toArray(); + const QJsonArray results = jsonDocumentObject.value(QStringLiteral("results")).toArray(); - foreach(const QJsonValue & resultValue, results) { + for (const QJsonValue& resultValue : results) { QJsonObject result = resultValue.toObject(); const QString id = result.value(QStringLiteral("id")).toString(); const QString fullName = result.value(QStringLiteral("fullName")).toString(); if (!id.isEmpty() && !fullName.isEmpty()) { const QString url = QStringLiteral("http://open.live.bbc.co.uk/weather/feeds/en/") + id + QStringLiteral("/observations.rss"); QString tmp = QStringLiteral("bbcukmet|") + fullName; // Duplicate places can exist if (m_locations.contains(tmp)) { tmp += QStringLiteral(" (#") + QString::number(counter) + QLatin1Char(')'); counter++; } XMLMapInfo& place = m_place[tmp]; place.XMLurl = url; place.place = fullName; m_locations.append(tmp); } } } validate(source); } // handle when no XML tag is found void UKMETIon::parseUnknownElement(QXmlStreamReader& xml) const { while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { parseUnknownElement(xml); } } } void UKMETIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data) { if (data.isEmpty() || !m_jobHtml.contains(job)) { return; } m_jobHtml[job]->append(data); } void UKMETIon::setup_slotJobFinished(KJob *job) { if (job->error() == KIO::ERR_SERVER_TIMEOUT) { setData(m_jobList[job], QStringLiteral("validate"), QStringLiteral("bbcukmet|timeout")); disconnectSource(m_jobList[job], this); m_jobList.remove(job); delete m_jobHtml[job]; m_jobHtml.remove(job); return; } // If Redirected, don't go to this routine if (!m_locations.contains(QStringLiteral("bbcukmet|") + m_jobList[job])) { QByteArray *reader = m_jobHtml.value(job); if (reader) { readSearchHTMLData(m_jobList[job], *reader); } } m_jobList.remove(job); delete m_jobHtml[job]; m_jobHtml.remove(job); } void UKMETIon::observation_slotDataArrived(KIO::Job *job, const QByteArray &data) { QByteArray local = data; if (data.isEmpty() || !m_obsJobXml.contains(job)) { return; } // Send to xml. m_obsJobXml[job]->addData(local); } void UKMETIon::observation_slotJobFinished(KJob *job) { const QString source = m_obsJobList.value(job); setData(source, Data()); QXmlStreamReader *reader = m_obsJobXml.value(job); if (reader) { readObservationXMLData(m_obsJobList[job], *reader); } m_obsJobList.remove(job); delete m_obsJobXml[job]; m_obsJobXml.remove(job); if (m_sourcesToReset.contains(source)) { m_sourcesToReset.removeAll(source); emit forceUpdate(this, source); } } void UKMETIon::forecast_slotDataArrived(KIO::Job *job, const QByteArray &data) { QByteArray local = data; if (data.isEmpty() || !m_forecastJobXml.contains(job)) { return; } // Send to xml. m_forecastJobXml[job]->addData(local); } void UKMETIon::forecast_slotJobFinished(KJob *job) { setData(m_forecastJobList[job], Data()); QXmlStreamReader *reader = m_forecastJobXml.value(job); if (reader) { readFiveDayForecastXMLData(m_forecastJobList[job], *reader); } m_forecastJobList.remove(job); delete m_forecastJobXml[job]; m_forecastJobXml.remove(job); } void UKMETIon::parsePlaceObservation(const QString &source, WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("rss")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("rss")) { break; } if (xml.isStartElement() && elementName == QLatin1String("channel")) { parseWeatherChannel(source, data, xml); } } } void UKMETIon::parsePlaceForecast(const QString &source, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("rss")); while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement() && xml.name() == QLatin1String("channel")) { parseWeatherForecast(source, xml); } } } void UKMETIon::parseWeatherChannel(const QString& source, WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("channel")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("channel")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("title")) { data.stationName = xml.readElementText().section(QStringLiteral("Observations for"), 1, 1).trimmed(); data.stationName.replace(QStringLiteral("United Kingdom"), i18n("UK")); data.stationName.replace(QStringLiteral("United States of America"), i18n("USA")); } else if (elementName == QLatin1String("item")) { parseWeatherObservation(source, data, xml); } else { parseUnknownElement(xml); } } } } void UKMETIon::parseWeatherForecast(const QString& source, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("channel")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("channel")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("item")) { parseFiveDayForecast(source, xml); } else { parseUnknownElement(xml); } } } } void UKMETIon::parseWeatherObservation(const QString& source, WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("item")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("item")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("title")) { QString conditionString = xml.readElementText(); // Get the observation time and condition int splitIndex = conditionString.lastIndexOf(QLatin1Char(':')); if (splitIndex >= 0) { QString conditionData = conditionString.mid(splitIndex + 1); // Skip ':' data.obsTime = conditionString.left(splitIndex); if (data.obsTime.contains(QLatin1Char('-'))) { // Saturday - 13:00 CET // Saturday - 12:00 GMT // timezone parsing is not yet supported by QDateTime, also is there just a dayname // so try manually // guess date from day const QString dayString = data.obsTime.section(QLatin1Char('-'), 0, 0).trimmed(); QDate date = QDate::currentDate(); const QString dayFormat = QStringLiteral("dddd"); const int testDayJumps[4] = { -1, // first to weekday yesterday 2, // then to weekday tomorrow -3, // then to weekday before yesterday, not sure if such day offset can happen? 4, // then to weekday after tomorrow, not sure if such day offset can happen? }; const int dayJumps = sizeof(testDayJumps)/sizeof(testDayJumps[0]); QLocale cLocale = QLocale::c(); int dayJump = 0; while (true) { if (cLocale.toString(date, dayFormat) == dayString) { break; } if (dayJump >= dayJumps) { // no weekday found near-by, set date invalid date = QDate(); break; } date = date.addDays(testDayJumps[dayJump]); ++dayJump; } if (date.isValid()) { const QString timeString = data.obsTime.section(QLatin1Char('-'), 1, 1).trimmed(); const QTime time = QTime::fromString(timeString.section(QLatin1Char(' '), 0, 0), QStringLiteral("hh:mm")); const QTimeZone timeZone = QTimeZone(timeString.section(QLatin1Char(' '), 1, 1).toUtf8()); // TODO: if non-IANA timezone id is not known, try to guess timezone from other data if (time.isValid() && timeZone.isValid()) { data.observationDateTime = QDateTime(date, time, timeZone); } } } if (conditionData.contains(QLatin1Char(','))) { data.condition = conditionData.section(QLatin1Char(','), 0, 0).trimmed(); if (data.condition == QLatin1String("null")) { data.condition.clear(); } } } } else if (elementName == QLatin1String("link")) { m_place[source].forecastHTMLUrl = xml.readElementText(); } else if (elementName == QLatin1String("description")) { QString observeString = xml.readElementText(); const QStringList observeData = observeString.split(QLatin1Char(':')); // FIXME: We should make this use a QRegExp but I need some help here :) -spstarr QString temperature_C = observeData[1].section(QChar(176), 0, 0).trimmed(); parseFloat(data.temperature_C, temperature_C); data.windDirection = observeData[2].section(QLatin1Char(','), 0, 0).trimmed(); if (data.windDirection.contains(QStringLiteral("null"))) { data.windDirection.clear(); } QString windSpeed_miles = observeData[3].section(QLatin1Char(','), 0, 0).section(QLatin1Char(' '),1 ,1).remove(QStringLiteral("mph")); parseFloat(data.windSpeed_miles, windSpeed_miles); QString humidity = observeData[4].section(QLatin1Char(','), 0, 0).section(QLatin1Char(' '),1 ,1); if (humidity.endsWith(QLatin1Char('%'))) { humidity.chop(1); } parseFloat(data.humidity, humidity); QString pressure = observeData[5].section(QLatin1Char(','), 0, 0).section(QLatin1Char(' '),1 ,1).section(QStringLiteral("mb"), 0, 0); parseFloat(data.pressure, pressure); data.pressureTendency = observeData[5].section(QLatin1Char(','), 1, 1).toLower().trimmed(); if (data.pressureTendency == QLatin1String("no change")) { data.pressureTendency = QStringLiteral("steady"); } data.visibilityStr = observeData[6].trimmed(); } else if (elementName == QLatin1String("lat")) { const QString ordinate = xml.readElementText(); data.stationLatitude = ordinate.toDouble(); } else if (elementName == QLatin1String("long")) { const QString ordinate = xml.readElementText(); data.stationLongitude = ordinate.toDouble(); } else if (elementName == QLatin1String("point") && xml.namespaceUri() == QLatin1String("http://www.georss.org/georss")) { const QStringList ordinates = xml.readElementText().split(QLatin1Char(' ')); data.stationLatitude = ordinates[0].toDouble(); data.stationLongitude = ordinates[1].toDouble(); } else { parseUnknownElement(xml); } } } } bool UKMETIon::readObservationXMLData(const QString& source, QXmlStreamReader& xml) { WeatherData data; bool haveObservation = false; while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { if (xml.name() == QLatin1String("rss")) { parsePlaceObservation(source, data, xml); haveObservation = true; } else { parseUnknownElement(xml); } } } if (!haveObservation) { return false; } m_weatherData[source] = data; // Get the 5 day forecast info next. getFiveDayForecast(source); return !xml.error(); } bool UKMETIon::readFiveDayForecastXMLData(const QString& source, QXmlStreamReader& xml) { bool haveFiveDay = false; while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { if (xml.name() == QLatin1String("rss")) { parsePlaceForecast(source, xml); haveFiveDay = true; } else { parseUnknownElement(xml); } } } if (!haveFiveDay) return false; updateWeather(source); return !xml.error(); } void UKMETIon::parseFiveDayForecast(const QString& source, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("item")); QVector& forecasts = m_weatherData[source].forecasts; // Flush out the old forecasts when updating. forecasts.clear(); WeatherData::ForecastInfo *forecast = new WeatherData::ForecastInfo; QString line; QString period; QString summary; QRegExp high(QStringLiteral("Maximum Temperature: (-?\\d+).C"), Qt::CaseInsensitive); QRegExp low(QStringLiteral("Minimum Temperature: (-?\\d+).C"), Qt::CaseInsensitive); while (!xml.atEnd()) { xml.readNext(); if (xml.name() == QLatin1String("title")) { line = xml.readElementText().trimmed(); // FIXME: We should make this all use QRegExps in UKMETIon::parseFiveDayForecast() for forecast -spstarr const QString p = line.section(QLatin1Char(','), 0, 0); period = p.section(QLatin1Char(':'), 0, 0); summary = p.section(QLatin1Char(':'), 1, 1).trimmed(); const QString temps = line.section(QLatin1Char(','), 1, 1); // Sometimes only one of min or max are reported if (high.indexIn(temps) != -1) { parseFloat(forecast->tempHigh, high.cap(1)); } if (low.indexIn(temps) != -1) { parseFloat(forecast->tempLow, low.cap(1)); } const QString summaryLC = summary.toLower(); forecast->period = period; forecast->iconName = getWeatherIcon(dayIcons(), summaryLC); // db uses original strings normalized to lowercase, but we prefer the unnormalized if without translation const QString summaryTranslated = i18nc("weather forecast", summaryLC.toUtf8().data()); forecast->summary = (summaryTranslated != summaryLC) ? summaryTranslated : summary; qCDebug(IONENGINE_BBCUKMET) << "i18n summary string: " << forecast->summary; forecasts.append(forecast); // prepare next forecast = new WeatherData::ForecastInfo; } } // remove unused delete forecast; } void UKMETIon::parseFloat(float& value, const QString& string) { bool ok = false; const float result = string.toFloat(&ok); if (ok) { value = result; } } void UKMETIon::validate(const QString& source) { if (m_locations.isEmpty()) { const QString invalidPlace = source.section(QLatin1Char('|'), 2, 2); if (m_place[QStringLiteral("bbcukmet|")+invalidPlace].place.isEmpty()) { setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("bbcukmet|invalid|multiple|") + invalidPlace)); } return; } QString placeList; - foreach(const QString &place, m_locations) { + for (const QString& place : qAsConst(m_locations)) { const QString p = place.section(QLatin1Char('|'), 1, 1); placeList.append(QStringLiteral("|place|") + p + QStringLiteral("|extra|") + m_place[place].XMLurl); } if (m_locations.count() > 1) { setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("bbcukmet|valid|multiple") + placeList)); } else { placeList[7] = placeList[7].toUpper(); setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("bbcukmet|valid|single") + placeList)); } m_locations.clear(); } void UKMETIon::updateWeather(const QString& source) { const XMLMapInfo& place = m_place[source]; QString weatherSource = source; // TODO: why the replacement here instead of just a new string? weatherSource.replace(QStringLiteral("bbcukmet|"), QStringLiteral("bbcukmet|weather|")); weatherSource.append(QLatin1Char('|') + place.XMLurl); const WeatherData& weatherData = m_weatherData[source]; Plasma::DataEngine::Data data; data.insert(QStringLiteral("Place"), weatherData.stationName); data.insert(QStringLiteral("Station"), weatherData.stationName); if (!weatherData.obsTime.isEmpty()) { data.insert(QStringLiteral("Observation Period"), weatherData.obsTime); } if (!weatherData.condition.isEmpty()) { // db uses original strings normalized to lowercase, but we prefer the unnormalized if without translation const QString conditionLC = weatherData.condition.toLower(); const QString conditionTranslated = i18nc("weather condition", conditionLC.toUtf8().data()); data.insert(QStringLiteral("Current Conditions"), (conditionTranslated != conditionLC) ? conditionTranslated : weatherData.condition); } // qCDebug(IONENGINE_BBCUKMET) << "i18n condition string: " << i18nc("weather condition", weatherData.condition.toUtf8().data()); const bool stationCoordsValid = (!qIsNaN(weatherData.stationLatitude) && !qIsNaN(weatherData.stationLongitude)); if (stationCoordsValid) { data.insert(QStringLiteral("Latitude"), weatherData.stationLatitude); data.insert(QStringLiteral("Longitude"), weatherData.stationLongitude); } bool useDayIcon = true; // TODO: get timeengine's solarsystem code to use directly #if 0 if (weatherData.observationDateTime.isValid() && stationCoordsValid) { PlasmaWeather::Sun sun; sun.setPosition(weatherData.stationLatitude, weatherData.stationLongitude); const int offset = weatherData.observationDateTime.timeZone().offsetFromUtc(weatherData.observationDateTime); sun.calcForDateTime(weatherData.observationDateTime, offset); const auto elevation = sun.calcElevation(); // Tell applet which icon to use for conditions and provide mapping for condition type to the icons to display useDayIcon = (elevation >= 0.0); } #endif data.insert(QStringLiteral("Condition Icon"), getWeatherIcon(useDayIcon ? dayIcons() : nightIcons(), weatherData.condition)); if (!qIsNaN(weatherData.humidity)) { data.insert(QStringLiteral("Humidity"), weatherData.humidity); data.insert(QStringLiteral("Humidity Unit"), KUnitConversion::Percent); } if (!weatherData.visibilityStr.isEmpty()) { data.insert(QStringLiteral("Visibility"), i18nc("visibility", weatherData.visibilityStr.toUtf8().data())); data.insert(QStringLiteral("Visibility Unit"), KUnitConversion::NoUnit); } if (!qIsNaN(weatherData.temperature_C)) { data.insert(QStringLiteral("Temperature"), weatherData.temperature_C); } // Used for all temperatures data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Celsius); if (!qIsNaN(weatherData.pressure)) { data.insert(QStringLiteral("Pressure"), weatherData.pressure); data.insert(QStringLiteral("Pressure Unit"), KUnitConversion::Millibar); if (!weatherData.pressureTendency.isEmpty()) { data.insert(QStringLiteral("Pressure Tendency"), weatherData.pressureTendency); } } if (!qIsNaN(weatherData.windSpeed_miles)) { data.insert(QStringLiteral("Wind Speed"), weatherData.windSpeed_miles); data.insert(QStringLiteral("Wind Speed Unit"), KUnitConversion::MilePerHour); if (!weatherData.windDirection.isEmpty()) { data.insert(QStringLiteral("Wind Direction"), getWindDirectionIcon(windIcons(), weatherData.windDirection.toLower())); } } // 5 Day forecast info const QVector & forecasts = weatherData.forecasts; // Set number of forecasts per day/night supported data.insert(QStringLiteral("Total Weather Days"), forecasts.size()); int i = 0; - foreach(const WeatherData::ForecastInfo *forecastInfo, forecasts) { + for (const WeatherData::ForecastInfo* forecastInfo : forecasts) { QString period = forecastInfo->period; period.replace(QStringLiteral("Saturday"), i18nc("Short for Saturday", "Sat")); period.replace(QStringLiteral("Sunday"), i18nc("Short for Sunday", "Sun")); period.replace(QStringLiteral("Monday"), i18nc("Short for Monday", "Mon")); period.replace(QStringLiteral("Tuesday"), i18nc("Short for Tuesday", "Tue")); period.replace(QStringLiteral("Wednesday"), i18nc("Short for Wednesday", "Wed")); period.replace(QStringLiteral("Thursday"), i18nc("Short for Thursday", "Thu")); period.replace(QStringLiteral("Friday"), i18nc("Short for Friday", "Fri")); const QString tempHigh = qIsNaN(forecastInfo->tempHigh) ? QString() : QString::number(forecastInfo->tempHigh); const QString tempLow = qIsNaN(forecastInfo->tempLow) ? QString() : QString::number(forecastInfo->tempLow); data.insert(QStringLiteral("Short Forecast Day %1").arg(i), QStringLiteral("%1|%2|%3|%4|%5|%6").arg( period, forecastInfo->iconName, forecastInfo->summary, tempHigh, tempLow, QString())); //.arg(forecastInfo->windSpeed) //arg(forecastInfo->windDirection)); ++i; } data.insert(QStringLiteral("Credit"), i18nc("credit line, keep string short", "Data from BBC\302\240Weather")); data.insert(QStringLiteral("Credit Url"), place.forecastHTMLUrl); setData(weatherSource, data); } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(bbcukmet, UKMETIon, "ion-bbcukmet.json") #include "ion_bbcukmet.moc" diff --git a/dataengines/weather/ions/envcan/ion_envcan.cpp b/dataengines/weather/ions/envcan/ion_envcan.cpp index 3cda1c483..d6fad5c15 100644 --- a/dataengines/weather/ions/envcan/ion_envcan.cpp +++ b/dataengines/weather/ions/envcan/ion_envcan.cpp @@ -1,1628 +1,1628 @@ /*************************************************************************** * Copyright (C) 2007-2011 by Shawn Starr * * * * 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. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ /* Ion for Environment Canada XML data */ #include "ion_envcan.h" #include "ion_envcandebug.h" #include #include #include #include #include WeatherData::WeatherData() : stationLatitude(qQNaN()) , stationLongitude(qQNaN()) , temperature(qQNaN()) , dewpoint(qQNaN()) , windchill(qQNaN()) , pressure(qQNaN()) , visibility(qQNaN()) , humidity(qQNaN()) , windSpeed(qQNaN()) , windGust(qQNaN()) , normalHigh(qQNaN()) , normalLow(qQNaN()) , prevHigh(qQNaN()) , prevLow(qQNaN()) , recordHigh(qQNaN()) , recordLow(qQNaN()) , recordRain(qQNaN()) , recordSnow(qQNaN()) { } WeatherData::ForecastInfo::ForecastInfo() : tempHigh(qQNaN()) , tempLow(qQNaN()) , popPrecent(qQNaN()) { } // ctor, dtor EnvCanadaIon::EnvCanadaIon(QObject *parent, const QVariantList &args) : IonInterface(parent, args) { // Get the real city XML URL so we can parse this getXMLSetup(); } void EnvCanadaIon::deleteForecasts() { QMutableHashIterator it(m_weatherData); while (it.hasNext()) { it.next(); WeatherData &item = it.value(); qDeleteAll(item.warnings); item.warnings.clear(); qDeleteAll(item.watches); item.watches.clear(); qDeleteAll(item.forecasts); item.forecasts.clear(); } } void EnvCanadaIon::reset() { deleteForecasts(); emitWhenSetup = true; m_sourcesToReset = sources(); getXMLSetup(); } EnvCanadaIon::~EnvCanadaIon() { // Destroy each watch/warning stored in a QVector deleteForecasts(); } QMap EnvCanadaIon::setupConditionIconMappings() const { return QMap { // Explicit periods { QStringLiteral("mainly sunny"), FewCloudsDay }, { QStringLiteral("mainly clear"), FewCloudsNight }, { QStringLiteral("sunny"), ClearDay }, { QStringLiteral("clear"), ClearNight }, // Available conditions { QStringLiteral("blowing snow"), Snow }, { QStringLiteral("cloudy"), Overcast }, { QStringLiteral("distant precipitation"), LightRain }, { QStringLiteral("drifting snow"), Flurries }, { QStringLiteral("drizzle"), LightRain }, { QStringLiteral("dust"), NotAvailable }, { QStringLiteral("dust devils"), NotAvailable }, { QStringLiteral("fog"), Mist }, { QStringLiteral("fog bank near station"), Mist }, { QStringLiteral("fog depositing ice"), Mist }, { QStringLiteral("fog patches"), Mist }, { QStringLiteral("freezing drizzle"), FreezingDrizzle }, { QStringLiteral("freezing rain"), FreezingRain }, { QStringLiteral("funnel cloud"), NotAvailable }, { QStringLiteral("hail"), Hail }, { QStringLiteral("haze"), Haze }, { QStringLiteral("heavy blowing snow"), Snow }, { QStringLiteral("heavy drifting snow"), Snow }, { QStringLiteral("heavy drizzle"), LightRain }, { QStringLiteral("heavy hail"), Hail }, { QStringLiteral("heavy mixed rain and drizzle"), LightRain }, { QStringLiteral("heavy mixed rain and snow shower"), RainSnow }, { QStringLiteral("heavy rain"), Rain }, { QStringLiteral("heavy rain and snow"), RainSnow }, { QStringLiteral("heavy rainshower"), Rain }, { QStringLiteral("heavy snow"), Snow }, { QStringLiteral("heavy snow pellets"), Snow }, { QStringLiteral("heavy snowshower"), Snow }, { QStringLiteral("heavy thunderstorm with hail"), Thunderstorm }, { QStringLiteral("heavy thunderstorm with rain"), Thunderstorm }, { QStringLiteral("ice crystals"), Flurries }, { QStringLiteral("ice pellets"), Hail }, { QStringLiteral("increasing cloud"), Overcast }, { QStringLiteral("light drizzle"), LightRain }, { QStringLiteral("light freezing drizzle"), FreezingRain }, { QStringLiteral("light freezing rain"), FreezingRain }, { QStringLiteral("light rain"), LightRain }, { QStringLiteral("light rainshower"), LightRain }, { QStringLiteral("light snow"), LightSnow }, { QStringLiteral("light snow pellets"), LightSnow }, { QStringLiteral("light snowshower"), Flurries }, { QStringLiteral("lightning visible"), Thunderstorm }, { QStringLiteral("mist"), Mist }, { QStringLiteral("mixed rain and drizzle"), LightRain }, { QStringLiteral("mixed rain and snow shower"), RainSnow }, { QStringLiteral("not reported"), NotAvailable }, { QStringLiteral("rain"), Rain }, { QStringLiteral("rain and snow"), RainSnow }, { QStringLiteral("rainshower"), LightRain }, { QStringLiteral("recent drizzle"), LightRain }, { QStringLiteral("recent dust or sand storm"), NotAvailable }, { QStringLiteral("recent fog"), Mist }, { QStringLiteral("recent freezing precipitation"), FreezingDrizzle }, { QStringLiteral("recent hail"), Hail }, { QStringLiteral("recent rain"), Rain }, { QStringLiteral("recent rain and snow"), RainSnow }, { QStringLiteral("recent rainshower"), Rain }, { QStringLiteral("recent snow"), Snow }, { QStringLiteral("recent snowshower"), Flurries }, { QStringLiteral("recent thunderstorm"), Thunderstorm }, { QStringLiteral("recent thunderstorm with hail"), Thunderstorm }, { QStringLiteral("recent thunderstorm with heavy hail"), Thunderstorm }, { QStringLiteral("recent thunderstorm with heavy rain"), Thunderstorm }, { QStringLiteral("recent thunderstorm with rain"), Thunderstorm }, { QStringLiteral("sand or dust storm"), NotAvailable }, { QStringLiteral("severe sand or dust storm"), NotAvailable }, { QStringLiteral("shallow fog"), Mist }, { QStringLiteral("smoke"), NotAvailable }, { QStringLiteral("snow"), Snow }, { QStringLiteral("snow crystals"), Flurries }, { QStringLiteral("snow grains"), Flurries }, { QStringLiteral("squalls"), Snow }, { QStringLiteral("thunderstorm with hail"), Thunderstorm }, { QStringLiteral("thunderstorm with rain"), Thunderstorm }, { QStringLiteral("thunderstorm with sand or dust storm"), Thunderstorm }, { QStringLiteral("thunderstorm without precipitation"), Thunderstorm }, { QStringLiteral("tornado"), NotAvailable }, }; } QMap EnvCanadaIon::setupForecastIconMappings() const { return QMap { // Abbreviated forecast descriptions { QStringLiteral("a few flurries"), Flurries }, { QStringLiteral("a few flurries mixed with ice pellets"), RainSnow }, { QStringLiteral("a few flurries or rain showers"), RainSnow }, { QStringLiteral("a few flurries or thundershowers"), RainSnow }, { QStringLiteral("a few rain showers or flurries"), RainSnow }, { QStringLiteral("a few rain showers or wet flurries"), RainSnow }, { QStringLiteral("a few showers"), LightRain }, { QStringLiteral("a few showers or drizzle"), LightRain }, { QStringLiteral("a few showers or thundershowers"), Thunderstorm }, { QStringLiteral("a few showers or thunderstorms"), Thunderstorm }, { QStringLiteral("a few thundershowers"), Thunderstorm }, { QStringLiteral("a few thunderstorms"), Thunderstorm }, { QStringLiteral("a few wet flurries"), RainSnow }, { QStringLiteral("a few wet flurries or rain showers"), RainSnow }, { QStringLiteral("a mix of sun and cloud"), PartlyCloudyDay }, { QStringLiteral("cloudy with sunny periods"), PartlyCloudyDay }, { QStringLiteral("partly cloudy"), PartlyCloudyDay }, { QStringLiteral("mainly sunny"), FewCloudsDay }, { QStringLiteral("sunny"), ClearDay }, { QStringLiteral("blizzard"), Snow }, { QStringLiteral("clear"), ClearNight }, { QStringLiteral("cloudy"), Overcast }, { QStringLiteral("drizzle"), LightRain }, { QStringLiteral("drizzle mixed with freezing drizzle"), FreezingDrizzle }, { QStringLiteral("drizzle mixed with rain"), LightRain }, { QStringLiteral("drizzle or freezing drizzle"), LightRain }, { QStringLiteral("drizzle or rain"), LightRain }, { QStringLiteral("flurries"), Flurries }, { QStringLiteral("flurries at times heavy"), Flurries }, { QStringLiteral("flurries at times heavy or rain snowers"), RainSnow }, { QStringLiteral("flurries mixed with ice pellets"), FreezingRain }, { QStringLiteral("flurries or ice pellets"), FreezingRain }, { QStringLiteral("flurries or rain showers"), RainSnow }, { QStringLiteral("flurries or thundershowers"), Flurries }, { QStringLiteral("fog"), Mist }, { QStringLiteral("fog developing"), Mist }, { QStringLiteral("fog dissipating"), Mist }, { QStringLiteral("fog patches"), Mist }, { QStringLiteral("freezing drizzle"), FreezingDrizzle }, { QStringLiteral("freezing rain"), FreezingRain }, { QStringLiteral("freezing rain mixed with rain"), FreezingRain }, { QStringLiteral("freezing rain mixed with snow"), FreezingRain }, { QStringLiteral("freezing rain or ice pellets"), FreezingRain }, { QStringLiteral("freezing rain or rain"), FreezingRain }, { QStringLiteral("freezing rain or snow"), FreezingRain }, { QStringLiteral("ice fog"), Mist }, { QStringLiteral("ice fog developing"), Mist }, { QStringLiteral("ice fog dissipating"), Mist }, { QStringLiteral("ice pellet"), Hail }, { QStringLiteral("ice pellet mixed with freezing rain"), Hail }, { QStringLiteral("ice pellet mixed with snow"), Hail }, { QStringLiteral("ice pellet or snow"), RainSnow }, { QStringLiteral("light snow"), LightSnow }, { QStringLiteral("light snow and blizzard"), LightSnow }, { QStringLiteral("light snow and blizzard and blowing snow"), Snow }, { QStringLiteral("light snow and blowing snow"), LightSnow }, { QStringLiteral("light snow mixed with freezing drizzle"), FreezingDrizzle }, { QStringLiteral("light snow mixed with freezing rain"), FreezingRain }, { QStringLiteral("light snow or ice pellets"), LightSnow }, { QStringLiteral("light snow or rain"), RainSnow }, { QStringLiteral("light wet snow"), RainSnow }, { QStringLiteral("light wet snow or rain"), RainSnow }, { QStringLiteral("local snow squalls"), Snow }, { QStringLiteral("near blizzard"), Snow }, { QStringLiteral("overcast"), Overcast }, { QStringLiteral("increasing cloudiness"), Overcast }, { QStringLiteral("increasing clouds"), Overcast }, { QStringLiteral("periods of drizzle"), LightRain }, { QStringLiteral("periods of drizzle mixed with freezing drizzle"), FreezingDrizzle }, { QStringLiteral("periods of drizzle mixed with rain"), LightRain }, { QStringLiteral("periods of drizzle or freezing drizzle"), FreezingDrizzle }, { QStringLiteral("periods of drizzle or rain"), LightRain }, { QStringLiteral("periods of freezing drizzle"), FreezingDrizzle }, { QStringLiteral("periods of freezing drizzle or drizzle"), FreezingDrizzle }, { QStringLiteral("periods of freezing drizzle or rain"), FreezingDrizzle }, { QStringLiteral("periods of freezing rain"), FreezingRain }, { QStringLiteral("periods of freezing rain mixed with ice pellets"), FreezingRain }, { QStringLiteral("periods of freezing rain mixed with rain"), FreezingRain }, { QStringLiteral("periods of freezing rain mixed with snow"), FreezingRain }, { QStringLiteral("periods of freezing rain mixed with freezing drizzle"), FreezingRain }, { QStringLiteral("periods of freezing rain or ice pellets"), FreezingRain }, { QStringLiteral("periods of freezing rain or rain"), FreezingRain }, { QStringLiteral("periods of freezing rain or snow"), FreezingRain }, { QStringLiteral("periods of ice pellet"), Hail }, { QStringLiteral("periods of ice pellet mixed with freezing rain"), Hail }, { QStringLiteral("periods of ice pellet mixed with snow"), Hail }, { QStringLiteral("periods of ice pellet or freezing rain"), Hail }, { QStringLiteral("periods of ice pellet or snow"), Hail }, { QStringLiteral("periods of light snow"), LightSnow }, { QStringLiteral("periods of light snow and blizzard"), Snow }, { QStringLiteral("periods of light snow and blizzard and blowing snow"), Snow }, { QStringLiteral("periods of light snow and blowing snow"), LightSnow }, { QStringLiteral("periods of light snow mixed with freezing drizzle"), RainSnow }, { QStringLiteral("periods of light snow mixed with freezing rain"), RainSnow }, { QStringLiteral("periods of light snow mixed with ice pelletS"), LightSnow }, { QStringLiteral("periods of light snow mixed with rain"), RainSnow }, { QStringLiteral("periods of light snow or freezing drizzle"), RainSnow }, { QStringLiteral("periods of light snow or freezing rain"), RainSnow }, { QStringLiteral("periods of light snow or ice pellets"), LightSnow }, { QStringLiteral("periods of light snow or rain"), RainSnow }, { QStringLiteral("periods of light wet snow"), LightSnow }, { QStringLiteral("periods of light wet snow mixed with rain"), RainSnow }, { QStringLiteral("periods of light wet snow or rain"), RainSnow }, { QStringLiteral("periods of rain"), Rain }, { QStringLiteral("periods of rain mixed with freezing rain"), Rain }, { QStringLiteral("periods of rain mixed with snow"), RainSnow }, { QStringLiteral("periods of rain or drizzle"), Rain }, { QStringLiteral("periods of rain or freezing rain"), Rain }, { QStringLiteral("periods of rain or thundershowers"), Showers }, { QStringLiteral("periods of rain or thunderstorms"), Thunderstorm }, { QStringLiteral("periods of rain or snow"), RainSnow }, { QStringLiteral("periods of snow"), Snow }, { QStringLiteral("periods of snow and blizzard"), Snow }, { QStringLiteral("periods of snow and blizzard and blowing snow"), Snow }, { QStringLiteral("periods of snow and blowing snow"), Snow }, { QStringLiteral("periods of snow mixed with freezing drizzle"), RainSnow }, { QStringLiteral("periods of snow mixed with freezing rain"), RainSnow }, { QStringLiteral("periods of snow mixed with ice pellets"), Snow }, { QStringLiteral("periods of snow mixed with rain"), RainSnow }, { QStringLiteral("periods of snow or freezing drizzle"), RainSnow }, { QStringLiteral("periods of snow or freezing rain"), RainSnow }, { QStringLiteral("periods of snow or ice pellets"), Snow }, { QStringLiteral("periods of snow or rain"), RainSnow }, { QStringLiteral("periods of rain or snow"), RainSnow }, { QStringLiteral("periods of wet snow"), Snow }, { QStringLiteral("periods of wet snow mixed with rain"), RainSnow }, { QStringLiteral("periods of wet snow or rain"), RainSnow }, { QStringLiteral("rain"), Rain }, { QStringLiteral("rain at times heavy"), Rain }, { QStringLiteral("rain at times heavy mixed with freezing rain"), FreezingRain }, { QStringLiteral("rain at times heavy mixed with snow"), RainSnow }, { QStringLiteral("rain at times heavy or drizzle"), Rain }, { QStringLiteral("rain at times heavy or freezing rain"), Rain }, { QStringLiteral("rain at times heavy or snow"), RainSnow }, { QStringLiteral("rain at times heavy or thundershowers"), Showers }, { QStringLiteral("rain at times heavy or thunderstorms"), Thunderstorm }, { QStringLiteral("rain mixed with freezing rain"), FreezingRain }, { QStringLiteral("rain mixed with snow"), RainSnow }, { QStringLiteral("rain or drizzle"), Rain }, { QStringLiteral("rain or freezing rain"), Rain }, { QStringLiteral("rain or snow"), RainSnow }, { QStringLiteral("rain or thundershowers"), Showers }, { QStringLiteral("rain or thunderstorms"), Thunderstorm }, { QStringLiteral("rain showers or flurries"), RainSnow }, { QStringLiteral("rain showers or wet flurries"), RainSnow }, { QStringLiteral("showers"), Showers }, { QStringLiteral("showers at times heavy"), Showers }, { QStringLiteral("showers at times heavy or thundershowers"), Showers }, { QStringLiteral("showers at times heavy or thunderstorms"), Thunderstorm }, { QStringLiteral("showers or drizzle"), Showers }, { QStringLiteral("showers or thundershowers"), Thunderstorm }, { QStringLiteral("showers or thunderstorms"), Thunderstorm }, { QStringLiteral("smoke"), NotAvailable }, { QStringLiteral("snow"), Snow }, { QStringLiteral("snow and blizzard"), Snow }, { QStringLiteral("snow and blizzard and blowing snow"), Snow }, { QStringLiteral("snow and blowing snow"), Snow }, { QStringLiteral("snow at times heavy"), Snow }, { QStringLiteral("snow at times heavy and blizzard"), Snow }, { QStringLiteral("snow at times heavy and blowing snow"), Snow }, { QStringLiteral("snow at times heavy mixed with freezing drizzle"), RainSnow }, { QStringLiteral("snow at times heavy mixed with freezing rain"), RainSnow }, { QStringLiteral("snow at times heavy mixed with ice pellets"), Snow }, { QStringLiteral("snow at times heavy mixed with rain"), RainSnow }, { QStringLiteral("snow at times heavy or freezing rain"), RainSnow }, { QStringLiteral("snow at times heavy or ice pellets"), Snow }, { QStringLiteral("snow at times heavy or rain"), RainSnow }, { QStringLiteral("snow mixed with freezing drizzle"), RainSnow }, { QStringLiteral("snow mixed with freezing rain"), RainSnow }, { QStringLiteral("snow mixed with ice pellets"), Snow }, { QStringLiteral("snow mixed with rain"), RainSnow }, { QStringLiteral("snow or freezing drizzle"), RainSnow }, { QStringLiteral("snow or freezing rain"), RainSnow }, { QStringLiteral("snow or ice pellets"), Snow }, { QStringLiteral("snow or rain"), RainSnow }, { QStringLiteral("snow squalls"), Snow }, { QStringLiteral("sunny"), ClearDay }, { QStringLiteral("sunny with cloudy periods"), PartlyCloudyDay }, { QStringLiteral("thunderstorms"), Thunderstorm }, { QStringLiteral("thunderstorms and possible hail"), Thunderstorm }, { QStringLiteral("wet flurries"), Flurries }, { QStringLiteral("wet flurries at times heavy"), Flurries }, { QStringLiteral("wet flurries at times heavy or rain snowers"), RainSnow }, { QStringLiteral("wet flurries or rain showers"), RainSnow }, { QStringLiteral("wet snow"), Snow }, { QStringLiteral("wet snow at times heavy"), Snow }, { QStringLiteral("wet snow at times heavy mixed with rain"), RainSnow }, { QStringLiteral("wet snow mixed with rain"), RainSnow }, { QStringLiteral("wet snow or rain"), RainSnow }, { QStringLiteral("windy"), NotAvailable }, { QStringLiteral("chance of drizzle mixed with freezing drizzle"), LightRain }, { QStringLiteral("chance of flurries mixed with ice pellets"), Flurries }, { QStringLiteral("chance of flurries or ice pellets"), Flurries }, { QStringLiteral("chance of flurries or rain showers"), RainSnow }, { QStringLiteral("chance of flurries or thundershowers"), RainSnow }, { QStringLiteral("chance of freezing drizzle"), FreezingDrizzle }, { QStringLiteral("chance of freezing rain"), FreezingRain }, { QStringLiteral("chance of freezing rain mixed with snow"), RainSnow }, { QStringLiteral("chance of freezing rain or rain"), FreezingRain }, { QStringLiteral("chance of freezing rain or snow"), RainSnow }, { QStringLiteral("chance of light snow and blowing snow"), LightSnow }, { QStringLiteral("chance of light snow mixed with freezing drizzle"), LightSnow }, { QStringLiteral("chance of light snow mixed with ice pellets"), LightSnow }, { QStringLiteral("chance of light snow mixed with rain"), RainSnow }, { QStringLiteral("chance of light snow or freezing rain"), RainSnow }, { QStringLiteral("chance of light snow or ice pellets"), LightSnow }, { QStringLiteral("chance of light snow or rain"), RainSnow }, { QStringLiteral("chance of light wet snow"), Snow }, { QStringLiteral("chance of rain"), Rain }, { QStringLiteral("chance of rain at times heavy"), Rain }, { QStringLiteral("chance of rain mixed with snow"), RainSnow }, { QStringLiteral("chance of rain or drizzle"), Rain }, { QStringLiteral("chance of rain or freezing rain"), Rain }, { QStringLiteral("chance of rain or snow"), RainSnow }, { QStringLiteral("chance of rain showers or flurries"), RainSnow }, { QStringLiteral("chance of rain showers or wet flurries"), RainSnow }, { QStringLiteral("chance of severe thunderstorms"), Thunderstorm }, { QStringLiteral("chance of showers at times heavy"), Rain }, { QStringLiteral("chance of showers at times heavy or thundershowers"), Thunderstorm }, { QStringLiteral("chance of showers at times heavy or thunderstorms"), Thunderstorm }, { QStringLiteral("chance of showers or thundershowers"), Thunderstorm }, { QStringLiteral("chance of showers or thunderstorms"), Thunderstorm }, { QStringLiteral("chance of snow"), Snow }, { QStringLiteral("chance of snow and blizzard"), Snow }, { QStringLiteral("chance of snow mixed with freezing drizzle"), Snow }, { QStringLiteral("chance of snow mixed with freezing rain"), RainSnow }, { QStringLiteral("chance of snow mixed with rain"), RainSnow }, { QStringLiteral("chance of snow or rain"), RainSnow }, { QStringLiteral("chance of snow squalls"), Snow }, { QStringLiteral("chance of thundershowers"), Showers }, { QStringLiteral("chance of thunderstorms"), Thunderstorm }, { QStringLiteral("chance of thunderstorms and possible hail"), Thunderstorm }, { QStringLiteral("chance of wet flurries"), Flurries }, { QStringLiteral("chance of wet flurries at times heavy"), Flurries }, { QStringLiteral("chance of wet flurries or rain showers"), RainSnow }, { QStringLiteral("chance of wet snow"), Snow }, { QStringLiteral("chance of wet snow mixed with rain"), RainSnow }, { QStringLiteral("chance of wet snow or rain"), RainSnow }, }; } QMap const& EnvCanadaIon::conditionIcons() const { static QMap const condval = setupConditionIconMappings(); return condval; } QMap const& EnvCanadaIon::forecastIcons() const { static QMap const foreval = setupForecastIconMappings(); return foreval; } QStringList EnvCanadaIon::validate(const QString& source) const { QStringList placeList; QString sourceNormalized = source.toUpper(); QHash::const_iterator it = m_places.constBegin(); while (it != m_places.constEnd()) { if (it.key().toUpper().contains(sourceNormalized)) { placeList.append(QStringLiteral("place|") + it.key()); } ++it; } placeList.sort(); return placeList; } // Get a specific Ion's data bool EnvCanadaIon::updateIonSource(const QString& source) { //qCDebug(IONENGINE_ENVCAN) << "updateIonSource()" << source; // We expect the applet to send the source in the following tokenization: // ionname|validate|place_name - Triggers validation of place // ionname|weather|place_name - Triggers receiving weather of place const QStringList sourceAction = source.split(QLatin1Char('|')); // Guard: if the size of array is not 2 then we have bad data, return an error if (sourceAction.size() < 2) { setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed")); return true; } if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() > 2) { const QStringList result = validate(sourceAction[2]); const QString reply = (result.size() == 1) ? QStringLiteral("envcan|valid|single|") + result[0] : (result.size() > 1) ? QStringLiteral("envcan|valid|multiple|") + result.join(QLatin1Char('|')) : /*else*/ QStringLiteral("envcan|invalid|single|") + sourceAction[2]; setData(source, QStringLiteral("validate"), reply); return true; } if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() > 2) { getXMLData(source); return true; } setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed")); return true; } // Parses city list and gets the correct city based on ID number void EnvCanadaIon::getXMLSetup() { //qCDebug(IONENGINE_ENVCAN) << "getXMLSetup()"; // If network is down, we need to spin and wait const QUrl url(QStringLiteral("http://dd.weatheroffice.ec.gc.ca/citypage_weather/xml/siteList.xml")); KIO::TransferJob* getJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); m_xmlSetup.clear(); connect(getJob, &KIO::TransferJob::data, this, &EnvCanadaIon::setup_slotDataArrived); connect(getJob, &KJob::result, this, &EnvCanadaIon::setup_slotJobFinished); } // Gets specific city XML data void EnvCanadaIon::getXMLData(const QString& source) { - foreach (const QString& fetching, m_jobList) { + for (const QString& fetching : qAsConst(m_jobList)) { if (fetching == source) { // already getting this source and awaiting the data return; } } //qCDebug(IONENGINE_ENVCAN) << source; // Demunge source name for key only. QString dataKey = source; dataKey.remove(QStringLiteral("envcan|weather|")); const XMLMapInfo& place = m_places[dataKey]; const QUrl url(QLatin1String("http://dd.weatheroffice.ec.gc.ca/citypage_weather/xml/") + place.territoryName + QLatin1Char('/') + place.cityCode + QStringLiteral("_e.xml")); //url="file:///home/spstarr/Desktop/s0000649_e.xml"; //qCDebug(IONENGINE_ENVCAN) << "Will Try URL: " << url; if (place.territoryName.isEmpty() && place.cityCode.isEmpty()) { setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed")); return; } KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); m_jobXml.insert(getJob, new QXmlStreamReader); m_jobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &EnvCanadaIon::slotDataArrived); connect(getJob, &KJob::result, this, &EnvCanadaIon::slotJobFinished); } void EnvCanadaIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job) if (data.isEmpty()) { //qCDebug(IONENGINE_ENVCAN) << "done!"; return; } // Send to xml. //qCDebug(IONENGINE_ENVCAN) << data; m_xmlSetup.addData(data); } void EnvCanadaIon::slotDataArrived(KIO::Job *job, const QByteArray &data) { if (data.isEmpty() || !m_jobXml.contains(job)) { return; } // Send to xml. m_jobXml[job]->addData(data); } void EnvCanadaIon::slotJobFinished(KJob *job) { // Dual use method, if we're fetching location data to parse we need to do this first const QString source = m_jobList.value(job); //qCDebug(IONENGINE_ENVCAN) << source << m_sourcesToReset.contains(source); setData(source, Data()); QXmlStreamReader *reader = m_jobXml.value(job); if (reader) { readXMLData(m_jobList[job], *reader); } m_jobList.remove(job); delete m_jobXml[job]; m_jobXml.remove(job); if (m_sourcesToReset.contains(source)) { m_sourcesToReset.removeAll(source); // so the weather engine updates it's data forceImmediateUpdateOfAllVisualizations(); // update the clients of our engine emit forceUpdate(this, source); } } void EnvCanadaIon::setup_slotJobFinished(KJob *job) { Q_UNUSED(job) const bool success = readXMLSetup(); m_xmlSetup.clear(); //qCDebug(IONENGINE_ENVCAN) << success << m_sourcesToReset; setInitialized(success); } // Parse the city list and store into a QMap bool EnvCanadaIon::readXMLSetup() { bool success = false; QString territory; QString code; QString cityName; //qCDebug(IONENGINE_ENVCAN) << "readXMLSetup()"; while (!m_xmlSetup.atEnd()) { m_xmlSetup.readNext(); const QStringRef elementName = m_xmlSetup.name(); if (m_xmlSetup.isStartElement()) { // XML ID code to match filename if (elementName == QLatin1String("site")) { code = m_xmlSetup.attributes().value(QStringLiteral("code")).toString(); } if (elementName == QLatin1String("nameEn")) { cityName = m_xmlSetup.readElementText(); // Name of cities } if (elementName == QLatin1String("provinceCode")) { territory = m_xmlSetup.readElementText(); // Provinces/Territory list } } if (m_xmlSetup.isEndElement() && elementName == QLatin1String("site")) { EnvCanadaIon::XMLMapInfo info; QString tmp = cityName + QStringLiteral(", ") + territory; // Build the key name. // Set the mappings info.cityCode = code; info.territoryName = territory; info.cityName = cityName; // Set the string list, we will use for the applet to display the available cities. m_places[tmp] = info; success = true; } } return (success && !m_xmlSetup.error()); } void EnvCanadaIon::parseWeatherSite(WeatherData& data, QXmlStreamReader& xml) { while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (elementName == QLatin1String("license")) { xml.readElementText(); } else if (elementName == QLatin1String("location")) { parseLocations(data, xml); } else if (elementName == QLatin1String("warnings")) { // Cleanup warning list on update data.warnings.clear(); data.watches.clear(); parseWarnings(data, xml); } else if (elementName == QLatin1String("currentConditions")) { parseConditions(data, xml); } else if (elementName == QLatin1String("forecastGroup")) { // Clean up forecast list on update data.forecasts.clear(); parseWeatherForecast(data, xml); } else if (elementName == QLatin1String("yesterdayConditions")) { parseYesterdayWeather(data, xml); } else if (elementName == QLatin1String("riseSet")) { parseAstronomicals(data, xml); } else if (elementName == QLatin1String("almanac")) { parseWeatherRecords(data, xml); } else { parseUnknownElement(xml); } } } } // Parse Weather data main loop, from here we have to decend into each tag pair bool EnvCanadaIon::readXMLData(const QString& source, QXmlStreamReader& xml) { WeatherData data; //qCDebug(IONENGINE_ENVCAN) << "readXMLData()"; QString dataKey = source; dataKey.remove(QStringLiteral("envcan|weather|")); data.shortTerritoryName = m_places[dataKey].territoryName; while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { if (xml.name() == QLatin1String("siteData")) { parseWeatherSite(data, xml); } else { parseUnknownElement(xml); } } } m_weatherData[source] = data; updateWeather(source); return !xml.error(); } void EnvCanadaIon::parseFloat(float& value, QXmlStreamReader& xml) { bool ok = false; const float result = xml.readElementText().toFloat(&ok); if (ok) { value = result; } } void EnvCanadaIon::parseDateTime(WeatherData& data, QXmlStreamReader& xml, WeatherData::WeatherEvent *event) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("dateTime")); // What kind of date info is this? const QString dateType = xml.attributes().value(QStringLiteral("name")).toString(); const QString dateZone = xml.attributes().value(QStringLiteral("zone")).toString(); const QString dateUtcOffset = xml.attributes().value(QStringLiteral("UTCOffset")).toString(); QString selectTimeStamp; while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (dateType == QLatin1String("xmlCreation")) { return; } if (dateZone == QLatin1String("UTC")) { return; } if (elementName == QLatin1String("year")) { xml.readElementText(); } else if (elementName == QLatin1String("month")) { xml.readElementText(); } else if (elementName == QLatin1String("day")) { xml.readElementText(); } else if (elementName == QLatin1String("hour")) xml.readElementText(); else if (elementName == QLatin1String("minute")) xml.readElementText(); else if (elementName == QLatin1String("timeStamp")) selectTimeStamp = xml.readElementText(); else if (elementName == QLatin1String("textSummary")) { if (dateType == QLatin1String("eventIssue")) { if (event) { event->timestamp = xml.readElementText(); } } else if (dateType == QLatin1String("observation")) { xml.readElementText(); QDateTime observationDateTime = QDateTime::fromString(selectTimeStamp, QStringLiteral("yyyyMMddHHmmss")); QTimeZone timeZone = QTimeZone(dateZone.toUtf8()); // if timezone id not recognized, fallback to utcoffset if (!timeZone.isValid()) { timeZone = QTimeZone(dateUtcOffset.toInt() * 3600); } if (observationDateTime.isValid() && timeZone.isValid()) { data.observationDateTime = observationDateTime; data.observationDateTime.setTimeZone(timeZone); } data.obsTimestamp = observationDateTime.toString(QStringLiteral("dd.MM.yyyy @ hh:mm")); } else if (dateType == QLatin1String("forecastIssue")) { data.forecastTimestamp = xml.readElementText(); } else if (dateType == QLatin1String("sunrise")) { data.sunriseTimestamp = xml.readElementText(); } else if (dateType == QLatin1String("sunset")) { data.sunsetTimestamp = xml.readElementText(); } else if (dateType == QLatin1String("moonrise")) { data.moonriseTimestamp = xml.readElementText(); } else if (dateType == QLatin1String("moonset")) { data.moonsetTimestamp = xml.readElementText(); } } } } } void EnvCanadaIon::parseLocations(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("location")); while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (elementName == QLatin1String("country")) { data.countryName = xml.readElementText(); } else if (elementName == QLatin1String("province") || elementName == QLatin1String("territory")) { data.longTerritoryName = xml.readElementText(); } else if (elementName == QLatin1String("name")) { data.cityName = xml.readElementText(); } else if (elementName == QLatin1String("region")) { data.regionName = xml.readElementText(); } else { parseUnknownElement(xml); } } } } void EnvCanadaIon::parseWindInfo(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("wind")); while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (elementName == QLatin1String("speed")) { parseFloat(data.windSpeed, xml); } else if (elementName == QLatin1String("gust")) { parseFloat(data.windGust, xml); } else if (elementName == QLatin1String("direction")) { data.windDirection = xml.readElementText(); } else if (elementName == QLatin1String("bearing")) { data.windDegrees = xml.attributes().value(QStringLiteral("degrees")).toString(); } else { parseUnknownElement(xml); } } } } void EnvCanadaIon::parseConditions(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("currentConditions")); data.temperature = qQNaN(); data.dewpoint = qQNaN(); data.condition = i18n("N/A"); data.humidex.clear(); data.stationID = i18n("N/A"); data.stationLatitude = qQNaN(); data.stationLongitude = qQNaN(); data.pressure = qQNaN(); data.visibility = qQNaN(); data.humidity = qQNaN(); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("currentConditions")) break; if (xml.isStartElement()) { if (elementName == QLatin1String("station")) { data.stationID = xml.attributes().value(QStringLiteral("code")).toString(); QRegularExpression dumpDirection(QStringLiteral("[^0-9.]")); data.stationLatitude = xml.attributes().value(QStringLiteral("lat")).toString().remove(dumpDirection).toDouble(); data.stationLongitude = xml.attributes().value(QStringLiteral("lon")).toString().remove(dumpDirection).toDouble(); } else if (elementName == QLatin1String("dateTime")) { parseDateTime(data, xml); } else if (elementName == QLatin1String("condition")) { data.condition = xml.readElementText(); } else if (elementName == QLatin1String("temperature")) { // prevent N/A text to result in 0.0 value parseFloat(data.temperature, xml); } else if (elementName == QLatin1String("dewpoint")) { // prevent N/A text to result in 0.0 value parseFloat(data.dewpoint, xml); } else if (elementName == QLatin1String("humidex")) { data.humidex = xml.readElementText(); } else if (elementName == QLatin1String("windChill")) { // prevent N/A text to result in 0.0 value parseFloat(data.windchill, xml); } else if (elementName == QLatin1String("pressure")) { data.pressureTendency = xml.attributes().value(QStringLiteral("tendency")).toString(); if (data.pressureTendency.isEmpty()) { data.pressureTendency = QStringLiteral("steady"); } parseFloat(data.pressure, xml); } else if (elementName == QLatin1String("visibility")) { parseFloat(data.visibility, xml); } else if (elementName == QLatin1String("relativeHumidity")) { parseFloat(data.humidity, xml); } else if (elementName == QLatin1String("wind")) { parseWindInfo(data, xml); } //} else { // parseUnknownElement(xml); //} } } } void EnvCanadaIon::parseWarnings(WeatherData &data, QXmlStreamReader& xml) { WeatherData::WeatherEvent *watch = new WeatherData::WeatherEvent; WeatherData::WeatherEvent *warning = new WeatherData::WeatherEvent; Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("warnings")); QString eventURL = xml.attributes().value(QStringLiteral("url")).toString(); int flag = 0; while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("warnings")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("dateTime")) { if (flag == 1) { parseDateTime(data, xml, watch); } if (flag == 2) { parseDateTime(data, xml, warning); } if (!warning->timestamp.isEmpty() && !warning->url.isEmpty()) { data.warnings.append(warning); warning = new WeatherData::WeatherEvent; } if (!watch->timestamp.isEmpty() && !watch->url.isEmpty()) { data.watches.append(watch); watch = new WeatherData::WeatherEvent; } } else if (elementName == QLatin1String("event")) { // Append new event to list. QString eventType = xml.attributes().value(QStringLiteral("type")).toString(); if (eventType == QLatin1String("watch")) { watch->url = eventURL; watch->type = eventType; watch->priority = xml.attributes().value(QStringLiteral("priority")).toString(); watch->description = xml.attributes().value(QStringLiteral("description")).toString(); flag = 1; } if (eventType == QLatin1String("warning")) { warning->url = eventURL; warning->type = eventType; warning->priority = xml.attributes().value(QStringLiteral("priority")).toString(); warning->description = xml.attributes().value(QStringLiteral("description")).toString(); flag = 2; } } else { if (xml.name() != QLatin1String("dateTime")) { parseUnknownElement(xml); } } } } delete watch; delete warning; } void EnvCanadaIon::parseWeatherForecast(WeatherData& data, QXmlStreamReader& xml) { WeatherData::ForecastInfo* forecast = new WeatherData::ForecastInfo; Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("forecastGroup")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("forecastGroup")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("dateTime")) { parseDateTime(data, xml); } else if (elementName == QLatin1String("regionalNormals")) { parseRegionalNormals(data, xml); } else if (elementName == QLatin1String("forecast")) { parseForecast(data, xml, forecast); forecast = new WeatherData::ForecastInfo; } else { parseUnknownElement(xml); } } } delete forecast; } void EnvCanadaIon::parseRegionalNormals(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("regionalNormals")); while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (elementName == QLatin1String("textSummary")) { xml.readElementText(); } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) { // prevent N/A text to result in 0.0 value parseFloat(data.normalHigh, xml); } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) { // prevent N/A text to result in 0.0 value parseFloat(data.normalLow, xml); } } } } void EnvCanadaIon::parseForecast(WeatherData& data, QXmlStreamReader& xml, WeatherData::ForecastInfo *forecast) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("forecast")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("forecast")) { data.forecasts.append(forecast); break; } if (xml.isStartElement()) { if (elementName == QLatin1String("period")) { forecast->forecastPeriod = xml.attributes().value(QStringLiteral("textForecastName")).toString(); } else if (elementName == QLatin1String("textSummary")) { forecast->forecastSummary = xml.readElementText(); } else if (elementName == QLatin1String("abbreviatedForecast")) { parseShortForecast(forecast, xml); } else if (elementName == QLatin1String("temperatures")) { parseForecastTemperatures(forecast, xml); } else if (elementName == QLatin1String("winds")) { parseWindForecast(forecast, xml); } else if (elementName == QLatin1String("precipitation")) { parsePrecipitationForecast(forecast, xml); } else if (elementName == QLatin1String("uv")) { data.UVRating = xml.attributes().value(QStringLiteral("category")).toString(); parseUVIndex(data, xml); // else if (elementName == QLatin1String("frost")) { FIXME: Wait until winter to see what this looks like. // parseFrost(xml, forecast); } else { if (elementName != QLatin1String("forecast")) { parseUnknownElement(xml); } } } } } void EnvCanadaIon::parseShortForecast(WeatherData::ForecastInfo *forecast, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("abbreviatedForecast")); QString shortText; while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("abbreviatedForecast")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("pop")) { parseFloat(forecast->popPrecent, xml); } if (elementName == QLatin1String("textSummary")) { shortText = xml.readElementText(); QMap forecastList = forecastIcons(); if ((forecast->forecastPeriod == QLatin1String("tonight")) || (forecast->forecastPeriod.contains(QLatin1String("night")))) { forecastList.insert(QStringLiteral("a few clouds"), FewCloudsNight); forecastList.insert(QStringLiteral("cloudy periods"), PartlyCloudyNight); forecastList.insert(QStringLiteral("chance of drizzle mixed with rain"), ChanceShowersNight); forecastList.insert(QStringLiteral("chance of drizzle"), ChanceShowersNight); forecastList.insert(QStringLiteral("chance of drizzle or rain"), ChanceShowersNight); forecastList.insert(QStringLiteral("chance of flurries"), ChanceSnowNight); forecastList.insert(QStringLiteral("chance of light snow"), ChanceSnowNight); forecastList.insert(QStringLiteral("chance of flurries at times heavy"), ChanceSnowNight); forecastList.insert(QStringLiteral("chance of showers or drizzle"), ChanceShowersNight); forecastList.insert(QStringLiteral("chance of showers"), ChanceShowersNight); forecastList.insert(QStringLiteral("clearing"), ClearNight); } else { forecastList.insert(QStringLiteral("a few clouds"), FewCloudsDay); forecastList.insert(QStringLiteral("cloudy periods"), PartlyCloudyDay); forecastList.insert(QStringLiteral("chance of drizzle mixed with rain"), ChanceShowersDay); forecastList.insert(QStringLiteral("chance of drizzle"), ChanceShowersDay); forecastList.insert(QStringLiteral("chance of drizzle or rain"), ChanceShowersDay); forecastList.insert(QStringLiteral("chance of flurries"), ChanceSnowDay); forecastList.insert(QStringLiteral("chance of light snow"), ChanceSnowDay); forecastList.insert(QStringLiteral("chance of flurries at times heavy"), ChanceSnowDay); forecastList.insert(QStringLiteral("chance of showers or drizzle"), ChanceShowersDay); forecastList.insert(QStringLiteral("chance of showers"), ChanceShowersDay); forecastList.insert(QStringLiteral("clearing"), ClearDay); } forecast->shortForecast = shortText; forecast->iconName = getWeatherIcon(forecastList, shortText.toLower()); } } } } void EnvCanadaIon::parseUVIndex(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("uv")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("uv")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("index")) { data.UVIndex = xml.readElementText(); } if (elementName == QLatin1String("textSummary")) { xml.readElementText(); } } } } void EnvCanadaIon::parseForecastTemperatures(WeatherData::ForecastInfo *forecast, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("temperatures")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("temperatures")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) { parseFloat(forecast->tempLow, xml); } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) { parseFloat(forecast->tempHigh, xml); } else if (elementName == QLatin1String("textSummary")) { xml.readElementText(); } } } } void EnvCanadaIon::parsePrecipitationForecast(WeatherData::ForecastInfo *forecast, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("precipitation")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("precipitation")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("textSummary")) { forecast->precipForecast = xml.readElementText(); } else if (elementName == QLatin1String("precipType")) { forecast->precipType = xml.readElementText(); } else if (elementName == QLatin1String("accumulation")) { parsePrecipTotals(forecast, xml); } } } } void EnvCanadaIon::parsePrecipTotals(WeatherData::ForecastInfo *forecast, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("accumulation")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("accumulation")) { break; } if (elementName == QLatin1String("name")) { xml.readElementText(); } else if (elementName == QLatin1String("amount")) { forecast->precipTotalExpected = xml.readElementText(); } } } void EnvCanadaIon::parseWindForecast(WeatherData::ForecastInfo *forecast, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("winds")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("winds")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("textSummary")) { forecast->windForecast = xml.readElementText(); } else { if (xml.name() != QLatin1String("winds")) { parseUnknownElement(xml); } } } } } void EnvCanadaIon::parseYesterdayWeather(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("yesterdayConditions")); while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) { parseFloat(data.prevHigh, xml); } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) { parseFloat(data.prevLow, xml); } else if (elementName == QLatin1String("precip")) { data.prevPrecipType = xml.attributes().value(QStringLiteral("units")).toString(); if (data.prevPrecipType.isEmpty()) { data.prevPrecipType = QString::number(KUnitConversion::NoUnit); } data.prevPrecipTotal = xml.readElementText(); } } } } void EnvCanadaIon::parseWeatherRecords(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("almanac")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("almanac")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeMax")) { parseFloat(data.recordHigh, xml); } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeMin")) { parseFloat(data.recordLow, xml); } else if (elementName == QLatin1String("precipitation") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeRainfall")) { parseFloat(data.recordRain, xml); } else if (elementName == QLatin1String("precipitation") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeSnowfall")) { parseFloat(data.recordSnow, xml); } } } } void EnvCanadaIon::parseAstronomicals(WeatherData& data, QXmlStreamReader& xml) { Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("riseSet")); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isEndElement() && elementName == QLatin1String("riseSet")) { break; } if (xml.isStartElement()) { if (elementName == QLatin1String("disclaimer")) { xml.readElementText(); } else if (elementName == QLatin1String("dateTime")) { parseDateTime(data, xml); } } } } // handle when no XML tag is found void EnvCanadaIon::parseUnknownElement(QXmlStreamReader& xml) const { while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { parseUnknownElement(xml); } } } void EnvCanadaIon::updateWeather(const QString& source) { //qCDebug(IONENGINE_ENVCAN) << "updateWeather()"; const WeatherData& weatherData = m_weatherData[source]; Plasma::DataEngine::Data data; data.insert(QStringLiteral("Country"), weatherData.countryName); data.insert(QStringLiteral("Place"), QVariant(weatherData.cityName + QStringLiteral(", ") + weatherData.shortTerritoryName)); data.insert(QStringLiteral("Region"), weatherData.regionName); data.insert(QStringLiteral("Station"), weatherData.stationID.isEmpty() ? i18n("N/A") : weatherData.stationID.toUpper()); const bool stationCoordValid = (!qIsNaN(weatherData.stationLatitude) && !qIsNaN(weatherData.stationLongitude)); if (stationCoordValid) { data.insert(QStringLiteral("Latitude"), weatherData.stationLatitude); data.insert(QStringLiteral("Longitude"), weatherData.stationLongitude); } // Real weather - Current conditions data.insert(QStringLiteral("Observation Period"), weatherData.obsTimestamp); if (!weatherData.condition.isEmpty()) { data.insert(QStringLiteral("Current Conditions"), i18nc("weather condition", weatherData.condition.toUtf8().data())); } //qCDebug(IONENGINE_ENVCAN) << "i18n condition string: " << qPrintable(condition(source)); bool useDayIcon = true; // TODO: get timeengine's solarsystem code to use directly #if 0 if (weatherData.observationDateTime.isValid() && stationCoordValid) { PlasmaWeather::Sun sun; sun.setPosition(weatherData.stationLatitude, weatherData.stationLongitude); const int offset = weatherData.observationDateTime.timeZone().offsetFromUtc(weatherData.observationDateTime); sun.calcForDateTime(weatherData.observationDateTime, offset); const auto elevation = sun.calcElevation(); // Tell applet which icon to use for conditions and provide mapping for condition type to the icons to display useDayIcon = (elevation >= 0.0); } #endif QMap conditionList = conditionIcons(); if (!useDayIcon) { conditionList.insert(QStringLiteral("decreasing cloud"), FewCloudsNight); conditionList.insert(QStringLiteral("mostly cloudy"), PartlyCloudyNight); conditionList.insert(QStringLiteral("partly cloudy"), PartlyCloudyNight); conditionList.insert(QStringLiteral("fair"), FewCloudsNight); } else { conditionList.insert(QStringLiteral("decreasing cloud"), FewCloudsDay); conditionList.insert(QStringLiteral("mostly cloudy"), PartlyCloudyDay); conditionList.insert(QStringLiteral("partly cloudy"), PartlyCloudyDay); conditionList.insert(QStringLiteral("fair"), FewCloudsDay); } data.insert(QStringLiteral("Condition Icon"), getWeatherIcon(conditionList, weatherData.condition)); if (!qIsNaN(weatherData.temperature)) { data.insert(QStringLiteral("Temperature"), weatherData.temperature); } if (!qIsNaN(weatherData.windchill)) { data.insert(QStringLiteral("Windchill"), weatherData.windchill); } if (!weatherData.humidex.isEmpty()) { data.insert(QStringLiteral("Humidex"), weatherData.humidex); } // Used for all temperatures data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Celsius); if (!qIsNaN(weatherData.dewpoint)) { data.insert(QStringLiteral("Dewpoint"), weatherData.dewpoint); } if (!qIsNaN(weatherData.pressure)) { data.insert(QStringLiteral("Pressure"), weatherData.pressure); data.insert(QStringLiteral("Pressure Unit"), KUnitConversion::Kilopascal); data.insert(QStringLiteral("Pressure Tendency"), weatherData.pressureTendency); } if (!qIsNaN(weatherData.visibility)) { data.insert(QStringLiteral("Visibility"), weatherData.visibility); data.insert(QStringLiteral("Visibility Unit"), KUnitConversion::Kilometer); } if (!qIsNaN(weatherData.humidity)) { data.insert(QStringLiteral("Humidity"), weatherData.humidity); data.insert(QStringLiteral("Humidity Unit"), KUnitConversion::Percent); } if (!qIsNaN(weatherData.windSpeed)) { data.insert(QStringLiteral("Wind Speed"), weatherData.windSpeed); } if (!qIsNaN(weatherData.windGust)) { data.insert(QStringLiteral("Wind Gust"), weatherData.windGust); } if (!qIsNaN(weatherData.windSpeed) || !qIsNaN(weatherData.windGust)) { data.insert(QStringLiteral("Wind Speed Unit"), KUnitConversion::KilometerPerHour); } if (!qIsNaN(weatherData.windSpeed) && static_cast(weatherData.windSpeed) == 0) { data.insert(QStringLiteral("Wind Direction"), QStringLiteral("VR")); // Variable/calm } else if (!weatherData.windDirection.isEmpty()) { data.insert(QStringLiteral("Wind Direction"), weatherData.windDirection); } if (!qIsNaN(weatherData.normalHigh)) { data.insert(QStringLiteral("Normal High"), weatherData.normalHigh); } if (!qIsNaN(weatherData.normalLow)) { data.insert(QStringLiteral("Normal Low"), weatherData.normalLow); } // Check if UV index is available for the location if (!weatherData.UVIndex.isEmpty()) { data.insert(QStringLiteral("UV Index"), weatherData.UVIndex); } if (!weatherData.UVRating.isEmpty()) { data.insert(QStringLiteral("UV Rating"), weatherData.UVRating); } const QVector& watches = weatherData.watches; // Set number of forecasts per day/night supported data.insert(QStringLiteral("Total Watches Issued"), watches.size()); // Check if we have warnings or watches for (int i = 0; i < watches.size(); ++i) { const WeatherData::WeatherEvent* watch = watches.at(i); const QString number = QString::number(i); data.insert(QStringLiteral("Watch Priority ") + number, watch->priority); data.insert(QStringLiteral("Watch Description ") + number, watch->description); data.insert(QStringLiteral("Watch Info ") + number, watch->url); data.insert(QStringLiteral("Watch Timestamp ") + number, watch->timestamp); } const QVector& warnings = weatherData.warnings; data.insert(QStringLiteral("Total Warnings Issued"), warnings.size()); for (int k = 0; k < warnings.size(); ++k) { const WeatherData::WeatherEvent* warning = warnings.at(k); const QString number = QString::number(k); data.insert(QStringLiteral("Warning Priority ") + number, warning->priority); data.insert(QStringLiteral("Warning Description ") + number, warning->description); data.insert(QStringLiteral("Warning Info ") + number, warning->url); data.insert(QStringLiteral("Warning Timestamp ") + number, warning->timestamp); } const QVector & forecasts = weatherData.forecasts; // Set number of forecasts per day/night supported data.insert(QStringLiteral("Total Weather Days"), forecasts.size()); int i = 0; - foreach(const WeatherData::ForecastInfo *forecastInfo, forecasts) { + for (const WeatherData::ForecastInfo* forecastInfo : forecasts) { QString forecastPeriod = forecastInfo->forecastPeriod; if (forecastPeriod.isEmpty()) { forecastPeriod = i18n("N/A"); } else { // We need to shortform the day/night strings. forecastPeriod.replace(QStringLiteral("Today"), i18n("day")); forecastPeriod.replace(QStringLiteral("Tonight"), i18nc("Short for tonight", "nite")); forecastPeriod.replace(QStringLiteral("night"), i18nc("Short for night, appended to the end of the weekday", "nt")); forecastPeriod.replace(QStringLiteral("Saturday"), i18nc("Short for Saturday", "Sat")); forecastPeriod.replace(QStringLiteral("Sunday"), i18nc("Short for Sunday", "Sun")); forecastPeriod.replace(QStringLiteral("Monday"), i18nc("Short for Monday", "Mon")); forecastPeriod.replace(QStringLiteral("Tuesday"), i18nc("Short for Tuesday", "Tue")); forecastPeriod.replace(QStringLiteral("Wednesday"), i18nc("Short for Wednesday", "Wed")); forecastPeriod.replace(QStringLiteral("Thursday"), i18nc("Short for Thursday", "Thu")); forecastPeriod.replace(QStringLiteral("Friday"), i18nc("Short for Friday", "Fri")); } const QString shortForecast = forecastInfo->shortForecast.isEmpty() ? i18n("N/A") : i18nc("weather forecast", forecastInfo->shortForecast.toUtf8().data()); const QString tempHigh = qIsNaN(forecastInfo->tempHigh) ? QString() : QString::number(forecastInfo->tempHigh); const QString tempLow = qIsNaN(forecastInfo->tempLow) ? QString() : QString::number(forecastInfo->tempLow); const QString popPrecent = qIsNaN(forecastInfo->popPrecent) ? QString() : QString::number(forecastInfo->popPrecent); data.insert(QStringLiteral("Short Forecast Day %1").arg(i), QStringLiteral("%1|%2|%3|%4|%5|%6").arg( forecastPeriod, forecastInfo->iconName, shortForecast, tempHigh, tempLow, popPrecent)); //qCDebug(IONENGINE_ENVCAN) << "i18n summary string: " << qPrintable(i18n(forecastInfo->shortForecast.toUtf8())); /* data.insert(QString("Long Forecast Day %1").arg(i), QString("%1|%2|%3|%4|%5|%6|%7|%8") \ .arg(fieldList[0]).arg(fieldList[2]).arg(fieldList[3]).arg(fieldList[4]).arg(fieldList[6]) \ .arg(fieldList[7]).arg(fieldList[8]).arg(fieldList[9])); */ ++i; } // yesterday if (!qIsNaN(weatherData.prevHigh)) { data.insert(QStringLiteral("Yesterday High"), weatherData.prevHigh); } if (!qIsNaN(weatherData.prevLow)) { data.insert(QStringLiteral("Yesterday Low"), weatherData.prevLow); } const QString& prevPrecipTotal = weatherData.prevPrecipTotal; if (prevPrecipTotal == QLatin1String("Trace")) { data.insert(QStringLiteral("Yesterday Precip Total"), i18nc("precipitation total, very little", "Trace")); } else if (!prevPrecipTotal.isEmpty()) { data.insert(QStringLiteral("Yesterday Precip Total"), prevPrecipTotal); const QString& prevPrecipType = weatherData.prevPrecipType; const KUnitConversion::UnitId unit = (prevPrecipType == QLatin1String("mm")) ? KUnitConversion::Millimeter : (prevPrecipType == QLatin1String("cm")) ? KUnitConversion::Centimeter : /*else*/ KUnitConversion::NoUnit; data.insert(QStringLiteral("Yesterday Precip Unit"), unit); } // records if (!qIsNaN(weatherData.recordHigh)) { data.insert(QStringLiteral("Record High Temperature"), weatherData.recordHigh); } if (!qIsNaN(weatherData.recordLow)) { data.insert(QStringLiteral("Record Low Temperature"), weatherData.recordLow); } if (!qIsNaN(weatherData.recordRain)) { data.insert(QStringLiteral("Record Rainfall"), weatherData.recordRain); data.insert(QStringLiteral("Record Rainfall Unit"), KUnitConversion::Millimeter); } if (!qIsNaN(weatherData.recordSnow)) { data.insert(QStringLiteral("Record Snowfall"), weatherData.recordSnow); data.insert(QStringLiteral("Record Snowfall Unit"), KUnitConversion::Centimeter); } data.insert(QStringLiteral("Credit"), i18nc("credit line, keep string short", "Data from Environment\302\240Canada")); setData(source, data); } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(envcan, EnvCanadaIon, "ion-envcan.json") #include "ion_envcan.moc" diff --git a/dataengines/weather/ions/noaa/ion_noaa.cpp b/dataengines/weather/ions/noaa/ion_noaa.cpp index f77dea586..302a7b918 100644 --- a/dataengines/weather/ions/noaa/ion_noaa.cpp +++ b/dataengines/weather/ions/noaa/ion_noaa.cpp @@ -1,882 +1,882 @@ /*************************************************************************** * Copyright (C) 2007-2009 by Shawn Starr * * * * 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. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ /* Ion for NOAA's National Weather Service XML data */ #include "ion_noaa.h" #include "ion_noaadebug.h" #include #include #include #include #include WeatherData::WeatherData() : stationLatitude(qQNaN()) , stationLongitude(qQNaN()) , temperature_F(qQNaN()) , temperature_C(qQNaN()) , humidity(qQNaN()) , windSpeed(qQNaN()) , windGust(qQNaN()) , pressure(qQNaN()) , dewpoint_F(qQNaN()) , dewpoint_C(qQNaN()) , heatindex_F(qQNaN()) , heatindex_C(qQNaN()) , windchill_F(qQNaN()) , windchill_C(qQNaN()) , visibility(qQNaN()) { } QMap NOAAIon::setupWindIconMappings() const { return QMap { { QStringLiteral("north"), N }, { QStringLiteral("northeast"), NE }, { QStringLiteral("south"), S }, { QStringLiteral("southwest"), SW }, { QStringLiteral("east"), E }, { QStringLiteral("southeast"), SE }, { QStringLiteral("west"), W }, { QStringLiteral("northwest"), NW }, { QStringLiteral("calm"), VR }, }; } QMap NOAAIon::setupConditionIconMappings() const { QMap conditionList; return conditionList; } QMap const& NOAAIon::conditionIcons() const { static QMap const condval = setupConditionIconMappings(); return condval; } QMap const& NOAAIon::windIcons() const { static QMap const wval = setupWindIconMappings(); return wval; } // ctor, dtor NOAAIon::NOAAIon(QObject *parent, const QVariantList &args) : IonInterface(parent, args) { // Get the real city XML URL so we can parse this getXMLSetup(); } void NOAAIon::reset() { m_sourcesToReset = sources(); getXMLSetup(); } NOAAIon::~NOAAIon() { //seems necessary to avoid crash removeAllSources(); } QStringList NOAAIon::validate(const QString& source) const { QStringList placeList; QString station; QString sourceNormalized = source.toUpper(); QHash::const_iterator it = m_places.constBegin(); // If the source name might look like a station ID, check these too and return the name bool checkState = source.count() == 2; while (it != m_places.constEnd()) { if (checkState) { if (it.value().stateName == source) { placeList.append(QStringLiteral("place|").append(it.key())); } } else if (it.key().toUpper().contains(sourceNormalized)) { placeList.append(QStringLiteral("place|").append(it.key())); } else if (it.value().stationID == sourceNormalized) { station = QStringLiteral("place|").append(it.key()); } ++it; } placeList.sort(); if (!station.isEmpty()) { placeList.prepend(station); } return placeList; } bool NOAAIon::updateIonSource(const QString& source) { // We expect the applet to send the source in the following tokenization: // ionname:validate:place_name - Triggers validation of place // ionname:weather:place_name - Triggers receiving weather of place QStringList sourceAction = source.split(QLatin1Char('|')); // Guard: if the size of array is not 2 then we have bad data, return an error if (sourceAction.size() < 2) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|malformed")); return true; } if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() > 2) { QStringList result = validate(sourceAction[2]); if (result.size() == 1) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|valid|single|").append(result.join(QLatin1Char('|')))); return true; } if (result.size() > 1) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|valid|multiple|").append(result.join(QLatin1Char('|')))); return true; } // result.size() == 0 setData(source, QStringLiteral("validate"), QStringLiteral("noaa|invalid|single|").append(sourceAction[2])); return true; } if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() > 2) { getXMLData(source); return true; } setData(source, QStringLiteral("validate"), QStringLiteral("noaa|malformed")); return true; } // Parses city list and gets the correct city based on ID number void NOAAIon::getXMLSetup() const { const QUrl url(QStringLiteral("http://www.weather.gov/data/current_obs/index.xml")); KIO::TransferJob* getJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); connect(getJob, &KIO::TransferJob::data, this, &NOAAIon::setup_slotDataArrived); connect(getJob, &KJob::result, this, &NOAAIon::setup_slotJobFinished); } // Gets specific city XML data void NOAAIon::getXMLData(const QString& source) { - foreach (const QString &fetching, m_jobList) { + for (const QString& fetching : qAsConst(m_jobList)) { if (fetching == source) { // already getting this source and awaiting the data return; } } QString dataKey = source; dataKey.remove(QStringLiteral("noaa|weather|")); const QUrl url(m_places[dataKey].XMLurl); // If this is empty we have no valid data, send out an error and abort. if (url.url().isEmpty()) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|malformed")); return; } KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); m_jobXml.insert(getJob, new QXmlStreamReader); m_jobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &NOAAIon::slotDataArrived); connect(getJob, &KJob::result, this, &NOAAIon::slotJobFinished); } void NOAAIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job) if (data.isEmpty()) { return; } // Send to xml. m_xmlSetup.addData(data); } void NOAAIon::slotDataArrived(KIO::Job *job, const QByteArray &data) { if (data.isEmpty() || !m_jobXml.contains(job)) { return; } // Send to xml. m_jobXml[job]->addData(data); } void NOAAIon::slotJobFinished(KJob *job) { // Dual use method, if we're fetching location data to parse we need to do this first const QString source(m_jobList.value(job)); removeAllData(source); QXmlStreamReader *reader = m_jobXml.value(job); if (reader) { readXMLData(m_jobList[job], *reader); } // Now that we have the longitude and latitude, fetch the seven day forecast. getForecast(m_jobList[job]); m_jobList.remove(job); m_jobXml.remove(job); delete reader; } void NOAAIon::setup_slotJobFinished(KJob *job) { Q_UNUSED(job) const bool success = readXMLSetup(); setInitialized(success); - foreach (const QString &source, m_sourcesToReset) { + for (const QString& source : qAsConst(m_sourcesToReset)) { updateSourceEvent(source); } } void NOAAIon::parseFloat(float& value, const QString& string) { bool ok = false; const float result = string.toFloat(&ok); if (ok) { value = result; } } void NOAAIon::parseFloat(float& value, QXmlStreamReader& xml) { bool ok = false; const float result = xml.readElementText().toFloat(&ok); if (ok) { value = result; } } void NOAAIon::parseDouble(double& value, QXmlStreamReader& xml) { bool ok = false; const double result = xml.readElementText().toDouble(&ok); if (ok) { value = result; } } void NOAAIon::parseStationID() { QString state; QString stationName; QString stationID; QString xmlurl; while (!m_xmlSetup.atEnd()) { m_xmlSetup.readNext(); const QStringRef elementName = m_xmlSetup.name(); if (m_xmlSetup.isEndElement() && elementName == QLatin1String("station")) { if (!xmlurl.isEmpty()) { NOAAIon::XMLMapInfo info; info.stateName = state; info.stationName = stationName; info.stationID = stationID; info.XMLurl = xmlurl; QString tmp = stationName + QStringLiteral(", ") + state; // Build the key name. m_places[tmp] = info; } break; } if (m_xmlSetup.isStartElement()) { if (elementName == QLatin1String("station_id")) { stationID = m_xmlSetup.readElementText(); } else if (elementName == QLatin1String("state")) { state = m_xmlSetup.readElementText(); } else if (elementName == QLatin1String("station_name")) { stationName = m_xmlSetup.readElementText(); } else if (elementName == QLatin1String("xml_url")) { xmlurl = m_xmlSetup.readElementText().replace(QStringLiteral("http://"), QStringLiteral("http://www.")); } else { parseUnknownElement(m_xmlSetup); } } } } void NOAAIon::parseStationList() { while (!m_xmlSetup.atEnd()) { m_xmlSetup.readNext(); if (m_xmlSetup.isEndElement()) { break; } if (m_xmlSetup.isStartElement()) { if (m_xmlSetup.name() == QLatin1String("station")) { parseStationID(); } else { parseUnknownElement(m_xmlSetup); } } } } // Parse the city list and store into a QMap bool NOAAIon::readXMLSetup() { bool success = false; while (!m_xmlSetup.atEnd()) { m_xmlSetup.readNext(); if (m_xmlSetup.isStartElement()) { if (m_xmlSetup.name() == QLatin1String("wx_station_index")) { parseStationList(); success = true; } } } return (!m_xmlSetup.error() && success); } void NOAAIon::parseWeatherSite(WeatherData& data, QXmlStreamReader& xml) { data.temperature_C = qQNaN(); data.temperature_F = qQNaN(); data.dewpoint_C = qQNaN(); data.dewpoint_F = qQNaN(); data.weather = QStringLiteral("N/A"); data.stationID = i18n("N/A"); data.pressure = qQNaN(); data.visibility = qQNaN(); data.humidity = qQNaN(); data.windSpeed = qQNaN(); data.windGust = qQNaN(); data.windchill_F = qQNaN(); data.windchill_C = qQNaN(); data.heatindex_F = qQNaN(); data.heatindex_C = qQNaN(); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (elementName == QLatin1String("location")) { data.locationName = xml.readElementText(); } else if (elementName == QLatin1String("station_id")) { data.stationID = xml.readElementText(); } else if (elementName == QLatin1String("latitude")) { parseDouble(data.stationLatitude, xml); } else if (elementName == QLatin1String("longitude")) { parseDouble(data.stationLongitude, xml); } else if (elementName == QLatin1String("observation_time_rfc822")) { data.observationDateTime = QDateTime::fromString(xml.readElementText(), Qt::RFC2822Date); } else if (elementName == QLatin1String("observation_time")) { data.observationTime = xml.readElementText(); QStringList tmpDateStr = data.observationTime.split(QLatin1Char(' ')); data.observationTime = QStringLiteral("%1 %2").arg(tmpDateStr[6], tmpDateStr[7]); } else if (elementName == QLatin1String("weather")) { const QString weather = xml.readElementText(); data.weather = (weather.isEmpty() || weather == QLatin1String("NA")) ? QStringLiteral("N/A") : weather; // Pick which icon set depending on period of day } else if (elementName == QLatin1String("temp_f")) { parseFloat(data.temperature_F, xml); } else if (elementName == QLatin1String("temp_c")) { parseFloat(data.temperature_C, xml); } else if (elementName == QLatin1String("relative_humidity")) { parseFloat(data.humidity, xml); } else if (elementName == QLatin1String("wind_dir")) { data.windDirection = xml.readElementText(); } else if (elementName == QLatin1String("wind_mph")) { const QString windSpeed = xml.readElementText(); if (windSpeed == QLatin1String("NA")) { data.windSpeed = 0.0; } else { parseFloat(data.windSpeed, windSpeed); } } else if (elementName == QLatin1String("wind_gust_mph")) { const QString windGust = xml.readElementText(); if (windGust == QLatin1String("NA") || windGust == QLatin1String("N/A")) { data.windGust = 0.0; } else { parseFloat(data.windGust, windGust); } } else if (elementName == QLatin1String("pressure_in")) { parseFloat(data.pressure, xml); } else if (elementName == QLatin1String("dewpoint_f")) { parseFloat(data.dewpoint_F, xml); } else if (elementName == QLatin1String("dewpoint_c")) { parseFloat(data.dewpoint_C, xml); } else if (elementName == QLatin1String("heat_index_f")) { parseFloat(data.heatindex_F, xml); } else if (elementName == QLatin1String("heat_index_c")) { parseFloat(data.heatindex_C, xml); } else if (elementName == QLatin1String("windchill_f")) { parseFloat(data.windchill_F, xml); } else if (elementName == QLatin1String("windchill_c")) { parseFloat(data.windchill_C, xml); } else if (elementName == QLatin1String("visibility_mi")) { parseFloat(data.visibility, xml); } else { parseUnknownElement(xml); } } } } // Parse Weather data main loop, from here we have to decend into each tag pair bool NOAAIon::readXMLData(const QString& source, QXmlStreamReader& xml) { WeatherData data; while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { if (xml.name() == QLatin1String("current_observation")) { parseWeatherSite(data, xml); } else { parseUnknownElement(xml); } } } m_weatherData[source] = data; return !xml.error(); } // handle when no XML tag is found void NOAAIon::parseUnknownElement(QXmlStreamReader& xml) const { while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { parseUnknownElement(xml); } } } void NOAAIon::updateWeather(const QString& source) { const WeatherData& weatherData = m_weatherData[source]; Plasma::DataEngine::Data data; data.insert(QStringLiteral("Country"), QStringLiteral("USA")); data.insert(QStringLiteral("Place"), weatherData.locationName); data.insert(QStringLiteral("Station"), weatherData.stationID); const bool stationCoordValid = (!qIsNaN(weatherData.stationLatitude) && !qIsNaN(weatherData.stationLongitude)); if (stationCoordValid) { data.insert(QStringLiteral("Latitude"), weatherData.stationLatitude); data.insert(QStringLiteral("Longitude"), weatherData.stationLongitude); } // Real weather - Current conditions data.insert(QStringLiteral("Observation Period"), weatherData.observationTime); const QString conditionI18n = weatherData.weather == QLatin1String("N/A") ? i18n("N/A") : i18nc("weather condition", weatherData.weather.toUtf8().data()); data.insert(QStringLiteral("Current Conditions"), conditionI18n); qCDebug(IONENGINE_NOAA) << "i18n condition string: " << qPrintable(conditionI18n); bool useDayIcon = true; // TODO: get timeengine's solarsystem code to use directly #if 0 if (weatherData.observationDateTime.isValid() && stationCoordValid) { PlasmaWeather::Sun sun; sun.setPosition(weatherData.stationLatitude, weatherData.stationLongitude); const int offset = weatherData.observationDateTime.timeZone().offsetFromUtc(weatherData.observationDateTime); sun.calcForDateTime(weatherData.observationDateTime, offset); const auto elevation = sun.calcElevation(); // Tell applet which icon to use for conditions and provide mapping for condition type to the icons to display useDayIcon = (elevation >= 0.0); } #endif const QString weather = weatherData.weather.toLower(); ConditionIcons condition = getConditionIcon(weather, useDayIcon); data.insert(QStringLiteral("Condition Icon"), getWeatherIcon(condition)); if (!qIsNaN(weatherData.temperature_F)) { data.insert(QStringLiteral("Temperature"), weatherData.temperature_F); } // Used for all temperatures data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Fahrenheit); if (!qIsNaN(weatherData.windchill_F)) { data.insert(QStringLiteral("Windchill"), weatherData.windchill_F); } if (!qIsNaN(weatherData.heatindex_F)) { data.insert(QStringLiteral("Heat Index"), weatherData.heatindex_F); } if (!qIsNaN(weatherData.dewpoint_F)) { data.insert(QStringLiteral("Dewpoint"), weatherData.dewpoint_F); } if (!qIsNaN(weatherData.pressure)) { data.insert(QStringLiteral("Pressure"), weatherData.pressure); data.insert(QStringLiteral("Pressure Unit"), KUnitConversion::InchesOfMercury); } if (!qIsNaN(weatherData.visibility)) { data.insert(QStringLiteral("Visibility"), weatherData.visibility); data.insert(QStringLiteral("Visibility Unit"), KUnitConversion::Mile); } if (!qIsNaN(weatherData.humidity)) { data.insert(QStringLiteral("Humidity"), weatherData.humidity); data.insert(QStringLiteral("Humidity Unit"), KUnitConversion::Percent); } if (!qIsNaN(weatherData.windSpeed)) { data.insert(QStringLiteral("Wind Speed"), weatherData.windSpeed); } if (!qIsNaN(weatherData.windSpeed) || !qIsNaN(weatherData.windGust)) { data.insert(QStringLiteral("Wind Speed Unit"), KUnitConversion::MilePerHour); } if (!qIsNaN(weatherData.windGust)) { data.insert(QStringLiteral("Wind Gust"), weatherData.windGust); } if (!qIsNaN(weatherData.windSpeed) && static_cast(weatherData.windSpeed) == 0) { data.insert(QStringLiteral("Wind Direction"), QStringLiteral("VR")); // Variable/calm } else if (!weatherData.windDirection.isEmpty()) { data.insert(QStringLiteral("Wind Direction"), getWindDirectionIcon(windIcons(), weatherData.windDirection.toLower())); } // Set number of forecasts per day/night supported data.insert(QStringLiteral("Total Weather Days"), weatherData.forecasts.size()); int i = 0; - foreach(const WeatherData::Forecast &forecast, weatherData.forecasts) { + for (const WeatherData::Forecast& forecast : weatherData.forecasts) { ConditionIcons icon = getConditionIcon(forecast.summary.toLower(), true); QString iconName = getWeatherIcon(icon); /* Sometimes the forecast for the later days is unavailable, if so skip remianing days * since their forecast data is probably unavailable. */ if (forecast.low.isEmpty() || forecast.high.isEmpty()) { break; } // Get the short day name for the forecast data.insert(QStringLiteral("Short Forecast Day %1").arg(i), QStringLiteral("%1|%2|%3|%4|%5|%6") .arg(forecast.day, iconName, i18nc("weather forecast", forecast.summary.toUtf8().data()), forecast.high, forecast.low, QString())); ++i; } data.insert(QStringLiteral("Credit"), i18nc("credit line, keep string short)", "Data from NOAA National\302\240Weather\302\240Service")); setData(source, data); } /** * Determine the condition icon based on the list of possible NOAA weather conditions as defined at * and * * Since the number of NOAA weather conditions need to be fitted into the narowly defined groups in IonInterface::ConditionIcons, we * try to group the NOAA conditions as best as we can based on their priorities/severity. * TODO: summaries "Hot" & "Cold" have no proper matching entry in ConditionIcons, consider extending it */ IonInterface::ConditionIcons NOAAIon::getConditionIcon(const QString& weather, bool isDayTime) const { IonInterface::ConditionIcons result; // Consider any type of storm, tornado or funnel to be a thunderstorm. if (weather.contains(QStringLiteral("thunderstorm")) || weather.contains(QStringLiteral("funnel")) || weather.contains(QStringLiteral("tornado")) || weather.contains(QStringLiteral("storm")) || weather.contains(QStringLiteral("tstms"))) { if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { result = isDayTime ? IonInterface::ChanceThunderstormDay : IonInterface::ChanceThunderstormNight; } else { result = IonInterface::Thunderstorm; } } else if (weather.contains(QStringLiteral("pellets")) || weather.contains(QStringLiteral("crystals")) || weather.contains(QStringLiteral("hail"))) { result = IonInterface::Hail; } else if (((weather.contains(QStringLiteral("rain")) || weather.contains(QStringLiteral("drizzle")) || weather.contains(QStringLiteral("showers"))) && weather.contains(QStringLiteral("snow"))) || weather.contains(QStringLiteral("wintry mix"))) { result = IonInterface::RainSnow; } else if (weather.contains(QStringLiteral("snow")) && weather.contains(QStringLiteral("light"))) { result = IonInterface::LightSnow; } else if (weather.contains(QStringLiteral("snow"))) { if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { result = isDayTime ? IonInterface::ChanceSnowDay : IonInterface::ChanceSnowNight; } else { result = IonInterface::Snow; } } else if (weather.contains(QStringLiteral("freezing rain"))) { result = IonInterface::FreezingRain; } else if (weather.contains(QStringLiteral("freezing drizzle"))) { result = IonInterface::FreezingDrizzle; } else if (weather.contains(QStringLiteral("cold"))) { // temperature condition has not hint about air ingredients, so let's assume chance of snow result = isDayTime ? IonInterface::ChanceSnowDay : IonInterface::ChanceSnowNight; } else if (weather.contains(QStringLiteral("showers"))) { if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { result = isDayTime ? IonInterface::ChanceShowersDay : IonInterface::ChanceShowersNight; } else { result = IonInterface::Showers; } } else if (weather.contains(QStringLiteral("light rain")) || weather.contains(QStringLiteral("drizzle"))) { result = IonInterface::LightRain; } else if (weather.contains(QStringLiteral("rain"))) { result = IonInterface::Rain; } else if (weather.contains(QStringLiteral("few clouds")) || weather.contains(QStringLiteral("mostly sunny")) || weather.contains(QStringLiteral("mostly clear")) || weather.contains(QStringLiteral("increasing clouds")) || weather.contains(QStringLiteral("becoming cloudy")) || weather.contains(QStringLiteral("clearing")) || weather.contains(QStringLiteral("decreasing clouds")) || weather.contains(QStringLiteral("becoming sunny"))) { result = isDayTime ? IonInterface::FewCloudsDay : IonInterface::FewCloudsNight; } else if (weather.contains(QStringLiteral("partly cloudy")) || weather.contains(QStringLiteral("partly sunny")) || weather.contains(QStringLiteral("partly clear"))) { result = isDayTime ? IonInterface::PartlyCloudyDay : IonInterface::PartlyCloudyNight; } else if (weather.contains(QStringLiteral("overcast")) || weather.contains(QStringLiteral("cloudy"))) { result = IonInterface::Overcast; } else if (weather.contains(QStringLiteral("haze")) || weather.contains(QStringLiteral("smoke")) || weather.contains(QStringLiteral("dust")) || weather.contains(QStringLiteral("sand"))) { result = IonInterface::Haze; } else if (weather.contains(QStringLiteral("fair")) || weather.contains(QStringLiteral("clear")) || weather.contains(QStringLiteral("sunny"))) { result = isDayTime ? IonInterface::ClearDay : IonInterface::ClearNight; } else if (weather.contains(QStringLiteral("fog"))) { result = IonInterface::Mist; } else if (weather.contains(QStringLiteral("hot"))) { // temperature condition has not hint about air ingredients, so let's assume the sky is clear when it is hot result = isDayTime ? IonInterface::ClearDay : IonInterface::ClearNight; } else { result = IonInterface::NotAvailable; } return result; } void NOAAIon::getForecast(const QString& source) { const double lat = m_weatherData[source].stationLatitude; const double lon = m_weatherData[source].stationLongitude; if (qIsNaN(lat) || qIsNaN(lon)) { return; } /* Assuming that we have the latitude and longitude data at this point, get the 7-day * forecast. */ const QUrl url(QLatin1String("http://www.weather.gov/forecasts/xml/sample_products/browser_interface/" "ndfdBrowserClientByDay.php?lat=") + QString::number(lat) + QLatin1String("&lon=") + QString::number(lon) + QLatin1String("&format=24+hourly&numDays=7")); KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); m_jobXml.insert(getJob, new QXmlStreamReader); m_jobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &NOAAIon::forecast_slotDataArrived); connect(getJob, &KJob::result, this, &NOAAIon::forecast_slotJobFinished); } void NOAAIon::forecast_slotDataArrived(KIO::Job *job, const QByteArray &data) { if (data.isEmpty() || !m_jobXml.contains(job)) { return; } // Send to xml. m_jobXml[job]->addData(data); } void NOAAIon::forecast_slotJobFinished(KJob *job) { QXmlStreamReader *reader = m_jobXml.value(job); const QString source = m_jobList.value(job); if (reader) { readForecast(source, *reader); updateWeather(source); } m_jobList.remove(job); delete m_jobXml[job]; m_jobXml.remove(job); if (m_sourcesToReset.contains(source)) { m_sourcesToReset.removeAll(source); // so the weather engine updates it's data forceImmediateUpdateOfAllVisualizations(); // update the clients of our engine emit forceUpdate(this, source); } } void NOAAIon::readForecast(const QString& source, QXmlStreamReader& xml) { QVector& forecasts = m_weatherData[source].forecasts; // Clear the current forecasts forecasts.clear(); while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { /* Read all reported days from . We check for existence of a specific * which indicates the separate day listings. The schema defines it to be * the first item before the day listings. */ if (xml.name() == QLatin1String("layout-key") && xml.readElementText() == QLatin1String("k-p24h-n7-1")) { // Read days until we get to end of parent ()tag while (! (xml.isEndElement() && xml.name() == QLatin1String("time-layout"))) { xml.readNext(); if (xml.name() == QLatin1String("start-valid-time")) { QString data = xml.readElementText(); QDateTime date = QDateTime::fromString(data, Qt::ISODate); WeatherData::Forecast forecast; forecast.day = QLocale().toString(date.date().day()); forecasts.append(forecast); //qCDebug(IONENGINE_NOAA) << forecast.day; } } } else if (xml.name() == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("type")) == QLatin1String("maximum")) { // Read max temps until we get to end tag int i = 0; while (! (xml.isEndElement() && xml.name() == QLatin1String("temperature")) && i < forecasts.count()) { xml.readNext(); if (xml.name() == QLatin1String("value")) { forecasts[i].high = xml.readElementText(); //qCDebug(IONENGINE_NOAA) << forecasts[i].high; i++; } } } else if (xml.name() == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("type")) == QLatin1String("minimum")) { // Read min temps until we get to end tag int i = 0; while (! (xml.isEndElement() && xml.name() == QLatin1String("temperature")) && i < forecasts.count()) { xml.readNext(); if (xml.name() == QLatin1String("value")) { forecasts[i].low = xml.readElementText(); //qCDebug(IONENGINE_NOAA) << forecasts[i].low; i++; } } } else if (xml.name() == QLatin1String("weather")) { // Read weather conditions until we get to end tag int i = 0; while (! (xml.isEndElement() && xml.name() == QLatin1String("weather")) && i < forecasts.count()) { xml.readNext(); if (xml.name() == QLatin1String("weather-conditions") && xml.isStartElement()) { QString summary = xml.attributes().value(QStringLiteral("weather-summary")).toString(); forecasts[i].summary = summary; //qCDebug(IONENGINE_NOAA) << forecasts[i].summary; qCDebug(IONENGINE_NOAA) << "i18n summary string: " << i18nc("weather forecast", forecasts[i].summary.toUtf8().data()); i++; } } } } } } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(noaa, NOAAIon, "ion-noaa.json") #include "ion_noaa.moc" diff --git a/dataengines/weather/ions/wetter.com/ion_wettercom.cpp b/dataengines/weather/ions/wetter.com/ion_wettercom.cpp index e764d79f6..81900ab48 100644 --- a/dataengines/weather/ions/wetter.com/ion_wettercom.cpp +++ b/dataengines/weather/ions/wetter.com/ion_wettercom.cpp @@ -1,826 +1,826 @@ /*************************************************************************** * Copyright (C) 2009 by Thilo-Alexander Ginkel * * * * Based upon BBC Weather Ion by Shawn Starr * * Copyright (C) 2007-2009 by Shawn Starr * * * * 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. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ /* Ion for weather data from wetter.com */ // Sample URLs: // http://api.wetter.com/location/index/search/Heidelberg/project/weatherion/cs/9090dec6e783b96bd6a6ca9d451f3fee // http://api.wetter.com/forecast/weather/city/DE0004329/project/weatherion/cs/89f1264869cce5c6fd5a2db80051f3d8 #include "ion_wettercom.h" #include "ion_wettercomdebug.h" #include #include #include #include #include #include /* * 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 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 WetterComIon::setupCommonIconMappings() const { return QMap { { 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 WetterComIon::setupDayIconMappings() const { QMap 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 const& WetterComIon::dayIcons() const { static QMap const val = setupDayIconMappings(); return val; } QMap WetterComIon::setupNightIconMappings() const { QMap 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 const& WetterComIon::nightIcons() const { static QMap const val = setupNightIconMappings(); return val; } QHash WetterComIon::setupCommonConditionMappings() const { return QHash { { 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 WetterComIon::setupDayConditionMappings() const { QHash conditionList = setupCommonConditionMappings(); conditionList.insert(QStringLiteral("0"), i18nc("weather condition", "sunny")); return conditionList; } QHash const& WetterComIon::dayConditions() const { static QHash const val = setupDayConditionMappings(); return val; } QHash WetterComIon::setupNightConditionMappings() const { QHash conditionList = setupCommonConditionMappings(); conditionList.insert(QStringLiteral("0"), i18nc("weather condition", "clear sky")); return conditionList; } QHash const& WetterComIon::nightConditions() const { static QHash const val = setupNightConditionMappings(); return val; } QString WetterComIon::getWeatherCondition(const QHash &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; - foreach(const QString &place, m_locations) { + 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) { - foreach (const QString &fetching, m_forecastJobList) { + 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 == QLatin1String("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 == QLatin1String("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; - foreach(WeatherData::ForecastPeriod * forecastPeriod, weatherData.forecasts) { + 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& forecastInfos) const { int result = -273; - foreach(const WeatherData::ForecastInfo * forecast, forecastInfos) { + for (const WeatherData::ForecastInfo* forecast : forecastInfos) { result = std::max(result, forecast->tempHigh); } return result; } int WeatherData::ForecastPeriod::getMinTemp(const QVector& forecastInfos) const { int result = 100; - foreach(const WeatherData::ForecastInfo * forecast, forecastInfos) { + for (const WeatherData::ForecastInfo* forecast : forecastInfos) { result = std::min(result, forecast->tempLow); } return result; } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(wettercom, WetterComIon, "ion-wettercom.json") #include "ion_wettercom.moc" diff --git a/dataengines/weather/weatherengine.cpp b/dataengines/weather/weatherengine.cpp index 4f6b752ea..53189021f 100644 --- a/dataengines/weather/weatherengine.cpp +++ b/dataengines/weather/weatherengine.cpp @@ -1,233 +1,234 @@ /*************************************************************************** * Copyright (C) 2007-2009 by Shawn Starr * * Copyright (C) 2009 by Aaron Seigo * * * * 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. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ #include "weatherengine.h" #include #include #include #include #include "ions/ion.h" #include "weatherenginedebug.h" // Constructor WeatherEngine::WeatherEngine(QObject *parent, const QVariantList& args) : Plasma::DataEngine(parent, args) { m_reconnectTimer.setSingleShot(true); connect(&m_reconnectTimer, &QTimer::timeout, this, &WeatherEngine::startReconnect); // Globally notify all plugins to remove their sources (and unload plugin) connect(this, &Plasma::DataEngine::sourceRemoved, this, &WeatherEngine::removeIonSource); connect(&m_networkConfigurationManager, &QNetworkConfigurationManager::onlineStateChanged, this, &WeatherEngine::onOnlineStateChanged); // Get the list of available plugins but don't load them connect(KSycoca::self(), static_cast(&KSycoca::databaseChanged), this, &WeatherEngine::updateIonList); updateIonList(); } // Destructor WeatherEngine::~WeatherEngine() { } /* FIXME: Q_PROPERTY functions to update the list of available plugins */ void WeatherEngine::updateIonList(const QStringList &changedResources) { if (changedResources.isEmpty() || changedResources.contains(QStringLiteral("services"))) { removeAllData(QStringLiteral("ions")); - foreach (const KPluginInfo &info, Plasma::PluginLoader::self()->listEngineInfo(QStringLiteral("weatherengine"))) { + const auto infos = Plasma::PluginLoader::self()->listEngineInfo(QStringLiteral("weatherengine")); + for (const KPluginInfo& info : infos) { const QString data = info.name() + QLatin1Char('|') + info.pluginName(); setData(QStringLiteral("ions"), info.pluginName(), data); } } } /** * SLOT: Remove the datasource from the ion and unload plugin if needed */ void WeatherEngine::removeIonSource(const QString& source) { QString ionName; IonInterface *ion = ionForSource(source, &ionName); if (ion) { ion->removeSource(source); // track used ions QHash::Iterator it = m_ionUsage.find(ionName); if (it == m_ionUsage.end()) { qCWarning(WEATHER) << "Removing ion source without being added before:" << source; } else { // no longer used? if (it.value() <= 1) { // forget about it m_ionUsage.erase(it); disconnect(ion, &IonInterface::forceUpdate, this, &WeatherEngine::forceUpdate); qCDebug(WEATHER) << "Ion no longer used as source:" << ionName; } else { --(it.value()); } } } else { qCWarning(WEATHER) << "Could not find ion to remove source for:" << source; } } /** * SLOT: Push out new data to applet */ void WeatherEngine::dataUpdated(const QString& source, const Plasma::DataEngine::Data& data) { qCDebug(WEATHER) << "dataUpdated() for:" << source; setData(source, data); } /** * SLOT: Set up each Ion for the first time and get any data */ bool WeatherEngine::sourceRequestEvent(const QString &source) { QString ionName; IonInterface* ion = ionForSource(source, &ionName); if (!ion) { qCWarning(WEATHER) << "Could not find ion to request source for:" << source; return false; } // track used ions QHash::Iterator it = m_ionUsage.find(ionName); if (it == m_ionUsage.end()) { m_ionUsage.insert(ionName, 1); connect(ion, &IonInterface::forceUpdate, this, &WeatherEngine::forceUpdate); qCDebug(WEATHER) << "Ion now used as source:" << ionName; } else { ++(*it); } // we should connect to the ion anyway, even if the network // is down. when it comes up again, then it will be refreshed ion->connectSource(source, this); qCDebug(WEATHER) << "sourceRequestEvent(): Network is: " << m_networkConfigurationManager.isOnline(); if (!m_networkConfigurationManager.isOnline()) { setData(source, Data()); return true; } if (!containerForSource(source)) { // it is an async reply, we need to set up the data anyways setData(source, Data()); } return true; } /** * SLOT: update the Applet with new data from all ions loaded. */ bool WeatherEngine::updateSourceEvent(const QString& source) { qCDebug(WEATHER) << "updateSourceEvent(): Network is: " << m_networkConfigurationManager.isOnline(); if (!m_networkConfigurationManager.isOnline()) { return false; } IonInterface *ion = ionForSource(source); if (!ion) { qCWarning(WEATHER) << "Could not find ion to update source for:" << source; return false; } return ion->updateSourceEvent(source); } void WeatherEngine::onOnlineStateChanged(bool isOnline) { if (isOnline) { qCDebug(WEATHER) << "starting m_reconnectTimer"; // allow the network to settle down and actually come up m_reconnectTimer.start(1000); } else { m_reconnectTimer.stop(); } } void WeatherEngine::startReconnect() { for(QHash::ConstIterator it = m_ionUsage.constBegin(); it != m_ionUsage.constEnd(); ++it) { const QString& ionName = it.key(); IonInterface * ion = qobject_cast(dataEngine(ionName)); if (ion) { qCDebug(WEATHER) << "Resetting ion" << ion; ion->reset(); } else { qCWarning(WEATHER) << "Could not find ion to reset:" << ionName; } } } void WeatherEngine::forceUpdate(IonInterface *ion, const QString &source) { Q_UNUSED(ion); Plasma::DataContainer *container = containerForSource(source); if (container) { qCDebug(WEATHER) << "immediate update of" << source; container->forceImmediateUpdate(); } else { qCWarning(WEATHER) << "innexplicable failure of" << source; } } IonInterface* WeatherEngine::ionForSource(const QString& source, QString* ionName) { const int offset = source.indexOf(QLatin1Char('|')); if (offset < 1) { return nullptr; } const QString name = source.left(offset); IonInterface* result = qobject_cast(dataEngine(name)); if (result && ionName) { *ionName = name; } return result; } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(weather, WeatherEngine, "plasma-dataengine-weather.json") #include "weatherengine.moc"