diff options
Diffstat (limited to 'examples/positioning/weatherinfo/appmodel.cpp')
-rw-r--r-- | examples/positioning/weatherinfo/appmodel.cpp | 514 |
1 files changed, 0 insertions, 514 deletions
diff --git a/examples/positioning/weatherinfo/appmodel.cpp b/examples/positioning/weatherinfo/appmodel.cpp deleted file mode 100644 index a5f207f1..00000000 --- a/examples/positioning/weatherinfo/appmodel.cpp +++ /dev/null @@ -1,514 +0,0 @@ -/**************************************************************************** -** -** 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 "appmodel.h" -#include "openweathermapbackend.h" -#include "weatherapibackend.h" - -#include <QGeoPositionInfoSource> -#include <QGeoPositionInfo> -#include <QGeoCircle> -#include <QLoggingCategory> - -Q_LOGGING_CATEGORY(requestsLog, "wapp.requests") - -WeatherData::WeatherData(QObject *parent) : - QObject(parent) -{ -} - -WeatherData::WeatherData(const WeatherData &other) : - QObject(nullptr), - m_dayOfWeek(other.m_dayOfWeek), - m_weather(other.m_weather), - m_weatherDescription(other.m_weatherDescription), - m_temperature(other.m_temperature) -{ -} - -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; -} - -/*! - * The icon value is based on OpenWeatherMap.org icon set. For details - * see http://bugs.openweathermap.org/projects/api/wiki/Weather_Condition_Codes - * - * e.g. 01d ->sunny day - * - * The icon string will be translated to - * http://openweathermap.org/img/w/01d.png - */ -QString WeatherData::weatherIcon() const -{ - return m_weather; -} - -QString WeatherData::weatherDescription() const -{ - return m_weatherDescription; -} - -QString WeatherData::temperature() const -{ - return m_temperature; -} - -void WeatherData::setDayOfWeek(const QString &value) -{ - m_dayOfWeek = value; - emit dataChanged(); -} - -void WeatherData::setWeatherIcon(const QString &value) -{ - m_weather = value; - emit dataChanged(); -} - -void WeatherData::setWeatherDescription(const QString &value) -{ - m_weatherDescription = value; - emit dataChanged(); -} - -void WeatherData::setTemperature(const QString &value) -{ - m_temperature = 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: - QGeoPositionInfoSource *src = nullptr; - QGeoCoordinate coord; - QString city; - WeatherData now; - QList<WeatherData*> forecast; - QQmlListProperty<WeatherData> *fcProp = nullptr; - bool ready = false; - bool useGps = true; - WeatherDataCache m_dataCache; - ProviderBackend *m_currentBackend = nullptr; - QList<ProviderBackend*> m_supportedBackends; - qsizetype m_currentBackendIndex = 0; -}; - -static void forecastAppend(QQmlListProperty<WeatherData> *prop, WeatherData *val) -{ - Q_UNUSED(val); - Q_UNUSED(prop); -} - -static WeatherData *forecastAt(QQmlListProperty<WeatherData> *prop, qsizetype index) -{ - AppModelPrivate *d = static_cast<AppModelPrivate*>(prop->data); - return d->forecast.at(index); -} - -static qsizetype forecastCount(QQmlListProperty<WeatherData> *prop) -{ - AppModelPrivate *d = static_cast<AppModelPrivate*>(prop->data); - return d->forecast.size(); -} - -static void forecastClear(QQmlListProperty<WeatherData> *prop) -{ - static_cast<AppModelPrivate*>(prop->data)->forecast.clear(); -} - -//! [0] -AppModel::AppModel(QObject *parent) : - QObject(parent), - d(new AppModelPrivate) -{ -//! [0] - d->fcProp = new QQmlListProperty<WeatherData>(this, d, forecastAppend, - forecastCount, - forecastAt, - forecastClear); - - d->m_supportedBackends.push_back(new OpenWeatherMapBackend(this)); - d->m_supportedBackends.push_back(new WeatherApiBackend(this)); - registerBackend(0); - -//! [1] - d->src = QGeoPositionInfoSource::createDefaultSource(this); - - if (d->src) { - d->useGps = true; - connect(d->src, SIGNAL(positionUpdated(QGeoPositionInfo)), - this, SLOT(positionUpdated(QGeoPositionInfo))); - connect(d->src, SIGNAL(errorOccurred(QGeoPositionInfoSource::Error)), - this, SLOT(positionError(QGeoPositionInfoSource::Error))); - d->src->startUpdates(); - } else { - d->useGps = false; - d->city = "Brisbane"; - emit cityChanged(); - requestWeatherByCity(); - } -} -//! [1] - -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; -} - -//! [2] -void AppModel::positionUpdated(QGeoPositionInfo gpsPos) -{ - d->coord = gpsPos.coordinate(); - - if (!d->useGps) - return; - - requestWeatherByCoordinates(); -} -//! [2] - -void AppModel::positionError(QGeoPositionInfoSource::Error e) -{ - Q_UNUSED(e); - qWarning() << "Position source error. Falling back to simulation mode."; - - // activate simulation mode - if (d->useGps) { - d->useGps = false; - d->city = "Brisbane"; - emit cityChanged(); - requestWeatherByCity(); - } -} - -void AppModel::refreshWeather() -{ - if (d->city.isEmpty()) { - qCDebug(requestsLog) << "refreshing weather skipped (no city)"; - return; - } - qCDebug(requestsLog) << "refreshing weather"; - 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::switchToNextBackend() -{ - deregisterCurrentBackend(); - registerBackend(d->m_currentBackendIndex + 1); - if (d->m_currentBackend) { - // repeat the query - if (d->useGps) - requestWeatherByCoordinates(); - else - requestWeatherByCity(); - } else { - qWarning("The application has iterated through all of the weather backends, " - "and none of them seems to respond now. Please wait until any of the " - "backends becomes available again."); - } -} - -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 false; - - if (city != d->city && d->useGps) { - d->city = city; - emit cityChanged(); - } - - // delete previous forecast - foreach (WeatherData *inf, d->forecast) - delete inf; - d->forecast.clear(); - - // 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); - } - - // 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); - } - - if (!d->ready) { - d->ready = true; - emit readyChanged(); - } - - 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 if (d->m_currentBackend) - d->m_currentBackend->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 if (d->m_currentBackend) - d->m_currentBackend->requestWeatherInfo(d->city); -} - -void AppModel::registerBackend(qsizetype index) -{ - if (index >= 0 && index < d->m_supportedBackends.size()) { - d->m_currentBackend = d->m_supportedBackends.at(index); - d->m_currentBackendIndex = index; - connect(d->m_currentBackend, &ProviderBackend::weatherInformation, - this, &AppModel::handleWeatherData); - connect(d->m_currentBackend, &ProviderBackend::errorOccurred, - this, &AppModel::switchToNextBackend); - } -} - -void AppModel::deregisterCurrentBackend() -{ - if (d->m_currentBackend) { - disconnect(d->m_currentBackend, &ProviderBackend::weatherInformation, - this, &AppModel::handleWeatherData); - disconnect(d->m_currentBackend, &ProviderBackend::errorOccurred, - this, &AppModel::switchToNextBackend); - d->m_currentBackend = nullptr; - } -} - -bool AppModel::hasValidCity() const -{ - return (!(d->city.isEmpty()) && d->city.size() > 1 && d->city != ""); -} - -bool AppModel::hasValidWeather() const -{ - return hasValidCity() && (!(d->now.weatherIcon().isEmpty()) && - (d->now.weatherIcon().size() > 1) && - d->now.weatherIcon() != ""); -} - -WeatherData *AppModel::weather() const -{ - return &(d->now); -} - -QQmlListProperty<WeatherData> AppModel::forecast() const -{ - return *(d->fcProp); -} - -bool AppModel::ready() const -{ - return d->ready; -} - -bool AppModel::hasSource() const -{ - return (d->src != NULL); -} - -bool AppModel::useGps() const -{ - return d->useGps; -} - -void AppModel::setUseGps(bool value) -{ - d->useGps = value; - if (value) { - d->city = ""; - 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()) - requestWeatherByCoordinates(); - } - emit useGpsChanged(); -} - -QString AppModel::city() const -{ - return d->city; -} - -void AppModel::setCity(const QString &value) -{ - d->city = value; - emit cityChanged(); - requestWeatherByCity(); -} |