Skip to content

Commit

Permalink
[feature][temporal] Add mode where start and end date are defined by …
Browse files Browse the repository at this point in the history
…expressions

Allows for handling any kind of corner case, e.g. start/end time in custom
string type fields, start/end time using numeric offsets from a reference
date, etc.

Fixes #36319
  • Loading branch information
nyalldawson committed May 13, 2020
1 parent 8e0ea29 commit f845189
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 4 deletions.
57 changes: 57 additions & 0 deletions python/core/auto_generated/qgsvectorlayertemporalproperties.sip.in
Expand Up @@ -40,6 +40,7 @@ The ``enabled`` argument specifies whether the temporal properties are initially
ModeFeatureDateTimeInstantFromField,
ModeFeatureDateTimeStartAndEndFromFields,
ModeFeatureDateTimeStartAndDurationFromFields,
ModeFeatureDateTimeStartAndEndFromExpressions,
ModeRedrawLayerOnly,
};

Expand Down Expand Up @@ -133,6 +134,62 @@ contains the end time for the feature's time spans.
.. seealso:: :py:func:`endField`

.. seealso:: :py:func:`setStartField`
%End

QString startExpression() const;
%Docstring
Returns the expression for the start time for the feature's time spans.

.. warning::

This setting is only effective when mode() is
QgsVectorLayerTemporalProperties.ModeFeatureDateTimeStartAndEndFromExpressions

.. seealso:: :py:func:`setStartExpression`

.. seealso:: :py:func:`endExpression`
%End

void setStartExpression( const QString &expression );
%Docstring
Sets the ``expression`` to use for the start time for the feature's time spans.

.. warning::

This setting is only effective when mode() is
QgsVectorLayerTemporalProperties.ModeFeatureDateTimeStartAndEndFromExpressions

.. seealso:: :py:func:`startExpression`

.. seealso:: :py:func:`setEndExpression`
%End

QString endExpression() const;
%Docstring
Returns the expression for the end time for the feature's time spans.

.. warning::

This setting is only effective when mode() is
QgsVectorLayerTemporalProperties.ModeFeatureDateTimeStartAndEndFromExpressions

.. seealso:: :py:func:`setEndExpression`

.. seealso:: :py:func:`startExpression`
%End

void setEndExpression( const QString &endExpression );
%Docstring
Sets the ``expression`` to use for the end time for the feature's time spans.

.. warning::

This setting is only effective when mode() is
QgsVectorLayerTemporalProperties.ModeFeatureDateTimeStartAndEndFromExpressions

.. seealso:: :py:func:`endExpression`

.. seealso:: :py:func:`setStartExpression`
%End

QString durationField() const;
Expand Down
Expand Up @@ -10,7 +10,7 @@



