QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 = qgis::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  const QDateTime begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
97  const QDateTime end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
98 
99  QDateTime frameStart = begin;
100 
101  if ( mCumulativeTemporalRange )
102  frameStart = start;
103 
104  if ( end <= mTemporalExtents.end() )
105  return QgsDateTimeRange( frameStart, end, true, false );
106 
107  return QgsDateTimeRange( frameStart, mTemporalExtents.end(), true, false );
108 }
109 
111 {
112  if ( mNavigationMode == mode )
113  return;
114 
115  mNavigationMode = mode;
116  emit navigationModeChanged( mode );
117 
118  if ( !mBlockUpdateTemporalRangeSignal )
119  {
120  switch ( mNavigationMode )
121  {
122  case Animated:
123  emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
124  break;
125  case FixedRange:
126  emit updateTemporalRange( mTemporalExtents );
127  break;
128  case NavigationOff:
129  emit updateTemporalRange( QgsDateTimeRange() );
130  break;
131  }
132  }
133 }
134 
135 void QgsTemporalNavigationObject::setTemporalExtents( const QgsDateTimeRange &temporalExtents )
136 {
137  if ( mTemporalExtents == temporalExtents )
138  {
139  return;
140  }
141  QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
142  mTemporalExtents = temporalExtents;
143  mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
144  emit temporalExtentsChanged( mTemporalExtents );
145 
146  switch ( mNavigationMode )
147  {
148  case Animated:
149  {
150  int currentFrameNumber = mCurrentFrameNumber;
151 
152  // Force to emit signal if the current frame number doesn't change
153  if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
154  emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
155  break;
156  }
157  case FixedRange:
158  if ( !mBlockUpdateTemporalRangeSignal )
159  emit updateTemporalRange( mTemporalExtents );
160  break;
161  case NavigationOff:
162  break;
163  }
164 
165 }
166 
168 {
169  return mTemporalExtents;
170 }
171 
173 {
174  if ( mCurrentFrameNumber != frameNumber )
175  {
176  mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
177  QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
178 
179  if ( !mBlockUpdateTemporalRangeSignal )
180  emit updateTemporalRange( range );
181  }
182 }
183 
185 {
186  return mCurrentFrameNumber;
187 }
188 
190 {
191  if ( mFrameDuration == frameDuration )
192  {
193  return;
194  }
195  QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
196  mFrameDuration = frameDuration;
197 
198  mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
199  emit temporalFrameDurationChanged( mFrameDuration );
200 
201  // forcing an update of our views
202  QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
203 
204  if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Animated )
205  emit updateTemporalRange( range );
206 }
207 
209 {
210  return mFrameDuration;
211 }
212 
213 void QgsTemporalNavigationObject::setFramesPerSecond( double framesPerSeconds )
214 {
215  if ( framesPerSeconds > 0 )
216  {
217  mFramesPerSecond = framesPerSeconds;
218  mNewFrameTimer->setInterval( ( 1.0 / mFramesPerSecond ) * 1000 );
219  }
220 }
221 
223 {
224  return mFramesPerSecond;
225 }
226 
228 {
229  mCumulativeTemporalRange = state;
230 }
231 
233 {
234  return mCumulativeTemporalRange;
235 }
236 
238 {
239  mNewFrameTimer->start( ( 1.0 / mFramesPerSecond ) * 1000 );
240 }
241 
243 {
244  mNewFrameTimer->stop();
245  setAnimationState( AnimationState::Idle );
246 }
247 
249 {
250  if ( mPlayBackMode == Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
251  {
252  // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
253  rewindToStart();
254  }
255 
256  setAnimationState( AnimationState::Forward );
257  play();
258 }
259 
261 {
262  if ( mPlayBackMode == Idle && mCurrentFrameNumber <= 0 )
263  {
264  // 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
265  skipToEnd();
266  }
267 
268  setAnimationState( AnimationState::Reverse );
269  play();
270 }
271 
273 {
274  setCurrentFrameNumber( mCurrentFrameNumber + 1 );
275 }
276 
278 {
279  setCurrentFrameNumber( mCurrentFrameNumber - 1 );
280 }
281 
283 {
285 }
286 
288 {
289  const long long frame = totalFrameCount() - 1;
290  setCurrentFrameNumber( frame );
291 }
292 
294 {
295  QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
296  return std::floor( totalAnimationLength.seconds() / mFrameDuration.seconds() ) + 1;
297 }
298 
300 {
301  if ( mode != mPlayBackMode )
302  {
303  mPlayBackMode = mode;
304  emit stateChanged( mPlayBackMode );
305  }
306 }
307 
309 {
310  return mPlayBackMode;
311 }
312 
313 long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
314 {
315  long long bestFrame = 0;
316  QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
317  // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
318  long long roughFrameStart = 0;
319  long long roughFrameEnd = totalFrameCount();
320  // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
321  // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
322  if ( mFrameDuration.originalUnit() != QgsUnitTypes::TemporalMonths && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalYears && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalDecades && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalCenturies )
323  {
324  // Only if we receive a valid frameStart, that is within current mTemporalExtents
325  // We tend to receive a framestart of 'now()' upon startup for example
326  if ( mTemporalExtents.contains( frameStart ) )
327  {
328  roughFrameStart = std::floor( ( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() );
329  }
330  roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
331  }
332  for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
333  {
334  QgsDateTimeRange range = dateTimeRangeForFrameNumber( i );
335  if ( range.overlaps( testFrame ) )
336  {
337  bestFrame = i;
338  break;
339  }
340  }
341  return bestFrame;
342 }
Single scope for storing variables and functions for use within a QgsExpressionContext.
A representation of the interval between two datetime values.
Definition: qgsinterval.h:42
double originalDuration() const
Returns the original interval duration.
Definition: qgsinterval.h:280
double seconds() const
Returns the interval duration in seconds.
Definition: qgsinterval.h:236
QgsUnitTypes::TemporalUnit originalUnit() const
Returns the original interval temporal unit.
Definition: qgsinterval.h:295
A controller base class for temporal objects, contains a signal for notifying updates of the objects ...
void updateTemporalRange(const QgsDateTimeRange &range)
Signals that a temporal range has changed and needs to be updated in all connected objects.
bool isLooping() const
Returns true if the animation should loop after hitting the end or start frame.
void previous()
Jumps back to the previous frame.
double framesPerSecond() const
Returns the animation frame rate, in frames per second.
long long findBestFrameNumberForFrameStart(const QDateTime &frameStart) const
Returns the best suited frame number for the specified datetime, based on the start of the correspond...
QgsExpressionContextScope * createExpressionContextScope() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void playForward()
Starts the animation playing in a forward direction up till the end of all frames.
NavigationMode
Represents the current temporal navigation mode.
@ NavigationOff
Temporal navigation is disabled.
@ FixedRange
Temporal navigation relies on a fixed datetime range.
@ Animated
Temporal navigation relies on frames within a datetime range.
long long currentFrameNumber() const
Returns the current frame number.
void setFrameDuration(QgsInterval duration)
Sets the frame duration, which dictates the temporal length of each frame in the animation.
void rewindToStart()
Rewinds the temporal navigation to start of the temporal extent.
void pause()
Pauses the temporal navigation.
void setCurrentFrameNumber(long long frame)
Sets the current animation frame number.
long long totalFrameCount() const
Returns the total number of frames for the navigation.
void skipToEnd()
Skips the temporal navigation to end of the temporal extent.
void temporalFrameDurationChanged(const QgsInterval &interval)
Emitted whenever the frameDuration interval of the controller changes.
void navigationModeChanged(NavigationMode mode)
Emitted whenever the navigation mode changes.
void setFramesPerSecond(double rate)
Sets the animation frame rate, in frames per second.
QgsDateTimeRange temporalExtents() const
Returns the navigation temporal extents, which dictate the earliest and latest date time possible in ...
void stateChanged(AnimationState state)
Emitted whenever the animation state changes.
AnimationState
Represents the current animation state.
bool temporalRangeCumulative() const
Returns the animation temporal range cumulative settings.
void next()
Advances to the next frame.
QgsDateTimeRange dateTimeRangeForFrameNumber(long long frame) const
Calculates the temporal range associated with a particular animation frame.
void setTemporalExtents(const QgsDateTimeRange &extents)
Sets the navigation temporal extents, which dictate the earliest and latest date time possible in the...
void setTemporalRangeCumulative(bool state)
Sets the animation temporal range as cumulative.
void play()
Starts playing the temporal navigation from its current frame, using the direction specified by anima...
void setLooping(bool loop)
Sets whether the animation should loop after hitting the end or start frame.
void playBackward()
Starts the animation playing in a reverse direction until the beginning of the time range.
void temporalExtentsChanged(const QgsDateTimeRange &extent)
Emitted whenever the temporalExtent extent changes.
void setAnimationState(AnimationState state)
Sets the current animation state.
void setNavigationMode(const NavigationMode mode)
Sets the temporal navigation mode.
QgsTemporalNavigationObject(QObject *parent=nullptr)
Constructor for QgsTemporalNavigationObject, with the specified parent object.
AnimationState animationState() const
Returns the current animation state.
QgsInterval frameDuration() const
Returns the current set frame duration, which dictates the temporal length of each frame in the anima...
static QDateTime calculateFrameTime(const QDateTime &start, const long long frame, const QgsInterval interval)
Calculates the frame time for an animation.
@ TemporalMonths
Months.
Definition: qgsunittypes.h:157
@ TemporalDecades
Decades.
Definition: qgsunittypes.h:159
@ TemporalCenturies
Centuries.
Definition: qgsunittypes.h:160
@ TemporalYears
Years.
Definition: qgsunittypes.h:158