Skip to content
Permalink
Browse files

[feature][temporal] Add option to "accumulate features"

Available when the mode is set to "Single Field with Date/Time"
(logically, it ONLY makes sense in this mode!), this setting
causes events to be visible whenever they have occurred before
or during the visible map temporal range.

Fixes #36321
  • Loading branch information
nyalldawson committed May 13, 2020
1 parent c162293 commit 8e0ea299886621c16c5490b98cd3498b0b7ed2fa
@@ -213,6 +213,32 @@ Units are specified by setDurationUnits()
.. seealso:: :py:func:`fixedDuration`

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

bool accumulateFeatures() const;
%Docstring
Returns ``True`` if features will be accumulated over time (i.e. all features which
occur before or within the map's temporal range should be rendered).

.. warning::

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

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

void setAccumulateFeatures( bool accumulate );
%Docstring
Sets whether features will be accumulated over time (i.e. all features which
occur before or within the map's temporal range should be rendered).

.. warning::

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

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

QString createFilterString( QgsVectorLayer *layer, const QgsDateTimeRange &range ) const;
@@ -174,6 +174,7 @@ bool QgsVectorLayerTemporalProperties::readXml( const QDomElement &element, cons
mDurationFieldName = temporalNode.attribute( QStringLiteral( "durationField" ) );
mDurationUnit = QgsUnitTypes::decodeTemporalUnit( temporalNode.attribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::TemporalMinutes ) ) );
mFixedDuration = temporalNode.attribute( QStringLiteral( "fixedDuration" ) ).toDouble();
mAccumulateFeatures = temporalNode.attribute( QStringLiteral( "accumulate" ), QStringLiteral( "0" ) ).toInt();

QDomNode rangeElement = temporalNode.namedItem( QStringLiteral( "fixedRange" ) );

@@ -204,6 +205,7 @@ QDomElement QgsVectorLayerTemporalProperties::writeXml( QDomElement &element, QD
temporalElement.setAttribute( QStringLiteral( "durationField" ), mDurationFieldName );
temporalElement.setAttribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( mDurationUnit ) );
temporalElement.setAttribute( QStringLiteral( "fixedDuration" ), qgsDoubleToString( mFixedDuration ) );
temporalElement.setAttribute( QStringLiteral( "accumulate" ), mAccumulateFeatures ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );

QDomElement rangeElement = document.createElement( QStringLiteral( "fixedRange" ) );

@@ -247,6 +249,16 @@ void QgsVectorLayerTemporalProperties::setDefaultsFromDataProviderTemporalCapabi
}
}

bool QgsVectorLayerTemporalProperties::accumulateFeatures() const
{
return mAccumulateFeatures;
}

void QgsVectorLayerTemporalProperties::setAccumulateFeatures( bool accumulateFeatures )
{
mAccumulateFeatures = accumulateFeatures;
}

