Skip to content

Commit

Permalink
[sensorthings] Support feature expansion
Browse files Browse the repository at this point in the history
This change allows SensorThings entities to be expanded to contain
their related child feature attributes, exposing the relational
SensorThings model as a traditional "flat" GIS-friendly table
structure.

Eg when selecting Location entities, you can now opt to expand
to "Things > Datastreams > Observations". This would result in
multiple "stacked" point location features, one corresponding
to each observation, with the attributes for each point feature
containing the location, thing, datastream and observation
attributes.

(Best used combined with some extent, feature limit, or custom
filter option, as this can otherwise result in very heavy
requests to the backend service!)

Fixes qgis#56805
  • Loading branch information
nyalldawson committed May 16, 2024
1 parent 66c864f commit 14e558e
Show file tree
Hide file tree
Showing 13 changed files with 1,373 additions and 210 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,26 @@ If ``plural`` is ``True`` then a plural string is returned (ie "Things" instead
Converts a string value corresponding to a SensorThings entity set to a :py:class:`Qgis`.SensorThingsEntity type.

Returns :py:class:`Qgis`.SensorThingsEntity.Invalid if the string could not be converted to a known entity set type.
%End

static QString entityToSetString( Qgis::SensorThingsEntity type );
%Docstring
Converts a SensorThings entity set to a SensorThings entity set string.

.. versionadded:: 3.38
%End

static QgsFields fieldsForEntityType( Qgis::SensorThingsEntity type );
%Docstring
Returns the fields which correspond to a specified entity ``type``.
%End

static QgsFields fieldsForExpandedEntityType( Qgis::SensorThingsEntity baseType, const QList< Qgis::SensorThingsEntity > &expandedTypes );
%Docstring
Returns the fields which correspond to a specified entity ``baseType``, expanded
using the specified list of ``expandedTypes``.

.. versionadded:: 3.38
%End

static QString geometryFieldForEntityType( Qgis::SensorThingsEntity type );
Expand Down Expand Up @@ -97,6 +112,13 @@ Returns a list of available geometry types for the server at the specified ``uri
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 );
%Docstring
Returns a list of permissible expand targets for a given base entity ``type``.

.. versionadded:: 3.38
%End

};
Expand Down
101 changes: 101 additions & 0 deletions python/PyQt6/core/conversions.sip
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,107 @@ which are not wrapped by PyQt:
%End
};

// adapted from the qpymultimedia_qlist.sip file from the PyQt6 sources
%MappedType QList<Qgis::SensorThingsEntity>
/TypeHintIn="Iterable[Qgis.SensorThingsEntity]",
TypeHintOut="List[Qgis.SensorThingsEntity]", TypeHintValue="[]"/
{
%TypeHeaderCode
#include "qgis.h"
%End

%ConvertFromTypeCode
PyObject *l = PyList_New(sipCpp->size());

if (!l)
return 0;

for (int i = 0; i < sipCpp->size(); ++i)
{
PyObject *eobj = sipConvertFromEnum(static_cast<int>(sipCpp->at(i)),
sipType_Qgis_SensorThingsEntity);

if (!eobj)
{
Py_DECREF(l);

return 0;
}

PyList_SetItem(l, i, eobj);
}

return l;
%End

%ConvertToTypeCode
PyObject *iter = PyObject_GetIter(sipPy);

if (!sipIsErr)
{
PyErr_Clear();
Py_XDECREF(iter);

return (iter && !PyBytes_Check(sipPy) && !PyUnicode_Check(sipPy));
}

if (!iter)
{
*sipIsErr = 1;

return 0;
}

QList<Qgis::SensorThingsEntity> *ql = new QList<Qgis::SensorThingsEntity>;

for (Py_ssize_t i = 0; ; ++i)
{
PyErr_Clear();
PyObject *itm = PyIter_Next(iter);

if (!itm)
{
if (PyErr_Occurred())
{
delete ql;
Py_DECREF(iter);
*sipIsErr = 1;

return 0;
}

break;
}

int v = sipConvertToEnum(itm, sipType_Qgis_SensorThingsEntity);

if (PyErr_Occurred())
{
PyErr_Format(PyExc_TypeError,
"index %zd has type '%s' but 'Qgis.SensorThingsEntity' is expected",
i, sipPyTypeName(Py_TYPE(itm)));

Py_DECREF(itm);
delete ql;
Py_DECREF(iter);
*sipIsErr = 1;

return 0;
}

ql->append(static_cast<Qgis::SensorThingsEntity>(v));

Py_DECREF(itm);
}

Py_DECREF(iter);

*sipCppPtr = ql;

return sipGetState(sipTransferObj);
%End
};