class QgsVectorLayerTemporalPropertiesWidget : QWidget
class QgsVectorLayerTemporalPropertiesWidget : QWidget, QgsExpressionContextGenerator
{
%Docstring
A widget for configuring the temporal properties for a vector layer.
Expand All @@ -33,6 +33,9 @@ Constructor for QgsVectorLayerTemporalPropertiesWidget.
Save widget temporal properties inputs.
%End

virtual QgsExpressionContext createExpressionContext() const;


};
/************************************************************************
* This file has been generated automatically from *
Expand Down
92 changes: 92 additions & 0 deletions src/core/qgsvectorlayertemporalproperties.cpp
Expand Up @@ -20,6 +20,7 @@
#include "qgsexpression.h"
#include "qgsvectorlayer.h"
#include "qgsfields.h"
#include "qgsexpressioncontextutils.h"

QgsVectorLayerTemporalProperties::QgsVectorLayerTemporalProperties( QObject *parent, bool enabled )
: QgsMapLayerTemporalProperties( parent, enabled )
Expand All @@ -40,6 +41,7 @@ bool QgsVectorLayerTemporalProperties::isVisibleInTemporalRange( const QgsDateTi
case ModeFeatureDateTimeStartAndEndFromFields:
case ModeRedrawLayerOnly:
case ModeFeatureDateTimeStartAndDurationFromFields:
case ModeFeatureDateTimeStartAndEndFromExpressions:
return true;
}
return true;
Expand Down Expand Up @@ -125,6 +127,46 @@ QgsDateTimeRange QgsVectorLayerTemporalProperties::calculateTemporalExtent( QgsM
break;
}

case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromExpressions:
{
QDateTime minTime;
QDateTime maxTime;

// no choice here but to loop through all features
QgsExpressionContext context;
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( vectorLayer ) );

QgsExpression startExpression( mStartExpression );
QgsExpression endExpression( mEndExpression );
startExpression.prepare( &context );
endExpression.prepare( &context );

QSet< QString > fields = startExpression.referencedColumns();
fields.unite( endExpression.referencedColumns() );
const bool needsGeom = startExpression.needsGeometry() || endExpression.needsGeometry();

QgsFeatureRequest req;
if ( !needsGeom )
req.setFlags( QgsFeatureRequest::NoGeometry );

req.setSubsetOfAttributes( fields, vectorLayer->fields() );

QgsFeature f;
QgsFeatureIterator it = vectorLayer->getFeatures( req );
while ( it.nextFeature( f ) )
{
context.setFeature( f );
const QDateTime start = startExpression.evaluate( &context ).toDateTime();
const QDateTime end = endExpression.evaluate( &context ).toDateTime();

if ( start.isValid() )
minTime = minTime.isValid() ? std::min( minTime, start ) : start;
if ( end.isValid() )
maxTime = maxTime.isValid() ? std::max( maxTime, end ) : end;
}
return QgsDateTimeRange( minTime, maxTime );
}

case QgsVectorLayerTemporalProperties::ModeRedrawLayerOnly:
break;
}
Expand Down Expand Up @@ -171,6 +213,8 @@ bool QgsVectorLayerTemporalProperties::readXml( const QDomElement &element, cons

mStartFieldName = temporalNode.attribute( QStringLiteral( "startField" ) );
mEndFieldName = temporalNode.attribute( QStringLiteral( "endField" ) );
mStartExpression = temporalNode.attribute( QStringLiteral( "startExpression" ) );
mEndExpression = temporalNode.attribute( QStringLiteral( "endExpression" ) );
mDurationFieldName = temporalNode.attribute( QStringLiteral( "durationField" ) );
mDurationUnit = QgsUnitTypes::decodeTemporalUnit( temporalNode.attribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::TemporalMinutes ) ) );
mFixedDuration = temporalNode.attribute( QStringLiteral( "fixedDuration" ) ).toDouble();
Expand Down Expand Up @@ -202,6 +246,8 @@ QDomElement QgsVectorLayerTemporalProperties::writeXml( QDomElement &element, QD

temporalElement.setAttribute( QStringLiteral( "startField" ), mStartFieldName );
temporalElement.setAttribute( QStringLiteral( "endField" ), mEndFieldName );
temporalElement.setAttribute( QStringLiteral( "startExpression" ), mStartExpression );
temporalElement.setAttribute( QStringLiteral( "endExpression" ), mEndExpression );
temporalElement.setAttribute( QStringLiteral( "durationField" ), mDurationFieldName );
temporalElement.setAttribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( mDurationUnit ) );
temporalElement.setAttribute( QStringLiteral( "fixedDuration" ), qgsDoubleToString( mFixedDuration ) );
Expand Down Expand Up @@ -249,6 +295,26 @@ void QgsVectorLayerTemporalProperties::setDefaultsFromDataProviderTemporalCapabi
}
}

QString QgsVectorLayerTemporalProperties::startExpression() const
{
return mStartExpression;
}

void QgsVectorLayerTemporalProperties::setStartExpression( const QString &startExpression )
{
mStartExpression = startExpression;
}

QString QgsVectorLayerTemporalProperties::endExpression() const
{
return mEndExpression;
}

void QgsVectorLayerTemporalProperties::setEndExpression( const QString &endExpression )
{
mEndExpression = endExpression;
}

bool QgsVectorLayerTemporalProperties::accumulateFeatures() const
{
return mAccumulateFeatures;
Expand Down Expand Up @@ -439,6 +505,32 @@ QString QgsVectorLayerTemporalProperties::createFilterString( QgsVectorLayer *,
}
break;
}

case ModeFeatureDateTimeStartAndEndFromExpressions:
{
if ( !mStartExpression.isEmpty() && !mEndExpression.isEmpty() )
{
return QStringLiteral( "((%1) %2 %3) AND ((%4) %5 %6)" ).arg( mStartExpression,
range.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
dateTimeExpressionLiteral( range.end() ),
mEndExpression,
range.includeBeginning() ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
dateTimeExpressionLiteral( range.begin() ) );
}
else if ( !mStartExpression.isEmpty() )
{
return QStringLiteral( "(%1) %2 %3" ).arg( mStartExpression,
range.includeBeginning() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
dateTimeExpressionLiteral( range.end() ) );
}
else if ( !mEndExpression.isEmpty() )
{
return QStringLiteral( "(%1) %2 %3" ).arg( mEndExpression,
range.includeBeginning() ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
dateTimeExpressionLiteral( range.begin() ) );
}
break;
}
}

return QString();
Expand Down
48 changes: 48 additions & 0 deletions src/core/qgsvectorlayertemporalproperties.h
Expand Up @@ -61,6 +61,7 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
ModeFeatureDateTimeInstantFromField, //!< Mode when features have a datetime instant taken from a single field
ModeFeatureDateTimeStartAndEndFromFields, //!< Mode when features have separate fields for start and end times
ModeFeatureDateTimeStartAndDurationFromFields, //!< Mode when features have a field for start time and a field for event duration
ModeFeatureDateTimeStartAndEndFromExpressions, //!< Mode when features use expressions for start and end times
ModeRedrawLayerOnly, //!< Redraw the layer when temporal range changes, but don't apply any filtering. Useful when symbology or rule based renderer expressions depend on the time range.
};

Expand Down Expand Up @@ -147,6 +148,50 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
*/
void setEndField( const QString &field );

