Skip to content

Commit

Permalink
[feature] Add basic temporal handling support for vector layers
Browse files Browse the repository at this point in the history
This exposes some basic temporal capabilities for vector layers:
- static time range for layer (to match raster layer possibilities), this
sets a single static time range which applies to the whole layer. ALL
features from the layer will be shown whenever the canvas time
overlaps the layer time range
- "Single field with datetime": Allows selection of a single date
or datetime field from the layer. Features will be shown whenever
this field value is within the canvas time range
- "Separate Fields for Start and End Date/Time": Allows selection
of start and end date/datetime fields from the layer. Features will
be shown whenever the time interval calculated from these fields
overlaps the canvas time range

Some known limitations/inefficiencies:
- currently only date/datetime fields can be used. This was done
to simplify the format handling and avoid the need to worry about
string fields with different datetime formats. In future we should
allow selection of string fields and allow users to enter a custom
datetime format string
- unlike the Time Manager plugin approach, the approach taken here
is to rely completely on QGIS expressions and feature requests to
do the filtering (Time Manager uses layer filter strings and attempts
to set a native SQL filter syntax so that filtering is done on the
backend). This is intentional, because it provides a unified filter
approach regardless of the provider used (i.e. we don't need to worry
about the different SQL syntaxes used natively by the different
providers). The beauty of feature request expression compilation
**should** mean that the QGIS expressions are magically turned into
native backend queries, BUUUUUUUUUUUT... because we lack QGIS expression
support for date time literals, we currently rely on the "to_datetime"
expression function and coerce everything through strings. None of
the expression compilers handle this function, so currently ALL
filtering is done on the QGIS side. We need to add functions for
optimised datetime literal creation, and then ensure that the different
compilers correctly map these literals across to the backend
filter syntax to allow all the filtering work to be done on the database
side...

So currently, performance is much worse with large layers compared
to Time Manager. But, the advantage is that we can use the native
temporal framework and have vector layers animated alongside mesh
and raster layers!
  • Loading branch information
nyalldawson committed May 8, 2020
1 parent 66760b4 commit d98defe
Show file tree
Hide file tree
Showing 30 changed files with 1,321 additions and 61 deletions.
161 changes: 161 additions & 0 deletions python/core/auto_generated/qgsvectorlayertemporalproperties.sip.in
@@ -0,0 +1,161 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsvectorlayertemporalproperties.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/






class QgsVectorLayerTemporalProperties : QgsMapLayerTemporalProperties
{
%Docstring
Implementation of map layer temporal properties for vector layers.

.. versionadded:: 3.14
%End

%TypeHeaderCode
#include "qgsvectorlayertemporalproperties.h"
%End
public:

QgsVectorLayerTemporalProperties( QObject *parent /TransferThis/ = 0, bool enabled = false );
%Docstring
Constructor for QgsVectorLayerTemporalProperties, with the specified ``parent`` object.

The ``enabled`` argument specifies whether the temporal properties are initially enabled or not (see isActive()).
%End

virtual bool isVisibleInTemporalRange( const QgsDateTimeRange &range ) const;


enum TemporalMode
{
ModeFixedTemporalRange,
ModeFeatureDateTimeInstantFromField,
ModeFeatureDateTimeStartAndEndFromFields,
};

TemporalMode mode() const;
%Docstring
Returns the temporal properties mode.

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

void setMode( TemporalMode mode );
%Docstring
Sets the temporal properties ``mode``.

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

void setFixedTemporalRange( const QgsDateTimeRange &range );
%Docstring
Sets a temporal ``range`` to apply to the whole layer. All features from
the layer will be rendered whenever the current datetime range of
a render context intersects the specified ``range``.

.. warning::

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

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

const QgsDateTimeRange &fixedTemporalRange() const;
%Docstring
Returns the fixed temporal range for the layer.

.. warning::

To be used only when mode() is
QgsVectorLayerTemporalProperties.ModeFixedTemporalRange

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

QString startField() const;
%Docstring
Returns the name of the start datetime field, which
contains the start time for the feature's time spans.

If mode() is ModeFeatureDateTimeInstantFromField, then this field
represents both the start AND end times.

.. seealso:: :py:func:`setStartField`

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

void setStartField( const QString &field );
%Docstring
Sets the name of the start datetime ``field``, which
contains the start time for the feature's time spans.

If mode() is ModeFeatureDateTimeInstantFromField, then this field
represents both the start AND end times.

.. seealso:: :py:func:`startField`

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

QString endField() const;
%Docstring
Returns the name of the end datetime field, which
contains the end time for the feature's time spans.

.. seealso:: :py:func:`setEndField`

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

void setEndField( const QString &field );
%Docstring
Sets the name of the end datetime ``field``, which
contains the end time for the feature's time spans.

.. seealso:: :py:func:`endField`

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

QString createFilterString( QgsVectorLayer *layer, const QgsDateTimeRange &range ) const;
%Docstring
Creates a QGIS expression filter string for filtering features from ``layer``
to those within the specified time ``range``.

The returned expression string considers the mode() and other related
settings (such as startField()) when building the filter string.

.. warning::

Note that ModeFixedTemporalRange is intentional NOT handled by this method
and if mode() is ModeFixedTemporalRange then an empty string will be returned. Use
isVisibleInTemporalRange() when testing whether features from a layer set to the
ModeFixedTemporalRange should ALL be filtered out.
%End

virtual QDomElement writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context );

