Skip to content

Commit 3dce1bf

Browse files
committedMay 13, 2020
[feature][temporal] Add "start field + duration field" mode for vector layers
Allows temporal filtering of layers which store a start time in one field and a duration in a different numeric field. Users are given a choice of temporal units for the duration (e.g. seconds, days, etc)
1 parent 60d87e7 commit 3dce1bf

7 files changed

+342
-7
lines changed
 

‎python/core/auto_generated/qgsvectorlayertemporalproperties.sip.in

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ The ``enabled`` argument specifies whether the temporal properties are initially
3939
ModeFixedTemporalRange,
4040
ModeFeatureDateTimeInstantFromField,
4141
ModeFeatureDateTimeStartAndEndFromFields,
42+
ModeFeatureDateTimeStartAndDurationFromFields,
4243
ModeRedrawLayerOnly,
4344
};
4445

@@ -132,6 +133,44 @@ contains the end time for the feature's time spans.
132133
.. seealso:: :py:func:`endField`
133134

134135
.. seealso:: :py:func:`setStartField`
136+
%End
137+
138+
QString durationField() const;
139+
%Docstring
140+
Returns the name of the duration field, which
141+
contains the duration of the event.
142+
143+
Units are specified by durationUnits()
144+
145+
.. seealso:: :py:func:`setDurationField`
146+
147+
.. seealso:: :py:func:`durationUnits`
148+
%End
149+
150+
void setDurationField( const QString &field );
151+
%Docstring
152+
Sets the name of the duration ``field``, which
153+
contains the duration of the event.
154+
155+
Units are specified by setDurationUnits()
156+
157+
.. seealso:: :py:func:`durationField`
158+
159+
.. seealso:: :py:func:`setDurationUnits`
160+
%End
161+
162+
QgsUnitTypes::TemporalUnit durationUnits() const;
163+
%Docstring
164+
Returns the units of the event's duration.
165+
166+
.. seealso:: :py:func:`setDurationUnits`
167+
%End
168+
169+
void setDurationUnits( QgsUnitTypes::TemporalUnit units );
170+
%Docstring
171+
Sets the ``units`` of the event's duration.
172+
173+
.. seealso:: :py:func:`durationUnits`
135174
%End
136175

137176
QString createFilterString( QgsVectorLayer *layer, const QgsDateTimeRange &range ) const;

