QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
16 #include "qgsnominatimgeocoder.h"
18 #include "qgsgeocodercontext.h"
19 #include "qgslogger.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 
35 QgsNominatimGeocoder::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 
44 QgsGeocoderInterface::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 
69 {
70  return QgsWkbTypes::Point;
71 }
72 
73 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 
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 
147 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 
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 
233 {
234  return mEndpoint;
235 }
236 
237 void QgsNominatimGeocoder::setEndpoint( const QString &endpoint )
238 {
239  mEndpoint = endpoint;
240 }
241 
243 {
244  return mCountryCodes;
245 }
246 
247 void QgsNominatimGeocoder::setCountryCodes( const QString &countryCodes )
248 {
249  mCountryCodes = countryCodes;
250 }
QgsWkbTypes::Point
@ Point
Definition: qgswkbtypes.h:72
QgsGeometry::transform
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
Definition: qgsgeometry.cpp:3128
qgsblockingnetworkrequest.h
CachedGeocodeResult
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult
Definition: qgsgooglemapsgeocoder.cpp:31
QgsFields
Container of fields for a vector layer.
Definition: qgsfields.h:44
QgsRectangle::yMinimum
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
QgsBlockingNetworkRequest::reply
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Definition: qgsblockingnetworkrequest.h:193
QgsGeometry::fromPointXY
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
Creates a new geometry from a QgsPointXY object.
Definition: qgsgeometry.cpp:176
QgsFeedback::isCanceled
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:67
QgsWkbTypes::Type
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:69
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsFields::append
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
QgsNominatimGeocoder::geocodeString
QList< QgsGeocoderResult > geocodeString(const QString &string, const QgsGeocoderContext &context, QgsFeedback *feedback=nullptr) const override
Geocodes a string.
Definition: qgsnominatimgeocoder.cpp:73
QgsGeocoderResult::setAdditionalAttributes
void setAdditionalAttributes(const QVariantMap &attributes)
Setss additional attributes generated during the geocode, which may be added to features being geocod...
Definition: qgsgeocoderresult.h:163
Q_GLOBAL_STATIC
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsNominatimGeocoder::endpoint
QString endpoint() const
Returns the API endpoint used for requests.
Definition: qgsnominatimgeocoder.cpp:232
QgsSetRequestInitiatorClass
#define QgsSetRequestInitiatorClass(request, _class)
Definition: qgsnetworkaccessmanager.h:45
QgsGeocoderResult::setGroup
void setGroup(const QString &group)
Sets the optional group value for the result.
Definition: qgsgeocoderresult.cpp:38
QgsBlockingNetworkRequest::ErrorCode
ErrorCode
Error codes.
Definition: qgsblockingnetworkrequest.h:52
QgsRectangle::xMaximum
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
QgsNominatimGeocoder::appendedFields
QgsFields appendedFields() const override
Returns a set of newly created fields which will be appended to existing features during the geocode ...
Definition: qgsnominatimgeocoder.cpp:49
QgsCsException
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
QgsNominatimGeocoder::jsonToResult
QgsGeocoderResult jsonToResult(const QVariantMap &json) const
Converts a JSON result returned from the Nominatim service to a geocoder result object.
Definition: qgsnominatimgeocoder.cpp:170
QgsNominatimGeocoder::setEndpoint
void setEndpoint(const QString &endpoint)
Sets a specific API endpoint to use for requests.
Definition: qgsnominatimgeocoder.cpp:237
QgsNominatimGeocoder::setCountryCodes
void setCountryCodes(const QString &countryCodes)
Sets the optional region bias which will be used to prioritize results in a certain region.
Definition: qgsnominatimgeocoder.cpp:247
QgsFeedback
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
qgsnetworkaccessmanager.h
QgsGeocoderResult::setViewport
void setViewport(const QgsRectangle &viewport)
Sets the suggested viewport for the result, which reflects a recommended map extent for displaying th...
Definition: qgsgeocoderresult.h:147
qgscoordinatetransform.h
QgsBlockingNetworkRequest::NoError
@ NoError
No error was encountered.
Definition: qgsblockingnetworkrequest.h:54
QgsGeometry::isEmpty
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
Definition: qgsgeometry.cpp:379
QgsGeocoderContext::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which should be used whenever the geocoder constructs a coo...
Definition: qgsgeocodercontext.h:60
QgsGeocoderInterface::Flag::GeocodesStrings
@ GeocodesStrings
Can geocode string input values.
QgsBlockingNetworkRequest::errorMessage
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
Definition: qgsblockingnetworkrequest.h:188
QgsGeocoderContext::areaOfInterest
QgsGeometry areaOfInterest() const
Returns the optional area of interest, which can be used to indicate the desired geographic area wher...
Definition: qgsgeocodercontext.h:84
QgsRectangle::xMinimum
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
QgsNominatimGeocoder::countryCodes
QString countryCodes() const
Returns the optional region bias which will be used to prioritize results in a certain region.
Definition: qgsnominatimgeocoder.cpp:242
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsNominatimGeocoder::flags
Flags flags() const override
Returns the geocoder's capability flags.
Definition: qgsnominatimgeocoder.cpp:44
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:58
QgsGeocoderResult::errorResult
static QgsGeocoderResult errorResult(const QString &errorMessage)
Creates an invalid error result, with the specified errorMessage string.
Definition: qgsgeocoderresult.cpp:18
QgsNominatimGeocoder::wkbType
QgsWkbTypes::Type wkbType() const override
Returns the WKB type of geometries returned by the geocoder.
Definition: qgsnominatimgeocoder.cpp:68
QgsGeocoderContext
Encapsulates the context of a geocoding operation.
Definition: qgsgeocodercontext.h:31
QgsBlockingNetworkRequest::get
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
Definition: qgsblockingnetworkrequest.cpp:58
QgsRectangle::yMaximum
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
QgsGeocoderResult
Represents a matching result from a geocoder search.
Definition: qgsgeocoderresult.h:40
QgsGeometry
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsGeocoderContext::areaOfInterestCrs
QgsCoordinateReferenceSystem areaOfInterestCrs() const
Returns the coordinate reference system for the area of interest, which can be used to indicate the d...
Definition: qgsgeocodercontext.h:109
QgsNominatimGeocoder::requestUrl
QUrl requestUrl(const QString &address, const QgsRectangle &bounds=QgsRectangle()) const
Returns the URL generated for geocoding the specified address.
Definition: qgsnominatimgeocoder.cpp:147
QgsGeometry::boundingBox
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Definition: qgsgeometry.cpp:1080
QgsNetworkReplyContent::content
QByteArray content() const
Returns the reply content.
Definition: qgsnetworkreply.h:171
qgslogger.h
QgsCoordinateTransform
Class for doing transforms between two map coordinate systems.
Definition: qgscoordinatetransform.h:57
qgsnominatimgeocoder.h
QgsNominatimGeocoder::QgsNominatimGeocoder
QgsNominatimGeocoder(const QString &countryCodes=QString(), const QString &endpoint=QString())
Constructor for QgsNominatimGeocoder.
Definition: qgsnominatimgeocoder.cpp:35
QgsRectangle::isNull
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
QgsBlockingNetworkRequest
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
Definition: qgsblockingnetworkrequest.h:46
QgsGeocoderInterface
Interface for geocoders.
Definition: qgsgeocoder.h:36
CachedGeocodeResult
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult
Definition: qgsnominatimgeocoder.cpp:31
qgsgeocodercontext.h
QgsField
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:50