File indexing completed on 2024-05-05 03:51:02

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "OsmNominatimSearchRunner.h"
0007 
0008 #include "MarbleDebug.h"
0009 #include "MarbleLocale.h"
0010 #include "GeoDataPlacemark.h"
0011 #include "GeoDataExtendedData.h"
0012 #include "GeoDataData.h"
0013 #include "GeoDataLatLonAltBox.h"
0014 #include "HttpDownloadManager.h"
0015 #include "StyleBuilder.h"
0016 #include "osm/OsmPlacemarkData.h"
0017 
0018 #include <QVector>
0019 #include <QUrl>
0020 #include <QTimer>
0021 #include <QNetworkReply>
0022 #include <QDomDocument>
0023 
0024 namespace Marble
0025 {
0026 
0027 OsmNominatimRunner::OsmNominatimRunner( QObject *parent ) :
0028     SearchRunner( parent ),
0029     m_manager()
0030 {
0031     connect(&m_manager, SIGNAL(finished(QNetworkReply*)),
0032             this, SLOT(handleResult(QNetworkReply*)));
0033 }
0034 
0035 OsmNominatimRunner::~OsmNominatimRunner()
0036 {
0037     // nothing to do
0038 }
0039 
0040 void OsmNominatimRunner::returnNoResults()
0041 {
0042     emit searchFinished( QVector<GeoDataPlacemark*>() );
0043 }
0044 
0045 void OsmNominatimRunner::search( const QString &searchTerm, const GeoDataLatLonBox &preferred )
0046 {    
0047     QString base = "https://nominatim.openstreetmap.org/search?";
0048     QString query = "q=%1&format=xml&addressdetails=1&accept-language=%2";
0049     QString url = QString(base + query).arg(searchTerm).arg(MarbleLocale::languageCode());
0050     if( !preferred.isEmpty() ) {
0051         GeoDataCoordinates::Unit deg = GeoDataCoordinates::Degree;
0052         QString viewbox( "&viewbox=%1,%2,%3,%4&bounded=1" ); // left, top, right, bottom
0053         url += viewbox.arg(preferred.west(deg))
0054                       .arg(preferred.north(deg))
0055                       .arg(preferred.east(deg))
0056                       .arg(preferred.south(deg));
0057 
0058     }
0059     m_request.setUrl(QUrl(url));
0060     m_request.setRawHeader("User-Agent", HttpDownloadManager::userAgent("Browser", "OsmNominatimRunner") );
0061 
0062     QEventLoop eventLoop;
0063 
0064     QTimer timer;
0065     timer.setSingleShot( true );
0066     timer.setInterval( 15000 );
0067 
0068     connect( &timer, SIGNAL(timeout()),
0069              &eventLoop, SLOT(quit()));
0070     connect( this, SIGNAL(searchFinished(QVector<GeoDataPlacemark*>)),
0071              &eventLoop, SLOT(quit()) );
0072 
0073     // @todo FIXME Must currently be done in the main thread, see bug 257376
0074     QTimer::singleShot( 0, this, SLOT(startSearch()) );
0075     timer.start();
0076 
0077     eventLoop.exec();
0078 }
0079 
0080 void OsmNominatimRunner::startSearch()
0081 {
0082     QNetworkReply *reply = m_manager.get( m_request );
0083     connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
0084             this, SLOT(returnNoResults()));
0085 }
0086 
0087 GeoDataExtendedData OsmNominatimRunner::extractChildren(const QDomNode &node)
0088 {
0089     GeoDataExtendedData data;
0090     QDomNodeList nodes = node.childNodes();
0091     for (int i=0, n=nodes.length(); i<n; ++i) {
0092         QDomNode child = nodes.item(i);
0093         data.addValue( GeoDataData( child.nodeName(), child.toElement().text() ) );
0094     }
0095     return data;
0096 }
0097 
0098 
0099 void OsmNominatimRunner::handleResult( QNetworkReply* reply )
0100 {   
0101     QDomDocument xml;
0102     if (!xml.setContent(reply->readAll())) {
0103         qWarning() << "Cannot parse osm nominatim result";
0104         qWarning() << reply->error();
0105         returnNoResults();
0106         return;
0107     }
0108 
0109     QVector<GeoDataPlacemark*> placemarks;
0110     QDomElement root = xml.documentElement();
0111     QDomNodeList places = root.elementsByTagName(QStringLiteral("place"));
0112     for (int i=0; i<places.size(); ++i) {
0113         QDomNode place = places.at(i);
0114         QDomNamedNodeMap attributes = place.attributes();
0115         QString lon = attributes.namedItem(QStringLiteral("lon")).nodeValue();
0116         QString lat = attributes.namedItem(QStringLiteral("lat")).nodeValue();
0117         QString desc = attributes.namedItem(QStringLiteral("display_name")).nodeValue();
0118         QString key = attributes.namedItem(QStringLiteral("class")).nodeValue();
0119         QString value = attributes.namedItem(QStringLiteral("type")).nodeValue();
0120 
0121         OsmPlacemarkData data;
0122 
0123         GeoDataExtendedData placemarkData = extractChildren(place);
0124         placemarkData.addValue(GeoDataData(QStringLiteral("class"), key));
0125         placemarkData.addValue(GeoDataData(QStringLiteral("type"), value));
0126 
0127         QString name = place.firstChildElement(value).text();
0128         QString road = place.firstChildElement(QStringLiteral("road")).text();
0129         placemarkData.addValue(GeoDataData(QStringLiteral("name"), name));
0130 
0131         QString city = place.firstChildElement(QStringLiteral("city")).text();
0132         if( city.isEmpty() ) {
0133             city = place.firstChildElement(QStringLiteral("town")).text();
0134             if( city.isEmpty() ) {
0135                 city = place.firstChildElement(QStringLiteral("village")).text();
0136             } if( city.isEmpty() ) {
0137                 city = place.firstChildElement(QStringLiteral("hamlet")).text();
0138             }
0139         }
0140 
0141         QString administrative = place.firstChildElement(QStringLiteral("county")).text();
0142         if( administrative.isEmpty() ) {
0143             administrative = place.firstChildElement(QStringLiteral("region")).text();
0144             if( administrative.isEmpty() ) {
0145                 administrative = place.firstChildElement(QStringLiteral("state")).text();
0146                 data.addTag(QStringLiteral("addr:state"), administrative);
0147             } else {
0148                 data.addTag(QStringLiteral("district"), administrative);
0149             }
0150         }
0151 
0152         QString country = place.firstChildElement(QStringLiteral("country")).text();
0153 
0154         QString description;
0155         for (int i=0; i<place.childNodes().size(); ++i) {
0156             QDomElement item = place.childNodes().at(i).toElement();
0157             description += item.nodeName() + QLatin1Char(':') + item.text() + QLatin1Char('\n');
0158         }
0159         description += QLatin1String("Category: ") + key + QLatin1Char('/') + value;
0160 
0161         if (!lon.isEmpty() && !lat.isEmpty() && !desc.isEmpty()) {
0162             QString placemarkName;
0163             GeoDataPlacemark* placemark = new GeoDataPlacemark;
0164             // try to provide 2 fields
0165             if (!name.isEmpty()) {
0166                 placemarkName = name;
0167             }
0168             if (!road.isEmpty() && road != placemarkName ) {
0169                 if( !placemarkName.isEmpty() ) {
0170                     placemarkName += QLatin1String(", ");
0171                 }
0172                 placemarkName += road;
0173                 data.addTag(QStringLiteral("addr:street"), road);
0174             }
0175             if (!city.isEmpty() && !placemarkName.contains(QLatin1Char(',')) && city != placemarkName) {
0176                 if( !placemarkName.isEmpty() ) {
0177                     placemarkName += QLatin1String(", ");
0178                 }
0179                 placemarkName += city;
0180                 data.addTag(QStringLiteral("addr:city"), city);
0181             }
0182             if (!administrative.isEmpty() && !placemarkName.contains(QLatin1Char(',')) && administrative != placemarkName) {
0183                 if( !placemarkName.isEmpty() ) {
0184                     placemarkName += QLatin1String(", ");
0185                 }
0186                 placemarkName += administrative;
0187             }
0188             if (!country.isEmpty() && !placemarkName.contains(QLatin1Char(',')) && country != placemarkName) {
0189                 if( !placemarkName.isEmpty() ) {
0190                     placemarkName += QLatin1String(", ");
0191                 }
0192                 placemarkName += country;
0193                 data.addTag(QStringLiteral("addr:country"), country);
0194             }
0195             if (placemarkName.isEmpty()) {
0196                 placemarkName = desc;
0197             }
0198             placemark->setName( placemarkName );
0199             placemark->setDescription(description);
0200             placemark->setAddress(desc);
0201             placemark->setCoordinate( lon.toDouble(), lat.toDouble(), 0, GeoDataCoordinates::Degree );
0202             const auto category = StyleBuilder::determineVisualCategory(data);
0203             placemark->setVisualCategory( category );
0204             placemark->setExtendedData(placemarkData);
0205             placemark->setOsmData(data);
0206             placemarks << placemark;
0207         }
0208     }
0209     
0210     emit searchFinished( placemarks );
0211 }
0212 
0213 } // namespace Marble
0214 
0215 #include "moc_OsmNominatimSearchRunner.cpp"