diff options
-rw-r--r-- | examples/positioning/weatherinfo/CMakeLists.txt | 2 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/appmodel.cpp | 348 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/appmodel.h | 10 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/openweathermapbackend.cpp | 230 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/openweathermapbackend.h | 85 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/providerbackend.cpp | 53 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/providerbackend.h | 84 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/weatherinfo.pro | 8 |
8 files changed, 545 insertions, 275 deletions
diff --git a/examples/positioning/weatherinfo/CMakeLists.txt b/examples/positioning/weatherinfo/CMakeLists.txt index 07407fd2..01aed5ca 100644 --- a/examples/positioning/weatherinfo/CMakeLists.txt +++ b/examples/positioning/weatherinfo/CMakeLists.txt @@ -24,6 +24,8 @@ find_package(Qt6 COMPONENTS Quick) qt_add_executable(weatherinfo appmodel.cpp appmodel.h + providerbackend.cpp providerbackend.h + openweathermapbackend.cpp openweathermapbackend.h main.cpp ) set_target_properties(weatherinfo PROPERTIES diff --git a/examples/positioning/weatherinfo/appmodel.cpp b/examples/positioning/weatherinfo/appmodel.cpp index 687ab073..63c00cb3 100644 --- a/examples/positioning/weatherinfo/appmodel.cpp +++ b/examples/positioning/weatherinfo/appmodel.cpp @@ -49,29 +49,13 @@ ****************************************************************************/ #include "appmodel.h" +#include "openweathermapbackend.h" -#include <qgeopositioninfosource.h> -#include <qgeosatelliteinfosource.h> -#include <qnmeapositioninfosource.h> -#include <qgeopositioninfo.h> -#include <qnetworkaccessmanager.h> - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QStringList> -#include <QTimer> -#include <QUrlQuery> -#include <QElapsedTimer> +#include <QGeoPositionInfoSource> +#include <QGeoPositionInfo> #include <QLoggingCategory> -/* - *This application uses http://openweathermap.org/api - **/ - -#define ZERO_KELVIN 273.15 - -Q_LOGGING_CATEGORY(requestsLog,"wapp.requests") +Q_LOGGING_CATEGORY(requestsLog, "wapp.requests") WeatherData::WeatherData(QObject *parent) : QObject(parent) @@ -79,7 +63,7 @@ WeatherData::WeatherData(QObject *parent) : } WeatherData::WeatherData(const WeatherData &other) : - QObject(0), + QObject(nullptr), m_dayOfWeek(other.m_dayOfWeek), m_weather(other.m_weather), m_weatherDescription(other.m_weatherDescription), @@ -87,6 +71,15 @@ WeatherData::WeatherData(const WeatherData &other) : { } +WeatherData::WeatherData(const WeatherInfo &other) + : QObject(nullptr), + m_dayOfWeek(other.m_dayOfWeek), + m_weather(other.m_weatherIconId), + m_weatherDescription(other.m_weatherDescription), + m_temperature(other.m_temperature) +{ +} + QString WeatherData::dayOfWeek() const { return m_dayOfWeek; @@ -143,42 +136,32 @@ void WeatherData::setTemperature(const QString &value) class AppModelPrivate { public: - static const int baseMsBeforeNewRequest = 5 * 1000; // 5 s, increased after each missing answer up to 10x - QGeoPositionInfoSource *src; + QGeoPositionInfoSource *src = nullptr; QGeoCoordinate coord; - QString longitude, latitude; QString city; - QNetworkAccessManager *nam; WeatherData now; QList<WeatherData*> forecast; - QQmlListProperty<WeatherData> *fcProp; - bool ready; - bool useGps; - QElapsedTimer throttle; - int nErrors; - int minMsBeforeNewRequest; - QTimer delayedCityRequestTimer; - QTimer requestNewWeatherTimer; - QString app_ident; - - AppModelPrivate() : - src(NULL), - nam(NULL), - fcProp(NULL), - ready(false), - useGps(true), - nErrors(0), - minMsBeforeNewRequest(baseMsBeforeNewRequest) - { - delayedCityRequestTimer.setSingleShot(true); - delayedCityRequestTimer.setInterval(1000); // 1 s - requestNewWeatherTimer.setSingleShot(false); - requestNewWeatherTimer.setInterval(20*60*1000); // 20 min - throttle.invalidate(); - app_ident = QStringLiteral("36496bad1955bf3365448965a42b9eac"); - } + QQmlListProperty<WeatherData> *fcProp = nullptr; + bool ready = false; + bool useGps = true; + OpenWeatherMapBackend m_openWeatherBackend; + + void requestWeatherByCoordinates(); + void requestWeatherByCity(); }; +void AppModelPrivate::requestWeatherByCoordinates() +{ + // TODO - add support for weather data cache + m_openWeatherBackend.requestWeatherInfo(coord); +} + +void AppModelPrivate::requestWeatherByCity() +{ + // TODO - add support for weather data cache + m_openWeatherBackend.requestWeatherInfo(city); +} + static void forecastAppend(QQmlListProperty<WeatherData> *prop, WeatherData *val) { Q_UNUSED(val); @@ -208,21 +191,15 @@ AppModel::AppModel(QObject *parent) : d(new AppModelPrivate) { //! [0] - d->fcProp = new QQmlListProperty<WeatherData>(this, d, - forecastAppend, - forecastCount, - forecastAt, - forecastClear); - - connect(&d->delayedCityRequestTimer, SIGNAL(timeout()), - this, SLOT(queryCity())); - connect(&d->requestNewWeatherTimer, SIGNAL(timeout()), - this, SLOT(refreshWeather())); - d->requestNewWeatherTimer.start(); + d->fcProp = new QQmlListProperty<WeatherData>(this, d, forecastAppend, + forecastCount, + forecastAt, + forecastClear); + connect(&d->m_openWeatherBackend, &ProviderBackend::weatherInformation, + this, &AppModel::handleWeatherData); //! [1] - d->nam = new QNetworkAccessManager(this); d->src = QGeoPositionInfoSource::createDefaultSource(this); if (d->src) { @@ -236,7 +213,7 @@ AppModel::AppModel(QObject *parent) : d->useGps = false; d->city = "Brisbane"; emit cityChanged(); - this->refreshWeather(); + d->requestWeatherByCity(); } } //! [1] @@ -245,6 +222,11 @@ AppModel::~AppModel() { if (d->src) d->src->stopUpdates(); + if (d->fcProp) + delete d->fcProp; + foreach (WeatherData *inf, d->forecast) + delete inf; + d->forecast.clear(); delete d; } @@ -253,46 +235,13 @@ void AppModel::positionUpdated(QGeoPositionInfo gpsPos) { d->coord = gpsPos.coordinate(); - if (!(d->useGps)) + if (!d->useGps) return; - queryCity(); + d->requestWeatherByCoordinates(); } //! [2] -void AppModel::queryCity() -{ - //don't update more often then once a minute - //to keep load on server low - if (d->throttle.isValid() && d->throttle.elapsed() < d->minMsBeforeNewRequest ) { - qCDebug(requestsLog) << "delaying query of city"; - if (!d->delayedCityRequestTimer.isActive()) - d->delayedCityRequestTimer.start(); - return; - } - qDebug(requestsLog) << "requested query of city"; - d->throttle.start(); - d->minMsBeforeNewRequest = (d->nErrors + 1) * d->baseMsBeforeNewRequest; - - QString latitude, longitude; - longitude.setNum(d->coord.longitude()); - latitude.setNum(d->coord.latitude()); - - QUrl url("http://api.openweathermap.org/data/2.5/weather"); - QUrlQuery query; - query.addQueryItem("lat", latitude); - query.addQueryItem("lon", longitude); - query.addQueryItem("mode", "json"); - query.addQueryItem("APPID", d->app_ident); - url.setQuery(query); - qCDebug(requestsLog) << "submitting request"; - - QNetworkReply *rep = d->nam->get(QNetworkRequest(url)); - // connect up the signal right away - connect(rep, &QNetworkReply::finished, - this, [this, rep]() { handleGeoNetworkData(rep); }); -} - void AppModel::positionError(QGeoPositionInfoSource::Error e) { Q_UNUSED(e); @@ -300,57 +249,13 @@ void AppModel::positionError(QGeoPositionInfoSource::Error e) // cleanup insufficient QGeoPositionInfoSource instance d->src->stopUpdates(); d->src->deleteLater(); - d->src = 0; + d->src = nullptr; // activate simulation mode d->useGps = false; d->city = "Brisbane"; emit cityChanged(); - this->refreshWeather(); -} - -void AppModel::hadError(bool tryAgain) -{ - qCDebug(requestsLog) << "hadError, will " << (tryAgain ? "" : "not ") << "rety"; - d->throttle.start(); - if (d->nErrors < 10) - ++d->nErrors; - d->minMsBeforeNewRequest = (d->nErrors + 1) * d->baseMsBeforeNewRequest; - if (tryAgain) - d->delayedCityRequestTimer.start(); -} - -void AppModel::handleGeoNetworkData(QNetworkReply *networkReply) -{ - if (!networkReply) { - hadError(false); // should retry? - return; - } - - if (!networkReply->error()) { - d->nErrors = 0; - if (!d->throttle.isValid()) - d->throttle.start(); - d->minMsBeforeNewRequest = d->baseMsBeforeNewRequest; - //convert coordinates to city name - QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll()); - - QJsonObject jo = document.object(); - QJsonValue jv = jo.value(QStringLiteral("name")); - - const QString city = jv.toString(); - qCDebug(requestsLog) << "got city: " << city; - // The reply is asynchronous, so it can come also after we switched - // to the next city. In this case we should not handle it. - if (city != d->city && d->useGps) { - d->city = city; - emit cityChanged(); - refreshWeather(); - } - } else { - hadError(true); - } - networkReply->deleteLater(); + d->requestWeatherByCity(); } void AppModel::refreshWeather() @@ -360,136 +265,46 @@ void AppModel::refreshWeather() return; } qCDebug(requestsLog) << "refreshing weather"; - QUrl url("http://api.openweathermap.org/data/2.5/weather"); - QUrlQuery query; - - query.addQueryItem("q", d->city); - query.addQueryItem("mode", "json"); - query.addQueryItem("APPID", d->app_ident); - url.setQuery(query); - - QNetworkReply *rep = d->nam->get(QNetworkRequest(url)); - // connect up the signal right away - connect(rep, &QNetworkReply::finished, - this, [this, rep]() { handleWeatherNetworkData(rep); }); -} - -static QString niceTemperatureString(double t) -{ - return QString::number(qRound(t-ZERO_KELVIN)) + QChar(0xB0); + d->requestWeatherByCity(); } -void AppModel::handleWeatherNetworkData(QNetworkReply *networkReply) +void AppModel::handleWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails) { - qCDebug(requestsLog) << "got weather network data"; - if (!networkReply) + // Check that we didn't get outdated weather data. The city should match, + // if only we do not use GPS. + if (city != d->city && !d->useGps) return; - if (!networkReply->error()) { - foreach (WeatherData *inf, d->forecast) - delete inf; - d->forecast.clear(); - - QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll()); - - if (document.isObject()) { - QJsonObject obj = document.object(); - QJsonObject tempObject; - QJsonValue val; - - if (obj.contains(QStringLiteral("weather"))) { - val = obj.value(QStringLiteral("weather")); - QJsonArray weatherArray = val.toArray(); - val = weatherArray.at(0); - tempObject = val.toObject(); - d->now.setWeatherDescription(tempObject.value(QStringLiteral("description")).toString()); - d->now.setWeatherIcon(tempObject.value("icon").toString()); - } - if (obj.contains(QStringLiteral("main"))) { - val = obj.value(QStringLiteral("main")); - tempObject = val.toObject(); - val = tempObject.value(QStringLiteral("temp")); - d->now.setTemperature(niceTemperatureString(val.toDouble())); - } - } + if (city != d->city && d->useGps) { + d->city = city; + emit cityChanged(); } - networkReply->deleteLater(); - //retrieve the forecast - QUrl url("http://api.openweathermap.org/data/2.5/forecast/daily"); - QUrlQuery query; - - query.addQueryItem("q", d->city); - query.addQueryItem("mode", "json"); - query.addQueryItem("cnt", "5"); - query.addQueryItem("APPID", d->app_ident); - url.setQuery(query); - - QNetworkReply *rep = d->nam->get(QNetworkRequest(url)); - // connect up the signal right away - connect(rep, &QNetworkReply::finished, - this, [this, rep]() { handleForecastNetworkData(rep); }); -} + // delete previous forecast + foreach (WeatherData *inf, d->forecast) + delete inf; + d->forecast.clear(); -void AppModel::handleForecastNetworkData(QNetworkReply *networkReply) -{ - qCDebug(requestsLog) << "got forecast"; - if (!networkReply) - return; + // The first item in the list represents current weather. + if (!weatherDetails.isEmpty()) { + d->now.setTemperature(weatherDetails.first().m_temperature); + d->now.setWeatherIcon(weatherDetails.first().m_weatherIconId); + d->now.setWeatherDescription(weatherDetails.first().m_weatherDescription); + } - if (!networkReply->error()) { - QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll()); - - QJsonObject jo; - QJsonValue jv; - QJsonObject root = document.object(); - jv = root.value(QStringLiteral("list")); - if (!jv.isArray()) - qWarning() << "Invalid forecast object"; - QJsonArray ja = jv.toArray(); - //we need 4 days of forecast -> first entry is today - if (ja.count() != 5) - qWarning() << "Invalid forecast object"; - - QString data; - for (int i = 1; i<ja.count(); i++) { - WeatherData *forecastEntry = new WeatherData(); - - //min/max temperature - QJsonObject subtree = ja.at(i).toObject(); - jo = subtree.value(QStringLiteral("temp")).toObject(); - jv = jo.value(QStringLiteral("min")); - data.clear(); - data += niceTemperatureString(jv.toDouble()); - data += QChar('/'); - jv = jo.value(QStringLiteral("max")); - data += niceTemperatureString(jv.toDouble()); - forecastEntry->setTemperature(data); - - //get date - jv = subtree.value(QStringLiteral("dt")); - QDateTime dt = QDateTime::fromMSecsSinceEpoch((qint64)jv.toDouble()*1000); - forecastEntry->setDayOfWeek(dt.date().toString(QStringLiteral("ddd"))); - - //get icon - QJsonArray weatherArray = subtree.value(QStringLiteral("weather")).toArray(); - jo = weatherArray.at(0).toObject(); - forecastEntry->setWeatherIcon(jo.value(QStringLiteral("icon")).toString()); - - //get description - forecastEntry->setWeatherDescription(jo.value(QStringLiteral("description")).toString()); - - d->forecast.append(forecastEntry); - } - - if (!(d->ready)) { - d->ready = true; - emit readyChanged(); - } + // The other items represent weather forecast. The amount of items depends + // on the provider backend. + for (qsizetype i = 1; i < weatherDetails.size(); ++i) { + WeatherData *forecastEntry = new WeatherData(weatherDetails.at(i)); + d->forecast.append(forecastEntry); + } - emit weatherChanged(); + if (!d->ready) { + d->ready = true; + emit readyChanged(); } - networkReply->deleteLater(); + + emit weatherChanged(); } bool AppModel::hasValidCity() const @@ -534,13 +349,12 @@ void AppModel::setUseGps(bool value) d->useGps = value; if (value) { d->city = ""; - d->throttle.invalidate(); emit cityChanged(); emit weatherChanged(); // if we already have a valid GPS position, do not wait until it // updates, but query the city immediately if (d->coord.isValid()) - queryCity(); + d->requestWeatherByCoordinates(); } emit useGpsChanged(); } @@ -554,5 +368,5 @@ void AppModel::setCity(const QString &value) { d->city = value; emit cityChanged(); - refreshWeather(); + d->requestWeatherByCity(); } diff --git a/examples/positioning/weatherinfo/appmodel.h b/examples/positioning/weatherinfo/appmodel.h index 44c8f9f9..5bec89e8 100644 --- a/examples/positioning/weatherinfo/appmodel.h +++ b/examples/positioning/weatherinfo/appmodel.h @@ -59,6 +59,8 @@ #include <QtPositioning/QGeoPositionInfoSource> +#include "providerbackend.h" + //! [0] class WeatherData : public QObject { Q_OBJECT @@ -79,6 +81,7 @@ class WeatherData : public QObject { public: explicit WeatherData(QObject *parent = 0); WeatherData(const WeatherData &other); + WeatherData(const WeatherInfo &other); QString dayOfWeek() const; QString weatherIcon() const; @@ -145,7 +148,6 @@ public: bool hasValidCity() const; bool hasValidWeather() const; void setUseGps(bool value); - void hadError(bool tryAgain); QString city() const; void setCity(const QString &value); @@ -158,12 +160,9 @@ public slots: //! [2] private slots: - void queryCity(); void positionUpdated(QGeoPositionInfo gpsPos); void positionError(QGeoPositionInfoSource::Error e); - void handleGeoNetworkData(QNetworkReply *networkReply); - void handleWeatherNetworkData(QNetworkReply *networkReply); - void handleForecastNetworkData(QNetworkReply *networkReply); + void handleWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails); //! [3] signals: @@ -171,7 +170,6 @@ signals: void useGpsChanged(); void cityChanged(); void weatherChanged(); - //! [3] private: diff --git a/examples/positioning/weatherinfo/openweathermapbackend.cpp b/examples/positioning/weatherinfo/openweathermapbackend.cpp new file mode 100644 index 00000000..d0853a2d --- /dev/null +++ b/examples/positioning/weatherinfo/openweathermapbackend.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "openweathermapbackend.h" + +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QUrlQuery> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> +#include <QJsonArray> +#include <QGeoCoordinate> +#include <QLoggingCategory> + +Q_DECLARE_LOGGING_CATEGORY(requestsLog) + +static constexpr auto kZeroKelvin = 273.15; + +static QString niceTemperatureString(double t) +{ + return QString::number(qRound(t - kZeroKelvin)) + QChar(0xB0); +} + +static void parseWeatherDescription(const QJsonObject &object, WeatherInfo &info) +{ + const QJsonArray weatherArray = object.value(u"weather").toArray(); + if (!weatherArray.isEmpty()) { + const QJsonObject obj = weatherArray.first().toObject(); + info.m_weatherDescription = obj.value(u"description").toString(); + // TODO - convert to some common string + info.m_weatherIconId = obj.value(u"icon").toString(); + } else { + qCDebug(requestsLog, "An empty weather array is returned."); + } +} + +OpenWeatherMapBackend::OpenWeatherMapBackend(QObject *parent) + : ProviderBackend(parent), + m_networkManager(new QNetworkAccessManager(this)), + m_appId(QStringLiteral("36496bad1955bf3365448965a42b9eac")) +{ +} + +void OpenWeatherMapBackend::requestWeatherInfo(const QString &city) +{ + QUrlQuery query; + query.addQueryItem(QStringLiteral("q"), city); + + requestCurrentWeather(query); +} + +void OpenWeatherMapBackend::requestWeatherInfo(const QGeoCoordinate &coordinate) +{ + QUrlQuery query; + query.addQueryItem(QStringLiteral("lat"), QString::number(coordinate.latitude())); + query.addQueryItem(QStringLiteral("lon"), QString::number(coordinate.longitude())); + + requestCurrentWeather(query); +} + +void OpenWeatherMapBackend::handleCurrentWeatherReply(QNetworkReply *reply) +{ + if (!reply) { + emit errorOccurred(); + return; + } + bool parsed = false; + if (!reply->error()) { + // extract info about current weather + const QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + const QJsonObject documentObject = document.object(); + + const QString city = documentObject.value(u"name").toString(); + qCDebug(requestsLog) << "Got current weather for" << city; + + WeatherInfo currentWeather; + + parseWeatherDescription(documentObject, currentWeather); + + const QJsonObject mainObject = documentObject.value(u"main").toObject(); + const QJsonValue tempValue = mainObject.value(u"temp"); + if (tempValue.isDouble()) + currentWeather.m_temperature = niceTemperatureString(tempValue.toDouble()); + else + qCDebug(requestsLog, "Failed to parse current temperature."); + + parsed = !city.isEmpty() && !currentWeather.m_temperature.isEmpty(); + + if (parsed) { + // request forecast + requestWeatherForecast(city, currentWeather); + } + } + if (!parsed) { + emit errorOccurred(); + if (reply->error()) + qCDebug(requestsLog) << reply->errorString(); + else + qCDebug(requestsLog, "Failed to parse current weather JSON."); + } + + reply->deleteLater(); +} + +void OpenWeatherMapBackend::handleWeatherForecastReply(QNetworkReply *reply, const QString &city, + const WeatherInfo ¤tWeather) +{ + if (!reply) { + emit errorOccurred(); + return; + } + if (!reply->error()) { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + const QJsonObject documentObject = document.object(); + + QList<WeatherInfo> weatherDetails; + // current weather will be the first in the list + weatherDetails << currentWeather; + + QJsonArray daysList = documentObject.value(u"list").toArray(); + // include current day as well + for (qsizetype i = 0; i < daysList.size(); ++i) { + QJsonObject dayObject = daysList.at(i).toObject(); + WeatherInfo info; + + const QDateTime dt = QDateTime::fromSecsSinceEpoch(dayObject.value(u"dt").toInteger()); + info.m_dayOfWeek = dt.toString(u"ddd"); + + const QJsonObject tempObject = dayObject.value(u"temp").toObject(); + + const QJsonValue minTemp = tempObject.value(u"min"); + const QJsonValue maxTemp = tempObject.value(u"max"); + 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(city, weatherDetails); + } else { + emit errorOccurred(); + qCDebug(requestsLog) << reply->errorString(); + } + + reply->deleteLater(); +} + +void OpenWeatherMapBackend::requestCurrentWeather(QUrlQuery &query) +{ + QUrl url("http://api.openweathermap.org/data/2.5/weather"); + query.addQueryItem(QStringLiteral("mode"), QStringLiteral("json")); + query.addQueryItem(QStringLiteral("APPID"), m_appId); + url.setQuery(query); + + QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, this, + [this, reply]() { handleCurrentWeatherReply(reply); }); +} + +void OpenWeatherMapBackend::requestWeatherForecast(const QString &city, + const WeatherInfo ¤tWeather) +{ + QUrl url("http://api.openweathermap.org/data/2.5/forecast/daily"); + QUrlQuery query; + query.addQueryItem(QStringLiteral("q"), city); + query.addQueryItem(QStringLiteral("mode"), QStringLiteral("json")); + query.addQueryItem(QStringLiteral("cnt"), QStringLiteral("4")); + query.addQueryItem(QStringLiteral("APPID"), m_appId); + url.setQuery(query); + + QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, this, [this, reply, city, currentWeather]() { + handleWeatherForecastReply(reply, city, currentWeather); + }); +} diff --git a/examples/positioning/weatherinfo/openweathermapbackend.h b/examples/positioning/weatherinfo/openweathermapbackend.h new file mode 100644 index 00000000..91271ca0 --- /dev/null +++ b/examples/positioning/weatherinfo/openweathermapbackend.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef OPENWEATHERMAPBACKEND_H +#define OPENWEATHERMAPBACKEND_H + +#include "providerbackend.h" + +QT_BEGIN_NAMESPACE +class QNetworkAccessManager; +class QNetworkReply; +class QUrlQuery; +QT_END_NAMESPACE + +class OpenWeatherMapBackend : public ProviderBackend +{ + Q_OBJECT +public: + explicit OpenWeatherMapBackend(QObject *parent = nullptr); + ~OpenWeatherMapBackend() = default; + + void requestWeatherInfo(const QString &city) override; + void requestWeatherInfo(const QGeoCoordinate &coordinate) override; + +private slots: + void handleCurrentWeatherReply(QNetworkReply *reply); + void handleWeatherForecastReply(QNetworkReply *reply, const QString &city, + const WeatherInfo ¤tWeather); + +private: + void requestCurrentWeather(QUrlQuery &query); + void requestWeatherForecast(const QString &city, const WeatherInfo ¤tWeather); + + QNetworkAccessManager *m_networkManager; + const QString m_appId; +}; + +#endif // OPENWEATHERMAPBACKEND_H diff --git a/examples/positioning/weatherinfo/providerbackend.cpp b/examples/positioning/weatherinfo/providerbackend.cpp new file mode 100644 index 00000000..30c45209 --- /dev/null +++ b/examples/positioning/weatherinfo/providerbackend.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "providerbackend.h" + +ProviderBackend::ProviderBackend(QObject *parent) : QObject(parent) { } diff --git a/examples/positioning/weatherinfo/providerbackend.h b/examples/positioning/weatherinfo/providerbackend.h new file mode 100644 index 00000000..3909642e --- /dev/null +++ b/examples/positioning/weatherinfo/providerbackend.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROVIDERBACKEND_H +#define PROVIDERBACKEND_H + +#include <QObject> + +QT_BEGIN_NAMESPACE +class QGeoCoordinate; +QT_END_NAMESPACE + +struct WeatherInfo +{ + QString m_dayOfWeek; + QString m_weatherIconId; + QString m_weatherDescription; + QString m_temperature; +}; + +class ProviderBackend : public QObject +{ + Q_OBJECT +public: + explicit ProviderBackend(QObject *parent = nullptr); + + virtual void requestWeatherInfo(const QString &city) = 0; + virtual void requestWeatherInfo(const QGeoCoordinate &coordinate) = 0; + +signals: + // The first element in weatherDetails represents current weather. + // Next are the weather forecast, including the current day. + void weatherInformation(const QString &city, const QList<WeatherInfo> &weatherDetails); + void errorOccurred(); +}; + +#endif // PROVIDERBACKEND_H diff --git a/examples/positioning/weatherinfo/weatherinfo.pro b/examples/positioning/weatherinfo/weatherinfo.pro index 376bf3f5..b3ab37b2 100644 --- a/examples/positioning/weatherinfo/weatherinfo.pro +++ b/examples/positioning/weatherinfo/weatherinfo.pro @@ -8,7 +8,9 @@ QML_IMPORT_NAME = WeatherInfo QML_IMPORT_MAJOR_VERSION = 1 SOURCES += main.cpp \ - appmodel.cpp + appmodel.cpp \ + openweathermapbackend.cpp \ + providerbackend.cpp OTHER_FILES += weatherinfo.qml \ components/WeatherIcon.qml \ @@ -19,7 +21,9 @@ OTHER_FILES += weatherinfo.qml \ RESOURCES += weatherinfo.qrc -HEADERS += appmodel.h +HEADERS += appmodel.h \ + openweathermapbackend.h \ + providerbackend.h target.path = $$[QT_INSTALL_EXAMPLES]/positioning/weatherinfo INSTALLS += target |