File indexing completed on 2025-01-05 03:59:28

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "OsmNominatimReverseGeocodingRunner.h"
0007 
0008 #include "MarbleLocale.h"
0009 #include "GeoDataPlacemark.h"
0010 #include "GeoDataExtendedData.h"
0011 #include "GeoDataData.h"
0012 #include "HttpDownloadManager.h"
0013 #include "OsmPlacemarkData.h"
0014 
0015 #include <QUrl>
0016 #include <QTimer>
0017 #include <QNetworkReply>
0018 #include <QDomDocument>
0019 
0020 #include "digikam_debug.h"
0021 
0022 namespace Marble
0023 {
0024 
0025 OsmNominatimRunner::OsmNominatimRunner(QObject* parent)
0026     : ReverseGeocodingRunner(parent),
0027       m_manager             (this)
0028 {
0029     connect(&m_manager, SIGNAL(finished(QNetworkReply*)),
0030             this, SLOT(handleResult(QNetworkReply*)));
0031 }
0032 
0033 OsmNominatimRunner::~OsmNominatimRunner()
0034 {
0035     // nothing to do
0036 }
0037 
0038 void OsmNominatimRunner::returnNoReverseGeocodingResult()
0039 {
0040     Q_EMIT reverseGeocodingFinished( m_coordinates, GeoDataPlacemark() );
0041 }
0042 
0043 void OsmNominatimRunner::reverseGeocoding( const GeoDataCoordinates &coordinates )
0044 {
0045     m_coordinates = coordinates;
0046     QString base  = QString::fromUtf8("https://nominatim.openstreetmap.org/reverse?format=xml&addressdetails=1");
0047     // @todo: Alternative URI with addressdetails=1 could be used for shorter placemark name
0048     QString query = QString::fromUtf8("&lon=%1&lat=%2&accept-language=%3");
0049     double lon    = coordinates.longitude( GeoDataCoordinates::Degree );
0050     double lat    = coordinates.latitude( GeoDataCoordinates::Degree );
0051     QString url   = QString( base + query ).arg( lon ).arg( lat ).arg( MarbleLocale::languageCode() );
0052 
0053     m_request.setUrl(QUrl(url));
0054     m_request.setRawHeader(QByteArray("User-Agent"),
0055                            HttpDownloadManager::userAgent(QLatin1String("Browser"),
0056                            QLatin1String("OsmNominatimRunner")) );
0057 
0058     QEventLoop eventLoop;
0059 
0060     QTimer timer;
0061     timer.setSingleShot( true );
0062     timer.setInterval( 15000 );
0063 
0064     connect( &timer, SIGNAL(timeout()),
0065              &eventLoop, SLOT(quit()));
0066 
0067     connect( this, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)),
0068              &eventLoop, SLOT(quit()) );
0069 
0070     // @todo FIXME Must currently be done in the main thread, see bug 257376
0071     QTimer::singleShot( 0, this, SLOT(startReverseGeocoding()) );
0072     timer.start();
0073 
0074     eventLoop.exec();
0075 }
0076 
0077 void OsmNominatimRunner::startReverseGeocoding()
0078 {
0079     QNetworkReply *reply = m_manager.get( m_request );
0080 
0081     connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)),
0082             this, SLOT(returnNoReverseGeocodingResult()));
0083 }
0084 
0085 void OsmNominatimRunner::handleResult( QNetworkReply* reply )
0086 {
0087     if ( !reply->bytesAvailable() )
0088     {
0089         returnNoReverseGeocodingResult();
0090         return;
0091     }
0092 
0093     QDomDocument xml;
0094 
0095     if ( !xml.setContent( reply->readAll() ) )
0096     {
0097         qCDebug(DIGIKAM_MARBLE_LOG) << "Cannot parse osm nominatim result " << xml.toString();
0098         returnNoReverseGeocodingResult();
0099         return;
0100     }
0101 
0102     QDomElement root    = xml.documentElement();
0103     QDomNodeList places = root.elementsByTagName(QStringLiteral("result"));
0104 
0105     if ( places.size() == 1 )
0106     {
0107         QString address = places.item( 0 ).toElement().text();
0108         GeoDataPlacemark placemark;
0109         placemark.setVisualCategory(GeoDataPlacemark::Coordinate);
0110         placemark.setAddress( address );
0111         placemark.setCoordinate( m_coordinates );
0112 
0113         QDomNode details = root.firstChildElement(QStringLiteral("addressparts"));
0114         extractChildren( details, placemark );
0115 
0116         Q_EMIT reverseGeocodingFinished( m_coordinates, placemark );
0117     }
0118     else
0119     {
0120         returnNoReverseGeocodingResult();
0121     }
0122 }
0123 
0124 void OsmNominatimRunner::extractChildren(const QDomNode &node, GeoDataPlacemark &placemark)
0125 {
0126     QMap<QString, QString> tagTranslator;
0127     tagTranslator[QLatin1String("house_number")]   = QLatin1String("addr:housenumber");
0128     tagTranslator[QLatin1String("road")]           = QLatin1String("addr:street");
0129     tagTranslator[QLatin1String("suburb")]         = QLatin1String("addr:suburb");
0130     tagTranslator[QLatin1String("city")]           = QLatin1String("addr:city");
0131     tagTranslator[QLatin1String("state_district")] = QLatin1String("addr:district");
0132     tagTranslator[QLatin1String("state")]          = QLatin1String("addr:state");
0133     tagTranslator[QLatin1String("postcode")]       = QLatin1String("addr:postcode");
0134     tagTranslator[QLatin1String("country_code")]   = QLatin1String("addr:country"); // correct mapping
0135     // @todo Find a proper mapping for those
0136     //tagTranslator["village"] = "";
0137     //tagTranslator["town"] = "";
0138 
0139     GeoDataExtendedData data;
0140     OsmPlacemarkData osmData;
0141     QDomNodeList nodes = node.childNodes();
0142 
0143     for (int i = 0 , n = nodes.length() ; i < n ; ++i)
0144     {
0145         QDomNode child = nodes.item(i);
0146         data.addValue( GeoDataData( child.nodeName(), child.toElement().text() ) );
0147 
0148         if (tagTranslator.contains(child.nodeName()))
0149         {
0150             QString const key = tagTranslator[child.nodeName()];
0151             osmData.addTag(key, child.toElement().text());
0152         }
0153     }
0154 
0155     placemark.setExtendedData(data);
0156     placemark.setOsmData(osmData);
0157 }
0158 
0159 } // namespace Marble
0160 
0161 #include "moc_OsmNominatimReverseGeocodingRunner.cpp"