File indexing completed on 2024-04-28 03:50:34

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 "MarbleDebug.h"
0009 #include "MarbleLocale.h"
0010 #include "GeoDataPlacemark.h"
0011 #include "GeoDataExtendedData.h"
0012 #include "GeoDataData.h"
0013 #include "HttpDownloadManager.h"
0014 #include "osm/OsmPlacemarkData.h"
0015 
0016 #include <QUrl>
0017 #include <QTimer>
0018 #include <QNetworkReply>
0019 #include <QDomDocument>
0020 
0021 namespace Marble
0022 {
0023 
0024 OsmNominatimRunner::OsmNominatimRunner( QObject *parent ) :
0025     ReverseGeocodingRunner( parent ),
0026     m_manager(this)
0027 {
0028     connect(&m_manager, SIGNAL(finished(QNetworkReply*)),
0029             this, SLOT(handleResult(QNetworkReply*)));
0030 }
0031 
0032 OsmNominatimRunner::~OsmNominatimRunner()
0033 {
0034     // nothing to do
0035 }
0036 
0037 
0038 void OsmNominatimRunner::returnNoReverseGeocodingResult()
0039 {
0040     emit reverseGeocodingFinished( m_coordinates, GeoDataPlacemark() );
0041 }
0042 
0043 void OsmNominatimRunner::reverseGeocoding( const GeoDataCoordinates &coordinates )
0044 {
0045     m_coordinates = coordinates;
0046     QString base = "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 = "&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("User-Agent", HttpDownloadManager::userAgent("Browser", "OsmNominatimRunner") );
0055 
0056     QEventLoop eventLoop;
0057 
0058     QTimer timer;
0059     timer.setSingleShot( true );
0060     timer.setInterval( 15000 );
0061 
0062     connect( &timer, SIGNAL(timeout()),
0063              &eventLoop, SLOT(quit()));
0064     connect( this, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)),
0065              &eventLoop, SLOT(quit()) );
0066 
0067     // @todo FIXME Must currently be done in the main thread, see bug 257376
0068     QTimer::singleShot( 0, this, SLOT(startReverseGeocoding()) );
0069     timer.start();
0070 
0071     eventLoop.exec();
0072 }
0073 
0074 void OsmNominatimRunner::startReverseGeocoding()
0075 {
0076     QNetworkReply *reply = m_manager.get( m_request );
0077     connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
0078             this, SLOT(returnNoReverseGeocodingResult()));
0079 }
0080 
0081 void OsmNominatimRunner::handleResult( QNetworkReply* reply )
0082 {
0083     if ( !reply->bytesAvailable() ) {
0084         returnNoReverseGeocodingResult();
0085         return;
0086     }
0087 
0088     QDomDocument xml;
0089     if ( !xml.setContent( reply->readAll() ) ) {
0090         mDebug() << "Cannot parse osm nominatim result " << xml.toString();
0091         returnNoReverseGeocodingResult();
0092         return;
0093     }
0094 
0095     QDomElement root = xml.documentElement();
0096     QDomNodeList places = root.elementsByTagName(QStringLiteral("result"));
0097     if ( places.size() == 1 ) {
0098         QString address = places.item( 0 ).toElement().text();
0099         GeoDataPlacemark placemark;
0100         placemark.setVisualCategory(GeoDataPlacemark::Coordinate);
0101         placemark.setAddress( address );
0102         placemark.setCoordinate( m_coordinates );
0103 
0104         QDomNode details = root.firstChildElement(QStringLiteral("addressparts"));
0105         extractChildren( details, placemark );
0106 
0107         emit reverseGeocodingFinished( m_coordinates, placemark );
0108     } else {
0109         returnNoReverseGeocodingResult();
0110     }
0111 }
0112 
0113 void OsmNominatimRunner::extractChildren(const QDomNode &node, GeoDataPlacemark &placemark)
0114 {
0115     QMap<QString, QString> tagTranslator;
0116     tagTranslator["house_number"] = "addr:housenumber";
0117     tagTranslator["road"] = "addr:street";
0118     tagTranslator["suburb"] = "addr:suburb";
0119     tagTranslator["city"] = "addr:city";
0120     tagTranslator["state_district"] = "addr:district";
0121     tagTranslator["state"] = "addr:state";
0122     tagTranslator["postcode"] = "addr:postcode";
0123     tagTranslator["country_code"] = "addr:country"; // correct mapping
0124     // @todo Find a proper mapping for those
0125     //tagTranslator["village"] = "";
0126     //tagTranslator["town"] = "";
0127 
0128     GeoDataExtendedData data;
0129     OsmPlacemarkData osmData;
0130     QDomNodeList nodes = node.childNodes();
0131     for (int i=0, n=nodes.length(); i<n; ++i) {
0132         QDomNode child = nodes.item(i);
0133         data.addValue( GeoDataData( child.nodeName(), child.toElement().text() ) );
0134 
0135         if (tagTranslator.contains(child.nodeName())) {
0136             QString const key = tagTranslator[child.nodeName()];
0137             osmData.addTag(key, child.toElement().text());
0138         }
0139     }
0140     placemark.setExtendedData(data);
0141     placemark.setOsmData(osmData);
0142 }
0143 
0144 } // namespace Marble
0145 
0146 #include "moc_OsmNominatimReverseGeocodingRunner.cpp"