‎src/core/expression/qgsexpressionfunction.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,12 +1159,12 @@ static QVariant fcnMakeDateTime( const QVariantList &values, const QgsExpression
11591159

11601160
static QVariant fcnMakeInterval( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
11611161
{
1162-
const int years = QgsExpressionUtils::getIntValue( values.at( 0 ), parent );
1163-
const int months = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
1164-
const int weeks = QgsExpressionUtils::getIntValue( values.at( 2 ), parent );
1165-
const int days = QgsExpressionUtils::getIntValue( values.at( 3 ), parent );
1166-
const int hours = QgsExpressionUtils::getIntValue( values.at( 4 ), parent );
1167-
const int minutes = QgsExpressionUtils::getIntValue( values.at( 5 ), parent );
1162+
const double years = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
1163+
const double months = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
1164+
const double weeks = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
1165+
const double days = QgsExpressionUtils::getDoubleValue( values.at( 3 ), parent );
1166+
const double hours = QgsExpressionUtils::getDoubleValue( values.at( 4 ), parent );
1167+
const double minutes = QgsExpressionUtils::getDoubleValue( values.at( 5 ), parent );
11681168
const double seconds = QgsExpressionUtils::getDoubleValue( values.at( 6 ), parent );
11691169

11701170
return QVariant::fromValue( QgsInterval( years, months, weeks, days, hours, minutes, seconds ) );

‎src/core/qgsvectorlayertemporalproperties.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ bool QgsVectorLayerTemporalProperties::isVisibleInTemporalRange( const QgsDateTi
3939
case ModeFeatureDateTimeInstantFromField:
4040
case ModeFeatureDateTimeStartAndEndFromFields:
4141
case ModeRedrawLayerOnly:
42+
case ModeFeatureDateTimeStartAndDurationFromFields:
4243
return true;
4344
}
4445
return true;
@@ -66,6 +67,38 @@ QgsDateTimeRange QgsVectorLayerTemporalProperties::calculateTemporalExtent( QgsM
6667
break;
6768
}
6869

70+
case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndDurationFromFields:
71+
{
72+
const int fieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
73+
const int durationFieldIndex = vectorLayer->fields().lookupField( mDurationFieldName );
74+
if ( fieldIndex >= 0 && durationFieldIndex >= 0 )
75+
{
76+
const QDateTime minTime = vectorLayer->minimumValue( fieldIndex ).toDateTime();
77+
// no choice here but to loop through all features to calculate max time :(
78+
79+
QgsFeature f;
80+
QgsFeatureIterator it = vectorLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() << durationFieldIndex << fieldIndex ) );
81+
QDateTime maxTime;
82+
while ( it.nextFeature( f ) )
83+
{
84+
const QDateTime start = f.attribute( fieldIndex ).toDateTime();
85+
if ( start.isValid() )
86+
{
87+
const QVariant durationValue = f.attribute( durationFieldIndex );
88+
if ( durationValue.isValid() )
89+
{
90+
const double duration = durationValue.toDouble();
91+
const QDateTime end = start.addMSecs( QgsInterval( duration, mDurationUnit ).seconds() * 1000.0 );
92+
if ( end.isValid() )
93+
maxTime = maxTime.isValid() ? std::max( maxTime, end ) : end;
94+
}
95+
}
96+
}
97+
return QgsDateTimeRange( minTime, maxTime );
98+
}
99+
break;
100+
}
101+
69102
case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields:
70103
{
71104
const int startFieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
@@ -136,6 +169,8 @@ bool QgsVectorLayerTemporalProperties::readXml( const QDomElement &element, cons
136169

137170
mStartFieldName = temporalNode.attribute( QStringLiteral( "startField" ) );
138171
mEndFieldName = temporalNode.attribute( QStringLiteral( "endField" ) );
172+
mDurationFieldName = temporalNode.attribute( QStringLiteral( "durationField" ) );
173+
mDurationUnit = QgsUnitTypes::decodeTemporalUnit( temporalNode.attribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::TemporalMinutes ) ) );
139174

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

@@ -163,6 +198,8 @@ QDomElement QgsVectorLayerTemporalProperties::writeXml( QDomElement &element, QD
163198

164199
temporalElement.setAttribute( QStringLiteral( "startField" ), mStartFieldName );
165200
temporalElement.setAttribute( QStringLiteral( "endField" ), mEndFieldName );
201+
temporalElement.setAttribute( QStringLiteral( "durationField" ), mDurationFieldName );
202+
temporalElement.setAttribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( mDurationUnit ) );
166203

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

@@ -226,6 +263,26 @@ void QgsVectorLayerTemporalProperties::setEndField( const QString &field )
226263
mEndFieldName = field;
227264
}
228265

