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