QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgssensorthingsshareddata.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssensorthingsshareddata.h
3 ----------------
4 begin : November 2023
5 copyright : (C) 2013 Nyall Dawson
6 email : nyall dot dawson 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
19#include "qgslogger.h"
20#include "qgsreadwritelocker.h"
23#include "qgsjsonutils.h"
24
25#include <QCryptographicHash>
26#include <QFile>
27#include <nlohmann/json.hpp>
28
30
31QgsSensorThingsSharedData::QgsSensorThingsSharedData( const QString &uri )
32{
33 const QVariantMap uriParts = QgsSensorThingsProviderMetadata().decodeUri( uri );
34
35 mEntityType = qgsEnumKeyToValue( uriParts.value( QStringLiteral( "entity" ) ).toString(), Qgis::SensorThingsEntity::Invalid );
36 mFields = QgsSensorThingsUtils::fieldsForEntityType( mEntityType );
37 mGeometryField = QgsSensorThingsUtils::geometryFieldForEntityType( mEntityType );
38 // use initial value of maximum page size as default
39 mMaximumPageSize = uriParts.value( QStringLiteral( "pageSize" ), mMaximumPageSize ).toInt();
40
42 {
43 const QString geometryType = uriParts.value( QStringLiteral( "geometryType" ) ).toString();
44 if ( geometryType.compare( QLatin1String( "point" ), Qt::CaseInsensitive ) == 0 )
45 {
46 mGeometryType = Qgis::WkbType::PointZ;
47 }
48 else if ( geometryType.compare( QLatin1String( "multipoint" ), Qt::CaseInsensitive ) == 0 )
49 {
50 mGeometryType = Qgis::WkbType::MultiPointZ;
51 }
52 else if ( geometryType.compare( QLatin1String( "line" ), Qt::CaseInsensitive ) == 0 )
53 {
54 mGeometryType = Qgis::WkbType::MultiLineStringZ;
55 }
56 else if ( geometryType.compare( QLatin1String( "polygon" ), Qt::CaseInsensitive ) == 0 )
57 {
58 mGeometryType = Qgis::WkbType::MultiPolygonZ;
59 }
60 // geometry is always GeoJSON spec (for now, at least), so CRS will always be WGS84
61 mSourceCRS = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
62 }
63 else
64 {
65 mGeometryType = Qgis::WkbType::NoGeometry;
66 }
67
68 QgsDataSourceUri dsUri;
69 dsUri.setEncodedUri( uri );
70 mAuthCfg = dsUri.authConfigId();
71 mHeaders = dsUri.httpHeaders();
72
73 mRootUri = uriParts.value( QStringLiteral( "url" ) ).toString();
74}
75
76QUrl QgsSensorThingsSharedData::parseUrl( const QUrl &url, bool *isTestEndpoint )
77{
78 if ( isTestEndpoint )
79 *isTestEndpoint = false;
80
81 QUrl modifiedUrl( url );
82 if ( modifiedUrl.toString().contains( QLatin1String( "fake_qgis_http_endpoint" ) ) )
83 {
84 if ( isTestEndpoint )
85 *isTestEndpoint = true;
86
87 // Just for testing with local files instead of http:// resources
88 QString modifiedUrlString = modifiedUrl.toString();
89 // Qt5 does URL encoding from some reason (of the FILTER parameter for example)
90 modifiedUrlString = QUrl::fromPercentEncoding( modifiedUrlString.toUtf8() );
91 modifiedUrlString.replace( QLatin1String( "fake_qgis_http_endpoint/" ), QLatin1String( "fake_qgis_http_endpoint_" ) );
92 QgsDebugMsgLevel( QStringLiteral( "Get %1" ).arg( modifiedUrlString ), 2 );
93 modifiedUrlString = modifiedUrlString.mid( QStringLiteral( "http://" ).size() );
94 QString args = modifiedUrlString.indexOf( '?' ) >= 0 ? modifiedUrlString.mid( modifiedUrlString.indexOf( '?' ) ) : QString();
95 if ( modifiedUrlString.size() > 150 )
96 {
97 args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
98 }
99 else
100 {
101 args.replace( QLatin1String( "?" ), QLatin1String( "_" ) );
102 args.replace( QLatin1String( "&" ), QLatin1String( "_" ) );
103 args.replace( QLatin1String( "$" ), QLatin1String( "_" ) );
104 args.replace( QLatin1String( "<" ), QLatin1String( "_" ) );
105 args.replace( QLatin1String( ">" ), QLatin1String( "_" ) );
106 args.replace( QLatin1String( "'" ), QLatin1String( "_" ) );
107 args.replace( QLatin1String( "\"" ), QLatin1String( "_" ) );
108 args.replace( QLatin1String( " " ), QLatin1String( "_" ) );
109 args.replace( QLatin1String( ":" ), QLatin1String( "_" ) );
110 args.replace( QLatin1String( "/" ), QLatin1String( "_" ) );
111 args.replace( QLatin1String( "\n" ), QLatin1String( "_" ) );
112 }
113#ifdef Q_OS_WIN
114 // Passing "urls" like "http://c:/path" to QUrl 'eats' the : after c,
115 // so we must restore it
116 if ( modifiedUrlString[1] == '/' )
117 {
118 modifiedUrlString = modifiedUrlString[0] + ":/" + modifiedUrlString.mid( 2 );
119 }
120#endif
121 modifiedUrlString = modifiedUrlString.mid( 0, modifiedUrlString.indexOf( '?' ) ) + args;
122 QgsDebugMsgLevel( QStringLiteral( "Get %1 (after laundering)" ).arg( modifiedUrlString ), 2 );
123 modifiedUrl = QUrl::fromLocalFile( modifiedUrlString );
124 if ( !QFile::exists( modifiedUrlString ) )
125 {
126 QgsDebugError( QStringLiteral( "Local test file %1 for URL %2 does not exist!!!" ).arg( modifiedUrlString, url.toString() ) );
127 }
128 }
129
130 return modifiedUrl;
131}
132
133long long QgsSensorThingsSharedData::featureCount( QgsFeedback *feedback ) const
134{
135 QgsReadWriteLocker locker( mReadWriteLock, QgsReadWriteLocker::Read );
136 if ( mFeatureCount >= 0 )
137 return mFeatureCount;
138
139 locker.changeMode( QgsReadWriteLocker::Write );
140 mError.clear();
141
142 // return no features, just the total count
143 QString countUri = QStringLiteral( "%1?$top=0&$count=true" ).arg( mEntityBaseUri );
144 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( mEntityType, mGeometryType );
145 if ( !typeFilter.isEmpty() )
146 countUri += QStringLiteral( "&$filter=" ) + typeFilter;
147
148 const QUrl url = parseUrl( QUrl( countUri ) );
149
150 QNetworkRequest request( url );
151 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
152 mHeaders.updateNetworkRequest( request );
153
154 QgsBlockingNetworkRequest networkRequest;
155 networkRequest.setAuthCfg( mAuthCfg );
156 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
157
158 if ( feedback && feedback->isCanceled() )
159 return mFeatureCount;
160
161 // Handle network errors
163 {
164 QgsDebugError( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
165 mError = networkRequest.errorMessage();
166 }
167 else
168 {
169 const QgsNetworkReplyContent content = networkRequest.reply();
170 try
171 {
172 auto rootContent = json::parse( content.content().toStdString() );
173 if ( !rootContent.contains( "@iot.count" ) )
174 {
175 mError = QObject::tr( "No '@iot.count' value in response" );
176 return mFeatureCount;
177 }
178
179 mFeatureCount = rootContent["@iot.count"].get<long long>();
180 }
181 catch ( const json::parse_error &ex )
182 {
183 mError = QObject::tr( "Error parsing response: %1" ).arg( ex.what() );
184 }
185 }
186
187 return mFeatureCount;
188}
189
190bool QgsSensorThingsSharedData::hasCachedAllFeatures() const
191{
192 QgsReadWriteLocker locker( mReadWriteLock, QgsReadWriteLocker::Read );
193 return mHasCachedAllFeatures || ( mFeatureCount > 0 && mCachedFeatures.size() == mFeatureCount );
194}
195
196bool QgsSensorThingsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, QgsFeedback *feedback )
197{
198 QgsReadWriteLocker locker( mReadWriteLock, QgsReadWriteLocker::Read );
199
200 // If cached, return cached feature
201 QMap<QgsFeatureId, QgsFeature>::const_iterator it = mCachedFeatures.constFind( id );
202 if ( it != mCachedFeatures.constEnd() )
203 {
204 f = it.value();
205 return true;
206 }
207
208 if ( hasCachedAllFeatures() )
209 return false; // all features are cached, and we didn't find a match
210
211 bool featureFetched = false;
212
213 if ( mNextPage.isEmpty() )
214 {
215 locker.changeMode( QgsReadWriteLocker::Write );
216 mNextPage = QStringLiteral( "%1?$top=%2&$count=false" ).arg( mEntityBaseUri ).arg( mMaximumPageSize );
217 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( mEntityType, mGeometryType );
218 if ( !typeFilter.isEmpty() )
219 mNextPage += QStringLiteral( "&$filter=" ) + typeFilter;
220 }
221
222 locker.unlock();
223
224 processFeatureRequest( mNextPage, feedback, [id, &f, &featureFetched]( const QgsFeature & feature )
225 {
226 if ( feature.id() == id )
227 {
228 f = feature;
229 featureFetched = true;
230 // don't break here -- store all the features we retrieved in this page first!
231 }
232 }, [&featureFetched, this]
233 {
234 return !featureFetched && !hasCachedAllFeatures();
235 }, [this]
236 {
237 mNextPage.clear();
238 mHasCachedAllFeatures = true;
239 } );
240
241 return featureFetched;
242}
243
244QgsFeatureIds QgsSensorThingsSharedData::getFeatureIdsInExtent( const QgsRectangle &extent, QgsFeedback *feedback, const QString &thisPage, QString &nextPage, const QgsFeatureIds &alreadyFetchedIds )
245{
246 const QgsGeometry extentGeom = QgsGeometry::fromRect( extent );
247 QgsReadWriteLocker locker( mReadWriteLock, QgsReadWriteLocker::Read );
248
249 if ( hasCachedAllFeatures() || mCachedExtent.contains( extentGeom ) )
250 {
251 // all features cached locally, rely on local spatial index
252 return qgis::listToSet( mSpatialIndex.intersects( extent ) );
253 }
254
255 // TODO -- is using 'geography' always correct here?
256 const QString typeFilter = QgsSensorThingsUtils::filterForWkbType( mEntityType, mGeometryType );
257 QString queryUrl = !thisPage.isEmpty() ? thisPage : QStringLiteral( "%1?$top=%2&$count=false&$filter=geo.intersects(%3, geography'%4')%5" ).arg( mEntityBaseUri ).arg( mMaximumPageSize ).arg( mGeometryField, extent.asWktPolygon(), typeFilter.isEmpty() ? QString() : ( QStringLiteral( " and " ) + typeFilter ) );
258
259 if ( thisPage.isEmpty() && mCachedExtent.intersects( extentGeom ) )
260 {
261 // we have SOME of the results from this extent cached. Let's return those first.
262 // This is slightly nicer from a rendering point of view, because panning the map won't see features
263 // previously visible disappear temporarily while we wait for them to be included in the service's result set...
264 nextPage = queryUrl;
265 return qgis::listToSet( mSpatialIndex.intersects( extent ) );
266 }
267
268 locker.unlock();
269
270 QgsFeatureIds ids;
271
272 bool noMoreFeatures = false;
273 bool hasFirstPage = false;
274 const bool res = processFeatureRequest( queryUrl, feedback, [&ids, &alreadyFetchedIds]( const QgsFeature & feature )
275 {
276 if ( !alreadyFetchedIds.contains( feature.id() ) )
277 ids.insert( feature.id() );
278 }, [&hasFirstPage]
279 {
280 if ( !hasFirstPage )
281 {
282 hasFirstPage = true;
283 return true;
284 }
285
286 return false;
287 }, [&noMoreFeatures]
288 {
289 noMoreFeatures = true;
290 } );
291 if ( noMoreFeatures && res && ( !feedback || !feedback->isCanceled() ) )
292 {
293 locker.changeMode( QgsReadWriteLocker::Write );
294 mCachedExtent = QgsGeometry::unaryUnion( { mCachedExtent, extentGeom } );
295 }
296 nextPage = noMoreFeatures || !res ? QString() : queryUrl;
297
298 return ids;
299}
300
301void QgsSensorThingsSharedData::clearCache()
302{
303 QgsReadWriteLocker locker( mReadWriteLock, QgsReadWriteLocker::Write );
304
305 mFeatureCount = static_cast< long long >( Qgis::FeatureCountState::Uncounted );
306 mCachedFeatures.clear();
307 mIotIdToFeatureId.clear();
308 mSpatialIndex = QgsSpatialIndex();
309}
310
311bool QgsSensorThingsSharedData::processFeatureRequest( QString &nextPage, QgsFeedback *feedback, const std::function< void( const QgsFeature & ) > &fetchedFeatureCallback, const std::function<bool ()> &continueFetchingCallback, const std::function<void ()> &onNoMoreFeaturesCallback )
312{
313 // copy some members before we unlock the read/write locker
314
315 QgsReadWriteLocker locker( mReadWriteLock, QgsReadWriteLocker::Read );
316 const QString authcfg = mAuthCfg;
317 const QgsHttpHeaders headers = mHeaders;
318 const QgsFields fields = mFields;
319
320 while ( continueFetchingCallback() )
321 {
322 // don't lock while doing the fetch
323 locker.unlock();
324
325 // from: https://docs.ogc.org/is/18-088/18-088.html#nextLink
326 // "SensorThings clients SHALL treat the URL of the nextLink as opaque, and SHALL NOT append system query options to the URL of a next link"
327 //
328 // ie don't mess with this URL!!
329 const QUrl url = parseUrl( nextPage );
330
331 QNetworkRequest request( url );
332 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsSensorThingsSharedData" ) );
333 headers.updateNetworkRequest( request );
334
335 QgsBlockingNetworkRequest networkRequest;
336 networkRequest.setAuthCfg( authcfg );
337 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
338 if ( feedback && feedback->isCanceled() )
339 {
340 return false;
341 }
342
344 {
345 QgsDebugError( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
346 locker.changeMode( QgsReadWriteLocker::Write );
347 mError = networkRequest.errorMessage();
348 QgsDebugMsgLevel( QStringLiteral( "Query returned empty result" ), 2 );
349 return false;
350 }
351 else
352 {
353 const QgsNetworkReplyContent content = networkRequest.reply();
354 try
355 {
356 const auto rootContent = json::parse( content.content().toStdString() );
357 if ( !rootContent.contains( "value" ) )
358 {
359 locker.changeMode( QgsReadWriteLocker::Write );
360 mError = QObject::tr( "No 'value' in response" );
361 QgsDebugMsgLevel( QStringLiteral( "No 'value' in response" ), 2 );
362 return false;
363 }
364 else
365 {
366 // all good, got a batch of features
367 const auto &values = rootContent["value"];
368 if ( values.empty() )
369 {
370 locker.changeMode( QgsReadWriteLocker::Write );
371
372 onNoMoreFeaturesCallback();
373
374 return true;
375 }
376 else
377 {
378 locker.changeMode( QgsReadWriteLocker::Write );
379 for ( const auto &featureData : values )
380 {
381 auto getString = []( const basic_json<> &json, const char *tag ) -> QVariant
382 {
383 if ( !json.contains( tag ) )
384 return QVariant();
385
386 const auto &jObj = json[tag];
387 if ( jObj.is_number_integer() )
388 {
389 return QString::number( jObj.get<int>() );
390 }
391 else if ( jObj.is_number_unsigned() )
392 {
393 return QString::number( jObj.get<unsigned>() );
394 }
395 else if ( jObj.is_boolean() )
396 {
397 return QString::number( jObj.get<bool>() );
398 }
399 else if ( jObj.is_number_float() )
400 {
401 return QString::number( jObj.get<double>() );
402 }
403
404 return QString::fromStdString( json[tag].get<std::string >() );
405 };
406
407 auto getDateTime = []( const basic_json<> &json, const char *tag ) -> QVariant
408 {
409 if ( !json.contains( tag ) )
410 return QVariant();
411
412 const auto &jObj = json[tag];
413 if ( jObj.is_string() )
414 {
415 const QString dateTimeString = QString::fromStdString( json[tag].get<std::string >() );
416 return QDateTime::fromString( dateTimeString, Qt::ISODateWithMs );
417 }
418
419 return QVariant();
420 };
421
422 auto getVariantMap = []( const basic_json<> &json, const char *tag ) -> QVariant
423 {
424 if ( !json.contains( tag ) )
425 return QVariant();
426
427 return QgsJsonUtils::jsonToVariant( json[tag] );
428 };
429
430 auto getStringList = []( const basic_json<> &json, const char *tag ) -> QVariant
431 {
432 if ( !json.contains( tag ) )
433 return QVariant();
434
435 const auto &jObj = json[tag];
436 if ( jObj.is_string() )
437 {
438 return QStringList{ QString::fromStdString( json[tag].get<std::string >() ) };
439 }
440 else if ( jObj.is_array() )
441 {
442 QStringList res;
443 for ( const auto &element : jObj )
444 {
445 if ( element.is_string() )
446 res.append( QString::fromStdString( element.get<std::string >() ) );
447 }
448 return res;
449 }
450
451 return QVariant();
452 };
453
454 auto getDateTimeRange = []( const basic_json<> &json, const char *tag ) -> std::pair< QVariant, QVariant >
455 {
456 if ( !json.contains( tag ) )
457 return { QVariant(), QVariant() };
458
459 const auto &jObj = json[tag];
460 if ( jObj.is_string() )
461 {
462 const QString rangeString = QString::fromStdString( json[tag].get<std::string >() );
463 const QStringList rangeParts = rangeString.split( '/' );
464 if ( rangeParts.size() == 2 )
465 {
466 return
467 {
468 QDateTime::fromString( rangeParts.at( 0 ), Qt::ISODateWithMs ),
469 QDateTime::fromString( rangeParts.at( 1 ), Qt::ISODateWithMs )
470 };
471 }
472 }
473
474 return { QVariant(), QVariant() };
475 };
476
477 // Set attributes
478 const QString iotId = getString( featureData, "@iot.id" ).toString();
479 auto existingFeatureIdIt = mIotIdToFeatureId.constFind( iotId );
480 if ( existingFeatureIdIt != mIotIdToFeatureId.constEnd() )
481 {
482 // we've previously fetched and cached this feature, skip it
483 fetchedFeatureCallback( *mCachedFeatures.find( *existingFeatureIdIt ) );
484 continue;
485 }
486
487 QgsFeature feature( fields );
488 feature.setId( mNextFeatureId++ );
489
490 const QString selfLink = getString( featureData, "@iot.selfLink" ).toString();
491
492 const QVariant properties = getVariantMap( featureData, "properties" );
493 // NOLINTBEGIN(bugprone-branch-clone)
494 switch ( mEntityType )
495 {
497 break;
498
500 feature.setAttributes(
502 << iotId
503 << selfLink
504 << getString( featureData, "name" )
505 << getString( featureData, "description" )
506 << properties
507 );
508 break;
509
511 feature.setAttributes(
513 << iotId
514 << selfLink
515 << getString( featureData, "name" )
516 << getString( featureData, "description" )
517 << properties
518 );
519 break;
520
522 feature.setAttributes(
524 << iotId
525 << selfLink
526 << getDateTime( featureData, "time" )
527 );
528 break;
529
531 {
532 std::pair< QVariant, QVariant > phenomenonTime = getDateTimeRange( featureData, "phenomenonTime" );
533 std::pair< QVariant, QVariant > resultTime = getDateTimeRange( featureData, "resultTime" );
534 feature.setAttributes(
536 << iotId
537 << selfLink
538 << getString( featureData, "name" )
539 << getString( featureData, "description" )
540 << getVariantMap( featureData, "unitOfMeasurement" )
541 << getString( featureData, "observationType" )
542 << properties
543 << phenomenonTime.first
544 << phenomenonTime.second
545 << resultTime.first
546 << resultTime.second
547 );
548 break;
549 }
550
552 feature.setAttributes(
554 << iotId
555 << selfLink
556 << getString( featureData, "name" )
557 << getString( featureData, "description" )
558 << getString( featureData, "metadata" )
559 << properties
560 );
561 break;
562
564 feature.setAttributes(
566 << iotId
567 << selfLink
568 << getString( featureData, "name" )
569 << getString( featureData, "definition" )
570 << getString( featureData, "description" )
571 << properties
572 );
573 break;
574
576 {
577 std::pair< QVariant, QVariant > phenomenonTime = getDateTimeRange( featureData, "phenomenonTime" );
578 std::pair< QVariant, QVariant > validTime = getDateTimeRange( featureData, "validTime" );
579 feature.setAttributes(
581 << iotId
582 << selfLink
583 << phenomenonTime.first
584 << phenomenonTime.second
585 << getString( featureData, "result" ) // TODO -- result type handling!
586 << getDateTime( featureData, "resultTime" )
587 << getStringList( featureData, "resultQuality" )
588 << validTime.first
589 << validTime.second
590 << getVariantMap( featureData, "parameters" )
591 );
592 break;
593 }
594
596 feature.setAttributes(
598 << iotId
599 << selfLink
600 << getString( featureData, "name" )
601 << getString( featureData, "description" )
602 << properties
603 );
604 break;
605 }
606 // NOLINTEND(bugprone-branch-clone)
607
608 // Set geometry
609 if ( mGeometryType != Qgis::WkbType::NoGeometry && featureData.contains( mGeometryField.toLocal8Bit().constData() ) )
610 {
611 feature.setGeometry( QgsJsonUtils::geometryFromGeoJson( featureData[mGeometryField.toLocal8Bit().constData()] ) );
612 }
613
614 mCachedFeatures.insert( feature.id(), feature );
615 mIotIdToFeatureId.insert( iotId, feature.id() );
616 mSpatialIndex.addFeature( feature );
617
618 fetchedFeatureCallback( feature );
619 }
620 locker.unlock();
621
622 if ( rootContent.contains( "@iot.nextLink" ) )
623 {
624 nextPage = QString::fromStdString( rootContent["@iot.nextLink"].get<std::string>() );
625 }
626 else
627 {
628 onNoMoreFeaturesCallback();
629 }
630
631 // if target feature was added to cache, return it
632 if ( !continueFetchingCallback() )
633 {
634 return true;
635 }
636 }
637 }
638 }
639 catch ( const json::parse_error &ex )
640 {
641 locker.changeMode( QgsReadWriteLocker::Write );
642 mError = QObject::tr( "Error parsing response: %1" ).arg( ex.what() );
643 QgsDebugMsgLevel( QStringLiteral( "Error parsing response: %1" ).arg( ex.what() ), 2 );
644 return false;
645 }
646 }
647 }
648 return false;
649}
650
@ Sensor
A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estima...
@ ObservedProperty
An ObservedProperty specifies the phenomenon of an Observation.
@ Invalid
An invalid/unknown entity.
@ FeatureOfInterest
In the context of the Internet of Things, many Observations’ FeatureOfInterest can be the Location of...
@ Datastream
A Datastream groups a collection of Observations measuring the same ObservedProperty and produced by ...
@ Observation
An Observation is the act of measuring or otherwise determining the value of a property.
@ Location
A Location entity locates the Thing or the Things it associated with. A Thing’s Location entity is de...
@ Thing
A Thing is an object of the physical world (physical things) or the information world (virtual things...
@ HistoricalLocation
A Thing’s HistoricalLocation entity set provides the times of the current (i.e., last known) and prev...
@ MultiPointZ
MultiPointZ.
@ NoGeometry
No geometry.
@ PointZ
PointZ.
@ MultiLineStringZ
MultiLineStringZ.
@ MultiPolygonZ
MultiPolygonZ.
A vector of attributes.
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.
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the 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 storing the component parts of a RDBMS data source URI (e.g.
void setEncodedUri(const QByteArray &uri)
Sets the complete encoded uri.
QgsHttpHeaders httpHeaders() const
Returns http headers.
QString authConfigId() const
Returns any associated authentication configuration ID stored in the URI.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:56
QgsFeatureId id
Definition qgsfeature.h:64
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
void setId(QgsFeatureId id)
Sets the feature id for this feature.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
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
Container of fields for a vector layer.
Definition qgsfields.h:45
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
This class implements simple http header management.
bool updateNetworkRequest(QNetworkRequest &request) const
Updates a request by adding all the HTTP headers.
static QgsGeometry geometryFromGeoJson(const json &geometry)
Parses a GeoJSON "geometry" value to a QgsGeometry object.
static QVariant jsonToVariant(const json &value)
Converts a JSON value to a QVariant, in case of parsing error an invalid QVariant is returned.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
@ Write
Lock for write.
A rectangle specified with double values.
QString asWktPolygon() const
Returns a string representation of the rectangle as a WKT Polygon.
static QString filterForWkbType(Qgis::SensorThingsEntity entityType, Qgis::WkbType wkbType)
Returns a filter string which restricts results to those matching the specified entityType and wkbTyp...
static bool entityTypeHasGeometry(Qgis::SensorThingsEntity type)
Returns true if the specified entity type can have geometry attached.
static QString geometryFieldForEntityType(Qgis::SensorThingsEntity type)
Returns the geometry field for a specified entity type.
static QgsFields fieldsForEntityType(Qgis::SensorThingsEntity type)
Returns the fields which correspond to a specified entity type.
A spatial index for QgsFeature objects.
@ Uncounted
Feature count not yet computed.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:5354
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)