QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 <QDateTime>
22 #include <QUrl>
23 #include <QUrlQuery>
24 #include <QMutex>
25 #include <QNetworkRequest>
26 #include <QJsonDocument>
27 #include <QJsonArray>
28 
29 QMutex QgsNominatimGeocoder::sMutex;
30 QMap< QUrl, QList< QgsGeocoderResult > > QgsNominatimGeocoder::sCachedResults;
31 qint64 QgsNominatimGeocoder::sLastRequestTimestamp = 0;
32 
33 QgsNominatimGeocoder::QgsNominatimGeocoder( const QString &countryCodes, const QString &endpoint )
35  , mCountryCodes( countryCodes )
36  , mEndpoint( QStringLiteral( "https://nominatim.openstreetmap.org/search" ) )
37 {
38  if ( !endpoint.isEmpty() )
39  mEndpoint = endpoint;
40 }
41 
42 QgsGeocoderInterface::Flags QgsNominatimGeocoder::flags() const
43 {
45 }
46 
48 {
49  QgsFields fields;
50  fields.append( QgsField( QStringLiteral( "osm_type" ), QVariant::String ) );
51  fields.append( QgsField( QStringLiteral( "display_name" ), QVariant::String ) );
52  fields.append( QgsField( QStringLiteral( "place_id" ), QVariant::String ) );
53  fields.append( QgsField( QStringLiteral( "class" ), QVariant::String ) );
54  fields.append( QgsField( QStringLiteral( "type" ), QVariant::String ) );
55  fields.append( QgsField( QStringLiteral( "road" ), QVariant::String ) );
56  fields.append( QgsField( QStringLiteral( "village" ), QVariant::String ) );
57  fields.append( QgsField( QStringLiteral( "city_district" ), QVariant::String ) );
58  fields.append( QgsField( QStringLiteral( "town" ), QVariant::String ) );
59  fields.append( QgsField( QStringLiteral( "city" ), QVariant::String ) );
60  fields.append( QgsField( QStringLiteral( "state" ), QVariant::String ) );
61  fields.append( QgsField( QStringLiteral( "country" ), QVariant::String ) );
62  fields.append( QgsField( QStringLiteral( "postcode" ), QVariant::String ) );
63  return fields;
64 }
65 
67 {
68  return QgsWkbTypes::Point;
69 }
70 
71 QList<QgsGeocoderResult> QgsNominatimGeocoder::geocodeString( const QString &string, const QgsGeocoderContext &context, QgsFeedback *feedback ) const
72 {
73  QgsRectangle bounds;
74  if ( !context.areaOfInterest().isEmpty() )
75  {
76  QgsGeometry g = context.areaOfInterest();
77  QgsCoordinateTransform ct( context.areaOfInterestCrs(), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), context.transformContext() );
78  try
79  {
80  g.transform( ct );
81  bounds = g.boundingBox();
82  }
83  catch ( QgsCsException & )
84  {
85  QgsDebugMsg( "Could not transform geocode bounds to WGS84" );
86  }
87  }
88 
89  const QUrl url = requestUrl( string, bounds );
90 
91  QMutexLocker locker( &sMutex );
92  auto it = sCachedResults.constFind( url );
93  if ( it != sCachedResults.constEnd() )
94  {
95  return *it;
96  }
97 
98  while ( QDateTime::currentMSecsSinceEpoch() - sLastRequestTimestamp < 1000 / mRequestsPerSecond )
99  {
100  QThread::msleep( 50 );
101  if ( feedback && feedback->isCanceled() )
102  return QList<QgsGeocoderResult>();
103  }
104 
105  QNetworkRequest request( url );
106  QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsNominatimGeocoder" ) );
107 
109  const QgsBlockingNetworkRequest::ErrorCode errorCode = newReq.get( request, false, feedback );
110 
111  sLastRequestTimestamp = QDateTime::currentMSecsSinceEpoch();
112 
113  if ( errorCode != QgsBlockingNetworkRequest::NoError )
114  {
115  return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( newReq.errorMessage() );
116  }
117 
118  // Parse data
119  QJsonParseError err;
120  QJsonDocument doc = QJsonDocument::fromJson( newReq.reply().content(), &err );
121  if ( doc.isNull() )
122  {
123  return QList<QgsGeocoderResult>() << QgsGeocoderResult::errorResult( err.errorString() );
124  }
125 
126  const QVariantList results = doc.array().toVariantList();
127  if ( results.isEmpty() )
128  {
129  sCachedResults.insert( url, QList<QgsGeocoderResult>() );
130  return QList<QgsGeocoderResult>();
131  }
132 
133  QList< QgsGeocoderResult > matches;
134  matches.reserve( results.size() );
135  for ( const QVariant &result : results )
136  {
137  matches << jsonToResult( result.toMap() );
138  }
139 
140  sCachedResults.insert( url, matches );
141 
142  return matches;
143 }
144 
145 QUrl QgsNominatimGeocoder::requestUrl( const QString &address, const QgsRectangle &bounds ) const
146 {
147  QUrl res( mEndpoint );
148  QUrlQuery query;
149  query.addQueryItem( QStringLiteral( "format" ), QStringLiteral( "json" ) );
150  query.addQueryItem( QStringLiteral( "addressdetails" ), QStringLiteral( "1" ) );
151  if ( !bounds.isNull() )
152  {
153  query.addQueryItem( QStringLiteral( "viewbox" ), QStringLiteral( "%1,%2,%3,%4" ).arg( bounds.xMinimum() )
154  .arg( bounds.yMinimum() )
155  .arg( bounds.xMaximum() )
156  .arg( bounds.yMaximum() ) );
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  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 
235 void QgsNominatimGeocoder::setEndpoint( const QString &endpoint )
236 {
237  mEndpoint = endpoint;
238 }
239 
241 {
242  return mCountryCodes;
243 }
244 
245 void QgsNominatimGeocoder::setCountryCodes( const QString &countryCodes )
246 {
247  mCountryCodes = countryCodes;
248 }
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
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:124
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...
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
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 Google Maps 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:44
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:172
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:447
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)