File indexing completed on 2025-01-19 03:51:19

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-06-01
0007  * Description : A simple backend to search OSM and Geonames.org.
0008  *
0009  * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2010-2011 by Michael G. Hansen <mike at mghansen dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "searchresultbackend.h"
0017 
0018 // Qt includes
0019 
0020 #include <QDomDocument>
0021 #include <QUrlQuery>
0022 
0023 // KDE includes
0024 
0025 #include <klocalizedstring.h>
0026 
0027 // Local includes
0028 
0029 #include "gpscommon.h"
0030 #include "digikam_debug.h"
0031 #include "networkmanager.h"
0032 
0033 namespace DigikamGenericGeolocationEditPlugin
0034 {
0035 
0036 class Q_DECL_HIDDEN SearchResultBackend::Private
0037 {
0038 public:
0039 
0040     explicit Private()
0041       : netReply(nullptr),
0042         mngr    (nullptr)
0043     {
0044     }
0045 
0046     SearchResultBackend::SearchResult::List results;
0047     QString                                 runningBackend;
0048     QByteArray                              searchData;
0049     QString                                 errorMessage;
0050 
0051     QNetworkReply*                          netReply;
0052     QNetworkAccessManager*                  mngr;
0053 };
0054 
0055 SearchResultBackend::SearchResultBackend(QObject* const parent)
0056     : QObject(parent),
0057       d      (new Private())
0058 {
0059     d->mngr = NetworkManager::instance()->getNetworkManager(this);
0060 
0061     connect(d->mngr, SIGNAL(finished(QNetworkReply*)),
0062             this, SLOT(slotFinished(QNetworkReply*)));
0063 }
0064 
0065 SearchResultBackend::~SearchResultBackend()
0066 {
0067     delete d;
0068 }
0069 
0070 bool SearchResultBackend::search(const QString& backendName, const QString& searchTerm)
0071 {
0072     d->searchData.clear();
0073     d->errorMessage.clear();
0074     d->results.clear();
0075 
0076     if (backendName == QLatin1String("osm"))
0077     {
0078         d->runningBackend = backendName;
0079 
0080         QUrl netUrl(QLatin1String("https://nominatim.openstreetmap.org/search"));
0081 
0082         QUrlQuery q(netUrl);
0083         q.addQueryItem(QLatin1String("format"), QLatin1String("xml"));
0084         q.addQueryItem(QLatin1String("q"), searchTerm);
0085         netUrl.setQuery(q);
0086 
0087         QNetworkRequest netRequest(netUrl);
0088         netRequest.setRawHeader("User-Agent", getUserAgentName().toLatin1());
0089 
0090         d->netReply = d->mngr->get(netRequest);
0091 
0092         return true;
0093     }
0094 
0095     if (backendName == QLatin1String("geonames.org"))
0096     {
0097         d->runningBackend = backendName;
0098 
0099         // documentation: https://www.geonames.org/export/geonames-search.html
0100 
0101         QUrl netUrl(QLatin1String("http://api.geonames.org/search"));       // krazy:exclude=insecurenet
0102 
0103         QUrlQuery q(netUrl);
0104         q.addQueryItem(QLatin1String("type"), QLatin1String("xml"));
0105         q.addQueryItem(QLatin1String("q"), searchTerm);
0106         q.addQueryItem(QLatin1String("username"), QLatin1String("digikam"));
0107         netUrl.setQuery(q);
0108 
0109         QNetworkRequest netRequest(netUrl);
0110         netRequest.setRawHeader("User-Agent", getUserAgentName().toLatin1());
0111 
0112         d->netReply = d->mngr->get(netRequest);
0113 
0114         return true;
0115     }
0116 
0117     return false;
0118 }
0119 
0120 void SearchResultBackend::slotFinished(QNetworkReply* reply)
0121 {
0122     if (reply != d->netReply)
0123     {
0124         return;
0125     }
0126 
0127     if (reply->error() != QNetworkReply::NoError)
0128     {
0129         d->errorMessage = reply->errorString();
0130         Q_EMIT signalSearchCompleted();
0131         reply->deleteLater();
0132         return;
0133     }
0134 
0135     d->searchData.append(reply->readAll());
0136 
0137     const QString resultString = QString::fromUtf8(d->searchData.constData(), d->searchData.count());
0138 
0139     if (d->runningBackend == QLatin1String("osm"))
0140     {
0141         QDomDocument doc;
0142         doc.setContent(resultString); // error-handling
0143         QDomElement docElement = doc.documentElement(); // error-handling
0144 
0145         for (QDomNode resultNode = docElement.firstChild(); !resultNode.isNull(); resultNode = resultNode.nextSibling())
0146         {
0147             QDomElement resultElement = resultNode.toElement();
0148 
0149             if (resultElement.isNull())
0150             {
0151                 continue;
0152             }
0153 
0154             if (resultElement.tagName() != QLatin1String("place"))
0155             {
0156                 continue;
0157             }
0158 
0159             const QString boundingBoxString = resultElement.attribute(QLatin1String("boundingbox"));
0160             const QString latString         = resultElement.attribute(QLatin1String("lat"));
0161             const QString lonString         = resultElement.attribute(QLatin1String("lon"));
0162             const QString displayName       = resultElement.attribute(QLatin1String("display_name"));
0163             const QString placeId           = resultElement.attribute(QLatin1String("place_id"));
0164 
0165             if (latString.isEmpty() || lonString.isEmpty() || displayName.isEmpty())
0166             {
0167                 continue;
0168             }
0169 
0170             // now parse the strings:
0171 
0172             qreal lat;
0173             qreal lon;
0174             bool okay = false;
0175             lat       = latString.toDouble(&okay);
0176 
0177             if (okay)
0178             {
0179                 lon = lonString.toDouble(&okay);
0180             }
0181 
0182             if (!okay)
0183             {
0184                 continue;
0185             }
0186 
0187             SearchResult result;
0188             result.coordinates = GeoCoordinates(lat, lon);
0189             result.name        = displayName;
0190 
0191             if (!placeId.isEmpty())
0192             {
0193                 result.internalId = QLatin1String("osm-") + placeId;
0194             }
0195 
0196             // TODO: parse bounding box
0197 
0198             d->results << result;
0199         }
0200     }
0201     else if (d->runningBackend == QLatin1String("geonames.org"))
0202     {
0203         QDomDocument doc;
0204         doc.setContent(resultString);                       // error-handling
0205         QDomElement docElement = doc.documentElement();     // error-handling
0206         qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG)<<docElement.toElement().tagName();
0207 
0208         for (QDomNode resultNode = docElement.firstChild() ; !resultNode.isNull() ; resultNode = resultNode.nextSibling())
0209         {
0210             QDomElement resultElement = resultNode.toElement();
0211             qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << resultElement.tagName();
0212 
0213             if (resultElement.isNull())
0214             {
0215                 continue;
0216             }
0217 
0218             if (resultElement.tagName() != QLatin1String("geoname"))
0219             {
0220                 continue;
0221             }
0222 
0223             QString latString;
0224             QString lonString;
0225             QString displayName;
0226             QString geoNameId;
0227 
0228             for (QDomNode resultSubNode = resultElement.firstChild(); !resultSubNode.isNull(); resultSubNode = resultSubNode.nextSibling())
0229             {
0230                 QDomElement resultSubElement = resultSubNode.toElement();
0231 
0232                 if (resultSubElement.isNull())
0233                 {
0234                     continue;
0235                 }
0236 
0237                 if      (resultSubElement.tagName() == QLatin1String("lat"))
0238                 {
0239                     latString = resultSubElement.text();
0240                 }
0241                 else if (resultSubElement.tagName() == QLatin1String("lng"))
0242                 {
0243                     lonString = resultSubElement.text();
0244                 }
0245                 else if (resultSubElement.tagName() == QLatin1String("name"))
0246                 {
0247                     displayName = resultSubElement.text();
0248                 }
0249                 else if (resultSubElement.tagName() == QLatin1String("geonameId"))
0250                 {
0251                     geoNameId = resultSubElement.text();
0252                 }
0253             }
0254 
0255             if (latString.isEmpty() || lonString.isEmpty() || displayName.isEmpty())
0256             {
0257                 continue;
0258             }
0259 
0260             // now parse the strings:
0261 
0262             qreal lat;
0263             qreal lon;
0264             bool okay = false;
0265             lat       = latString.toDouble(&okay);
0266 
0267             if (okay)
0268             {
0269                 lon = lonString.toDouble(&okay);
0270             }
0271 
0272             if (!okay)
0273             {
0274                 continue;
0275             }
0276 
0277             SearchResult result;
0278             result.coordinates = GeoCoordinates(lat, lon);
0279             result.name        = displayName;
0280 
0281             if (!geoNameId.isEmpty())
0282             {
0283                 result.internalId = QLatin1String("geonames.org-") + geoNameId;
0284             }
0285 
0286             d->results << result;
0287         }
0288     }
0289 
0290     Q_EMIT signalSearchCompleted();
0291 
0292     reply->deleteLater();
0293 }
0294 
0295 SearchResultBackend::SearchResult::List SearchResultBackend::getResults() const
0296 {
0297     return d->results;
0298 }
0299 
0300 QString SearchResultBackend::getErrorMessage() const
0301 {
0302     return d->errorMessage;
0303 }
0304 
0305 QList<QPair<QString, QString> > SearchResultBackend::getBackends() const
0306 {
0307     QList<QPair<QString, QString> > resultList;
0308     resultList << QPair<QString, QString>(i18n("GeoNames"), QLatin1String("geonames.org"));
0309     resultList << QPair<QString, QString>(i18n("OSM"), QLatin1String("osm"));
0310 
0311     return resultList;
0312 }
0313 
0314 } // namespace DigikamGenericGeolocationEditPlugin
0315 
0316 #include "moc_searchresultbackend.cpp"