Skip to content
Permalink
Browse files

[temporal] Try to auto-populate reasonable initial choices for

vector layer temporal settings

When a new vector layer is loaded, we scan through the fields
for likely candidates for the start and end field choices. If found,
we initially setup the temporal properties to these guessed fields,
but we DON'T automatically enable temporal properties for the layer.

It's just a nice little shortcut, much like how we auto-guess the
appropriate title field for a newly added layer
  • Loading branch information
nyalldawson committed May 13, 2020
1 parent c93049a commit 60d87e785a6c5cb5e30fc4060897ef133b476232
@@ -150,6 +150,12 @@ settings (such as startField()) when building the filter string.
ModeFixedTemporalRange should ALL be filtered out.
%End

void guessDefaultsFromFields( const QgsFields &fields );
%Docstring
Attempts to setup the temporal properties by scanning a set of ``fields``
and looking for standard naming conventions (e.g. "begin_date").
%End

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

virtual bool readXml( const QDomElement &element, const QgsReadWriteContext &context );
@@ -188,6 +188,12 @@ QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath,
if ( mValid )
{
mTemporalProperties->setDefaultsFromDataProviderTemporalCapabilities( mDataProvider->temporalCapabilities() );
if ( !mTemporalProperties->isActive() )
{
// didn't populate temporal properties from provider metadata, so at least try to setup some initially nice
// selections
mTemporalProperties->guessDefaultsFromFields( mFields );
}
}

connect( this, &QgsVectorLayer::selectionChanged, this, [ = ] { triggerRepaint(); } );
@@ -19,6 +19,7 @@
#include "qgsvectordataprovidertemporalcapabilities.h"
#include "qgsexpression.h"
#include "qgsvectorlayer.h"
#include "qgsfields.h"

QgsVectorLayerTemporalProperties::QgsVectorLayerTemporalProperties( QObject *parent, bool enabled )
: QgsMapLayerTemporalProperties( parent, enabled )
@@ -282,3 +283,93 @@ QString QgsVectorLayerTemporalProperties::createFilterString( QgsVectorLayer *,

return QString();
}

void QgsVectorLayerTemporalProperties::guessDefaultsFromFields( const QgsFields &fields )
{

// Check the fields and keep the first one that matches.
// We assume that the user has organized the data with the
// more "interesting" field names first.
// This candidates list is a prioritized list of candidates ranked by "interestingness"!
// See discussion at https://github.com/qgis/QGIS/pull/30245 - this list must NOT be translated,
// but adding hardcoded localized variants of the strings is encouraged.
static QStringList sStartCandidates{ QStringLiteral( "start" ),
QStringLiteral( "begin" ),
QStringLiteral( "from" )};

static QStringList sEndCandidates{ QStringLiteral( "end" ),
QStringLiteral( "last" ),
QStringLiteral( "to" )};

static QStringList sSingleFieldCandidates{ QStringLiteral( "event" ) };


bool foundStart = false;
bool foundEnd = false;

for ( const QgsField &field : fields )
{
if ( field.type() != QVariant::Date && field.type() != QVariant::DateTime )
continue;

if ( !foundStart )
{
for ( const QString &candidate : sStartCandidates )
{
QString fldName = field.name();
if ( fldName.indexOf( candidate, 0, Qt::CaseInsensitive ) > -1 )
{
mStartFieldName = fldName;
foundStart = true;
}
}
}

if ( !foundEnd )
{
for ( const QString &candidate : sEndCandidates )
{
QString fldName = field.name();
if ( fldName.indexOf( candidate, 0, Qt::CaseInsensitive ) > -1 )
{
mEndFieldName = fldName;
foundEnd = true;
}
}
}

if ( foundStart && foundEnd )
break;
}

if ( !foundStart )
{
// loop again, looking for likely "single field" candidates
for ( const QgsField &field : fields )
{
if ( field.type() != QVariant::Date && field.type() != QVariant::DateTime )
continue;

for ( const QString &candidate : sSingleFieldCandidates )
{
QString fldName = field.name();
if ( fldName.indexOf( candidate, 0, Qt::CaseInsensitive ) > -1 )
{
mStartFieldName = fldName;
foundStart = true;
}
}

if ( foundStart )
break;
}
}

if ( foundStart && foundEnd )
mMode = ModeFeatureDateTimeStartAndEndFromFields;
else if ( foundStart )
mMode = ModeFeatureDateTimeInstantFromField;

// note -- NEVER auto enable temporal properties here! It's just a helper designed
// to shortcut the initial field selection
}
@@ -26,6 +26,7 @@
#include "qgsrasterdataprovidertemporalcapabilities.h"

class QgsVectorLayer;
class QgsFields;

/**
* \class QgsVectorLayerTemporalProperties
@@ -158,6 +159,12 @@ class CORE_EXPORT QgsVectorLayerTemporalProperties : public QgsMapLayerTemporalP
*/
QString createFilterString( QgsVectorLayer *layer, const QgsDateTimeRange &range ) const;

/**
* Attempts to setup the temporal properties by scanning a set of \a fields
* and looking for standard naming conventions (e.g. "begin_date").
*/
void guessDefaultsFromFields( const QgsFields &fields );

QDomElement writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) override;
bool readXml( const QDomElement &element, const QgsReadWriteContext &context ) override;
void setDefaultsFromDataProviderTemporalCapabilities( const QgsDataProviderTemporalCapabilities *capabilities ) override;
@@ -82,6 +82,41 @@ def testModeFromProvider(self):
self.assertEqual(props.endField(), 'end_field')
self.assertEqual(props.mode(), QgsVectorLayerTemporalProperties.ModeFeatureDateTimeStartAndEndFromFields)

def testGuessDefaultsFromFields(self):
layer = QgsVectorLayer("Point?field=start_date:string&field=end_date:integer", "test", "memory")
self.assertTrue(layer.isValid())
# no date/datetime fields, should not guess anything
props = layer.temporalProperties()
self.assertFalse(props.isActive())
self.assertFalse(props.startField())
self.assertFalse(props.endField())

# datetime fields, but not expected names
layer = QgsVectorLayer("Point?field=date_created:date&field=date_modified:date", "test", "memory")
self.assertTrue(layer.isValid())
props = layer.temporalProperties()
self.assertFalse(props.isActive())
self.assertFalse(props.startField())
self.assertFalse(props.endField())

# sample table with likely single field
layer = QgsVectorLayer("Point?field=event_id:integer&field=event_date:date", "test", "memory")
self.assertTrue(layer.isValid())
props = layer.temporalProperties()
self.assertFalse(props.isActive())
self.assertEqual(props.startField(), 'event_date')
self.assertFalse(props.endField())
self.assertEqual(props.mode(), QgsVectorLayerTemporalProperties.ModeFeatureDateTimeInstantFromField)

# sample table with likely dual fields
layer = QgsVectorLayer("Point?field=event_id:integer&field=start_date:datetime&field=end_date:datetime", "test", "memory")
self.assertTrue(layer.isValid())
props = layer.temporalProperties()
self.assertFalse(props.isActive())
self.assertEqual(props.startField(), 'start_date')
self.assertEqual(props.endField(), 'end_date')
self.assertEqual(props.mode(), QgsVectorLayerTemporalProperties.ModeFeatureDateTimeStartAndEndFromFields)

def testFixedRangeMode(self):
props = QgsVectorLayerTemporalProperties(enabled=True)
props.setMode(QgsVectorLayerTemporalProperties.ModeFixedTemporalRange)

0 comments on commit 60d87e7

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