/**
* Returns the expression for the start time for the feature's time spans.
*
* \warning This setting is only effective when mode() is
* QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromExpressions
*
* \see setStartExpression()
* \see endExpression()
*/
QString startExpression() const;

/**
* Sets the \a expression to use for the start time for the feature's time spans.
*
* \warning This setting is only effective when mode() is
* QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromExpressions
*
* \see startExpression()
* \see setEndExpression()
*/
void setStartExpression( const QString &expression );

/**
* Returns the expression for the end time for the feature's time spans.
*
* \warning This setting is only effective when mode() is
* QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromExpressions
*
* \see setEndExpression()
* \see startExpression()
*/
QString endExpression() const;

/**
* Sets the \a expression to use for the end time for the feature's time spans.
*
* \warning This setting is only effective when mode() is
* QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromExpressions
*
* \see endExpression()
* \see setStartExpression()
*/
void setEndExpression( const QString &endExpression );

/**
* Returns the name of the duration field, which
* contains the duration of the event.
Expand Down Expand Up @@ -278,6 +323,9 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP

bool mAccumulateFeatures = false;

QString mStartExpression;
QString mEndExpression;

};

#endif // QGSVECTORLAYERTEMPORALPROPERTIES_H
22 changes: 22 additions & 0 deletions src/gui/qgsvectorlayertemporalpropertieswidget.cpp
Expand Up @@ -23,6 +23,7 @@
#include "qgsvectorlayer.h"
#include "qgsvectorlayertemporalproperties.h"
#include "qgsstringutils.h"
#include "qgsexpressioncontextutils.h"

QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget( QWidget *parent, QgsVectorLayer *layer )
: QWidget( parent )
Expand All @@ -35,6 +36,7 @@ QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget(
mModeComboBox->addItem( tr( "Single Field with Date/Time" ), QgsVectorLayerTemporalProperties::ModeFeatureDateTimeInstantFromField );
mModeComboBox->addItem( tr( "Separate Fields for Start and End Date/Time" ), QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields );
mModeComboBox->addItem( tr( "Separate Fields for Start and Event Duration" ), QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndDurationFromFields );
mModeComboBox->addItem( tr( "Start and End Date/Time from Expressions" ), QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromExpressions );
mModeComboBox->addItem( tr( "Redraw Layer Only" ), QgsVectorLayerTemporalProperties::ModeRedrawLayerOnly );

const QgsVectorLayerTemporalProperties *properties = qobject_cast< QgsVectorLayerTemporalProperties * >( layer->temporalProperties() );
Expand Down Expand Up @@ -111,6 +113,16 @@ QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget(
mFixedDurationUnitsComboBox->setEnabled( !checked );
mFixedDurationSpinBox->setEnabled( !checked );
} );

mStartExpressionWidget->setAllowEmptyFieldName( true );
mEndExpressionWidget->setAllowEmptyFieldName( true );
mStartExpressionWidget->setLayer( layer );
mEndExpressionWidget->setLayer( layer );
mStartExpressionWidget->registerExpressionContextGenerator( this );
mEndExpressionWidget->registerExpressionContextGenerator( this );

mStartExpressionWidget->setField( properties->startExpression() );
mEndExpressionWidget->setField( properties->endExpression() );
}

void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
Expand All @@ -130,6 +142,7 @@ void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeInstantFromField:
case QgsVectorLayerTemporalProperties::ModeFixedTemporalRange:
case QgsVectorLayerTemporalProperties::ModeRedrawLayerOnly:
case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromExpressions:
properties->setStartField( mSingleFieldComboBox->currentField() );
properties->setDurationUnits( static_cast< QgsUnitTypes::TemporalUnit >( mFixedDurationUnitsComboBox->currentData().toInt() ) );
break;
Expand All @@ -149,4 +162,13 @@ void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
properties->setDurationField( mDurationFieldComboBox->currentField() );
properties->setFixedDuration( mFixedDurationSpinBox->value() );
properties->setAccumulateFeatures( mAccumulateCheckBox->isChecked() );
properties->setStartExpression( mStartExpressionWidget->currentField() );
properties->setEndExpression( mEndExpressionWidget->currentField() );
}

QgsExpressionContext QgsVectorLayerTemporalPropertiesWidget::createExpressionContext() const
{
QgsExpressionContext context;
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
return context;
}
5 changes: 4 additions & 1 deletion src/gui/qgsvectorlayertemporalpropertieswidget.h
Expand Up @@ -19,6 +19,7 @@
#define QGSVECTORLAYERTEMPORALPROPERTIESWIDGET_H

#include "ui_qgsvectorlayertemporalpropertieswidgetbase.h"
#include "qgsexpressioncontextgenerator.h"
#include "qgis_gui.h"

class QgsVectorLayer;
Expand All @@ -30,7 +31,7 @@ class QgsVectorLayer;
*
* \since QGIS 3.14
*/
class GUI_EXPORT QgsVectorLayerTemporalPropertiesWidget : public QWidget, private Ui::QgsVectorLayerTemporalPropertiesWidgetBase
class GUI_EXPORT QgsVectorLayerTemporalPropertiesWidget : public QWidget, public QgsExpressionContextGenerator, private Ui::QgsVectorLayerTemporalPropertiesWidgetBase
{
Q_OBJECT
public:
Expand All @@ -45,6 +46,8 @@ class GUI_EXPORT QgsVectorLayerTemporalPropertiesWidget : public QWidget, privat
*/
void saveTemporalProperties();

QgsExpressionContext createExpressionContext() const override;

private:

/**
Expand Down

0 comments on commit f845189

Please sign in to comment.