QGIS API Documentation 3.41.0-Master (45a0abf3bec)
Loading...
Searching...
No Matches
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 "moc_qgstemporalnavigationobject.cpp"
20#include "qgis.h"
21#include "qgstemporalutils.h"
22#include "qgsunittypes.h"
23
25 : QgsTemporalController( parent )
26{
27 mNewFrameTimer = new QTimer( this );
28
29 connect( mNewFrameTimer, &QTimer::timeout,
30 this, &QgsTemporalNavigationObject::timerTimeout );
31}
32
33void QgsTemporalNavigationObject::timerTimeout()
34{
35 switch ( mPlayBackMode )
36 {
38 next();
39 if ( mCurrentFrameNumber >= totalFrameCount() - 1 )
40 {
41 if ( mLoopAnimation )
42 mCurrentFrameNumber = -1; // we don't jump immediately to frame 0, instead we delay that till the next timeout
43 else
44 pause();
45 }
46 break;
47
49 previous();
50 if ( mCurrentFrameNumber <= 0 )
51 {
52 if ( mLoopAnimation )
53 mCurrentFrameNumber = totalFrameCount(); // we don't jump immediately to real last frame..., instead we delay that till the next timeout
54 else
55 pause();
56 }
57 break;
58
60 // should not happen - in an idle state the timeout won't occur
61 break;
62 }
63}
64
66{
67 return mTotalMovieFrames;
68}
69
71{
72 if ( frames == mTotalMovieFrames )
73 return;
74
75 mTotalMovieFrames = frames;
76
77 emit totalMovieFramesChanged( mTotalMovieFrames );
78 emit temporalExtentsChanged( mTemporalExtents );
79}
80
82{
83 return mLoopAnimation;
84}
85
87{
88 mLoopAnimation = loopAnimation;
89}
90
92{
93 std::unique_ptr< QgsExpressionContextScope > scope = std::make_unique< QgsExpressionContextScope >( QStringLiteral( "temporal" ) );
94 scope->setVariable( QStringLiteral( "frame_rate" ), mFramesPerSecond, true );
95 scope->setVariable( QStringLiteral( "frame_number" ), mCurrentFrameNumber, true );
96 scope->setVariable( QStringLiteral( "frame_duration" ), mFrameDuration, true );
97 scope->setVariable( QStringLiteral( "frame_timestep" ), mFrameDuration.originalDuration(), true );
98 scope->setVariable( QStringLiteral( "frame_timestep_unit" ), static_cast< int >( mFrameDuration.originalUnit() ), true );
99 scope->setVariable( QStringLiteral( "frame_timestep_units" ), QgsUnitTypes::toString( mFrameDuration.originalUnit() ), true );
100 scope->setVariable( QStringLiteral( "animation_start_time" ), mTemporalExtents.begin(), true );
101 scope->setVariable( QStringLiteral( "animation_end_time" ), mTemporalExtents.end(), true );
102 scope->setVariable( QStringLiteral( "animation_interval" ), QgsInterval( mTemporalExtents.end() - mTemporalExtents.begin() ), true );
103 scope->setVariable( QStringLiteral( "total_frame_count" ), totalFrameCount() );
104
105 scope->addHiddenVariable( QStringLiteral( "frame_timestep_unit" ) );
106
107 return scope.release();
108}
109
111{
112 const QDateTime start = mTemporalExtents.begin();
113
114 if ( frame < 0 )
115 frame = 0;
116
117 const long long nextFrame = frame + 1;
118
119 QDateTime begin;
120 QDateTime end;
121 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
122 {
123 if ( mAllRanges.empty() )
124 return QgsDateTimeRange();
125
126 return frame < mAllRanges.size() ? mAllRanges.at( frame ) : mAllRanges.constLast();
127 }
128 else
129 {
130 begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
131 end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
132 }
133
134 QDateTime frameStart = begin;
135
136 if ( mCumulativeTemporalRange )
137 frameStart = start;
138
139 return QgsDateTimeRange( frameStart, end, true, false );
140}
141
143{
144 if ( mNavigationMode == mode )
145 return;
146
147 mNavigationMode = mode;
148 emit navigationModeChanged( mode );
149
150 if ( !mBlockUpdateTemporalRangeSignal )
151 {
152 switch ( mNavigationMode )
153 {
155 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
156 break;
158 emit updateTemporalRange( mTemporalExtents );
159 break;
163 break;
164 }
165 }
166}
167
169{
170 if ( mTemporalExtents == temporalExtents )
171 {
172 return;
173 }
175 mTemporalExtents = temporalExtents;
176 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
177 emit temporalExtentsChanged( mTemporalExtents );
178
179 switch ( mNavigationMode )
180 {
182 {
183 const long long currentFrameNumber = mCurrentFrameNumber;
184
185 // Force to emit signal if the current frame number doesn't change
186 if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
187 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
188 break;
189 }
191 if ( !mBlockUpdateTemporalRangeSignal )
192 emit updateTemporalRange( mTemporalExtents );
193 break;
196 break;
197 }
198
199}
200
202{
203 return mTemporalExtents;
204}
205
206void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
207{
208 mAllRanges = ranges;
209}
210
212{
213 return mAllRanges;
214}
215
217{
218 if ( mCurrentFrameNumber != frameNumber )
219 {
220 mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
221 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
222
223 if ( !mBlockUpdateTemporalRangeSignal )
224 emit updateTemporalRange( range );
225 }
226}
227
229{
230 return mCurrentFrameNumber;
231}
232
234{
235 if ( mFrameDuration == frameDuration )
236 {
237 return;
238 }
239
241 mFrameDuration = frameDuration;
242
243 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
244 emit temporalFrameDurationChanged( mFrameDuration );
245
246 // forcing an update of our views
247 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
248
249 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
250 emit updateTemporalRange( range );
251}
252
254{
255 return mFrameDuration;
256}
257
259{
260 if ( framesPerSeconds > 0 )
261 {
262 mFramesPerSecond = framesPerSeconds;
263 mNewFrameTimer->setInterval( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
264 }
265}
266
268{
269 return mFramesPerSecond;
270}
271
273{
274 if ( mCumulativeTemporalRange == state )
275 return;
276
277 mCumulativeTemporalRange = state;
278
279 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
280 {
281 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
282 }
283}
284
286{
287 return mCumulativeTemporalRange;
288}
289
291{
292 mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
293}
294
296{
297 mNewFrameTimer->stop();
299}
300
302{
303 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
304 {
305 // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
307 }
308
310 play();
311}
312
314{
315 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber <= 0 )
316 {
317 // 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
318 skipToEnd();
319 }
320
322 play();
323}
324
326{
327 setCurrentFrameNumber( mCurrentFrameNumber + 1 );
328}
329
331{
332 setCurrentFrameNumber( mCurrentFrameNumber - 1 );
333}
334
339
341{
342 const long long frame = totalFrameCount() - 1;
343 setCurrentFrameNumber( frame );
344}
345
347{
348 if ( mNavigationMode == Qgis::TemporalNavigationMode::Movie )
349 return mTotalMovieFrames;
350
351 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
352 {
353 return mAllRanges.count();
354 }
355 else
356 {
357 const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
358 return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
359 }
360}
361
363{
364 if ( mode != mPlayBackMode )
365 {
366 mPlayBackMode = mode;
367 emit stateChanged( mPlayBackMode );
368 }
369}
370
372{
373 return mPlayBackMode;
374}
375
376long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
377{
378 long long bestFrame = 0;
379 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
380 {
381 for ( const QgsDateTimeRange &range : mAllRanges )
382 {
383 if ( range.contains( frameStart ) )
384 return bestFrame;
385 else if ( range.begin() > frameStart )
386 // if we've gone past the target date, go back one frame if possible
387 return std::max( 0LL, bestFrame - 1 );
388 bestFrame++;
389 }
390 return mAllRanges.count() - 1;
391 }
392 else
393 {
394 const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
395 // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
396 long long roughFrameStart = 0;
397 long long roughFrameEnd = totalFrameCount();
398 // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
399 // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
400 if ( mFrameDuration.originalUnit() != Qgis::TemporalUnit::Months && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Years && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Decades && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Centuries )
401 {
402 // Only if we receive a valid frameStart, that is within current mTemporalExtents
403 // We tend to receive a framestart of 'now()' upon startup for example
404 if ( mTemporalExtents.contains( frameStart ) )
405 {
406 roughFrameStart = static_cast< long long >( std::floor( QgsInterval( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
407 }
408 roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
409 }
410 for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
411 {
413 if ( range.overlaps( testFrame ) )
414 {
415 bestFrame = i;
416 break;
417 }
418 }
419 return bestFrame;
420 }
421}
TemporalNavigationMode
Temporal navigation modes.
Definition qgis.h:2366
@ 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.
@ 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:2382
@ 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.
double seconds() const
Returns the interval duration in seconds.
Qgis::TemporalUnit originalUnit() const
Returns the original interval temporal unit.
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