Skip to content

Commit

Permalink
[feature][temporal] Add "start field + duration field" mode for vecto…
Browse files Browse the repository at this point in the history
…r 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)
  • Loading branch information
nyalldawson committed May 13, 2020
1 parent 60d87e7 commit 3dce1bf
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 7 deletions.
39 changes: 39 additions & 0 deletions python/core/auto_generated/qgsvectorlayertemporalproperties.sip.in
Expand Up @@ -39,6 +39,7 @@ The ``enabled`` argument specifies whether the temporal properties are initially
ModeFixedTemporalRange,
ModeFeatureDateTimeInstantFromField,
ModeFeatureDateTimeStartAndEndFromFields,
ModeFeatureDateTimeStartAndDurationFromFields,
ModeRedrawLayerOnly,
};

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

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

QString durationField() const;
%Docstring
Returns the name of the duration field, which
contains the duration of the event.

Units are specified by durationUnits()

.. seealso:: :py:func:`setDurationField`

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

void setDurationField( const QString &field );
%Docstring
Sets the name of the duration ``field``, which
contains the duration of the event.

Units are specified by setDurationUnits()

.. seealso:: :py:func:`durationField`

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

QgsUnitTypes::TemporalUnit durationUnits() const;
%Docstring
Returns the units of the event's duration.

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

void setDurationUnits( QgsUnitTypes::TemporalUnit units );
%Docstring
Sets the ``units`` of the event's duration.

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

QString createFilterString( QgsVectorLayer *layer, const QgsDateTimeRange &range ) const;
Expand Down
12 changes: 6 additions & 6 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -1159,12 +1159,12 @@ static QVariant fcnMakeDateTime( const QVariantList &values, const QgsExpression

static QVariant fcnMakeInterval( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const int years = QgsExpressionUtils::getIntValue( values.at( 0 ), parent );
const int months = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
const int weeks = QgsExpressionUtils::getIntValue( values.at( 2 ), parent );
const int days = QgsExpressionUtils::getIntValue( values.at( 3 ), parent );
const int hours = QgsExpressionUtils::getIntValue( values.at( 4 ), parent );
const int minutes = QgsExpressionUtils::getIntValue( values.at( 5 ), parent );
const double years = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
const double months = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
const double weeks = QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent );
const double days = QgsExpressionUtils::getDoubleValue( values.at( 3 ), parent );
const double hours = QgsExpressionUtils::getDoubleValue( values.at( 4 ), parent );
const double minutes = QgsExpressionUtils::getDoubleValue( values.at( 5 ), parent );
const double seconds = QgsExpressionUtils::getDoubleValue( values.at( 6 ), parent );

return QVariant::fromValue( QgsInterval( years, months, weeks, days, hours, minutes, seconds ) );
Expand Down
115 changes: 115 additions & 0 deletions src/core/qgsvectorlayertemporalproperties.cpp
Expand Up @@ -39,6 +39,7 @@ bool QgsVectorLayerTemporalProperties::isVisibleInTemporalRange( const QgsDateTi
case ModeFeatureDateTimeInstantFromField:
case ModeFeatureDateTimeStartAndEndFromFields:
case ModeRedrawLayerOnly:
case ModeFeatureDateTimeStartAndDurationFromFields:
return true;
}
return true;
Expand Down Expand Up @@ -66,6 +67,38 @@ QgsDateTimeRange QgsVectorLayerTemporalProperties::calculateTemporalExtent( QgsM
break;
}

case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndDurationFromFields:
{
const int fieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
const int durationFieldIndex = vectorLayer->fields().lookupField( mDurationFieldName );
if ( fieldIndex >= 0 && durationFieldIndex >= 0 )
{
const QDateTime minTime = vectorLayer->minimumValue( fieldIndex ).toDateTime();
// no choice here but to loop through all features to calculate max time :(

QgsFeature f;
QgsFeatureIterator it = vectorLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() << durationFieldIndex << fieldIndex ) );
QDateTime maxTime;
while ( it.nextFeature( f ) )
{
const QDateTime start = f.attribute( fieldIndex ).toDateTime();
if ( start.isValid() )
{
const QVariant durationValue = f.attribute( durationFieldIndex );
if ( durationValue.isValid() )
{
const double duration = durationValue.toDouble();
const QDateTime end = start.addMSecs( QgsInterval( duration, mDurationUnit ).seconds() * 1000.0 );
if ( end.isValid() )
maxTime = maxTime.isValid() ? std::max( maxTime, end ) : end;
}
}
}
return QgsDateTimeRange( minTime, maxTime );
}
break;
}

