QGIS API Documentation 3.37.0-Master (c2d15952569)
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
18#include "qgsgeocodercontext.h"
19#include "qgslogger.h"
22#include <QDateTime>
23#include <QUrl>
24#include <QUrlQuery>
25#include <QMutex>
26#include <QNetworkRequest>
27#include <QJsonDocument>
28#include <QJsonArray>
29
30QMutex QgsNominatimGeocoder::sMutex;
31typedef QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult;
32Q_GLOBAL_STATIC( CachedGeocodeResult, sCachedResults )
33qint64 QgsNominatimGeocoder::sLastRequestTimestamp = 0;
34
35QgsNominatimGeocoder::QgsNominatimGeocoder( const QString &countryCodes, const QString &endpoint )
37 , mCountryCodes( countryCodes )
38 , mEndpoint( QStringLiteral( "https://nominatim.qgis.org/search" ) )
39{
40 if ( !endpoint.isEmpty() )
41 mEndpoint = endpoint;
42}
43
44QgsGeocoderInterface::Flags QgsNominatimGeocoder::flags() const
45{
47}
48
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
72
73QList<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 QgsDebugError( "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
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
147QUrl 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() && bounds.isFinite() )
154 {
155 query.addQueryItem( QStringLiteral( "viewbox" ), bounds.toString( 7 ).replace( QLatin1String( " : " ), QLatin1String( "," ) ) );
156 }
157 if ( !mCountryCodes.isEmpty() )
158 {
159 query.addQueryItem( QStringLiteral( "countrycodes" ), mCountryCodes.toLower() );
160 }
161 query.addQueryItem( QStringLiteral( "q" ), address );
162 res.setQuery( query );
163
164 return res;
165}
166
168{
169 const double latitude = json.value( QStringLiteral( "lat" ) ).toDouble();
170 const double longitude = json.value( QStringLiteral( "lon" ) ).toDouble();
171
172 const QgsGeometry geom = QgsGeometry::fromPointXY( QgsPointXY( longitude, latitude ) );
173
174 QgsGeocoderResult res( json.value( QStringLiteral( "display_name" ) ).toString(),
175 geom,
176 QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
177
178 QVariantMap attributes;
179
180 if ( json.contains( QStringLiteral( "display_name" ) ) )
181 attributes.insert( QStringLiteral( "display_name" ), json.value( QStringLiteral( "display_name" ) ).toString() );
182 if ( json.contains( QStringLiteral( "place_id" ) ) )
183 attributes.insert( QStringLiteral( "place_id" ), json.value( QStringLiteral( "place_id" ) ).toString() );
184 if ( json.contains( QStringLiteral( "osm_type" ) ) )
185 attributes.insert( QStringLiteral( "osm_type" ), json.value( QStringLiteral( "osm_type" ) ).toString() );
186 if ( json.contains( QStringLiteral( "class" ) ) )
187 attributes.insert( QStringLiteral( "class" ), json.value( QStringLiteral( "class" ) ).toString() );
188 if ( json.contains( QStringLiteral( "type" ) ) )
189 attributes.insert( QStringLiteral( "type" ), json.value( QStringLiteral( "type" ) ).toString() );
190
191 if ( json.contains( QStringLiteral( "address" ) ) )
192 {
193 const QVariantMap address_components = json.value( QStringLiteral( "address" ) ).toMap();
194 if ( address_components.contains( QStringLiteral( "road" ) ) )
195 attributes.insert( QStringLiteral( "road" ), address_components.value( QStringLiteral( "road" ) ).toString() );
196 if ( address_components.contains( QStringLiteral( "village" ) ) )
197 attributes.insert( QStringLiteral( "village" ), address_components.value( QStringLiteral( "village" ) ).toString() );
198 if ( address_components.contains( QStringLiteral( "city_district" ) ) )
199 attributes.insert( QStringLiteral( "city_district" ), address_components.value( QStringLiteral( "city_district" ) ).toString() );
200 if ( address_components.contains( QStringLiteral( "town" ) ) )
201 attributes.insert( QStringLiteral( "town" ), address_components.value( QStringLiteral( "town" ) ).toString() );
202 if ( address_components.contains( QStringLiteral( "city" ) ) )
203 attributes.insert( QStringLiteral( "city" ), address_components.value( QStringLiteral( "city" ) ).toString() );
204 if ( address_components.contains( QStringLiteral( "state" ) ) )
205 {
206 attributes.insert( QStringLiteral( "state" ), address_components.value( QStringLiteral( "state" ) ).toString() );
207 res.setGroup( address_components.value( QStringLiteral( "state" ) ).toString() );
208 }
209 if ( address_components.contains( QStringLiteral( "country" ) ) )
210 attributes.insert( QStringLiteral( "country" ), address_components.value( QStringLiteral( "country" ) ).toString() );
211 if ( address_components.contains( QStringLiteral( "postcode" ) ) )
212 attributes.insert( QStringLiteral( "postcode" ), address_components.value( QStringLiteral( "postcode" ) ).toString() );
213 }
214
215 if ( json.contains( QStringLiteral( "boundingbox" ) ) )
216 {
217 const QVariantList boundingBox = json.value( QStringLiteral( "boundingbox" ) ).toList();
218 if ( boundingBox.size() == 4 )
219 res.setViewport( QgsRectangle( boundingBox.at( 2 ).toDouble(),
220 boundingBox.at( 0 ).toDouble(),
221 boundingBox.at( 3 ).toDouble(),
222 boundingBox.at( 1 ).toDouble() ) );
223 }
224
225 res.setAdditionalAttributes( attributes );
226 return res;
227}
228
230{
231 return mEndpoint;
232}
233
234void QgsNominatimGeocoder::setEndpoint( const QString &endpoint )
235{
236 mEndpoint = endpoint;
237}
238
240{
241 return mCountryCodes;
242}
243
244void QgsNominatimGeocoder::setCountryCodes( const QString &countryCodes )
245{
246 mCountryCodes = countryCodes;
247}
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.
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.
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.
A class to represent a 2D point.
Definition qgspointxy.h:60
A rectangle specified with double values.
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).
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:38
#define QgsSetRequestInitiatorClass(request, _class)
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult