QGIS API Documentation 3.41.0-Master (57ec4277f5e)
Loading...
Searching...
No Matches
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 "moc_qgstemporalcontrollerwidget.cpp"
21#include "qgsmaplayermodel.h"
22#include "qgsproject.h"
25#include "qgstemporalutils.h"
27#include "qgsmeshlayer.h"
28#include "qgsrasterlayer.h"
29#include "qgsunittypes.h"
30
31#include <QAction>
32#include <QMenu>
33#include <QRegularExpression>
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 auto handleOperation = [=]( Qgis::PlaybackOperation operation ) {
48 switch ( operation )
49 {
51 mNavigationObject->rewindToStart();
52 break;
53
55 mNavigationObject->previous();
56 break;
57
59 mNavigationObject->playBackward();
60 break;
61
63 mNavigationObject->pause();
64 break;
65
67 mNavigationObject->playForward();
68 break;
69
71 mNavigationObject->next();
72 break;
73
75 mNavigationObject->skipToEnd();
76 break;
77 }
78 };
79 connect( mAnimationController, &QgsPlaybackControllerWidget::operationTriggered, this, handleOperation );
80 connect( mMovieController, &QgsPlaybackControllerWidget::operationTriggered, this, handleOperation );
81
82 connect( mAnimationLoopingCheckBox, &QCheckBox::toggled, this, [=]( bool state ) { mNavigationObject->setLooping( state ); mMovieLoopingCheckBox->setChecked( state ); } );
83 connect( mMovieLoopingCheckBox, &QCheckBox::toggled, this, [=]( bool state ) { mNavigationObject->setLooping( state ); mAnimationLoopingCheckBox->setChecked( state ); } );
84
85 setWidgetStateFromNavigationMode( mNavigationObject->navigationMode() );
86 connect( mNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsTemporalControllerWidget::setWidgetStateFromNavigationMode );
87 connect( mNavigationObject, &QgsTemporalNavigationObject::temporalExtentsChanged, this, &QgsTemporalControllerWidget::setDates );
88 connect( mNavigationObject, &QgsTemporalNavigationObject::temporalFrameDurationChanged, this, [=]( const QgsInterval &timeStep ) {
89 if ( mBlockFrameDurationUpdates )
90 return;
91
92 mBlockFrameDurationUpdates++;
93 updateTimeStepInputs( timeStep );
94 mBlockFrameDurationUpdates--;
95 } );
96 connect( mNavigationOff, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationOff_clicked );
97 connect( mNavigationFixedRange, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationFixedRange_clicked );
98 connect( mNavigationAnimated, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationAnimated_clicked );
99 connect( mNavigationMovie, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationMovie_clicked );
100
101 connect( mNavigationObject, &QgsTemporalNavigationObject::stateChanged, this, [=]( Qgis::AnimationState state ) {
102 mAnimationController->setState( state );
103 mMovieController->setState( state );
104 } );
105
106 connect( mStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
107 connect( mEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
108 connect( mFixedRangeStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
109 connect( mFixedRangeEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
110 connect( mStepSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
111 connect( mTimeStepsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
112 connect( mAnimationSlider, &QSlider::valueChanged, this, &QgsTemporalControllerWidget::timeSlider_valueChanged );
113 connect( mMovieSlider, &QSlider::valueChanged, this, &QgsTemporalControllerWidget::timeSlider_valueChanged );
114
115 connect( mTotalFramesSpinBox, qOverload<int>( &QSpinBox::valueChanged ), this, [=]( int frames ) {
116 mNavigationObject->setTotalMovieFrames( frames );
117 } );
118
119 mStepSpinBox->setClearValue( 1 );
120
121 connect( mNavigationObject, &QgsTemporalNavigationObject::updateTemporalRange, this, &QgsTemporalControllerWidget::updateSlider );
122 connect( mNavigationObject, &QgsTemporalNavigationObject::totalMovieFramesChanged, this, &QgsTemporalControllerWidget::totalMovieFramesChanged );
123
124 connect( mSettings, &QPushButton::clicked, this, &QgsTemporalControllerWidget::settings_clicked );
125
126 mMapLayerModel = new QgsMapLayerModel( this );
127
128 mRangeMenu.reset( new QMenu( this ) );
129
130 mRangeSetToAllLayersAction = new QAction( tr( "Set to Full Range" ), mRangeMenu.get() );
131 mRangeSetToAllLayersAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRefresh.svg" ) ) );
132 connect( mRangeSetToAllLayersAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered );
133 mRangeMenu->addAction( mRangeSetToAllLayersAction );
134
135 mRangeSetToProjectAction = new QAction( tr( "Set to Preset Project Range" ), mRangeMenu.get() );
136 connect( mRangeSetToProjectAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered );
137 mRangeMenu->addAction( mRangeSetToProjectAction );
138
139 mRangeMenu->addSeparator();
140
141 mRangeLayersSubMenu.reset( new QMenu( tr( "Set to Single Layer's Range" ), mRangeMenu.get() ) );
142 mRangeLayersSubMenu->setEnabled( false );
143 mRangeMenu->addMenu( mRangeLayersSubMenu.get() );
144 connect( mRangeMenu.get(), &QMenu::aboutToShow, this, &QgsTemporalControllerWidget::aboutToShowRangeMenu );
145
146 mSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
147 mSetRangeButton->setMenu( mRangeMenu.get() );
148 mSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
149 mFixedRangeSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
150 mFixedRangeSetRangeButton->setMenu( mRangeMenu.get() );
151 mFixedRangeSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
152
153 connect( mExportAnimationButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::exportAnimation );
154 connect( mExportMovieButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::exportAnimation );
155
156 QgsDateTimeRange range;
157
158 if ( QgsProject::instance()->timeSettings() )
160
161 if ( range.begin().isValid() && range.end().isValid() )
162 {
163 whileBlocking( mStartDateTime )->setDateTime( range.begin() );
164 whileBlocking( mEndDateTime )->setDateTime( range.end() );
165 whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
166 whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
167 }
168
169 for ( const Qgis::TemporalUnit u :
170 {
182 } )
183 {
184 mTimeStepsComboBox->addItem( u != Qgis::TemporalUnit::IrregularStep ? QgsUnitTypes::toString( u ) : tr( "source timestamps" ), static_cast<int>( u ) );
185 }
186
187 // TODO: might want to choose an appropriate default unit based on the range
188 mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( static_cast<int>( Qgis::TemporalUnit::Hours ) ) );
189
190 // NOTE 'minimum' and 'decimals' should be in sync with the 'decimals' in qgstemporalcontrollerwidgetbase.ui
191 mStepSpinBox->setDecimals( 3 );
192 // minimum timestep one millisecond
193 mStepSpinBox->setMinimum( 0.001 );
194 mStepSpinBox->setMaximum( std::numeric_limits<int>::max() );
195 mStepSpinBox->setSingleStep( 1 );
196 mStepSpinBox->setValue( 1 );
197
198 updateFrameDuration();
199
200 connect( QgsProject::instance(), &QgsProject::readProject, this, &QgsTemporalControllerWidget::setWidgetStateFromProject );
201 connect( QgsProject::instance(), &QgsProject::layersAdded, this, &QgsTemporalControllerWidget::onLayersAdded );
202 connect( QgsProject::instance(), &QgsProject::cleared, this, &QgsTemporalControllerWidget::onProjectCleared );
203}
204
206{
207 return true;
208}
209
211{
212 if ( ( mAnimationSlider->hasFocus() || mMovieSlider->hasFocus() ) && e->key() == Qt::Key_Space )
213 {
214 mAnimationController->togglePause();
215 // connections will auto-sync mMovieController state!
216 }
218}
219
220void QgsTemporalControllerWidget::aboutToShowRangeMenu()
221{
222 QgsDateTimeRange projectRange;
223 if ( QgsProject::instance()->timeSettings() )
224 projectRange = QgsProject::instance()->timeSettings()->temporalRange();
225 mRangeSetToProjectAction->setEnabled( projectRange.begin().isValid() && projectRange.end().isValid() );
226
227 mRangeLayersSubMenu->clear();
228 for ( int i = 0; i < mMapLayerModel->rowCount(); ++i )
229 {
230 const QModelIndex index = mMapLayerModel->index( i, 0 );
231 QgsMapLayer *currentLayer = mMapLayerModel->data( index, static_cast<int>( QgsMapLayerModel::CustomRole::Layer ) ).value<QgsMapLayer *>();
232 if ( !currentLayer->temporalProperties() || !currentLayer->temporalProperties()->isActive() )
233 continue;
234
235 const QIcon icon = qvariant_cast<QIcon>( mMapLayerModel->data( index, Qt::DecorationRole ) );
236 const QString text = mMapLayerModel->data( index, Qt::DisplayRole ).toString();
237 const QgsDateTimeRange range = currentLayer->temporalProperties()->calculateTemporalExtent( currentLayer );
238 if ( range.begin().isValid() && range.end().isValid() )
239 {
240 QAction *action = new QAction( icon, text, mRangeLayersSubMenu.get() );
241 connect( action, &QAction::triggered, this, [=] {
242 setDates( range );
243 saveRangeToProject();
244 } );
245 mRangeLayersSubMenu->addAction( action );
246 }
247 }
248 mRangeLayersSubMenu->setEnabled( !mRangeLayersSubMenu->actions().isEmpty() );
249}
250
251void QgsTemporalControllerWidget::updateTemporalExtent()
252{
253 // TODO - consider whether the overall time range set for animations should include the end date time or not.
254 // (currently it DOES include the end date time).
255 const QDateTime start = mStartDateTime->dateTime();
256 const QDateTime end = mEndDateTime->dateTime();
257 const bool isTimeInstant = start == end;
258 const QgsDateTimeRange temporalExtent = QgsDateTimeRange( start, end, true, !isTimeInstant && mNavigationObject->navigationMode() == Qgis::TemporalNavigationMode::FixedRange ? false : true );
259 mNavigationObject->setTemporalExtents( temporalExtent );
260 mAnimationSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
261 mAnimationSlider->setValue( mNavigationObject->currentFrameNumber() );
262 mMovieSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
263 mMovieSlider->setValue( mNavigationObject->currentFrameNumber() );
264}
265
266void QgsTemporalControllerWidget::updateFrameDuration()
267{
268 if ( mBlockSettingUpdates )
269 return;
270
271 // save new settings into project
272 const Qgis::TemporalUnit unit = static_cast<Qgis::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() );
274 QgsProject::instance()->timeSettings()->setTimeStep( unit == Qgis::TemporalUnit::IrregularStep ? 1 : mStepSpinBox->value() );
275
276 if ( !mBlockFrameDurationUpdates )
277 {
278 mNavigationObject->setFrameDuration(
279 QgsInterval( QgsProject::instance()->timeSettings()->timeStep(), QgsProject::instance()->timeSettings()->timeStepUnit() )
280 );
281 mAnimationSlider->setValue( mNavigationObject->currentFrameNumber() );
282 }
283 mAnimationSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
284 mAnimationSlider->setValue( mNavigationObject->currentFrameNumber() );
285 mMovieSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
286 mMovieSlider->setValue( mNavigationObject->currentFrameNumber() );
287
289 {
290 mStepSpinBox->setEnabled( false );
291 mStepSpinBox->setValue( 1 );
292 mAnimationSlider->setTickInterval( 1 );
293 mAnimationSlider->setTickPosition( QSlider::TicksBothSides );
294 }
295 else
296 {
297 mStepSpinBox->setEnabled( true );
298 mAnimationSlider->setTickInterval( 0 );
299 mAnimationSlider->setTickPosition( QSlider::NoTicks );
300 }
301}
302
303void QgsTemporalControllerWidget::setWidgetStateFromProject()
304{
305 mBlockSettingUpdates++;
306 mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( static_cast<int>( QgsProject::instance()->timeSettings()->timeStepUnit() ) ) );
307 mStepSpinBox->setValue( QgsProject::instance()->timeSettings()->timeStep() );
308 mBlockSettingUpdates--;
309
310 bool ok = false;
311 const Qgis::TemporalNavigationMode mode = static_cast<Qgis::TemporalNavigationMode>( QgsProject::instance()->readNumEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ), 0, &ok ) );
312 if ( ok )
313 {
314 mNavigationObject->setNavigationMode( mode );
315 setWidgetStateFromNavigationMode( mode );
316 }
317 else
318 {
320 setWidgetStateFromNavigationMode( Qgis::TemporalNavigationMode::Disabled );
321 }
322
323 mNavigationObject->setTotalMovieFrames( QgsProject::instance()->timeSettings()->totalMovieFrames() );
324 mTotalFramesSpinBox->setValue( QgsProject::instance()->timeSettings()->totalMovieFrames() );
325
326 const QString startString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/StartDateTime" ) );
327 const QString endString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/EndDateTime" ) );
328 if ( !startString.isEmpty() && !endString.isEmpty() )
329 {
330 whileBlocking( mStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
331 whileBlocking( mEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
332 whileBlocking( mFixedRangeStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
333 whileBlocking( mFixedRangeEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
334 }
335 else
336 {
337 setDatesToProjectTime( false );
338 }
339 updateTemporalExtent();
340 updateFrameDuration();
341
342 mNavigationObject->setFramesPerSecond( QgsProject::instance()->timeSettings()->framesPerSecond() );
343 mNavigationObject->setTemporalRangeCumulative( QgsProject::instance()->timeSettings()->isTemporalRangeCumulative() );
344}
345
346void QgsTemporalControllerWidget::mNavigationOff_clicked()
347{
348 QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ), static_cast<int>( Qgis::TemporalNavigationMode::Disabled ) );
349
351 setWidgetStateFromNavigationMode( Qgis::TemporalNavigationMode::Disabled );
352}
353
354void QgsTemporalControllerWidget::mNavigationFixedRange_clicked()
355{
356 QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ), static_cast<int>( Qgis::TemporalNavigationMode::FixedRange ) );
357
359 setWidgetStateFromNavigationMode( Qgis::TemporalNavigationMode::FixedRange );
360}
361
362void QgsTemporalControllerWidget::mNavigationAnimated_clicked()
363{
364 QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ), static_cast<int>( Qgis::TemporalNavigationMode::Animated ) );
365
367 setWidgetStateFromNavigationMode( Qgis::TemporalNavigationMode::Animated );
368}
369
370void QgsTemporalControllerWidget::mNavigationMovie_clicked()
371{
372 QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ), static_cast<int>( Qgis::TemporalNavigationMode::Movie ) );
373
375 setWidgetStateFromNavigationMode( Qgis::TemporalNavigationMode::Movie );
376}
377
378void QgsTemporalControllerWidget::setWidgetStateFromNavigationMode( const Qgis::TemporalNavigationMode mode )
379{
380 mNavigationOff->setChecked( mode == Qgis::TemporalNavigationMode::Disabled );
381 mNavigationFixedRange->setChecked( mode == Qgis::TemporalNavigationMode::FixedRange );
382 mNavigationAnimated->setChecked( mode == Qgis::TemporalNavigationMode::Animated );
383 mNavigationMovie->setChecked( mode == Qgis::TemporalNavigationMode::Movie );
384
385 switch ( mode )
386 {
388 mNavigationModeStackedWidget->setCurrentIndex( 0 );
389 break;
391 mNavigationModeStackedWidget->setCurrentIndex( 1 );
392 break;
394 mNavigationModeStackedWidget->setCurrentIndex( 2 );
395 break;
397 mNavigationModeStackedWidget->setCurrentIndex( 3 );
398 break;
399 }
400}
401
402void QgsTemporalControllerWidget::onLayersAdded( const QList<QgsMapLayer *> &layers )
403{
404 if ( !mHasTemporalLayersLoaded )
405 {
406 for ( QgsMapLayer *layer : layers )
407 {
408 if ( layer->temporalProperties() )
409 {
410 mHasTemporalLayersLoaded |= layer->temporalProperties()->isActive();
411
412 if ( !mHasTemporalLayersLoaded )
413 {
414 connect( layer, &QgsMapLayer::dataSourceChanged, this, [this, layer] {
415 if ( layer->isValid() && layer->temporalProperties()->isActive() && !mHasTemporalLayersLoaded )
416 {
417 mHasTemporalLayersLoaded = true;
418 firstTemporalLayerLoaded( layer );
419 mNavigationObject->setAvailableTemporalRanges( QgsTemporalUtils::usedTemporalRangesForProject( QgsProject::instance() ) );
420 }
421 } );
422 }
423
424 firstTemporalLayerLoaded( layer );
425 }
426 }
427 }
428
430}
431
432void QgsTemporalControllerWidget::firstTemporalLayerLoaded( QgsMapLayer *layer )
433{
434 setDatesToProjectTime( true );
435
436 if ( QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer ) )
437 {
438 mBlockFrameDurationUpdates++;
439 setTimeStep( meshLayer->firstValidTimeStep() );
440 mBlockFrameDurationUpdates--;
441 updateFrameDuration();
442 }
443 else if ( QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer ) )
444 {
445 if ( rasterLayer->dataProvider() && rasterLayer->dataProvider()->temporalCapabilities() )
446 {
447 mBlockFrameDurationUpdates++;
448 setTimeStep( rasterLayer->dataProvider()->temporalCapabilities()->defaultInterval() );
449 mBlockFrameDurationUpdates--;
450 updateFrameDuration();
451 }
452 }
453}
454
455void QgsTemporalControllerWidget::onProjectCleared()
456{
457 mHasTemporalLayersLoaded = false;
458
460 setWidgetStateFromNavigationMode( Qgis::TemporalNavigationMode::Disabled );
461
462 // default to showing the last 24 hours, ending at the current date's hour, in one hour blocks...
463 // it's COMPLETELY arbitrary, but better than starting with a "zero length" duration!
464 const QTime startOfCurrentHour = QTime( QTime::currentTime().hour(), 0, 0 );
465 const QDateTime end = QDateTime( QDate::currentDate(), startOfCurrentHour, Qt::UTC );
466 const QDateTime start = end.addSecs( -24 * 60 * 60 );
467
468 whileBlocking( mStartDateTime )->setDateTime( start );
469 whileBlocking( mEndDateTime )->setDateTime( end );
470 whileBlocking( mFixedRangeStartDateTime )->setDateTime( start );
471 whileBlocking( mFixedRangeEndDateTime )->setDateTime( end );
472
473 updateTemporalExtent();
474 mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( static_cast<int>( Qgis::TemporalUnit::Hours ) ) );
475 mStepSpinBox->setValue( 1 );
476}
477
478void QgsTemporalControllerWidget::updateSlider( const QgsDateTimeRange &range )
479{
480 whileBlocking( mAnimationSlider )->setValue( mNavigationObject->currentFrameNumber() );
481 whileBlocking( mMovieSlider )->setValue( mNavigationObject->currentFrameNumber() );
482 updateRangeLabel( range );
483}
484
485void QgsTemporalControllerWidget::totalMovieFramesChanged( long long frames )
486{
488 mTotalFramesSpinBox->setValue( frames );
489 mCurrentRangeLabel->setText( tr( "Current frame: %1/%2" ).arg( mNavigationObject->currentFrameNumber() ).arg( frames ) );
490}
491
492void QgsTemporalControllerWidget::updateRangeLabel( const QgsDateTimeRange &range )
493{
494 QString timeFrameFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss" );
495 // but if timesteps are < 1 second (as: in milliseconds), add milliseconds to the format
496 if ( static_cast<Qgis::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() ) == Qgis::TemporalUnit::Milliseconds )
497 timeFrameFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss.zzz" );
498 switch ( mNavigationObject->navigationMode() )
499 {
501 mCurrentRangeLabel->setText( tr( "Current frame: %1 ≤ <i>t</i> &lt; %2" ).arg( range.begin().toString( timeFrameFormat ), range.end().toString( timeFrameFormat ) ) );
502 break;
504 mCurrentRangeLabel->setText( tr( "Range: %1 ≤ <i>t</i> &lt; %2" ).arg( range.begin().toString( timeFrameFormat ), range.end().toString( timeFrameFormat ) ) );
505 break;
507 mCurrentRangeLabel->setText( tr( "Temporal navigation disabled" ) );
508 break;
510 mCurrentRangeLabel->setText( tr( "Current frame: %1/%2" ).arg( mNavigationObject->currentFrameNumber() ).arg( mNavigationObject->totalMovieFrames() ) );
511 break;
512 }
513}
514
519
520void QgsTemporalControllerWidget::settings_clicked()
521{
522 QgsTemporalMapSettingsWidget *settingsWidget = new QgsTemporalMapSettingsWidget( this );
523 settingsWidget->setFrameRateValue( mNavigationObject->framesPerSecond() );
524 settingsWidget->setIsTemporalRangeCumulative( mNavigationObject->temporalRangeCumulative() );
525
526 connect( settingsWidget, &QgsTemporalMapSettingsWidget::frameRateChanged, this, [=]( double rate ) {
527 // save new settings into project
529 mNavigationObject->setFramesPerSecond( rate );
530 } );
531
532 connect( settingsWidget, &QgsTemporalMapSettingsWidget::temporalRangeCumulativeChanged, this, [=]( bool state ) {
533 // save new settings into project
535 mNavigationObject->setTemporalRangeCumulative( state );
536 } );
537 openPanel( settingsWidget );
538}
539
540void QgsTemporalControllerWidget::timeSlider_valueChanged( int value )
541{
542 mNavigationObject->setCurrentFrameNumber( value );
543}
544
545void QgsTemporalControllerWidget::startEndDateTime_changed()
546{
547 whileBlocking( mFixedRangeStartDateTime )->setDateTime( mStartDateTime->dateTime() );
548 whileBlocking( mFixedRangeEndDateTime )->setDateTime( mEndDateTime->dateTime() );
549
550 updateTemporalExtent();
551 saveRangeToProject();
552}
553
554void QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed()
555{
556 whileBlocking( mStartDateTime )->setDateTime( mFixedRangeStartDateTime->dateTime() );
557 whileBlocking( mEndDateTime )->setDateTime( mFixedRangeEndDateTime->dateTime() );
558
559 updateTemporalExtent();
560 saveRangeToProject();
561}
562
563void QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered()
564{
565 setDatesToAllLayers();
566 saveRangeToProject();
567}
568
569void QgsTemporalControllerWidget::setTimeStep( const QgsInterval &timeStep )
570{
571 if ( !timeStep.isValid() || timeStep.seconds() <= 0 )
572 return;
573
574 int selectedUnit = -1;
575 double selectedValue = std::numeric_limits<double>::max();
577 {
578 // Search the time unit the most appropriate :
579 // the one that gives the smallest time step value for double spin box with round value (if possible) and/or the less signifiant digits
580
581 int stringSize = std::numeric_limits<int>::max();
582 const int precision = mStepSpinBox->decimals();
583 for ( int i = 0; i < mTimeStepsComboBox->count(); ++i )
584 {
585 const Qgis::TemporalUnit unit = static_cast<Qgis::TemporalUnit>( mTimeStepsComboBox->itemData( i ).toInt() );
586 const double value = timeStep.seconds() * QgsUnitTypes::fromUnitToUnitFactor( Qgis::TemporalUnit::Seconds, unit );
587 QString string = QString::number( value, 'f', precision );
588
589 const thread_local QRegularExpression trailingZeroRegEx = QRegularExpression( QStringLiteral( "0+$" ) );
590 //remove trailing zero
591 string.remove( trailingZeroRegEx );
592
593 const thread_local QRegularExpression trailingPointRegEx = QRegularExpression( QStringLiteral( "[.]+$" ) );
594 //remove last point if present
595 string.remove( trailingPointRegEx );
596
597 if ( value >= 1
598 && string.size() <= stringSize // less significant digit than currently selected
599 && value < selectedValue ) // less than currently selected
600 {
601 selectedUnit = i;
602 selectedValue = value;
603 stringSize = string.size();
604 }
605 else if ( string != '0'
606 && string.size() < precision + 2 //round value (ex: 0.xx with precision=3)
607 && string.size() < stringSize ) //less significant digit than currently selected
608 {
609 selectedUnit = i;
610 selectedValue = value;
611 stringSize = string.size();
612 }
613 }
614 }
615 else
616 {
617 selectedUnit = mTimeStepsComboBox->findData( static_cast<int>( timeStep.originalUnit() ) );
618 selectedValue = 1;
619 }
620
621 if ( selectedUnit >= 0 )
622 {
623 mStepSpinBox->setValue( selectedValue );
624 mTimeStepsComboBox->setCurrentIndex( selectedUnit );
625 }
626
627 updateFrameDuration();
628}
629
630void QgsTemporalControllerWidget::updateTimeStepInputs( const QgsInterval &timeStep )
631{
632 if ( !timeStep.isValid() || timeStep.seconds() <= 0.0001 )
633 return;
634
635 QString timeDisplayFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss" );
637 {
638 timeDisplayFormat = QStringLiteral( "yyyy-MM-dd HH:mm:ss.zzz" );
639 // very big change that you have to update the range too, as defaulting to NOT handling millis
640 updateTemporalExtent();
641 }
642 mStartDateTime->setDisplayFormat( timeDisplayFormat );
643 mEndDateTime->setDisplayFormat( timeDisplayFormat );
644 mFixedRangeStartDateTime->setDisplayFormat( timeDisplayFormat );
645 mFixedRangeEndDateTime->setDisplayFormat( timeDisplayFormat );
646
647 // Only update ui when the intervals are different
648 if ( timeStep == QgsInterval( mStepSpinBox->value(), static_cast<Qgis::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() ) ) )
649 return;
650
651 if ( timeStep.originalUnit() != Qgis::TemporalUnit::Unknown )
652 {
653 mStepSpinBox->setValue( timeStep.originalDuration() );
654 mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( static_cast<int>( timeStep.originalUnit() ) ) );
655 }
656
657 updateFrameDuration();
658}
659
660void QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered()
661{
662 setDatesToProjectTime( false );
663 saveRangeToProject();
664}
665
666void QgsTemporalControllerWidget::setDates( const QgsDateTimeRange &range )
667{
668 if ( range.begin().isValid() && range.end().isValid() )
669 {
670 whileBlocking( mStartDateTime )->setDateTime( range.begin() );
671 whileBlocking( mEndDateTime )->setDateTime( range.end() );
672 whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
673 whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
674 updateTemporalExtent();
675 }
676}
677
678void QgsTemporalControllerWidget::setDatesToAllLayers()
679{
680 QgsDateTimeRange range;
683
684 setDates( range );
685}
686
687void QgsTemporalControllerWidget::setDatesToProjectTime( bool tryLastStoredRange )
688{
689 QgsDateTimeRange range;
690
691 if ( tryLastStoredRange )
692 {
693 const QString startString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/StartDateTime" ) );
694 const QString endString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/EndDateTime" ) );
695 if ( !startString.isEmpty() && !endString.isEmpty() )
696 {
697 range = QgsDateTimeRange( QDateTime::fromString( startString, Qt::ISODateWithMs ), QDateTime::fromString( endString, Qt::ISODateWithMs ) );
698 }
699 }
700
701 // by default try taking the project's fixed temporal extent
702 if ( ( !range.begin().isValid() || !range.end().isValid() ) && QgsProject::instance()->timeSettings() )
704
705 // if that's not set, calculate the extent from the project's layers
706 if ( !range.begin().isValid() || !range.end().isValid() )
707 {
709 }
710
712
713 setDates( range );
714}
715
716void QgsTemporalControllerWidget::saveRangeToProject()
717{
718 QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/StartDateTime" ), mStartDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );
719 QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/EndDateTime" ), mEndDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );
720}
TemporalNavigationMode
Temporal navigation modes.
Definition qgis.h:2429
@ Animated
Temporal navigation relies on frames within a datetime range.
@ Movie
Movie mode – behaves like a video player, with a fixed frame duration and no temporal range.
@ FixedRange
Temporal navigation relies on a fixed datetime range.
@ Disabled
Temporal navigation is disabled.
PlaybackOperation
Media playback operations.
Definition qgis.h:2458
@ Pause
Pause playback.
@ PlayReverse
Play in reverse.
@ PlayForward
Play forward.
@ SkipToStart
Jump to start of playback.
@ PreviousFrame
Step to previous frame.
@ SkipToEnd
Jump to end of playback.
@ NextFrame
Step to next frame.
TemporalUnit
Temporal units.
Definition qgis.h:4886
@ IrregularStep
Special 'irregular step' time unit, used for temporal data which uses irregular, non-real-world unit ...
@ Milliseconds
Milliseconds.
@ Unknown
Unknown time unit.
@ Centuries
Centuries.
AnimationState
Animation states.
Definition qgis.h:2445
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:46
double originalDuration() const
Returns the original interval duration.
bool isValid() const
Returns true if the interval is valid.
double seconds() const
Returns the interval duration in seconds.
Qgis::TemporalUnit originalUnit() const
Returns the original interval temporal unit.
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
@ Layer
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:76
void dataSourceChanged()
Emitted whenever the layer's data source has been changed.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
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 ...
void keyPressEvent(QKeyEvent *event) override
Overridden key press event to handle the esc event on the widget.
void operationTriggered(Qgis::PlaybackOperation operation)
Emitted when a playback operation is triggered.
QgsDateTimeRange temporalRange() const
Returns the project's temporal range, which indicates the earliest and latest datetime ranges associa...
void setTotalMovieFrames(long long frames)
Sets the total number of frames for the movie.
void setFramesPerSecond(double rate)
Sets the project's default animation frame rate, in frames per second.
Qgis::TemporalUnit timeStepUnit() const
Returns the project's time step (length of one animation frame) unit, which is used as the default va...
void setTimeStepUnit(Qgis::TemporalUnit unit)
Sets the project's time step (length of one animation frame) unit, which is used as the default value...
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.
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.
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
void readProject(const QDomDocument &document)
Emitted when a project is being read.
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 stateChanged(Qgis::AnimationState state)
Emitted whenever the animation state changes.
long long totalMovieFrames() const
Returns the total number of frames for the movie.
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 navigationModeChanged(Qgis::TemporalNavigationMode mode)
Emitted whenever the navigation mode changes.
void setNavigationMode(const Qgis::TemporalNavigationMode mode)
Sets the temporal navigation mode.
void playForward()
Starts the animation playing in a forward direction up till the end of all frames.
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 setFramesPerSecond(double rate)
Sets the animation frame rate, in frames per second.
bool temporalRangeCumulative() const
Returns the animation temporal range cumulative settings.
void next()
Advances to the next frame.
void totalMovieFramesChanged(long long frames)
Emitted whenever the total number of frames in the movie is changed.
void setTotalMovieFrames(long long frames)
Sets the total number of frames for the movie.
void setTemporalExtents(const QgsDateTimeRange &extents)
Sets the navigation temporal extents, which dictate the earliest and latest date time possible in the...
Qgis::TemporalNavigationMode navigationMode() const
Returns the current temporal navigation mode.
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.
bool isActive() const
Returns true if the temporal property is active.
T begin() const
Returns the beginning of the range.
Definition qgsrange.h:444
T end() const
Returns the upper bound of the range.
Definition qgsrange.h:451
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(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:5970
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition qgsrange.h:742
int precision