case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields:
{
const int startFieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
Expand Down Expand Up @@ -136,6 +169,8 @@ bool QgsVectorLayerTemporalProperties::readXml( const QDomElement &element, cons

mStartFieldName = temporalNode.attribute( QStringLiteral( "startField" ) );
mEndFieldName = temporalNode.attribute( QStringLiteral( "endField" ) );
mDurationFieldName = temporalNode.attribute( QStringLiteral( "durationField" ) );
mDurationUnit = QgsUnitTypes::decodeTemporalUnit( temporalNode.attribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::TemporalMinutes ) ) );

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

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

temporalElement.setAttribute( QStringLiteral( "startField" ), mStartFieldName );
temporalElement.setAttribute( QStringLiteral( "endField" ), mEndFieldName );
temporalElement.setAttribute( QStringLiteral( "durationField" ), mDurationFieldName );
temporalElement.setAttribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( mDurationUnit ) );

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

Expand Down Expand Up @@ -226,6 +263,26 @@ void QgsVectorLayerTemporalProperties::setEndField( const QString &field )
mEndFieldName = field;
}

QString QgsVectorLayerTemporalProperties::durationField() const
{
return mDurationFieldName;
}

void QgsVectorLayerTemporalProperties::setDurationField( const QString &field )
{
mDurationFieldName = field;
}

QgsUnitTypes::TemporalUnit QgsVectorLayerTemporalProperties::durationUnits() const
{
return mDurationUnit;
}

void QgsVectorLayerTemporalProperties::setDurationUnits( QgsUnitTypes::TemporalUnit units )
{
mDurationUnit = units;
}