virtual bool readXml( const QDomElement &element, const QgsReadWriteContext &context );

virtual void setDefaultsFromDataProviderTemporalCapabilities( const QgsDataProviderTemporalCapabilities *capabilities );


};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsvectorlayertemporalproperties.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -236,6 +236,7 @@
%Include auto_generated/qgsvectorlayerjoinbuffer.sip
%Include auto_generated/qgsvectorlayerjoininfo.sip
%Include auto_generated/qgsvectorlayerserverproperties.sip
%Include auto_generated/qgsvectorlayertemporalproperties.sip
%Include auto_generated/qgsvectorlayertools.sip
%Include auto_generated/qgsvectorlayerundocommand.sip
%Include auto_generated/qgsvectorlayerundopassthroughcommand.sip
Expand Down
7 changes: 7 additions & 0 deletions python/gui/auto_generated/qgsoptionsdialogbase.sip.in
Expand Up @@ -78,6 +78,13 @@ Resizes all tabs when the dialog is resized
bool iconOnly();
%Docstring
Determine if the options list is in icon only mode
%End

void setCurrentPage( const QString &page );
%Docstring
Sets the dialog ``page`` (by object name) to show.

.. versionadded:: 3.14
%End

public slots:
Expand Down
@@ -0,0 +1,43 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsvectorlayertemporalpropertieswidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/





class QgsVectorLayerTemporalPropertiesWidget : QWidget
{
%Docstring
A widget for configuring the temporal properties for a vector layer.

.. versionadded:: 3.14
%End

%TypeHeaderCode
#include "qgsvectorlayertemporalpropertieswidget.h"
%End
public:

QgsVectorLayerTemporalPropertiesWidget( QWidget *parent = 0, QgsVectorLayer *layer = 0 );
%Docstring
Constructor for QgsVectorLayerTemporalPropertiesWidget.
%End

void saveTemporalProperties();
%Docstring
Save widget temporal properties inputs.
%End

};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsvectorlayertemporalpropertieswidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
Expand Up @@ -34,13 +34,6 @@ Constructor
:param canvas: the QgsMapCanvas instance
:param parent: the parent of this widget
:param fl: windows flag
%End

void setCurrentPage( const QString &page );
%Docstring
Sets the dialog ``page`` (by object name) to show.

.. versionadded:: 3.14
%End