template <TYPE>
%MappedType QVector< QVector<TYPE> >
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,26 @@ If ``plural`` is ``True`` then a plural string is returned (ie "Things" instead
Converts a string value corresponding to a SensorThings entity set to a :py:class:`Qgis`.SensorThingsEntity type.

Returns :py:class:`Qgis`.SensorThingsEntity.Invalid if the string could not be converted to a known entity set type.
%End

static QString entityToSetString( Qgis::SensorThingsEntity type );
%Docstring
Converts a SensorThings entity set to a SensorThings entity set string.

.. versionadded:: 3.38
%End

static QgsFields fieldsForEntityType( Qgis::SensorThingsEntity type );
%Docstring
Returns the fields which correspond to a specified entity ``type``.
%End

static QgsFields fieldsForExpandedEntityType( Qgis::SensorThingsEntity baseType, const QList< Qgis::SensorThingsEntity > &expandedTypes );
%Docstring
Returns the fields which correspond to a specified entity ``baseType``, expanded
using the specified list of ``expandedTypes``.

.. versionadded:: 3.38
%End

static QString geometryFieldForEntityType( Qgis::SensorThingsEntity type );
Expand Down Expand Up @@ -97,6 +112,13 @@ Returns a list of available geometry types for the server at the specified ``uri
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 );
%Docstring
Returns a list of permissible expand targets for a given base entity ``type``.

.. versionadded:: 3.38
%End

};
Expand Down
101 changes: 101 additions & 0 deletions python/core/conversions.sip
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,107 @@ which are not wrapped by PyQt:
%End
};

// adapted from the qpymultimedia_qlist.sip file from the PyQt6 sources
%MappedType QList<Qgis::SensorThingsEntity>
/TypeHintIn="Iterable[Qgis.SensorThingsEntity]",
TypeHintOut="List[Qgis.SensorThingsEntity]", TypeHintValue="[]"/
{
%TypeHeaderCode
#include "qgis.h"
%End

%ConvertFromTypeCode
PyObject *l = PyList_New(sipCpp->size());

if (!l)
return 0;

for (int i = 0; i < sipCpp->size(); ++i)
{
PyObject *eobj = sipConvertFromEnum(static_cast<int>(sipCpp->at(i)),
sipType_Qgis_SensorThingsEntity);

if (!eobj)
{
Py_DECREF(l);

return 0;
}

PyList_SetItem(l, i, eobj);
}

return l;
%End

%ConvertToTypeCode
PyObject *iter = PyObject_GetIter(sipPy);

if (!sipIsErr)
{
PyErr_Clear();
Py_XDECREF(iter);

return (iter && !PyBytes_Check(sipPy) && !PyUnicode_Check(sipPy));
}

if (!iter)
{
*sipIsErr = 1;

return 0;
}

QList<Qgis::SensorThingsEntity> *ql = new QList<Qgis::SensorThingsEntity>;

for (Py_ssize_t i = 0; ; ++i)
{
PyErr_Clear();
PyObject *itm = PyIter_Next(iter);

if (!itm)
{
if (PyErr_Occurred())
{
delete ql;
Py_DECREF(iter);
*sipIsErr = 1;

return 0;
}

break;
}

int v = sipConvertToEnum(itm, sipType_Qgis_SensorThingsEntity);

if (PyErr_Occurred())
{
PyErr_Format(PyExc_TypeError,
"index %zd has type '%s' but 'Qgis.SensorThingsEntity' is expected",
i, sipPyTypeName(Py_TYPE(itm)));

Py_DECREF(itm);
delete ql;
Py_DECREF(iter);
*sipIsErr = 1;

return 0;
}

ql->append(static_cast<Qgis::SensorThingsEntity>(v));

Py_DECREF(itm);
}

Py_DECREF(iter);

*sipCppPtr = ql;

return sipGetState(sipTransferObj);
%End
};

template <TYPE>
%MappedType QVector< QVector<TYPE> >
{
Expand Down
8 changes: 8 additions & 0 deletions src/core/providers/sensorthings/qgssensorthingsprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ 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" ) );
if ( !expandToParam.isEmpty() )
components.insert( QStringLiteral( "expandTo" ), expandToParam.split( ',' ) );

bool ok = false;
const int maxPageSizeParam = dsUri.param( QStringLiteral( "pageSize" ) ).toInt( &ok );
if ( ok )
Expand Down Expand Up @@ -466,6 +470,10 @@ QString QgsSensorThingsProviderMetadata::encodeUri( const QVariantMap &parts ) c
qgsEnumValueToKey( entity ) );
}

const QStringList expandToParam = parts.value( QStringLiteral( "expandTo" ) ).toStringList();
if ( !expandToParam.isEmpty() )
dsUri.setParam( QStringLiteral( "expandTo" ), expandToParam.join( ',' ) );

bool ok = false;
const int maxPageSizeParam = parts.value( QStringLiteral( "pageSize" ) ).toInt( &ok );
if ( ok )
Expand Down

0 comments on commit 14e558e

Please sign in to comment.