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"