protected slots:
Expand Down
1 change: 1 addition & 0 deletions python/gui/gui_auto.sip
Expand Up @@ -215,6 +215,7 @@
%Include auto_generated/qgsuserinputwidget.sip
%Include auto_generated/qgsvaliditycheckresultswidget.sip
%Include auto_generated/qgsvariableeditorwidget.sip
%Include auto_generated/qgsvectorlayertemporalpropertieswidget.sip
%Include auto_generated/qgsvertexmarker.sip
%Include auto_generated/qgsvscrollarea.sip
%Include auto_generated/qgswindowmanagerinterface.sip
Expand Down
6 changes: 6 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -15366,6 +15366,9 @@ void QgisApp::showLayerProperties( QgsMapLayer *mapLayer, const QString &page )
case QgsMapLayerType::MeshLayer:
{
QgsMeshLayerProperties meshLayerPropertiesDialog( mapLayer, mMapCanvas, this );
if ( !page.isEmpty() )
meshLayerPropertiesDialog.setCurrentPage( page );

mMapStyleWidget->blockUpdates( true );
if ( meshLayerPropertiesDialog.exec() )
{
Expand Down Expand Up @@ -15400,6 +15403,9 @@ void QgisApp::showLayerProperties( QgsMapLayer *mapLayer, const QString &page )
vectorLayerPropertiesDialog->addPropertiesPageFactory( factory );
}

if ( !page.isEmpty() )
vectorLayerPropertiesDialog->setCurrentPage( page );

mMapStyleWidget->blockUpdates( true );
if ( vectorLayerPropertiesDialog->exec() )
{
Expand Down
6 changes: 4 additions & 2 deletions src/app/qgslayertreeviewtemporalindicator.cpp
Expand Up @@ -51,12 +51,14 @@ void QgsLayerTreeViewTemporalIndicatorProvider::onIndicatorClicked( const QModel
switch ( layer->type() )
{
case QgsMapLayerType::RasterLayer:
QgisApp::instance()->showLayerProperties( qobject_cast<QgsRasterLayer *>( layer ), QStringLiteral( "mOptsPage_Temporal" ) );
QgisApp::instance()->showLayerProperties( layer, QStringLiteral( "mOptsPage_Temporal" ) );
break;
case QgsMapLayerType::MeshLayer:
QgisApp::instance()->showLayerProperties( qobject_cast<QgsMeshLayer *>( layer ), QStringLiteral( "mOptsPage_Temporal" ) );
QgisApp::instance()->showLayerProperties( layer, QStringLiteral( "mOptsPage_Temporal" ) );
break;
case QgsMapLayerType::VectorLayer:
QgisApp::instance()->showLayerProperties( layer, QStringLiteral( "mOptsPage_Temporal" ) );
break;
case QgsMapLayerType::PluginLayer:
case QgsMapLayerType::VectorTileLayer:
break;
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -432,6 +432,7 @@ SET(QGIS_CORE_SRCS
qgsvectorlayerjoinbuffer.cpp
qgsvectorlayerjoininfo.cpp
qgsvectorlayerrenderer.cpp
qgsvectorlayertemporalproperties.cpp
qgsvectorlayertools.cpp
qgsvectorlayerundocommand.cpp
qgsvectorlayerundopassthroughcommand.cpp
Expand Down Expand Up @@ -994,6 +995,7 @@ SET(QGIS_CORE_HDRS
qgsvectorlayerjoininfo.h
qgsvectorlayerrenderer.h
qgsvectorlayerserverproperties.h
qgsvectorlayertemporalproperties.h
qgsvectorlayertools.h
qgsvectorlayerundocommand.h
qgsvectorlayerundopassthroughcommand.h
Expand Down
50 changes: 47 additions & 3 deletions src/core/qgstemporalutils.cpp
Expand Up @@ -18,6 +18,8 @@
#include "qgsmaplayertemporalproperties.h"
#include "qgsrasterlayer.h"
#include "qgsmeshlayer.h"
#include "qgsvectorlayer.h"
#include "qgsvectorlayertemporalproperties.h"

QgsDateTimeRange QgsTemporalUtils::calculateTemporalRangeForProject( QgsProject *project )
{
Expand Down Expand Up @@ -51,15 +53,57 @@ QgsDateTimeRange QgsTemporalUtils::calculateTemporalRangeForProject( QgsProject
layerRange = rasterLayer->dataProvider()->temporalCapabilities()->availableTemporalRange();
break;
}
break;
}

case QgsMapLayerType::VectorLayer:
{
QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( currentLayer );

QgsVectorLayerTemporalProperties *properties = static_cast< QgsVectorLayerTemporalProperties * >( vectorLayer->temporalProperties() );
switch ( properties->mode() )
{
case QgsVectorLayerTemporalProperties::ModeFixedTemporalRange:
layerRange = properties->fixedTemporalRange();
break;

case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeInstantFromField:
{
const int fieldIndex = vectorLayer->fields().lookupField( properties->startField() );
if ( fieldIndex >= 0 )
{
layerRange = QgsDateTimeRange( vectorLayer->minimumValue( fieldIndex ).toDateTime(),
vectorLayer->maximumValue( fieldIndex ).toDateTime() );
}
break;
}

case QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields:
{
const int startFieldIndex = vectorLayer->fields().lookupField( properties->startField() );
const int endFieldIndex = vectorLayer->fields().lookupField( properties->endField() );
if ( startFieldIndex >= 0 && endFieldIndex >= 0 )
{
layerRange = QgsDateTimeRange( std::min( vectorLayer->minimumValue( startFieldIndex ).toDateTime(),
vectorLayer->minimumValue( endFieldIndex ).toDateTime() ),
std::max( vectorLayer->maximumValue( startFieldIndex ).toDateTime(),
vectorLayer->maximumValue( endFieldIndex ).toDateTime() ) );
}
break;
}
}
break;
}
break;

case QgsMapLayerType::MeshLayer:
{
QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( currentLayer );
layerRange = meshLayer->temporalProperties()->timeExtent();
break;
}
break;
default:

case QgsMapLayerType::PluginLayer:
case QgsMapLayerType::VectorTileLayer:
break;
}

Expand Down

0 comments on commit d98defe

Please sign in to comment.