1 /***************************************************************************
2                          qgsvectorlayertemporalproperties.cpp
3                          ---------------
4     begin                : May 2020
5     copyright            : (C) 2020 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsvectorlayertemporalproperties.h"
19 #include "qgsvectordataprovidertemporalcapabilities.h"
20 #include "qgsexpression.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsfields.h"
23 #include "qgsexpressioncontextutils.h"
24 
QgsVectorLayerTemporalProperties(QObject * parent,bool enabled)25 QgsVectorLayerTemporalProperties::QgsVectorLayerTemporalProperties( QObject *parent, bool enabled )
26   :  QgsMapLayerTemporalProperties( parent, enabled )
27 {
28 }
29 
isVisibleInTemporalRange(const QgsDateTimeRange & range) const30 bool QgsVectorLayerTemporalProperties::isVisibleInTemporalRange( const QgsDateTimeRange &range ) const
31 {
32   if ( !isActive() )
33     return true;
34 
35   switch ( mMode )
36   {
37     case Qgis::VectorTemporalMode::FixedTemporalRange:
38       return range.isInfinite() || mFixedRange.isInfinite() || mFixedRange.overlaps( range );
39 
40     case Qgis::VectorTemporalMode::FeatureDateTimeInstantFromField:
41     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromFields:
42     case Qgis::VectorTemporalMode::RedrawLayerOnly:
43     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndDurationFromFields:
44     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromExpressions:
45       return true;
46   }
47   return true;
48 }
49 
calculateTemporalExtent(QgsMapLayer * layer) const50 QgsDateTimeRange QgsVectorLayerTemporalProperties::calculateTemporalExtent( QgsMapLayer *layer ) const
51 {
52   QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
53   if ( !layer )
54     return QgsDateTimeRange();
55 
56   switch ( mMode )
57   {
58     case Qgis::VectorTemporalMode::FixedTemporalRange:
59       return mFixedRange;
60 
61     case Qgis::VectorTemporalMode::FeatureDateTimeInstantFromField:
62     {
63       const int fieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
64       if ( fieldIndex >= 0 )
65       {
66         QVariant minVal;
67         QVariant maxVal;
68         vectorLayer->minimumAndMaximumValue( fieldIndex, minVal, maxVal );
69 
70         const QDateTime min = minVal.toDateTime();
71         const QDateTime maxStartTime = maxVal.toDateTime();
72         const QgsInterval eventDuration = QgsInterval( mFixedDuration, mDurationUnit );
73         return QgsDateTimeRange( min, maxStartTime + eventDuration );
74       }
75       break;
76     }
77 
78     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndDurationFromFields:
79     {
80       const int fieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
81       const int durationFieldIndex = vectorLayer->fields().lookupField( mDurationFieldName );
82       if ( fieldIndex >= 0 && durationFieldIndex >= 0 )
83       {
84         const QDateTime minTime = vectorLayer->minimumValue( fieldIndex ).toDateTime();
85         // no choice here but to loop through all features to calculate max time :(
86 
87         QgsFeature f;
88         QgsFeatureIterator it = vectorLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() << durationFieldIndex << fieldIndex ) );
89         QDateTime maxTime;
90         while ( it.nextFeature( f ) )
91         {
92           const QDateTime start = f.attribute( fieldIndex ).toDateTime();
93           if ( start.isValid() )
94           {
95             const QVariant durationValue = f.attribute( durationFieldIndex );
96             if ( durationValue.isValid() )
97             {
98               const double duration = durationValue.toDouble();
99               const QDateTime end = start.addMSecs( QgsInterval( duration, mDurationUnit ).seconds() * 1000.0 );
100               if ( end.isValid() )
101                 maxTime = maxTime.isValid() ? std::max( maxTime, end ) : end;
102             }
103           }
104         }
105         return QgsDateTimeRange( minTime, maxTime );
106       }
107       break;
108     }
109 
110     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromFields:
111     {
112       const int startFieldIndex = vectorLayer->fields().lookupField( mStartFieldName );
113       const int endFieldIndex = vectorLayer->fields().lookupField( mEndFieldName );
114       if ( startFieldIndex >= 0 && endFieldIndex >= 0 )
115       {
116         QVariant startMinVal;
117         QVariant startMaxVal;
118         vectorLayer->minimumAndMaximumValue( startFieldIndex, startMinVal, startMaxVal );
119         QVariant endMinVal;
120         QVariant endMaxVal;
121         vectorLayer->minimumAndMaximumValue( endFieldIndex, endMinVal, endMaxVal );
122 
123         return QgsDateTimeRange( std::min( startMinVal.toDateTime(),
124                                            endMinVal.toDateTime() ),
125                                  std::max( startMaxVal.toDateTime(),
126                                            endMaxVal.toDateTime() ) );
127       }
128       else if ( startFieldIndex >= 0 )
129       {
130         QVariant startMinVal;
131         QVariant startMaxVal;
132         vectorLayer->minimumAndMaximumValue( startFieldIndex, startMinVal, startMaxVal );
133         return QgsDateTimeRange( startMinVal.toDateTime(),
134                                  startMaxVal.toDateTime() );
135       }
136       else if ( endFieldIndex >= 0 )
137       {
138         QVariant endMinVal;
139         QVariant endMaxVal;
140         vectorLayer->minimumAndMaximumValue( endFieldIndex, endMinVal, endMaxVal );
141         return QgsDateTimeRange( endMinVal.toDateTime(),
142                                  endMaxVal.toDateTime() );
143       }
144       break;
145     }
146 
147     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromExpressions:
148     {
149       const bool hasStartExpression = !mStartExpression.isEmpty();
150       const bool hasEndExpression = !mEndExpression.isEmpty();
151       if ( !hasStartExpression && !hasEndExpression )
152         return QgsDateTimeRange();
153 
154       QDateTime minTime;
155       QDateTime maxTime;
156 
157       // no choice here but to loop through all features
158       QgsExpressionContext context;
159       context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( vectorLayer ) );
160 
161       QgsExpression startExpression;
162       if ( hasStartExpression )
163       {
164         startExpression.setExpression( mStartExpression );
165         startExpression.prepare( &context );
166       }
167 
168       QgsExpression endExpression;
169       if ( hasEndExpression )
170       {
171         endExpression.setExpression( mEndExpression );
172         endExpression.prepare( &context );
173       }
174 
175       QSet< QString > fields;
176       if ( hasStartExpression )
177         fields.unite( startExpression.referencedColumns() );
178       if ( hasEndExpression )
179         fields.unite( endExpression.referencedColumns() );
180 
181       const bool needsGeom = startExpression.needsGeometry() || endExpression.needsGeometry();
182 
183       QgsFeatureRequest req;
184       if ( !needsGeom )
185         req.setFlags( QgsFeatureRequest::NoGeometry );
186 
187       req.setSubsetOfAttributes( fields, vectorLayer->fields() );
188 
189       QgsFeature f;
190       QgsFeatureIterator it = vectorLayer->getFeatures( req );
191       while ( it.nextFeature( f ) )
192       {
193         context.setFeature( f );
194         const QDateTime start = hasStartExpression ? startExpression.evaluate( &context ).toDateTime() : QDateTime();
195         const QDateTime end = hasEndExpression ? endExpression.evaluate( &context ).toDateTime() : QDateTime();
196 
197         if ( start.isValid() )
198         {
199           minTime = minTime.isValid() ? std::min( minTime, start ) : start;
200           if ( !hasEndExpression )
201             maxTime = maxTime.isValid() ? std::max( maxTime, start ) : start;
202         }
203         if ( end.isValid() )
204         {
205           maxTime = maxTime.isValid() ? std::max( maxTime, end ) : end;
206           if ( !hasStartExpression )
207             minTime = minTime.isValid() ? std::min( minTime, end ) : end;
208         }
209       }
210       return QgsDateTimeRange( minTime, maxTime );
211     }
212 
213     case Qgis::VectorTemporalMode::RedrawLayerOnly:
214       break;
215   }
216 
217   return QgsDateTimeRange();
218 }
219 
mode() const220 Qgis::VectorTemporalMode QgsVectorLayerTemporalProperties::mode() const
221 {
222   return mMode;
223 }
224 
setMode(Qgis::VectorTemporalMode mode)225 void QgsVectorLayerTemporalProperties::setMode( Qgis::VectorTemporalMode mode )
226 {
227   if ( mMode == mode )
228     return;
229   mMode = mode;
230 }
231 
limitMode() const232 Qgis::VectorTemporalLimitMode QgsVectorLayerTemporalProperties::limitMode() const
233 {
234   return mLimitMode;
235 }
236 
setLimitMode(Qgis::VectorTemporalLimitMode limitMode)237 void QgsVectorLayerTemporalProperties::setLimitMode( Qgis::VectorTemporalLimitMode limitMode )
238 {
239   if ( mLimitMode == limitMode )
240     return;
241   mLimitMode = limitMode;
242 }
243 
flags() const244 QgsTemporalProperty::Flags QgsVectorLayerTemporalProperties::flags() const
245 {
246   return mode() == Qgis::VectorTemporalMode::FixedTemporalRange ? QgsTemporalProperty::FlagDontInvalidateCachedRendersWhenRangeChanges : QgsTemporalProperty::Flags();
247 }
248 
setFixedTemporalRange(const QgsDateTimeRange & range)249 void  QgsVectorLayerTemporalProperties::setFixedTemporalRange( const QgsDateTimeRange &range )
250 {
251   mFixedRange = range;
252 }
253 
fixedTemporalRange() const254 const QgsDateTimeRange &QgsVectorLayerTemporalProperties::fixedTemporalRange() const
255 {
256   return mFixedRange;
257 }
258 
readXml(const QDomElement & element,const QgsReadWriteContext & context)259 bool QgsVectorLayerTemporalProperties::readXml( const QDomElement &element, const QgsReadWriteContext &context )
260 {
261   Q_UNUSED( context )
262 
263   const QDomElement temporalNode = element.firstChildElement( QStringLiteral( "temporal" ) );
264 
265   setIsActive( temporalNode.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt() );
266 
267   mMode = static_cast< Qgis::VectorTemporalMode >( temporalNode.attribute( QStringLiteral( "mode" ), QStringLiteral( "0" ) ). toInt() );
268 
269   mLimitMode = static_cast< Qgis::VectorTemporalLimitMode >( temporalNode.attribute( QStringLiteral( "limitMode" ), QStringLiteral( "0" ) ). toInt() );
270   mStartFieldName = temporalNode.attribute( QStringLiteral( "startField" ) );
271   mEndFieldName = temporalNode.attribute( QStringLiteral( "endField" ) );
272   mStartExpression = temporalNode.attribute( QStringLiteral( "startExpression" ) );
273   mEndExpression = temporalNode.attribute( QStringLiteral( "endExpression" ) );
274   mDurationFieldName = temporalNode.attribute( QStringLiteral( "durationField" ) );
275   mDurationUnit = QgsUnitTypes::decodeTemporalUnit( temporalNode.attribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::TemporalMinutes ) ) );
276   mFixedDuration = temporalNode.attribute( QStringLiteral( "fixedDuration" ) ).toDouble();
277   mAccumulateFeatures = temporalNode.attribute( QStringLiteral( "accumulate" ), QStringLiteral( "0" ) ).toInt();
278 
279   const QDomNode rangeElement = temporalNode.namedItem( QStringLiteral( "fixedRange" ) );
280 
281   const QDomNode begin = rangeElement.namedItem( QStringLiteral( "start" ) );
282   const QDomNode end = rangeElement.namedItem( QStringLiteral( "end" ) );
283 
284   const QDateTime beginDate = QDateTime::fromString( begin.toElement().text(), Qt::ISODate );
285   const QDateTime endDate = QDateTime::fromString( end.toElement().text(), Qt::ISODate );
286 
287   const QgsDateTimeRange range = QgsDateTimeRange( beginDate, endDate );
288   setFixedTemporalRange( range );
289 
290   return true;
291 }
292 
writeXml(QDomElement & element,QDomDocument & document,const QgsReadWriteContext & context)293 QDomElement QgsVectorLayerTemporalProperties::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context )
294 {
295   Q_UNUSED( context )
296   if ( element.isNull() )
297     return QDomElement();
298 
299   QDomElement temporalElement = document.createElement( QStringLiteral( "temporal" ) );
300   temporalElement.setAttribute( QStringLiteral( "enabled" ), isActive() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
301   temporalElement.setAttribute( QStringLiteral( "mode" ), QString::number( static_cast< int >( mMode ) ) );
302 
303   temporalElement.setAttribute( QStringLiteral( "limitMode" ), QString::number( static_cast< int >( mLimitMode ) ) );
304   temporalElement.setAttribute( QStringLiteral( "startField" ), mStartFieldName );
305   temporalElement.setAttribute( QStringLiteral( "endField" ), mEndFieldName );
306   temporalElement.setAttribute( QStringLiteral( "startExpression" ), mStartExpression );
307   temporalElement.setAttribute( QStringLiteral( "endExpression" ), mEndExpression );
308   temporalElement.setAttribute( QStringLiteral( "durationField" ), mDurationFieldName );
309   temporalElement.setAttribute( QStringLiteral( "durationUnit" ), QgsUnitTypes::encodeUnit( mDurationUnit ) );
310   temporalElement.setAttribute( QStringLiteral( "fixedDuration" ), qgsDoubleToString( mFixedDuration ) );
311   temporalElement.setAttribute( QStringLiteral( "accumulate" ), mAccumulateFeatures ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
312 
313   QDomElement rangeElement = document.createElement( QStringLiteral( "fixedRange" ) );
314 
315   QDomElement startElement = document.createElement( QStringLiteral( "start" ) );
316   QDomElement endElement = document.createElement( QStringLiteral( "end" ) );
317 
318   const QDomText startText = document.createTextNode( mFixedRange.begin().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODate ) );
319   const QDomText endText = document.createTextNode( mFixedRange.end().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODate ) );
320   startElement.appendChild( startText );
321   endElement.appendChild( endText );
322   rangeElement.appendChild( startElement );
323   rangeElement.appendChild( endElement );
324 
325   temporalElement.appendChild( rangeElement );
326 
327   element.appendChild( temporalElement );
328 
329   return element;
330 }
331 
setDefaultsFromDataProviderTemporalCapabilities(const QgsDataProviderTemporalCapabilities * capabilities)332 void QgsVectorLayerTemporalProperties::setDefaultsFromDataProviderTemporalCapabilities( const QgsDataProviderTemporalCapabilities *capabilities )
333 {
334   if ( const QgsVectorDataProviderTemporalCapabilities *vectorCaps = dynamic_cast< const QgsVectorDataProviderTemporalCapabilities *>( capabilities ) )
335   {
336     setIsActive( vectorCaps->hasTemporalCapabilities() );
337     setFixedTemporalRange( vectorCaps->availableTemporalRange() );
338     setStartField( vectorCaps->startField() );
339     setEndField( vectorCaps->endField() );
340     switch ( vectorCaps->mode() )
341     {
342       case Qgis::VectorDataProviderTemporalMode::HasFixedTemporalRange:
343         setMode( Qgis::VectorTemporalMode::FixedTemporalRange );
344         break;
345       case Qgis::VectorDataProviderTemporalMode::StoresFeatureDateTimeInstantInField:
346         setMode( Qgis::VectorTemporalMode::FeatureDateTimeInstantFromField );
347         break;
348       case Qgis::VectorDataProviderTemporalMode::StoresFeatureDateTimeStartAndEndInSeparateFields:
349         setMode( Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromFields );
350         break;
351     }
352   }
353 }
354 
startExpression() const355 QString QgsVectorLayerTemporalProperties::startExpression() const
356 {
357   return mStartExpression;
358 }
359 
setStartExpression(const QString & startExpression)360 void QgsVectorLayerTemporalProperties::setStartExpression( const QString &startExpression )
361 {
362   mStartExpression = startExpression;
363 }
364 
endExpression() const365 QString QgsVectorLayerTemporalProperties::endExpression() const
366 {
367   return mEndExpression;
368 }
369 
setEndExpression(const QString & endExpression)370 void QgsVectorLayerTemporalProperties::setEndExpression( const QString &endExpression )
371 {
372   mEndExpression = endExpression;
373 }
374 
accumulateFeatures() const375 bool QgsVectorLayerTemporalProperties::accumulateFeatures() const
376 {
377   return mAccumulateFeatures;
378 }
379 
setAccumulateFeatures(bool accumulateFeatures)380 void QgsVectorLayerTemporalProperties::setAccumulateFeatures( bool accumulateFeatures )
381 {
382   mAccumulateFeatures = accumulateFeatures;
383 }
384 
fixedDuration() const385 double QgsVectorLayerTemporalProperties::fixedDuration() const
386 {
387   return mFixedDuration;
388 }
389 
setFixedDuration(double fixedDuration)390 void QgsVectorLayerTemporalProperties::setFixedDuration( double fixedDuration )
391 {
392   mFixedDuration = fixedDuration;
393 }
394 
startField() const395 QString QgsVectorLayerTemporalProperties::startField() const
396 {
397   return mStartFieldName;
398 }
399 
setStartField(const QString & startFieldName)400 void QgsVectorLayerTemporalProperties::setStartField( const QString &startFieldName )
401 {
402   mStartFieldName = startFieldName;
403 }
404 
endField() const405 QString QgsVectorLayerTemporalProperties::endField() const
406 {
407   return mEndFieldName;
408 }
409 
setEndField(const QString & field)410 void QgsVectorLayerTemporalProperties::setEndField( const QString &field )
411 {
412   mEndFieldName = field;
413 }
414 
durationField() const415 QString QgsVectorLayerTemporalProperties::durationField() const
416 {
417   return mDurationFieldName;
418 }
419 
setDurationField(const QString & field)420 void QgsVectorLayerTemporalProperties::setDurationField( const QString &field )
421 {
422   mDurationFieldName = field;
423 }
424 
durationUnits() const425 QgsUnitTypes::TemporalUnit QgsVectorLayerTemporalProperties::durationUnits() const
426 {
427   return mDurationUnit;
428 }
429 
setDurationUnits(QgsUnitTypes::TemporalUnit units)430 void QgsVectorLayerTemporalProperties::setDurationUnits( QgsUnitTypes::TemporalUnit units )
431 {
432   mDurationUnit = units;
433 }
434 
dateTimeExpressionLiteral(const QDateTime & datetime)435 QString dateTimeExpressionLiteral( const QDateTime &datetime )
436 {
437   return QStringLiteral( "make_datetime(%1,%2,%3,%4,%5,%6)" ).arg( datetime.date().year() )
438          .arg( datetime.date().month() )
439          .arg( datetime.date().day() )
440          .arg( datetime.time().hour() )
441          .arg( datetime.time().minute() )
442          .arg( datetime.time().second() + datetime.time().msec() / 1000.0 );
443 }
444 
createFilterString(QgsVectorLayerTemporalContext,const QgsDateTimeRange & filterRange) const445 QString QgsVectorLayerTemporalProperties::createFilterString( QgsVectorLayerTemporalContext, const QgsDateTimeRange &filterRange ) const
446 {
447   if ( !isActive() )
448     return QString();
449 
450   switch ( mMode )
451   {
452     case Qgis::VectorTemporalMode::FixedTemporalRange:
453     case Qgis::VectorTemporalMode::RedrawLayerOnly:
454       return QString();
455 
456     case Qgis::VectorTemporalMode::FeatureDateTimeInstantFromField:
457     {
458       if ( mAccumulateFeatures )
459       {
460         return QStringLiteral( "(%1 %2 %3) OR %1 IS NULL" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
461                filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
462                dateTimeExpressionLiteral( filterRange.end() ) );
463       }
464       else if ( qgsDoubleNear( mFixedDuration, 0.0 ) )
465       {
466         return QStringLiteral( "(%1 %2 %3 AND %1 %4 %5) OR %1 IS NULL" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
467                filterRange.includeBeginning() ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
468                dateTimeExpressionLiteral( filterRange.begin() ),
469                filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
470                dateTimeExpressionLiteral( filterRange.end() ) );
471       }
472       else
473       {
474         // Working with features with events with a duration, so taking this duration into account (+ QgsInterval( -mFixedDuration, mDurationUnit ) ))
475         return QStringLiteral( "(%1 %2 %3 AND %1 %4 %5) OR %1 IS NULL" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
476                limitMode() == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
477                dateTimeExpressionLiteral( filterRange.begin() + QgsInterval( -mFixedDuration, mDurationUnit ) ),
478                filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
479                dateTimeExpressionLiteral( filterRange.end() ) );
480       }
481     }
482 
483     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndDurationFromFields:
484     {
485       QString intervalExpression;
486       switch ( mDurationUnit )
487       {
488         case QgsUnitTypes::TemporalMilliseconds:
489           intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,0,%1/1000)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
490           break;
491 
492         case QgsUnitTypes::TemporalSeconds:
493           intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,0,%1)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
494           break;
495 
496         case QgsUnitTypes::TemporalMinutes:
497           intervalExpression = QStringLiteral( "make_interval(0,0,0,0,0,%1,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
498           break;
499 
500         case QgsUnitTypes::TemporalHours:
501           intervalExpression = QStringLiteral( "make_interval(0,0,0,0,%1,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
502           break;
503 
504         case QgsUnitTypes::TemporalDays:
505           intervalExpression = QStringLiteral( "make_interval(0,0,0,%1,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
506           break;
507 
508         case QgsUnitTypes::TemporalWeeks:
509           intervalExpression = QStringLiteral( "make_interval(0,0,%1,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
510           break;
511 
512         case QgsUnitTypes::TemporalMonths:
513           intervalExpression = QStringLiteral( "make_interval(0,%1,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
514           break;
515 
516         case QgsUnitTypes::TemporalYears:
517           intervalExpression = QStringLiteral( "make_interval(%1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
518           break;
519 
520         case QgsUnitTypes::TemporalDecades:
521           intervalExpression = QStringLiteral( "make_interval(10 * %1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
522           break;
523 
524         case QgsUnitTypes::TemporalCenturies:
525           intervalExpression = QStringLiteral( "make_interval(100 * %1,0,0,0,0,0,0)" ).arg( QgsExpression::quotedColumnRef( mDurationFieldName ) );
526           break;
527 
528         case QgsUnitTypes::TemporalUnknownUnit:
529         case QgsUnitTypes::TemporalIrregularStep:
530           return QString();
531       }
532       return QStringLiteral( "(%1 %2 %3 OR %1 IS NULL) AND ((%1 + %4 %5 %6) OR %7 IS NULL)" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
533              filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
534              dateTimeExpressionLiteral( filterRange.end() ),
535              intervalExpression,
536              limitMode() == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
537              dateTimeExpressionLiteral( filterRange.begin() ),
538              QgsExpression::quotedColumnRef( mDurationFieldName ) );
539     }
540 
541     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromFields:
542     {
543       if ( !mStartFieldName.isEmpty() && !mEndFieldName.isEmpty() )
544       {
545         return QStringLiteral( "(%1 %2 %3 OR %1 IS NULL) AND (%4 %5 %6 OR %4 IS NULL)" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
546                filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
547                dateTimeExpressionLiteral( filterRange.end() ),
548                QgsExpression::quotedColumnRef( mEndFieldName ),
549                limitMode() == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
550                dateTimeExpressionLiteral( filterRange.begin() ) );
551 
552       }
553       else if ( !mStartFieldName.isEmpty() )
554       {
555         return QStringLiteral( "%1 %2 %3 OR %1 IS NULL" ).arg( QgsExpression::quotedColumnRef( mStartFieldName ),
556                filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
557                dateTimeExpressionLiteral( filterRange.end() ) );
558       }
559       else if ( !mEndFieldName.isEmpty() )
560       {
561         return QStringLiteral( "%1 %2 %3 OR %1 IS NULL" ).arg( QgsExpression::quotedColumnRef( mEndFieldName ),
562                limitMode() == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
563                dateTimeExpressionLiteral( filterRange.begin() ) );
564       }
565       break;
566     }
567 
568     case Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromExpressions:
569     {
570       if ( !mStartExpression.isEmpty() && !mEndExpression.isEmpty() )
571       {
572         return QStringLiteral( "((%1) %2 %3) AND ((%4) %5 %6)" ).arg( mStartExpression,
573                filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
574                dateTimeExpressionLiteral( filterRange.end() ),
575                mEndExpression,
576                limitMode() == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
577                dateTimeExpressionLiteral( filterRange.begin() ) );
578       }
579       else if ( !mStartExpression.isEmpty() )
580       {
581         return QStringLiteral( "(%1) %2 %3" ).arg( mStartExpression,
582                filterRange.includeEnd() ? QStringLiteral( "<=" ) : QStringLiteral( "<" ),
583                dateTimeExpressionLiteral( filterRange.end() ) );
584       }
585       else if ( !mEndExpression.isEmpty() )
586       {
587         return QStringLiteral( "(%1) %2 %3" ).arg( mEndExpression,
588                limitMode() == Qgis::VectorTemporalLimitMode::IncludeBeginIncludeEnd ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
589                dateTimeExpressionLiteral( filterRange.begin() ) );
590       }
591       break;
592     }
593   }
594 
595   return QString();
596 }
597 
guessDefaultsFromFields(const QgsFields & fields)598 void QgsVectorLayerTemporalProperties::guessDefaultsFromFields( const QgsFields &fields )
599 {
600 
601   // Check the fields and keep the first one that matches.
602   // We assume that the user has organized the data with the
603   // more "interesting" field names first.
604   // This candidates list is a prioritized list of candidates ranked by "interestingness"!
605   // See discussion at https://github.com/qgis/QGIS/pull/30245 - this list must NOT be translated,
606   // but adding hardcoded localized variants of the strings is encouraged.
607   static const QStringList sStartCandidates{ QStringLiteral( "start" ),
608       QStringLiteral( "begin" ),
609       QStringLiteral( "from" )};
610 
611   static const QStringList sEndCandidates{ QStringLiteral( "end" ),
612       QStringLiteral( "last" ),
613       QStringLiteral( "to" )};
614 
615   static const QStringList sSingleFieldCandidates{ QStringLiteral( "event" ) };
616 
617 
618   bool foundStart = false;
619   bool foundEnd = false;
620 
621   for ( const QgsField &field : fields )
622   {
623     if ( field.type() != QVariant::Date && field.type() != QVariant::DateTime )
624       continue;
625 
626     if ( !foundStart )
627     {
628       for ( const QString &candidate : sStartCandidates )
629       {
630         const QString fldName = field.name();
631         if ( fldName.indexOf( candidate, 0, Qt::CaseInsensitive ) > -1 )
632         {
633           mStartFieldName = fldName;
634           foundStart = true;
635         }
636       }
637     }
638 
639     if ( !foundEnd )
640     {
641       for ( const QString &candidate : sEndCandidates )
642       {
643         const QString fldName = field.name();
644         if ( fldName.indexOf( candidate, 0, Qt::CaseInsensitive ) > -1 )
645         {
646           mEndFieldName = fldName;
647           foundEnd = true;
648         }
649       }
650     }
651 
652     if ( foundStart && foundEnd )
653       break;
654   }
655 
656   if ( !foundStart )
657   {
658     // loop again, looking for likely "single field" candidates
659     for ( const QgsField &field : fields )
660     {
661       if ( field.type() != QVariant::Date && field.type() != QVariant::DateTime )
662         continue;
663 
664       for ( const QString &candidate : sSingleFieldCandidates )
665       {
666         const QString fldName = field.name();
667         if ( fldName.indexOf( candidate, 0, Qt::CaseInsensitive ) > -1 )
668         {
669           mStartFieldName = fldName;
670           foundStart = true;
671         }
672       }
673 
674       if ( foundStart )
675         break;
676     }
677   }
678 
679   if ( foundStart && foundEnd )
680     mMode = Qgis::VectorTemporalMode::FeatureDateTimeStartAndEndFromFields;
681   else if ( foundStart )
682     mMode = Qgis::VectorTemporalMode::FeatureDateTimeInstantFromField;
683 
684   // note -- NEVER auto enable temporal properties here! It's just a helper designed
685   // to shortcut the initial field selection
686 }
687 
layer() const688 QgsVectorLayer *QgsVectorLayerTemporalContext::layer() const
689 {
690   return mLayer;
691 }
692 
setLayer(QgsVectorLayer * layer)693 void QgsVectorLayerTemporalContext::setLayer( QgsVectorLayer *layer )
694 {
695   mLayer = layer;
696 }
697