QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgssensorthingsprovider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssensorthingsprovider.cpp
3 ----------------
4 begin : November 2023
5 copyright : (C) 2013 Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
20#include "qgsapplication.h"
24#include "qgsthreadingutils.h"
25#include "qgsreadwritelocker.h"
29
30#include <QIcon>
31#include <QNetworkRequest>
32#include <nlohmann/json.hpp>
33
35
36QgsSensorThingsProvider::QgsSensorThingsProvider( const QString &uri, const ProviderOptions &options, QgsDataProvider::ReadFlags flags )
37 : QgsVectorDataProvider( uri, options, flags )
38{
39 mSharedData = std::make_shared< QgsSensorThingsSharedData >( uri );
40
41 const QUrl url( QgsSensorThingsSharedData::parseUrl( mSharedData->mRootUri ) );
42
43 QNetworkRequest request = QNetworkRequest( url );
44 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsProvider" ) )
45 mSharedData->mHeaders.updateNetworkRequest( request );
46
47 QgsBlockingNetworkRequest networkRequest;
48 networkRequest.setAuthCfg( mSharedData->mAuthCfg );
49
50 switch ( networkRequest.get( request ) )
51 {
53 break;
54
58 appendError( QgsErrorMessage( tr( "Connection failed: %1" ).arg( networkRequest.errorMessage() ), QStringLiteral( "SensorThings" ) ) );
59 return;
60 }
61
62 const QgsNetworkReplyContent content = networkRequest.reply();
63
64 try
65 {
66 auto rootContent = json::parse( content.content().toStdString() );
67 if ( !rootContent.contains( "value" ) )
68 {
69 appendError( QgsErrorMessage( tr( "No 'value' array in response" ), QStringLiteral( "SensorThings" ) ) );
70 return;
71 }
72
73 bool foundMatchingEntity = false;
74 for ( const auto &valueJson : rootContent["value"] )
75 {
76 if ( valueJson.contains( "name" ) && valueJson.contains( "url" ) )
77 {
78 const QString name = QString::fromStdString( valueJson["name"].get<std::string>() );
80 if ( entityType == mSharedData->mEntityType )
81 {
82 const QString url = QString::fromStdString( valueJson["url"].get<std::string>() );
83 if ( !url.isEmpty() )
84 {
85 foundMatchingEntity = true;
86 mSharedData->mEntityBaseUri = url;
87
88 // TODO:
89 // if we always retrieve feature count, is that less expensive then deferring this till we need it?
90 // by retrieving upfront, we can save a lot of requests where we've fetched features from spatial extents
91 // as we'll have a way of determining whether we've fetched all features from the source. Otherwise
92 // we never know if we've got everything yet, and are forced to re-fetched everything when a non-filtered request
93 // comes in...
94 ( void ) mSharedData->featureCount();
95 }
96 }
97 }
98 }
99
100 if ( !foundMatchingEntity )
101 {
102 appendError( QgsErrorMessage( tr( "Could not find url for %1" ).arg( qgsEnumValueToKey( mSharedData->mEntityType ) ), QStringLiteral( "SensorThings" ) ) );
103 return;
104 }
105 }
106 catch ( const json::parse_error &ex )
107 {
108 appendError( QgsErrorMessage( tr( "Error parsing response: %1" ).arg( ex.what() ), QStringLiteral( "SensorThings" ) ) );
109 return;
110 }
111
112 mValid = true;
113}
114
115QString QgsSensorThingsProvider::storageType() const
116{
118
119 return QStringLiteral( "OGC SensorThings API" );
120}
121
122QgsAbstractFeatureSource *QgsSensorThingsProvider::featureSource() const
123{
125
126 return new QgsSensorThingsFeatureSource( mSharedData );
127}
128
129QgsFeatureIterator QgsSensorThingsProvider::getFeatures( const QgsFeatureRequest &request ) const
130{
132
133 return new QgsSensorThingsFeatureIterator( new QgsSensorThingsFeatureSource( mSharedData ), true, request );
134}
135
136Qgis::WkbType QgsSensorThingsProvider::wkbType() const
137{
139
140 return mSharedData->mGeometryType;
141}
142
143long long QgsSensorThingsProvider::featureCount() const
144{
146
147 if ( ( mReadFlags & QgsDataProvider::SkipFeatureCount ) != 0 )
148 {
149 return static_cast< long long >( Qgis::FeatureCountState::UnknownCount );
150 }
151
152 const long long count = mSharedData->featureCount();
153 if ( !mSharedData->error().isEmpty() )
154 pushError( mSharedData->error() );
155
156 return count;
157}
158
159QgsFields QgsSensorThingsProvider::fields() const
160{
162
163 return mSharedData->mFields;
164}
165
166QgsLayerMetadata QgsSensorThingsProvider::layerMetadata() const
167{
169
170 return mLayerMetadata;
171}
172
173QString QgsSensorThingsProvider::htmlMetadata() const
174{
176
177 QString metadata;
178
179 QgsReadWriteLocker locker( mSharedData->mReadWriteLock, QgsReadWriteLocker::Read );
180
181 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Entity Type" ) % QStringLiteral( "</td><td>%1" ).arg( qgsEnumValueToKey( mSharedData->mEntityType ) ) % QStringLiteral( "</td></tr>\n" );
182 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Endpoint" ) % QStringLiteral( "</td><td><a href=\"%1\">%1</a>" ).arg( mSharedData->mEntityBaseUri ) % QStringLiteral( "</td></tr>\n" );
183
184 return metadata;
185}
186
187Qgis::DataProviderFlags QgsSensorThingsProvider::flags() const
188{
190
192}
193
194QgsVectorDataProvider::Capabilities QgsSensorThingsProvider::capabilities() const
195{
197
200 | QgsVectorDataProvider::Capability::ReloadData;
201
202 return c;
203}
204
205bool QgsSensorThingsProvider::supportsSubsetString() const
206{
208 return true;
209}
210
211QString QgsSensorThingsProvider::subsetString() const
212{
214 return mSharedData->subsetString();
215}
216
217bool QgsSensorThingsProvider::setSubsetString( const QString &subset, bool )
218{
220
221 const QString trimmedSubset = subset.trimmed();
222 if ( trimmedSubset == mSharedData->subsetString() )
223 return true;
224
225 // store this and restore it after the data source is changed,
226 // to avoid an unwanted network request to retrieve this again
227 const QString baseUri = mSharedData->mEntityBaseUri;
228
229 QgsDataSourceUri uri = dataSourceUri();
230 uri.setSql( trimmedSubset );
231 setDataSourceUri( uri.uri( false ) );
232
233 mSharedData->mEntityBaseUri = baseUri;
234
235 clearMinMaxCache();
236
237 emit dataChanged();
238
239 return true;
240}
241
242void QgsSensorThingsProvider::setDataSourceUri( const QString &uri )
243{
245
246 mSharedData = std::make_shared< QgsSensorThingsSharedData >( uri );
248}
249
251{
253
254 return mSharedData->mSourceCRS;
255}
256
257QgsRectangle QgsSensorThingsProvider::extent() const
258{
260 return mSharedData->extent();
261}
262
263QString QgsSensorThingsProvider::name() const
264{
266
267 return SENSORTHINGS_PROVIDER_KEY;
268}
269
270QString QgsSensorThingsProvider::providerKey()
271{
272 return SENSORTHINGS_PROVIDER_KEY;
273}
274
275void QgsSensorThingsProvider::handlePostCloneOperations( QgsVectorDataProvider *source )
276{
277 mSharedData = qobject_cast<QgsSensorThingsProvider *>( source )->mSharedData;
278}
279
280QString QgsSensorThingsProvider::description() const
281{
282 return SENSORTHINGS_PROVIDER_DESCRIPTION;
283}
284
285bool QgsSensorThingsProvider::renderInPreview( const PreviewContext & )
286{
287 // be nice to the endpoint and don't make any requests we don't have to!
288 return false;
289}
290
291void QgsSensorThingsProvider::reloadProviderData()
292{
293#if 0
294 mSharedData->clearCache();
295#endif
296}
297
298//
299// QgsSensorThingsProviderMetadata
300//
301
302QgsSensorThingsProviderMetadata::QgsSensorThingsProviderMetadata():
303 QgsProviderMetadata( QgsSensorThingsProvider::SENSORTHINGS_PROVIDER_KEY, QgsSensorThingsProvider::SENSORTHINGS_PROVIDER_DESCRIPTION )
304{
305}
306
307QIcon QgsSensorThingsProviderMetadata::icon() const
308{
309 // TODO
310 return QgsApplication::getThemeIcon( QStringLiteral( "mIconAfs.svg" ) );
311}
312
313QList<QgsDataItemProvider *> QgsSensorThingsProviderMetadata::dataItemProviders() const
314{
315 return { new QgsSensorThingsDataItemProvider() };
316}
317
318QVariantMap QgsSensorThingsProviderMetadata::decodeUri( const QString &uri ) const
319{
320 const QgsDataSourceUri dsUri = QgsDataSourceUri( uri );
321
322 QVariantMap components;
323 components.insert( QStringLiteral( "url" ), dsUri.param( QStringLiteral( "url" ) ) );
324
325 if ( !dsUri.authConfigId().isEmpty() )
326 {
327 components.insert( QStringLiteral( "authcfg" ), dsUri.authConfigId() );
328 }
329 if ( !dsUri.username().isEmpty() )
330 {
331 components.insert( QStringLiteral( "username" ), dsUri.username() );
332 }
333 if ( !dsUri.password().isEmpty() )
334 {
335 components.insert( QStringLiteral( "password" ), dsUri.password() );
336 }
337
338 // there's two different ways the referer can be set, so we need to check both. Which way it has been
339 // set depends on the widget used to create the URI. It's messy, but QgsHttpHeaders has a bunch of logic in
340 // it to handle upgrading old referer handling for connections created before QgsHttpHeaders was invented,
341 // and if we rely on that entirely then we get multiple "referer" parameters included in the URI, which is
342 // both ugly and unnecessary for a provider created post QgsHttpHeaders.
343 if ( !dsUri.param( QStringLiteral( "referer" ) ).isEmpty() )
344 {
345 components.insert( QStringLiteral( "referer" ), dsUri.param( QStringLiteral( "referer" ) ) );
346 }
347 if ( !dsUri.param( QStringLiteral( "http-header:referer" ) ).isEmpty() )
348 {
349 components.insert( QStringLiteral( "referer" ), dsUri.param( QStringLiteral( "http-header:referer" ) ) );
350 }
351
352 const QString entityParam = dsUri.param( QStringLiteral( "entity" ) );
354 if ( entity == Qgis::SensorThingsEntity::Invalid )
355 entity = QgsSensorThingsUtils::stringToEntity( entityParam );
356
357 if ( entity != Qgis::SensorThingsEntity::Invalid )
358 components.insert( QStringLiteral( "entity" ), qgsEnumValueToKey( entity ) );
359
360 bool ok = false;
361 const int maxPageSizeParam = dsUri.param( QStringLiteral( "pageSize" ) ).toInt( &ok );
362 if ( ok )
363 {
364 components.insert( QStringLiteral( "pageSize" ), maxPageSizeParam );
365 }
366
367 ok = false;
368 const int featureLimitParam = dsUri.param( QStringLiteral( "featureLimit" ) ).toInt( &ok );
369 if ( ok )
370 {
371 components.insert( QStringLiteral( "featureLimit" ), featureLimitParam );
372 }
373
374 switch ( QgsWkbTypes::geometryType( dsUri.wkbType() ) )
375 {
377 if ( QgsWkbTypes::isMultiType( dsUri.wkbType() ) )
378 components.insert( QStringLiteral( "geometryType" ), QStringLiteral( "multipoint" ) );
379 else
380 components.insert( QStringLiteral( "geometryType" ), QStringLiteral( "point" ) );
381 break;
383 components.insert( QStringLiteral( "geometryType" ), QStringLiteral( "line" ) );
384 break;
386 components.insert( QStringLiteral( "geometryType" ), QStringLiteral( "polygon" ) );
387 break;
388
391 break;
392 }
393
394 const QStringList bbox = dsUri.param( QStringLiteral( "bbox" ) ).split( ',' );
395 if ( bbox.size() == 4 )
396 {
397 QgsRectangle r;
398 bool xminOk = false;
399 bool yminOk = false;
400 bool xmaxOk = false;
401 bool ymaxOk = false;
402 r.setXMinimum( bbox[0].toDouble( &xminOk ) );
403 r.setYMinimum( bbox[1].toDouble( &yminOk ) );
404 r.setXMaximum( bbox[2].toDouble( &xmaxOk ) );
405 r.setYMaximum( bbox[3].toDouble( &ymaxOk ) );
406 if ( xminOk && yminOk && xmaxOk && ymaxOk )
407 components.insert( QStringLiteral( "bounds" ), r );
408 }
409
410 if ( !dsUri.sql().isEmpty() )
411 components.insert( QStringLiteral( "sql" ), dsUri.sql() );
412
413 return components;
414}
415
416QString QgsSensorThingsProviderMetadata::encodeUri( const QVariantMap &parts ) const
417{
418 QgsDataSourceUri dsUri;
419 dsUri.setParam( QStringLiteral( "url" ), parts.value( QStringLiteral( "url" ) ).toString() );
420
421 if ( !parts.value( QStringLiteral( "authcfg" ) ).toString().isEmpty() )
422 {
423 dsUri.setAuthConfigId( parts.value( QStringLiteral( "authcfg" ) ).toString() );
424 }
425 if ( !parts.value( QStringLiteral( "username" ) ).toString().isEmpty() )
426 {
427 dsUri.setUsername( parts.value( QStringLiteral( "username" ) ).toString() );
428 }
429 if ( !parts.value( QStringLiteral( "password" ) ).toString().isEmpty() )
430 {
431 dsUri.setPassword( parts.value( QStringLiteral( "password" ) ).toString() );
432 }
433 if ( !parts.value( QStringLiteral( "referer" ) ).toString().isEmpty() )
434 {
435 dsUri.setParam( QStringLiteral( "referer" ), parts.value( QStringLiteral( "referer" ) ).toString() );
436 }
437
439 parts.value( QStringLiteral( "entity" ) ).toString() );
440 if ( entity == Qgis::SensorThingsEntity::Invalid )
441 entity = QgsSensorThingsUtils::stringToEntity( parts.value( QStringLiteral( "entity" ) ).toString() );
442
443 if ( entity != Qgis::SensorThingsEntity::Invalid )
444 {
445 dsUri.setParam( QStringLiteral( "entity" ),
446 qgsEnumValueToKey( entity ) );
447 }
448
449 bool ok = false;
450 const int maxPageSizeParam = parts.value( QStringLiteral( "pageSize" ) ).toInt( &ok );
451 if ( ok )
452 {
453 dsUri.setParam( QStringLiteral( "pageSize" ), QString::number( maxPageSizeParam ) );
454 }
455
456 ok = false;
457 const int featureLimitParam = parts.value( QStringLiteral( "featureLimit" ) ).toInt( &ok );
458 if ( ok )
459 {
460 dsUri.setParam( QStringLiteral( "featureLimit" ), QString::number( featureLimitParam ) );
461 }
462
463 const QString geometryType = parts.value( QStringLiteral( "geometryType" ) ).toString();
464 if ( geometryType.compare( QLatin1String( "point" ), Qt::CaseInsensitive ) == 0 )
465 {
467 }
468 else if ( geometryType.compare( QLatin1String( "multipoint" ), Qt::CaseInsensitive ) == 0 )
469 {
471 }
472 else if ( geometryType.compare( QLatin1String( "line" ), Qt::CaseInsensitive ) == 0 )
473 {
475 }
476 else if ( geometryType.compare( QLatin1String( "polygon" ), Qt::CaseInsensitive ) == 0 )
477 {
479 }
480
481 if ( parts.contains( QStringLiteral( "bounds" ) ) && parts.value( QStringLiteral( "bounds" ) ).userType() == QMetaType::type( "QgsRectangle" ) )
482 {
483 const QgsRectangle bBox = parts.value( QStringLiteral( "bounds" ) ).value< QgsRectangle >();
484 dsUri.setParam( QStringLiteral( "bbox" ), QStringLiteral( "%1,%2,%3,%4" ).arg( bBox.xMinimum() ).arg( bBox.yMinimum() ).arg( bBox.xMaximum() ).arg( bBox.yMaximum() ) );
485 }
486
487 if ( !parts.value( QStringLiteral( "sql" ) ).toString().isEmpty() )
488 dsUri.setSql( parts.value( QStringLiteral( "sql" ) ).toString() );
489
490 return dsUri.uri( false );
491}
492
493QgsSensorThingsProvider *QgsSensorThingsProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
494{
495 return new QgsSensorThingsProvider( uri, options, flags );
496}
497
498QList<Qgis::LayerType> QgsSensorThingsProviderMetadata::supportedLayerTypes() const
499{
500 return { Qgis::LayerType::Vector };
501}
502
503QMap<QString, QgsAbstractProviderConnection *> QgsSensorThingsProviderMetadata::connections( bool cached )
504{
505 return connectionsProtected<QgsSensorThingsProviderConnection, QgsSensorThingsProviderConnection>( cached );
506}
507
508QgsAbstractProviderConnection *QgsSensorThingsProviderMetadata::createConnection( const QString &name )
509{
510 return new QgsSensorThingsProviderConnection( name );
511}
512
513void QgsSensorThingsProviderMetadata::deleteConnection( const QString &name )
514{
515 deleteConnectionProtected<QgsSensorThingsProviderConnection>( name );
516}
517
518void QgsSensorThingsProviderMetadata::saveConnection( const QgsAbstractProviderConnection *connection, const QString &name )
519{
520 saveConnectionProtected( connection, name );
521}
522
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
Definition: qgis.h:1853
@ FastExtent2D
Provider's 2D extent retrieval via QgsDataProvider::extent() is always guaranteed to be trivial/fast ...
SensorThingsEntity
OGC SensorThings API entity types.
Definition: qgis.h:4865
@ Invalid
An invalid/unknown entity.
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
@ Vector
Vector layer.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:182
@ MultiPointZ
MultiPointZ.
@ PointZ
PointZ.
@ MultiLineStringZ
MultiLineStringZ.
@ MultiPolygonZ
MultiPolygonZ.
Base class that can be used for any class that is capable of returning features.
The QgsAbstractProviderConnection provides an interface for data provider connections.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
@ NetworkError
A network error occurred.
@ ServerExceptionError
An exception was raised by the server.
@ NoError
No error was encountered.
@ TimeoutError
Timeout was reached before a reply was received.
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).
@ SkipFeatureCount
Make featureCount() return -1 to indicate unknown, and subLayers() to return a unknown feature count ...
QFlags< ReadFlag > ReadFlags
virtual void setDataSourceUri(const QString &uri)
Set the data source specification.
Class for storing the component parts of a RDBMS data source URI (e.g.
void setSql(const QString &sql)
Sets the sql filter for the URI.
void setAuthConfigId(const QString &authcfg)
Sets the authentication configuration ID for the URI.
QString uri(bool expandAuthConfig=true) const
Returns the complete URI as a string.
void setUsername(const QString &username)
Sets the username for the URI.
QString param(const QString &key) const
Returns a generic parameter value corresponding to the specified key.
QString username() const
Returns the username stored in the URI.
Qgis::WkbType wkbType() const
Returns the WKB type associated with the URI.
void setWkbType(Qgis::WkbType type)
Sets the WKB type associated with the URI.
void setParam(const QString &key, const QString &value)
Sets a generic parameter value on the URI.
QString password() const
Returns the password stored in the URI.
QString authConfigId() const
Returns any associated authentication configuration ID stored in the URI.
QString sql() const
Returns the SQL filter stored in the URI, if set.
void setPassword(const QString &password)
Sets the password for the URI.
QgsErrorMessage represents single error message.
Definition: qgserror.h:33
Wrapper for iterator of features from vector data provider or vector layer.
This class wraps a request for features to a vector layer (or directly its vector data provider).
Container of fields for a vector layer.
Definition: qgsfields.h:45
A structured metadata store for a map layer.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
Holds data provider key, description, and associated shared library file or function pointer informat...
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
@ Read
Lock for read.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:159
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:149
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:164
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:154
Represents connections to SensorThings data sources.
static Qgis::SensorThingsEntity stringToEntity(const QString &type)
Converts a string value to a Qgis::SensorThingsEntity type.
static Qgis::SensorThingsEntity entitySetStringToEntity(const QString &type)
Converts a string value corresponding to a SensorThings entity set to a Qgis::SensorThingsEntity type...
This is the base class for vector data providers.
@ ReadLayerMetadata
Provider can read layer metadata from data store. Since QGIS 3.0. See QgsDataProvider::layerMetadata(...
@ SelectAtId
Fast access to features using their ID.
QFlags< Capability > Capabilities
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:862
static bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:758
@ UnknownCount
Provider returned an unknown feature count.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition: qgis.h:5398
#define QgsSetRequestInitiatorClass(request, _class)
QgsSQLStatement::Node * parse(const QString &str, QString &parserErrorMsg, bool allowFragments)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
const QgsCoordinateReferenceSystem & crs
Setting options for creating vector data providers.