QGIS API Documentation  3.19.0-Master (c022ae99b5)
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  if ( end <= mTemporalExtents.end() )
117  return QgsDateTimeRange( frameStart, end, true, false );
118 
119  return QgsDateTimeRange( frameStart, mTemporalExtents.end(), true, false );
120 }
121 
123 {
124  if ( mNavigationMode == mode )
125  return;
126 
127  mNavigationMode = mode;
128  emit navigationModeChanged( mode );
129 
130  if ( !mBlockUpdateTemporalRangeSignal )
131  {
132  switch ( mNavigationMode )
133  {
134  case Animated:
135  emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
136  break;
137  case FixedRange:
138  emit updateTemporalRange( mTemporalExtents );
139  break;
140  case NavigationOff:
141  emit updateTemporalRange( QgsDateTimeRange() );
142  break;
143  }
144  }
145 }
146 
147 void QgsTemporalNavigationObject::setTemporalExtents( const QgsDateTimeRange &temporalExtents )
148 {
149  if ( mTemporalExtents == temporalExtents )
150  {
151  return;
152  }
153  QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
154  mTemporalExtents = temporalExtents;
155  mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
156  emit temporalExtentsChanged( mTemporalExtents );
157 
158  switch ( mNavigationMode )
159  {
160  case Animated:
161  {
162  int currentFrameNumber = mCurrentFrameNumber;
163 
164  // Force to emit signal if the current frame number doesn't change
165  if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
166  emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
167  break;
168  }
169  case FixedRange:
170  if ( !mBlockUpdateTemporalRangeSignal )
171  emit updateTemporalRange( mTemporalExtents );
172  break;
173  case NavigationOff:
174  break;
175  }
176 
177 }
178 
180 {
181  return mTemporalExtents;
182 }
183 
184 void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
185 {
186  mAllRanges = ranges;
187 }
188 
190 {
191  return mAllRanges;
192 }
193 
195 {
196  if ( mCurrentFrameNumber != frameNumber )
197  {
198  mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
199  QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
200 
201  if ( !mBlockUpdateTemporalRangeSignal )
202  emit updateTemporalRange( range );
203  }
204 }
205 
207 {
208  return mCurrentFrameNumber;
209 }
210 
212 {
213  if ( mFrameDuration == frameDuration )
214  {
215  return;
216  }
217 
218  QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
219  mFrameDuration = frameDuration;
220 
221  mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
222  emit temporalFrameDurationChanged( mFrameDuration );
223 
224  // forcing an update of our views
225  QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
226 
227  if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Animated )
228  emit updateTemporalRange( range );
229 }
230 
232 {
233  return mFrameDuration;
234 }
235 
236 void QgsTemporalNavigationObject::setFramesPerSecond( double framesPerSeconds )
237 {
238  if ( framesPerSeconds > 0 )
239  {
240  mFramesPerSecond = framesPerSeconds;
241  mNewFrameTimer->setInterval( ( 1.0 / mFramesPerSecond ) * 1000 );
242  }
243 }
244 
246 {
247  return mFramesPerSecond;
248 }
249 
251 {
252  mCumulativeTemporalRange = state;
253 }
254 
256 {
257  return mCumulativeTemporalRange;
258 }
259 
261 {
262  mNewFrameTimer->start( ( 1.0 / mFramesPerSecond ) * 1000 );
263 }
264 
266 {
267  mNewFrameTimer->stop();
268  setAnimationState( AnimationState::Idle );
269 }
270 
272 {
273  if ( mPlayBackMode == Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
274  {
275  // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
276  rewindToStart();
277  }
278 
279  setAnimationState( AnimationState::Forward );
280  play();
281 }
282 
284 {
285  if ( mPlayBackMode == Idle && mCurrentFrameNumber <= 0 )
286  {
287  // 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
288  skipToEnd();
289  }
290 
291  setAnimationState( AnimationState::Reverse );
292  play();
293 }
294 
296 {
297  setCurrentFrameNumber( mCurrentFrameNumber + 1 );
298 }
299 
301 {
302  setCurrentFrameNumber( mCurrentFrameNumber - 1 );
303 }
304 
306 {
308 }
309 
311 {
312  const long long frame = totalFrameCount() - 1;
313  setCurrentFrameNumber( frame );
314 }
315 
317 {
318  if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
319  {
320  return mAllRanges.count();
321  }
322  else
323  {
324  QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
325  return std::floor( totalAnimationLength.seconds() / mFrameDuration.seconds() ) + 1;
326  }
327 }
328 
330 {
331  if ( mode != mPlayBackMode )
332  {
333  mPlayBackMode = mode;
334  emit stateChanged( mPlayBackMode );
335  }
336 }
337 
339 {
340  return mPlayBackMode;
341 }
342 
343 long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
344 {
345  long long bestFrame = 0;
346  if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
347  {
348  for ( const QgsDateTimeRange &range : mAllRanges )
349  {
350  if ( range.contains( frameStart ) )
351  return bestFrame;
352  else if ( range.begin() > frameStart )
353  // if we've gone past the target date, go back one frame if possible
354  return std::max( 0LL, bestFrame - 1 );
355  bestFrame++;
356  }
357  return mAllRanges.count() - 1;
358  }
359  else
360  {
361  QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
362  // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
363  long long roughFrameStart = 0;
364  long long roughFrameEnd = totalFrameCount();
365  // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
366  // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
367  if ( mFrameDuration.originalUnit() != QgsUnitTypes::TemporalMonths && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalYears && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalDecades && mFrameDuration.originalUnit() != QgsUnitTypes::TemporalCenturies )
368  {
369  // Only if we receive a valid frameStart, that is within current mTemporalExtents
370  // We tend to receive a framestart of 'now()' upon startup for example
371  if ( mTemporalExtents.contains( frameStart ) )
372  {
373  roughFrameStart = std::floor( ( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() );
374  }
375  roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
376  }
377  for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
378  {
379  QgsDateTimeRange range = dateTimeRangeForFrameNumber( i );
380  if ( range.overlaps( testFrame ) )
381  {
382  bestFrame = i;
383  break;
384  }
385  }
386  return bestFrame;
387  }
388 }
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.
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.
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 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.
QList< QgsDateTimeRange > availableTemporalRanges() const
Returns the list of all available temporal ranges which have data available.
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
@ TemporalIrregularStep
Special "irregular step" time unit, used for temporal data which uses irregular, non-real-world unit ...
Definition: qgsunittypes.h:161
@ TemporalDecades
Decades.
Definition: qgsunittypes.h:159
@ TemporalCenturies
Centuries.
Definition: qgsunittypes.h:160
@ TemporalYears
Years.
Definition: qgsunittypes.h:158