QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
qgsnominatimgeocoder.cpp
Go to the documentation of this file.
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
17
20#include "qgsgeocodercontext.h"
21#include "qgslogger.h"
24
25#include <QDateTime>
26#include <QJsonArray>
27#include <QJsonDocument>
28#include <QMutex>
29#include <QNetworkRequest>
30#include <QString>
31#include <QUrl>
32#include <QUrlQuery>
33
34using namespace Qt::StringLiterals;
35
36QMutex QgsNominatimGeocoder::sMutex;
37typedef QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult;
38Q_GLOBAL_STATIC( CachedGeocodeResult, sCachedResultsNominatim )
39qint64 QgsNominatimGeocoder::sLastRequestTimestamp = 0;
40
43 , mCountryCodes( countryCodes )
44 , mEndpoint( u"https://nominatim.qgis.org/search"_s )
45{
46 if ( !endpoint.isEmpty() )
47 mEndpoint = endpoint;
48}
49
54
56{
57 QgsFields fields;
58 fields.append( QgsField( u"osm_type"_s, QMetaType::Type::QString ) );
59 fields.append( QgsField( u"display_name"_s, QMetaType::Type::QString ) );
60 fields.append( QgsField( u"place_id"_s, QMetaType::Type::QString ) );
61 fields.append( QgsField( u"class"_s, QMetaType::Type::QString ) );
62 fields.append( QgsField( u"type"_s, QMetaType::Type::QString ) );
63 fields.append( QgsField( u"road"_s, QMetaType::Type::QString ) );
64 fields.append( QgsField( u"village"_s, QMetaType::Type::QString ) );
65 fields.append( QgsField( u"city_district"_s, QMetaType::Type::QString ) );
66 fields.append( QgsField( u"town"_s, QMetaType::Type::QString ) );
67 fields.append( QgsField( u"city"_s, QMetaType::Type::QString ) );
68 fields.append( QgsField( u"state"_s, QMetaType::Type::QString ) );
69 fields.append( QgsField( u"country"_s, QMetaType::Type::QString ) );
70 fields.append( QgsField( u"postcode"_s, QMetaType::Type::QString ) );
71 return fields;
72}
73
78
79QList<QgsGeocoderResult> QgsNominatimGeocoder::geocodeString( const QString &string, const QgsGeocoderContext &context, QgsFeedback *feedback ) const
80{
81 QgsRectangle bounds;
82 if ( !context.areaOfInterest().isEmpty() )
83 {
84 QgsGeometry g = context.areaOfInterest();
85 const QgsCoordinateTransform ct( context.areaOfInterestCrs(), QgsCoordinateReferenceSystem( u"EPSG:4326"_s ), context.transformContext() );
86 try
87 {
88 g.transform( ct );
89 bounds = g.boundingBox();
90 }
91 catch ( QgsCsException & )
92 {
93 QgsDebugError( "Could not transform geocode bounds to WGS84" );
94 }
95 }
96
97 const QUrl url = requestUrl( string, bounds );
98
99 const QMutexLocker locker( &sMutex );
100 const auto it = sCachedResultsNominatim()->constFind( url );
101 if ( it != sCachedResultsNominatim()->constEnd() )
102 {
103 return *it;
104 }
105
106 while ( QDateTime::currentMSecsSinceEpoch() - sLastRequestTimestamp < 1000 / mRequestsPerSecond )
107 {
108 QThread::msleep( 50 );
109 if ( feedback && feedback->isCanceled() )
110 return QList<QgsGeocoderResult>();
111 }
112
113 QNetworkRequest request( url );
114 QgsSetRequestInitiatorClass( request, u"QgsNominatimGeocoder"_s );
115
117 const QgsBlockingNetworkRequest::ErrorCode errorCode = newReq.get( request, false, feedback );
118
119 sLastRequestTimestamp = QDateTime::currentMSecsSinceEpoch();
120
121 if ( errorCode != QgsBlockingNetworkRequest::NoError )
122 {
123 return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( newReq.errorMessage() );
124 }
125
126 // Parse data
127 QJsonParseError err;
128 const QJsonDocument doc = QJsonDocument::fromJson( newReq.reply().content(), &err );
129 if ( doc.isNull() )
130 {
131 return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( err.errorString() );
132 }
133
134 const QVariantList results = doc.array().toVariantList();
135 if ( results.isEmpty() )
136 {
137 sCachedResultsNominatim()->insert( url, QList<QgsGeocoderResult>() );
138 return QList<QgsGeocoderResult>();
139 }
140
141 QList< QgsGeocoderResult > matches;
142 matches.reserve( results.size() );
143 for ( const QVariant &result : results )
144 {
145 matches << jsonToResult( result.toMap() );
146 }
147
148 sCachedResultsNominatim()->insert( url, matches );
149
150 return matches;
151}
152
153QUrl QgsNominatimGeocoder::requestUrl( const QString &address, const QgsRectangle &bounds ) const
154{
155 QUrl res( mEndpoint );
156 QUrlQuery query;
157 query.addQueryItem( u"format"_s, u"json"_s );
158 query.addQueryItem( u"addressdetails"_s, u"1"_s );
159 if ( !bounds.isNull() && bounds.isFinite() )
160 {
161 query.addQueryItem( u"viewbox"_s, bounds.toString( 7 ).replace( " : "_L1, ","_L1 ) );
162 }
163 if ( !mCountryCodes.isEmpty() )
164 {
165 query.addQueryItem( u"countrycodes"_s, mCountryCodes.toLower() );
166 }
167 query.addQueryItem( u"q"_s, address );
168 res.setQuery( query );
169
170 return res;
171}
172
174{
175 const double latitude = json.value( u"lat"_s ).toDouble();
176 const double longitude = json.value( u"lon"_s ).toDouble();
177
178 const QgsGeometry geom = QgsGeometry::fromPointXY( QgsPointXY( longitude, latitude ) );
179
180 QgsGeocoderResult res( json.value( u"display_name"_s ).toString(),
181 geom,
182 QgsCoordinateReferenceSystem( u"EPSG:4326"_s ) );
183
184 QVariantMap attributes;
185
186 if ( json.contains( u"display_name"_s ) )
187 attributes.insert( u"display_name"_s, json.value( u"display_name"_s ).toString() );
188 if ( json.contains( u"place_id"_s ) )
189 attributes.insert( u"place_id"_s, json.value( u"place_id"_s ).toString() );
190 if ( json.contains( u"osm_type"_s ) )
191 attributes.insert( u"osm_type"_s, json.value( u"osm_type"_s ).toString() );
192 if ( json.contains( u"class"_s ) )
193 attributes.insert( u"class"_s, json.value( u"class"_s ).toString() );
194 if ( json.contains( u"type"_s ) )
195 attributes.insert( u"type"_s, json.value( u"type"_s ).toString() );
196
197 if ( json.contains( u"address"_s ) )
198 {
199 const QVariantMap address_components = json.value( u"address"_s ).toMap();
200 if ( address_components.contains( u"road"_s ) )
201 attributes.insert( u"road"_s, address_components.value( u"road"_s ).toString() );
202 if ( address_components.contains( u"village"_s ) )
203 attributes.insert( u"village"_s, address_components.value( u"village"_s ).toString() );
204 if ( address_components.contains( u"city_district"_s ) )
205 attributes.insert( u"city_district"_s, address_components.value( u"city_district"_s ).toString() );
206 if ( address_components.contains( u"town"_s ) )
207 attributes.insert( u"town"_s, address_components.value( u"town"_s ).toString() );
208 if ( address_components.contains( u"city"_s ) )
209 attributes.insert( u"city"_s, address_components.value( u"city"_s ).toString() );
210 if ( address_components.contains( u"state"_s ) )
211 {
212 attributes.insert( u"state"_s, address_components.value( u"state"_s ).toString() );
213 res.setGroup( address_components.value( u"state"_s ).toString() );
214 }
215 if ( address_components.contains( u"country"_s ) )
216 attributes.insert( u"country"_s, address_components.value( u"country"_s ).toString() );
217 if ( address_components.contains( u"postcode"_s ) )
218 attributes.insert( u"postcode"_s, address_components.value( u"postcode"_s ).toString() );
219 }
220
221 if ( json.contains( u"boundingbox"_s ) )
222 {
223 const QVariantList boundingBox = json.value( u"boundingbox"_s ).toList();
224 if ( boundingBox.size() == 4 )
225 res.setViewport( QgsRectangle( boundingBox.at( 2 ).toDouble(),
226 boundingBox.at( 0 ).toDouble(),
227 boundingBox.at( 3 ).toDouble(),
228 boundingBox.at( 1 ).toDouble() ) );
229 }
230
231 res.setAdditionalAttributes( attributes );
232 return res;
233}
234
236{
237 return mEndpoint;
238}
239
241{
242 mEndpoint = endpoint;
243}
244
246{
247 return mCountryCodes;
248}
249
251{
252 mCountryCodes = countryCodes;
253}
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
@ Point
Point.
Definition qgis.h:282
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ NoError
No error was encountered.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:76
Encapsulates the context of a geocoding operation.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which should be used whenever the geocoder constructs a coo...
QgsCoordinateReferenceSystem areaOfInterestCrs() const
Returns the coordinate reference system for the area of interest, which can be used to indicate the d...
QgsGeometry areaOfInterest() const
Returns the optional area of interest, which can be used to indicate the desired geographic area wher...
Interface for geocoders.
Definition qgsgeocoder.h:37
@ GeocodesStrings
Can geocode string input values.
Definition qgsgeocoder.h:44
QFlags< Flag > Flags
Definition qgsgeocoder.h:47
Represents a matching result from a geocoder search.
void setAdditionalAttributes(const QVariantMap &attributes)
Setss additional attributes generated during the geocode, which may be added to features being geocod...
void setGroup(const QString &group)
Sets the optional group value for the result.
void setViewport(const QgsRectangle &viewport)
Sets the suggested viewport for the result, which reflects a recommended map extent for displaying th...
static QgsGeocoderResult errorResult(const QString &errorMessage)
Creates an invalid error result, with the specified errorMessage string.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QByteArray content() const
Returns the reply content.
QList< QgsGeocoderResult > geocodeString(const QString &string, const QgsGeocoderContext &context, QgsFeedback *feedback=nullptr) const override
Geocodes a string.
QgsGeocoderResult jsonToResult(const QVariantMap &json) const
Converts a JSON result returned from the Nominatim service to a geocoder result object.
QgsNominatimGeocoder(const QString &countryCodes=QString(), const QString &endpoint=QString())
Constructor for QgsNominatimGeocoder.
QString countryCodes() const
Returns the optional region bias which will be used to prioritize results in a certain region.
QString endpoint() const
Returns the API endpoint used for requests.
QgsFields appendedFields() const override
Returns a set of newly created fields which will be appended to existing features during the geocode ...
Qgis::WkbType wkbType() const override
Returns the WKB type of geometries returned by the geocoder.
void setEndpoint(const QString &endpoint)
Sets a specific API endpoint to use for requests.
Flags flags() const override
Returns the geocoder's capability flags.
void setCountryCodes(const QString &countryCodes)
Sets the optional region bias which will be used to prioritize results in a certain region.
QUrl requestUrl(const QString &address, const QgsRectangle &bounds=QgsRectangle()) const
Returns the URL generated for geocoding the specified address.
Represents a 2D point.
Definition qgspointxy.h:62
A rectangle specified with double values.
Q_INVOKABLE QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)