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 : OSM Nominatim backend for Reverse Geocoding
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-osm-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 class Q_DECL_HIDDEN OsmInternalJobs
0035 {
0036 public:
0037 
0038     OsmInternalJobs()
0039       : netReply(nullptr)
0040     {
0041     }
0042 
0043     ~OsmInternalJobs()
0044     {
0045         if (netReply)
0046         {
0047             netReply->deleteLater();
0048         }
0049     }
0050 
0051     QString            language;
0052     QList<RGInfo>      request;
0053     QByteArray         data;
0054 
0055     QNetworkReply*     netReply;
0056 };
0057 
0058 class Q_DECL_HIDDEN BackendOsmRG::Private
0059 {
0060 public:
0061 
0062     explicit Private()
0063       : mngr(nullptr)
0064     {
0065     }
0066 
0067     QList<OsmInternalJobs> jobs;
0068     QString                errorMessage;
0069 
0070     QNetworkAccessManager* mngr;
0071 };
0072 
0073 /**
0074  * @class BackendOsmRG
0075  *
0076  * @brief This class calls Open Street Map's reverse geocoding service.
0077  */
0078 
0079 /**
0080  * Constructor
0081  * @param Parent object.
0082  */
0083 BackendOsmRG::BackendOsmRG(QObject* const parent)
0084     : RGBackend(parent),
0085       d        (new Private())
0086 {
0087     d->mngr = NetworkManager::instance()->getNetworkManager(this);
0088 
0089     connect(d->mngr, SIGNAL(finished(QNetworkReply*)),
0090             this, SLOT(slotFinished(QNetworkReply*)));
0091 }
0092 
0093 /**
0094  * Destructor
0095  */
0096 BackendOsmRG::~BackendOsmRG()
0097 {
0098     delete d;
0099 }
0100 
0101 /**
0102  * This slot calls Open Street Map's reverse geocoding service for each image.
0103  */
0104 void BackendOsmRG::nextPhoto()
0105 {
0106     if (d->jobs.isEmpty())
0107     {
0108         return;
0109     }
0110 
0111     QUrl netUrl(QLatin1String("https://nominatim.openstreetmap.org/reverse"));
0112 
0113     QUrlQuery q(netUrl);
0114     q.addQueryItem(QLatin1String("format"),          QLatin1String("xml"));
0115     q.addQueryItem(QLatin1String("lat"),             d->jobs.first().request.first().coordinates.latString());
0116     q.addQueryItem(QLatin1String("lon"),             d->jobs.first().request.first().coordinates.lonString());
0117     q.addQueryItem(QLatin1String("zoom"),            QLatin1String("18"));
0118     q.addQueryItem(QLatin1String("addressdetails"),  QLatin1String("1"));
0119     q.addQueryItem(QLatin1String("accept-language"), d->jobs.first().language);
0120     netUrl.setQuery(q);
0121 
0122     QNetworkRequest netRequest(netUrl);
0123     netRequest.setRawHeader("User-Agent", getUserAgentName().toLatin1());
0124 
0125     d->jobs.first().netReply = d->mngr->get(netRequest);
0126 }
0127 
0128 /**
0129  * Takes the coordinate of each image and then connects to Open Street Map's reverse geocoding service.
0130  * @param rgList A list containing information needed in reverse geocoding process. At this point, it contains only coordinates.
0131  * @param language The language in which the data will be returned.
0132  */
0133 void BackendOsmRG::callRGBackend(const QList<RGInfo>& rgList, const QString& language)
0134 {
0135     d->errorMessage.clear();
0136 
0137     for (int i = 0 ; i < rgList.count() ; ++i)
0138     {
0139         bool foundIt = false;
0140 
0141         for (int j = 0 ; j < d->jobs.count() ; ++j)
0142         {
0143             if (d->jobs[j].request.first().coordinates.sameLonLatAs(rgList[i].coordinates))
0144             {
0145                 d->jobs[j].request << rgList[i];
0146                 d->jobs[j].language = language;
0147                 foundIt             = true;
0148                 break;
0149             }
0150         }
0151 
0152         if (!foundIt)
0153         {
0154             OsmInternalJobs newJob;
0155             newJob.request << rgList.at(i);
0156             newJob.language = language;
0157             d->jobs << newJob;
0158         }
0159     }
0160 
0161     if (!d->jobs.isEmpty())
0162     {
0163         nextPhoto();
0164     }
0165 }
0166 
0167 /**
0168  * The data is returned from Open Street Map in a XML. This function translates the XML into a QMap.
0169  * @param xmlData The returned XML.
0170  */
0171 QMap<QString, QString> BackendOsmRG::makeQMapFromXML(const QString& xmlData)
0172 {
0173     QString resultString;
0174     QMap<QString, QString> mappedData;
0175     QDomDocument doc;
0176 
0177     doc.setContent(xmlData);
0178 
0179     QDomElement docElem = doc.documentElement();
0180     QDomNode n          = docElem.lastChild().firstChild();
0181 
0182     while (!n.isNull())
0183     {
0184         QDomElement e = n.toElement();
0185 
0186         if (!e.isNull())
0187         {
0188             if ((e.tagName() == QLatin1String("country"))         ||
0189                 (e.tagName() == QLatin1String("country_code"))    ||
0190                 (e.tagName() == QLatin1String("state"))           ||
0191                 (e.tagName() == QLatin1String("state_district"))  ||
0192                 (e.tagName() == QLatin1String("county"))          ||
0193                 (e.tagName() == QLatin1String("city"))            ||
0194                 (e.tagName() == QLatin1String("city_district"))   ||
0195                 (e.tagName() == QLatin1String("suburb"))          ||
0196                 (e.tagName() == QLatin1String("town"))            ||
0197                 (e.tagName() == QLatin1String("village"))         ||
0198                 (e.tagName() == QLatin1String("hamlet"))          ||
0199                 (e.tagName() == QLatin1String("place"))           ||
0200                 (e.tagName() == QLatin1String("road"))            ||
0201                 (e.tagName() == QLatin1String("house_number")))
0202             {
0203                 mappedData.insert(e.tagName(), e.text());
0204                 resultString.append(e.tagName() + QLatin1Char(':') + e.text() + QLatin1Char('\n'));
0205             }
0206         }
0207 
0208         n = n.nextSibling();
0209     }
0210 
0211     return mappedData;
0212 }
0213 
0214 /**
0215  * @return Error message, if any.
0216  */
0217 QString BackendOsmRG::getErrorMessage()
0218 {
0219     return d->errorMessage;
0220 }
0221 
0222 /**
0223  * @return Backend name.
0224  */
0225 QString BackendOsmRG::backendName()
0226 {
0227     return QLatin1String("OSM");
0228 }
0229 
0230 void BackendOsmRG::slotFinished(QNetworkReply* reply)
0231 {
0232     for (int i = 0 ; i < d->jobs.count() ; ++i)
0233     {
0234         if (d->jobs.at(i).netReply == reply)
0235         {
0236             if (reply->error() != QNetworkReply::NoError)
0237             {
0238                 d->errorMessage = reply->errorString();
0239                 Q_EMIT signalRGReady(d->jobs.first().request);
0240                 reply->deleteLater();
0241                 d->jobs.clear();
0242 
0243                 return;
0244             }
0245 
0246             d->jobs[i].data.append(reply->readAll());
0247             break;
0248         }
0249     }
0250 
0251     for (int i = 0 ; i < d->jobs.count() ; ++i)
0252     {
0253         if (d->jobs.at(i).netReply == reply)
0254         {
0255             QString dataString;
0256             dataString = QString::fromUtf8(d->jobs[i].data.constData(), qstrlen(d->jobs[i].data.constData()));
0257             int pos    = dataString.indexOf(QLatin1String("<reversegeocode"));
0258             dataString.remove(0, pos);
0259 
0260             QMap<QString, QString> resultMap = makeQMapFromXML(dataString);
0261 
0262             for (int j = 0 ; j < d->jobs[i].request.count() ; ++j)
0263             {
0264                 d->jobs[i].request[j].rgData = resultMap;
0265             }
0266 
0267             Q_EMIT signalRGReady(d->jobs[i].request);
0268 
0269             d->jobs.removeAt(i);
0270 
0271             if (!d->jobs.isEmpty())
0272             {
0273                 QTimer::singleShot(500, this, SLOT(nextPhoto()));
0274             }
0275 
0276             reply->deleteLater();
0277             break;
0278         }
0279     }
0280 }
0281 
0282 void BackendOsmRG::cancelRequests()
0283 {
0284     d->jobs.clear();
0285     d->errorMessage.clear();
0286 }
0287 
0288 } // namespace Digikam
0289 
0290 #include "moc_backend-osm-rg.cpp"