18#include <nlohmann/json.hpp>
29#include <QNetworkRequest>
30#include <QRegularExpression>
31#include <QRegularExpressionMatch>
35using namespace Qt::StringLiterals;
88 mChildEntity = entity;
128 if ( !mOrderBy.isEmpty() )
129 parts.append( u
"orderby=%1,%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? u
"asc"_s : u
"desc"_s ) );
131 parts.append( u
"limit=%1"_s.arg( mLimit ) );
132 if ( !mFilter.trimmed().isEmpty() )
134 QString escapedFilter = mFilter;
135 escapedFilter.replace(
':',
"\\colon"_L1 );
136 parts.append( u
"filter=%1"_s.arg( escapedFilter ) );
138 return parts.join(
':' );
143 const QStringList parts =
string.split(
':', Qt::SkipEmptyParts );
149 for (
int i = 1; i < parts.count(); ++i )
151 const QString &part = parts.at( i );
152 const thread_local QRegularExpression orderByRegEx( u
"^orderby=(.*),(.*?)$"_s );
153 const thread_local QRegularExpression orderLimitRegEx( u
"^limit=(\\d+)$"_s );
154 const thread_local QRegularExpression filterRegEx( u
"^filter=(.*)$"_s );
156 const QRegularExpressionMatch orderByMatch = orderByRegEx.match( part );
157 if ( orderByMatch.hasMatch() )
159 definition.
setOrderBy( orderByMatch.captured( 1 ) );
160 definition.
setSortOrder( orderByMatch.captured( 2 ) ==
"asc"_L1 ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder );
164 const QRegularExpressionMatch limitMatch = orderLimitRegEx.match( part );
165 if ( limitMatch.hasMatch() )
167 definition.
setLimit( limitMatch.captured( 1 ).toInt() );
171 const QRegularExpressionMatch filterMatch = filterRegEx.match( part );
172 if ( filterMatch.hasMatch() )
174 QString
filter = filterMatch.captured( 1 );
175 filter.replace(
"\\colon"_L1,
":"_L1 );
192 QString childEntityString;
199 switch ( cardinality )
215 QString res = u
"$expand=%1"_s.arg( childEntityString );
217 QStringList queryOptions;
218 if ( !mOrderBy.isEmpty() )
219 queryOptions.append( u
"$orderby=%1%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QString() : u
" desc"_s ) );
222 queryOptions.append( u
"$top=%1"_s.arg( mLimit ) );
224 if ( !mFilter.isEmpty() )
225 queryOptions.append( u
"$filter=%1"_s.arg( mFilter ) );
227 queryOptions.append( additionalOptions );
229 if ( !queryOptions.isEmpty() )
230 res.append( u
"(%1)"_s.arg( queryOptions.join(
';' ) ) );
240 return mChildEntity == other.mChildEntity && mSortOrder == other.mSortOrder && mLimit == other.mLimit && mOrderBy == other.mOrderBy && mFilter == other.mFilter;
245 return !( *
this == other );
264 const QString trimmed = type.trimmed();
265 if ( trimmed.compare(
"Thing"_L1, Qt::CaseInsensitive ) == 0 )
267 if ( trimmed.compare(
"Location"_L1, Qt::CaseInsensitive ) == 0 )
269 if ( trimmed.compare(
"HistoricalLocation"_L1, Qt::CaseInsensitive ) == 0 )
271 if ( trimmed.compare(
"Datastream"_L1, Qt::CaseInsensitive ) == 0 )
273 if ( trimmed.compare(
"Sensor"_L1, Qt::CaseInsensitive ) == 0 )
275 if ( trimmed.compare(
"ObservedProperty"_L1, Qt::CaseInsensitive ) == 0 )
277 if ( trimmed.compare(
"Observation"_L1, Qt::CaseInsensitive ) == 0 )
279 if ( trimmed.compare(
"FeatureOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
281 if ( trimmed.compare(
"MultiDatastream"_L1, Qt::CaseInsensitive ) == 0 )
294 return plural ? QObject::tr(
"Things" ) : QObject::tr(
"Thing" );
296 return plural ? QObject::tr(
"Locations" ) : QObject::tr(
"Location" );
298 return plural ? QObject::tr(
"Historical Locations" ) : QObject::tr(
"Historical Location" );
300 return plural ? QObject::tr(
"Datastreams" ) : QObject::tr(
"Datastream" );
302 return plural ? QObject::tr(
"Sensors" ) : QObject::tr(
"Sensor" );
304 return plural ? QObject::tr(
"Observed Properties" ) : QObject::tr(
"Observed Property" );
306 return plural ? QObject::tr(
"Observations" ) : QObject::tr(
"Observation" );
308 return plural ? QObject::tr(
"Features of Interest" ) : QObject::tr(
"Feature of Interest" );
310 return plural ? QObject::tr(
"MultiDatastreams" ) : QObject::tr(
"MultiDatastream" );
317 const QString trimmed = type.trimmed();
318 if ( trimmed.compare(
"Things"_L1, Qt::CaseInsensitive ) == 0 )
320 if ( trimmed.compare(
"Locations"_L1, Qt::CaseInsensitive ) == 0 )
322 if ( trimmed.compare(
"HistoricalLocations"_L1, Qt::CaseInsensitive ) == 0 )
324 if ( trimmed.compare(
"Datastreams"_L1, Qt::CaseInsensitive ) == 0 )
326 if ( trimmed.compare(
"Sensors"_L1, Qt::CaseInsensitive ) == 0 )
328 if ( trimmed.compare(
"ObservedProperties"_L1, Qt::CaseInsensitive ) == 0 )
330 if ( trimmed.compare(
"Observations"_L1, Qt::CaseInsensitive ) == 0 )
332 if ( trimmed.compare(
"FeaturesOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
334 if ( trimmed.compare(
"MultiDatastreams"_L1, Qt::CaseInsensitive ) == 0 )
349 return u
"Locations"_s;
351 return u
"HistoricalLocations"_s;
353 return u
"Datastreams"_s;
357 return u
"ObservedProperties"_s;
359 return u
"Observations"_s;
361 return u
"FeaturesOfInterest"_s;
363 return u
"MultiDatastreams"_s;
410 u
"unitOfMeasurement"_s,
411 u
"observationType"_s,
469 u
"unitOfMeasurements"_s,
470 u
"observationType"_s,
471 u
"multiObservationDataTypes"_s,
487 fields.
append(
QgsField( u
"selfLink"_s, QMetaType::Type::QString ) );
497 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
498 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
504 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
505 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
510 fields.
append(
QgsField( u
"time"_s, QMetaType::Type::QDateTime ) );
516 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
517 fields.
append(
QgsField( u
"unitOfMeasurement"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
518 fields.
append(
QgsField( u
"observationType"_s, QMetaType::Type::QString ) );
519 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
520 if ( includeRangeFieldProxies )
522 fields.
append(
QgsField( u
"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
523 fields.
append(
QgsField( u
"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
524 fields.
append(
QgsField( u
"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
525 fields.
append(
QgsField( u
"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
532 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
533 fields.
append(
QgsField( u
"metadata"_s, QMetaType::Type::QString ) );
534 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
540 fields.
append(
QgsField( u
"definition"_s, QMetaType::Type::QString ) );
541 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
542 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
547 if ( includeRangeFieldProxies )
549 fields.
append(
QgsField( u
"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
550 fields.
append(
QgsField( u
"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
554 fields.
append(
QgsField( u
"result"_s, QMetaType::Type::QString ) );
556 fields.
append(
QgsField( u
"resultTime"_s, QMetaType::Type::QDateTime ) );
557 fields.
append(
QgsField( u
"resultQuality"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
558 if ( includeRangeFieldProxies )
560 fields.
append(
QgsField( u
"validTimeStart"_s, QMetaType::Type::QDateTime ) );
561 fields.
append(
QgsField( u
"validTimeEnd"_s, QMetaType::Type::QDateTime ) );
563 fields.
append(
QgsField( u
"parameters"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
569 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
570 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
576 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
577 fields.
append(
QgsField( u
"unitOfMeasurements"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
578 fields.
append(
QgsField( u
"observationType"_s, QMetaType::Type::QString ) );
579 fields.
append(
QgsField( u
"multiObservationDataTypes"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
580 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
581 if ( includeRangeFieldProxies )
583 fields.
append(
QgsField( u
"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
584 fields.
append(
QgsField( u
"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
585 fields.
append(
QgsField( u
"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
586 fields.
append(
QgsField( u
"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
596 if ( expandedTypes.empty() )
603 path = ( path.isEmpty() ? QString() : ( path +
'_' ) ) +
qgsEnumValueToKey( expandedType );
605 for (
const QgsField &expandedField : expandedFields )
607 QgsField renamedExpandedField = expandedField;
608 renamedExpandedField.
setName( u
"%1_%2"_s.arg( path, expandedField.name() ) );
609 fields.
append( renamedExpandedField );
628 return u
"location"_s;
635 return u
"observedArea"_s;
684 QString geometryTypeString;
688 geometryTypeString = u
"Point"_s;
691 geometryTypeString = u
"Polygon"_s;
694 geometryTypeString = u
"LineString"_s;
703 if ( filterTarget.isEmpty() )
706 return u
"%1/type eq '%2' or %1/geometry/type eq '%2'"_s.arg( filterTarget, geometryTypeString );
712 return ( extent.
isNull() || geometryField.isEmpty() ) ? QString() : u
"geo.intersects(%1, geography'%2')"_s.arg( geometryField, extent.
asWktPolygon() );
717 QStringList nonEmptyFilters;
718 for (
const QString &filter : filters )
720 if ( !filter.isEmpty() )
721 nonEmptyFilters.append( filter );
723 if ( nonEmptyFilters.empty() )
725 if ( nonEmptyFilters.size() == 1 )
726 return nonEmptyFilters.at( 0 );
728 return u
"("_s + nonEmptyFilters.join(
") and ("_L1 ) + u
")"_s;
733 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
739 switch ( networkRequest.
get( request ) )
751 QString entityBaseUri;
755 auto rootContent = nlohmann::json::parse( content.
content().toStdString() );
756 if ( !rootContent.contains(
"value" ) )
762 bool foundMatchingEntity =
false;
763 for (
const auto &valueJson : rootContent[
"value"] )
765 if ( valueJson.contains(
"name" ) && valueJson.contains(
"url" ) )
767 const QString name = QString::fromStdString( valueJson[
"name"].get<std::string>() );
769 if ( entityType == type )
771 const QString url = QString::fromStdString( valueJson[
"url"].get<std::string>() );
772 if ( !url.isEmpty() )
774 foundMatchingEntity =
true;
782 if ( !foundMatchingEntity )
788 catch (
const nlohmann::json::parse_error &ex )
790 QgsDebugError( u
"Error parsing response: %1"_s.arg( ex.what() ) );
794 auto getCountForType = [entityBaseUri, type, authCfg, feedback](
Qgis::GeometryType geometryType ) ->
long long {
796 QString countUri = u
"%1?$top=0&$count=true"_s.arg( entityBaseUri );
799 if ( !typeFilter.isEmpty() )
800 countUri += u
"&$filter="_s + typeFilter;
802 const QUrl url( countUri );
804 QNetworkRequest request( url );
825 auto rootContent = nlohmann::json::parse( content.
content().toStdString() );
826 if ( !rootContent.contains(
"@iot.count" ) )
832 return rootContent[
"@iot.count"].get<
long long>();
834 catch (
const nlohmann::json::parse_error &ex )
836 QgsDebugError( u
"Error parsing response: %1"_s.arg( ex.what() ) );
842 QList<Qgis::GeometryType> types;
845 const long long matchCount = getCountForType( geometryType );
846 if ( matchCount < 0 )
848 else if ( matchCount > 0 )
849 types.append( geometryType );
916 switch ( relatedType )
938 switch ( relatedType )
958 switch ( relatedType )
987 switch ( relatedType )
1011 switch ( relatedType )
1033 switch ( relatedType )
1056 switch ( relatedType )
1077 switch ( relatedType )
1099 switch ( relatedType )
1130 for (
int i = expansions.size() - 1; i >= 0; i-- )
1138 res = expansion.
asQueryString( parentType, res.isEmpty() ? QStringList() : QStringList { res } );
SensorThingsEntity
OGC SensorThings API entity types.
@ Sensor
A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estima...
@ MultiDatastream
A MultiDatastream groups a collection of Observations and the Observations in a MultiDatastream have ...
@ 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...
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
RelationshipCardinality
Relationship cardinality.
@ ManyToMany
Many to many relationship.
@ ManyToOne
Many to one relationship.
@ OneToOne
One to one relationship.
@ OneToMany
One to many relationship.
WkbType
The WKB type describes the number of dimensions a geometry has.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
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.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ 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...
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.
Encapsulate a field in an attribute table or data source.
void setName(const QString &name)
Set the field name.
Container of fields for a vector layer.
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
A rectangle specified with double values.
Q_INVOKABLE QString asWktPolygon() const
Returns a string representation of the rectangle as a WKT Polygon.
Encapsulates information about how relationships in a SensorThings API service should be expanded.
Qgis::SensorThingsEntity childEntity() const
Returns the target child entity which should be expanded.
void setLimit(int limit)
Sets the limit on the number of child features to fetch.
void setChildEntity(Qgis::SensorThingsEntity entity)
Sets the target child entity which should be expanded.
void setSortOrder(Qt::SortOrder order)
Sets the sort order for the expanded child entities.
static QgsSensorThingsExpansionDefinition defaultDefinitionForEntity(Qgis::SensorThingsEntity entity)
Returns an expansion definition for the specified entity type, populated with reasonable defaults whi...
static QgsSensorThingsExpansionDefinition fromString(const QString &string)
Returns a QgsSensorThingsExpansionDefinition from a string representation.
bool operator!=(const QgsSensorThingsExpansionDefinition &other) const
void setFilter(const QString &filter)
Returns the string filter to filter expanded child entities by.
QString asQueryString(Qgis::SensorThingsEntity parentEntityType, const QStringList &additionalOptions=QStringList()) const
Returns the expansion as a valid SensorThings API query string, eg "$expand=Observations($orderby=phe...
bool operator==(const QgsSensorThingsExpansionDefinition &other) const
int limit() const
Returns the limit on the number of child features to fetch.
void setOrderBy(const QString &field)
Sets the field name to order the expanded child entities by.
Qt::SortOrder sortOrder() const
Returns the sort order for the expanded child entities.
QString toString() const
Returns a string encapsulation of the expansion definition.
QString orderBy() const
Returns the field name to order the expanded child entities by.
QString filter() const
Returns the string filter to filter expanded child entities by.
QgsSensorThingsExpansionDefinition(Qgis::SensorThingsEntity childEntity=Qgis::SensorThingsEntity::Invalid, const QString &orderBy=QString(), Qt::SortOrder sortOrder=Qt::SortOrder::AscendingOrder, int limit=QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT, const QString &filter=QString())
Constructor for QgsSensorThingsExpansionDefinition, targeting the specified child entity type.
bool isValid() const
Returns true if the definition is valid.
static QStringList propertiesForEntityType(Qgis::SensorThingsEntity type)
Returns the SensorThings properties which correspond to a specified entity type.
static QList< Qgis::GeometryType > availableGeometryTypes(const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback=nullptr, const QString &authCfg=QString())
Returns a list of available geometry types for the server at the specified uri and entity type.
static Qgis::SensorThingsEntity stringToEntity(const QString &type)
Converts a string value to a Qgis::SensorThingsEntity type.
static QString entityToSetString(Qgis::SensorThingsEntity type)
Converts a SensorThings entity set to a SensorThings entity set string.
static QString asQueryString(Qgis::SensorThingsEntity baseType, const QList< QgsSensorThingsExpansionDefinition > &expansions)
Returns a list of expansions as a valid SensorThings API query string, eg "$expand=Locations($orderby...
static Qgis::SensorThingsEntity entitySetStringToEntity(const QString &type)
Converts a string value corresponding to a SensorThings entity set to a Qgis::SensorThingsEntity type...
static QString combineFilters(const QStringList &filters)
Combines a set of SensorThings API filter operators.
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 Qgis::RelationshipCardinality relationshipCardinality(Qgis::SensorThingsEntity baseType, Qgis::SensorThingsEntity relatedType, bool &valid)
Returns the cardinality of the relationship between a base entity type and a related entity type.
static Qgis::GeometryType geometryTypeForEntity(Qgis::SensorThingsEntity type)
Returns the geometry type for if the specified entity type.
static QgsFields fieldsForEntityType(Qgis::SensorThingsEntity type, bool includeRangeFieldProxies=true)
Returns the fields which correspond to a specified entity type.
static QString displayString(Qgis::SensorThingsEntity type, bool plural=false)
Converts a Qgis::SensorThingsEntity type to a user-friendly translated string.
static bool entityTypeHasGeometry(Qgis::SensorThingsEntity type)
Returns true if the specified entity type can have geometry attached.
static QgsFields fieldsForExpandedEntityType(Qgis::SensorThingsEntity baseType, const QList< Qgis::SensorThingsEntity > &expandedTypes)
Returns the fields which correspond to a specified entity baseType, expanded using the specified list...
static QString geometryFieldForEntityType(Qgis::SensorThingsEntity type)
Returns the geometry field for a specified entity type.
static QList< Qgis::SensorThingsEntity > expandableTargets(Qgis::SensorThingsEntity type)
Returns a list of permissible expand targets for a given base entity type.
static QString filterForExtent(const QString &geometryField, const QgsRectangle &extent)
Returns a filter string which restricts results to those within the specified extent.
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...
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.
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
#define BUILTIN_UNREACHABLE
#define QgsDebugError(str)
#define QgsSetRequestInitiatorClass(request, _class)