QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
31void 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
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( "frame_timestep_units" ), QgsUnitTypes::toString( mFrameDuration.originalUnit() ), true );
82 scope->setVariable( QStringLiteral( "animation_start_time" ), mTemporalExtents.begin(), true );
83 scope->setVariable( QStringLiteral( "animation_end_time" ), mTemporalExtents.end(), true );
84 scope->setVariable( QStringLiteral( "animation_interval" ), mTemporalExtents.end() - mTemporalExtents.begin(), true );
85
86 scope->addHiddenVariable( QStringLiteral( "frame_timestep_unit" ) );
87
88 return scope.release();
89}
90
91QgsDateTimeRange QgsTemporalNavigationObject::dateTimeRangeForFrameNumber( long long frame ) const
92{
93 const QDateTime start = mTemporalExtents.begin();
94
95 if ( frame < 0 )
96 frame = 0;
97
98 const long long nextFrame = frame + 1;
99
100 QDateTime begin;
101 QDateTime end;
102 if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
103 {
104 if ( mAllRanges.empty() )
105 return QgsDateTimeRange();
106
107 return frame < mAllRanges.size() ? mAllRanges.at( frame ) : mAllRanges.constLast();
108 }
109 else
110 {
111 begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
112 end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
113 }
114
115 QDateTime frameStart = begin;
116
117 if ( mCumulativeTemporalRange )
118 frameStart = start;
119
120 return QgsDateTimeRange( frameStart, end, true, false );
121}
122
124{
125 if ( mNavigationMode == mode )
126 return;
127
128 mNavigationMode = mode;
129 emit navigationModeChanged( mode );
130
131 if ( !mBlockUpdateTemporalRangeSignal )
132 {
133 switch ( mNavigationMode )
134 {
135 case Animated:
136 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
137 break;
138 case FixedRange:
139 emit updateTemporalRange( mTemporalExtents );
140 break;
141 case NavigationOff:
142 emit updateTemporalRange( QgsDateTimeRange() );
143 break;
144 }
145 }
146}
147
148void QgsTemporalNavigationObject::setTemporalExtents( const QgsDateTimeRange &temporalExtents )
149{
150 if ( mTemporalExtents == temporalExtents )
151 {
152 return;
153 }
154 const QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
155 mTemporalExtents = temporalExtents;
156 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
157 emit temporalExtentsChanged( mTemporalExtents );
158
159 switch ( mNavigationMode )
160 {
161 case Animated:
162 {
163 const long long currentFrameNumber = mCurrentFrameNumber;
164
165 // Force to emit signal if the current frame number doesn't change
166 if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
167 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
168 break;
169 }
170 case FixedRange:
171 if ( !mBlockUpdateTemporalRangeSignal )
172 emit updateTemporalRange( mTemporalExtents );
173 break;
174 case NavigationOff:
175 break;
176 }
177
178}
179
181{
182 return mTemporalExtents;
183}
184
185void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
186{
187 mAllRanges = ranges;
188}
189
191{
192 return mAllRanges;
193}
194
196{
197 if ( mCurrentFrameNumber != frameNumber )
198 {
199 mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
200 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
201
202 if ( !mBlockUpdateTemporalRangeSignal )
203 emit updateTemporalRange( range );
204 }
205}
206
208{
209 return mCurrentFrameNumber;
210}
211
213{
214 if ( mFrameDuration == frameDuration )
215 {
216 return;
217 }
218
219 const QgsDateTimeRange oldFrame = dateTimeRangeForFrameNumber( currentFrameNumber() );
220 mFrameDuration = frameDuration;
221
222 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
223 emit temporalFrameDurationChanged( mFrameDuration );
224
225 // forcing an update of our views
226 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
227
228 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Animated )
229 emit updateTemporalRange( range );
230}
231
233{
234 return mFrameDuration;
235}
236
238{
239 if ( framesPerSeconds > 0 )
240 {
241 mFramesPerSecond = framesPerSeconds;
242 mNewFrameTimer->setInterval( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
243 }
244}
245
247{
248 return mFramesPerSecond;
249}
250
252{
253 mCumulativeTemporalRange = state;
254}
255
257{
258 return mCumulativeTemporalRange;
259}
260
262{
263 mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
264}
265
267{
268 mNewFrameTimer->stop();
269 setAnimationState( AnimationState::Idle );
270}
271
273{
274 if ( mPlayBackMode == Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
275 {
276 // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
278 }
279
280 setAnimationState( AnimationState::Forward );
281 play();
282}
283
285{
286 if ( mPlayBackMode == Idle && mCurrentFrameNumber <= 0 )
287 {
288 // 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
289 skipToEnd();
290 }
291
292 setAnimationState( AnimationState::Reverse );
293 play();
294}
295
297{
298 setCurrentFrameNumber( mCurrentFrameNumber + 1 );
299}
300
302{
303 setCurrentFrameNumber( mCurrentFrameNumber - 1 );
304}
305
307{
309}
310
312{
313 const long long frame = totalFrameCount() - 1;
314 setCurrentFrameNumber( frame );
315}
316
318{
319 if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
320 {
321 return mAllRanges.count();
322 }
323 else
324 {
325 const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
326 return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
327 }
328}
329
331{
332 if ( mode != mPlayBackMode )
333 {
334 mPlayBackMode = mode;
335 emit stateChanged( mPlayBackMode );
336 }
337}
338
340{
341 return mPlayBackMode;
342}
343
344long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
345{
346 long long bestFrame = 0;
347 if ( mFrameDuration.originalUnit() == QgsUnitTypes::TemporalIrregularStep )
348 {
349 for ( const QgsDateTimeRange &range : mAllRanges )
350 {
351 if ( range.contains( frameStart ) )
352 return bestFrame;
353 else if ( range.begin() > frameStart )
354 // if we've gone past the target date, go back one frame if possible
355 return std::max( 0LL, bestFrame - 1 );
356 bestFrame++;
357 }
358 return mAllRanges.count() - 1;
359 }
360 else
361 {
362 const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
363 // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
364 long long roughFrameStart = 0;
365 long long roughFrameEnd = totalFrameCount();
366 // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
367 // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
369 {
370 // Only if we receive a valid frameStart, that is within current mTemporalExtents
371 // We tend to receive a framestart of 'now()' upon startup for example
372 if ( mTemporalExtents.contains( frameStart ) )
373 {
374 roughFrameStart = static_cast< long long >( std::floor( ( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
375 }
376 roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
377 }
378 for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
379 {
380 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( i );
381 if ( range.overlaps( testFrame ) )
382 {
383 bestFrame = i;
384 break;
385 }
386 }
387 return bestFrame;
388 }
389}
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...
void stateChanged(QgsTemporalNavigationObject::AnimationState state)
Emitted whenever the animation state changes.
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 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 ...
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 navigationModeChanged(QgsTemporalNavigationObject::NavigationMode mode)
Emitted whenever the navigation mode changes.
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.
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
@ 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