QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 }
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() or post() request has been made.
@ NoError
No error was encountered.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get() or post() request has been made.
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:66
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
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.
Definition: qgsgeometry.h:125
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.
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
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 ...
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.
QgsWkbTypes::Type wkbType() const override
Returns the WKB type of geometries returned by the geocoder.
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:59
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)
QMap< QUrl, QList< QgsGeocoderResult > > CachedGeocodeResult