25#include <QCryptographicHash>
27#include <nlohmann/json.hpp>
31QgsSensorThingsSharedData::QgsSensorThingsSharedData(
const QString &uri )
33 const QVariantMap uriParts = QgsSensorThingsProviderMetadata().decodeUri( uri );
39 mMaximumPageSize = uriParts.value( QStringLiteral(
"pageSize" ), mMaximumPageSize ).toInt();
43 const QString geometryType = uriParts.value( QStringLiteral(
"geometryType" ) ).toString();
44 if ( geometryType.compare( QLatin1String(
"point" ), Qt::CaseInsensitive ) == 0 )
48 else if ( geometryType.compare( QLatin1String(
"multipoint" ), Qt::CaseInsensitive ) == 0 )
52 else if ( geometryType.compare( QLatin1String(
"line" ), Qt::CaseInsensitive ) == 0 )
56 else if ( geometryType.compare( QLatin1String(
"polygon" ), Qt::CaseInsensitive ) == 0 )
73 mRootUri = uriParts.value( QStringLiteral(
"url" ) ).toString();
76QUrl QgsSensorThingsSharedData::parseUrl(
const QUrl &url,
bool *isTestEndpoint )
79 *isTestEndpoint =
false;
81 QUrl modifiedUrl( url );
82 if ( modifiedUrl.toString().contains( QLatin1String(
"fake_qgis_http_endpoint" ) ) )
85 *isTestEndpoint =
true;
88 QString modifiedUrlString = modifiedUrl.toString();
90 modifiedUrlString = QUrl::fromPercentEncoding( modifiedUrlString.toUtf8() );
91 modifiedUrlString.replace( QLatin1String(
"fake_qgis_http_endpoint/" ), QLatin1String(
"fake_qgis_http_endpoint_" ) );
93 modifiedUrlString = modifiedUrlString.mid( QStringLiteral(
"http://" ).size() );
94 QString args = modifiedUrlString.indexOf(
'?' ) >= 0 ? modifiedUrlString.mid( modifiedUrlString.indexOf(
'?' ) ) : QString();
95 if ( modifiedUrlString.size() > 150 )
97 args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
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(
"_" ) );
116 if ( modifiedUrlString[1] ==
'/' )
118 modifiedUrlString = modifiedUrlString[0] +
":/" + modifiedUrlString.mid( 2 );
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 ) )
126 QgsDebugError( QStringLiteral(
"Local test file %1 for URL %2 does not exist!!!" ).arg( modifiedUrlString, url.toString() ) );
133long long QgsSensorThingsSharedData::featureCount(
QgsFeedback *feedback )
const
136 if ( mFeatureCount >= 0 )
137 return mFeatureCount;
143 QString countUri = QStringLiteral(
"%1?$top=0&$count=true" ).arg( mEntityBaseUri );
145 if ( !typeFilter.isEmpty() )
146 countUri += QStringLiteral(
"&$filter=" ) + typeFilter;
148 const QUrl url = parseUrl( QUrl( countUri ) );
150 QNetworkRequest request( url );
152 mHeaders.updateNetworkRequest( request );
159 return mFeatureCount;
172 auto rootContent = json::parse( content.
content().toStdString() );
173 if ( !rootContent.contains(
"@iot.count" ) )
175 mError = QObject::tr(
"No '@iot.count' value in response" );
176 return mFeatureCount;
179 mFeatureCount = rootContent[
"@iot.count"].get<
long long>();
181 catch (
const json::parse_error &ex )
183 mError = QObject::tr(
"Error parsing response: %1" ).arg( ex.what() );
187 return mFeatureCount;
190bool QgsSensorThingsSharedData::hasCachedAllFeatures()
const
193 return mHasCachedAllFeatures || ( mFeatureCount > 0 && mCachedFeatures.size() == mFeatureCount );
201 QMap<QgsFeatureId, QgsFeature>::const_iterator it = mCachedFeatures.constFind(
id );
202 if ( it != mCachedFeatures.constEnd() )
208 if ( hasCachedAllFeatures() )
211 bool featureFetched =
false;
213 if ( mNextPage.isEmpty() )
216 mNextPage = QStringLiteral(
"%1?$top=%2&$count=false" ).arg( mEntityBaseUri ).arg( mMaximumPageSize );
218 if ( !typeFilter.isEmpty() )
219 mNextPage += QStringLiteral(
"&$filter=" ) + typeFilter;
224 processFeatureRequest( mNextPage, feedback, [
id, &f, &featureFetched](
const QgsFeature & feature )
226 if ( feature.
id() ==
id )
229 featureFetched = true;
232 }, [&featureFetched,
this]
234 return !featureFetched && !hasCachedAllFeatures();
238 mHasCachedAllFeatures =
true;
241 return featureFetched;
249 if ( hasCachedAllFeatures() || mCachedExtent.contains( extentGeom ) )
252 return qgis::listToSet( mSpatialIndex.intersects( extent ) );
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 ) );
259 if ( thisPage.isEmpty() && mCachedExtent.intersects( extentGeom ) )
265 return qgis::listToSet( mSpatialIndex.intersects( extent ) );
272 bool noMoreFeatures =
false;
273 bool hasFirstPage =
false;
274 const bool res = processFeatureRequest( queryUrl, feedback, [&ids, &alreadyFetchedIds](
const QgsFeature & feature )
276 if ( !alreadyFetchedIds.contains( feature.
id() ) )
277 ids.insert( feature.
id() );
289 noMoreFeatures =
true;
291 if ( noMoreFeatures && res && ( !feedback || !feedback->
isCanceled() ) )
296 nextPage = noMoreFeatures || !res ? QString() : queryUrl;
301void QgsSensorThingsSharedData::clearCache()
306 mCachedFeatures.clear();
307 mIotIdToFeatureId.clear();
311bool QgsSensorThingsSharedData::processFeatureRequest( QString &nextPage,
QgsFeedback *feedback,
const std::function<
void(
const QgsFeature & ) > &fetchedFeatureCallback,
const std::function<
bool ()> &continueFetchingCallback,
const std::function<
void ()> &onNoMoreFeaturesCallback )
316 const QString authcfg = mAuthCfg;
320 while ( continueFetchingCallback() )
329 const QUrl url = parseUrl( nextPage );
331 QNetworkRequest request( url );
356 const auto rootContent = json::parse( content.
content().toStdString() );
357 if ( !rootContent.contains(
"value" ) )
360 mError = QObject::tr(
"No 'value' in response" );
367 const auto &values = rootContent[
"value"];
368 if ( values.empty() )
372 onNoMoreFeaturesCallback();
379 for (
const auto &featureData : values )
381 auto getString = [](
const basic_json<> &json,
const char *tag ) -> QVariant
383 if ( !json.contains( tag ) )
386 const auto &jObj = json[tag];
387 if ( jObj.is_number_integer() )
389 return QString::number( jObj.get<
int>() );
391 else if ( jObj.is_number_unsigned() )
393 return QString::number( jObj.get<
unsigned>() );
395 else if ( jObj.is_boolean() )
397 return QString::number( jObj.get<
bool>() );
399 else if ( jObj.is_number_float() )
401 return QString::number( jObj.get<
double>() );
404 return QString::fromStdString( json[tag].get<std::string >() );
407 auto getDateTime = [](
const basic_json<> &json,
const char *tag ) -> QVariant
409 if ( !json.contains( tag ) )
412 const auto &jObj = json[tag];
413 if ( jObj.is_string() )
415 const QString dateTimeString = QString::fromStdString( json[tag].get<std::string >() );
416 return QDateTime::fromString( dateTimeString, Qt::ISODateWithMs );
422 auto getVariantMap = [](
const basic_json<> &json,
const char *tag ) -> QVariant
424 if ( !json.contains( tag ) )
430 auto getStringList = [](
const basic_json<> &json,
const char *tag ) -> QVariant
432 if ( !json.contains( tag ) )
435 const auto &jObj = json[tag];
436 if ( jObj.is_string() )
438 return QStringList{ QString::fromStdString( json[tag].get<std::string >() ) };
440 else if ( jObj.is_array() )
443 for (
const auto &element : jObj )
445 if ( element.is_string() )
446 res.append( QString::fromStdString( element.get<std::string >() ) );
454 auto getDateTimeRange = [](
const basic_json<> &json,
const char *tag ) -> std::pair< QVariant, QVariant >
456 if ( !json.contains( tag ) )
457 return { QVariant(), QVariant() };
459 const auto &jObj = json[tag];
460 if ( jObj.is_string() )
462 const QString rangeString = QString::fromStdString( json[tag].get<std::string >() );
463 const QStringList rangeParts = rangeString.split(
'/' );
464 if ( rangeParts.size() == 2 )
468 QDateTime::fromString( rangeParts.at( 0 ), Qt::ISODateWithMs ),
469 QDateTime::fromString( rangeParts.at( 1 ), Qt::ISODateWithMs )
474 return { QVariant(), QVariant() };
478 const QString iotId = getString( featureData,
"@iot.id" ).toString();
479 auto existingFeatureIdIt = mIotIdToFeatureId.constFind( iotId );
480 if ( existingFeatureIdIt != mIotIdToFeatureId.constEnd() )
483 fetchedFeatureCallback( *mCachedFeatures.find( *existingFeatureIdIt ) );
488 feature.
setId( mNextFeatureId++ );
490 const QString selfLink = getString( featureData,
"@iot.selfLink" ).toString();
492 const QVariant properties = getVariantMap( featureData,
"properties" );
494 switch ( mEntityType )
504 << getString( featureData,
"name" )
505 << getString( featureData,
"description" )
515 << getString( featureData,
"name" )
516 << getString( featureData,
"description" )
526 << getDateTime( featureData,
"time" )
532 std::pair< QVariant, QVariant > phenomenonTime = getDateTimeRange( featureData,
"phenomenonTime" );
533 std::pair< QVariant, QVariant > resultTime = getDateTimeRange( featureData,
"resultTime" );
538 << getString( featureData,
"name" )
539 << getString( featureData,
"description" )
540 << getVariantMap( featureData,
"unitOfMeasurement" )
541 << getString( featureData,
"observationType" )
543 << phenomenonTime.first
544 << phenomenonTime.second
556 << getString( featureData,
"name" )
557 << getString( featureData,
"description" )
558 << getString( featureData,
"metadata" )
568 << getString( featureData,
"name" )
569 << getString( featureData,
"definition" )
570 << getString( featureData,
"description" )
577 std::pair< QVariant, QVariant > phenomenonTime = getDateTimeRange( featureData,
"phenomenonTime" );
578 std::pair< QVariant, QVariant > validTime = getDateTimeRange( featureData,
"validTime" );
583 << phenomenonTime.first
584 << phenomenonTime.second
585 << getString( featureData,
"result" )
586 << getDateTime( featureData,
"resultTime" )
587 << getStringList( featureData,
"resultQuality" )
590 << getVariantMap( featureData,
"parameters" )
600 << getString( featureData,
"name" )
601 << getString( featureData,
"description" )
614 mCachedFeatures.insert( feature.
id(), feature );
615 mIotIdToFeatureId.insert( iotId, feature.
id() );
616 mSpatialIndex.addFeature( feature );
618 fetchedFeatureCallback( feature );
622 if ( rootContent.contains(
"@iot.nextLink" ) )
624 nextPage = QString::fromStdString( rootContent[
"@iot.nextLink"].get<std::string>() );
628 onNoMoreFeaturesCallback();
632 if ( !continueFetchingCallback() )
639 catch (
const json::parse_error &ex )
642 mError = QObject::tr(
"Error parsing response: %1" ).arg( ex.what() );
643 QgsDebugMsgLevel( QStringLiteral(
"Error parsing response: %1" ).arg( ex.what() ), 2 );
@ 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.
@ MultiLineStringZ
MultiLineStringZ.
@ MultiPolygonZ
MultiPolygonZ.
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...
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.
bool isCanceled() const
Tells whether the operation has been canceled already.
Container of fields for a vector layer.
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 ¶meters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
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...
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.
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
#define QgsSetRequestInitiatorClass(request, _class)