Use a more flexible API for handling SensorThings expansions

This allows us to control the sort order and limit for each expansion,
and gives us more flexibility in future to eg handle per expansion
filter strings
This commit is contained in:
Nyall Dawson 2024-03-26 10:43:24 +10:00
parent e471a0bd1d
commit ecfcf6ced1
10 changed files with 1043 additions and 123 deletions

View File

@ -46,6 +46,13 @@ Returns :py:class:`Qgis`.SensorThingsEntity.Invalid if the string could not be c
%Docstring
Converts a SensorThings entity set to a SensorThings entity set string.
.. versionadded:: 3.38
%End
static QStringList propertiesForEntityType( Qgis::SensorThingsEntity type );
%Docstring
Returns the SensorThings properties which correspond to a specified entity ``type``.
.. versionadded:: 3.38
%End
@ -114,13 +121,166 @@ and entity ``type``.
This method will block while network requests are made to the server.
%End
static QList< QList< Qgis::SensorThingsEntity > > expandableTargets( Qgis::SensorThingsEntity type );
static QList< Qgis::SensorThingsEntity > expandableTargets( Qgis::SensorThingsEntity type );
%Docstring
Returns a list of permissible expand targets for a given base entity ``type``.
.. versionadded:: 3.38
%End
static QString asQueryString( const QList< QgsSensorThingsExpansionDefinition > &expansions );
%Docstring
Returns a list of ``expansions`` as a valid SensorThings API query string, eg "$expand=Locations($orderby=id desc;$top=3;$expand=Datastreams($top=101))".
.. versionadded:: 3.38
%End
};
class QgsSensorThingsExpansionDefinition
{
%Docstring(signature="appended")
Encapsulates information about how relationships in a SensorThings API service should be expanded.
.. versionadded:: 3.38
%End
%TypeHeaderCode
#include "qgssensorthingsutils.h"
%End
public:
QgsSensorThingsExpansionDefinition( Qgis::SensorThingsEntity childEntity = Qgis::SensorThingsEntity::Invalid,
const QString &orderBy = QString(),
Qt::SortOrder sortOrder = Qt::SortOrder::AscendingOrder,
int limit = QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT );
%Docstring
Constructor for QgsSensorThingsExpansionDefinition, targeting the specified child entity type.
%End
bool isValid() const;
%Docstring
Returns ``True`` if the definition is valid.
%End
Qgis::SensorThingsEntity childEntity() const;
%Docstring
Returns the target child entity which should be expanded.
.. seealso:: :py:func:`setChildEntity`
%End
void setChildEntity( Qgis::SensorThingsEntity entity );
%Docstring
Sets the target child ``entity`` which should be expanded.
.. seealso:: :py:func:`childEntity`
%End
QString orderBy() const;
%Docstring
Returns the field name to order the expanded child entities by.
.. seealso:: :py:func:`sortOrder`
.. seealso:: :py:func:`setOrderBy`
%End
void setOrderBy( const QString &field );
%Docstring
Sets the ``field`` name to order the expanded child entities by.
.. seealso:: :py:func:`orderBy`
.. seealso:: :py:func:`setSortOrder`
%End
Qt::SortOrder sortOrder() const;
%Docstring
Returns the sort order for the expanded child entities.
.. seealso:: :py:func:`orderBy`
.. seealso:: :py:func:`setSortOrder`
%End
void setSortOrder( Qt::SortOrder order );
%Docstring
Sets the sort order for the expanded child entities.
.. seealso:: :py:func:`setOrderBy`
.. seealso:: :py:func:`sortOrder`
%End
int limit() const;
%Docstring
Returns the limit on the number of child features to fetch.
Returns -1 if no limit is defined.
.. seealso:: :py:func:`setLimit`
%End
void setLimit( int limit );
%Docstring
Sets the ``limit`` on the number of child features to fetch.
Set to -1 if no limit is desired.
.. seealso:: :py:func:`limit`
%End
QString toString() const;
%Docstring
Returns a string encapsulation of the expansion definition.
.. seealso:: :py:func:`fromString`
%End
static QgsSensorThingsExpansionDefinition fromString( const QString &string );
%Docstring
Returns a QgsSensorThingsExpansionDefinition from a string representation.
.. seealso:: :py:func:`toString`
%End
QString asQueryString( const QStringList &additionalOptions = QStringList() ) const;
%Docstring
Returns the expansion as a valid SensorThings API query string, eg "$expand=Observations($orderby=phenomenonTime desc;$top=10)".
Optionally a list of additional query options can be specified for the expansion.
%End
bool operator==( const QgsSensorThingsExpansionDefinition &other ) const;
bool operator!=( const QgsSensorThingsExpansionDefinition &other ) const;
SIP_PYOBJECT __repr__();
%MethodCode
if ( !sipCpp->isValid() )
{
sipRes = PyUnicode_FromString( "<QgsSensorThingsExpansionDefinition: invalid>" );
return;
}
QString innerDefinition;
if ( !sipCpp->orderBy().isEmpty() )
{
innerDefinition = QStringLiteral( "by %1 (%2)" ).arg( sipCpp->orderBy(), sipCpp->sortOrder() == Qt::SortOrder::AscendingOrder ? QStringLiteral( "asc" ) : QStringLiteral( "desc" ) );
}
if ( sipCpp->limit() >= 0 )
{
if ( !innerDefinition.isEmpty() )
innerDefinition = QStringLiteral( "%1, limit %2" ).arg( innerDefinition ).arg( sipCpp->limit() );
else
innerDefinition = QStringLiteral( "limit %1" ).arg( sipCpp->limit() );
}
QString str = QStringLiteral( "<QgsSensorThingsExpansionDefinition: %1%2>" ).arg( qgsEnumValueToKey( sipCpp->childEntity() ), innerDefinition.isEmpty() ? QString() : ( QStringLiteral( " " ) + innerDefinition ) );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
};
/************************************************************************

View File

@ -46,6 +46,13 @@ Returns :py:class:`Qgis`.SensorThingsEntity.Invalid if the string could not be c
%Docstring
Converts a SensorThings entity set to a SensorThings entity set string.
.. versionadded:: 3.38
%End
static QStringList propertiesForEntityType( Qgis::SensorThingsEntity type );
%Docstring
Returns the SensorThings properties which correspond to a specified entity ``type``.
.. versionadded:: 3.38
%End
@ -114,13 +121,166 @@ and entity ``type``.
This method will block while network requests are made to the server.
%End
static QList< QList< Qgis::SensorThingsEntity > > expandableTargets( Qgis::SensorThingsEntity type );
static QList< Qgis::SensorThingsEntity > expandableTargets( Qgis::SensorThingsEntity type );
%Docstring
Returns a list of permissible expand targets for a given base entity ``type``.
.. versionadded:: 3.38
%End
static QString asQueryString( const QList< QgsSensorThingsExpansionDefinition > &expansions );
%Docstring
Returns a list of ``expansions`` as a valid SensorThings API query string, eg "$expand=Locations($orderby=id desc;$top=3;$expand=Datastreams($top=101))".
.. versionadded:: 3.38
%End
};
class QgsSensorThingsExpansionDefinition
{
%Docstring(signature="appended")
Encapsulates information about how relationships in a SensorThings API service should be expanded.
.. versionadded:: 3.38
%End
%TypeHeaderCode
#include "qgssensorthingsutils.h"
%End
public:
QgsSensorThingsExpansionDefinition( Qgis::SensorThingsEntity childEntity = Qgis::SensorThingsEntity::Invalid,
const QString &orderBy = QString(),
Qt::SortOrder sortOrder = Qt::SortOrder::AscendingOrder,
int limit = QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT );
%Docstring
Constructor for QgsSensorThingsExpansionDefinition, targeting the specified child entity type.
%End
bool isValid() const;
%Docstring
Returns ``True`` if the definition is valid.
%End
Qgis::SensorThingsEntity childEntity() const;
%Docstring
Returns the target child entity which should be expanded.
.. seealso:: :py:func:`setChildEntity`
%End
void setChildEntity( Qgis::SensorThingsEntity entity );
%Docstring
Sets the target child ``entity`` which should be expanded.
.. seealso:: :py:func:`childEntity`
%End
QString orderBy() const;
%Docstring
Returns the field name to order the expanded child entities by.
.. seealso:: :py:func:`sortOrder`
.. seealso:: :py:func:`setOrderBy`
%End
void setOrderBy( const QString &field );
%Docstring
Sets the ``field`` name to order the expanded child entities by.
.. seealso:: :py:func:`orderBy`
.. seealso:: :py:func:`setSortOrder`
%End
Qt::SortOrder sortOrder() const;
%Docstring
Returns the sort order for the expanded child entities.
.. seealso:: :py:func:`orderBy`
.. seealso:: :py:func:`setSortOrder`
%End
void setSortOrder( Qt::SortOrder order );
%Docstring
Sets the sort order for the expanded child entities.
.. seealso:: :py:func:`setOrderBy`
.. seealso:: :py:func:`sortOrder`
%End
int limit() const;
%Docstring
Returns the limit on the number of child features to fetch.
Returns -1 if no limit is defined.
.. seealso:: :py:func:`setLimit`
%End
void setLimit( int limit );
%Docstring
Sets the ``limit`` on the number of child features to fetch.
Set to -1 if no limit is desired.
.. seealso:: :py:func:`limit`
%End
QString toString() const;
%Docstring
Returns a string encapsulation of the expansion definition.
.. seealso:: :py:func:`fromString`
%End
static QgsSensorThingsExpansionDefinition fromString( const QString &string );
%Docstring
Returns a QgsSensorThingsExpansionDefinition from a string representation.
.. seealso:: :py:func:`toString`
%End
QString asQueryString( const QStringList &additionalOptions = QStringList() ) const;
%Docstring
Returns the expansion as a valid SensorThings API query string, eg "$expand=Observations($orderby=phenomenonTime desc;$top=10)".
Optionally a list of additional query options can be specified for the expansion.
%End
bool operator==( const QgsSensorThingsExpansionDefinition &other ) const;
bool operator!=( const QgsSensorThingsExpansionDefinition &other ) const;
SIP_PYOBJECT __repr__();
%MethodCode
if ( !sipCpp->isValid() )
{
sipRes = PyUnicode_FromString( "<QgsSensorThingsExpansionDefinition: invalid>" );
return;
}
QString innerDefinition;
if ( !sipCpp->orderBy().isEmpty() )
{
innerDefinition = QStringLiteral( "by %1 (%2)" ).arg( sipCpp->orderBy(), sipCpp->sortOrder() == Qt::SortOrder::AscendingOrder ? QStringLiteral( "asc" ) : QStringLiteral( "desc" ) );
}
if ( sipCpp->limit() >= 0 )
{
if ( !innerDefinition.isEmpty() )
innerDefinition = QStringLiteral( "%1, limit %2" ).arg( innerDefinition ).arg( sipCpp->limit() );
else
innerDefinition = QStringLiteral( "limit %1" ).arg( sipCpp->limit() );
}
QString str = QStringLiteral( "<QgsSensorThingsExpansionDefinition: %1%2>" ).arg( qgsEnumValueToKey( sipCpp->childEntity() ), innerDefinition.isEmpty() ? QString() : ( QStringLiteral( " " ) + innerDefinition ) );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
};
/************************************************************************

View File

@ -3285,7 +3285,7 @@ geometricians:geometers
geomtry:geometry
gerat:great
get's:gets
geting:getting
geting:getting:*
Ghandi:Gandhi
gived:given
glight:flight

View File

@ -377,9 +377,23 @@ QVariantMap QgsSensorThingsProviderMetadata::decodeUri( const QString &uri ) con
if ( entity != Qgis::SensorThingsEntity::Invalid )
components.insert( QStringLiteral( "entity" ), qgsEnumValueToKey( entity ) );
const QString expandToParam = dsUri.param( QStringLiteral( "expandTo" ) );
const QStringList expandToParam = dsUri.param( QStringLiteral( "expandTo" ) ).split( ';', Qt::SkipEmptyParts );
if ( !expandToParam.isEmpty() )
components.insert( QStringLiteral( "expandTo" ), expandToParam.split( ',' ) );
{
QVariantList expandParts;
for ( const QString &expandString : expandToParam )
{
const QgsSensorThingsExpansionDefinition definition = QgsSensorThingsExpansionDefinition::fromString( expandString );
if ( definition.isValid() )
{
expandParts.append( QVariant::fromValue( definition ) );
}
}
if ( !expandParts.isEmpty() )
{
components.insert( QStringLiteral( "expandTo" ), expandParts );
}
}
bool ok = false;
const int maxPageSizeParam = dsUri.param( QStringLiteral( "pageSize" ) ).toInt( &ok );
@ -394,12 +408,6 @@ QVariantMap QgsSensorThingsProviderMetadata::decodeUri( const QString &uri ) con
{
components.insert( QStringLiteral( "featureLimit" ), featureLimitParam );
}
ok = false;
const int expansionLimitParam = dsUri.param( QStringLiteral( "expansionLimit" ) ).toInt( &ok );
if ( ok )
{
components.insert( QStringLiteral( "expansionLimit" ), expansionLimitParam );
}
switch ( QgsWkbTypes::geometryType( dsUri.wkbType() ) )
{
@ -476,9 +484,23 @@ QString QgsSensorThingsProviderMetadata::encodeUri( const QVariantMap &parts ) c
qgsEnumValueToKey( entity ) );
}
const QStringList expandToParam = parts.value( QStringLiteral( "expandTo" ) ).toStringList();
const QVariantList expandToParam = parts.value( QStringLiteral( "expandTo" ) ).toList();
if ( !expandToParam.isEmpty() )
dsUri.setParam( QStringLiteral( "expandTo" ), expandToParam.join( ',' ) );
{
QStringList expandToStringList;
for ( const QVariant &expansion : expandToParam )
{
const QgsSensorThingsExpansionDefinition expansionDefinition = expansion.value< QgsSensorThingsExpansionDefinition >();
if ( !expansionDefinition.isValid() )
continue;
expandToStringList.append( expansionDefinition.toString() );
}
if ( !expandToStringList.isEmpty() )
{
dsUri.setParam( QStringLiteral( "expandTo" ), expandToStringList.join( ';' ) );
}
}
bool ok = false;
const int maxPageSizeParam = parts.value( QStringLiteral( "pageSize" ) ).toInt( &ok );
@ -493,12 +515,6 @@ QString QgsSensorThingsProviderMetadata::encodeUri( const QVariantMap &parts ) c
{
dsUri.setParam( QStringLiteral( "featureLimit" ), QString::number( featureLimitParam ) );
}
ok = false;
const int expansionLimitParam = parts.value( QStringLiteral( "expansionLimit" ) ).toInt( &ok );
if ( ok )
{
dsUri.setParam( QStringLiteral( "expansionLimit" ), QString::number( expansionLimitParam ) );
}
const QString geometryType = parts.value( QStringLiteral( "geometryType" ) ).toString();
if ( geometryType.compare( QLatin1String( "point" ), Qt::CaseInsensitive ) == 0 )

View File

@ -34,28 +34,21 @@ QgsSensorThingsSharedData::QgsSensorThingsSharedData( const QString &uri )
const QVariantMap uriParts = QgsSensorThingsProviderMetadata().decodeUri( uri );
mEntityType = qgsEnumKeyToValue( uriParts.value( QStringLiteral( "entity" ) ).toString(), Qgis::SensorThingsEntity::Invalid );
const QStringList expandTo = uriParts.value( QStringLiteral( "expandTo" ) ).toStringList();
QStringList expandQueryParts;
for ( const QString &expand : expandTo )
const QVariantList expandTo = uriParts.value( QStringLiteral( "expandTo" ) ).toList();
QList< Qgis::SensorThingsEntity > expandedEntities;
for ( const QVariant &expansionVariant : expandTo )
{
const Qgis::SensorThingsEntity expandToEntityType = qgsEnumKeyToValue( expand, Qgis::SensorThingsEntity::Invalid );
if ( expandToEntityType != Qgis::SensorThingsEntity::Invalid )
const QgsSensorThingsExpansionDefinition expansion = expansionVariant.value< QgsSensorThingsExpansionDefinition >();
if ( expansion.isValid() )
{
mExpandTo.append( expandToEntityType );
// NOTE: from the specifications, it looks look SOMETIMES plural is used, sometimes singular??
// We might need to be more flexible here to support all connections
expandQueryParts.append( QgsSensorThingsUtils::entityToSetString( expandToEntityType ) );
mExpansions.append( expansion );
expandedEntities.append( expansion.childEntity() );
}
}
mExpansionLimit = uriParts.value( QStringLiteral( "expansionLimit" ) ).toInt();
if ( !expandQueryParts.empty() )
{
mExpandQueryString = QStringLiteral( "$expand=" ) + expandQueryParts.join( '/' );
if ( mExpansionLimit > 0 )
mExpandQueryString += QStringLiteral( "($top=%1)" ).arg( mExpansionLimit );
mExpandQueryString = QgsSensorThingsUtils::asQueryString( mExpansions );
}
mFields = QgsSensorThingsUtils::fieldsForExpandedEntityType( mEntityType, mExpandTo );
mFields = QgsSensorThingsUtils::fieldsForExpandedEntityType( mEntityType, expandedEntities );
mGeometryField = QgsSensorThingsUtils::geometryFieldForEntityType( mEntityType );
// use initial value of maximum page size as default
@ -190,7 +183,7 @@ long long QgsSensorThingsSharedData::featureCount( QgsFeedback *feedback ) const
// MISSING PART -- how to handle feature count when we are expanding features?
// This situation is not handled by the SensorThings standard at all, so we'll just have
// to return an unknown count whenever expansion is used
if ( !mExpandTo.isEmpty() )
if ( !mExpansions.isEmpty() )
{
return static_cast< long long >( Qgis::FeatureCountState::UnknownCount );
}
@ -414,7 +407,7 @@ bool QgsSensorThingsSharedData::processFeatureRequest( QString &nextPage, QgsFee
const QString authcfg = mAuthCfg;
const QgsHttpHeaders headers = mHeaders;
const QgsFields fields = mFields;
const QList< Qgis::SensorThingsEntity > expandTo = mExpandTo;
const QList< QgsSensorThingsExpansionDefinition > expansions = mExpansions;
while ( continueFetchingCallback() )
{
@ -587,7 +580,7 @@ bool QgsSensorThingsSharedData::processFeatureRequest( QString &nextPage, QgsFee
};
const QString iotId = getString( featureData, "@iot.id" ).toString();
if ( expandTo.isEmpty() )
if ( expansions.isEmpty() )
{
auto existingFeatureIdIt = mIotIdToFeatureId.constFind( iotId );
if ( existingFeatureIdIt != mIotIdToFeatureId.constEnd() )
@ -757,17 +750,17 @@ bool QgsSensorThingsSharedData::processFeatureRequest( QString &nextPage, QgsFee
};
const QString baseFeatureId = getString( featureData, "@iot.id" ).toString();
if ( !expandTo.empty() )
if ( !expansions.empty() )
{
mRetrievedBaseFeatureCount++;
std::function< void( const nlohmann::json &, const QList<Qgis::SensorThingsEntity > &, const QString &, const QgsAttributes & ) > traverseExpansion;
traverseExpansion = [this, &feature, &getString, &traverseExpansion, &fetchedFeatureCallback, &extendAttributes, &processFeature]( const nlohmann::json & currentLevelData, const QList<Qgis::SensorThingsEntity > &expansionTargets, const QString & lowerLevelId, const QgsAttributes & lowerLevelAttributes )
std::function< void( const nlohmann::json &, const QList<QgsSensorThingsExpansionDefinition > &, const QString &, const QgsAttributes & ) > traverseExpansion;
traverseExpansion = [this, &feature, &getString, &traverseExpansion, &fetchedFeatureCallback, &extendAttributes, &processFeature]( const nlohmann::json & currentLevelData, const QList<QgsSensorThingsExpansionDefinition > &expansionTargets, const QString & lowerLevelId, const QgsAttributes & lowerLevelAttributes )
{
const Qgis::SensorThingsEntity currentExpansionTarget = expansionTargets.at( 0 );
const QList< Qgis::SensorThingsEntity > remainingExpansionTargets = expansionTargets.mid( 1 );
const QgsSensorThingsExpansionDefinition currentExpansionTarget = expansionTargets.at( 0 );
const QList< QgsSensorThingsExpansionDefinition > remainingExpansionTargets = expansionTargets.mid( 1 );
const QString currentExpansionPropertyString = QgsSensorThingsUtils::entityToSetString( currentExpansionTarget );
const QString currentExpansionPropertyString = QgsSensorThingsUtils::entityToSetString( currentExpansionTarget.childEntity() );
if ( currentLevelData.contains( currentExpansionPropertyString.toLocal8Bit().constData() ) )
{
const auto &expandedEntity = currentLevelData[currentExpansionPropertyString.toLocal8Bit().constData()];
@ -790,8 +783,7 @@ bool QgsSensorThingsSharedData::processFeatureRequest( QString &nextPage, QgsFee
}
}
extendAttributes( currentExpansionTarget, expandedEntityElement, expandedAttributes );
extendAttributes( currentExpansionTarget.childEntity(), expandedEntityElement, expandedAttributes );
if ( !remainingExpansionTargets.empty() )
{
// traverse deeper
@ -817,7 +809,7 @@ bool QgsSensorThingsSharedData::processFeatureRequest( QString &nextPage, QgsFee
}
};
traverseExpansion( featureData, expandTo, baseFeatureId, attributes );
traverseExpansion( featureData, expansions, baseFeatureId, attributes );
if ( mFeatureLimit > 0 && mFeatureLimit <= mRetrievedBaseFeatureCount )
break;

View File

@ -82,10 +82,9 @@ class QgsSensorThingsSharedData
QString mExpandQueryString;
Qgis::SensorThingsEntity mEntityType = Qgis::SensorThingsEntity::Invalid;
QList< Qgis::SensorThingsEntity > mExpandTo;
QList< QgsSensorThingsExpansionDefinition > mExpansions;
int mFeatureLimit = 0;
int mExpansionLimit = 0;
Qgis::WkbType mGeometryType = Qgis::WkbType::Unknown;
QString mGeometryField;
QgsFields mFields;

View File

@ -24,8 +24,158 @@
#include "qgsrectangle.h"
#include <QUrl>
#include <QNetworkRequest>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <nlohmann/json.hpp>
//
// QgsSensorThingsExpansionDefinition
//
QgsSensorThingsExpansionDefinition::QgsSensorThingsExpansionDefinition( Qgis::SensorThingsEntity childEntity, const QString &orderBy, Qt::SortOrder sortOrder, int limit )
: mChildEntity( childEntity )
, mOrderBy( orderBy )
, mSortOrder( sortOrder )
, mLimit( limit )
{
}
bool QgsSensorThingsExpansionDefinition::isValid() const
{
return mChildEntity != Qgis::SensorThingsEntity::Invalid;
}
Qgis::SensorThingsEntity QgsSensorThingsExpansionDefinition::childEntity() const
{
return mChildEntity;
}
void QgsSensorThingsExpansionDefinition::setChildEntity( Qgis::SensorThingsEntity entity )
{
mChildEntity = entity;
}
Qt::SortOrder QgsSensorThingsExpansionDefinition::sortOrder() const
{
return mSortOrder;
}
void QgsSensorThingsExpansionDefinition::setSortOrder( Qt::SortOrder order )
{
mSortOrder = order;
}
int QgsSensorThingsExpansionDefinition::limit() const
{
return mLimit;
}
void QgsSensorThingsExpansionDefinition::setLimit( int limit )
{
mLimit = limit;
}
QString QgsSensorThingsExpansionDefinition::toString() const
{
if ( !isValid() )
return QString();
QStringList parts;
parts.append( qgsEnumValueToKey( mChildEntity ) );
if ( !mOrderBy.isEmpty() )
parts.append( QStringLiteral( "orderby=%1,%2" ).arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QStringLiteral( "asc" ) : QStringLiteral( "desc" ) ) );
if ( mLimit >= 0 )
parts.append( QStringLiteral( "limit=%1" ).arg( mLimit ) );
return parts.join( ':' );
}
QgsSensorThingsExpansionDefinition QgsSensorThingsExpansionDefinition::fromString( const QString &string )
{
const QStringList parts = string.split( ':', Qt::SkipEmptyParts );
if ( parts.empty() )
return QgsSensorThingsExpansionDefinition();
QgsSensorThingsExpansionDefinition definition( qgsEnumKeyToValue( parts.at( 0 ), Qgis::SensorThingsEntity::Invalid ) );
definition.setLimit( -1 );
for ( int i = 1; i < parts.count(); ++i )
{
const QString &part = parts.at( i );
const thread_local QRegularExpression orderByRegEx( QStringLiteral( "^orderby=(.*),(.*?)$" ) );
const thread_local QRegularExpression orderLimitRegEx( QStringLiteral( "^limit=(\\d+)$" ) );
const QRegularExpressionMatch orderByMatch = orderByRegEx.match( part );
if ( orderByMatch.hasMatch() )
{
definition.setOrderBy( orderByMatch.captured( 1 ) );
definition.setSortOrder( orderByMatch.captured( 2 ) == QLatin1String( "asc" ) ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder );
continue;
}
const QRegularExpressionMatch limitMatch = orderLimitRegEx.match( part );
if ( limitMatch.hasMatch() )
{
definition.setLimit( limitMatch.captured( 1 ).toInt() );
continue;
}
}
return definition;
}
QString QgsSensorThingsExpansionDefinition::asQueryString( const QStringList &additionalOptions ) const
{
if ( !isValid() )
return QString();
// NOTE: from the specifications, it looks look SOMETIMES plural is used, sometimes singular??
// We might need to be more flexible here to support all connections
QString res = QStringLiteral( "$expand=%1" ).arg( QgsSensorThingsUtils::entityToSetString( mChildEntity ) );
QStringList queryOptions;
if ( !mOrderBy.isEmpty() )
queryOptions.append( QStringLiteral( "$orderby=%1%2" ).arg( mOrderBy, mSortOrder == Qt::SortOrder::AscendingOrder ? QString() : QStringLiteral( " desc" ) ) );
if ( mLimit > -1 )
queryOptions.append( QStringLiteral( "$top=%1" ).arg( mLimit ) );
queryOptions.append( additionalOptions );
if ( !queryOptions.isEmpty() )
res.append( QStringLiteral( "(%1)" ).arg( queryOptions.join( ';' ) ) );
return res;
}
bool QgsSensorThingsExpansionDefinition::operator==( const QgsSensorThingsExpansionDefinition &other ) const
{
if ( mChildEntity == Qgis::SensorThingsEntity::Invalid )
return other.mChildEntity == Qgis::SensorThingsEntity::Invalid;
return mChildEntity == other.mChildEntity
&& mSortOrder == other.mSortOrder
&& mLimit == other.mLimit
&& mOrderBy == other.mOrderBy;
}
bool QgsSensorThingsExpansionDefinition::operator!=( const QgsSensorThingsExpansionDefinition &other ) const
{
return !( *this == other );
}
QString QgsSensorThingsExpansionDefinition::orderBy() const
{
return mOrderBy;
}
void QgsSensorThingsExpansionDefinition::setOrderBy( const QString &field )
{
mOrderBy = field;
}
//
// QgsSensorThingsUtils
//
Qgis::SensorThingsEntity QgsSensorThingsUtils::stringToEntity( const QString &type )
{
const QString trimmed = type.trimmed();
@ -132,6 +282,110 @@ QString QgsSensorThingsUtils::entityToSetString( Qgis::SensorThingsEntity type )
BUILTIN_UNREACHABLE
}
QStringList QgsSensorThingsUtils::propertiesForEntityType( Qgis::SensorThingsEntity type )
{
switch ( type )
{
case Qgis::SensorThingsEntity::Invalid:
return {};
case Qgis::SensorThingsEntity::Thing:
// https://docs.ogc.org/is/18-088/18-088.html#thing
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "name" ),
QStringLiteral( "description" ),
QStringLiteral( "properties" ),
};
case Qgis::SensorThingsEntity::Location:
// https://docs.ogc.org/is/18-088/18-088.html#location
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "name" ),
QStringLiteral( "description" ),
QStringLiteral( "properties" ),
};
case Qgis::SensorThingsEntity::HistoricalLocation:
// https://docs.ogc.org/is/18-088/18-088.html#historicallocation
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "time" ),
};
case Qgis::SensorThingsEntity::Datastream:
// https://docs.ogc.org/is/18-088/18-088.html#datastream
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "name" ),
QStringLiteral( "description" ),
QStringLiteral( "unitOfMeasurement" ),
QStringLiteral( "observationType" ),
QStringLiteral( "properties" ),
QStringLiteral( "phenomenonTime" ),
QStringLiteral( "resultTime" ),
};
case Qgis::SensorThingsEntity::Sensor:
// https://docs.ogc.org/is/18-088/18-088.html#sensor
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "name" ),
QStringLiteral( "description" ),
QStringLiteral( "metadata" ),
QStringLiteral( "properties" ),
};
case Qgis::SensorThingsEntity::ObservedProperty:
// https://docs.ogc.org/is/18-088/18-088.html#observedproperty
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "name" ),
QStringLiteral( "definition" ),
QStringLiteral( "description" ),
QStringLiteral( "properties" ),
};
case Qgis::SensorThingsEntity::Observation:
// https://docs.ogc.org/is/18-088/18-088.html#observation
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "phenomenonTime" ),
QStringLiteral( "result" ),
QStringLiteral( "resultTime" ),
QStringLiteral( "resultQuality" ),
QStringLiteral( "validTime" ),
QStringLiteral( "parameters" ),
};
case Qgis::SensorThingsEntity::FeatureOfInterest:
// https://docs.ogc.org/is/18-088/18-088.html#featureofinterest
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "name" ),
QStringLiteral( "description" ),
QStringLiteral( "properties" ),
};
case Qgis::SensorThingsEntity::MultiDatastream:
// https://docs.ogc.org/is/18-088/18-088.html#multidatastream-extension
return { QStringLiteral( "id" ),
QStringLiteral( "selfLink" ),
QStringLiteral( "name" ),
QStringLiteral( "description" ),
QStringLiteral( "unitOfMeasurements" ),
QStringLiteral( "observationType" ),
QStringLiteral( "multiObservationDataTypes" ),
QStringLiteral( "properties" ),
QStringLiteral( "phenomenonTime" ),
QStringLiteral( "resultTime" ),
};
}
return {};
}
QgsFields QgsSensorThingsUtils::fieldsForEntityType( Qgis::SensorThingsEntity type )
{
QgsFields fields;
@ -503,7 +757,7 @@ QList<Qgis::GeometryType> QgsSensorThingsUtils::availableGeometryTypes( const QS
return types;
}
QList<QList<Qgis::SensorThingsEntity> > QgsSensorThingsUtils::expandableTargets( Qgis::SensorThingsEntity type )
QList<Qgis::SensorThingsEntity> QgsSensorThingsUtils::expandableTargets( Qgis::SensorThingsEntity type )
{
// note that we are restricting these choices so that the geometry enabled entity type MUST be the base type
switch ( type )
@ -514,95 +768,79 @@ QList<QList<Qgis::SensorThingsEntity> > QgsSensorThingsUtils::expandableTargets(
case Qgis::SensorThingsEntity::Thing:
return
{
{ Qgis::SensorThingsEntity::HistoricalLocation },
{ Qgis::SensorThingsEntity::Datastream },
{ Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Sensor },
{ Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::ObservedProperty },
{ Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Observation },
Qgis::SensorThingsEntity::HistoricalLocation,
Qgis::SensorThingsEntity::Datastream
};
case Qgis::SensorThingsEntity::Location:
return
{
{ Qgis::SensorThingsEntity::Thing },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Sensor },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::ObservedProperty },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Observation },
{ Qgis::SensorThingsEntity::HistoricalLocation },
Qgis::SensorThingsEntity::Thing,
Qgis::SensorThingsEntity::HistoricalLocation,
};
case Qgis::SensorThingsEntity::HistoricalLocation:
return
{
{Qgis::SensorThingsEntity::Thing },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Sensor },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::ObservedProperty },
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Observation },
Qgis::SensorThingsEntity::Thing
};
case Qgis::SensorThingsEntity::Datastream:
return
{
{Qgis::SensorThingsEntity::Thing},
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::HistoricalLocation },
{Qgis::SensorThingsEntity::Sensor},
{Qgis::SensorThingsEntity::ObservedProperty},
{Qgis::SensorThingsEntity::Observation}
Qgis::SensorThingsEntity::Thing,
Qgis::SensorThingsEntity::Sensor,
Qgis::SensorThingsEntity::ObservedProperty,
Qgis::SensorThingsEntity::Observation
};
case Qgis::SensorThingsEntity::Sensor:
return
{
{Qgis::SensorThingsEntity::Datastream},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::HistoricalLocation },
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::ObservedProperty},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Observation}
Qgis::SensorThingsEntity::Datastream
};
case Qgis::SensorThingsEntity::ObservedProperty:
return
{
{Qgis::SensorThingsEntity::Datastream},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Sensor},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::HistoricalLocation },
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Observation}
Qgis::SensorThingsEntity::Datastream
};
case Qgis::SensorThingsEntity::Observation:
return
{
{Qgis::SensorThingsEntity::Datastream},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Sensor},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing},
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::HistoricalLocation },
{Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::ObservedProperty}
Qgis::SensorThingsEntity::Datastream
};
case Qgis::SensorThingsEntity::FeatureOfInterest:
return
{
{Qgis::SensorThingsEntity::Observation},
{Qgis::SensorThingsEntity::Observation, Qgis::SensorThingsEntity::Datastream},
{Qgis::SensorThingsEntity::Observation, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Sensor},
{Qgis::SensorThingsEntity::Observation, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing},
{Qgis::SensorThingsEntity::Observation, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::HistoricalLocation },
{Qgis::SensorThingsEntity::Observation, Qgis::SensorThingsEntity::Datastream, Qgis::SensorThingsEntity::ObservedProperty}
Qgis::SensorThingsEntity::Observation
};
case Qgis::SensorThingsEntity::MultiDatastream:
return
{
{Qgis::SensorThingsEntity::Thing},
{ Qgis::SensorThingsEntity::Thing, Qgis::SensorThingsEntity::HistoricalLocation },
{Qgis::SensorThingsEntity::Sensor},
{Qgis::SensorThingsEntity::ObservedProperty},
{Qgis::SensorThingsEntity::Observation}
Qgis::SensorThingsEntity::Thing,
Qgis::SensorThingsEntity::Sensor,
Qgis::SensorThingsEntity::ObservedProperty,
Qgis::SensorThingsEntity::Observation
};
}
BUILTIN_UNREACHABLE
}
QString QgsSensorThingsUtils::asQueryString( const QList<QgsSensorThingsExpansionDefinition> &expansions )
{
QString res;
for ( int i = expansions.size() - 1; i >= 0 ; i-- )
{
const QgsSensorThingsExpansionDefinition &expansion = expansions.at( i );
if ( !expansion.isValid() )
continue;
res = expansion.asQueryString( res.isEmpty() ? QStringList() : QStringList{ res } );
}
return res;
}

View File

@ -22,6 +22,7 @@
class QgsFields;
class QgsFeedback;
class QgsRectangle;
class QgsSensorThingsExpansionDefinition;
/**
* \ingroup core
@ -71,6 +72,13 @@ class CORE_EXPORT QgsSensorThingsUtils
*/
static QString entityToSetString( Qgis::SensorThingsEntity type );
/**
* Returns the SensorThings properties which correspond to a specified entity \a type.
*
* \since QGIS 3.38
*/
static QStringList propertiesForEntityType( Qgis::SensorThingsEntity type );
/**
* Returns the fields which correspond to a specified entity \a type.
*/
@ -141,8 +149,163 @@ class CORE_EXPORT QgsSensorThingsUtils
*
* \since QGIS 3.38
*/
static QList< QList< Qgis::SensorThingsEntity > > expandableTargets( Qgis::SensorThingsEntity type );
static QList< Qgis::SensorThingsEntity > expandableTargets( Qgis::SensorThingsEntity type );
/**
* Returns a list of \a expansions as a valid SensorThings API query string, eg "$expand=Locations($orderby=id desc;$top=3;$expand=Datastreams($top=101))".
*
* \since QGIS 3.38
*/
static QString asQueryString( const QList< QgsSensorThingsExpansionDefinition > &expansions );
};
/**
* \ingroup core
* \brief Encapsulates information about how relationships in a SensorThings API service should be expanded.
*
* \since QGIS 3.38
*/
class CORE_EXPORT QgsSensorThingsExpansionDefinition
{
public:
/**
* Constructor for QgsSensorThingsExpansionDefinition, targeting the specified child entity type.
*/
QgsSensorThingsExpansionDefinition( Qgis::SensorThingsEntity childEntity = Qgis::SensorThingsEntity::Invalid,
const QString &orderBy = QString(),
Qt::SortOrder sortOrder = Qt::SortOrder::AscendingOrder,
int limit = QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT );
/**
* Returns TRUE if the definition is valid.
*/
bool isValid() const;
/**
* Returns the target child entity which should be expanded.
*
* \see setChildEntity()
*/
Qgis::SensorThingsEntity childEntity() const;
/**
* Sets the target child \a entity which should be expanded.
*
* \see childEntity()
*/
void setChildEntity( Qgis::SensorThingsEntity entity );
/**
* Returns the field name to order the expanded child entities by.
*
* \see sortOrder()
* \see setOrderBy()
*/
QString orderBy() const;
/**
* Sets the \a field name to order the expanded child entities by.
*
* \see orderBy()
* \see setSortOrder()
*/
void setOrderBy( const QString &field );
/**
* Returns the sort order for the expanded child entities.
*
* \see orderBy()
* \see setSortOrder()
*/
Qt::SortOrder sortOrder() const;
/**
* Sets the sort order for the expanded child entities.
*
* \see setOrderBy()
* \see sortOrder()
*/
void setSortOrder( Qt::SortOrder order );
/**
* Returns the limit on the number of child features to fetch.
*
* Returns -1 if no limit is defined.
*
* \see setLimit()
*/
int limit() const;
/**
* Sets the \a limit on the number of child features to fetch.
*
* Set to -1 if no limit is desired.
*
* \see limit()
*/
void setLimit( int limit );
/**
* Returns a string encapsulation of the expansion definition.
*
* \see fromString()
*/
QString toString() const;
/**
* Returns a QgsSensorThingsExpansionDefinition from a string representation.
*
* \see toString()
*/
static QgsSensorThingsExpansionDefinition fromString( const QString &string );
/**
* Returns the expansion as a valid SensorThings API query string, eg "$expand=Observations($orderby=phenomenonTime desc;$top=10)".
*
* Optionally a list of additional query options can be specified for the expansion.
*/
QString asQueryString( const QStringList &additionalOptions = QStringList() ) const;
bool operator==( const QgsSensorThingsExpansionDefinition &other ) const;
bool operator!=( const QgsSensorThingsExpansionDefinition &other ) const;
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
if ( !sipCpp->isValid() )
{
sipRes = PyUnicode_FromString( "<QgsSensorThingsExpansionDefinition: invalid>" );
return;
}
QString innerDefinition;
if ( !sipCpp->orderBy().isEmpty() )
{
innerDefinition = QStringLiteral( "by %1 (%2)" ).arg( sipCpp->orderBy(), sipCpp->sortOrder() == Qt::SortOrder::AscendingOrder ? QStringLiteral( "asc" ) : QStringLiteral( "desc" ) );
}
if ( sipCpp->limit() >= 0 )
{
if ( !innerDefinition.isEmpty() )
innerDefinition = QStringLiteral( "%1, limit %2" ).arg( innerDefinition ).arg( sipCpp->limit() );
else
innerDefinition = QStringLiteral( "limit %1" ).arg( sipCpp->limit() );
}
QString str = QStringLiteral( "<QgsSensorThingsExpansionDefinition: %1%2>" ).arg( qgsEnumValueToKey( sipCpp->childEntity() ), innerDefinition.isEmpty() ? QString() : ( QStringLiteral( " " ) + innerDefinition ) );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
private:
Qgis::SensorThingsEntity mChildEntity = Qgis::SensorThingsEntity::Invalid;
QString mOrderBy;
Qt::SortOrder mSortOrder = Qt::SortOrder::AscendingOrder;
int mLimit = QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT;
};
Q_DECLARE_METATYPE( QgsSensorThingsExpansionDefinition )
#endif // QGSSENSORTHINGSUTILS_H

