QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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#include "qgsunittypes.h"
22
24 : QgsTemporalController( parent )
25{
26 mNewFrameTimer = new QTimer( this );
27
28 connect( mNewFrameTimer, &QTimer::timeout,
29 this, &QgsTemporalNavigationObject::timerTimeout );
30}
31
32void QgsTemporalNavigationObject::timerTimeout()
33{
34 switch ( mPlayBackMode )
35 {
37 next();
38 if ( mCurrentFrameNumber >= totalFrameCount() - 1 )
39 {
40 if ( mLoopAnimation )
41 mCurrentFrameNumber = -1; // we don't jump immediately to frame 0, instead we delay that till the next timeout
42 else
43 pause();
44 }
45 break;
46
48 previous();
49 if ( mCurrentFrameNumber <= 0 )
50 {
51 if ( mLoopAnimation )
52 mCurrentFrameNumber = totalFrameCount(); // we don't jump immediately to real last frame..., instead we delay that till the next timeout
53 else
54 pause();
55 }
56 break;
57
59 // should not happen - in an idle state the timeout won't occur
60 break;
61 }
62}
63
65{
66 return mTotalMovieFrames;
67}
68
70{
71 if ( frames == mTotalMovieFrames )
72 return;
73
74 mTotalMovieFrames = frames;
75
76 emit totalMovieFramesChanged( mTotalMovieFrames );
77 emit temporalExtentsChanged( mTemporalExtents );
78}
79
81{
82 return mLoopAnimation;
83}
84
86{
87 mLoopAnimation = loopAnimation;
88}
89
91{
92 std::unique_ptr< QgsExpressionContextScope > scope = std::make_unique< QgsExpressionContextScope >( QStringLiteral( "temporal" ) );
93 scope->setVariable( QStringLiteral( "frame_rate" ), mFramesPerSecond, true );
94 scope->setVariable( QStringLiteral( "frame_number" ), mCurrentFrameNumber, true );
95 scope->setVariable( QStringLiteral( "frame_duration" ), mFrameDuration, true );
96 scope->setVariable( QStringLiteral( "frame_timestep" ), mFrameDuration.originalDuration(), true );
97 scope->setVariable( QStringLiteral( "frame_timestep_unit" ), static_cast< int >( mFrameDuration.originalUnit() ), true );
98 scope->setVariable( QStringLiteral( "frame_timestep_units" ), QgsUnitTypes::toString( mFrameDuration.originalUnit() ), true );
99 scope->setVariable( QStringLiteral( "animation_start_time" ), mTemporalExtents.begin(), true );
100 scope->setVariable( QStringLiteral( "animation_end_time" ), mTemporalExtents.end(), true );
101 scope->setVariable( QStringLiteral( "animation_interval" ), QgsInterval( mTemporalExtents.end() - mTemporalExtents.begin() ), true );
102 scope->setVariable( QStringLiteral( "total_frame_count" ), totalFrameCount() );
103
104 scope->addHiddenVariable( QStringLiteral( "frame_timestep_unit" ) );
105
106 return scope.release();
107}
108
110{
111 const QDateTime start = mTemporalExtents.begin();
112
113 if ( frame < 0 )
114 frame = 0;
115
116 const long long nextFrame = frame + 1;
117
118 QDateTime begin;
119 QDateTime end;
120 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
121 {
122 if ( mAllRanges.empty() )
123 return QgsDateTimeRange();
124
125 return frame < mAllRanges.size() ? mAllRanges.at( frame ) : mAllRanges.constLast();
126 }
127 else
128 {
129 begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
130 end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
131 }
132
133 QDateTime frameStart = begin;
134
135 if ( mCumulativeTemporalRange )
136 frameStart = start;
137
138 return QgsDateTimeRange( frameStart, end, true, false );
139}
140
142{
143 if ( mNavigationMode == mode )
144 return;
145
146 mNavigationMode = mode;
147 emit navigationModeChanged( mode );
148
149 if ( !mBlockUpdateTemporalRangeSignal )
150 {
151 switch ( mNavigationMode )
152 {
154 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
155 break;
157 emit updateTemporalRange( mTemporalExtents );
158 break;
162 break;
163 }
164 }
165}
166
168{
169 if ( mTemporalExtents == temporalExtents )
170 {
171 return;
172 }
174 mTemporalExtents = temporalExtents;
175 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
176 emit temporalExtentsChanged( mTemporalExtents );
177
178 switch ( mNavigationMode )
179 {
181 {
182 const long long currentFrameNumber = mCurrentFrameNumber;
183
184 // Force to emit signal if the current frame number doesn't change
185 if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
186 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
187 break;
188 }
190 if ( !mBlockUpdateTemporalRangeSignal )
191 emit updateTemporalRange( mTemporalExtents );
192 break;
195 break;
196 }
197
198}
199
201{
202 return mTemporalExtents;
203}
204
205void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
206{
207 mAllRanges = ranges;
208}
209
211{
212 return mAllRanges;
213}
214
216{
217 if ( mCurrentFrameNumber != frameNumber )
218 {
219 mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
220 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
221
222 if ( !mBlockUpdateTemporalRangeSignal )
223 emit updateTemporalRange( range );
224 }
225}
226
228{
229 return mCurrentFrameNumber;
230}
231
233{
234 if ( mFrameDuration == frameDuration )
235 {
236 return;
237 }
238
240 mFrameDuration = frameDuration;
241
242 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
243 emit temporalFrameDurationChanged( mFrameDuration );
244
245 // forcing an update of our views
246 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
247
248 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
249 emit updateTemporalRange( range );
250}
251
253{
254 return mFrameDuration;
255}
256
258{
259 if ( framesPerSeconds > 0 )
260 {
261 mFramesPerSecond = framesPerSeconds;
262 mNewFrameTimer->setInterval( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
263 }
264}
265
267{
268 return mFramesPerSecond;
269}
270
272{
273 mCumulativeTemporalRange = state;
274}
275
277{
278 return mCumulativeTemporalRange;
279}
280
282{
283 mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
284}
285
287{
288 mNewFrameTimer->stop();
290}
291
293{
294 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
295 {
296 // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
298 }
299
301 play();
302}
303
305{
306 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber <= 0 )
307 {
308 // 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
309 skipToEnd();
310 }
311
313 play();
314}
315
317{
318 setCurrentFrameNumber( mCurrentFrameNumber + 1 );
319}
320
322{
323 setCurrentFrameNumber( mCurrentFrameNumber - 1 );
324}
325
327{
329}
330
332{
333 const long long frame = totalFrameCount() - 1;
334 setCurrentFrameNumber( frame );
335}
336
338{
339 if ( mNavigationMode == Qgis::TemporalNavigationMode::Movie )
340 return mTotalMovieFrames;
341
342 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
343 {
344 return mAllRanges.count();
345 }
346 else
347 {
348 const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
349 return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
350 }
351}
352
354{
355 if ( mode != mPlayBackMode )
356 {
357 mPlayBackMode = mode;
358 emit stateChanged( mPlayBackMode );
359 }
360}
361
363{
364 return mPlayBackMode;
365}
366
367long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
368{
369 long long bestFrame = 0;
370 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
371 {
372 for ( const QgsDateTimeRange &range : mAllRanges )
373 {
374 if ( range.contains( frameStart ) )
375 return bestFrame;
376 else if ( range.begin() > frameStart )
377 // if we've gone past the target date, go back one frame if possible
378 return std::max( 0LL, bestFrame - 1 );
379 bestFrame++;
380 }
381 return mAllRanges.count() - 1;
382 }
383 else
384 {
385 const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
386 // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
387 long long roughFrameStart = 0;
388 long long roughFrameEnd = totalFrameCount();
389 // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
390 // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
391 if ( mFrameDuration.originalUnit() != Qgis::TemporalUnit::Months && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Years && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Decades && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Centuries )
392 {
393 // Only if we receive a valid frameStart, that is within current mTemporalExtents
394 // We tend to receive a framestart of 'now()' upon startup for example
395 if ( mTemporalExtents.contains( frameStart ) )
396 {
397 roughFrameStart = static_cast< long long >( std::floor( QgsInterval( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
398 }
399 roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
400 }
401 for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
402 {
404 if ( range.overlaps( testFrame ) )
405 {
406 bestFrame = i;
407 break;
408 }
409 }
410 return bestFrame;
411 }
412}
TemporalNavigationMode
Temporal navigation modes.
Definition: qgis.h:2056
@ 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 (since QG...
@ FixedRange
Temporal navigation relies on a fixed datetime range.
@ Disabled
Temporal navigation is disabled.
@ IrregularStep
Special 'irregular step' time unit, used for temporal data which uses irregular, non-real-world unit ...
@ Centuries
Centuries.
AnimationState
Animation states.
Definition: qgis.h:2072
@ Forward
Animation is playing forward.
@ Reverse
Animation is playing in reverse.
@ Idle
Animation is paused.
Single scope for storing variables and functions for use within a QgsExpressionContext.
A representation of the interval between two datetime values.
Definition: qgsinterval.h:46
double originalDuration() const
Returns the original interval duration.
Definition: qgsinterval.h:304
double seconds() const
Returns the interval duration in seconds.
Definition: qgsinterval.h:260
Qgis::TemporalUnit originalUnit() const
Returns the original interval temporal unit.
Definition: qgsinterval.h:319
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.
void stateChanged(Qgis::AnimationState state)
Emitted whenever the animation state changes.
bool isLooping() const
Returns true if the animation should loop after hitting the end or start frame.
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.
long long findBestFrameNumberForFrameStart(const QDateTime &frameStart) const
Returns the best suited frame number for the specified datetime, based on the start of the correspond...
void navigationModeChanged(Qgis::TemporalNavigationMode mode)
Emitted whenever the navigation mode changes.
void setNavigationMode(const Qgis::TemporalNavigationMode mode)
Sets the temporal navigation mode.
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.
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.
Qgis::AnimationState animationState() const
Returns the current animation state.
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.
QgsDateTimeRange temporalExtents() const
Returns the navigation temporal extents, which dictate the earliest and latest date time possible in ...
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.
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.
QgsTemporalNavigationObject(QObject *parent=nullptr)
Constructor for QgsTemporalNavigationObject, with the specified parent object.
void setAnimationState(Qgis::AnimationState state)
Sets the current animation state.
QgsInterval frameDuration() const
Returns the current set frame duration, which dictates the temporal length of each frame in the anima...
T begin() const
Returns the beginning of the range.
Definition: qgsrange.h:444
bool contains(const QgsTemporalRange< T > &other) const
Returns true if this range contains another range.
Definition: qgsrange.h:510
T end() const
Returns the upper bound of the range.
Definition: qgsrange.h:451
bool overlaps(const QgsTemporalRange< T > &other) const
Returns true if this range overlaps another range.
Definition: qgsrange.h:569
static QDateTime calculateFrameTime(const QDateTime &start, const long long frame, const QgsInterval &interval)
Calculates the frame time for an animation.
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition: qgsrange.h:742