diff options
-rw-r--r-- | examples/positioning/weatherinfo/appmodel.cpp | 146 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/appmodel.h | 6 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/openweathermapbackend.cpp | 36 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/openweathermapbackend.h | 8 | ||||
-rw-r--r-- | examples/positioning/weatherinfo/providerbackend.h | 16 |
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 ¤tWeather) { 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 ¤tWeather) { 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 ¤tWeather); private: - void requestCurrentWeather(QUrlQuery &query); - void requestWeatherForecast(const QString &city, const WeatherInfo ¤tWeather); + void requestCurrentWeather(QUrlQuery &query, const QGeoCoordinate &coordinate); + void requestWeatherForecast(const LocationInfo &location, const WeatherInfo ¤tWeather); 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(); }; |