summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/positioning/weatherinfo/appmodel.cpp146
-rw-r--r--examples/positioning/weatherinfo/appmodel.h6
-rw-r--r--examples/positioning/weatherinfo/openweathermapbackend.cpp36
-rw-r--r--examples/positioning/weatherinfo/openweathermapbackend.h8
-rw-r--r--examples/positioning/weatherinfo/providerbackend.h16
5 files changed, 164 insertions, 48 deletions
diff --git a/examples/positioning/weatherinfo/appmodel.cpp b/examples/positioning/weatherinfo/appmodel.cpp
index 63c00cb3..0d603b43 100644
--- a/examples/positioning/weatherinfo/appmodel.cpp
+++ b/examples/positioning/weatherinfo/appmodel.cpp
@@ -53,6 +53,7 @@
#include <QGeoPositionInfoSource>
#include <QGeoPositionInfo>
+#include <QGeoCircle>
#include <QLoggingCategory>
Q_LOGGING_CATEGORY(requestsLog, "wapp.requests")
@@ -133,6 +134,92 @@ void WeatherData::setTemperature(const QString &value)
emit dataChanged();
}
+/*
+ The class is used as a cache for the weather information.
+ It contains a map to cache weather for cities.
+ The gps location is cached separately.
+
+ For the coordiante search we do not compare the coordinate directly, but
+ check if it's within a circle of 3 km radius (we assume that the weather
+ does not really change within that radius).
+
+ The cache returns a pair with empty location and weather data if no data
+ is found, or if the data is outdated.
+*/
+class WeatherDataCache
+{
+public:
+ WeatherDataCache() = default;
+
+ using WeatherDataPair = QPair<QString, QList<WeatherInfo>>;
+
+ WeatherDataPair getWeatherData(const QString &name) const;
+ WeatherDataPair getWeatherData(const QGeoCoordinate &coordinate) const;
+
+ void addCacheElement(const LocationInfo &location, const QList<WeatherInfo> &info);
+
+ static bool isCacheResultValid(const WeatherDataPair &result);
+
+private:
+ struct CacheItem
+ {
+ qint64 m_cacheTime;
+ QList<WeatherInfo> m_weatherData;
+ };
+
+ QMap<QString, CacheItem> m_cityCache;
+
+ QGeoCoordinate m_gpsLocation;
+ QString m_gpsName;
+ CacheItem m_gpsData;
+
+ static const qint64 kCacheTimeoutInterval = 3600; // 1 hour
+ static const int kCircleRadius = 3000; // 3 km
+};
+
+WeatherDataCache::WeatherDataPair WeatherDataCache::getWeatherData(const QString &name) const
+{
+ if (m_cityCache.contains(name)) {
+ const qint64 currentTime = QDateTime::currentSecsSinceEpoch();
+ const auto &item = m_cityCache.value(name);
+ if (currentTime - item.m_cacheTime < kCacheTimeoutInterval)
+ return qMakePair(name, item.m_weatherData);
+ }
+ return qMakePair(QString(), QList<WeatherInfo>());
+}
+
+WeatherDataCache::WeatherDataPair WeatherDataCache::getWeatherData(const QGeoCoordinate &coordinate) const
+{
+ if (m_gpsLocation.isValid() && !m_gpsName.isEmpty()) {
+ const QGeoCircle area(m_gpsLocation, kCircleRadius);
+ if (area.contains(coordinate)) {
+ const qint64 currentTime = QDateTime::currentSecsSinceEpoch();
+ if (currentTime - m_gpsData.m_cacheTime < kCacheTimeoutInterval)
+ return qMakePair(m_gpsName, m_gpsData.m_weatherData);
+ }
+ }
+ return qMakePair(QString(), QList<WeatherInfo>());
+}
+
+void WeatherDataCache::addCacheElement(const LocationInfo &location, const QList<WeatherInfo> &info)
+{
+ // It it expected that we have valid QGeoCoordinate only when the weather
+ // is received based on coordinates.
+ const qint64 currentTime = QDateTime::currentSecsSinceEpoch();
+ if (location.m_coordinate.isValid()) {
+ m_gpsLocation = location.m_coordinate;
+ m_gpsName = location.m_name;
+ m_gpsData = { currentTime, info };
+ } else {
+ m_cityCache[location.m_name] = { currentTime, info };
+ }
+}
+
+bool WeatherDataCache::isCacheResultValid(const WeatherDataCache::WeatherDataPair &result)
+{
+ return !result.first.isEmpty() && !result.second.isEmpty();
+}
+
class AppModelPrivate
{
public:
@@ -144,24 +231,10 @@ public:
QQmlListProperty<WeatherData> *fcProp = nullptr;
bool ready = false;
bool useGps = true;
+ WeatherDataCache m_dataCache;
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);
@@ -213,7 +286,7 @@ AppModel::AppModel(QObject *parent) :
d->useGps = false;
d->city = "Brisbane";
emit cityChanged();
- d->requestWeatherByCity();
+ requestWeatherByCity();
}
}
//! [1]
@@ -238,7 +311,7 @@ void AppModel::positionUpdated(QGeoPositionInfo gpsPos)
if (!d->useGps)
return;
- d->requestWeatherByCoordinates();
+ requestWeatherByCoordinates();
}
//! [2]
@@ -255,7 +328,7 @@ void AppModel::positionError(QGeoPositionInfoSource::Error e)
d->useGps = false;
d->city = "Brisbane";
emit cityChanged();
- d->requestWeatherByCity();
+ requestWeatherByCity();
}
void AppModel::refreshWeather()
@@ -265,15 +338,22 @@ void AppModel::refreshWeather()
return;
}
qCDebug(requestsLog) << "refreshing weather";
- d->requestWeatherByCity();
+ requestWeatherByCity();
+}
+
+void AppModel::handleWeatherData(const LocationInfo &location,
+ const QList<WeatherInfo> &weatherDetails)
+{
+ if (applyWeatherData(location.m_name, weatherDetails))
+ d->m_dataCache.addCacheElement(location, weatherDetails);
}
-void AppModel::handleWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails)
+bool AppModel::applyWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails)
{
// 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;
+ return false;
if (city != d->city && d->useGps) {
d->city = city;
@@ -305,6 +385,26 @@ void AppModel::handleWeatherData(const QString &city, const QList<WeatherInfo> &
}
emit weatherChanged();
+
+ return true;
+}
+
+void AppModel::requestWeatherByCoordinates()
+{
+ const auto cacheResult = d->m_dataCache.getWeatherData(d->coord);
+ if (WeatherDataCache::isCacheResultValid(cacheResult))
+ applyWeatherData(cacheResult.first, cacheResult.second);
+ else
+ d->m_openWeatherBackend.requestWeatherInfo(d->coord);
+}
+
+void AppModel::requestWeatherByCity()
+{
+ const auto cacheResult = d->m_dataCache.getWeatherData(d->city);
+ if (WeatherDataCache::isCacheResultValid(cacheResult))
+ applyWeatherData(cacheResult.first, cacheResult.second);
+ else
+ d->m_openWeatherBackend.requestWeatherInfo(d->city);
}
bool AppModel::hasValidCity() const
@@ -354,7 +454,7 @@ void AppModel::setUseGps(bool value)
// if we already have a valid GPS position, do not wait until it
// updates, but query the city immediately
if (d->coord.isValid())
- d->requestWeatherByCoordinates();
+ requestWeatherByCoordinates();
}
emit useGpsChanged();
}
@@ -368,5 +468,5 @@ void AppModel::setCity(const QString &value)
{
d->city = value;
emit cityChanged();
- d->requestWeatherByCity();
+ requestWeatherByCity();
}
diff --git a/examples/positioning/weatherinfo/appmodel.h b/examples/positioning/weatherinfo/appmodel.h
index 5bec89e8..b9d48ef7 100644
--- a/examples/positioning/weatherinfo/appmodel.h
+++ b/examples/positioning/weatherinfo/appmodel.h
@@ -162,7 +162,7 @@ public slots:
private slots:
void positionUpdated(QGeoPositionInfo gpsPos);
void positionError(QGeoPositionInfoSource::Error e);
- void handleWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails);
+ void handleWeatherData(const LocationInfo &location, const QList<WeatherInfo> &weatherDetails);
//! [3]
signals:
@@ -173,6 +173,10 @@ signals:
//! [3]
private:
+ bool applyWeatherData(const QString &city, const QList<WeatherInfo> &weatherDetails);
+ void requestWeatherByCoordinates();
+ void requestWeatherByCity();
+
AppModelPrivate *d;
//! [4]
diff --git a/examples/positioning/weatherinfo/openweathermapbackend.cpp b/examples/positioning/weatherinfo/openweathermapbackend.cpp
index d0853a2d..94f2eec3 100644
--- a/examples/positioning/weatherinfo/openweathermapbackend.cpp
+++ b/examples/positioning/weatherinfo/openweathermapbackend.cpp
@@ -94,7 +94,7 @@ void OpenWeatherMapBackend::requestWeatherInfo(const QString &city)
QUrlQuery query;
query.addQueryItem(QStringLiteral("q"), city);
- requestCurrentWeather(query);
+ requestCurrentWeather(query, QGeoCoordinate());
}
void OpenWeatherMapBackend::requestWeatherInfo(const QGeoCoordinate &coordinate)
@@ -103,10 +103,11 @@ void OpenWeatherMapBackend::requestWeatherInfo(const QGeoCoordinate &coordinate)
query.addQueryItem(QStringLiteral("lat"), QString::number(coordinate.latitude()));
query.addQueryItem(QStringLiteral("lon"), QString::number(coordinate.longitude()));
- requestCurrentWeather(query);
+ requestCurrentWeather(query, coordinate);
}
-void OpenWeatherMapBackend::handleCurrentWeatherReply(QNetworkReply *reply)
+void OpenWeatherMapBackend::handleCurrentWeatherReply(QNetworkReply *reply,
+ const QGeoCoordinate &coordinate)
{
if (!reply) {
emit errorOccurred();
@@ -118,8 +119,11 @@ void OpenWeatherMapBackend::handleCurrentWeatherReply(QNetworkReply *reply)
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;
+ LocationInfo currentLocation;
+ currentLocation.m_name = documentObject.value(u"name").toString();
+ if (coordinate.isValid())
+ currentLocation.m_coordinate = coordinate;
+ qCDebug(requestsLog) << "Got current weather for" << currentLocation.m_name;
WeatherInfo currentWeather;
@@ -132,11 +136,11 @@ void OpenWeatherMapBackend::handleCurrentWeatherReply(QNetworkReply *reply)
else
qCDebug(requestsLog, "Failed to parse current temperature.");
- parsed = !city.isEmpty() && !currentWeather.m_temperature.isEmpty();
+ parsed = !currentLocation.m_name.isEmpty() && !currentWeather.m_temperature.isEmpty();
if (parsed) {
// request forecast
- requestWeatherForecast(city, currentWeather);
+ requestWeatherForecast(currentLocation, currentWeather);
}
}
if (!parsed) {
@@ -150,7 +154,8 @@ void OpenWeatherMapBackend::handleCurrentWeatherReply(QNetworkReply *reply)
reply->deleteLater();
}
-void OpenWeatherMapBackend::handleWeatherForecastReply(QNetworkReply *reply, const QString &city,
+void OpenWeatherMapBackend::handleWeatherForecastReply(QNetworkReply *reply,
+ const LocationInfo &location,
const WeatherInfo &currentWeather)
{
if (!reply) {
@@ -191,7 +196,7 @@ void OpenWeatherMapBackend::handleWeatherForecastReply(QNetworkReply *reply, con
weatherDetails.push_back(info);
}
- emit weatherInformation(city, weatherDetails);
+ emit weatherInformation(location, weatherDetails);
} else {
emit errorOccurred();
qCDebug(requestsLog) << reply->errorString();
@@ -200,7 +205,8 @@ void OpenWeatherMapBackend::handleWeatherForecastReply(QNetworkReply *reply, con
reply->deleteLater();
}
-void OpenWeatherMapBackend::requestCurrentWeather(QUrlQuery &query)
+void OpenWeatherMapBackend::requestCurrentWeather(QUrlQuery &query,
+ const QGeoCoordinate &coordinate)
{
QUrl url("http://api.openweathermap.org/data/2.5/weather");
query.addQueryItem(QStringLiteral("mode"), QStringLiteral("json"));
@@ -209,22 +215,22 @@ void OpenWeatherMapBackend::requestCurrentWeather(QUrlQuery &query)
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this,
- [this, reply]() { handleCurrentWeatherReply(reply); });
+ [this, reply, coordinate]() { handleCurrentWeatherReply(reply, coordinate); });
}
-void OpenWeatherMapBackend::requestWeatherForecast(const QString &city,
+void OpenWeatherMapBackend::requestWeatherForecast(const LocationInfo &location,
const WeatherInfo &currentWeather)
{
QUrl url("http://api.openweathermap.org/data/2.5/forecast/daily");
QUrlQuery query;
- query.addQueryItem(QStringLiteral("q"), city);
+ query.addQueryItem(QStringLiteral("q"), location.m_name);
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);
+ connect(reply, &QNetworkReply::finished, this, [this, reply, location, currentWeather]() {
+ handleWeatherForecastReply(reply, location, currentWeather);
});
}
diff --git a/examples/positioning/weatherinfo/openweathermapbackend.h b/examples/positioning/weatherinfo/openweathermapbackend.h
index 91271ca0..7f4e5f97 100644
--- a/examples/positioning/weatherinfo/openweathermapbackend.h
+++ b/examples/positioning/weatherinfo/openweathermapbackend.h
@@ -70,13 +70,13 @@ public:
void requestWeatherInfo(const QGeoCoordinate &coordinate) override;
private slots:
- void handleCurrentWeatherReply(QNetworkReply *reply);
- void handleWeatherForecastReply(QNetworkReply *reply, const QString &city,
+ void handleCurrentWeatherReply(QNetworkReply *reply, const QGeoCoordinate &coordinate);
+ void handleWeatherForecastReply(QNetworkReply *reply, const LocationInfo &location,
const WeatherInfo &currentWeather);
private:
- void requestCurrentWeather(QUrlQuery &query);
- void requestWeatherForecast(const QString &city, const WeatherInfo &currentWeather);
+ void requestCurrentWeather(QUrlQuery &query, const QGeoCoordinate &coordinate);
+ void requestWeatherForecast(const LocationInfo &location, const WeatherInfo &currentWeather);
QNetworkAccessManager *m_networkManager;
const QString m_appId;
diff --git a/examples/positioning/weatherinfo/providerbackend.h b/examples/positioning/weatherinfo/providerbackend.h
index 3909642e..c59f6c49 100644
--- a/examples/positioning/weatherinfo/providerbackend.h
+++ b/examples/positioning/weatherinfo/providerbackend.h
@@ -52,10 +52,7 @@
#define PROVIDERBACKEND_H
#include <QObject>
-
-QT_BEGIN_NAMESPACE
-class QGeoCoordinate;
-QT_END_NAMESPACE
+#include <QGeoCoordinate>
struct WeatherInfo
{
@@ -65,6 +62,12 @@ struct WeatherInfo
QString m_temperature;
};
+struct LocationInfo
+{
+ QString m_name;
+ QGeoCoordinate m_coordinate;
+};
+
class ProviderBackend : public QObject
{
Q_OBJECT
@@ -77,7 +80,10 @@ public:
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);
+ // The LocationInfo object should contain valid coordinate only when it was
+ // initially used to request the weather. If the city name was used, an
+ // empty coordinate is expected to be transferred.
+ void weatherInformation(const LocationInfo &location, const QList<WeatherInfo> &weatherDetails);
void errorOccurred();
};