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