QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
18#include "qgsgeocodercontext.h"
19#include "qgslogger.h"
23#include <QDateTime>
24#include <QUrl>
25#include <QUrlQuery>
26#include <QMutex>
27#include <QNetworkRequest>
28#include <QJsonDocument>
29#include <QJsonArray>
30
31QMutex QgsNominatimGeocoder::sMutex;
32typedef QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult;
33Q_GLOBAL_STATIC( CachedGeocodeResult, sCachedResults )
34qint64 QgsNominatimGeocoder::sLastRequestTimestamp = 0;
35
36QgsNominatimGeocoder::QgsNominatimGeocoder( const QString &countryCodes, const QString &endpoint )
38 , mCountryCodes( countryCodes )
39 , mEndpoint( QStringLiteral( "https://nominatim.qgis.org/search" ) )
40{
41 if ( !endpoint.isEmpty() )
42 mEndpoint = endpoint;
43}
44
46{
48}
49
51{
52 QgsFields fields;
53 fields.append( QgsField( QStringLiteral( "osm_type" ), QVariant::String ) );
54 fields.append( QgsField( QStringLiteral( "display_name" ), QVariant::String ) );
55 fields.append( QgsField( QStringLiteral( "place_id" ), QVariant::String ) );
56 fields.append( QgsField( QStringLiteral( "class" ), QVariant::String ) );
57 fields.append( QgsField( QStringLiteral( "type" ), QVariant::String ) );
58 fields.append( QgsField( QStringLiteral( "road" ), QVariant::String ) );
59 fields.append( QgsField( QStringLiteral( "village" ), QVariant::String ) );
60 fields.append( QgsField( QStringLiteral( "city_district" ), QVariant::String ) );
61 fields.append( QgsField( QStringLiteral( "town" ), QVariant::String ) );
62 fields.append( QgsField( QStringLiteral( "city" ), QVariant::String ) );
63 fields.append( QgsField( QStringLiteral( "state" ), QVariant::String ) );
64 fields.append( QgsField( QStringLiteral( "country" ), QVariant::String ) );
65 fields.append( QgsField( QStringLiteral( "postcode" ), QVariant::String ) );
66 return fields;
67}
68
70{
72}
73
74QList<QgsGeocoderResult> QgsNominatimGeocoder::geocodeString( const QString &string, const QgsGeocoderContext &context, QgsFeedback *feedback ) const
75{
76 QgsRectangle bounds;
77 if ( !context.areaOfInterest().isEmpty() )
78 {
79 QgsGeometry g = context.areaOfInterest();
80 const QgsCoordinateTransform ct( context.areaOfInterestCrs(), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), context.transformContext() );
81 try
82 {
83 g.transform( ct );
84 bounds = g.boundingBox();
85 }
86 catch ( QgsCsException & )
87 {
88 QgsDebugError( "Could not transform geocode bounds to WGS84" );
89 }
90 }
91
92 const QUrl url = requestUrl( string, bounds );
93
94 const QMutexLocker locker( &sMutex );
95 const auto it = sCachedResults()->constFind( url );
96 if ( it != sCachedResults()->constEnd() )
97 {
98 return *it;
99 }
100
101 while ( QDateTime::currentMSecsSinceEpoch() - sLastRequestTimestamp < 1000 / mRequestsPerSecond )
102 {
103 QThread::msleep( 50 );
104 if ( feedback && feedback->isCanceled() )
105 return QList<QgsGeocoderResult>();
106 }
107
108 QNetworkRequest request( url );
109 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsNominatimGeocoder" ) );
110
112 const QgsBlockingNetworkRequest::ErrorCode errorCode = newReq.get( request, false, feedback );
113
114 sLastRequestTimestamp = QDateTime::currentMSecsSinceEpoch();
115
116 if ( errorCode != QgsBlockingNetworkRequest::NoError )
117 {
118 return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( newReq.errorMessage() );
119 }
120
121 // Parse data
122 QJsonParseError err;
123 const QJsonDocument doc = QJsonDocument::fromJson( newReq.reply().content(), &err );
124 if ( doc.isNull() )
125 {
126 return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( err.errorString() );
127 }
128
129 const QVariantList results = doc.array().toVariantList();
130 if ( results.isEmpty() )
131 {
132 sCachedResults()->insert( url, QList<QgsGeocoderResult>() );
133 return QList<QgsGeocoderResult>();
134 }
135
136 QList< QgsGeocoderResult > matches;
137 matches.reserve( results.size() );
138 for ( const QVariant &result : results )
139 {
140 matches << jsonToResult( result.toMap() );
141 }
142
143 sCachedResults()->insert( url, matches );
144
145 return matches;
146}
147
148QUrl QgsNominatimGeocoder::requestUrl( const QString &address, const QgsRectangle &bounds ) const
149{
150 QUrl res( mEndpoint );
151 QUrlQuery query;
152 query.addQueryItem( QStringLiteral( "format" ), QStringLiteral( "json" ) );
153 query.addQueryItem( QStringLiteral( "addressdetails" ), QStringLiteral( "1" ) );
154 if ( !bounds.isNull() && bounds.isFinite() )
155 {
156 query.addQueryItem( QStringLiteral( "viewbox" ), bounds.toString( 7 ).replace( QLatin1String( " : " ), QLatin1String( "," ) ) );
157 }
158 if ( !mCountryCodes.isEmpty() )
159 {
160 query.addQueryItem( QStringLiteral( "countrycodes" ), mCountryCodes.toLower() );
161 }
162 query.addQueryItem( QStringLiteral( "q" ), address );
163 res.setQuery( query );
164
165 return res;
166}
167
169{
170 const double latitude = json.value( QStringLiteral( "lat" ) ).toDouble();
171 const double longitude = json.value( QStringLiteral( "lon" ) ).toDouble();
172
173 const QgsGeometry geom = QgsGeometry::fromPointXY( QgsPointXY( longitude, latitude ) );
174
175 QgsGeocoderResult res( json.value( QStringLiteral( "display_name" ) ).toString(),
176 geom,
177 QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
178
179 QVariantMap attributes;
180
181 if ( json.contains( QStringLiteral( "display_name" ) ) )
182 attributes.insert( QStringLiteral( "display_name" ), json.value( QStringLiteral( "display_name" ) ).toString() );
183 if ( json.contains( QStringLiteral( "place_id" ) ) )
184 attributes.insert( QStringLiteral( "place_id" ), json.value( QStringLiteral( "place_id" ) ).toString() );
185 if ( json.contains( QStringLiteral( "osm_type" ) ) )
186 attributes.insert( QStringLiteral( "osm_type" ), json.value( QStringLiteral( "osm_type" ) ).toString() );
187 if ( json.contains( QStringLiteral( "class" ) ) )
188 attributes.insert( QStringLiteral( "class" ), json.value( QStringLiteral( "class" ) ).toString() );
189 if ( json.contains( QStringLiteral( "type" ) ) )
190 attributes.insert( QStringLiteral( "type" ), json.value( QStringLiteral( "type" ) ).toString() );
191
192 if ( json.contains( QStringLiteral( "address" ) ) )
193 {
194 const QVariantMap address_components = json.value( QStringLiteral( "address" ) ).toMap();
195 if ( address_components.contains( QStringLiteral( "road" ) ) )
196 attributes.insert( QStringLiteral( "road" ), address_components.value( QStringLiteral( "road" ) ).toString() );
197 if ( address_components.contains( QStringLiteral( "village" ) ) )
198 attributes.insert( QStringLiteral( "village" ), address_components.value( QStringLiteral( "village" ) ).toString() );
199 if ( address_components.contains( QStringLiteral( "city_district" ) ) )
200 attributes.insert( QStringLiteral( "city_district" ), address_components.value( QStringLiteral( "city_district" ) ).toString() );
201 if ( address_components.contains( QStringLiteral( "town" ) ) )
202 attributes.insert( QStringLiteral( "town" ), address_components.value( QStringLiteral( "town" ) ).toString() );
203 if ( address_components.contains( QStringLiteral( "city" ) ) )
204 attributes.insert( QStringLiteral( "city" ), address_components.value( QStringLiteral( "city" ) ).toString() );
205 if ( address_components.contains( QStringLiteral( "state" ) ) )
206 {
207 attributes.insert( QStringLiteral( "state" ), address_components.value( QStringLiteral( "state" ) ).toString() );
208 res.setGroup( address_components.value( QStringLiteral( "state" ) ).toString() );
209 }
210 if ( address_components.contains( QStringLiteral( "country" ) ) )
211 attributes.insert( QStringLiteral( "country" ), address_components.value( QStringLiteral( "country" ) ).toString() );
212 if ( address_components.contains( QStringLiteral( "postcode" ) ) )
213 attributes.insert( QStringLiteral( "postcode" ), address_components.value( QStringLiteral( "postcode" ) ).toString() );
214 }
215
216 if ( json.contains( QStringLiteral( "boundingbox" ) ) )
217 {
218 const QVariantList boundingBox = json.value( QStringLiteral( "boundingbox" ) ).toList();
219 if ( boundingBox.size() == 4 )
220 res.setViewport( QgsRectangle( boundingBox.at( 2 ).toDouble(),
221 boundingBox.at( 0 ).toDouble(),
222 boundingBox.at( 3 ).toDouble(),
223 boundingBox.at( 1 ).toDouble() ) );
224 }
225
226 res.setAdditionalAttributes( attributes );
227 return res;
228}
229
231{
232 return mEndpoint;
233}
234
235void QgsNominatimGeocoder::setEndpoint( const QString &endpoint )
236{
237 mEndpoint = endpoint;
238}
239
241{
242 return mCountryCodes;
243}
244
245void QgsNominatimGeocoder::setCountryCodes( const QString &countryCodes )
246{
247 mCountryCodes = countryCodes;
248}
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:182
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
@ 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...
This class represents a coordinate reference system (CRS).
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
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:53
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
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.
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.
Definition: qgsgeometry.h:162
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.
A class to represent a 2D point.
Definition: qgspointxy.h:60
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
bool isNull() const
Test if the rectangle is null (holding no spatial information).
Definition: qgsrectangle.h:505
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Definition: qgsrectangle.h:588
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult
#define QgsDebugError(str)
Definition: qgslogger.h:38
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult
#define QgsSetRequestInitiatorClass(request, _class)