File indexing completed on 2024-04-28 04:42:42
0001 /* 0002 * SPDX-FileCopyrightText: 2020-2021 Han Young <hanyoung@protonmail.com> 0003 * SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com> 0004 * SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org> 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "locationqueryreply.h" 0009 #include "kweathercore_p.h" 0010 #include "locationquery.h" 0011 #include "locationqueryresult.h" 0012 #include "reply_p.h" 0013 0014 #include <QCoreApplication> 0015 #include <QGeoPositionInfo> 0016 #include <QGeoPositionInfoSource> 0017 #include <QJsonArray> 0018 #include <QJsonDocument> 0019 #include <QJsonObject> 0020 #include <QNetworkAccessManager> 0021 #include <QNetworkReply> 0022 #include <QUrlQuery> 0023 0024 #if QT_CONFIG(permissions) 0025 #include <QPermissions> 0026 #endif 0027 0028 using namespace KWeatherCore; 0029 0030 class KWeatherCore::LocationQueryReplyPrivate : public ReplyPrivate 0031 { 0032 public: 0033 void requestPosition(LocationQueryReply *q, QGeoPositionInfoSource *source, QNetworkAccessManager *nam); 0034 std::vector<LocationQueryResult> m_result; 0035 }; 0036 0037 static std::optional<QString> findSubdivision(const QJsonObject &json) 0038 { 0039 const auto adminCodeIter = json.constFind(QLatin1String("adminCodes1")); 0040 if (adminCodeIter == json.constEnd()) { 0041 return std::nullopt; 0042 } else { 0043 return (*adminCodeIter).toObject().value(QLatin1String("ISO3166_2")).toString(); 0044 } 0045 } 0046 0047 LocationQueryReply::LocationQueryReply(const QString &name, int number, QNetworkAccessManager *nam, QObject *parent) 0048 : Reply(new LocationQueryReplyPrivate, parent) 0049 { 0050 QUrl url(QStringLiteral("http://api.geonames.org/searchJSON")); 0051 QUrlQuery urlQuery; 0052 0053 urlQuery.addQueryItem(QStringLiteral("q"), name); 0054 urlQuery.addQueryItem(QStringLiteral("maxRows"), QString::number(number)); 0055 urlQuery.addQueryItem(QStringLiteral("username"), QStringLiteral("kweatherdev")); 0056 url.setQuery(urlQuery); 0057 0058 auto reply = nam->get(QNetworkRequest(url)); 0059 QObject::connect(reply, &QNetworkReply::finished, this, [reply, this]() { 0060 Q_D(LocationQueryReply); 0061 reply->deleteLater(); 0062 if (reply->error() != QNetworkReply::NoError) { 0063 d->setError(Reply::NetworkError, reply->errorString()); 0064 Q_EMIT finished(); 0065 return; 0066 } 0067 0068 const QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); 0069 const QJsonObject root = document.object(); 0070 0071 auto counts = root[QLatin1String("totalResultsCount")].toInt(); 0072 // if no result 0073 if (!counts) { 0074 d->setError(Reply::NotFound); 0075 Q_EMIT finished(); 0076 return; 0077 } 0078 0079 // if our api calls reached daily limit 0080 if (root[QLatin1String("status")].toObject()[QLatin1String("value")].toInt() == 18) { 0081 d->setError(Reply::RateLimitExceeded); 0082 qWarning("API calls reached daily limit"); 0083 Q_EMIT finished(); 0084 return; 0085 } 0086 0087 const auto geonames = root.value(QLatin1String("geonames")).toArray(); 0088 // add query results 0089 for (const auto &resRef : std::as_const(geonames)) { 0090 const auto res = resRef.toObject(); 0091 const auto result = LocationQueryResult(res.value(QLatin1String("lat")).toString().toFloat(), 0092 res.value(QLatin1String("lng")).toString().toFloat(), 0093 res.value(QLatin1String("toponymName")).toString(), 0094 res.value(QLatin1String("name")).toString(), 0095 res.value(QLatin1String("countryCode")).toString(), 0096 res.value(QLatin1String("countryName")).toString(), 0097 QString::number(res.value(QLatin1String("geonameId")).toInt()), 0098 findSubdivision(res)); 0099 d->m_result.push_back(result); 0100 } 0101 0102 Q_EMIT finished(); 0103 }); 0104 } 0105 0106 LocationQueryReply::LocationQueryReply(QGeoPositionInfoSource *source, QNetworkAccessManager *nam, QObject *parent) 0107 : Reply(new LocationQueryReplyPrivate, parent) 0108 { 0109 Q_D(LocationQueryReply); 0110 if (!source) { 0111 d->setError(LocationQueryReply::NoService); 0112 QMetaObject::invokeMethod(this, &LocationQueryReply::finished, Qt::QueuedConnection); 0113 return; 0114 } 0115 0116 #if QT_CONFIG(permissions) 0117 QLocationPermission permission; 0118 permission.setAccuracy(QLocationPermission::Precise); 0119 permission.setAvailability(QLocationPermission::WhenInUse); 0120 switch (QCoreApplication::instance()->checkPermission(permission)) { 0121 case Qt::PermissionStatus::Undetermined: 0122 QCoreApplication::instance()->requestPermission(permission, this, [this, nam, source](const auto &permission) { 0123 Q_D(LocationQueryReply); 0124 if (permission.status() == Qt::PermissionStatus::Granted) { 0125 d->requestPosition(this, source, nam); 0126 } else { 0127 d->setError(LocationQueryReply::NoService); 0128 Q_EMIT finished(); 0129 } 0130 }); 0131 return; 0132 case Qt::PermissionStatus::Denied: 0133 d->setError(LocationQueryReply::NoService); 0134 QMetaObject::invokeMethod(this, &LocationQueryReply::finished, Qt::QueuedConnection); 0135 return; 0136 case Qt::PermissionStatus::Granted: 0137 d->requestPosition(this, source, nam); 0138 break; 0139 } 0140 #else 0141 d->requestPosition(this, source, nam); 0142 #endif 0143 } 0144 0145 LocationQueryReply::~LocationQueryReply() = default; 0146 0147 void LocationQueryReplyPrivate::requestPosition(LocationQueryReply *q, QGeoPositionInfoSource *source, QNetworkAccessManager *nam) 0148 { 0149 QObject::connect(source, &QGeoPositionInfoSource::positionUpdated, q, [this, q, nam](const QGeoPositionInfo &pos) { 0150 const auto lat = pos.coordinate().latitude(); 0151 const auto lon = pos.coordinate().longitude(); 0152 QUrl url(QStringLiteral("http://api.geonames.org/findNearbyJSON")); 0153 QUrlQuery urlQuery; 0154 0155 urlQuery.addQueryItem(QStringLiteral("lat"), KWeatherCorePrivate::toFixedString(lat)); 0156 urlQuery.addQueryItem(QStringLiteral("lng"), KWeatherCorePrivate::toFixedString(lon)); 0157 urlQuery.addQueryItem(QStringLiteral("username"), QStringLiteral("kweatherdev")); 0158 url.setQuery(urlQuery); 0159 0160 auto req = QNetworkRequest(url); 0161 0162 qWarning() << "lat: " << lat << "lon: " << lon; 0163 auto reply = nam->get(req); 0164 0165 QObject::connect(reply, &QNetworkReply::finished, q, [this, q, lat, lon, reply] { 0166 reply->deleteLater(); 0167 const QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); 0168 const QJsonObject root = document.object(); 0169 const auto array = root[QLatin1String("geonames")].toArray(); 0170 if (array.size()) { 0171 m_result.push_back(LocationQueryResult(lat, 0172 lon, 0173 array.at(0)[QLatin1String("toponymName")].toString(), 0174 array.at(0)[QLatin1String("name")].toString(), 0175 array.at(0)[QLatin1String("countryCode")].toString(), 0176 array.at(0)[QLatin1String("countryName")].toString(), 0177 QString::number(root[QLatin1String("geonameId")].toInt()))); 0178 } else { 0179 setError(Reply::NotFound); 0180 } 0181 0182 Q_EMIT q->finished(); 0183 }); 0184 }); 0185 0186 source->requestUpdate(); 0187 } 0188 0189 const std::vector<LocationQueryResult> &LocationQueryReply::result() const 0190 { 0191 Q_D(const LocationQueryReply); 0192 return d->m_result; 0193 }