QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgstemporalnavigationobject.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstemporalnavigationobject.cpp
3  ---------------
4  begin : March 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 #include "qgis.h"
20 #include "qgstemporalutils.h"
21 
23  : QgsTemporalController( parent )
24 {
25  mNewFrameTimer = new QTimer( this );
26 
27  connect( mNewFrameTimer, &QTimer::timeout,
28  this, &QgsTemporalNavigationObject::timerTimeout );
29 }
30 
31 void QgsTemporalNavigationObject::timerTimeout()
32 {
33  switch ( mPlayBackMode )
34  {
35  case AnimationState::Forward:
36  next();
37  if ( mCurrentFrameNumber >= totalFrameCount() - 1 )
38  {
39  if ( mLoopAnimation )
40  mCurrentFrameNumber = -1; // we don't jump immediately to frame 0, instead we delay that till the next timeout
41  else
42  pause();
43  }
44  break;
45 
46  case AnimationState::Reverse:
47  previous();
48  if ( mCurrentFrameNumber <= 0 )
49  {
50  if ( mLoopAnimation )
51  mCurrentFrameNumber = totalFrameCount(); // we don't jump immediately to real last frame..., instead we delay that till the next timeout
52  else
53  pause();
54  }
55  break;
56 
57  case AnimationState::Idle:
58  // should not happen - in an idle state the timeout won't occur
59  break;
60  }
61 }
62 
64 {
65  return mLoopAnimation;
66 }
67 
68 void QgsTemporalNavigationObject::setLooping( bool loopAnimation )
69 {
70  mLoopAnimation = loopAnimation;
71 }
72 
74 {
75  std::unique_ptr< QgsExpressionContextScope > scope = std::make_unique< QgsExpressionContextScope >( QStringLiteral( "temporal" ) );
76  scope->setVariable( QStringLiteral( "frame_rate" ), mFramesPerSecond, true );
77  scope->setVariable( QStringLiteral( "frame_number" ), mCurrentFrameNumber, true );
78  scope->setVariable( QStringLiteral( "frame_duration" ), mFrameDuration, true );
79  scope->setVariable( QStringLiteral( "frame_timestep" ), mFrameDuration.originalDuration(), true );
80  scope->setVariable( QStringLiteral( "frame_timestep_unit" ), mFrameDuration.originalUnit(), true );
81  scope->setVariable( QStringLiteral( "animation_start_time" ), mTemporalExtents.begin(), true );
82  scope->setVariable( QStringLiteral( "animation_end_time" ), mTemporalExtents.end(), true );
83  scope->setVariable( QStringLiteral( "animation_interval" ), mTemporalExtents.end() - mTemporalExtents.begin(), true );
84  return scope.release();
85 }
86 
87 QgsDateTimeRange QgsTemporalNavigationObject::dateTimeRangeForFrameNumber( long long frame ) const
88 {
89  const QDateTime start = mTemporalExtents.begin();
90 
91  if ( frame < 0 )
92  frame = 0;
93 
94  const long long nextFrame = frame + 1;
95 
96  QDateTime begin;
97  QDateTime end;
98  if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
99  {
100  if ( mAllRanges.empty() )
101  return QgsDateTimeRange();
102 
103  return frame < mAllRanges.size() ? mAllRanges.at( frame ) : mAllRanges.constLast();
104  }
105  else
106  {
107  begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
108  end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
109  }
110 
111  QDateTime frameStart = begin;
112 
113  if ( mCumulativeTemporalRange )
114  frameStart = start;
115 
116  return QgsDateTimeRange( frameStart, end, true, false );
117 }
118 
120 {
121  if ( mNavigationMode == mode )
122  return;
123 
124  mNavigationMode = mode;
125  emit navigationModeChanged( mode );
126 
127  if ( !mBlockUpdateTemporalRangeSignal )
128  {
129  switch ( mNavigationMode )
130  {
131  case Animated:
132  emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
133  break;
134  case FixedRange:
135  emit updateTemporalRange( mTemporalExtents );
136  break;
137  case NavigationOff:
138  emit updateTemporalRange( QgsDateTimeRange() );
139  break;
140  }
141  }
142 }
143 
144 void QgsTemporalNavigationObject::setTemporalExtents( const QgsDateTimeRange &temporalExtents )
145 {
146  if ( mTemporalExtents == temporalExtents )
147  {
148  return;
149  }
150  const QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
151  mTemporalExtents = temporalExtents;
152  mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
153  emit temporalExtentsChanged( mTemporalExtents );
154 
155  switch ( mNavigationMode )
156  {
157  case Animated:
158  {
159  const long long currentFrameNumber = mCurrentFrameNumber;
160 
161  // Force to emit signal if the current frame number doesn't change
162  if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
163  emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
164  break;
165  }
166  case FixedRange:
167  if ( !mBlockUpdateTemporalRangeSignal )
168  emit updateTemporalRange( mTemporalExtents );
169  break;
170  case NavigationOff:
171  break;
172  }
173 
174 }
175 
177 {
178  return mTemporalExtents;
179 }
180 
181 void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
182 {
183  mAllRanges = ranges;
184 }
185 
187 {
188  return mAllRanges;
189 }
190 
192 {
193  if ( mCurrentFrameNumber != frameNumber )
194  {
195  mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
196  const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
197 
198  if ( !mBlockUpdateTemporalRangeSignal )
199  emit updateTemporalRange( range );
200  }
201 }
202 
204 {
205  return mCurrentFrameNumber;
206 }
207 
209 {
210  if ( mFrameDuration == frameDuration )
211  {
212  return;
213  }
214 
215  const QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
216  mFrameDuration = frameDuration;
217 
218  mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
219  emit temporalFrameDurationChanged( mFrameDuration );
220 
221  // forcing an update of our views
222  const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
223 
224  if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Animated )
225  emit updateTemporalRange( range );
226 }
227 
229 {
230  return mFrameDuration;
231 }
232 
233 void QgsTemporalNavigationObject::setFramesPerSecond( double framesPerSeconds )
234 {
235  if ( framesPerSeconds > 0 )
236  {
237  mFramesPerSecond = framesPerSeconds;
238  mNewFrameTimer->setInterval( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
239  }
240 }
241 
243 {
244  return mFramesPerSecond;
245 }
246 
248 {
249  mCumulativeTemporalRange = state;
250 }
251 
253 {
254  return mCumulativeTemporalRange;
255 }
256 
258 {
259  mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
260 }
261 
263 {
264  mNewFrameTimer->stop();
265  setAnimationState( AnimationState::Idle );
266 }
267 
269 {
270  if ( mPlayBackMode == Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
271  {
272  // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
273  rewindToStart();
274  }
275 
276  setAnimationState( AnimationState::Forward );
277  play();
278 }
279 
281 {
282  if ( mPlayBackMode == Idle && mCurrentFrameNumber <= 0 )
283  {
284  // if we are paused at the start of the video, and the user hits play, we automatically skip to end and play in reverse again
285  skipToEnd();
286  }
287 
288  setAnimationState( AnimationState::Reverse );
289  play();
290 }
291 
293 {
294  setCurrentFrameNumber( mCurrentFrameNumber + 1 );
295 }
296 
298 {
299  setCurrentFrameNumber( mCurrentFrameNumber - 1 );
300 }
301 
303 {
305 }
306 
308 {
309  const long long frame = totalFrameCount() - 1;
310  setCurrentFrameNumber( frame );
311 }
312 
314 {
315  if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
316  {
317  return mAllRanges.count();
318  }
319  else
320  {
321  const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
322  return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
323  }
324 }
325 
327 {
328  if ( mode != mPlayBackMode )
329  {
330  mPlayBackMode = mode;
331  emit stateChanged( mPlayBackMode );
332  }
333 }
334 
336 {
337  return mPlayBackMode;
338 }
339 
340 long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
341 {
342  long long bestFrame = 0;
343  if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
344  {
345  for ( const QgsDateTimeRange &range : mAllRanges )
346  {
347  if ( range.contains( frameStart ) )
348  return bestFrame;
349  else if ( range.begin() > frameStart )
350  // if we've gone past the target date, go back one frame if possible
351  return std::max( 0LL, bestFrame - 1 );
352  bestFrame++;
353  }
354  return mAllRanges.count() - 1;
355  }
356  else
357  {
358  const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
359  // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
360  long long roughFrameStart = 0;
361  long long roughFrameEnd = totalFrameCount();
362  // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
363  // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
364  if ( mFrameDuration.originalUnit() != QgsUnitTypes::TemporalMonths && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalYears && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalDecades && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalCenturies )
365  {
366  // Only if we receive a valid frameStart, that is within current mTemporalExtents
367  // We tend to receive a framestart of 'now()' upon startup for example
368  if ( mTemporalExtents.contains( frameStart ) )
369  {
370  roughFrameStart = static_cast< long long >( std::floor( ( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
371  }
372  roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
373  }
374  for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
375  {
376  const QgsDateTimeRange range = dateTimeRangeForFrameNumber( i );
377  if ( range.overlaps( testFrame ) )
378  {
379  bestFrame = i;
380  break;
381  }
382  }
383  return bestFrame;
384  }
385 }
QgsUnitTypes::TemporalDecades
@ TemporalDecades
Decades.
Definition: qgsunittypes.h:159
QgsTemporalNavigationObject::rewindToStart
void rewindToStart()
Rewinds the temporal navigation to start of the temporal extent.
Definition: qgstemporalnavigationobject.cpp:302
QgsTemporalNavigationObject::Animated
@ Animated
Temporal navigation relies on frames within a datetime range.
Definition: qgstemporalnavigationobject.h:54
QgsInterval::seconds
double seconds() const
Returns the interval duration in seconds.
Definition: qgsinterval.h:236
QgsTemporalNavigationObject::findBestFrameNumberForFrameStart
long long findBestFrameNumberForFrameStart(const QDateTime &frameStart) const
Returns the best suited frame number for the specified datetime, based on the start of the correspond...
Definition: qgstemporalnavigationobject.cpp:340
QgsTemporalNavigationObject::totalFrameCount
long long totalFrameCount() const
Returns the total number of frames for the navigation.
Definition: qgstemporalnavigationobject.cpp:313
QgsInterval::originalDuration
double originalDuration() const
Returns the original interval duration.
Definition: qgsinterval.h:280
QgsTemporalNavigationObject::setTemporalRangeCumulative
void setTemporalRangeCumulative(bool state)
Sets the animation temporal range as cumulative.
Definition: qgstemporalnavigationobject.cpp:247
QgsTemporalNavigationObject::setAvailableTemporalRanges
void setAvailableTemporalRanges(const QList< QgsDateTimeRange > &ranges)
Sets the list of all available temporal ranges which have data available.
Definition: qgstemporalnavigationobject.cpp:181
QgsUnitTypes::TemporalYears
@ TemporalYears
Years.
Definition: qgsunittypes.h:158
qgstemporalnavigationobject.h
qgis.h
QgsTemporalNavigationObject::dateTimeRangeForFrameNumber
QgsDateTimeRange dateTimeRangeForFrameNumber(long long frame) const
Calculates the temporal range associated with a particular animation frame.
Definition: qgstemporalnavigationobject.cpp:87
QgsTemporalNavigationObject::playForward
void playForward()
Starts the animation playing in a forward direction up till the end of all frames.
Definition: qgstemporalnavigationobject.cpp:268
QgsTemporalNavigationObject::NavigationMode
NavigationMode
Represents the current temporal navigation mode.
Definition: qgstemporalnavigationobject.h:51
QgsTemporalNavigationObject::frameDuration
QgsInterval frameDuration() const
Returns the current set frame duration, which dictates the temporal length of each frame in the anima...
Definition: qgstemporalnavigationobject.cpp:228
QgsUnitTypes::TemporalIrregularStep
@ TemporalIrregularStep
Special "irregular step" time unit, used for temporal data which uses irregular, non-real-world unit ...
Definition: qgsunittypes.h:161
QgsTemporalNavigationObject::temporalExtentsChanged
void temporalExtentsChanged(const QgsDateTimeRange &extent)
Emitted whenever the temporalExtent extent changes.
QgsTemporalNavigationObject::navigationModeChanged
void navigationModeChanged(QgsTemporalNavigationObject::NavigationMode mode)
Emitted whenever the navigation mode changes.
QgsTemporalNavigationObject::QgsTemporalNavigationObject
QgsTemporalNavigationObject(QObject *parent=nullptr)
Constructor for QgsTemporalNavigationObject, with the specified parent object.
Definition: qgstemporalnavigationobject.cpp:22
QgsTemporalNavigationObject::setLooping
void setLooping(bool loop)
Sets whether the animation should loop after hitting the end or start frame.
Definition: qgstemporalnavigationobject.cpp:68
QgsUnitTypes::TemporalMonths
@ TemporalMonths
Months.
Definition: qgsunittypes.h:157
QgsTemporalNavigationObject::playBackward
void playBackward()
Starts the animation playing in a reverse direction until the beginning of the time range.
Definition: qgstemporalnavigationobject.cpp:280
QgsTemporalNavigationObject::skipToEnd
void skipToEnd()
Skips the temporal navigation to end of the temporal extent.
Definition: qgstemporalnavigationobject.cpp:307
QgsTemporalNavigationObject::setCurrentFrameNumber
void setCurrentFrameNumber(long long frame)
Sets the current animation frame number.
Definition: qgstemporalnavigationobject.cpp:191
QgsTemporalNavigationObject::setAnimationState
void setAnimationState(AnimationState state)
Sets the current animation state.
Definition: qgstemporalnavigationobject.cpp:326
QgsTemporalNavigationObject::stateChanged
void stateChanged(QgsTemporalNavigationObject::AnimationState state)
Emitted whenever the animation state changes.
QgsTemporalNavigationObject::setTemporalExtents
void setTemporalExtents(const QgsDateTimeRange &extents)
Sets the navigation temporal extents, which dictate the earliest and latest date time possible in the...
Definition: qgstemporalnavigationobject.cpp:144
QgsTemporalNavigationObject::setFramesPerSecond
void setFramesPerSecond(double rate)
Sets the animation frame rate, in frames per second.
Definition: qgstemporalnavigationobject.cpp:233
QgsTemporalController
A controller base class for temporal objects, contains a signal for notifying updates of the objects ...
Definition: qgstemporalcontroller.h:41
QgsTemporalNavigationObject::createExpressionContextScope
QgsExpressionContextScope * createExpressionContextScope() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Definition: qgstemporalnavigationobject.cpp:73
QgsTemporalNavigationObject::Idle
@ Idle
Animation is paused.
Definition: qgstemporalnavigationobject.h:63
QgsTemporalNavigationObject::setFrameDuration
void setFrameDuration(const QgsInterval &duration)
Sets the frame duration, which dictates the temporal length of each frame in the animation.
Definition: qgstemporalnavigationobject.cpp:208
qgstemporalutils.h
QgsExpressionContextScope
Single scope for storing variables and functions for use within a QgsExpressionContext....
Definition: qgsexpressioncontext.h:113
QgsTemporalNavigationObject::isLooping
bool isLooping() const
Returns true if the animation should loop after hitting the end or start frame.
Definition: qgstemporalnavigationobject.cpp:63
QgsTemporalController::updateTemporalRange
void updateTemporalRange(const QgsDateTimeRange &range)
Signals that a temporal range has changed and needs to be updated in all connected objects.
QgsTemporalUtils::calculateFrameTime
static QDateTime calculateFrameTime(const QDateTime &start, const long long frame, const QgsInterval &interval)
Calculates the frame time for an animation.
Definition: qgstemporalutils.cpp:167
QgsUnitTypes::TemporalCenturies
@ TemporalCenturies
Centuries.
Definition: qgsunittypes.h:160
QgsTemporalNavigationObject::pause
void pause()
Pauses the temporal navigation.
Definition: qgstemporalnavigationobject.cpp:262
QgsTemporalNavigationObject::next
void next()
Advances to the next frame.
Definition: qgstemporalnavigationobject.cpp:292
QgsTemporalNavigationObject::availableTemporalRanges
QList< QgsDateTimeRange > availableTemporalRanges() const
Returns the list of all available temporal ranges which have data available.
Definition: qgstemporalnavigationobject.cpp:186
QgsTemporalNavigationObject::temporalFrameDurationChanged
void temporalFrameDurationChanged(const QgsInterval &interval)
Emitted whenever the frameDuration interval of the controller changes.
QgsTemporalNavigationObject::temporalRangeCumulative
bool temporalRangeCumulative() const
Returns the animation temporal range cumulative settings.
Definition: qgstemporalnavigationobject.cpp:252
QgsInterval
A representation of the interval between two datetime values.
Definition: qgsinterval.h:41
QgsTemporalNavigationObject::previous
void previous()
Jumps back to the previous frame.
Definition: qgstemporalnavigationobject.cpp:297
QgsTemporalNavigationObject::NavigationOff
@ NavigationOff
Temporal navigation is disabled.
Definition: qgstemporalnavigationobject.h:53
QgsTemporalNavigationObject::AnimationState
AnimationState
Represents the current animation state.
Definition: qgstemporalnavigationobject.h:59
QgsTemporalNavigationObject::play
void play()
Starts playing the temporal navigation from its current frame, using the direction specified by anima...
Definition: qgstemporalnavigationobject.cpp:257
QgsTemporalNavigationObject::currentFrameNumber
long long currentFrameNumber() const
Returns the current frame number.
Definition: qgstemporalnavigationobject.cpp:203
QgsTemporalNavigationObject::setNavigationMode
void setNavigationMode(const NavigationMode mode)
Sets the temporal navigation mode.
Definition: qgstemporalnavigationobject.cpp:119
QgsTemporalNavigationObject::FixedRange
@ FixedRange
Temporal navigation relies on a fixed datetime range.
Definition: qgstemporalnavigationobject.h:55
QgsInterval::originalUnit
QgsUnitTypes::TemporalUnit originalUnit() const
Returns the original interval temporal unit.
Definition: qgsinterval.h:295
QgsTemporalNavigationObject::framesPerSecond
double framesPerSecond() const
Returns the animation frame rate, in frames per second.
Definition: qgstemporalnavigationobject.cpp:242
QgsTemporalNavigationObject::animationState
AnimationState animationState() const
Returns the current animation state.
Definition: qgstemporalnavigationobject.cpp:335
QgsTemporalNavigationObject::temporalExtents
QgsDateTimeRange temporalExtents() const
Returns the navigation temporal extents, which dictate the earliest and latest date time possible in ...
Definition: qgstemporalnavigationobject.cpp:176