View File

@ -86,6 +86,7 @@
#include "qgsinterval.h"
#include "qgsgpsconnection.h"
#include "qgssensorregistry.h"
#include "qgssensorthingsutils.h"
#include "gps/qgsgpsconnectionregistry.h"
#include "processing/qgsprocessingregistry.h"
@ -313,6 +314,7 @@ void QgsApplication::init( QString profileFolder )
qRegisterMetaType<QList<QNetworkReply::RawHeaderPair>>( "QList<QNetworkReply::RawHeaderPair>" );
qRegisterMetaType< QAuthenticator * >( "QAuthenticator*" );
qRegisterMetaType< QgsGpsInformation >( "QgsGpsInformation" );
qRegisterMetaType< QgsSensorThingsExpansionDefinition >( "QgsSensorThingsExpansionDefinition" );
} );
( void ) resolvePkgPath();

View File

@ -22,7 +22,8 @@ from qgis.core import (
QgsSettings,
QgsSensorThingsUtils,
QgsFeatureRequest,
QgsRectangle
QgsRectangle,
QgsSensorThingsExpansionDefinition
)
from qgis.testing import start_app, QgisTestCase
@ -230,6 +231,196 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
"MultiDatastreams",
)
def test_expansion_definition(self):
"""
Test QgsSensorThingsExpansionDefinition
"""
expansion = QgsSensorThingsExpansionDefinition()
self.assertFalse(expansion.isValid())
self.assertFalse(expansion.asQueryString())
# test getters/setters
expansion = QgsSensorThingsExpansionDefinition(Qgis.SensorThingsEntity.ObservedProperty)
self.assertTrue(expansion.isValid())
self.assertEqual(expansion.childEntity(), Qgis.SensorThingsEntity.ObservedProperty)
self.assertEqual(expansion.limit(), 100)
self.assertEqual(repr(expansion), '<QgsSensorThingsExpansionDefinition: ObservedProperty limit 100>')
self.assertEqual(expansion.asQueryString(), '$expand=ObservedProperties($top=100)')
self.assertEqual(expansion.asQueryString(['$expand=Locations($top=101)']),
'$expand=ObservedProperties($top=100;$expand=Locations($top=101))')
expansion.setChildEntity(Qgis.SensorThingsEntity.Location)
self.assertEqual(expansion.childEntity(),
Qgis.SensorThingsEntity.Location)
self.assertEqual(repr(expansion),
'<QgsSensorThingsExpansionDefinition: Location limit 100>')
self.assertEqual(expansion.asQueryString(),
'$expand=Locations($top=100)')
self.assertEqual(expansion.asQueryString(['$expand=Datastreams($top=101)']),
'$expand=Locations($top=100;$expand=Datastreams($top=101))')
expansion.setLimit(-1)
self.assertEqual(expansion.limit(), -1)
self.assertEqual(repr(expansion),
'<QgsSensorThingsExpansionDefinition: Location>')
self.assertEqual(expansion.asQueryString(),
'$expand=Locations')
self.assertEqual(expansion.asQueryString(['$expand=Datastreams($top=101)']),
'$expand=Locations($expand=Datastreams($top=101))')
expansion.setOrderBy('id')
self.assertEqual(expansion.orderBy(), 'id')
self.assertEqual(repr(expansion),
'<QgsSensorThingsExpansionDefinition: Location by id (asc)>')
self.assertEqual(expansion.asQueryString(),
'$expand=Locations($orderby=id)')
self.assertEqual(expansion.asQueryString(['$expand=Datastreams($top=101)']),
'$expand=Locations($orderby=id;$expand=Datastreams($top=101))')
expansion.setSortOrder(Qt.SortOrder.DescendingOrder)
self.assertEqual(expansion.sortOrder(), Qt.SortOrder.DescendingOrder)
self.assertEqual(repr(expansion),
'<QgsSensorThingsExpansionDefinition: Location by id (desc)>')
self.assertEqual(expansion.asQueryString(),
'$expand=Locations($orderby=id desc)')
self.assertEqual(expansion.asQueryString(['$expand=Datastreams($top=101)']),
'$expand=Locations($orderby=id desc;$expand=Datastreams($top=101))')
expansion.setLimit(3)
self.assertEqual(repr(expansion),
'<QgsSensorThingsExpansionDefinition: Location by id (desc), limit 3>')
self.assertEqual(expansion.asQueryString(),
'$expand=Locations($orderby=id desc;$top=3)')
self.assertEqual(expansion.asQueryString(['$expand=Datastreams($top=101)']),
'$expand=Locations($orderby=id desc;$top=3;$expand=Datastreams($top=101))')
# test equality
expansion1 = QgsSensorThingsExpansionDefinition(
Qgis.SensorThingsEntity.ObservedProperty)
expansion2 = QgsSensorThingsExpansionDefinition(
Qgis.SensorThingsEntity.ObservedProperty)
self.assertEqual(expansion1, expansion2)
self.assertNotEqual(expansion1, QgsSensorThingsExpansionDefinition())
self.assertNotEqual(QgsSensorThingsExpansionDefinition(), expansion2)
self.assertEqual(QgsSensorThingsExpansionDefinition(),
QgsSensorThingsExpansionDefinition())
expansion2.setChildEntity(Qgis.SensorThingsEntity.Sensor)
self.assertNotEqual(expansion1, expansion2)
expansion2.setChildEntity(Qgis.SensorThingsEntity.ObservedProperty)
self.assertEqual(expansion1, expansion2)
expansion2.setOrderBy('x')
self.assertNotEqual(expansion1, expansion2)
expansion2.setOrderBy('')
self.assertEqual(expansion1, expansion2)
expansion2.setSortOrder(Qt.SortOrder.DescendingOrder)
self.assertNotEqual(expansion1, expansion2)
expansion2.setSortOrder(Qt.SortOrder.AscendingOrder)
self.assertEqual(expansion1, expansion2)
expansion2.setLimit(33)
self.assertNotEqual(expansion1, expansion2)
expansion2.setLimit(100)
self.assertEqual(expansion1, expansion2)
# test to/from string
expansion = QgsSensorThingsExpansionDefinition()
string = expansion.toString()
self.assertFalse(string)
res = QgsSensorThingsExpansionDefinition.fromString(string)
self.assertFalse(res.isValid())
expansion.setChildEntity(Qgis.SensorThingsEntity.Sensor)
expansion.setLimit(-1)
string = expansion.toString()
res = QgsSensorThingsExpansionDefinition.fromString(string)
self.assertTrue(res.isValid())
self.assertEqual(res.childEntity(), Qgis.SensorThingsEntity.Sensor)
self.assertFalse(res.orderBy())
self.assertEqual(res.limit(), -1)
expansion.setOrderBy('test')
string = expansion.toString()
res = QgsSensorThingsExpansionDefinition.fromString(string)
self.assertTrue(res.isValid())
self.assertEqual(res.childEntity(), Qgis.SensorThingsEntity.Sensor)
self.assertEqual(res.orderBy(), 'test')
self.assertEqual(res.sortOrder(), Qt.SortOrder.AscendingOrder)
self.assertEqual(res.limit(), -1)
expansion.setSortOrder(Qt.SortOrder.DescendingOrder)
string = expansion.toString()
res = QgsSensorThingsExpansionDefinition.fromString(string)
self.assertTrue(res.isValid())
self.assertEqual(res.childEntity(), Qgis.SensorThingsEntity.Sensor)
self.assertEqual(res.orderBy(), 'test')
self.assertEqual(res.sortOrder(), Qt.SortOrder.DescendingOrder)
self.assertEqual(res.limit(), -1)
expansion.setLimit(5)
string = expansion.toString()
res = QgsSensorThingsExpansionDefinition.fromString(string)
self.assertTrue(res.isValid())
self.assertEqual(res.childEntity(), Qgis.SensorThingsEntity.Sensor)
self.assertEqual(res.orderBy(), 'test')
self.assertEqual(res.sortOrder(), Qt.SortOrder.DescendingOrder)
self.assertEqual(res.limit(), 5)
expansion.setOrderBy('')
string = expansion.toString()
res = QgsSensorThingsExpansionDefinition.fromString(string)
self.assertTrue(res.isValid())
self.assertEqual(res.childEntity(), Qgis.SensorThingsEntity.Sensor)
self.assertFalse(res.orderBy())
self.assertEqual(res.limit(), 5)
def test_expansions_as_query_string(self):
"""
Test constructing query strings from a list of expansions
"""
self.assertFalse(
QgsSensorThingsUtils.asQueryString([])
)
self.assertEqual(
QgsSensorThingsUtils.asQueryString([
QgsSensorThingsExpansionDefinition(Qgis.SensorThingsEntity.Location,
orderBy='id', limit=3)
]),
'$expand=Locations($orderby=id;$top=3)'
)
self.assertEqual(
QgsSensorThingsUtils.asQueryString([
QgsSensorThingsExpansionDefinition(),
QgsSensorThingsExpansionDefinition(Qgis.SensorThingsEntity.Location,
orderBy='id', limit=3)
]),
'$expand=Locations($orderby=id;$top=3)'
)
self.assertEqual(
QgsSensorThingsUtils.asQueryString([
QgsSensorThingsExpansionDefinition(Qgis.SensorThingsEntity.Location,
orderBy='id', limit=3),
QgsSensorThingsExpansionDefinition(
Qgis.SensorThingsEntity.Sensor,
orderBy='description', limit=30)
]),
'$expand=Locations($orderby=id;$top=3;$expand=Sensors($orderby=description;$top=30))'
)
self.assertEqual(
QgsSensorThingsUtils.asQueryString([
QgsSensorThingsExpansionDefinition(Qgis.SensorThingsEntity.Location,
orderBy='id', limit=3),
QgsSensorThingsExpansionDefinition(
Qgis.SensorThingsEntity.Sensor,
orderBy='description', limit=30),
QgsSensorThingsExpansionDefinition(
Qgis.SensorThingsEntity.Datastream,
orderBy='name', limit=-1)
]),
'$expand=Locations($orderby=id;$top=3;$expand=Sensors($orderby=description;$top=30;$expand=Datastreams($orderby=name)))'
)
def test_fields_for_expanded_entity(self):
"""
Test calculating fields for an expanded entity
@ -273,14 +464,9 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
"""
self.assertEqual(QgsSensorThingsUtils.expandableTargets(
Qgis.SensorThingsEntity.Thing),
[[Qgis.SensorThingsEntity.HistoricalLocation],
[Qgis.SensorThingsEntity.Datastream],
[Qgis.SensorThingsEntity.Datastream,
Qgis.SensorThingsEntity.Sensor],
[Qgis.SensorThingsEntity.Datastream,
Qgis.SensorThingsEntity.ObservedProperty],
[Qgis.SensorThingsEntity.Datastream,
Qgis.SensorThingsEntity.Observation]]
[Qgis.SensorThingsEntity.HistoricalLocation,
Qgis.SensorThingsEntity.Datastream
]
)
def test_filter_for_extent(self):
@ -3707,7 +3893,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
with open(
sanitize(endpoint,
"/Locations?$top=2&$count=false&$expand=Things/Datastreams&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"/Locations?$top=2&$count=false&$expand=Things($expand=Datastreams)&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"wt",
encoding="utf8",
) as f:
@ -3877,7 +4063,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
}
],
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2&$expand=Things/Datastreams&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2&$expand=Things($expand=Datastreams)&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"
}
""".replace(
"endpoint", "http://" + endpoint
@ -3886,7 +4072,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
with open(
sanitize(endpoint,
"/Locations?$top=2&$skip=2&$expand=Things/Datastreams&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"/Locations?$top=2&$skip=2&$expand=Things($expand=Datastreams)&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"wt",
encoding="utf8",
) as f:
@ -3975,7 +4161,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
vl = QgsVectorLayer(
f"url='http://{endpoint}' pageSize=2 type=MultiPolygonZ entity='Location' expandTo='Thing,Datastream'",
f"url='http://{endpoint}' pageSize=2 type=MultiPolygonZ entity='Location' expandTo='Thing;Datastream'",
"test",
"sensorthings",
)
@ -4162,7 +4348,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
with open(
sanitize(endpoint,
"/Locations?$top=2&$count=false&$expand=Things/Datastreams($top=1)&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"/Locations?$top=2&$count=false&$expand=Things($expand=Datastreams($top=1))&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"wt",
encoding="utf8",
) as f:
@ -4289,7 +4475,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
}
],
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2&$expand=Things/Datastreams($top=1)&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"
"@iot.nextLink": "endpoint/Locations?$top=2&$skip=2&$expand=Things($expand=Datastreams($top=1))&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"
}
""".replace(
"endpoint", "http://" + endpoint
@ -4298,7 +4484,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
with open(
sanitize(endpoint,
"/Locations?$top=2&$skip=2&$expand=Things/Datastreams($top=1)&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"/Locations?$top=2&$skip=2&$expand=Things($expand=Datastreams($top=1))&$filter=location/type eq 'Polygon' or location/geometry/type eq 'Polygon'"),
"wt",
encoding="utf8",
) as f:
@ -4371,7 +4557,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
)
vl = QgsVectorLayer(
f"url='http://{endpoint}' pageSize=2 type=MultiPolygonZ entity='Location' expansionLimit=1 expandTo='Thing,Datastream'",
f"url='http://{endpoint}' pageSize=2 type=MultiPolygonZ entity='Location' expandTo='Thing;Datastream:limit=1'",
"test",
"sensorthings",
)
@ -4603,7 +4789,7 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
},
)
uri = "url='https://sometest.com/api' type=MultiPolygonZ authcfg='abc' expandTo='Thing,Datastream' expansionLimit=30 entity='Location'"
uri = "url='https://sometest.com/api' type=MultiPolygonZ authcfg='abc' expandTo='Thing:orderby=description,asc:limit=5;Datastream:orderby=time,asc:limit=3' entity='Location'"
parts = QgsProviderRegistry.instance().decodeUri("sensorthings", uri)
self.assertEqual(
parts,
@ -4612,8 +4798,12 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
"entity": "Location",
"geometryType": "polygon",
"authcfg": "abc",
'expansionLimit': 30,
"expandTo": ['Thing', 'Datastream']
"expandTo": [QgsSensorThingsExpansionDefinition(
Qgis.SensorThingsEntity.Thing, orderBy='description',
limit=5),
QgsSensorThingsExpansionDefinition(
Qgis.SensorThingsEntity.Datastream,
orderBy='time', limit=3)],
},
)
@ -4714,13 +4904,13 @@ class TestPyQgsSensorThingsProvider(QgisTestCase): # , ProviderTestCase):
"authcfg": "aaaaa",
"entity": "location",
"geometryType": "polygon",
"expandTo": ["Thing", "Datastream"],
'expansionLimit': 30
"expandTo": [QgsSensorThingsExpansionDefinition(Qgis.SensorThingsEntity.Thing, orderBy='description', limit=5),
QgsSensorThingsExpansionDefinition(Qgis.SensorThingsEntity.Datastream, orderBy='time', limit=3)]
}
uri = QgsProviderRegistry.instance().encodeUri("sensorthings", parts)
self.assertEqual(
uri,
"authcfg=aaaaa type=MultiPolygonZ entity='Location' expandTo='Thing,Datastream' expansionLimit='30' url='http://blah.com'",
"authcfg=aaaaa type=MultiPolygonZ entity='Location' expandTo='Thing:orderby=description,asc:limit=5;Datastream:orderby=time,asc:limit=3' url='http://blah.com'",
)