1 /***************************************************************************
2 qgsnominatimgeocoder.cpp
3 ---------------
4 Date : December 2020
5 Copyright : (C) 2020 by Mathieu Pellerin
6 Email : nirvn dot asia at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include "qgsnominatimgeocoder.h"
17 #include "qgsblockingnetworkrequest.h"
18 #include "qgsgeocodercontext.h"
19 #include "qgslogger.h"
20 #include "qgsnetworkaccessmanager.h"
21 #include "qgscoordinatetransform.h"
22 #include <QDateTime>
23 #include <QUrl>
24 #include <QUrlQuery>
25 #include <QMutex>
26 #include <QNetworkRequest>
27 #include <QJsonDocument>
28 #include <QJsonArray>
29
30 QMutex QgsNominatimGeocoder::sMutex;
31 typedef QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult;
32 Q_GLOBAL_STATIC( CachedGeocodeResult, sCachedResults )
33 qint64 QgsNominatimGeocoder::sLastRequestTimestamp = 0;
34
QgsNominatimGeocoder(const QString & countryCodes,const QString & endpoint)35 QgsNominatimGeocoder::QgsNominatimGeocoder( const QString &countryCodes, const QString &endpoint )
36 : QgsGeocoderInterface()
37 , mCountryCodes( countryCodes )
38 , mEndpoint( QStringLiteral( "https://nominatim.qgis.org/search" ) )
39 {
40 if ( !endpoint.isEmpty() )
41 mEndpoint = endpoint;
42 }
43
flags() const44 QgsGeocoderInterface::Flags QgsNominatimGeocoder::flags() const
45 {
46 return QgsGeocoderInterface::Flag::GeocodesStrings;
47 }
48
appendedFields() const49 QgsFields QgsNominatimGeocoder::appendedFields() const
50 {
51 QgsFields fields;
52 fields.append( QgsField( QStringLiteral( "osm_type" ), QVariant::String ) );
53 fields.append( QgsField( QStringLiteral( "display_name" ), QVariant::String ) );
54 fields.append( QgsField( QStringLiteral( "place_id" ), QVariant::String ) );
55 fields.append( QgsField( QStringLiteral( "class" ), QVariant::String ) );
56 fields.append( QgsField( QStringLiteral( "type" ), QVariant::String ) );
57 fields.append( QgsField( QStringLiteral( "road" ), QVariant::String ) );
58 fields.append( QgsField( QStringLiteral( "village" ), QVariant::String ) );
59 fields.append( QgsField( QStringLiteral( "city_district" ), QVariant::String ) );
60 fields.append( QgsField( QStringLiteral( "town" ), QVariant::String ) );
61 fields.append( QgsField( QStringLiteral( "city" ), QVariant::String ) );
62 fields.append( QgsField( QStringLiteral( "state" ), QVariant::String ) );
63 fields.append( QgsField( QStringLiteral( "country" ), QVariant::String ) );
64 fields.append( QgsField( QStringLiteral( "postcode" ), QVariant::String ) );
65 return fields;
66 }
67
wkbType() const68 QgsWkbTypes::Type QgsNominatimGeocoder::wkbType() const
69 {
70 return QgsWkbTypes::Point;
71 }
72
geocodeString(const QString & string,const QgsGeocoderContext & context,QgsFeedback * feedback) const73 QList<QgsGeocoderResult> QgsNominatimGeocoder::geocodeString( const QString &string, const QgsGeocoderContext &context, QgsFeedback *feedback ) const
74 {
75 QgsRectangle bounds;
76 if ( !context.areaOfInterest().isEmpty() )
77 {
78 QgsGeometry g = context.areaOfInterest();
79 const QgsCoordinateTransform ct( context.areaOfInterestCrs(), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), context.transformContext() );
80 try
81 {
82 g.transform( ct );
83 bounds = g.boundingBox();
84 }
85 catch ( QgsCsException & )
86 {
87 QgsDebugMsg( "Could not transform geocode bounds to WGS84" );
88 }
89 }
90
91 const QUrl url = requestUrl( string, bounds );
92
93 const QMutexLocker locker( &sMutex );
94 const auto it = sCachedResults()->constFind( url );
95 if ( it != sCachedResults()->constEnd() )
96 {
97 return *it;
98 }
99
100 while ( QDateTime::currentMSecsSinceEpoch() - sLastRequestTimestamp < 1000 / mRequestsPerSecond )
101 {
102 QThread::msleep( 50 );
103 if ( feedback && feedback->isCanceled() )
104 return QList<QgsGeocoderResult>();
105 }
106
107 QNetworkRequest request( url );
108 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsNominatimGeocoder" ) );
109
110 QgsBlockingNetworkRequest newReq;
111 const QgsBlockingNetworkRequest::ErrorCode errorCode = newReq.get( request, false, feedback );
112
113 sLastRequestTimestamp = QDateTime::currentMSecsSinceEpoch();
114
115 if ( errorCode != QgsBlockingNetworkRequest::NoError )
116 {
117 return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( newReq.errorMessage() );
118 }
119
120 // Parse data
121 QJsonParseError err;
122 const QJsonDocument doc = QJsonDocument::fromJson( newReq.reply().content(), &err );
123 if ( doc.isNull() )
124 {
125 return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( err.errorString() );
126 }
127
128 const QVariantList results = doc.array().toVariantList();
129 if ( results.isEmpty() )
130 {
131 sCachedResults()->insert( url, QList<QgsGeocoderResult>() );
132 return QList<QgsGeocoderResult>();
133 }
134
135 QList< QgsGeocoderResult > matches;
136 matches.reserve( results.size() );
137 for ( const QVariant &result : results )
138 {
139 matches << jsonToResult( result.toMap() );
140 }
141
142 sCachedResults()->insert( url, matches );
143
144 return matches;
145 }
146
requestUrl(const QString & address,const QgsRectangle & bounds) const147 QUrl QgsNominatimGeocoder::requestUrl( const QString &address, const QgsRectangle &bounds ) const
148 {
149 QUrl res( mEndpoint );
150 QUrlQuery query;
151 query.addQueryItem( QStringLiteral( "format" ), QStringLiteral( "json" ) );
152 query.addQueryItem( QStringLiteral( "addressdetails" ), QStringLiteral( "1" ) );
153 if ( !bounds.isNull() )
154 {
155 query.addQueryItem( QStringLiteral( "viewbox" ), QStringLiteral( "%1,%2,%3,%4" ).arg( bounds.xMinimum() )
156 .arg( bounds.yMinimum() )
157 .arg( bounds.xMaximum() )
158 .arg( bounds.yMaximum() ) );
159 }
160 if ( !mCountryCodes.isEmpty() )
161 {
162 query.addQueryItem( QStringLiteral( "countrycodes" ), mCountryCodes.toLower() );
163 }
164 query.addQueryItem( QStringLiteral( "q" ), address );
165 res.setQuery( query );
166
167 return res;
168 }
169
jsonToResult(const QVariantMap & json) const170 QgsGeocoderResult QgsNominatimGeocoder::jsonToResult( const QVariantMap &json ) const
171 {
172 const double latitude = json.value( QStringLiteral( "lat" ) ).toDouble();
173 const double longitude = json.value( QStringLiteral( "lon" ) ).toDouble();
174
175 const QgsGeometry geom = QgsGeometry::fromPointXY( QgsPointXY( longitude, latitude ) );
176
177 QgsGeocoderResult res( json.value( QStringLiteral( "display_name" ) ).toString(),
178 geom,
179 QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
180
181 QVariantMap attributes;
182
183 if ( json.contains( QStringLiteral( "display_name" ) ) )
184 attributes.insert( QStringLiteral( "display_name" ), json.value( QStringLiteral( "display_name" ) ).toString() );
185 if ( json.contains( QStringLiteral( "place_id" ) ) )
186 attributes.insert( QStringLiteral( "place_id" ), json.value( QStringLiteral( "place_id" ) ).toString() );
187 if ( json.contains( QStringLiteral( "osm_type" ) ) )
188 attributes.insert( QStringLiteral( "osm_type" ), json.value( QStringLiteral( "osm_type" ) ).toString() );
189 if ( json.contains( QStringLiteral( "class" ) ) )
190 attributes.insert( QStringLiteral( "class" ), json.value( QStringLiteral( "class" ) ).toString() );
191 if ( json.contains( QStringLiteral( "type" ) ) )
192 attributes.insert( QStringLiteral( "type" ), json.value( QStringLiteral( "type" ) ).toString() );
193
194 if ( json.contains( QStringLiteral( "address" ) ) )
195 {
196 const QVariantMap address_components = json.value( QStringLiteral( "address" ) ).toMap();
197 if ( address_components.contains( QStringLiteral( "road" ) ) )
198 attributes.insert( QStringLiteral( "road" ), address_components.value( QStringLiteral( "road" ) ).toString() );
199 if ( address_components.contains( QStringLiteral( "village" ) ) )
200 attributes.insert( QStringLiteral( "village" ), address_components.value( QStringLiteral( "village" ) ).toString() );
201 if ( address_components.contains( QStringLiteral( "city_district" ) ) )
202 attributes.insert( QStringLiteral( "city_district" ), address_components.value( QStringLiteral( "city_district" ) ).toString() );
203 if ( address_components.contains( QStringLiteral( "town" ) ) )
204 attributes.insert( QStringLiteral( "town" ), address_components.value( QStringLiteral( "town" ) ).toString() );
205 if ( address_components.contains( QStringLiteral( "city" ) ) )
206 attributes.insert( QStringLiteral( "city" ), address_components.value( QStringLiteral( "city" ) ).toString() );
207 if ( address_components.contains( QStringLiteral( "state" ) ) )
208 {
209 attributes.insert( QStringLiteral( "state" ), address_components.value( QStringLiteral( "state" ) ).toString() );
210 res.setGroup( address_components.value( QStringLiteral( "state" ) ).toString() );
211 }
212 if ( address_components.contains( QStringLiteral( "country" ) ) )
213 attributes.insert( QStringLiteral( "country" ), address_components.value( QStringLiteral( "country" ) ).toString() );
214 if ( address_components.contains( QStringLiteral( "postcode" ) ) )
215 attributes.insert( QStringLiteral( "postcode" ), address_components.value( QStringLiteral( "postcode" ) ).toString() );
216 }
217
218 if ( json.contains( QStringLiteral( "boundingbox" ) ) )
219 {
220 const QVariantList boundingBox = json.value( QStringLiteral( "boundingbox" ) ).toList();
221 if ( boundingBox.size() == 4 )
222 res.setViewport( QgsRectangle( boundingBox.at( 2 ).toDouble(),
223 boundingBox.at( 0 ).toDouble(),
224 boundingBox.at( 3 ).toDouble(),
225 boundingBox.at( 1 ).toDouble() ) );
226 }
227
228 res.setAdditionalAttributes( attributes );
229 return res;
230 }
231
endpoint() const232 QString QgsNominatimGeocoder::endpoint() const
233 {
234 return mEndpoint;
235 }
236
setEndpoint(const QString & endpoint)237 void QgsNominatimGeocoder::setEndpoint( const QString &endpoint )
238 {
239 mEndpoint = endpoint;
240 }
241
countryCodes() const242 QString QgsNominatimGeocoder::countryCodes() const
243 {
244 return mCountryCodes;
245 }
246
setCountryCodes(const QString & countryCodes)247 void QgsNominatimGeocoder::setCountryCodes( const QString &countryCodes )
248 {
249 mCountryCodes = countryCodes;
250 }
251