summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2021-08-16 16:12:06 +0200
committerIvan Solovev <ivan.solovev@qt.io>2021-08-24 09:44:08 +0200
commit0d908c344c80cfcadb5ea1bb964bd4d8332d8803 (patch)
tree41cc978193c5ea6d6e634a2bb8d979a9907d3d1e
parent5de1a40705bbb6b60b5d9fd1010e847743676734 (diff)
downloadqtlocation-0d908c344c80cfcadb5ea1bb964bd4d8332d8803.tar.gz
Introduce weather data cache
This patch introduces weather data cache. Before sending a request to weather provider, the cache is checked, and if the corresponding weather information exists in the cache and is not outdated, then it's used in the application. While working with GPS location, the cache does not perform a direct comparison of coordinates. Instead it checks if the new coordinate is in the specified area around the cached one, or not. Task-number: QTBUG-60467 Pick-to: 6.2 Change-Id: I150a1023587be656ee8b190d1c2bce667c6301e2 Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
-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();
};