summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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