QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
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 "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 if ( mCumulativeTemporalRange == state )
274 return;
275
276 mCumulativeTemporalRange = state;
277
278 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
279 {
280 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
281 }
282}
283
285{
286 return mCumulativeTemporalRange;
287}
288
290{
291 mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
292}
293
295{
296 mNewFrameTimer->stop();
298}
299
301{
302 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
303 {
304 // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
306 }
307
309 play();
310}
311
313{
314 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber <= 0 )
315 {
316 // 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
317 skipToEnd();
318 }
319
321 play();
322}
323
325{
326 setCurrentFrameNumber( mCurrentFrameNumber + 1 );
327}
328
330{
331 setCurrentFrameNumber( mCurrentFrameNumber - 1 );
332}
333
338
340{
341 const long long frame = totalFrameCount() - 1;
342 setCurrentFrameNumber( frame );
343}
344
346{
347 if ( mNavigationMode == Qgis::TemporalNavigationMode::Movie )
348 return mTotalMovieFrames;
349
350 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
351 {
352 return mAllRanges.count();
353 }
354 else
355 {
356 const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
357 return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
358 }
359}
360
362{
363 if ( mode != mPlayBackMode )
364 {
365 mPlayBackMode = mode;
366 emit stateChanged( mPlayBackMode );
367 }
368}
369
371{
372 return mPlayBackMode;
373}
374
375long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
376{
377 long long bestFrame = 0;
378 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
379 {
380 for ( const QgsDateTimeRange &range : mAllRanges )
381 {
382 if ( range.contains( frameStart ) )
383 return bestFrame;
384 else if ( range.begin() > frameStart )
385 // if we've gone past the target date, go back one frame if possible
386 return std::max( 0LL, bestFrame - 1 );
387 bestFrame++;
388 }
389 return mAllRanges.count() - 1;
390 }
391 else
392 {
393 const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
394 // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
395 long long roughFrameStart = 0;
396 long long roughFrameEnd = totalFrameCount();
397 // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
398 // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
399 if ( mFrameDuration.originalUnit() != Qgis::TemporalUnit::Months && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Years && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Decades && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Centuries )
400 {
401 // Only if we receive a valid frameStart, that is within current mTemporalExtents
402 // We tend to receive a framestart of 'now()' upon startup for example
403 if ( mTemporalExtents.contains( frameStart ) )
404 {
405 roughFrameStart = static_cast< long long >( std::floor( QgsInterval( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
406 }
407 roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
408 }
409 for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
410 {
412 if ( range.overlaps( testFrame ) )
413 {
414 bestFrame = i;
415 break;
416 }
417 }
418 return bestFrame;
419 }
420}
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