QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 
29 #include <QAction>
30 #include <QMenu>
31 
33  : QgsPanelWidget( parent )
34 {
35  setupUi( this );
36 
37  mNavigationObject = new QgsTemporalNavigationObject( this );
38 
39  mStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
40  mEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
41  mFixedRangeStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
42  mFixedRangeEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
43 
44  connect( mForwardButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePlayForward );
45  connect( mBackButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePlayBackward );
46  connect( mStopButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePause );
47  connect( mNextButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::next );
48  connect( mPreviousButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::previous );
49  connect( mFastForwardButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::skipToEnd );
50  connect( mRewindButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::rewindToStart );
51  connect( mLoopingCheckBox, &QCheckBox::toggled, this, [ = ]( bool state ) { mNavigationObject->setLooping( state ); } );
52 
53  setWidgetStateFromNavigationMode( mNavigationObject->navigationMode() );
54  connect( mNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsTemporalControllerWidget::setWidgetStateFromNavigationMode );
55  connect( mNavigationObject, &QgsTemporalNavigationObject::temporalExtentsChanged, this, &QgsTemporalControllerWidget::setDates );
56  connect( mNavigationObject, &QgsTemporalNavigationObject::temporalFrameDurationChanged, this, [ = ]( const QgsInterval & timeStep )
57  {
58  if ( mBlockFrameDurationUpdates )
59  return;
60 
61  mBlockFrameDurationUpdates++;
62  updateTimeStepInputs( timeStep );
63  mBlockFrameDurationUpdates--;
64  } );
65  connect( mNavigationOff, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationOff_clicked );
66  connect( mNavigationFixedRange, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationFixedRange_clicked );
67  connect( mNavigationAnimated, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationAnimated_clicked );
68 
69  connect( mNavigationObject, &QgsTemporalNavigationObject::stateChanged, this, [ = ]( QgsTemporalNavigationObject::AnimationState state )
70  {
71  mForwardButton->setChecked( state == QgsTemporalNavigationObject::Forward );
72  mBackButton->setChecked( state == QgsTemporalNavigationObject::Reverse );
73  mStopButton->setChecked( state == QgsTemporalNavigationObject::Idle );
74  } );
75 
76  connect( mStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
77  connect( mEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
78  connect( mFixedRangeStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
79  connect( mFixedRangeEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
80  connect( mStepSpinBox, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
81  connect( mTimeStepsComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
82  connect( mSlider, &QSlider::valueChanged, this, &QgsTemporalControllerWidget::timeSlider_valueChanged );
83 
84  mStepSpinBox->setClearValue( 1 );
85 
86  connect( mNavigationObject, &QgsTemporalNavigationObject::updateTemporalRange, this, &QgsTemporalControllerWidget::updateSlider );
87 
88  connect( mSettings, &QPushButton::clicked, this, &QgsTemporalControllerWidget::settings_clicked );
89 
90  mMapLayerModel = new QgsMapLayerModel( this );
91 
92  mRangeMenu.reset( new QMenu( this ) );
93 
94  mRangeSetToAllLayersAction = new QAction( tr( "Set to Full Range" ), mRangeMenu.get() );
95  mRangeSetToAllLayersAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRefresh.svg" ) ) );
96  connect( mRangeSetToAllLayersAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered );
97  mRangeMenu->addAction( mRangeSetToAllLayersAction );
98 
99  mRangeSetToProjectAction = new QAction( tr( "Set to Preset Project Range" ), mRangeMenu.get() );
100  connect( mRangeSetToProjectAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered );
101  mRangeMenu->addAction( mRangeSetToProjectAction );
102 
103  mRangeMenu->addSeparator();
104 
105  mRangeLayersSubMenu.reset( new QMenu( tr( "Set to Single Layer's Range" ), mRangeMenu.get() ) );
106  mRangeLayersSubMenu->setEnabled( false );
107  mRangeMenu->addMenu( mRangeLayersSubMenu.get() );
108  connect( mRangeMenu.get(), &QMenu::aboutToShow, this, &QgsTemporalControllerWidget::aboutToShowRangeMenu );
109 
110  mSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
111  mSetRangeButton->setMenu( mRangeMenu.get() );
112  mSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
113  mFixedRangeSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
114  mFixedRangeSetRangeButton->setMenu( mRangeMenu.get() );
115  mFixedRangeSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
116 
117  connect( mExportAnimationButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::exportAnimation );
118 
119  QgsDateTimeRange range;
120 
121  if ( QgsProject::instance()->timeSettings() )
123 
124  if ( range.begin().isValid() && range.end().isValid() )
125  {
126  whileBlocking( mStartDateTime )->setDateTime( range.begin() );
127  whileBlocking( mEndDateTime )->setDateTime( range.end() );
128  whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
129  whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
130  }
131 
133  {
144  } )
145  {
146  mTimeStepsComboBox->addItem( QgsUnitTypes::toString( u ), u );
147  }
148 
149  // TODO: might want to choose an appropriate default unit based on the range
150  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsUnitTypes::TemporalHours ) );
151 
152  // NOTE 'minimum' and 'decimals' should be in sync with the 'decimals' in qgstemporalcontrollerwidgetbase.ui
153  mStepSpinBox->setDecimals( 3 );
154  // minimum timestep one millisecond
155  mStepSpinBox->setMinimum( 0.001 );
156  mStepSpinBox->setMaximum( std::numeric_limits<int>::max() );
157  mStepSpinBox->setSingleStep( 1 );
158  mStepSpinBox->setValue( 1 );
159 
160  mForwardButton->setToolTip( tr( "Play" ) );
161  mBackButton->setToolTip( tr( "Reverse" ) );
162  mNextButton->setToolTip( tr( "Go to next frame" ) );
163  mPreviousButton->setToolTip( tr( "Go to previous frame" ) );
164  mStopButton->setToolTip( tr( "Pause" ) );
165  mRewindButton->setToolTip( tr( "Rewind to start" ) );
166  mFastForwardButton->setToolTip( tr( "Fast forward to end" ) );
167 
168  updateFrameDuration();
169 
170  connect( QgsProject::instance(), &QgsProject::readProject, this, &QgsTemporalControllerWidget::setWidgetStateFromProject );
171  connect( QgsProject::instance(), &QgsProject::layersAdded, this, &QgsTemporalControllerWidget::onLayersAdded );
172  connect( QgsProject::instance(), &QgsProject::cleared, this, &QgsTemporalControllerWidget::onProjectCleared );
173 }
174 
176 {
177  if ( mSlider->hasFocus() && e->key() == Qt::Key_Space )
178  {
179  togglePause();
180  }
181  QWidget::keyPressEvent( e );
182 }
183 
184 void QgsTemporalControllerWidget::aboutToShowRangeMenu()
185 {
186  QgsDateTimeRange projectRange;
187  if ( QgsProject::instance()->timeSettings() )
188  projectRange = QgsProject::instance()->timeSettings()->temporalRange();
189  mRangeSetToProjectAction->setEnabled( projectRange.begin().isValid() && projectRange.end().isValid() );
190 
191  mRangeLayersSubMenu->clear();
192  for ( int i = 0; i < mMapLayerModel->rowCount(); ++i )
193  {
194  QModelIndex index = mMapLayerModel->index( i, 0 );
195  QgsMapLayer *currentLayer = mMapLayerModel->data( index, QgsMapLayerModel::LayerRole ).value<QgsMapLayer *>();
196  if ( !currentLayer->temporalProperties() || !currentLayer->temporalProperties()->isActive() )
197  continue;
198 
199  QIcon icon = qvariant_cast<QIcon>( mMapLayerModel->data( index, Qt::DecorationRole ) );
200  QString text = mMapLayerModel->data( index, Qt::DisplayRole ).toString();
201  QgsDateTimeRange range = currentLayer->temporalProperties()->calculateTemporalExtent( currentLayer );
202  if ( range.begin().isValid() && range.end().isValid() )
203  {
204  QAction *action = new QAction( icon, text, mRangeLayersSubMenu.get() );
205  connect( action, &QAction::triggered, this, [ = ]
206  {
207  setDates( range );
208  saveRangeToProject();
209  } );
210  mRangeLayersSubMenu->addAction( action );
211  }
212  }
213  mRangeLayersSubMenu->setEnabled( !mRangeLayersSubMenu->actions().isEmpty() );
214 }
215 
216 void QgsTemporalControllerWidget::togglePlayForward()
217 {
218  mPlayingForward = true;
219 
220  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Forward )
221  {
222  mStopButton->setChecked( false );
223  mBackButton->setChecked( false );
224  mForwardButton->setChecked( true );
225  mNavigationObject->playForward();
226  }
227  else
228  {
229  mBackButton->setChecked( true );
230  mForwardButton->setChecked( false );
231  mNavigationObject->pause();
232  }
233 }
234 
235 void QgsTemporalControllerWidget::togglePlayBackward()
236 {
237  mPlayingForward = false;
238 
239  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Reverse )
240  {
241  mStopButton->setChecked( false );
242  mBackButton->setChecked( true );
243  mForwardButton->setChecked( false );
244  mNavigationObject->playBackward();
245  }
246  else
247  {
248  mBackButton->setChecked( true );
249  mBackButton->setChecked( false );
250  mNavigationObject->pause();
251  }
252 }
253 
254 void QgsTemporalControllerWidget::togglePause()
255 {
256  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Idle )
257  {
258  mStopButton->setChecked( true );
259  mBackButton->setChecked( false );
260  mForwardButton->setChecked( false );
261  mNavigationObject->pause();
262  }
263  else
264  {
265  mBackButton->setChecked( mPlayingForward ? false : true );
266  mForwardButton->setChecked( mPlayingForward ? false : true );
267  if ( mPlayingForward )
268  {
269  mNavigationObject->playForward();
270  }
271  else
272  {
273  mNavigationObject->playBackward();
274  }
275  }
276 }
277 
278 void QgsTemporalControllerWidget::updateTemporalExtent()
279 {
280  QgsDateTimeRange temporalExtent = QgsDateTimeRange( mStartDateTime->dateTime(),
281  mEndDateTime->dateTime() );
282  mNavigationObject->setTemporalExtents( temporalExtent );
283  mSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
284  mSlider->setValue( mNavigationObject->currentFrameNumber() );
285 }
286 
287 void QgsTemporalControllerWidget::updateFrameDuration()
288 {
289  if ( mBlockSettingUpdates )
290  return;
291 
292  // save new settings into project
293  QgsProject::instance()->timeSettings()->setTimeStepUnit( static_cast< QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() ) );
294  QgsProject::instance()->timeSettings()->setTimeStep( mStepSpinBox->value() );
295 
296  if ( !mBlockFrameDurationUpdates )
297  {
298  mNavigationObject->setFrameDuration(
299  QgsInterval( QgsProject::instance()->timeSettings()->timeStep(),
300  QgsProject::instance()->timeSettings()->timeStepUnit() ) );
301  mSlider->setValue( mNavigationObject->currentFrameNumber() );
302  }
303  mSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
304  mSlider->setValue( mNavigationObject->currentFrameNumber() );
305 }
306 
307 void QgsTemporalControllerWidget::setWidgetStateFromProject()
308 {
309  mBlockSettingUpdates++;
310  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsProject::instance()->timeSettings()->timeStepUnit() ) );
311  mStepSpinBox->setValue( QgsProject::instance()->timeSettings()->timeStep() );
312  mBlockSettingUpdates--;
313 
314  bool ok = false;
315  QgsTemporalNavigationObject::NavigationMode mode = static_cast< QgsTemporalNavigationObject::NavigationMode>( QgsProject::instance()->readNumEntry( QStringLiteral( "TemporalControllerWidget" ),
316  QStringLiteral( "/NavigationMode" ), 0, &ok ) );
317  if ( ok )
318  {
319  mNavigationObject->setNavigationMode( mode );
320  setWidgetStateFromNavigationMode( mode );
321  }
322  else
323  {
325  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
326  }
327 
328  const QString startString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/StartDateTime" ) );
329  const QString endString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/EndDateTime" ) );
330  if ( !startString.isEmpty() && !endString.isEmpty() )
331  {
332  whileBlocking( mStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
333  whileBlocking( mEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
334  whileBlocking( mFixedRangeStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
335  whileBlocking( mFixedRangeEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
336  }
337  else
338  {
339  setDatesToProjectTime();
340  }
341  updateTemporalExtent();
342  updateFrameDuration();
343 
344  mNavigationObject->setFramesPerSecond( QgsProject::instance()->timeSettings()->framesPerSecond() );
345  mNavigationObject->setTemporalRangeCumulative( QgsProject::instance()->timeSettings()->isTemporalRangeCumulative() );
346 }
347 
348 void QgsTemporalControllerWidget::mNavigationOff_clicked()
349 {
350  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
351  static_cast<int>( QgsTemporalNavigationObject::NavigationOff ) );
352 
354  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
355 }
356 
357 void QgsTemporalControllerWidget::mNavigationFixedRange_clicked()
358 {
359  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
360  static_cast<int>( QgsTemporalNavigationObject::FixedRange ) );
361 
363  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::FixedRange );
364 }
365 
366 void QgsTemporalControllerWidget::mNavigationAnimated_clicked()
367 {
368  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
369  static_cast<int>( QgsTemporalNavigationObject::Animated ) );
370 
372  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::Animated );
373 }
374 
375 void QgsTemporalControllerWidget::setWidgetStateFromNavigationMode( const QgsTemporalNavigationObject::NavigationMode mode )
376 {
377  mNavigationOff->setChecked( mode == QgsTemporalNavigationObject::NavigationOff );
378  mNavigationFixedRange->setChecked( mode == QgsTemporalNavigationObject::FixedRange );
379  mNavigationAnimated->setChecked( mode == QgsTemporalNavigationObject::Animated );
380 
381  switch ( mode )
382  {
384  mNavigationModeStackedWidget->setCurrentIndex( 0 );
385  break;
387  mNavigationModeStackedWidget->setCurrentIndex( 1 );
388  break;
390  mNavigationModeStackedWidget->setCurrentIndex( 2 );
391  break;
392  }
393 }
394 
395 void QgsTemporalControllerWidget::onLayersAdded( const QList<QgsMapLayer *> &layers )
396 {
397  if ( !mHasTemporalLayersLoaded )
398  {
399  for ( QgsMapLayer *layer : layers )
400  {
401  if ( layer->temporalProperties() )
402  {
403  mHasTemporalLayersLoaded |= layer->temporalProperties()->isActive();
404 
405  if ( !mHasTemporalLayersLoaded )
406  {
407  connect( layer, &QgsMapLayer::dataSourceChanged, this, [ this, layer ]
408  {
409  if ( layer->isValid() && layer->temporalProperties()->isActive() && !mHasTemporalLayersLoaded )
410  {
411  mHasTemporalLayersLoaded = true;
412  firstTemporalLayerLoaded( layer );
413  }
414  } );
415  }
416 
417  firstTemporalLayerLoaded( layer );
418  }
419  }
420  }
421 }
422 
423 void QgsTemporalControllerWidget::firstTemporalLayerLoaded( QgsMapLayer *layer )
424 {
425  setDatesToProjectTime();
426 
427  QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer );
428  if ( meshLayer )
429  {
430  mBlockFrameDurationUpdates++;
431  setTimeStep( meshLayer->firstValidTimeStep() );
432  mBlockFrameDurationUpdates--;
433  updateFrameDuration();
434  }
435 }
436 
437 void QgsTemporalControllerWidget::onProjectCleared()
438 {
439  mHasTemporalLayersLoaded = false;
440 
442  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
443 
444  whileBlocking( mStartDateTime )->setDateTime( QDateTime( QDate::currentDate(), QTime( 0, 0, 0 ), Qt::UTC ) );
445  whileBlocking( mEndDateTime )->setDateTime( mStartDateTime->dateTime() );
446  whileBlocking( mFixedRangeStartDateTime )->setDateTime( QDateTime( QDate::currentDate(), QTime( 0, 0, 0 ), Qt::UTC ) );
447  whileBlocking( mFixedRangeEndDateTime )->setDateTime( mStartDateTime->dateTime() );
448  updateTemporalExtent();
449  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsUnitTypes::TemporalHours ) );
450  mStepSpinBox->setValue( 1 );
451 }
452 
453 void QgsTemporalControllerWidget::updateSlider( const QgsDateTimeRange &range )
454 {
455  whileBlocking( mSlider )->setValue( mNavigationObject->currentFrameNumber() );
456  updateRangeLabel( range );
457 }
458 
459 void QgsTemporalControllerWidget::updateRangeLabel( const QgsDateTimeRange &range )
460 {
461  QString timeFrameFormat = "yyyy-MM-dd HH:mm:ss";
462  // but if timesteps are < 1 second (as: in milliseconds), add milliseconds to the format
463  if ( mTimeStepsComboBox->currentIndex() == mTimeStepsComboBox->findData( QgsUnitTypes::TemporalMilliseconds ) )
464  timeFrameFormat = "yyyy-MM-dd HH:mm:ss.zzz";
465  switch ( mNavigationObject->navigationMode() )
466  {
468  mCurrentRangeLabel->setText( tr( "Frame: %1 to %2" ).arg(
469  range.begin().toString( timeFrameFormat ),
470  range.end().toString( timeFrameFormat ) ) );
471  break;
473  mCurrentRangeLabel->setText( tr( "Range: %1 to %2" ).arg(
474  range.begin().toString( timeFrameFormat ),
475  range.end().toString( timeFrameFormat ) ) );
476  break;
478  mCurrentRangeLabel->setText( tr( "Temporal navigation disabled" ) );
479  break;
480  }
481 }
482 
484 {
485  return mNavigationObject;
486 }
487 
488 void QgsTemporalControllerWidget::settings_clicked()
489 {
490  QgsTemporalMapSettingsWidget *settingsWidget = new QgsTemporalMapSettingsWidget( this );
491  settingsWidget->setFrameRateValue( mNavigationObject->framesPerSecond() );
492  settingsWidget->setIsTemporalRangeCumulative( mNavigationObject->temporalRangeCumulative() );
493 
494  connect( settingsWidget, &QgsTemporalMapSettingsWidget::frameRateChanged, this, [ = ]( double rate )
495  {
496  // save new settings into project
498  mNavigationObject->setFramesPerSecond( rate );
499  } );
500 
501  connect( settingsWidget, &QgsTemporalMapSettingsWidget::temporalRangeCumulativeChanged, this, [ = ]( bool state )
502  {
503  // save new settings into project
505  mNavigationObject->setTemporalRangeCumulative( state );
506  } );
507  openPanel( settingsWidget );
508 }
509 
510 void QgsTemporalControllerWidget::timeSlider_valueChanged( int value )
511 {
512  mNavigationObject->setCurrentFrameNumber( value );
513 }
514 
515 void QgsTemporalControllerWidget::startEndDateTime_changed()
516 {
517  whileBlocking( mFixedRangeStartDateTime )->setDateTime( mStartDateTime->dateTime() );
518  whileBlocking( mFixedRangeEndDateTime )->setDateTime( mEndDateTime->dateTime() );
519 
520  updateTemporalExtent();
521  saveRangeToProject();
522 }
523 
524 void QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed()
525 {
526  whileBlocking( mStartDateTime )->setDateTime( mFixedRangeStartDateTime->dateTime() );
527  whileBlocking( mEndDateTime )->setDateTime( mFixedRangeEndDateTime->dateTime() );
528 
529  updateTemporalExtent();
530  saveRangeToProject();
531 }
532 
533 void QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered()
534 {
535  setDatesToAllLayers();
536  saveRangeToProject();
537 }
538 
539 void QgsTemporalControllerWidget::setTimeStep( const QgsInterval &timeStep )
540 {
541  if ( ! timeStep.isValid() || timeStep.seconds() <= 0 )
542  return;
543 
544  // Search the time unit the most appropriate :
545  // the one that gives the smallest time step value for double spin box with round value (if possible) and/or the less signifiant digits
546 
547  int selectedUnit = -1;
548  int stringSize = std::numeric_limits<int>::max();
549  int precision = mStepSpinBox->decimals();
550  double selectedValue = std::numeric_limits<double>::max();
551  for ( int i = 0; i < mTimeStepsComboBox->count(); ++i )
552  {
553  QgsUnitTypes::TemporalUnit unit = static_cast<QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->itemData( i ).toInt() );
554  double value = timeStep.seconds() * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::TemporalSeconds, unit );
555  QString string = QString::number( value, 'f', precision );
556  string.remove( QRegExp( "0+$" ) ); //remove trailing zero
557  string.remove( QRegExp( "[.]+$" ) ); //remove last point if present
558 
559  if ( value >= 1
560  && string.size() <= stringSize // less significant digit than currently selected
561  && value < selectedValue ) // less than currently selected
562  {
563  selectedUnit = i;
564  selectedValue = value;
565  stringSize = string.size();
566  }
567  else if ( string != '0'
568  && string.size() < precision + 2 //round value (ex: 0.xx with precision=3)
569  && string.size() < stringSize ) //less significant digit than currently selected
570  {
571  selectedUnit = i ;
572  selectedValue = value ;
573  stringSize = string.size();
574  }
575  }
576 
577  if ( selectedUnit >= 0 )
578  {
579  mStepSpinBox->setValue( selectedValue );
580  mTimeStepsComboBox->setCurrentIndex( selectedUnit );
581  }
582 
583  updateFrameDuration();
584 }
585 
586 void QgsTemporalControllerWidget::updateTimeStepInputs( const QgsInterval &timeStep )
587 {
588  if ( ! timeStep.isValid() || timeStep.seconds() <= 0.0001 )
589  return;
590 
591  QString timeDisplayFormat = "yyyy-MM-dd HH:mm:ss";
593  {
594  timeDisplayFormat = "yyyy-MM-dd HH:mm:ss.zzz";
595  // very big change that you have to update the range too, as defaulting to NOT handling millis
596  updateTemporalExtent();
597  }
598  mStartDateTime->setDisplayFormat( timeDisplayFormat );
599  mEndDateTime->setDisplayFormat( timeDisplayFormat );
600  mFixedRangeStartDateTime->setDisplayFormat( timeDisplayFormat );
601  mFixedRangeEndDateTime->setDisplayFormat( timeDisplayFormat );
602 
603  // Only update ui when the intervals are different
604  if ( timeStep == QgsInterval( mStepSpinBox->value(),
605  static_cast< QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() ) ) )
606  return;
607 
609  {
610  mStepSpinBox->setValue( timeStep.originalDuration() );
611  mTimeStepsComboBox->setCurrentIndex( timeStep.originalUnit() );
612  }
613 
614  updateFrameDuration();
615 }
616 
617 void QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered()
618 {
619  setDatesToProjectTime();
620  saveRangeToProject();
621 }
622 
623 void QgsTemporalControllerWidget::setDates( const QgsDateTimeRange &range )
624 {
625  if ( range.begin().isValid() && range.end().isValid() )
626  {
627  whileBlocking( mStartDateTime )->setDateTime( range.begin() );
628  whileBlocking( mEndDateTime )->setDateTime( range.end() );
629  whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
630  whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
631  updateTemporalExtent();
632  }
633 }
634 
635 void QgsTemporalControllerWidget::setDatesToAllLayers()
636 {
637  QgsDateTimeRange range;
639  setDates( range );
640 }
641 
642 void QgsTemporalControllerWidget::setDatesToProjectTime()
643 {
644  QgsDateTimeRange range;
645 
646  // by default try taking the project's fixed temporal extent
647  if ( QgsProject::instance()->timeSettings() )
649 
650  // if that's not set, calculate the extent from the project's layers
651  if ( !range.begin().isValid() || !range.end().isValid() )
652  {
654  }
655 
656  setDates( range );
657 }
658 
659 void QgsTemporalControllerWidget::saveRangeToProject()
660 {
661  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ),
662  QStringLiteral( "/StartDateTime" ), mStartDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );
663  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ),
664  QStringLiteral( "/EndDateTime" ), mEndDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );
665 }
static QIcon getThemeIcon(const QString &name)
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:85
void dataSourceChanged()
Emitted whenever the layer's data source has been changed.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1231
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:95
QgsInterval firstValidTimeStep() const
Returns the first valid time step of the dataset groups, invalid QgInterval if no time step is presen...
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:501
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...
void exportAnimation()
Triggered when an animation should be exported.
void keyPressEvent(QKeyEvent *e) override
QgsTemporalController * temporalController()
Returns the temporal controller object used by this object in navigation.
QgsTemporalControllerWidget(QWidget *parent=nullptr)
Constructor for QgsTemporalControllerWidget, with the specified parent widget.
A controller base class for temporal objects, contains a signal for notifying updates of the objects ...
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 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 setFrameDuration(QgsInterval duration)
Sets the frame duration, which dictates the temporal length of each frame in the animation.
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 currenttemporal 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 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:161
@ TemporalWeeks
Weeks.
Definition: qgsunittypes.h:156
@ TemporalMilliseconds
Milliseconds.
Definition: qgsunittypes.h:151
@ 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:263
int precision