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"