File indexing completed on 2025-03-09 03:57:14
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-05-12 0007 * Description : Backend for reverse geocoding using geonames.org (non-US) 0008 * 0009 * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2010 by Michael G. Hansen <mike at mghansen dot de> 0011 * SPDX-FileCopyrightText: 2010 by Gabriel Voicu <ping dot gabi at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "backend-geonames-rg.h" 0018 0019 // Qt includes 0020 0021 #include <QDomDocument> 0022 #include <QUrlQuery> 0023 #include <QTimer> 0024 0025 // Local includes 0026 0027 #include "digikam_debug.h" 0028 #include "networkmanager.h" 0029 #include "gpscommon.h" 0030 0031 namespace Digikam 0032 { 0033 0034 /** 0035 * @class BackendGeonamesRG 0036 * 0037 * @brief This class calls Geonames' reverse geocoding service. 0038 */ 0039 0040 class Q_DECL_HIDDEN GeonamesInternalJobs 0041 { 0042 public: 0043 0044 GeonamesInternalJobs() 0045 : netReply(nullptr) 0046 { 0047 } 0048 0049 ~GeonamesInternalJobs() 0050 { 0051 if (netReply) 0052 { 0053 netReply->deleteLater(); 0054 } 0055 } 0056 0057 QString language; 0058 QList<RGInfo> request; 0059 QByteArray data; 0060 0061 QNetworkReply* netReply; 0062 }; 0063 0064 class Q_DECL_HIDDEN BackendGeonamesRG::Private 0065 { 0066 public: 0067 0068 explicit Private() 0069 : itemCounter (0), 0070 itemCount (0), 0071 mngr (nullptr) 0072 { 0073 } 0074 0075 int itemCounter; 0076 int itemCount; 0077 QList<GeonamesInternalJobs> jobs; 0078 QString errorMessage; 0079 0080 QNetworkAccessManager* mngr; 0081 }; 0082 0083 /** 0084 * Constructor 0085 * @param parent the parent object. 0086 */ 0087 BackendGeonamesRG::BackendGeonamesRG(QObject* const parent) 0088 : RGBackend(parent), 0089 d (new Private()) 0090 { 0091 d->mngr = NetworkManager::instance()->getNetworkManager(this); 0092 0093 connect(d->mngr, SIGNAL(finished(QNetworkReply*)), 0094 this, SLOT(slotFinished(QNetworkReply*))); 0095 } 0096 0097 /** 0098 * Destructor 0099 */ 0100 BackendGeonamesRG::~BackendGeonamesRG() 0101 { 0102 delete d; 0103 } 0104 0105 /** 0106 * This function calls Geonames's reverse geocoding service for each image. 0107 */ 0108 void BackendGeonamesRG::nextPhoto() 0109 { 0110 if (d->jobs.isEmpty()) 0111 { 0112 return; 0113 } 0114 0115 QUrl netUrl(QLatin1String("http://api.geonames.org/findNearbyPlaceName")); // krazy:exclude=insecurenet 0116 0117 QUrlQuery q(netUrl); 0118 q.addQueryItem(QLatin1String("lat"), d->jobs.first().request.first().coordinates.latString()); 0119 q.addQueryItem(QLatin1String("lng"), d->jobs.first().request.first().coordinates.lonString()); 0120 q.addQueryItem(QLatin1String("lang"), d->jobs.first().language); 0121 q.addQueryItem(QLatin1String("username"), QLatin1String("digikam")); 0122 netUrl.setQuery(q); 0123 0124 QNetworkRequest netRequest(netUrl); 0125 netRequest.setRawHeader("User-Agent", getUserAgentName().toLatin1()); 0126 0127 d->jobs.first().netReply = d->mngr->get(netRequest); 0128 } 0129 0130 /** 0131 * Takes coordinates from each image and then connects to Open Street Map's reverse geocoding service. 0132 * @param rgList A list containing information needed in reverse geocoding process. At this point, it contains only coordinates. 0133 * @param language The language in which the data will be returned. 0134 */ 0135 void BackendGeonamesRG::callRGBackend(const QList<RGInfo>& rgList, const QString& language) 0136 { 0137 d->errorMessage.clear(); 0138 0139 for (int i = 0 ; i < rgList.count() ; ++i) 0140 { 0141 bool foundIt = false; 0142 0143 for (int j = 0 ; j < d->jobs.count() ; ++j) 0144 { 0145 if (d->jobs[j].request.first().coordinates.sameLonLatAs(rgList[i].coordinates)) 0146 { 0147 d->jobs[j].request << rgList[i]; 0148 d->jobs[j].language = language; 0149 foundIt = true; 0150 break; 0151 } 0152 } 0153 0154 if (!foundIt) 0155 { 0156 GeonamesInternalJobs newJob; 0157 newJob.request << rgList.at(i); 0158 newJob.language = language; 0159 d->jobs << newJob; 0160 } 0161 } 0162 0163 nextPhoto(); 0164 } 0165 0166 /** 0167 * The data is returned from Open Street Map in a XML. This function translates the XML into a QMap. 0168 * @param xmlData The returned XML. 0169 */ 0170 QMap<QString, QString> BackendGeonamesRG::makeQMapFromXML(const QString& xmlData) 0171 { 0172 QMap<QString, QString> mappedData; 0173 QString resultString; 0174 QDomDocument doc; 0175 0176 doc.setContent(xmlData); 0177 0178 QDomElement docElem = doc.documentElement(); 0179 QDomNode n = docElem.firstChild().firstChild(); 0180 0181 while (!n.isNull()) 0182 { 0183 const QDomElement e = n.toElement(); 0184 0185 if (!e.isNull()) 0186 { 0187 if ((e.tagName() == QLatin1String("countryName")) || 0188 (e.tagName() == QLatin1String("countryCode")) || 0189 (e.tagName() == QLatin1String("name"))) 0190 { 0191 mappedData.insert(e.tagName(), e.text()); 0192 resultString.append(e.tagName() + QLatin1Char(':') + e.text() + QLatin1Char('\n')); 0193 } 0194 } 0195 0196 n = n.nextSibling(); 0197 } 0198 0199 return mappedData; 0200 } 0201 0202 /** 0203 * @return Error message, if any. 0204 */ 0205 QString BackendGeonamesRG::getErrorMessage() 0206 { 0207 return d->errorMessage; 0208 } 0209 0210 /** 0211 * @return Backend name. 0212 */ 0213 QString BackendGeonamesRG::backendName() 0214 { 0215 return QLatin1String("Geonames"); 0216 } 0217 0218 void BackendGeonamesRG::slotFinished(QNetworkReply* reply) 0219 { 0220 for (int i = 0 ; i < d->jobs.count() ; ++i) 0221 { 0222 if (d->jobs.at(i).netReply == reply) 0223 { 0224 if (reply->error() != QNetworkReply::NoError) 0225 { 0226 d->errorMessage = reply->errorString(); 0227 Q_EMIT signalRGReady(d->jobs.first().request); 0228 reply->deleteLater(); 0229 d->jobs.clear(); 0230 0231 return; 0232 } 0233 0234 d->jobs[i].data.append(reply->readAll()); 0235 break; 0236 } 0237 } 0238 0239 for (int i = 0 ; i < d->jobs.count() ; ++i) 0240 { 0241 if (d->jobs.at(i).netReply == reply) 0242 { 0243 QString dataString; 0244 dataString = QString::fromUtf8(d->jobs[i].data.constData(),qstrlen(d->jobs[i].data.constData())); 0245 int pos = dataString.indexOf(QLatin1String("<geonames")); 0246 dataString.remove(0,pos); 0247 dataString.chop(1); 0248 0249 QMap<QString, QString> resultMap = makeQMapFromXML(dataString); 0250 0251 for (int j = 0 ; j < d->jobs[i].request.count() ; ++j) 0252 { 0253 d->jobs[i].request[j].rgData = resultMap; 0254 } 0255 0256 Q_EMIT signalRGReady(d->jobs[i].request); 0257 0258 d->jobs.removeAt(i); 0259 0260 if (!d->jobs.isEmpty()) 0261 { 0262 QTimer::singleShot(500, this, SLOT(nextPhoto())); 0263 } 0264 0265 reply->deleteLater(); 0266 break; 0267 } 0268 } 0269 } 0270 0271 void BackendGeonamesRG::cancelRequests() 0272 { 0273 d->jobs.clear(); 0274 d->errorMessage.clear(); 0275 } 0276 0277 } // namespace Digikam 0278 0279 #include "moc_backend-geonames-rg.cpp"