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"