QString dateTimeExpressionLiteral( const QDateTime &datetime )
{
return QStringLiteral( "make_datetime(%1,%2,%3,%4,%5,%6)" ).arg( datetime.date().year() )
Expand Down Expand Up @@ -254,6 +311,64 @@ QString QgsVectorLayerTemporalProperties::createFilterString( QgsVectorLayer *,
range.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
dateTimeExpressionLiteral( range.end() ) );

case ModeFeatureDateTimeStartAndDurationFromFields:
{
QString intervalExpression;
switch ( mDurationUnit )
{
case QgsUnitTypes::TemporalMilliseconds:
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,0,%1/1000)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalSeconds:
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,0,%1)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalMinutes:
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,%1,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalHours:
intervalExpression = QStringLiteral( "make_interval(0,0,0,0,%1,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalDays:
intervalExpression = QStringLiteral( "make_interval(0,0,0,%1,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalWeeks:
intervalExpression = QStringLiteral( "make_interval(0,0,%1,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalMonths:
intervalExpression = QStringLiteral( "make_interval(0,%1,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalYears:
intervalExpression = QStringLiteral( "make_interval(%1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalDecades:
intervalExpression = QStringLiteral( "make_interval(10 * %1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalCenturies:
intervalExpression = QStringLiteral( "make_interval(100 * %1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;

case QgsUnitTypes::TemporalUnknownUnit:
return QString();
}
return QStringLiteral( "(%1 %2 %3 OR %1 IS NULL) AND ((%1 + %4 %5 %6) OR %7 IS NULL)" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
range.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
dateTimeExpressionLiteral( range.end() ),
intervalExpression,
range.includeBeginning() ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
dateTimeExpressionLiteral( range.begin() ),
QgsExpression::quotedColumnRef( mDurationFieldName ) );
break;
}

case ModeFeatureDateTimeStartAndEndFromFields:
{
if ( !mStartFieldName.isEmpty() && !mEndFieldName.isEmpty() )
Expand Down
40 changes: 40 additions & 0 deletions src/core/qgsvectorlayertemporalproperties.h
Expand Up @@ -24,6 +24,7 @@
#include "qgsrange.h"
#include "qgsmaplayertemporalproperties.h"
#include "qgsrasterdataprovidertemporalcapabilities.h"
#include "qgsunittypes.h"

class QgsVectorLayer;
class QgsFields;
Expand Down Expand Up @@ -59,6 +60,7 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
ModeFixedTemporalRange = 0, //!< Mode when temporal properties have fixed start and end datetimes.
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
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 @@ -145,6 +147,42 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
*/
void setEndField( const QString &field );

/**
* Returns the name of the duration field, which
* contains the duration of the event.
*
* Units are specified by durationUnits()
*
* \see setDurationField()
* \see durationUnits()
*/
QString durationField() const;

/**
* Sets the name of the duration \a field, which
* contains the duration of the event.
*
* Units are specified by setDurationUnits()
*
* \see durationField()
* \see setDurationUnits()
*/
void setDurationField( const QString &field );

/**
* Returns the units of the event's duration.
*
* \see setDurationUnits()
*/
QgsUnitTypes::TemporalUnit durationUnits() const;

/**
* Sets the \a units of the event's duration.
*
* \see durationUnits()
*/
void setDurationUnits( QgsUnitTypes::TemporalUnit units );

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

QString mStartFieldName;
QString mEndFieldName;
QString mDurationFieldName;
QgsUnitTypes::TemporalUnit mDurationUnit = QgsUnitTypes::TemporalMinutes;

};

Expand Down
31 changes: 31 additions & 0 deletions src/gui/qgsvectorlayertemporalpropertieswidget.cpp
Expand Up @@ -34,6 +34,7 @@ QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget(
mModeComboBox->addItem( tr( "Fixed Time Range" ), QgsVectorLayerTemporalProperties::ModeFixedTemporalRange );
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( "Redraw Layer Only" ), QgsVectorLayerTemporalProperties::ModeRedrawLayerOnly );

const QgsVectorLayerTemporalProperties *properties = qobject_cast< QgsVectorLayerTemporalProperties * >( layer->temporalProperties() );
Expand All @@ -54,21 +55,45 @@ QgsVectorLayerTemporalPropertiesWidget::QgsVectorLayerTemporalPropertiesWidget(
mSingleFieldComboBox->setLayer( layer );
mStartFieldComboBox->setLayer( layer );
mEndFieldComboBox->setLayer( layer );
mDurationStartFieldComboBox->setLayer( layer );
mDurationFieldComboBox->setLayer( layer );
mSingleFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
mStartFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
mStartFieldComboBox->setAllowEmptyFieldName( true );
mEndFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
mEndFieldComboBox->setAllowEmptyFieldName( true );
mDurationStartFieldComboBox->setFilters( QgsFieldProxyModel::DateTime | QgsFieldProxyModel::Date );
mDurationFieldComboBox->setFilters( QgsFieldProxyModel::Numeric );

for ( QgsUnitTypes::TemporalUnit u :
{
QgsUnitTypes::TemporalMilliseconds,
QgsUnitTypes::TemporalSeconds,
QgsUnitTypes::TemporalMinutes,
QgsUnitTypes::TemporalHours,
QgsUnitTypes::TemporalDays,
QgsUnitTypes::TemporalWeeks,
QgsUnitTypes::TemporalMonths,
QgsUnitTypes::TemporalYears,
QgsUnitTypes::TemporalDecades,
QgsUnitTypes::TemporalCenturies
} )
{
mDurationUnitsComboBox->addItem( QgsUnitTypes::toString( u ), u );
}

if ( !properties->startField().isEmpty() )
{
mSingleFieldComboBox->setField( properties->startField() );
mStartFieldComboBox->setField( properties->startField() );
mDurationStartFieldComboBox->setField( properties->startField() );
}
if ( !properties->endField().isEmpty() )
{
mEndFieldComboBox->setField( properties->endField() );
}
mDurationFieldComboBox->setField( properties->durationField() );
mDurationUnitsComboBox->setCurrentIndex( mDurationUnitsComboBox->findData( properties->durationUnits() ) );
}

void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
Expand All @@ -94,7 +119,13 @@ void QgsVectorLayerTemporalPropertiesWidget::saveTemporalProperties()
case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields:
properties->setStartField( mStartFieldComboBox->currentField() );
break;

case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndDurationFromFields:
properties->setStartField( mDurationStartFieldComboBox->currentField() );
break;
}

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

0 comments on commit 3dce1bf

Please sign in to comment.