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