summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2021-08-13 18:20:44 +0200
committerIvan Solovev <ivan.solovev@qt.io>2021-08-24 09:44:03 +0200
commit5de1a40705bbb6b60b5d9fd1010e847743676734 (patch)
treee5102d4f58d0a8e8a79b50c12895faee418db277 /examples
parent03b026e2c46ac8badd7391897b67a54358479bfa (diff)
downloadqtlocation-5de1a40705bbb6b60b5d9fd1010e847743676734.tar.gz
Move OpenWeatherMap support to separate backend class
The idea is to provide support for multiple backends in the weatherinfo application. This patch introduces class hierarchy for the backends and moves current OpenWeatherMap support to a separate class, so that it can later be replaced at runtime. During the refactoring the number of requests is also optimized (we now perform 2 requests instead of 3 to get the information about one city). Currently the OpenWeatherMap backend is used directly as a property of AppModelPrivate. More backends will be added in the following patches, and a mechanism to switch between them will be provided. The code for limiting the amount of the requests is currently removed. It will be replaced by a weather data cache in the following patches. As a drive-by: fixed a memory leak in the AppModel. Task-number: QTBUG-60467 Pick-to: 6.2 Change-Id: I90daac9451e4c14749a1725c4a04afce0d4b7467 Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Diffstat (limited to 'examples')
-rw-r--r--examples/positioning/weatherinfo/CMakeLists.txt2
-rw-r--r--examples/positioning/weatherinfo/appmodel.cpp348
-rw-r--r--examples/positioning/weatherinfo/appmodel.h10
-rw-r--r--examples/positioning/weatherinfo/openweathermapbackend.cpp230
-rw-r--r--examples/positioning/weatherinfo/openweathermapbackend.h85
-rw-r--r--examples/positioning/weatherinfo/providerbackend.cpp53
-rw-r--r--examples/positioning/weatherinfo/providerbackend.h84
-rw-r--r--examples/positioning/weatherinfo/weatherinfo.pro8
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 &currentWeather)
+{
+ 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 &currentWeather)
+{
+ 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 &currentWeather);
+
+private:
+ void requestCurrentWeather(QUrlQuery &query);
+ void requestWeatherForecast(const QString &city, const WeatherInfo &currentWeather);
+
+ 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