double QgsVectorLayerTemporalProperties::fixedDuration() const
{
return mFixedDuration;
@@ -320,7 +332,13 @@ QString QgsVectorLayerTemporalProperties::createFilterString( QgsVectorLayer *,

case ModeFeatureDateTimeInstantFromField:
{
if ( qgsDoubleNear( mFixedDuration, 0.0 ) )
if ( mAccumulateFeatures )
{
return QStringLiteral( "(%1 %2 %3) OR %1 IS NULL" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
range.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
dateTimeExpressionLiteral( range.end() ) );
}
else if ( qgsDoubleNear( mFixedDuration, 0.0 ) )
{
return QStringLiteral( "(%1 %2 %3 AND %1 %4 %5) OR %1 IS NULL" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
range.includeBeginning() ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
@@ -215,6 +215,28 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
*/
void setFixedDuration( double duration );

/**
* Returns TRUE if features will be accumulated over time (i.e. all features which
* occur before or within the map's temporal range should be rendered).
*
* \warning This setting is only effective when mode() is
* QgsVectorLayerTemporalProperties::ModeFeatureDateTimeInstantFromField
*
* \see setAccumulateFeatures()
*/
bool accumulateFeatures() const;

/**
* Sets whether features will be accumulated over time (i.e. all features which
* occur before or within the map's temporal range should be rendered).
*
* \warning This setting is only effective when mode() is
* QgsVectorLayerTemporalProperties::ModeFeatureDateTimeInstantFromField
*
* \see accumulateFeatures()
*/
void setAccumulateFeatures( bool accumulate );

/**
* Creates a QGIS expression filter string for filtering features from \a layer
* to those within the specified time \a range.
@@ -254,6 +276,8 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP

double mFixedDuration = 0;

bool mAccumulateFeatures = false;

};

#endif // QGSVECTORLAYERTEMPORALPROPERTIES_H
@@ -102,6 +102,15 @@ QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget(
mDurationFieldComboBox->setField( properties->durationField() );
mDurationUnitsComboBox->setCurrentIndex( mDurationUnitsComboBox->findData( properties->durationUnits() ) );
mFixedDurationUnitsComboBox->setCurrentIndex( mDurationUnitsComboBox->findData( properties->durationUnits() ) );

mAccumulateCheckBox->setChecked( properties->accumulateFeatures() );
mFixedDurationUnitsComboBox->setEnabled( !mAccumulateCheckBox->isChecked() );
mFixedDurationSpinBox->setEnabled( !mAccumulateCheckBox->isChecked() );
connect( mAccumulateCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
{
mFixedDurationUnitsComboBox->setEnabled( !checked );
mFixedDurationSpinBox->setEnabled( !checked );
} );
}

void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
@@ -139,4 +148,5 @@ void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
properties->setEndField( mEndFieldComboBox->currentField() );
properties->setDurationField( mDurationFieldComboBox->currentField() );
properties->setFixedDuration( mFixedDurationSpinBox->value() );
properties->setAccumulateFeatures( mAccumulateCheckBox->isChecked() );
}
@@ -219,6 +219,16 @@ background: white;QgsCollapsibleGroupBoxBasic::title, QgsCollapsibleGroupBox::ti
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="mAccumulateCheckBox">
<property name="toolTip">
<string>If checked, then features will be shown whenever their defined temporal value falls within or before the map's temporal range</string>
</property>
<property name="text">
<string>Accumulate features over time</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
@@ -39,6 +39,7 @@ def testReadWrite(self):
props.setDurationField('duration')
props.setDurationUnits(QgsUnitTypes.TemporalWeeks)
props.setFixedDuration(5.6)
props.setAccumulateFeatures(True)

# save to xml
doc = QDomDocument("testdoc")
@@ -56,6 +57,7 @@ def testReadWrite(self):
self.assertEqual(props2.durationField(), props.durationField())
self.assertEqual(props2.durationUnits(), props.durationUnits())
self.assertEqual(props2.fixedDuration(), props.fixedDuration())
self.assertEqual(props2.accumulateFeatures(), props.accumulateFeatures())

def testModeFromProvider(self):
caps = QgsVectorDataProviderTemporalCapabilities()
@@ -178,6 +180,17 @@ def testSingleFieldMode(self):
self.assertEqual(props.createFilterString(layer, range),
'("start_field" >= make_datetime(2019,3,4,11,9,13) AND "start_field" <= make_datetime(2020,5,6,8,9,10)) OR "start_field" IS NULL')

# accumulate mode
props.setAccumulateFeatures(True)
range = QgsDateTimeRange(QDateTime(QDate(2019, 3, 4), QTime(11, 12, 13)), QDateTime(QDate(2020, 5, 6), QTime(8, 9, 10)))
self.assertEqual(props.createFilterString(layer, range), '("start_field" <= make_datetime(2020,5,6,8,9,10)) OR "start_field" IS NULL')

range = QgsDateTimeRange(QDateTime(QDate(2019, 3, 4), QTime(11, 12, 13)), QDateTime(QDate(2020, 5, 6), QTime(8, 9, 10)), includeBeginning=False)
self.assertEqual(props.createFilterString(layer, range), '("start_field" <= make_datetime(2020,5,6,8,9,10)) OR "start_field" IS NULL')

range = QgsDateTimeRange(QDateTime(QDate(2019, 3, 4), QTime(11, 12, 13)), QDateTime(QDate(2020, 5, 6), QTime(8, 9, 10)), includeEnd=False)
self.assertEqual(props.createFilterString(layer, range), '("start_field" < make_datetime(2020,5,6,8,9,10)) OR "start_field" IS NULL')

def testDualFieldMode(self):
layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=start_field:datetime&field=end_field:datetime", "test", "memory")
self.assertTrue(layer.isValid())

0 comments on commit 8e0ea29

Please sign in to comment.
You can’t perform that action at this time.