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