// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "weatherapibackend.h" #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(requestsLog) static QString niceTemperatureString(double t) { return QString::number(qRound(t)) + QChar(0xB0); } /* Converts weather code to a string that will be used to show the icon. The possible strings are based on the icon names. The icon name is built up as follows: weather-[mystring].png where [mystring] is the value returned by this method. Check resources for the full list of available icons. */ static QString weatherCodeToString(int code) { switch (code) { case 1000: return "sunny"; case 1003: return "sunny-very-few-clouds"; case 1006: return "few-clouds"; case 1009: return "overcast"; case 1030: case 1135: case 1147: return "fog"; case 1063: case 1072: case 1150: case 1153: case 1168: case 1171: case 1180: case 1183: case 1186: case 1189: case 1198: return "showers-scattered"; case 1066: case 1069: case 1114: case 1117: case 1210: case 1213: case 1216: case 1219: case 1222: case 1225: case 1237: case 1255: case 1258: case 1261: case 1264: case 1279: case 1282: return "snow"; case 1087: return "storm"; case 1192: case 1195: case 1201: case 1240: case 1243: case 1246: return "showers"; case 1204: case 1207: case 1249: case 1252: return "sleet"; case 1273: case 1276: return "thundershower"; default: return "sunny"; } return "sunny"; // default choice } static void parseWeatherDescription(const QJsonObject &object, WeatherInfo &info) { const QJsonObject conditionObject = object.value(u"condition").toObject(); info.m_weatherDescription = conditionObject.value(u"text").toString(); info.m_weatherIconId = weatherCodeToString(conditionObject.value(u"code").toInt()); } WeatherApiBackend::WeatherApiBackend(QObject *parent) : ProviderBackend(parent), m_networkManager(new QNetworkAccessManager(this)), m_apiKey(QStringLiteral("8edde160c63c4be6b77101828211208")) { } void WeatherApiBackend::requestWeatherInfo(const QString &city) { generateWeatherRequest(city, QGeoCoordinate()); } void WeatherApiBackend::requestWeatherInfo(const QGeoCoordinate &coordinate) { const QString coordinateStr = QString("%1,%2").arg(coordinate.latitude()).arg(coordinate.longitude()); generateWeatherRequest(coordinateStr, coordinate); } void WeatherApiBackend::handleWeatherForecastReply(QNetworkReply *reply, const QGeoCoordinate &coordinate) { if (!reply) { emit errorOccurred(); return; } bool parsed = false; if (!reply->error()) { QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); const QJsonObject documentObject = document.object(); const QJsonObject locationObject = documentObject.value(u"location").toObject(); // extract location name LocationInfo location; location.m_name = locationObject.value(u"name").toString(); if (coordinate.isValid()) location.m_coordinate = coordinate; qCDebug(requestsLog) << "Got weather for" << location.m_name; // extract current weather WeatherInfo currentWeather; const QJsonObject currentWeatherObject = documentObject.value(u"current").toObject(); const QJsonValue temperature = currentWeatherObject.value(u"temp_c"); if (temperature.isDouble()) currentWeather.m_temperature = niceTemperatureString(temperature.toDouble()); parseWeatherDescription(currentWeatherObject, currentWeather); parsed = !location.m_name.isEmpty() && !currentWeather.m_temperature.isEmpty(); if (parsed) { QList weatherDetails; weatherDetails << currentWeather; // extract forecast details const QJsonObject forecastObject = documentObject.value(u"forecast").toObject(); const QJsonArray forecastDays = forecastObject.value(u"forecastday").toArray(); for (qsizetype i = 0; i < forecastDays.size(); ++i) { const QJsonObject dayInfo = forecastDays.at(i).toObject(); WeatherInfo info; const QDateTime dt = QDateTime::fromSecsSinceEpoch(dayInfo.value(u"date_epoch").toInteger()); info.m_dayOfWeek = dt.toString(u"ddd"); const QJsonObject dayObject = dayInfo.value(u"day").toObject(); const QJsonValue minTemp = dayObject.value(u"mintemp_c"); const QJsonValue maxTemp = dayObject.value(u"maxtemp_c"); if (minTemp.isDouble() && maxTemp.isDouble()) { info.m_temperature = niceTemperatureString(minTemp.toDouble()) + QChar('/') + niceTemperatureString(maxTemp.toDouble()); } else { qCDebug(requestsLog, "Failed to parse min or max temperature."); } parseWeatherDescription(dayObject, info); if (!info.m_temperature.isEmpty() && !info.m_weatherIconId.isEmpty()) weatherDetails.push_back(info); } emit weatherInformation(location, weatherDetails); } } if (!parsed) { emit errorOccurred(); if (reply->error()) qCDebug(requestsLog) << reply->errorString(); else qCDebug(requestsLog, "Failed to parse weather JSON."); } reply->deleteLater(); } void WeatherApiBackend::generateWeatherRequest(const QString &locationString, const QGeoCoordinate &coordinate) { QUrl url("https://api.weatherapi.com/v1/forecast.json"); QUrlQuery query; query.addQueryItem(QStringLiteral("key"), m_apiKey); query.addQueryItem(QStringLiteral("q"), locationString); query.addQueryItem(QStringLiteral("days"), QStringLiteral("4")); query.addQueryItem(QStringLiteral("aqi"), QStringLiteral("no")); query.addQueryItem(QStringLiteral("alerts"), QStringLiteral("no")); url.setQuery(query); QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url)); connect(reply, &QNetworkReply::finished, this, [this, reply, coordinate]() { handleWeatherForecastReply(reply, coordinate); }); }