QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
20#include "qgis.h"
21#include "qgstemporalutils.h"
22#include "qgsunittypes.h"
23
24#include "moc_qgstemporalnavigationobject.cpp"
25
27 : QgsTemporalController( parent )
28{
29 mNewFrameTimer = new QTimer( this );
30
31 connect( mNewFrameTimer, &QTimer::timeout,
32 this, &QgsTemporalNavigationObject::timerTimeout );
33}
34
35void QgsTemporalNavigationObject::timerTimeout()
36{
37 switch ( mPlayBackMode )
38 {
40 next();
41 if ( mCurrentFrameNumber >= totalFrameCount() - 1 )
42 {
43 if ( mLoopAnimation )
44 mCurrentFrameNumber = -1; // we don't jump immediately to frame 0, instead we delay that till the next timeout
45 else
46 pause();
47 }
48 break;
49
51 previous();
52 if ( mCurrentFrameNumber <= 0 )
53 {
54 if ( mLoopAnimation )
55 mCurrentFrameNumber = totalFrameCount(); // we don't jump immediately to real last frame..., instead we delay that till the next timeout
56 else
57 pause();
58 }
59 break;
60
62 // should not happen - in an idle state the timeout won't occur
63 break;
64 }
65}
66
68{
69 return mTotalMovieFrames;
70}
71
73{
74 if ( frames == mTotalMovieFrames )
75 return;
76
77 mTotalMovieFrames = frames;
78
79 emit totalMovieFramesChanged( mTotalMovieFrames );
80 emit temporalExtentsChanged( mTemporalExtents );
81}
82
84{
85 return mLoopAnimation;
86}
87
89{
90 mLoopAnimation = loopAnimation;
91}
92
94{
95 auto scope = std::make_unique< QgsExpressionContextScope >( QStringLiteral( "temporal" ) );
96 scope->setVariable( QStringLiteral( "frame_rate" ), mFramesPerSecond, true );
97 scope->setVariable( QStringLiteral( "frame_number" ), mCurrentFrameNumber, true );
98 scope->setVariable( QStringLiteral( "frame_duration" ), mFrameDuration, true );
99 scope->setVariable( QStringLiteral( "frame_timestep" ), mFrameDuration.originalDuration(), true );
100 scope->setVariable( QStringLiteral( "frame_timestep_unit" ), static_cast< int >( mFrameDuration.originalUnit() ), true );
101 scope->setVariable( QStringLiteral( "frame_timestep_units" ), QgsUnitTypes::toString( mFrameDuration.originalUnit() ), true );
102 scope->setVariable( QStringLiteral( "animation_start_time" ), mTemporalExtents.begin(), true );
103 scope->setVariable( QStringLiteral( "animation_end_time" ), mTemporalExtents.end(), true );
104 scope->setVariable( QStringLiteral( "animation_interval" ), QgsInterval( mTemporalExtents.end() - mTemporalExtents.begin() ), true );
105 scope->setVariable( QStringLiteral( "total_frame_count" ), totalFrameCount() );
106
107 scope->addHiddenVariable( QStringLiteral( "frame_timestep_unit" ) );
108
109 return scope.release();
110}
111
113{
114 const QDateTime start = mTemporalExtents.begin();
115
116 if ( frame < 0 )
117 frame = 0;
118
119 const long long nextFrame = frame + 1;
120
121 QDateTime begin;
122 QDateTime end;
123 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
124 {
125 if ( mAllRanges.empty() )
126 return QgsDateTimeRange();
127
128 return frame < mAllRanges.size() ? mAllRanges.at( frame ) : mAllRanges.constLast();
129 }
130 else
131 {
132 begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
133 end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
134 }
135
136 QDateTime frameStart = begin;
137
138 if ( mCumulativeTemporalRange )
139 frameStart = start;
140
141 return QgsDateTimeRange( frameStart, end, true, false );
142}
143
145{
146 if ( mNavigationMode == mode )
147 return;
148
149 mNavigationMode = mode;
150 emit navigationModeChanged( mode );
151
152 if ( !mBlockUpdateTemporalRangeSignal )
153 {
154 switch ( mNavigationMode )
155 {
157 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
158 break;
160 emit updateTemporalRange( mTemporalExtents );
161 break;
165 break;
166 }
167 }
168}
169
171{
172 if ( mTemporalExtents == temporalExtents )
173 {
174 return;
175 }
177 mTemporalExtents = temporalExtents;
178 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
179 emit temporalExtentsChanged( mTemporalExtents );
180
181 switch ( mNavigationMode )
182 {
184 {
185 const long long currentFrameNumber = mCurrentFrameNumber;
186
187 // Force to emit signal if the current frame number doesn't change
188 if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
189 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
190 break;
191 }
193 if ( !mBlockUpdateTemporalRangeSignal )
194 emit updateTemporalRange( mTemporalExtents );
195 break;
198 break;
199 }
200
201}
202
204{
205 return mTemporalExtents;
206}
207
208void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
209{
210 mAllRanges = ranges;
211}
212
214{
215 return mAllRanges;
216}
217
219{
220 if ( mCurrentFrameNumber != frameNumber )
221 {
222 mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
223 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
224
225 if ( !mBlockUpdateTemporalRangeSignal )
226 emit updateTemporalRange( range );
227 }
228}
229
231{
232 return mCurrentFrameNumber;
233}
234
236{
237 if ( mFrameDuration == frameDuration )
238 {
239 return;
240 }
241
243 mFrameDuration = frameDuration;
244
245 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
246 emit temporalFrameDurationChanged( mFrameDuration );
247
248 // forcing an update of our views
249 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
250
251 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
252 emit updateTemporalRange( range );
253}
254
256{
257 return mFrameDuration;
258}
259
261{
262 if ( framesPerSeconds > 0 )
263 {
264 mFramesPerSecond = framesPerSeconds;
265 mNewFrameTimer->setInterval( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
266 }
267}
268
270{
271 return mFramesPerSecond;
272}
273
275{
276 if ( mCumulativeTemporalRange == state )
277 return;
278
279 mCumulativeTemporalRange = state;
280
281 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
282 {
283 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
284 }
285}
286
288{
289 return mCumulativeTemporalRange;
290}
291
293{
294 mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
295}
296
298{
299 mNewFrameTimer->stop();
301}
302
304{
305 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
306 {
307 // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
309 }
310
312 play();
313}
314
316{
317 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber <= 0 )
318 {
319 // 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
320 skipToEnd();
321 }
322
324 play();
325}
326
328{
329 setCurrentFrameNumber( mCurrentFrameNumber + 1 );
330}
331
333{
334 setCurrentFrameNumber( mCurrentFrameNumber - 1 );
335}
336
341
343{
344 const long long frame = totalFrameCount() - 1;
345 setCurrentFrameNumber( frame );
346}
347
349{
350 if ( mNavigationMode == Qgis::TemporalNavigationMode::Movie )
351 return mTotalMovieFrames;
352
353 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
354 {
355 return mAllRanges.count();
356 }
357 else
358 {
359 const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
360 return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
361 }
362}
363
365{
366 if ( mode != mPlayBackMode )
367 {
368 mPlayBackMode = mode;
369 emit stateChanged( mPlayBackMode );
370 }
371}
372
374{
375 return mPlayBackMode;
376}
377
378long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
379{
380 long long bestFrame = 0;
381 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
382 {
383 for ( const QgsDateTimeRange &range : mAllRanges )
384 {
385 if ( range.contains( frameStart ) )
386 return bestFrame;
387 else if ( range.begin() > frameStart )
388 // if we've gone past the target date, go back one frame if possible
389 return std::max( 0LL, bestFrame - 1 );
390 bestFrame++;
391 }
392 return mAllRanges.count() - 1;
393 }
394 else
395 {
396 const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
397 // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
398 long long roughFrameStart = 0;
399 long long roughFrameEnd = totalFrameCount();
400 // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
401 // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
402 if ( mFrameDuration.originalUnit() != Qgis::TemporalUnit::Months && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Years && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Decades && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Centuries )
403 {
404 // Only if we receive a valid frameStart, that is within current mTemporalExtents
405 // We tend to receive a framestart of 'now()' upon startup for example
406 if ( mTemporalExtents.contains( frameStart ) )
407 {
408 roughFrameStart = static_cast< long long >( std::floor( QgsInterval( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
409 }
410 roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
411 }
412 for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
413 {
415 if ( range.overlaps( testFrame ) )
416 {
417 bestFrame = i;
418 break;
419 }
420 }
421 return bestFrame;
422 }
423}
TemporalNavigationMode
Temporal navigation modes.
Definition qgis.h:2534
@ Animated
Temporal navigation relies on frames within a datetime range.
Definition qgis.h:2536
@ Movie
Movie mode – behaves like a video player, with a fixed frame duration and no temporal range.
Definition qgis.h:2538
@ FixedRange
Temporal navigation relies on a fixed datetime range.
Definition qgis.h:2537
@ Disabled
Temporal navigation is disabled.
Definition qgis.h:2535
@ IrregularStep
Special 'irregular step' time unit, used for temporal data which uses irregular, non-real-world unit ...
Definition qgis.h:5170
@ Centuries
Centuries.
Definition qgis.h:5169
@ Years
Years.
Definition qgis.h:5167
@ Decades
Decades.
Definition qgis.h:5168
@ Months
Months.
Definition qgis.h:5166
AnimationState
Animation states.
Definition qgis.h:2550
@ Forward
Animation is playing forward.
Definition qgis.h:2551
@ Reverse
Animation is playing in reverse.
Definition qgis.h:2552
@ Idle
Animation is paused.
Definition qgis.h:2553
Single scope for storing variables and functions for use within a QgsExpressionContext.
A representation of the interval between two datetime values.
Definition qgsinterval.h:47
double seconds() const
Returns the interval duration in seconds.
QgsTemporalController(QObject *parent=nullptr)
Constructor for QgsTemporalController, with the specified parent object.
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:446
bool overlaps(const QgsTemporalRange< T > &other) const
Returns true if this range overlaps another range.
Definition qgsrange.h:572
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:761