diff --git a/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp b/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp index 459fbb394..658fc46f4 100644 --- a/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp +++ b/dataengines/weather/ions/bbcukmet/ion_bbcukmet.cpp @@ -1,1043 +1,1047 @@ /*************************************************************************** * 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 rain showers"), 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("heavy rain showers"), Rain }, { QStringLiteral("thundery shower"), Thunderstorm }, { QStringLiteral("thundery showers"), 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 rain showers"), 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("heavy rain showers"), Rain }, { QStringLiteral("thundery shower"), Thunderstorm }, { QStringLiteral("thundery showers"), 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; } XMLMapInfo& place = m_place[QStringLiteral("bbcukmet|") + sourceAction[2]]; // backward compatibility after rss feed url change in 2018/03 place.sourceExtraArg = sourceAction[3]; if (place.sourceExtraArg.startsWith(QLatin1String("http://open.live.bbc.co.uk/"))) { // Old data source id stored the full (now outdated) observation feed url // http://open.live.bbc.co.uk/weather/feeds/en/STATIOID/observations.rss // as extra argument, so extract the id from that place.stationId = place.sourceExtraArg.section(QLatin1Char('/'), -2, -2); } else { place.stationId = place.sourceExtraArg; } 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) { for (const QString& fetching : qAsConst(m_obsJobList)) { if (fetching == source) { // already getting this source and awaiting the data return; } } const QUrl url(QStringLiteral("https://weather-broker-cdn.api.bbci.co.uk/en/observation/rss/") + m_place[source].stationId); 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("https://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]; const QUrl url(QStringLiteral("https://weather-broker-cdn.api.bbci.co.uk/en/forecast/rss/3day/") + place.stationId); 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()) { const QJsonArray results = jsonDocumentObject.value(QStringLiteral("results")).toArray(); 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()) { 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.stationId = id; 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 if (elementName == QLatin1String("link") && xml.namespaceUri().isEmpty()) { m_place[source].forecastHTMLUrl = xml.readElementText(); } else { parseUnknownElement(xml); } } } } void UKMETIon::parseWeatherObservation(const QString& source, WeatherData& data, QXmlStreamReader& xml) { Q_UNUSED(source); 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 == QLatin1String("Not Available")) { data.condition.clear(); } } } } 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(); if (data.visibilityStr == QLatin1String("--")) { data.visibilityStr.clear(); } } 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; data.isForecastsDataPending = true; 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; } bool solarDataSourceNeedsConnect = false; Plasma::DataEngine* timeEngine = dataEngine(QStringLiteral("time")); if (timeEngine) { const bool canCalculateElevation = (data.observationDateTime.isValid() && (!qIsNaN(data.stationLatitude) && !qIsNaN(data.stationLongitude))); if (canCalculateElevation) { data.solarDataTimeEngineSourceName = QStringLiteral("Local|Solar|Latitude=%1|Longitude=%2|DateTime=%3") .arg(data.stationLatitude) .arg(data.stationLongitude) .arg(data.observationDateTime.toString(Qt::ISODate)); solarDataSourceNeedsConnect = true; } // check any previous data const auto it = m_weatherData.constFind(source); if (it != m_weatherData.constEnd()) { const QString& oldSolarDataTimeEngineSource = it.value().solarDataTimeEngineSourceName; if (oldSolarDataTimeEngineSource == data.solarDataTimeEngineSourceName) { // can reuse elevation source (if any), copy over data data.isNight = it.value().isNight; solarDataSourceNeedsConnect = false; } else if (!oldSolarDataTimeEngineSource.isEmpty()) { // drop old elevation source timeEngine->disconnectSource(oldSolarDataTimeEngineSource, this); } } } m_weatherData[source] = data; // connect only after m_weatherData has the data, so the instant data push handling can see it if (solarDataSourceNeedsConnect) { data.isSolarDataPending = true; timeEngine->connectSource(data.solarDataTimeEngineSourceName, this); } // 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")); WeatherData& weatherData = m_weatherData[source]; QVector& forecasts = weatherData.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; } } weatherData.isForecastsDataPending = false; // 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; 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].stationId); } 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 WeatherData& weatherData = m_weatherData[source]; if (weatherData.isForecastsDataPending || weatherData.isSolarDataPending) { return; } 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.sourceExtraArg); Plasma::DataEngine::Data data; // work-around for buggy observation RSS feed missing the station name QString stationName = weatherData.stationName; if (stationName.isEmpty() || stationName == QLatin1String(",")) { stationName = source.section(QLatin1Char('|'), 1, 1); } data.insert(QStringLiteral("Place"), stationName); data.insert(QStringLiteral("Station"), stationName); if (weatherData.observationDateTime.isValid()) { data.insert(QStringLiteral("Observation Timestamp"), weatherData.observationDateTime); } 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); } data.insert(QStringLiteral("Condition Icon"), getWeatherIcon(weatherData.isNight ? nightIcons() : dayIcons(), 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; for (const WeatherData::ForecastInfo* forecastInfo : forecasts) { QString period = forecastInfo->period; + // same day + period.replace(QStringLiteral("Today"), i18nc("Short for Today", "Today")); + period.replace(QStringLiteral("Tonight"), i18nc("Short for Tonight", "Tonight")); + // upcoming days 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); } void UKMETIon::dataUpdated(const QString& sourceName, const Plasma::DataEngine::Data& data) { const bool isNight = (data.value(QStringLiteral("Corrected Elevation")).toDouble() < 0.0); for (auto end = m_weatherData.end(), it = m_weatherData.begin(); it != end; ++it) { auto& weatherData = it.value(); if (weatherData.solarDataTimeEngineSourceName == sourceName) { weatherData.isNight = isNight; weatherData.isSolarDataPending = false; updateWeather(it.key()); } } } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(bbcukmet, UKMETIon, "ion-bbcukmet.json") #include "ion_bbcukmet.moc" diff --git a/dataengines/weather/ions/data/envcan_i18n.dat b/dataengines/weather/ions/data/envcan_i18n.dat index 8f6a64679..c0a0b7f88 100644 --- a/dataengines/weather/ions/data/envcan_i18n.dat +++ b/dataengines/weather/ions/data/envcan_i18n.dat @@ -1,338 +1,339 @@ weather condition|N/A weather condition|Blowing Snow weather condition|Clear weather condition|Cloudy weather condition|Decreasing Cloud weather condition|Distant Precipitation weather condition|Drifting Snow weather condition|Drizzle weather condition|Dust weather condition|Dust Devils weather condition|Fog weather condition|Fog Bank Near Station weather condition|Fog Depositing Ice weather condition|Fog Patches weather condition|Freezing drizzle weather condition|Freezing rain weather condition|Funnel Cloud weather condition|Hail weather condition|Haze weather condition|Heavy Blowing Snow weather condition|Heavy Drifting Snow weather condition|Heavy Drizzle weather condition|Heavy Hail weather condition|Heavy Mixed Rain and Drizzle weather condition|Heavy Mixed Rain and Snow Shower weather condition|Heavy Rain weather condition|Heavy Rain and Snow weather condition|Heavy Rainshower weather condition|Heavy Snow weather condition|Heavy Snow Pellets weather condition|Heavy Snowshower weather condition|Heavy Thunderstorm with Hail weather condition|Heavy Thunderstorm with Rain weather condition|Ice Crystals weather condition|Ice Pellets weather condition|Increasing Cloud weather condition|Light Drizzle weather condition|Light Freezing Drizzle weather condition|Light Freezing Rain weather condition|Light Rain weather condition|Light Rainshower weather condition|Light Snow weather condition|Light Snow Pellets weather condition|Light Snowshower weather condition|Lightning Visible weather condition|Mainly Clear weather condition|Mainly Sunny weather condition|Mist weather condition|Mixed Rain and Drizzle weather condition|Mixed Rain and Snow Shower weather condition|Mostly Cloudy weather condition|Not Reported weather condition|Partly Cloudy weather condition|Rain weather condition|Rain and Snow weather condition|Rainshower weather condition|Recent Drizzle weather condition|Recent Dust or Sand Storm weather condition|Recent Fog weather condition|Recent Freezing Precipitation weather condition|Recent Hail weather condition|Recent Rain weather condition|Recent Rain and Snow weather condition|Recent Rainshower weather condition|Recent Snow weather condition|Recent Snowshower weather condition|Recent Thunderstorm weather condition|Recent Thunderstorm with Hail weather condition|Recent Thunderstorm with Heavy Hail weather condition|Recent Thunderstorm with Heavy Rain weather condition|Recent Thunderstorm with Rain weather condition|Sand or Dust Storm weather condition|Severe Sand or Dust Storm weather condition|Shallow Fog weather condition|Smoke weather condition|Snow weather condition|Snow Crystals weather condition|Snow Grains weather condition|Squalls weather condition|Sunny weather condition|Thunderstorm with Hail weather condition|Thunderstorm with Rain weather condition|Thunderstorm with Sand or Dust Storm weather condition|Thunderstorm without Precipitation weather condition|Tornado weather forecast|A few clouds weather forecast|A few flurries weather forecast|A few flurries mixed with ice pellets weather forecast|A few flurries or rain showers weather forecast|A few flurries or thundershowers weather forecast|A few rain showers or flurries weather forecast|A few rain showers or wet flurries weather forecast|A few showers weather forecast|A few showers or drizzle weather forecast|A few showers or thundershowers weather forecast|A few showers or thunderstorms weather forecast|A few thundershowers weather forecast|A few thunderstorms weather forecast|A few wet flurries weather forecast|A few wet flurries or rain showers weather forecast|A mix of sun and cloud weather forecast|Blizzard weather forecast|Chance of drizzle weather forecast|Chance of drizzle mixed with freezing drizzle weather forecast|Chance of drizzle mixed with rain weather forecast|Chance of drizzle or rain weather forecast|Chance of flurries weather forecast|Chance of flurries at times heavy weather forecast|Chance of flurries mixed with ice pellets weather forecast|Chance of flurries or ice pellets weather forecast|Chance of flurries or rain showers weather forecast|Chance of flurries or thundershowers weather forecast|Chance of freezing drizzle weather forecast|Chance of freezing rain weather forecast|Chance of freezing rain mixed with snow weather forecast|Chance of freezing rain or rain weather forecast|Chance of freezing rain or snow weather forecast|Chance of light snow weather forecast|Chance of light snow and blowing snow weather forecast|Chance of light snow mixed with freezing drizzle weather forecast|Chance of light snow mixed with ice pellets weather forecast|Chance of light snow mixed with rain weather forecast|Chance of light snow or freezing rain weather forecast|Chance of light snow or ice pellets weather forecast|Chance of light snow or rain weather forecast|Chance of light wet snow weather forecast|Chance of rain weather forecast|Chance of rain at times heavy weather forecast|Chance of rain mixed with snow weather forecast|Chance of rain or drizzle weather forecast|Chance of rain or freezing rain weather forecast|Chance of rain or snow weather forecast|Chance of rain showers or flurries weather forecast|Chance of rain showers or wet flurries weather forecast|Chance of severe thunderstorms weather forecast|Chance of showers weather forecast|Chance of showers at times heavy weather forecast|Chance of showers at times heavy or thundershowers weather forecast|Chance of showers at times heavy or thunderstorms weather forecast|Chance of showers or drizzle weather forecast|Chance of showers or thundershowers weather forecast|Chance of showers or thunderstorms weather forecast|Chance of snow weather forecast|Chance of snow and blizzard weather forecast|Chance of snow mixed with freezing drizzle weather forecast|Chance of snow mixed with freezing rain weather forecast|Chance of snow mixed with rain weather forecast|Chance of snow or rain weather forecast|Chance of snow squalls weather forecast|Chance of thundershowers weather forecast|Chance of thunderstorms weather forecast|Chance of thunderstorms and possible hail weather forecast|Chance of wet flurries weather forecast|Chance of wet flurries at times heavy weather forecast|Chance of wet flurries or rain showers weather forecast|Chance of wet snow weather forecast|Chance of wet snow mixed with rain weather forecast|Chance of wet snow or rain weather forecast|Clear weather forecast|Clearing weather forecast|Cloudy +weather forecast|Partly cloudy weather forecast|Mainly cloudy weather forecast|Cloudy periods weather forecast|Cloudy with sunny periods weather forecast|Drizzle weather forecast|Drizzle mixed with freezing drizzle weather forecast|Drizzle mixed with rain weather forecast|Drizzle or freezing drizzle weather forecast|Drizzle or rain weather forecast|Flurries weather forecast|Flurries at times heavy weather forecast|Flurries at times heavy or rain showers weather forecast|Flurries mixed with ice pellets weather forecast|Flurries or ice pellets weather forecast|Flurries or rain showers weather forecast|Flurries or thundershowers weather forecast|Fog weather forecast|Fog developing weather forecast|Fog dissipating weather forecast|Fog patches weather forecast|Freezing drizzle weather forecast|Freezing rain weather forecast|Freezing rain mixed with ice pellets weather forecast|Freezing rain mixed with rain weather forecast|Freezing rain mixed with snow weather forecast|Freezing rain or ice pellets weather forecast|Freezing rain or rain weather forecast|Freezing rain or snow weather forecast|Ice fog weather forecast|Ice fog developing weather forecast|Ice fog dissipating weather forecast|Ice pellets weather forecast|Ice pellets mixed with freezing rain weather forecast|Ice pellets mixed with snow weather forecast|Ice pellets or freezing rain weather forecast|Ice pellets or snow weather forecast|Increasing cloudiness weather forecast|Increasing clouds weather forecast|Light snow weather forecast|Light snow and blizzard weather forecast|Light snow and blizzard and blowing snow weather forecast|Light snow and blowing snow weather forecast|Light snow mixed with freezing drizzle weather forecast|Light snow mixed with freezing rain weather forecast|Light snow mixed with ice pellets weather forecast|Light snow mixed with rain weather forecast|Light snow or freezing drizzle weather forecast|Light snow or freezing rain weather forecast|Light snow or ice pellets weather forecast|Light snow or rain weather forecast|Light wet snow weather forecast|Light wet snow or rain weather forecast|Local snow squalls weather forecast|Near blizzard weather forecast|Overcast weather forecast|Periods of drizzle weather forecast|Periods of drizzle mixed with freezing drizzle weather forecast|Periods of drizzle mixed with rain weather forecast|Periods of drizzle or freezing drizzle weather forecast|Periods of drizzle or rain weather forecast|Periods of freezing drizzle weather forecast|Periods of freezing drizzle or drizzle weather forecast|Periods of freezing drizzle or rain weather forecast|Periods of freezing rain weather forecast|Periods of freezing rain mixed with ice pellets weather forecast|Periods of freezing rain mixed with rain weather forecast|Periods of freezing rain mixed with snow weather forecast|Periods of freezing rain or ice pellets weather forecast|Periods of freezing rain or rain weather forecast|Periods of freezing rain or snow weather forecast|Periods of ice pellets weather forecast|Periods of ice pellets mixed with freezing rain weather forecast|Periods of ice pellets mixed with snow weather forecast|Periods of ice pellets or freezing rain weather forecast|Periods of ice pellets or snow weather forecast|Periods of light snow weather forecast|Periods of light snow and blizzard weather forecast|Periods of light snow and blizzard and blowing snow weather forecast|Periods of light snow and blowing snow weather forecast|Periods of light snow mixed with freezing drizzle weather forecast|Periods of light snow mixed with freezing rain weather forecast|Periods of light snow mixed with ice pellets weather forecast|Periods of light snow mixed with rain weather forecast|Periods of light snow or freezing drizzle weather forecast|Periods of light snow or freezing rain weather forecast|Periods of light snow or ice pellets weather forecast|Periods of light snow or rain weather forecast|Periods of light wet snow weather forecast|Periods of light wet snow mixed with rain weather forecast|Periods of light wet snow or rain weather forecast|Periods of rain weather forecast|Periods of rain mixed with freezing rain weather forecast|Periods of rain mixed with snow weather forecast|Periods of rain or drizzle weather forecast|Periods of rain or freezing rain weather forecast|Periods of rain or snow weather forecast|Periods of rain or thundershowers weather forecast|Periods of rain or thunderstorms weather forecast|Periods of snow weather forecast|Periods of snow and blizzard weather forecast|Periods of snow and blizzard and blowing snow weather forecast|Periods of snow and blowing snow weather forecast|Periods of snow mixed with freezing drizzle weather forecast|Periods of snow mixed with freezing rain weather forecast|Periods of snow mixed with ice pellets weather forecast|Periods of snow mixed with rain weather forecast|Periods of snow or freezing drizzle weather forecast|Periods of snow or freezing rain weather forecast|Periods of snow or ice pellets weather forecast|Periods of snow or rain weather forecast|Periods of wet snow weather forecast|Periods of wet snow mixed with rain weather forecast|Periods of wet snow or rain weather forecast|Rain weather forecast|Rain at times heavy weather forecast|Rain at times heavy mixed with freezing rain weather forecast|Rain at times heavy mixed with snow weather forecast|Rain at times heavy or drizzle weather forecast|Rain at times heavy or freezing rain weather forecast|Rain at times heavy or snow weather forecast|Rain at times heavy or thundershowers weather forecast|Rain at times heavy or thunderstorms weather forecast|Rain mixed with freezing rain weather forecast|Rain mixed with snow weather forecast|Rain or drizzle weather forecast|Rain or freezing rain weather forecast|Rain or snow weather forecast|Rain or thundershowers weather forecast|Rain or thunderstorms weather forecast|Rain showers or flurries weather forecast|Rain showers or wet flurries weather forecast|Showers weather forecast|Showers at times heavy weather forecast|Showers at times heavy or thundershowers weather forecast|Showers at times heavy or thunderstorms weather forecast|Showers or drizzle weather forecast|Showers or thundershowers weather forecast|Showers or thunderstorms weather forecast|Smoke weather forecast|Snow weather forecast|Snow and blizzard weather forecast|Snow and blizzard and blowing snow weather forecast|Snow and blowing snow weather forecast|Snow at times heavy weather forecast|Snow at times heavy and blizzard weather forecast|Snow at times heavy and blowing snow weather forecast|Snow at times heavy mixed with freezing drizzle weather forecast|Snow at times heavy mixed with freezing rain weather forecast|Snow at times heavy mixed with ice pellets weather forecast|Snow at times heavy mixed with rain weather forecast|Snow at times heavy or freezing rain weather forecast|Snow at times heavy or ice pellets weather forecast|Snow at times heavy or rain weather forecast|Snow mixed with freezing drizzle weather forecast|Snow mixed with freezing rain weather forecast|Snow mixed with ice pellets weather forecast|Snow mixed with rain weather forecast|Snow or freezing drizzle weather forecast|Snow or freezing rain weather forecast|Snow or ice pellets weather forecast|Snow or rain weather forecast|Snow squalls weather forecast|Sunny weather forecast|Mainly Sunny weather forecast|Sunny with cloudy periods weather forecast|Thunderstorms weather forecast|Thunderstorms and possible hail weather forecast|Wet flurries weather forecast|Wet flurries at times heavy weather forecast|Wet flurries at times heavy or rain showers weather forecast|Wet flurries or rain showers weather forecast|Wet snow weather forecast|Wet snow at times heavy weather forecast|Wet snow at times heavy mixed with rain weather forecast|Wet snow mixed with rain weather forecast|Wet snow or rain weather forecast|Windy Trace