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