266+
QString QgsVectorLayerTemporalProperties::durationField() const
267+
{
268+
return mDurationFieldName;
269+
}
270+
271+
void QgsVectorLayerTemporalProperties::setDurationField( const QString &field )
272+
{
273+
mDurationFieldName = field;
274+
}
275+
276+
QgsUnitTypes::TemporalUnit QgsVectorLayerTemporalProperties::durationUnits() const
277+
{
278+
return mDurationUnit;
279+
}
280+
281+
void QgsVectorLayerTemporalProperties::setDurationUnits( QgsUnitTypes::TemporalUnit units )
282+
{
283+
mDurationUnit = units;
284+
}
285+
229286
QString dateTimeExpressionLiteral( const QDateTime &datetime )
230287
{
231288
return QStringLiteral( "make_datetime(%1,%2,%3,%4,%5,%6)" ).arg( datetime.date().year() )
@@ -254,6 +311,64 @@ QString QgsVectorLayerTemporalProperties::createFilterString( QgsVectorLayer *,
254311
range.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
255312
dateTimeExpressionLiteral( range.end() ) );
256313

314+
case ModeFeatureDateTimeStartAndDurationFromFields:
315+
{
316+
QString intervalExpression;
317+
switch ( mDurationUnit )
318+
{
319+
case QgsUnitTypes::TemporalMilliseconds:
320+
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,0,%1/1000)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
321+
break;
322+
323+
case QgsUnitTypes::TemporalSeconds:
324+
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,0,%1)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
325+
break;
326+
327+
case QgsUnitTypes::TemporalMinutes:
328+
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,%1,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
329+
break;
330+
331+
case QgsUnitTypes::TemporalHours:
332+
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,%1,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
333+
break;
334+
335+
case QgsUnitTypes::TemporalDays:
336+
intervalExpression = QStringLiteral( "make_interval(0,0,0,%1,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
337+
break;
338+
339+
case QgsUnitTypes::TemporalWeeks:
340+
intervalExpression = QStringLiteral( "make_interval(0,0,%1,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
341+
break;
342+
343+
case QgsUnitTypes::TemporalMonths:
344+
intervalExpression = QStringLiteral( "make_interval(0,%1,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
345+
break;
346+
347+
case QgsUnitTypes::TemporalYears:
348+
intervalExpression = QStringLiteral( "make_interval(%1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
349+
break;
350+
351+
case QgsUnitTypes::TemporalDecades:
352+
intervalExpression = QStringLiteral( "make_interval(10 * %1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
353+
break;
354+
355+
case QgsUnitTypes::TemporalCenturies:
356+
intervalExpression = QStringLiteral( "make_interval(100 * %1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
357+
break;
358+
359+
case QgsUnitTypes::TemporalUnknownUnit:
360+
return QString();
361+
}
362+
return QStringLiteral( "(%1 %2 %3 OR %1 IS NULL) AND ((%1 + %4 %5 %6) OR %7 IS NULL)" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
363+
range.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
364+
dateTimeExpressionLiteral( range.end() ),
365+
intervalExpression,
366+
range.includeBeginning() ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
367+
dateTimeExpressionLiteral( range.begin() ),
368+
QgsExpression::quotedColumnRef( mDurationFieldName ) );
369+
break;
370+
}
371+
257372
case ModeFeatureDateTimeStartAndEndFromFields:
258373
{
259374
if ( !mStartFieldName.isEmpty() && !mEndFieldName.isEmpty() )

‎src/core/qgsvectorlayertemporalproperties.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "qgsrange.h"
2525
#include "qgsmaplayertemporalproperties.h"
2626
#include "qgsrasterdataprovidertemporalcapabilities.h"
27+
#include "qgsunittypes.h"
2728

2829
class QgsVectorLayer;
2930
class QgsFields;
@@ -59,6 +60,7 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
5960
ModeFixedTemporalRange = 0, //!< Mode when temporal properties have fixed start and end datetimes.
6061
ModeFeatureDateTimeInstantFromField, //!< Mode when features have a datetime instant taken from a single field
6162
ModeFeatureDateTimeStartAndEndFromFields, //!< Mode when features have separate fields for start and end times
63+
ModeFeatureDateTimeStartAndDurationFromFields, //!< Mode when features have a field for start time and a field for event duration
6264
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.
6365
};
6466

@@ -145,6 +147,42 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
145147
*/
146148
void setEndField( const QString &field );
147149

150+
/**
151+
* Returns the name of the duration field, which
152+
* contains the duration of the event.
153+
*
154+
* Units are specified by durationUnits()
155+
*
156+
* \see setDurationField()
157+
* \see durationUnits()
158+
*/
159+
QString durationField() const;
160+
161+
/**
162+
* Sets the name of the duration \a field, which
163+
* contains the duration of the event.
164+
*
165+
* Units are specified by setDurationUnits()
166+
*
167+
* \see durationField()
168+
* \see setDurationUnits()
169+
*/
170+
void setDurationField( const QString &field );
171+
172+
/**
173+
* Returns the units of the event's duration.
174+
*
175+
* \see setDurationUnits()
176+
*/
177+
QgsUnitTypes::TemporalUnit durationUnits() const;
178+
179+
/**
180+
* Sets the \a units of the event's duration.
181+
*
182+
* \see durationUnits()
183+
*/
184+
void setDurationUnits( QgsUnitTypes::TemporalUnit units );
185+
148186
/**
149187
* Creates a QGIS expression filter string for filtering features from \a layer
150188
* to those within the specified time \a range.
@@ -179,6 +217,8 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
179217

180218
QString mStartFieldName;
181219
QString mEndFieldName;
220+
QString mDurationFieldName;
221+
QgsUnitTypes::TemporalUnit mDurationUnit = QgsUnitTypes::TemporalMinutes;
182222

183223
};
184224

‎src/gui/qgsvectorlayertemporalpropertieswidget.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget(
3434
mModeComboBox->addItem( tr( "Fixed Time Range" ), QgsVectorLayerTemporalProperties::ModeFixedTemporalRange );
3535
mModeComboBox->addItem( tr( "Single Field with Date/Time" ), QgsVectorLayerTemporalProperties::ModeFeatureDateTimeInstantFromField );
3636
mModeComboBox->addItem( tr( "Separate Fields for Start and End Date/Time" ), QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields );
37+
mModeComboBox->addItem( tr( "Separate Fields for Start and Event Duration" ), QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndDurationFromFields );
3738
mModeComboBox->addItem( tr( "Redraw Layer Only" ), QgsVectorLayerTemporalProperties::ModeRedrawLayerOnly );
3839

3940
const QgsVectorLayerTemporalProperties *properties = qobject_cast< QgsVectorLayerTemporalProperties * >( layer->temporalProperties() );
@@ -54,21 +55,45 @@ QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget(
5455
mSingleFieldComboBox->setLayer( layer );
5556
mStartFieldComboBox->setLayer( layer );
5657
mEndFieldComboBox->setLayer( layer );
58+
mDurationStartFieldComboBox->setLayer( layer );
59+
mDurationFieldComboBox->setLayer( layer );
5760
mSingleFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
5861
mStartFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
5962
mStartFieldComboBox->setAllowEmptyFieldName( true );
6063
mEndFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
6164
mEndFieldComboBox->setAllowEmptyFieldName( true );
65+
mDurationStartFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
66+
mDurationFieldComboBox->setFilters( QgsFieldProxyModel::Numeric );
67+
68+
for ( QgsUnitTypes::TemporalUnit u :
69+
{
70+
QgsUnitTypes::TemporalMilliseconds,
71+
QgsUnitTypes::TemporalSeconds,
72+
QgsUnitTypes::TemporalMinutes,
73+
QgsUnitTypes::TemporalHours,
74+
QgsUnitTypes::TemporalDays,
75+
QgsUnitTypes::TemporalWeeks,
76+
QgsUnitTypes::TemporalMonths,
77+
QgsUnitTypes::TemporalYears,
78+
QgsUnitTypes::TemporalDecades,
79+
QgsUnitTypes::TemporalCenturies
80+
} )
81+
{
82+
mDurationUnitsComboBox->addItem( QgsUnitTypes::toString( u ), u );
83+
}
6284

6385
if ( !properties->startField().isEmpty() )
6486
{
6587
mSingleFieldComboBox->setField( properties->startField() );
6688
mStartFieldComboBox->setField( properties->startField() );
89+
mDurationStartFieldComboBox->setField( properties->startField() );
6790
}
6891
if ( !properties->endField().isEmpty() )
6992
{
7093
mEndFieldComboBox->setField( properties->endField() );
7194
}
95+
mDurationFieldComboBox->setField( properties->durationField() );
96+
mDurationUnitsComboBox->setCurrentIndex( mDurationUnitsComboBox->findData( properties->durationUnits() ) );
7297
}
7398

7499
void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
@@ -94,7 +119,13 @@ void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
94119
case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields:
95120
properties->setStartField( mStartFieldComboBox->currentField() );
96121
break;
122+
123+
case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndDurationFromFields:
124+
properties->setStartField( mDurationStartFieldComboBox->currentField() );
125+
break;
97126
}
98127

99128
properties->setEndField( mEndFieldComboBox->currentField() );
129+
properties->setDurationField( mDurationFieldComboBox->currentField() );
130+
properties->setDurationUnits( static_cast< QgsUnitTypes::TemporalUnit >( mDurationUnitsComboBox->currentData().toInt() ) );
100131
}

‎src/ui/qgsvectorlayertemporalpropertieswidgetbase.ui

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,53 @@ background: white;QgsCollapsibleGroupBoxBasic::title, QgsCollapsibleGroupBox::ti
211211
</item>
212212
</layout>
213213
</widget>
214+
<widget class="QWidget" name="page_5">
215+
<layout class="QGridLayout" name="gridLayout_7">
216+
<item row="1" column="0">
217+
<widget class="QLabel" name="label_9">
218+
<property name="text">
219+
<string>Event duration field</string>
220+
</property>
221+
</widget>
222+
</item>
223+
<item row="1" column="1">
224+
<widget class="QgsFieldComboBox" name="mDurationFieldComboBox"/>
225+
</item>
226+
<item row="3" column="0">
227+
<spacer name="verticalSpacer_5">
228+
<property name="orientation">
229+
<enum>Qt::Vertical</enum>
230+
</property>
231+
<property name="sizeHint" stdset="0">
232+
<size>
233+
<width>20</width>
234+
<height>40</height>
235+
</size>
236+
</property>
237+
</spacer>
238+
</item>
239+
<item row="0" column="0">
240+
<widget class="QLabel" name="label_8">
241+
<property name="text">
242+
<string>Start field</string>
243+
</property>
244+
</widget>
245+
</item>
246+
<item row="0" column="1">
247+
<widget class="QgsFieldComboBox" name="mDurationStartFieldComboBox"/>
248+
</item>
249+
<item row="2" column="0">
250+
<widget class="QLabel" name="label_10">
251+
<property name="text">
252+
<string>Event duration units</string>
253+
</property>
254+
</widget>
255+
</item>
256+
<item row="2" column="1">
257+
<widget class="QComboBox" name="mDurationUnitsComboBox"/>
258+
</item>
259+
</layout>
260+
</widget>
214261
<widget class="QWidget" name="page_4">
215262
<layout class="QVBoxLayout" name="verticalLayout">
216263
<item>

‎tests/src/python/test_qgsvectorlayertemporalproperties.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
QgsVectorLayerTemporalProperties,
1717
QgsReadWriteContext,
1818
QgsVectorLayer,
19-
QgsVectorDataProviderTemporalCapabilities)
19+
QgsVectorDataProviderTemporalCapabilities,
20+
QgsUnitTypes)
2021
from qgis.PyQt.QtCore import (QDateTime,
2122
QDate,
2223
QTime,
@@ -35,6 +36,8 @@ def testReadWrite(self):
3536
props.setFixedTemporalRange(QgsDateTimeRange(QDateTime(QDate(2019, 3, 4), QTime(11, 12, 13)), QDateTime(QDate(2020, 5, 6), QTime(8, 9, 10))))
3637
props.setStartField('start')
3738
props.setEndField('end')
39+
props.setDurationField('duration')
40+
props.setDurationUnits(QgsUnitTypes.TemporalWeeks)
3841

3942
# save to xml
4043
doc = QDomDocument("testdoc")
@@ -49,6 +52,8 @@ def testReadWrite(self):
4952
self.assertEqual(props2.fixedTemporalRange(), props.fixedTemporalRange())
5053
self.assertEqual(props2.startField(), props.startField())
5154
self.assertEqual(props2.endField(), props.endField())
55+
self.assertEqual(props2.durationField(), props.durationField())
56+
self.assertEqual(props2.durationUnits(), props.durationUnits())
5257

5358
def testModeFromProvider(self):
5459
caps = QgsVectorDataProviderTemporalCapabilities()
@@ -196,6 +201,64 @@ def testDualFieldMode(self):
196201
range = QgsDateTimeRange(QDateTime(QDate(2019, 3, 4), QTime(11, 12, 13)), QDateTime(QDate(2020, 5, 6), QTime(8, 9, 10)), includeEnd=False)
197202
self.assertEqual(props.createFilterString(layer, range), '"end_field" >= make_datetime(2019,3,4,11,12,13) OR "end_field" IS NULL')
198203

204+
def testStartAndDurationMode(self):
205+
layer = QgsVectorLayer(
206+
"Point?field=fldtxt:string&field=fldint:integer&field=start_field:datetime&field=duration:double",
207+
"test", "memory")
208+
self.assertTrue(layer.isValid())
209+
self.assertEqual(layer.fields()[2].type(), QVariant.DateTime)
210+
self.assertEqual(layer.fields()[3].type(), QVariant.Double)
211+
212+
range = QgsDateTimeRange(QDateTime(QDate(2019, 3, 4), QTime(11, 12, 13)),
213+
QDateTime(QDate(2020, 5, 6), QTime(8, 9, 10)))
214+
215+
props = QgsVectorLayerTemporalProperties(enabled=False)
216+
props.setMode(QgsVectorLayerTemporalProperties.ModeFeatureDateTimeStartAndDurationFromFields)
217+
props.setStartField('start_field')
218+
props.setDurationField('duration')
219+
props.setDurationUnits(QgsUnitTypes.TemporalMilliseconds)
220+
self.assertFalse(props.createFilterString(layer, range))
221+
222+
props.setIsActive(True)
223+
self.assertEqual(props.createFilterString(layer, range),
224+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(0,0,0,0,0,0,"duration"/1000) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
225+
226+
props.setDurationUnits(QgsUnitTypes.TemporalSeconds)
227+
self.assertEqual(props.createFilterString(layer, range),
228+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(0,0,0,0,0,0,"duration") >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
229+
230+
props.setDurationUnits(QgsUnitTypes.TemporalMinutes)
231+
self.assertEqual(props.createFilterString(layer, range),
232+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(0,0,0,0,0,"duration",0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
233+
234+
props.setDurationUnits(QgsUnitTypes.TemporalHours)
235+
self.assertEqual(props.createFilterString(layer, range),
236+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(0,0,0,0,"duration",0,0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
237+
238+
props.setDurationUnits(QgsUnitTypes.TemporalDays)
239+
self.assertEqual(props.createFilterString(layer, range),
240+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(0,0,0,"duration",0,0,0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
241+
242+
props.setDurationUnits(QgsUnitTypes.TemporalWeeks)
243+
self.assertEqual(props.createFilterString(layer, range),
244+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(0,0,"duration",0,0,0,0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
245+
246+
props.setDurationUnits(QgsUnitTypes.TemporalMonths)
247+
self.assertEqual(props.createFilterString(layer, range),
248+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(0,"duration",0,0,0,0,0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
249+
250+
props.setDurationUnits(QgsUnitTypes.TemporalYears)
251+
self.assertEqual(props.createFilterString(layer, range),
252+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval("duration",0,0,0,0,0,0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
253+
254+
props.setDurationUnits(QgsUnitTypes.TemporalDecades)
255+
self.assertEqual(props.createFilterString(layer, range),
256+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(10 * "duration",0,0,0,0,0,0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
257+
258+
props.setDurationUnits(QgsUnitTypes.TemporalCenturies)
259+
self.assertEqual(props.createFilterString(layer, range),
260+
'("start_field" <= make_datetime(2020,5,6,8,9,10) OR "start_field" IS NULL) AND (("start_field" + make_interval(100 * "duration",0,0,0,0,0,0) >= make_datetime(2019,3,4,11,12,13)) OR "duration" IS NULL)')
261+
199262

200263
if __name__ == '__main__':
201264
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.