You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
733 lines
27 KiB
733 lines
27 KiB
/* |
|
SPDX-FileCopyrightText: 2021 Emily Ehlert |
|
|
|
Based upon BBC Weather Ion and ENV Canada Ion by Shawn Starr |
|
SPDX-FileCopyrightText: 2007-2009 Shawn Starr <shawn.starr@rogers.com> |
|
|
|
also |
|
|
|
the wetter.com Ion by Thilo-Alexander Ginkel |
|
SPDX-FileCopyrightText: 2009 Thilo-Alexander Ginkel <thilo@ginkel.com> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
/* Ion for weather data from Deutscher Wetterdienst (DWD) / German Weather Service */ |
|
|
|
#include "ion_dwd.h" |
|
|
|
#include "ion_dwddebug.h" |
|
|
|
#include <KIO/Job> |
|
#include <KLocalizedString> |
|
#include <KUnitConversion/Converter> |
|
|
|
#include <QDateTime> |
|
#include <QJsonArray> |
|
#include <QJsonDocument> |
|
#include <QJsonObject> |
|
#include <QLocale> |
|
#include <QVariant> |
|
|
|
/* |
|
* Initialization |
|
*/ |
|
|
|
WeatherData::WeatherData() |
|
: temperature(qQNaN()) |
|
, humidity(qQNaN()) |
|
, pressure(qQNaN()) |
|
, windSpeed(qQNaN()) |
|
, gustSpeed(qQNaN()) |
|
, dewpoint(qQNaN()) |
|
, windSpeedAlt(qQNaN()) |
|
, gustSpeedAlt(qQNaN()) |
|
{ |
|
} |
|
|
|
WeatherData::ForecastInfo::ForecastInfo() |
|
: tempHigh(qQNaN()) |
|
, tempLow(qQNaN()) |
|
, windSpeed(qQNaN()) |
|
{ |
|
} |
|
|
|
DWDIon::DWDIon(QObject *parent, const QVariantList &args) |
|
: IonInterface(parent, args) |
|
|
|
{ |
|
setInitialized(true); |
|
} |
|
|
|
DWDIon::~DWDIon() |
|
{ |
|
deleteForecasts(); |
|
} |
|
|
|
void DWDIon::reset() |
|
{ |
|
deleteForecasts(); |
|
m_sourcesToReset = sources(); |
|
updateAllSources(); |
|
} |
|
|
|
void DWDIon::deleteForecasts() |
|
{ |
|
// Destroy each forecast stored in a QVector |
|
for (auto it = m_weatherData.begin(), end = m_weatherData.end(); it != end; ++it) { |
|
qDeleteAll(it.value().forecasts); |
|
it.value().forecasts.clear(); |
|
} |
|
} |
|
|
|
QMap<QString, IonInterface::ConditionIcons> DWDIon::setupDayIconMappings() const |
|
{ |
|
// DWD supplies it's own icon number which we can use to determine a condition |
|
|
|
return QMap<QString, ConditionIcons>{{QStringLiteral("1"), ClearDay}, |
|
{QStringLiteral("2"), PartlyCloudyDay}, |
|
{QStringLiteral("3"), PartlyCloudyDay}, |
|
{QStringLiteral("4"), Overcast}, |
|
{QStringLiteral("5"), Mist}, |
|
{QStringLiteral("6"), Mist}, |
|
{QStringLiteral("7"), LightRain}, |
|
{QStringLiteral("8"), Rain}, |
|
{QStringLiteral("9"), Rain}, |
|
{QStringLiteral("10"), LightRain}, |
|
{QStringLiteral("11"), Rain}, |
|
{QStringLiteral("12"), Flurries}, |
|
{QStringLiteral("13"), RainSnow}, |
|
{QStringLiteral("14"), LightSnow}, |
|
{QStringLiteral("15"), Snow}, |
|
{QStringLiteral("16"), Snow}, |
|
{QStringLiteral("17"), Hail}, |
|
{QStringLiteral("18"), LightRain}, |
|
{QStringLiteral("19"), Rain}, |
|
{QStringLiteral("20"), Flurries}, |
|
{QStringLiteral("21"), RainSnow}, |
|
{QStringLiteral("22"), LightSnow}, |
|
{QStringLiteral("23"), Snow}, |
|
{QStringLiteral("24"), Hail}, |
|
{QStringLiteral("25"), Hail}, |
|
{QStringLiteral("26"), Thunderstorm}, |
|
{QStringLiteral("27"), Thunderstorm}, |
|
{QStringLiteral("28"), Thunderstorm}, |
|
{QStringLiteral("29"), Thunderstorm}, |
|
{QStringLiteral("30"), Thunderstorm}, |
|
{QStringLiteral("31"), ClearWindyDay}}; |
|
} |
|
|
|
QMap<QString, IonInterface::WindDirections> DWDIon::setupWindIconMappings() const |
|
{ |
|
return QMap<QString, WindDirections>{ |
|
{QStringLiteral("0"), N}, {QStringLiteral("10"), N}, {QStringLiteral("20"), NNE}, {QStringLiteral("30"), NNE}, {QStringLiteral("40"), NE}, |
|
{QStringLiteral("50"), NE}, {QStringLiteral("60"), ENE}, {QStringLiteral("70"), ENE}, {QStringLiteral("80"), E}, {QStringLiteral("90"), E}, |
|
{QStringLiteral("100"), E}, {QStringLiteral("120"), ESE}, {QStringLiteral("130"), ESE}, {QStringLiteral("140"), SE}, {QStringLiteral("150"), SE}, |
|
{QStringLiteral("160"), SSE}, {QStringLiteral("170"), SSE}, {QStringLiteral("180"), S}, {QStringLiteral("190"), S}, {QStringLiteral("200"), SSW}, |
|
{QStringLiteral("210"), SSW}, {QStringLiteral("220"), SW}, {QStringLiteral("230"), SW}, {QStringLiteral("240"), WSW}, {QStringLiteral("250"), WSW}, |
|
{QStringLiteral("260"), W}, {QStringLiteral("270"), W}, {QStringLiteral("280"), W}, {QStringLiteral("290"), WNW}, {QStringLiteral("300"), WNW}, |
|
{QStringLiteral("310"), NW}, {QStringLiteral("320"), NW}, {QStringLiteral("330"), NNW}, {QStringLiteral("340"), NNW}, {QStringLiteral("350"), N}, |
|
{QStringLiteral("360"), N}, |
|
}; |
|
} |
|
|
|
QMap<QString, IonInterface::ConditionIcons> const &DWDIon::dayIcons() const |
|
{ |
|
static QMap<QString, ConditionIcons> const dval = setupDayIconMappings(); |
|
return dval; |
|
} |
|
|
|
QMap<QString, IonInterface::WindDirections> const &DWDIon::windIcons() const |
|
{ |
|
static QMap<QString, WindDirections> const wval = setupWindIconMappings(); |
|
return wval; |
|
} |
|
|
|
bool DWDIon::updateIonSource(const QString &source) |
|
{ |
|
// We expect the applet to send the source in the following tokenization: |
|
// ionname|validate|place_name|extra - Triggers validation (search) 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("dwd|malformed")); |
|
return true; |
|
} |
|
|
|
if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() >= 3) { |
|
// Look for places to match |
|
findPlace(sourceAction[2]); |
|
return true; |
|
} |
|
if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() >= 3) { |
|
if (sourceAction.count() >= 4) { |
|
if (sourceAction[2].isEmpty()) { |
|
setData(source, QStringLiteral("validate"), QStringLiteral("dwd|malformed")); |
|
return true; |
|
} |
|
|
|
// Extra data: station_id |
|
m_place[sourceAction[2]] = sourceAction[3]; |
|
|
|
qCDebug(IONENGINE_dwd) << "About to retrieve forecast for source: " << sourceAction[2]; |
|
|
|
fetchWeather(sourceAction[2], m_place[sourceAction[2]]); |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
setData(source, QStringLiteral("validate"), QStringLiteral("dwd|malformed")); |
|
return true; |
|
} |
|
|
|
void DWDIon::findPlace(const QString &searchText) |
|
{ |
|
// Checks if the stations have already been loaded, always contains the currently active one |
|
if (m_place.size() > 1) { |
|
setData(QStringLiteral("dwd|validate|") + searchText, Data()); |
|
searchInStationList(searchText); |
|
} else { |
|
const QUrl forecastURL(QStringLiteral(CATALOGUE_URL)); |
|
KIO::TransferJob *getJob = KIO::get(forecastURL, KIO::Reload, KIO::HideProgressInfo); |
|
getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); |
|
|
|
m_searchJobList.insert(getJob, searchText); |
|
m_searchJobData.insert(getJob, QByteArray("")); |
|
|
|
connect(getJob, &KIO::TransferJob::data, this, &DWDIon::setup_slotDataArrived); |
|
connect(getJob, &KJob::result, this, &DWDIon::setup_slotJobFinished); |
|
} |
|
} |
|
|
|
void DWDIon::fetchWeather(QString placeName, QString placeID) |
|
{ |
|
for (const QString &fetching : qAsConst(m_forecastJobList)) { |
|
if (fetching == placeName) { |
|
// already fetching! |
|
return; |
|
} |
|
} |
|
|
|
// Fetch forecast data |
|
|
|
const QUrl forecastURL(QStringLiteral(FORECAST_URL).arg(placeID)); |
|
KIO::TransferJob *getJob = KIO::get(forecastURL, KIO::Reload, KIO::HideProgressInfo); |
|
getJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); |
|
|
|
m_forecastJobList.insert(getJob, placeName); |
|
m_forecastJobJSON.insert(getJob, QByteArray("")); |
|
|
|
qCDebug(IONENGINE_dwd) << "Requesting URL: " << forecastURL; |
|
|
|
connect(getJob, &KIO::TransferJob::data, this, &DWDIon::forecast_slotDataArrived); |
|
connect(getJob, &KJob::result, this, &DWDIon::forecast_slotJobFinished); |
|
m_weatherData[placeName].isForecastsDataPending = true; |
|
|
|
// Fetch current measurements (different url AND different API, AMAZING) |
|
|
|
const QUrl measureURL(QStringLiteral(MEASURE_URL).arg(placeID)); |
|
KIO::TransferJob *getMeasureJob = KIO::get(measureURL, KIO::Reload, KIO::HideProgressInfo); |
|
getMeasureJob->addMetaData(QStringLiteral("cookies"), QStringLiteral("none")); |
|
|
|
m_measureJobList.insert(getMeasureJob, placeName); |
|
m_measureJobJSON.insert(getMeasureJob, QByteArray("")); |
|
|
|
qCDebug(IONENGINE_dwd) << "Requesting URL: " << measureURL; |
|
|
|
connect(getMeasureJob, &KIO::TransferJob::data, this, &DWDIon::measure_slotDataArrived); |
|
connect(getMeasureJob, &KJob::result, this, &DWDIon::measure_slotJobFinished); |
|
m_weatherData[placeName].isMeasureDataPending = true; |
|
} |
|
|
|
void DWDIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data) |
|
{ |
|
QByteArray local = data; |
|
|
|
if (data.isEmpty() || !m_searchJobData.contains(job)) { |
|
return; |
|
} |
|
|
|
m_searchJobData[job].append(local); |
|
} |
|
|
|
void DWDIon::measure_slotDataArrived(KIO::Job *job, const QByteArray &data) |
|
{ |
|
QByteArray local = data; |
|
|
|
if (data.isEmpty() || !m_measureJobJSON.contains(job)) { |
|
return; |
|
} |
|
|
|
m_measureJobJSON[job].append(local); |
|
} |
|
|
|
void DWDIon::forecast_slotDataArrived(KIO::Job *job, const QByteArray &data) |
|
{ |
|
QByteArray local = data; |
|
|
|
if (data.isEmpty() || !m_forecastJobJSON.contains(job)) { |
|
return; |
|
} |
|
|
|
m_forecastJobJSON[job].append(local); |
|
} |
|
|
|
void DWDIon::setup_slotJobFinished(KJob *job) |
|
{ |
|
const QString searchText(m_searchJobList.value(job)); |
|
setData(QStringLiteral("dwd|validate|") + searchText, Data()); |
|
|
|
QByteArray catalogueData = m_searchJobData[job]; |
|
if (!catalogueData.isNull()) { |
|
parseStationData(catalogueData); |
|
searchInStationList(searchText); |
|
} |
|
|
|
m_searchJobList.remove(job); |
|
m_searchJobData.remove(job); |
|
} |
|
|
|
void DWDIon::measure_slotJobFinished(KJob *job) |
|
{ |
|
const QString source(m_measureJobList.value(job)); |
|
setData(source, Data()); |
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(m_measureJobJSON.value(job)); |
|
|
|
// Not all stations have current measurements |
|
if (!doc.isNull()) { |
|
parseMeasureData(source, doc); |
|
} else { |
|
m_weatherData[source].isMeasureDataPending = false; |
|
updateWeather(source); |
|
} |
|
|
|
m_measureJobList.remove(job); |
|
m_measureJobJSON.remove(job); |
|
} |
|
|
|
void DWDIon::forecast_slotJobFinished(KJob *job) |
|
{ |
|
const QString source(m_forecastJobList.value(job)); |
|
setData(source, Data()); |
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(m_forecastJobJSON.value(job)); |
|
|
|
if (!doc.isNull()) { |
|
parseForecastData(source, doc); |
|
} |
|
|
|
m_forecastJobList.remove(job); |
|
m_forecastJobJSON.remove(job); |
|
|
|
if (m_sourcesToReset.contains(source)) { |
|
m_sourcesToReset.removeAll(source); |
|
const QString weatherSource = QStringLiteral("dwd|weather|%1|%2").arg(source, m_place[source]); |
|
|
|
// so the weather engine updates it's data |
|
forceImmediateUpdateOfAllVisualizations(); |
|
|
|
// update the clients of our engine |
|
emit forceUpdate(this, weatherSource); |
|
} |
|
} |
|
|
|
void DWDIon::calculatePositions(QStringList lines, QVector<int> &namePositionalInfo, QVector<int> &stationIdPositionalInfo) |
|
{ |
|
QStringList stringLengths = lines[3].split(QChar::Space); |
|
QVector<int> lengths; |
|
for (const QString &length : qAsConst(stringLengths)) { |
|
lengths.append(length.count()); |
|
} |
|
|
|
int curpos = 0; |
|
|
|
for (int labelLength : lengths) { |
|
QString label = lines[2].mid(curpos, labelLength); |
|
|
|
if (label.contains(QStringLiteral("name"))) { |
|
namePositionalInfo[0] = curpos; |
|
namePositionalInfo[1] = labelLength; |
|
} else if (label.contains(QStringLiteral("id"))) { |
|
stationIdPositionalInfo[0] = curpos; |
|
stationIdPositionalInfo[1] = labelLength; |
|
} |
|
|
|
curpos += labelLength + 1; |
|
} |
|
} |
|
|
|
void DWDIon::parseStationData(QByteArray data) |
|
{ |
|
QString stringData = QString::fromLatin1(data); |
|
QStringList lines = stringData.split(QChar::LineFeed); |
|
|
|
QVector<int> namePositionalInfo(2); |
|
QVector<int> stationIdPositionalInfo(2); |
|
calculatePositions(lines, namePositionalInfo, stationIdPositionalInfo); |
|
|
|
// This loop parses the station file (https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_stationskatalog.cfg) |
|
// clu CofX id ICAO name nb. el. elev Hmod-H type |
|
// ===== ----- ===== ---- -------------------- ------ ------- ----- ------ ---- |
|
// 99801 504 07335 LFBI POITIERS 46.35 0.18 120 -10 LAND |
|
// 99802 504 07354 LFLX CHATEAUROUX 46.52 1.43 155 12 LAND |
|
// 99803 470 07379 LFLN SAINT-YAN. 46.25 4.01 242 9 LAND |
|
bool start = true; |
|
for (const QString &line : qAsConst(lines)) { |
|
if (!start && line.isEmpty()) { |
|
break; |
|
} |
|
start = false; |
|
|
|
QString name = line.mid(namePositionalInfo[0], namePositionalInfo[1]).trimmed(); |
|
QString id = line.mid(stationIdPositionalInfo[0], stationIdPositionalInfo[1]).trimmed(); |
|
|
|
// This checks if this station is a station we know is working |
|
// With this we remove all non working but also a lot of working ones. |
|
if (id.startsWith(QLatin1Char('0')) || id.startsWith(QLatin1Char('1'))) { |
|
m_place.insert(camelCaseString(name), id); |
|
} |
|
} |
|
qCDebug(IONENGINE_dwd) << "Number of parsed stations: " << m_place.size(); |
|
} |
|
|
|
void DWDIon::searchInStationList(const QString searchText) |
|
{ |
|
qCDebug(IONENGINE_dwd) << searchText; |
|
|
|
QMap<QString, QString>::const_iterator it = m_place.constBegin(); |
|
auto end = m_place.constEnd(); |
|
|
|
while (it != end) { |
|
QString name = it.key(); |
|
if (name.contains(searchText, Qt::CaseInsensitive)) { |
|
m_locations.append(it.key()); |
|
} |
|
++it; |
|
} |
|
|
|
validate(searchText); |
|
} |
|
|
|
void DWDIon::parseForecastData(const QString source, QJsonDocument doc) |
|
{ |
|
QVariantMap weatherMap = doc.object().toVariantMap().first().toMap(); |
|
if (!weatherMap.isEmpty()) { |
|
// Forecast data |
|
QVariantList daysList = weatherMap[QStringLiteral("days")].toList(); |
|
|
|
WeatherData &weatherData = m_weatherData[source]; |
|
QVector<WeatherData::ForecastInfo *> &forecasts = weatherData.forecasts; |
|
|
|
// Flush out the old forecasts when updating. |
|
forecasts.clear(); |
|
|
|
WeatherData::ForecastInfo *forecast = new WeatherData::ForecastInfo; |
|
|
|
int dayNumber = 0; |
|
|
|
for (const QVariant &day : daysList) { |
|
QMap dayMap = day.toMap(); |
|
QString period = dayMap[QStringLiteral("dayDate")].toString(); |
|
QString cond = dayMap[QStringLiteral("icon")].toString(); |
|
|
|
forecast->period = QDateTime::fromString(period, QStringLiteral("yyyy-MM-dd")); |
|
forecast->tempHigh = parseNumber(dayMap[QStringLiteral("temperatureMax")].toInt()); |
|
forecast->tempLow = parseNumber(dayMap[QStringLiteral("temperatureMin")].toInt()); |
|
forecast->iconName = getWeatherIcon(dayIcons(), cond); |
|
; |
|
|
|
if (dayNumber == 0) { |
|
// These alternative measurements are used, when the stations doesn't have it's own measurements, uses forecast data from the current day |
|
weatherData.windSpeedAlt = parseNumber(dayMap[QStringLiteral("windSpeed")].toInt()); |
|
weatherData.gustSpeedAlt = parseNumber(dayMap[QStringLiteral("windGust")].toInt()); |
|
QString windDirection = roundWindDirections(dayMap[QStringLiteral("windDirection")].toInt()); |
|
weatherData.windDirectionAlt = getWindDirectionIcon(windIcons(), windDirection); |
|
} |
|
|
|
forecasts.append(forecast); |
|
forecast = new WeatherData::ForecastInfo; |
|
|
|
dayNumber++; |
|
// Only get the next 7 days (including today) |
|
if (dayNumber == 7) |
|
break; |
|
} |
|
|
|
delete forecast; |
|
|
|
// Warnings data |
|
QVariantList warningData = weatherMap[QStringLiteral("warnings")].toList(); |
|
|
|
QVector<WeatherData::WarningInfo *> &warningList = weatherData.warnings; |
|
|
|
// Flush out the old forecasts when updating. |
|
warningList.clear(); |
|
|
|
WeatherData::WarningInfo *warning = new WeatherData::WarningInfo; |
|
|
|
for (const QVariant &warningElement : warningData) { |
|
QMap warningMap = warningElement.toMap(); |
|
|
|
QString warningDesc = warningMap[QStringLiteral("description")].toString(); |
|
|
|
// This loop adds line breaks because the weather widget doesn't seem to do that which completly ruins the layout |
|
// Should be removed if the weather widget is ever fixed |
|
for (int i = 1; i <= (warningDesc.size() / 50); i++) { |
|
warningDesc.insert(i * 50, QChar::LineFeed); |
|
} |
|
|
|
warning->description = warningDesc; |
|
warning->priority = warningMap[QStringLiteral("level")].toInt(); |
|
warning->type = warningMap[QStringLiteral("event")].toString(); |
|
warning->timestamp = QDateTime::fromMSecsSinceEpoch(warningMap[QStringLiteral("start")].toLongLong()); |
|
|
|
warningList.append(warning); |
|
warning = new WeatherData::WarningInfo; |
|
} |
|
|
|
delete warning; |
|
|
|
weatherData.isForecastsDataPending = false; |
|
|
|
updateWeather(source); |
|
} |
|
} |
|
|
|
void DWDIon::parseMeasureData(const QString source, QJsonDocument doc) |
|
{ |
|
WeatherData &weatherData = m_weatherData[source]; |
|
QVariantMap weatherMap = doc.object().toVariantMap(); |
|
|
|
if (!weatherMap.isEmpty()) { |
|
bool windIconValid = false; |
|
bool tempValid = false; |
|
bool humidityValid = false; |
|
bool pressureValid = false; |
|
bool windSpeedValid = false; |
|
bool gustSpeedValid = false; |
|
bool dewpointValid = false; |
|
|
|
QDateTime time = QDateTime::fromMSecsSinceEpoch(weatherMap[QStringLiteral("time")].toLongLong()); |
|
QString condIconNumber = weatherMap[QStringLiteral("icon")].toString(); |
|
int windDirection = weatherMap[QStringLiteral("winddirection")].toInt(&windIconValid); |
|
float temp = parseNumber(weatherMap[QStringLiteral("temperature")].toInt(&tempValid)); |
|
float humidity = parseNumber(weatherMap[QStringLiteral("humidity")].toInt(&humidityValid)); |
|
float pressure = parseNumber(weatherMap[QStringLiteral("pressure")].toInt(&pressureValid)); |
|
float windSpeed = parseNumber(weatherMap[QStringLiteral("meanwind")].toInt(&windSpeedValid)); |
|
float gustSpeed = parseNumber(weatherMap[QStringLiteral("maxwind")].toInt(&gustSpeedValid)); |
|
float dewpoint = parseNumber(weatherMap[QStringLiteral("dewpoint")].toInt(&dewpointValid)); |
|
|
|
if (condIconNumber != QLatin1String("")) |
|
weatherData.conditionIcon = getWeatherIcon(dayIcons(), condIconNumber); |
|
if (windIconValid) |
|
weatherData.windDirection = getWindDirectionIcon(windIcons(), roundWindDirections(windDirection)); |
|
if (tempValid) |
|
weatherData.temperature = temp; |
|
if (humidityValid) |
|
weatherData.humidity = humidity; |
|
if (pressureValid) |
|
weatherData.pressure = pressure; |
|
if (windSpeedValid) |
|
weatherData.windSpeed = windSpeed; |
|
if (gustSpeedValid) |
|
weatherData.gustSpeed = gustSpeed; |
|
if (dewpointValid) |
|
weatherData.dewpoint = dewpoint; |
|
weatherData.observationDateTime = time; |
|
} |
|
|
|
weatherData.isMeasureDataPending = false; |
|
|
|
updateWeather(source); |
|
} |
|
|
|
void DWDIon::validate(const QString &searchText) |
|
{ |
|
const QString source(QStringLiteral("dwd|validate|") + searchText); |
|
|
|
if (m_locations.isEmpty()) { |
|
const QString invalidPlace = searchText; |
|
setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("dwd|invalid|multiple|") + invalidPlace)); |
|
return; |
|
} |
|
|
|
QString placeList; |
|
for (const QString &place : qAsConst(m_locations)) { |
|
placeList.append(QStringLiteral("|place|") + place + QStringLiteral("|extra|") + m_place[place]); |
|
} |
|
if (m_locations.count() > 1) { |
|
setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("dwd|valid|multiple") + placeList)); |
|
} else { |
|
placeList[7] = placeList[7].toUpper(); |
|
setData(source, QStringLiteral("validate"), QVariant(QStringLiteral("dwd|valid|single") + placeList)); |
|
} |
|
m_locations.clear(); |
|
} |
|
|
|
void DWDIon::updateWeather(const QString &source) |
|
{ |
|
const WeatherData &weatherData = m_weatherData[source]; |
|
|
|
if (weatherData.isForecastsDataPending || weatherData.isMeasureDataPending) { |
|
return; |
|
} |
|
|
|
QString placeCode = m_place[source]; |
|
QString weatherSource = QStringLiteral("dwd|weather|%1|%2").arg(source, placeCode); |
|
|
|
Plasma::DataEngine::Data data; |
|
|
|
data.insert(QStringLiteral("Place"), source); |
|
data.insert(QStringLiteral("Station"), source); |
|
|
|
data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Celsius); |
|
data.insert(QStringLiteral("Wind Speed Unit"), KUnitConversion::KilometerPerHour); |
|
data.insert(QStringLiteral("Humidity Unit"), KUnitConversion::Percent); |
|
data.insert(QStringLiteral("Pressure Unit"), KUnitConversion::Hectopascal); |
|
|
|
if (!weatherData.observationDateTime.isNull()) |
|
data.insert(QStringLiteral("Observation Timestamp"), weatherData.observationDateTime); |
|
else |
|
data.insert(QStringLiteral("Observation Timestamp"), QDateTime::currentDateTime()); |
|
|
|
if (!weatherData.conditionIcon.isEmpty()) |
|
data.insert(QStringLiteral("Condition Icon"), weatherData.conditionIcon); |
|
|
|
if (!qIsNaN(weatherData.temperature)) |
|
data.insert(QStringLiteral("Temperature"), weatherData.temperature); |
|
|
|
if (!qIsNaN(weatherData.humidity)) |
|
data.insert(QStringLiteral("Humidity"), weatherData.humidity); |
|
|
|
if (!qIsNaN(weatherData.pressure)) |
|
data.insert(QStringLiteral("Pressure"), weatherData.pressure); |
|
|
|
if (!qIsNaN(weatherData.dewpoint)) |
|
data.insert(QStringLiteral("Dewpoint"), weatherData.dewpoint); |
|
|
|
if (!qIsNaN(weatherData.windSpeed)) |
|
data.insert(QStringLiteral("Wind Speed"), weatherData.windSpeed); |
|
else |
|
data.insert(QStringLiteral("Wind Speed"), weatherData.windSpeedAlt); |
|
|
|
if (!qIsNaN(weatherData.gustSpeed)) |
|
data.insert(QStringLiteral("Wind Gust Speed"), weatherData.gustSpeed); |
|
else |
|
data.insert(QStringLiteral("Wind Gust Speed"), weatherData.gustSpeedAlt); |
|
|
|
if (!weatherData.windDirection.isEmpty()) { |
|
data.insert(QStringLiteral("Wind Direction"), weatherData.windDirection); |
|
} else { |
|
data.insert(QStringLiteral("Wind Direction"), weatherData.windDirectionAlt); |
|
} |
|
|
|
int dayNumber = 0; |
|
for (const WeatherData::ForecastInfo *dayForecast : weatherData.forecasts) { |
|
if (dayNumber > 0) { |
|
QString period = dayForecast->period.toString(QStringLiteral("dddd")); |
|
|
|
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")); |
|
|
|
data.insert(QStringLiteral("Short Forecast Day %1").arg(dayNumber), |
|
QStringLiteral("%1|%2|%3|%4|%5|%6") |
|
.arg(period, dayForecast->iconName, QLatin1String("")) |
|
.arg(dayForecast->tempHigh) |
|
.arg(dayForecast->tempLow) |
|
.arg(QLatin1String(""))); |
|
dayNumber++; |
|
} else { |
|
data.insert(QStringLiteral("Short Forecast Day %1").arg(dayNumber), |
|
QStringLiteral("%1|%2|%3|%4|%5|%6") |
|
.arg(i18nc("Short for Today", "Today"), dayForecast->iconName, QLatin1String("")) |
|
.arg(dayForecast->tempHigh) |
|
.arg(dayForecast->tempLow) |
|
.arg(QLatin1String(""))); |
|
dayNumber++; |
|
} |
|
} |
|
|
|
int k = 0; |
|
|
|
for (const WeatherData::WarningInfo *warning : weatherData.warnings) { |
|
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 Timestamp ") + number, warning->timestamp.toString(QStringLiteral("dd.MM.yyyy"))); |
|
|
|
++k; |
|
} |
|
|
|
data.insert(QStringLiteral("Total Weather Days"), weatherData.forecasts.size()); |
|
data.insert(QStringLiteral("Total Warnings Issued"), weatherData.warnings.size()); |
|
data.insert(QStringLiteral("Credit"), i18nc("credit line, don't change name!", "Source: Deutscher Wetterdienst")); |
|
data.insert(QStringLiteral("Credit Url"), QStringLiteral("https://www.dwd.de/")); |
|
|
|
setData(weatherSource, data); |
|
} |
|
|
|
/* |
|
* Helper methods |
|
*/ |
|
|
|
// e.g. DWD API int 17 equals 1.7 |
|
float DWDIon::parseNumber(int number) |
|
{ |
|
return ((float)number) / 10; |
|
} |
|
|
|
QString DWDIon::roundWindDirections(int windDirection) |
|
{ |
|
QString roundedWindDirection = QString::number(qRound(((float)windDirection) / 100) * 10); |
|
return roundedWindDirection; |
|
} |
|
|
|
QString DWDIon::extractString(QByteArray array, int start, int length) |
|
{ |
|
QString string; |
|
|
|
for (int i = start; i < start + length; i++) { |
|
string.append(QLatin1Char(array[i])); |
|
} |
|
|
|
return string; |
|
} |
|
|
|
QString DWDIon::camelCaseString(const QString text) |
|
{ |
|
QString result; |
|
bool nextBig = true; |
|
|
|
for (QChar c : text) { |
|
if (c.isLetter()) { |
|
if (nextBig) { |
|
result.append(c.toUpper()); |
|
nextBig = false; |
|
} else { |
|
result.append(c.toLower()); |
|
} |
|
} else { |
|
if (c == QChar::Space || c == QLatin1Char('-')) { |
|
nextBig = true; |
|
} |
|
result.append(c); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
K_PLUGIN_CLASS_WITH_JSON(DWDIon, "ion-dwd.json") |
|
|
|
#include "ion_dwd.moc"
|
|
|