18#include <nlohmann/json.hpp>
29#include <QNetworkRequest>
30#include <QRegularExpression>
31#include <QRegularExpressionMatch>
35using namespace Qt::StringLiterals;
71 u
"phenomenonTime"_s, Qt::SortOrder::DescendingOrder
80 QString(), Qt::SortOrder::AscendingOrder, 10
98 mChildEntity = entity;
138 if ( !mOrderBy.isEmpty() )
139 parts.append( u
"orderby=%1,%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? u
"asc"_s : u
"desc"_s ) );
141 parts.append( u
"limit=%1"_s.arg( mLimit ) );
142 if ( !mFilter.trimmed().isEmpty() )
144 QString escapedFilter = mFilter;
145 escapedFilter.replace(
':',
"\\colon"_L1 );
146 parts.append( u
"filter=%1"_s.arg( escapedFilter ) );
148 return parts.join(
':' );
153 const QStringList parts =
string.split(
':', Qt::SkipEmptyParts );
159 for (
int i = 1; i < parts.count(); ++i )
161 const QString &part = parts.at( i );
162 const thread_local QRegularExpression orderByRegEx( u
"^orderby=(.*),(.*?)$"_s );
163 const thread_local QRegularExpression orderLimitRegEx( u
"^limit=(\\d+)$"_s );
164 const thread_local QRegularExpression filterRegEx( u
"^filter=(.*)$"_s );
166 const QRegularExpressionMatch orderByMatch = orderByRegEx.match( part );
167 if ( orderByMatch.hasMatch() )
169 definition.
setOrderBy( orderByMatch.captured( 1 ) );
170 definition.
setSortOrder( orderByMatch.captured( 2 ) ==
"asc"_L1 ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder );
174 const QRegularExpressionMatch limitMatch = orderLimitRegEx.match( part );
175 if ( limitMatch.hasMatch() )
177 definition.
setLimit( limitMatch.captured( 1 ).toInt() );
181 const QRegularExpressionMatch filterMatch = filterRegEx.match( part );
182 if ( filterMatch.hasMatch() )
184 QString
filter = filterMatch.captured( 1 );
185 filter.replace(
"\\colon"_L1,
":"_L1 );
202 QString childEntityString;
209 switch ( cardinality )
225 QString res = u
"$expand=%1"_s.arg( childEntityString );
227 QStringList queryOptions;
228 if ( !mOrderBy.isEmpty() )
229 queryOptions.append( u
"$orderby=%1%2"_s.arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QString() : u
" desc"_s ) );
232 queryOptions.append( u
"$top=%1"_s.arg( mLimit ) );
234 if ( !mFilter.isEmpty() )
235 queryOptions.append( u
"$filter=%1"_s.arg( mFilter ) );
237 queryOptions.append( additionalOptions );
239 if ( !queryOptions.isEmpty() )
240 res.append( u
"(%1)"_s.arg( queryOptions.join(
';' ) ) );
250 return mChildEntity == other.mChildEntity
251 && mSortOrder == other.mSortOrder
252 && mLimit == other.mLimit
253 && mOrderBy == other.mOrderBy
254 && mFilter == other.mFilter;
259 return !( *
this == other );
278 const QString trimmed = type.trimmed();
279 if ( trimmed.compare(
"Thing"_L1, Qt::CaseInsensitive ) == 0 )
281 if ( trimmed.compare(
"Location"_L1, Qt::CaseInsensitive ) == 0 )
283 if ( trimmed.compare(
"HistoricalLocation"_L1, Qt::CaseInsensitive ) == 0 )
285 if ( trimmed.compare(
"Datastream"_L1, Qt::CaseInsensitive ) == 0 )
287 if ( trimmed.compare(
"Sensor"_L1, Qt::CaseInsensitive ) == 0 )
289 if ( trimmed.compare(
"ObservedProperty"_L1, Qt::CaseInsensitive ) == 0 )
291 if ( trimmed.compare(
"Observation"_L1, Qt::CaseInsensitive ) == 0 )
293 if ( trimmed.compare(
"FeatureOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
295 if ( trimmed.compare(
"MultiDatastream"_L1, Qt::CaseInsensitive ) == 0 )
308 return plural ? QObject::tr(
"Things" ) : QObject::tr(
"Thing" );
310 return plural ? QObject::tr(
"Locations" ) : QObject::tr(
"Location" );
312 return plural ? QObject::tr(
"Historical Locations" ) : QObject::tr(
"Historical Location" );
314 return plural ? QObject::tr(
"Datastreams" ) : QObject::tr(
"Datastream" );
316 return plural ? QObject::tr(
"Sensors" ) : QObject::tr(
"Sensor" );
318 return plural ? QObject::tr(
"Observed Properties" ) : QObject::tr(
"Observed Property" );
320 return plural ? QObject::tr(
"Observations" ) : QObject::tr(
"Observation" );
322 return plural ? QObject::tr(
"Features of Interest" ) : QObject::tr(
"Feature of Interest" );
324 return plural ? QObject::tr(
"MultiDatastreams" ) : QObject::tr(
"MultiDatastream" );
331 const QString trimmed = type.trimmed();
332 if ( trimmed.compare(
"Things"_L1, Qt::CaseInsensitive ) == 0 )
334 if ( trimmed.compare(
"Locations"_L1, Qt::CaseInsensitive ) == 0 )
336 if ( trimmed.compare(
"HistoricalLocations"_L1, Qt::CaseInsensitive ) == 0 )
338 if ( trimmed.compare(
"Datastreams"_L1, Qt::CaseInsensitive ) == 0 )
340 if ( trimmed.compare(
"Sensors"_L1, Qt::CaseInsensitive ) == 0 )
342 if ( trimmed.compare(
"ObservedProperties"_L1, Qt::CaseInsensitive ) == 0 )
344 if ( trimmed.compare(
"Observations"_L1, Qt::CaseInsensitive ) == 0 )
346 if ( trimmed.compare(
"FeaturesOfInterest"_L1, Qt::CaseInsensitive ) == 0 )
348 if ( trimmed.compare(
"MultiDatastreams"_L1, Qt::CaseInsensitive ) == 0 )
363 return u
"Locations"_s;
365 return u
"HistoricalLocations"_s;
367 return u
"Datastreams"_s;
371 return u
"ObservedProperties"_s;
373 return u
"Observations"_s;
375 return u
"FeaturesOfInterest"_s;
377 return u
"MultiDatastreams"_s;
420 u
"unitOfMeasurement"_s,
421 u
"observationType"_s,
474 u
"unitOfMeasurements"_s,
475 u
"observationType"_s,
476 u
"multiObservationDataTypes"_s,
492 fields.
append(
QgsField( u
"selfLink"_s, QMetaType::Type::QString ) );
502 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
503 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
509 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
510 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
515 fields.
append(
QgsField( u
"time"_s, QMetaType::Type::QDateTime ) );
521 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
522 fields.
append(
QgsField( u
"unitOfMeasurement"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
523 fields.
append(
QgsField( u
"observationType"_s, QMetaType::Type::QString ) );
524 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
525 if ( includeRangeFieldProxies )
527 fields.
append(
QgsField( u
"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
528 fields.
append(
QgsField( u
"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
529 fields.
append(
QgsField( u
"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
530 fields.
append(
QgsField( u
"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
537 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
538 fields.
append(
QgsField( u
"metadata"_s, QMetaType::Type::QString ) );
539 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
545 fields.
append(
QgsField( u
"definition"_s, QMetaType::Type::QString ) );
546 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
547 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
552 if ( includeRangeFieldProxies )
554 fields.
append(
QgsField( u
"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
555 fields.
append(
QgsField( u
"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
559 fields.
append(
QgsField( u
"result"_s, QMetaType::Type::QString ) );
561 fields.
append(
QgsField( u
"resultTime"_s, QMetaType::Type::QDateTime ) );
562 fields.
append(
QgsField( u
"resultQuality"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
563 if ( includeRangeFieldProxies )
565 fields.
append(
QgsField( u
"validTimeStart"_s, QMetaType::Type::QDateTime ) );
566 fields.
append(
QgsField( u
"validTimeEnd"_s, QMetaType::Type::QDateTime ) );
568 fields.
append(
QgsField( u
"parameters"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
574 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
575 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
581 fields.
append(
QgsField( u
"description"_s, QMetaType::Type::QString ) );
582 fields.
append(
QgsField( u
"unitOfMeasurements"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
583 fields.
append(
QgsField( u
"observationType"_s, QMetaType::Type::QString ) );
584 fields.
append(
QgsField( u
"multiObservationDataTypes"_s, QMetaType::Type::QStringList, QString(), 0, 0, QString(), QMetaType::Type::QString ) );
585 fields.
append(
QgsField( u
"properties"_s, QMetaType::Type::QVariantMap, u
"json"_s, 0, 0, QString(), QMetaType::Type::QString ) );
586 if ( includeRangeFieldProxies )
588 fields.
append(
QgsField( u
"phenomenonTimeStart"_s, QMetaType::Type::QDateTime ) );
589 fields.
append(
QgsField( u
"phenomenonTimeEnd"_s, QMetaType::Type::QDateTime ) );
590 fields.
append(
QgsField( u
"resultTimeStart"_s, QMetaType::Type::QDateTime ) );
591 fields.
append(
QgsField( u
"resultTimeEnd"_s, QMetaType::Type::QDateTime ) );
601 if ( expandedTypes.empty() )
608 path = ( path.isEmpty() ? QString() : ( path +
'_' ) ) +
qgsEnumValueToKey( expandedType );
610 for (
const QgsField &expandedField : expandedFields )
612 QgsField renamedExpandedField = expandedField;
613 renamedExpandedField.
setName( u
"%1_%2"_s.arg( path, expandedField.name() ) );
614 fields.
append( renamedExpandedField );
633 return u
"location"_s;
640 return u
"observedArea"_s;
689 QString geometryTypeString;
693 geometryTypeString = u
"Point"_s;
696 geometryTypeString = u
"Polygon"_s;
699 geometryTypeString = u
"LineString"_s;
708 if ( filterTarget.isEmpty() )
711 return u
"%1/type eq '%2' or %1/geometry/type eq '%2'"_s.arg( filterTarget, geometryTypeString );
717 return ( extent.
isNull() || geometryField.isEmpty() )
719 : u
"geo.intersects(%1, geography'%2')"_s.arg( geometryField, extent.
asWktPolygon() );
724 QStringList nonEmptyFilters;
725 for (
const QString &filter : filters )
727 if ( !filter.isEmpty() )
728 nonEmptyFilters.append( filter );
730 if ( nonEmptyFilters.empty() )
732 if ( nonEmptyFilters.size() == 1 )
733 return nonEmptyFilters.at( 0 );
735 return u
"("_s + nonEmptyFilters.join(
") and ("_L1 ) + u
")"_s;
740 QNetworkRequest request = QNetworkRequest( QUrl( uri ) );
746 switch ( networkRequest.
get( request ) )
758 QString entityBaseUri;
762 auto rootContent = nlohmann::json::parse( content.
content().toStdString() );
763 if ( !rootContent.contains(
"value" ) )
769 bool foundMatchingEntity =
false;
770 for (
const auto &valueJson : rootContent[
"value"] )
772 if ( valueJson.contains(
"name" ) && valueJson.contains(
"url" ) )
774 const QString name = QString::fromStdString( valueJson[
"name"].get<std::string>() );
776 if ( entityType == type )
778 const QString url = QString::fromStdString( valueJson[
"url"].get<std::string>() );
779 if ( !url.isEmpty() )
781 foundMatchingEntity =
true;
789 if ( !foundMatchingEntity )
795 catch (
const nlohmann::json::parse_error &ex )
797 QgsDebugError( u
"Error parsing response: %1"_s.arg( ex.what() ) );
801 auto getCountForType = [entityBaseUri, type, authCfg, feedback](
Qgis::GeometryType geometryType ) ->
long long
804 QString countUri = u
"%1?$top=0&$count=true"_s.arg( entityBaseUri );
807 if ( !typeFilter.isEmpty() )
808 countUri += u
"&$filter="_s + typeFilter;
810 const QUrl url( countUri );
812 QNetworkRequest request( url );
833 auto rootContent = nlohmann::json::parse( content.
content().toStdString() );
834 if ( !rootContent.contains(
"@iot.count" ) )
840 return rootContent[
"@iot.count"].get<
long long>();
842 catch (
const nlohmann::json::parse_error &ex )
844 QgsDebugError( u
"Error parsing response: %1"_s.arg( ex.what() ) );
850 QList<Qgis::GeometryType> types;
858 const long long matchCount = getCountForType( geometryType );
859 if ( matchCount < 0 )
861 else if ( matchCount > 0 )
862 types.append( geometryType );
958 switch ( relatedType )
980 switch ( relatedType )
1000 switch ( relatedType )
1029 switch ( relatedType )
1053 switch ( relatedType )
1075 switch ( relatedType )
1098 switch ( relatedType )
1119 switch ( relatedType )
1141 switch ( relatedType )
1172 for (
int i = expansions.size() - 1; i >= 0 ; i-- )
1180 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)