QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgstemporalcontrollerwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstemporalcontrollerwidget.cpp
3  ------------------------------
4  begin : February 2020
5  copyright : (C) 2020 by Samweli Mwakisambwe
6  email : samweli at kartoza 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 "qgsapplication.h"
20 #include "qgsgui.h"
21 #include "qgsmaplayermodel.h"
22 #include "qgsproject.h"
23 #include "qgsprojecttimesettings.h"
25 #include "qgstemporalutils.h"
27 #include "qgsmeshlayer.h"
28 #include "qgsrasterlayer.h"
29 
30 #include <QAction>
31 #include <QMenu>
32 #include <QRegularExpression>
33 
35  : QgsPanelWidget( parent )
36 {
37  setupUi( this );
38 
39  mNavigationObject = new QgsTemporalNavigationObject( this );
40 
41  mStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
42  mEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
43  mFixedRangeStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
44  mFixedRangeEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
45 
46  connect( mForwardButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePlayForward );
47  connect( mBackButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePlayBackward );
48  connect( mStopButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePause );
49  connect( mNextButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::next );
50  connect( mPreviousButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::previous );
51  connect( mFastForwardButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::skipToEnd );
52  connect( mRewindButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::rewindToStart );
53  connect( mLoopingCheckBox, &QCheckBox::toggled, this, [ = ]( bool state ) { mNavigationObject->setLooping( state ); } );
54 
55  setWidgetStateFromNavigationMode( mNavigationObject->navigationMode() );
56  connect( mNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsTemporalControllerWidget::setWidgetStateFromNavigationMode );
57  connect( mNavigationObject, &QgsTemporalNavigationObject::temporalExtentsChanged, this, &QgsTemporalControllerWidget::setDates );
58  connect( mNavigationObject, &QgsTemporalNavigationObject::temporalFrameDurationChanged, this, [ = ]( const QgsInterval & timeStep )
59  {
60  if ( mBlockFrameDurationUpdates )
61  return;
62 
63  mBlockFrameDurationUpdates++;
64  updateTimeStepInputs( timeStep );
65  mBlockFrameDurationUpdates--;
66  } );
67  connect( mNavigationOff, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationOff_clicked );
68  connect( mNavigationFixedRange, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationFixedRange_clicked );
69  connect( mNavigationAnimated, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationAnimated_clicked );
70 
71  connect( mNavigationObject, &QgsTemporalNavigationObject::stateChanged, this, [ = ]( QgsTemporalNavigationObject::AnimationState state )
72  {
73  mForwardButton->setChecked( state == QgsTemporalNavigationObject::Forward );
74  mBackButton->setChecked( state == QgsTemporalNavigationObject::Reverse );
75  mStopButton->setChecked( state == QgsTemporalNavigationObject::Idle );
76  } );
77 
78  connect( mStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
79  connect( mEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
80  connect( mFixedRangeStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
81  connect( mFixedRangeEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
82  connect( mStepSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
83  connect( mTimeStepsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
84  connect( mSlider, &QSlider::valueChanged, this, &QgsTemporalControllerWidget::timeSlider_valueChanged );
85 
86  mStepSpinBox->setClearValue( 1 );
87 
88  connect( mNavigationObject, &QgsTemporalNavigationObject::updateTemporalRange, this, &QgsTemporalControllerWidget::updateSlider );
89 
90  connect( mSettings, &QPushButton::clicked, this, &QgsTemporalControllerWidget::settings_clicked );
91 
92  mMapLayerModel = new QgsMapLayerModel( this );
93 
94  mRangeMenu.reset( new QMenu( this ) );
95 
96  mRangeSetToAllLayersAction = new QAction( tr( "Set to Full Range" ), mRangeMenu.get() );
97  mRangeSetToAllLayersAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRefresh.svg" ) ) );
98  connect( mRangeSetToAllLayersAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered );
99  mRangeMenu->addAction( mRangeSetToAllLayersAction );
100 
101  mRangeSetToProjectAction = new QAction( tr( "Set to Preset Project Range" ), mRangeMenu.get() );
102  connect( mRangeSetToProjectAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered );
103  mRangeMenu->addAction( mRangeSetToProjectAction );
104 
105  mRangeMenu->addSeparator();
106 
107  mRangeLayersSubMenu.reset( new QMenu( tr( "Set to Single Layer's Range" ), mRangeMenu.get() ) );
108  mRangeLayersSubMenu->setEnabled( false );
109  mRangeMenu->addMenu( mRangeLayersSubMenu.get() );
110  connect( mRangeMenu.get(), &QMenu::aboutToShow, this, &QgsTemporalControllerWidget::aboutToShowRangeMenu );
111 
112  mSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
113  mSetRangeButton->setMenu( mRangeMenu.get() );
114  mSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
115  mFixedRangeSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
116  mFixedRangeSetRangeButton->setMenu( mRangeMenu.get() );
117  mFixedRangeSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
118 
119  connect( mExportAnimationButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::exportAnimation );
120 
121  QgsDateTimeRange range;
122 
123  if ( QgsProject::instance()->timeSettings() )
125 
126  if ( range.begin().isValid() && range.end().isValid() )
127  {
128  whileBlocking( mStartDateTime )->setDateTime( range.begin() );
129  whileBlocking( mEndDateTime )->setDateTime( range.end() );
130  whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
131  whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
132  }
133 
134  for ( const QgsUnitTypes::TemporalUnit u :
135  {
147  } )
148  {
149  mTimeStepsComboBox->addItem( u != QgsUnitTypes::TemporalIrregularStep ? QgsUnitTypes::toString( u ) : tr( "source timestamps" ), u );
150  }
151 
152  // TODO: might want to choose an appropriate default unit based on the range
153  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsUnitTypes::TemporalHours ) );
154 
155  // NOTE 'minimum' and 'decimals' should be in sync with the 'decimals' in qgstemporalcontrollerwidgetbase.ui
156  mStepSpinBox->setDecimals( 3 );
157  // minimum timestep one millisecond
158  mStepSpinBox->setMinimum( 0.001 );
159  mStepSpinBox->setMaximum( std::numeric_limits<int>::max() );
160  mStepSpinBox->setSingleStep( 1 );
161  mStepSpinBox->setValue( 1 );
162 
163  mForwardButton->setToolTip( tr( "Play" ) );
164  mBackButton->setToolTip( tr( "Reverse" ) );
165  mNextButton->setToolTip( tr( "Go to next frame" ) );
166  mPreviousButton->setToolTip( tr( "Go to previous frame" ) );
167  mStopButton->setToolTip( tr( "Pause" ) );
168  mRewindButton->setToolTip( tr( "Rewind to start" ) );
169  mFastForwardButton->setToolTip( tr( "Fast forward to end" ) );
170 
171  updateFrameDuration();
172 
173  connect( QgsProject::instance(), &QgsProject::readProject, this, &QgsTemporalControllerWidget::setWidgetStateFromProject );
174  connect( QgsProject::instance(), &QgsProject::layersAdded, this, &QgsTemporalControllerWidget::onLayersAdded );
175  connect( QgsProject::instance(), &QgsProject::cleared, this, &QgsTemporalControllerWidget::onProjectCleared );
176 }
177 
179 {
180  return true;
181 }
182 
184 {
185  if ( mSlider->hasFocus() && e->key() == Qt::Key_Space )
186  {
187  togglePause();
188  }
189  QWidget::keyPressEvent( e );
190 }
191 
192 void QgsTemporalControllerWidget::aboutToShowRangeMenu()
193 {
194  QgsDateTimeRange projectRange;
195  if ( QgsProject::instance()->timeSettings() )
196  projectRange = QgsProject::instance()->timeSettings()->temporalRange();
197  mRangeSetToProjectAction->setEnabled( projectRange.begin().isValid() && projectRange.end().isValid() );
198 
199  mRangeLayersSubMenu->clear();
200  for ( int i = 0; i < mMapLayerModel->rowCount(); ++i )
201  {
202  const QModelIndex index = mMapLayerModel->index( i, 0 );
203  QgsMapLayer *currentLayer = mMapLayerModel->data( index, QgsMapLayerModel::LayerRole ).value<QgsMapLayer *>();
204  if ( !currentLayer->temporalProperties() || !currentLayer->temporalProperties()->isActive() )
205  continue;
206 
207  const QIcon icon = qvariant_cast<QIcon>( mMapLayerModel->data( index, Qt::DecorationRole ) );
208  const QString text = mMapLayerModel->data( index, Qt::DisplayRole ).toString();
209  const QgsDateTimeRange range = currentLayer->temporalProperties()->calculateTemporalExtent( currentLayer );
210  if ( range.begin().isValid() && range.end().isValid() )
211  {
212  QAction *action = new QAction( icon, text, mRangeLayersSubMenu.get() );
213  connect( action, &QAction::triggered, this, [ = ]
214  {
215  setDates( range );
216  saveRangeToProject();
217  } );
218  mRangeLayersSubMenu->addAction( action );
219  }
220  }
221  mRangeLayersSubMenu->setEnabled( !mRangeLayersSubMenu->actions().isEmpty() );
222 }
223 
224 void QgsTemporalControllerWidget::togglePlayForward()
225 {
226  mPlayingForward = true;
227 
228  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Forward )
229  {
230  mStopButton->setChecked( false );
231  mBackButton->setChecked( false );
232  mForwardButton->setChecked( true );
233  mNavigationObject->playForward();
234  }
235  else
236  {
237  mBackButton->setChecked( true );
238  mForwardButton->setChecked( false );
239  mNavigationObject->pause();
240  }
241 }
242 
243 void QgsTemporalControllerWidget::togglePlayBackward()
244 {
245  mPlayingForward = false;
246 
247  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Reverse )
248  {
249  mStopButton->setChecked( false );
250  mBackButton->setChecked( true );
251  mForwardButton->setChecked( false );
252  mNavigationObject->playBackward();
253  }
254  else
255  {
256  mBackButton->setChecked( true );
257  mBackButton->setChecked( false );
258  mNavigationObject->pause();
259  }
260 }
261 
262 void QgsTemporalControllerWidget::togglePause()
263 {
264  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Idle )
265  {
266  mStopButton->setChecked( true );
267  mBackButton->setChecked( false );
268  mForwardButton->setChecked( false );
269  mNavigationObject->pause();
270  }
271  else
272  {
273  mBackButton->setChecked( mPlayingForward ? false : true );
274  mForwardButton->setChecked( mPlayingForward ? false : true );
275  if ( mPlayingForward )
276  {
277  mNavigationObject->playForward();
278  }
279  else
280  {
281  mNavigationObject->playBackward();
282  }
283  }
284 }
285 
286 void QgsTemporalControllerWidget::updateTemporalExtent()
287 {
288  // TODO - consider whether the overall time range set for animations should include the end date time or not.
289  // (currently it DOES include the end date time).
290  const QDateTime start = mStartDateTime->dateTime();
291  const QDateTime end = mEndDateTime->dateTime();
292  const bool isTimeInstant = start == end;
293  const QgsDateTimeRange temporalExtent = QgsDateTimeRange( start, end,
294  true, !isTimeInstant && mNavigationObject->navigationMode() == QgsTemporalNavigationObject::FixedRange ? false : true );
295  mNavigationObject->setTemporalExtents( temporalExtent );
296  mSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
297  mSlider->setValue( mNavigationObject->currentFrameNumber() );
298 }
299 
300 void QgsTemporalControllerWidget::updateFrameDuration()
301 {
302  if ( mBlockSettingUpdates )
303  return;
304 
305  // save new settings into project
306  const QgsUnitTypes::TemporalUnit unit = static_cast< QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() );
308  QgsProject::instance()->timeSettings()->setTimeStep( unit == QgsUnitTypes::TemporalIrregularStep ? 1 : mStepSpinBox->value() );
309 
310  if ( !mBlockFrameDurationUpdates )
311  {
312  mNavigationObject->setFrameDuration(
313  QgsInterval( QgsProject::instance()->timeSettings()->timeStep(),
314  QgsProject::instance()->timeSettings()->timeStepUnit() ) );
315  mSlider->setValue( mNavigationObject->currentFrameNumber() );
316  }
317  mSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
318  mSlider->setValue( mNavigationObject->currentFrameNumber() );
319 
321  {
322  mStepSpinBox->setEnabled( false );
323  mStepSpinBox->setValue( 1 );
324  mSlider->setTickInterval( 1 );
325  mSlider->setTickPosition( QSlider::TicksBothSides );
326  }
327  else
328  {
329  mStepSpinBox->setEnabled( true );
330  mSlider->setTickInterval( 0 );
331  mSlider->setTickPosition( QSlider::NoTicks );
332  }
333 }
334 
335 void QgsTemporalControllerWidget::setWidgetStateFromProject()
336 {
337  mBlockSettingUpdates++;
338  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsProject::instance()->timeSettings()->timeStepUnit() ) );
339  mStepSpinBox->setValue( QgsProject::instance()->timeSettings()->timeStep() );
340  mBlockSettingUpdates--;
341 
342  bool ok = false;
343  const QgsTemporalNavigationObject::NavigationMode mode = static_cast< QgsTemporalNavigationObject::NavigationMode>( QgsProject::instance()->readNumEntry( QStringLiteral( "TemporalControllerWidget" ),
344  QStringLiteral( "/NavigationMode" ), 0, &ok ) );
345  if ( ok )
346  {
347  mNavigationObject->setNavigationMode( mode );
348  setWidgetStateFromNavigationMode( mode );
349  }
350  else
351  {
353  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
354  }
355 
356  const QString startString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/StartDateTime" ) );
357  const QString endString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/EndDateTime" ) );
358  if ( !startString.isEmpty() && !endString.isEmpty() )
359  {
360  whileBlocking( mStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
361  whileBlocking( mEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
362  whileBlocking( mFixedRangeStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
363  whileBlocking( mFixedRangeEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
364  }
365  else
366  {
367  setDatesToProjectTime();
368  }
369  updateTemporalExtent();
370  updateFrameDuration();
371 
372  mNavigationObject->setFramesPerSecond( QgsProject::instance()->timeSettings()->framesPerSecond() );
373  mNavigationObject->setTemporalRangeCumulative( QgsProject::instance()->timeSettings()->isTemporalRangeCumulative() );
374 }
375 
376 void QgsTemporalControllerWidget::mNavigationOff_clicked()
377 {
378  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
379  static_cast<int>( QgsTemporalNavigationObject::NavigationOff ) );
380 
382  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
383 }
384 
385 void QgsTemporalControllerWidget::mNavigationFixedRange_clicked()
386 {
387  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
388  static_cast<int>( QgsTemporalNavigationObject::FixedRange ) );
389 
391  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::FixedRange );
392 }
393 
394 void QgsTemporalControllerWidget::mNavigationAnimated_clicked()
395 {
396  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
397  static_cast<int>( QgsTemporalNavigationObject::Animated ) );
398 
400  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::Animated );
401 }
402 
403 void QgsTemporalControllerWidget::setWidgetStateFromNavigationMode( const QgsTemporalNavigationObject::NavigationMode mode )
404 {
405  mNavigationOff->setChecked( mode == QgsTemporalNavigationObject::NavigationOff );
406  mNavigationFixedRange->setChecked( mode == QgsTemporalNavigationObject::FixedRange );
407  mNavigationAnimated->setChecked( mode == QgsTemporalNavigationObject::Animated );
408 
409  switch ( mode )
410  {
412  mNavigationModeStackedWidget->setCurrentIndex( 0 );
413  break;
415  mNavigationModeStackedWidget->setCurrentIndex( 1 );
416  break;
418  mNavigationModeStackedWidget->setCurrentIndex( 2 );
419  break;
420  }
421 }
422 
423 void QgsTemporalControllerWidget::onLayersAdded( const QList<QgsMapLayer *> &layers )
424 {
425  if ( !mHasTemporalLayersLoaded )
426  {
427  for ( QgsMapLayer *layer : layers )
428  {
429  if ( layer->temporalProperties() )
430  {
431  mHasTemporalLayersLoaded |= layer->temporalProperties()->isActive();
432 
433  if ( !mHasTemporalLayersLoaded )
434  {
435  connect( layer, &QgsMapLayer::dataSourceChanged, this, [ this, layer ]
436  {
437  if ( layer->isValid() && layer->temporalProperties()->isActive() && !mHasTemporalLayersLoaded )
438  {
439  mHasTemporalLayersLoaded = true;
440  firstTemporalLayerLoaded( layer );
441  mNavigationObject->setAvailableTemporalRanges( QgsTemporalUtils::usedTemporalRangesForProject( QgsProject::instance() ) );
442  }
443  } );
444  }
445 
446  firstTemporalLayerLoaded( layer );
447  }
448  }
449  }
450 
452 }
453 
454 void QgsTemporalControllerWidget::firstTemporalLayerLoaded( QgsMapLayer *layer )
455 {
456  setDatesToProjectTime();
457 
458  if ( QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer ) )
459  {
460  mBlockFrameDurationUpdates++;
461  setTimeStep( meshLayer->firstValidTimeStep() );
462  mBlockFrameDurationUpdates--;
463  updateFrameDuration();
464  }
465  else if ( QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer ) )
466  {
467  if ( rasterLayer->dataProvider() && rasterLayer->dataProvider()->temporalCapabilities() )
468  {
469  mBlockFrameDurationUpdates++;
470  setTimeStep( rasterLayer->dataProvider()->temporalCapabilities()->defaultInterval() );
471  mBlockFrameDurationUpdates--;
472  updateFrameDuration();
473  }
474  }
475 }
476 
477 void QgsTemporalControllerWidget::onProjectCleared()
478 {
479  mHasTemporalLayersLoaded = false;
480 
482  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
483 
484  // default to showing the last 24 hours, ending at the current date's hour, in one hour blocks...
485  // it's COMPLETELY arbitrary, but better than starting with a "zero length" duration!
486  const QTime startOfCurrentHour = QTime( QTime::currentTime().hour(), 0, 0 );
487  const QDateTime end = QDateTime( QDate::currentDate(), startOfCurrentHour, Qt::UTC );
488  const QDateTime start = end.addSecs( -24 * 60 * 60 );
489 
490  whileBlocking( mStartDateTime )->setDateTime( start );
491  whileBlocking( mEndDateTime )->setDateTime( end );
492  whileBlocking( mFixedRangeStartDateTime )->setDateTime( start );
493  whileBlocking( mFixedRangeEndDateTime )->setDateTime( end );
494 
495  updateTemporalExtent();
496  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsUnitTypes::TemporalHours ) );
497  mStepSpinBox->setValue( 1 );
498 }
499 
500 void QgsTemporalControllerWidget::updateSlider( const QgsDateTimeRange &range )
501 {
502  whileBlocking( mSlider )->setValue( mNavigationObject->currentFrameNumber() );
503  updateRangeLabel( range );
504 }
505 
506 void QgsTemporalControllerWidget::updateRangeLabel( const QgsDateTimeRange &range )
507 {
508  QString timeFrameFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss" );
509  // but if timesteps are < 1 second (as: in milliseconds), add milliseconds to the format
510  if ( mTimeStepsComboBox->currentIndex() == mTimeStepsComboBox->findData( QgsUnitTypes::TemporalMilliseconds ) )
511  timeFrameFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss.zzz" );
512  switch ( mNavigationObject->navigationMode() )
513  {
515  mCurrentRangeLabel->setText( tr( "Current frame: %1 ≤ <i>t</i> &lt; %2" ).arg(
516  range.begin().toString( timeFrameFormat ),
517  range.end().toString( timeFrameFormat ) ) );
518  break;
520  mCurrentRangeLabel->setText( tr( "Range: %1 ≤ <i>t</i> &lt; %2" ).arg(
521  range.begin().toString( timeFrameFormat ),
522  range.end().toString( timeFrameFormat ) ) );
523  break;
525  mCurrentRangeLabel->setText( tr( "Temporal navigation disabled" ) );
526  break;
527  }
528 }
529 
531 {
532  return mNavigationObject;
533 }
534 
535 void QgsTemporalControllerWidget::settings_clicked()
536 {
537  QgsTemporalMapSettingsWidget *settingsWidget = new QgsTemporalMapSettingsWidget( this );
538  settingsWidget->setFrameRateValue( mNavigationObject->framesPerSecond() );
539  settingsWidget->setIsTemporalRangeCumulative( mNavigationObject->temporalRangeCumulative() );
540 
541  connect( settingsWidget, &QgsTemporalMapSettingsWidget::frameRateChanged, this, [ = ]( double rate )
542  {
543  // save new settings into project
545  mNavigationObject->setFramesPerSecond( rate );
546  } );
547 
548  connect( settingsWidget, &QgsTemporalMapSettingsWidget::temporalRangeCumulativeChanged, this, [ = ]( bool state )
549  {
550  // save new settings into project
552  mNavigationObject->setTemporalRangeCumulative( state );
553  } );
554  openPanel( settingsWidget );
555 }
556 
557 void QgsTemporalControllerWidget::timeSlider_valueChanged( int value )
558 {
559  mNavigationObject->setCurrentFrameNumber( value );
560 }
561 
562 void QgsTemporalControllerWidget::startEndDateTime_changed()
563 {
564  whileBlocking( mFixedRangeStartDateTime )->setDateTime( mStartDateTime->dateTime() );
565  whileBlocking( mFixedRangeEndDateTime )->setDateTime( mEndDateTime->dateTime() );
566 
567  updateTemporalExtent();
568  saveRangeToProject();
569 }
570 
571 void QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed()
572 {
573  whileBlocking( mStartDateTime )->setDateTime( mFixedRangeStartDateTime->dateTime() );
574  whileBlocking( mEndDateTime )->setDateTime( mFixedRangeEndDateTime->dateTime() );
575 
576  updateTemporalExtent();
577  saveRangeToProject();
578 }
579 
580 void QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered()
581 {
582  setDatesToAllLayers();
583  saveRangeToProject();
584 }
585 
586 void QgsTemporalControllerWidget::setTimeStep( const QgsInterval &timeStep )
587 {
588  if ( ! timeStep.isValid() || timeStep.seconds() <= 0 )
589  return;
590 
591  int selectedUnit = -1;
592  double selectedValue = std::numeric_limits<double>::max();
594  {
595  // Search the time unit the most appropriate :
596  // the one that gives the smallest time step value for double spin box with round value (if possible) and/or the less signifiant digits
597 
598  int stringSize = std::numeric_limits<int>::max();
599  const int precision = mStepSpinBox->decimals();
600  for ( int i = 0; i < mTimeStepsComboBox->count(); ++i )
601  {
602  const QgsUnitTypes::TemporalUnit unit = static_cast<QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->itemData( i ).toInt() );
603  const double value = timeStep.seconds() * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::TemporalSeconds, unit );
604  QString string = QString::number( value, 'f', precision );
605 
606  const thread_local QRegularExpression trailingZeroRegEx = QRegularExpression( QStringLiteral( "0+$" ) );
607  //remove trailing zero
608  string.remove( trailingZeroRegEx );
609 
610  const thread_local QRegularExpression trailingPointRegEx = QRegularExpression( QStringLiteral( "[.]+$" ) );
611  //remove last point if present
612  string.remove( trailingPointRegEx );
613 
614  if ( value >= 1
615  && string.size() <= stringSize // less significant digit than currently selected
616  && value < selectedValue ) // less than currently selected
617  {
618  selectedUnit = i;
619  selectedValue = value;
620  stringSize = string.size();
621  }
622  else if ( string != '0'
623  && string.size() < precision + 2 //round value (ex: 0.xx with precision=3)
624  && string.size() < stringSize ) //less significant digit than currently selected
625  {
626  selectedUnit = i ;
627  selectedValue = value ;
628  stringSize = string.size();
629  }
630  }
631  }
632  else
633  {
634  selectedUnit = mTimeStepsComboBox->findData( static_cast< int >( timeStep.originalUnit() ) );
635  selectedValue = 1;
636  }
637 
638  if ( selectedUnit >= 0 )
639  {
640  mStepSpinBox->setValue( selectedValue );
641  mTimeStepsComboBox->setCurrentIndex( selectedUnit );
642  }
643 
644  updateFrameDuration();
645 }
646 
647 void QgsTemporalControllerWidget::updateTimeStepInputs( const QgsInterval &timeStep )
648 {
649  if ( ! timeStep.isValid() || timeStep.seconds() <= 0.0001 )
650  return;
651 
652  QString timeDisplayFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss" );
654  {
655  timeDisplayFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss.zzz" );
656  // very big change that you have to update the range too, as defaulting to NOT handling millis
657  updateTemporalExtent();
658  }
659  mStartDateTime->setDisplayFormat( timeDisplayFormat );
660  mEndDateTime->setDisplayFormat( timeDisplayFormat );
661  mFixedRangeStartDateTime->setDisplayFormat( timeDisplayFormat );
662  mFixedRangeEndDateTime->setDisplayFormat( timeDisplayFormat );
663 
664  // Only update ui when the intervals are different
665  if ( timeStep == QgsInterval( mStepSpinBox->value(),
666  static_cast< QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() ) ) )
667  return;
668 
670  {
671  mStepSpinBox->setValue( timeStep.originalDuration() );
672  mTimeStepsComboBox->setCurrentIndex( timeStep.originalUnit() );
673  }
674 
675  updateFrameDuration();
676 }
677 
678 void QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered()
679 {
680  setDatesToProjectTime();
681  saveRangeToProject();
682 }
683 
684 void QgsTemporalControllerWidget::setDates( const QgsDateTimeRange &range )
685 {
686  if ( range.begin().isValid() && range.end().isValid() )
687  {
688  whileBlocking( mStartDateTime )->setDateTime( range.begin() );
689  whileBlocking( mEndDateTime )->setDateTime( range.end() );
690  whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
691  whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
692  updateTemporalExtent();
693  }
694 }
695 
696 void QgsTemporalControllerWidget::setDatesToAllLayers()
697 {
698  QgsDateTimeRange range;
701 
702  setDates( range );
703 }
704 
705 void QgsTemporalControllerWidget::setDatesToProjectTime()
706 {
707  QgsDateTimeRange range;
708 
709  // by default try taking the project's fixed temporal extent
710  if ( QgsProject::instance()->timeSettings() )
712 
713  // if that's not set, calculate the extent from the project's layers
714  if ( !range.begin().isValid() || !range.end().isValid() )
715  {
717  }
718 
720 
721  setDates( range );
722 }
723 
724 void QgsTemporalControllerWidget::saveRangeToProject()
725 {
726  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ),
727  QStringLiteral( "/StartDateTime" ), mStartDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );
728  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ),
729  QStringLiteral( "/EndDateTime" ), mEndDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );
730 }
QgsUnitTypes::TemporalDecades
@ TemporalDecades
Decades.
Definition: qgsunittypes.h:159
QgsTemporalNavigationObject::rewindToStart
void rewindToStart()
Rewinds the temporal navigation to start of the temporal extent.
Definition: qgstemporalnavigationobject.cpp:302
QgsProject::writeEntry
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
Definition: qgsproject.cpp:2861
QgsTemporalControllerWidget::exportAnimation
void exportAnimation()
Triggered when an animation should be exported.
QgsTemporalNavigationObject::Animated
@ Animated
Temporal navigation relies on frames within a datetime range.
Definition: qgstemporalnavigationobject.h:54
QgsProjectTimeSettings::setIsTemporalRangeCumulative
void setIsTemporalRangeCumulative(bool state)
Sets the project's temporal range as cumulative in animation settings.
Definition: qgsprojecttimesettings.cpp:131
QgsInterval::seconds
double seconds() const
Returns the interval duration in seconds.
Definition: qgsinterval.h:236
qgsrasterlayer.h
QgsProjectTimeSettings::setFramesPerSecond
void setFramesPerSecond(double rate)
Sets the project's default animation frame rate, in frames per second.
Definition: qgsprojecttimesettings.cpp:121
QgsTemporalNavigationObject::totalFrameCount
long long totalFrameCount() const
Returns the total number of frames for the navigation.
Definition: qgstemporalnavigationobject.cpp:313
QgsProject::layersAdded
void layersAdded(const QList< QgsMapLayer * > &layers)
Emitted when one or more layers were added to the registry.
QgsInterval::originalDuration
double originalDuration() const
Returns the original interval duration.
Definition: qgsinterval.h:280
qgstemporalmapsettingswidget.h
QgsTemporalNavigationObject::setTemporalRangeCumulative
void setTemporalRangeCumulative(bool state)
Sets the animation temporal range as cumulative.
Definition: qgstemporalnavigationobject.cpp:247
QgsTemporalUtils::usedTemporalRangesForProject
static QList< QgsDateTimeRange > usedTemporalRangesForProject(QgsProject *project)
Calculates all temporal ranges which are in use for a project.
Definition: qgstemporalutils.cpp:56
QgsTemporalNavigationObject::Reverse
@ Reverse
Animation is playing in reverse.
Definition: qgstemporalnavigationobject.h:62
QgsTemporalNavigationObject::setAvailableTemporalRanges
void setAvailableTemporalRanges(const QList< QgsDateTimeRange > &ranges)
Sets the list of all available temporal ranges which have data available.
Definition: qgstemporalnavigationobject.cpp:181
qgsgui.h
QgsUnitTypes::TemporalYears
@ TemporalYears
Years.
Definition: qgsunittypes.h:158
QgsProject::cleared
void cleared()
Emitted when the project is cleared (and additionally when an open project is cleared just before a n...
QgsTemporalControllerWidget::applySizeConstraintsToStack
bool applySizeConstraintsToStack() const override
Returns true if the size constraints and hints for the panel widget should be applied to the parent Q...
Definition: qgstemporalcontrollerwidget.cpp:178
QgsUnitTypes::TemporalDays
@ TemporalDays
Days.
Definition: qgsunittypes.h:155
QgsPanelWidget::openPanel
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
Definition: qgspanelwidget.cpp:84
QgsTemporalNavigationObject::navigationMode
NavigationMode navigationMode() const
Returns the current temporal navigation mode.
Definition: qgstemporalnavigationobject.h:92
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
QgsUnitTypes::TemporalUnknownUnit
@ TemporalUnknownUnit
Unknown time unit.
Definition: qgsunittypes.h:162
QgsProject::readProject
void readProject(const QDomDocument &)
Emitted when a project is being read.
QgsMapLayerTemporalProperties::calculateTemporalExtent
virtual QgsDateTimeRange calculateTemporalExtent(QgsMapLayer *layer) const
Attempts to calculate the overall temporal extent for the specified layer, using the settings defined...
Definition: qgsmaplayertemporalproperties.cpp:30
QgsMapLayerModel::data
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Definition: qgsmaplayermodel.cpp:295
QgsProject::readEntry
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
Definition: qgsproject.cpp:2946
QgsTemporalNavigationObject::playForward
void playForward()
Starts the animation playing in a forward direction up till the end of all frames.
Definition: qgstemporalnavigationobject.cpp:268
QgsUnitTypes::TemporalUnit
TemporalUnit
Temporal units.
Definition: qgsunittypes.h:149
QgsTemporalNavigationObject::NavigationMode
NavigationMode
Represents the current temporal navigation mode.
Definition: qgstemporalnavigationobject.h:51
QgsTemporalNavigationObject::Forward
@ Forward
Animation is playing forward.
Definition: qgstemporalnavigationobject.h:61
QgsProjectTimeSettings::temporalRange
QgsDateTimeRange temporalRange() const
Returns the project's temporal range, which indicates the earliest and latest datetime ranges associa...
Definition: qgsprojecttimesettings.cpp:34
QgsTemporalControllerWidget::temporalController
QgsTemporalNavigationObject * temporalController()
Returns the temporal controller object used by this object in navigation.
Definition: qgstemporalcontrollerwidget.cpp:530
QgsUnitTypes::TemporalSeconds
@ TemporalSeconds
Seconds.
Definition: qgsunittypes.h:152
QgsUnitTypes::TemporalIrregularStep
@ TemporalIrregularStep
Special "irregular step" time unit, used for temporal data which uses irregular, non-real-world unit ...
Definition: qgsunittypes.h:161
QgsTemporalNavigationObject::temporalExtentsChanged
void temporalExtentsChanged(const QgsDateTimeRange &extent)
Emitted whenever the temporalExtent extent changes.
qgstemporalcontrollerwidget.h
qgsapplication.h
QgsTemporalNavigationObject::navigationModeChanged
void navigationModeChanged(QgsTemporalNavigationObject::NavigationMode mode)
Emitted whenever the navigation mode changes.
QgsTemporalNavigationObject::setLooping
void setLooping(bool loop)
Sets whether the animation should loop after hitting the end or start frame.
Definition: qgstemporalnavigationobject.cpp:68
QgsUnitTypes::TemporalMonths
@ TemporalMonths
Months.
Definition: qgsunittypes.h:157
QgsProjectTimeSettings::timeStepUnit
QgsUnitTypes::TemporalUnit timeStepUnit() const
Returns the project's time step (length of one animation frame) unit, which is used as the default va...
Definition: qgsprojecttimesettings.cpp:101
QgsMapLayer::dataSourceChanged
void dataSourceChanged()
Emitted whenever the layer's data source has been changed.
QgsUnitTypes::fromUnitToUnitFactor
static Q_INVOKABLE double fromUnitToUnitFactor(QgsUnitTypes::DistanceUnit fromUnit, QgsUnitTypes::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
Definition: qgsunittypes.cpp:352
QgsTemporalControllerWidget::QgsTemporalControllerWidget
QgsTemporalControllerWidget(QWidget *parent=nullptr)
Constructor for QgsTemporalControllerWidget, with the specified parent widget.
Definition: qgstemporalcontrollerwidget.cpp:34
QgsMapLayerModel::rowCount
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Definition: qgsmaplayermodel.cpp:280
precision
int precision
Definition: qgswfsgetfeature.cpp:103
qgsmaplayertemporalproperties.h
QgsTemporalNavigationObject::playBackward
void playBackward()
Starts the animation playing in a reverse direction until the beginning of the time range.
Definition: qgstemporalnavigationobject.cpp:280
QgsPanelWidget
Base class for any widget that can be shown as a inline panel.
Definition: qgspanelwidget.h:29
QgsProject::timeSettings
const QgsProjectTimeSettings * timeSettings() const
Returns the project's time settings, which contains the project's temporal range and other time based...
Definition: qgsproject.cpp:3511
QgsTemporalProperty::isActive
bool isActive() const
Returns true if the temporal property is active.
Definition: qgstemporalproperty.cpp:36
QgsTemporalNavigationObject::skipToEnd
void skipToEnd()
Skips the temporal navigation to end of the temporal extent.
Definition: qgstemporalnavigationobject.cpp:307
whileBlocking
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2191
QgsTemporalNavigationObject::setCurrentFrameNumber
void setCurrentFrameNumber(long long frame)
Sets the current animation frame number.
Definition: qgstemporalnavigationobject.cpp:191
QgsUnitTypes::toString
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
Definition: qgsunittypes.cpp:199
QgsTemporalNavigationObject::stateChanged
void stateChanged(QgsTemporalNavigationObject::AnimationState state)
Emitted whenever the animation state changes.
QgsMeshLayer
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:98
QgsTemporalNavigationObject::setTemporalExtents
void setTemporalExtents(const QgsDateTimeRange &extents)
Sets the navigation temporal extents, which dictate the earliest and latest date time possible in the...
Definition: qgstemporalnavigationobject.cpp:144
QgsTemporalNavigationObject::setFramesPerSecond
void setFramesPerSecond(double rate)
Sets the animation frame rate, in frames per second.
Definition: qgstemporalnavigationobject.cpp:233
QgsMapLayer::temporalProperties
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1502
QgsTemporalNavigationObject::Idle
@ Idle
Animation is paused.
Definition: qgstemporalnavigationobject.h:63
QgsTemporalNavigationObject::setFrameDuration
void setFrameDuration(const QgsInterval &duration)
Sets the frame duration, which dictates the temporal length of each frame in the animation.
Definition: qgstemporalnavigationobject.cpp:208
QgsRasterLayer
Represents a raster layer.
Definition: qgsrasterlayer.h:76
QgsTemporalNavigationObject
Implements a temporal controller based on a frame by frame navigation and animation.
Definition: qgstemporalnavigationobject.h:39
qgstemporalutils.h
qgsmeshlayer.h
QgsUnitTypes::TemporalHours
@ TemporalHours
Hours.
Definition: qgsunittypes.h:154
qgsprojecttimesettings.h
QgsUnitTypes::TemporalMinutes
@ TemporalMinutes
Minutes.
Definition: qgsunittypes.h:153
QgsUnitTypes::TemporalMilliseconds
@ TemporalMilliseconds
Milliseconds.
Definition: qgsunittypes.h:151
QgsTemporalController::updateTemporalRange
void updateTemporalRange(const QgsDateTimeRange &range)
Signals that a temporal range has changed and needs to be updated in all connected objects.
QgsUnitTypes::TemporalCenturies
@ TemporalCenturies
Centuries.
Definition: qgsunittypes.h:160
QgsTemporalNavigationObject::pause
void pause()
Pauses the temporal navigation.
Definition: qgstemporalnavigationobject.cpp:262
qgsmaplayermodel.h
QgsTemporalNavigationObject::next
void next()
Advances to the next frame.
Definition: qgstemporalnavigationobject.cpp:292
QgsUnitTypes::TemporalWeeks
@ TemporalWeeks
Weeks.
Definition: qgsunittypes.h:156
QgsTemporalNavigationObject::temporalFrameDurationChanged
void temporalFrameDurationChanged(const QgsInterval &interval)
Emitted whenever the frameDuration interval of the controller changes.
QgsTemporalNavigationObject::temporalRangeCumulative
bool temporalRangeCumulative() const
Returns the animation temporal range cumulative settings.
Definition: qgstemporalnavigationobject.cpp:252
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsMapLayerModel::LayerRole
@ LayerRole
Stores pointer to the map layer itself.
Definition: qgsmaplayermodel.h:52
QgsInterval
A representation of the interval between two datetime values.
Definition: qgsinterval.h:41
QgsTemporalNavigationObject::previous
void previous()
Jumps back to the previous frame.
Definition: qgstemporalnavigationobject.cpp:297
QgsTemporalNavigationObject::NavigationOff
@ NavigationOff
Temporal navigation is disabled.
Definition: qgstemporalnavigationobject.h:53
QgsTemporalNavigationObject::AnimationState
AnimationState
Represents the current animation state.
Definition: qgstemporalnavigationobject.h:59
QgsTemporalControllerWidget::keyPressEvent
void keyPressEvent(QKeyEvent *e) override
Definition: qgstemporalcontrollerwidget.cpp:183
QgsApplication::getThemeIcon
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Definition: qgsapplication.cpp:693
QgsMapLayerModel::index
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Definition: qgsmaplayermodel.cpp:254
QgsTemporalNavigationObject::currentFrameNumber
long long currentFrameNumber() const
Returns the current frame number.
Definition: qgstemporalnavigationobject.cpp:203
QgsInterval::isValid
bool isValid() const
Returns true if the interval is valid.
Definition: qgsinterval.h:255
QgsProjectTimeSettings::setTimeStep
void setTimeStep(double step)
Sets the project's time step (length of one animation frame), which is used as the default value when...
Definition: qgsprojecttimesettings.cpp:116
qgsproject.h
QgsProjectTimeSettings::setTimeStepUnit
void setTimeStepUnit(QgsUnitTypes::TemporalUnit unit)
Sets the project's time step (length of one animation frame) unit, which is used as the default value...
Definition: qgsprojecttimesettings.cpp:106
QgsTemporalNavigationObject::setNavigationMode
void setNavigationMode(const NavigationMode mode)
Sets the temporal navigation mode.
Definition: qgstemporalnavigationobject.cpp:119
QgsTemporalNavigationObject::FixedRange
@ FixedRange
Temporal navigation relies on a fixed datetime range.
Definition: qgstemporalnavigationobject.h:55
QgsInterval::originalUnit
QgsUnitTypes::TemporalUnit originalUnit() const
Returns the original interval temporal unit.
Definition: qgsinterval.h:295
QgsProject::readNumEntry
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
Definition: qgsproject.cpp:2972
QgsMapLayerModel
The QgsMapLayerModel class is a model to display layers in widgets.
Definition: qgsmaplayermodel.h:37
QgsTemporalNavigationObject::framesPerSecond
double framesPerSecond() const
Returns the animation frame rate, in frames per second.
Definition: qgstemporalnavigationobject.cpp:242
QgsTemporalNavigationObject::animationState
AnimationState animationState() const
Returns the current animation state.
Definition: qgstemporalnavigationobject.cpp:335
QgsTemporalUtils::calculateTemporalRangeForProject
static QgsDateTimeRange calculateTemporalRangeForProject(QgsProject *project)
Calculates the temporal range for a project.
Definition: qgstemporalutils.cpp:33