QGIS API Documentation 3.99.0-Master (d270888f95f)
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 <QString>
25
26#include "moc_qgstemporalnavigationobject.cpp"
27
28using namespace Qt::StringLiterals;
29
31 : QgsTemporalController( parent )
32{
33 mNewFrameTimer = new QTimer( this );
34
35 connect( mNewFrameTimer, &QTimer::timeout,
36 this, &QgsTemporalNavigationObject::timerTimeout );
37}
38
39void QgsTemporalNavigationObject::timerTimeout()
40{
41 switch ( mPlayBackMode )
42 {
44 next();
45 if ( mCurrentFrameNumber >= totalFrameCount() - 1 )
46 {
47 if ( mLoopAnimation )
48 mCurrentFrameNumber = -1; // we don't jump immediately to frame 0, instead we delay that till the next timeout
49 else
50 pause();
51 }
52 break;
53
55 previous();
56 if ( mCurrentFrameNumber <= 0 )
57 {
58 if ( mLoopAnimation )
59 mCurrentFrameNumber = totalFrameCount(); // we don't jump immediately to real last frame..., instead we delay that till the next timeout
60 else
61 pause();
62 }
63 break;
64
66 // should not happen - in an idle state the timeout won't occur
67 break;
68 }
69}
70
72{
73 return mTotalMovieFrames;
74}
75
77{
78 if ( frames == mTotalMovieFrames )
79 return;
80
81 mTotalMovieFrames = frames;
82
83 emit totalMovieFramesChanged( mTotalMovieFrames );
84 emit temporalExtentsChanged( mTemporalExtents );
85}
86
88{
89 return mLoopAnimation;
90}
91
93{
94 mLoopAnimation = loopAnimation;
95}
96
98{
99 auto scope = std::make_unique< QgsExpressionContextScope >( u"temporal"_s );
100 scope->setVariable( u"frame_rate"_s, mFramesPerSecond, true );
101 scope->setVariable( u"frame_number"_s, mCurrentFrameNumber, true );
102 scope->setVariable( u"frame_duration"_s, mFrameDuration, true );
103 scope->setVariable( u"frame_timestep"_s, mFrameDuration.originalDuration(), true );
104 scope->setVariable( u"frame_timestep_unit"_s, static_cast< int >( mFrameDuration.originalUnit() ), true );
105 scope->setVariable( u"frame_timestep_units"_s, QgsUnitTypes::toString( mFrameDuration.originalUnit() ), true );
106 scope->setVariable( u"animation_start_time"_s, mTemporalExtents.begin(), true );
107 scope->setVariable( u"animation_end_time"_s, mTemporalExtents.end(), true );
108 scope->setVariable( u"animation_interval"_s, QgsInterval( mTemporalExtents.end() - mTemporalExtents.begin() ), true );
109 scope->setVariable( u"total_frame_count"_s, totalFrameCount() );
110
111 scope->addHiddenVariable( u"frame_timestep_unit"_s );
112
113 return scope.release();
114}
115
117{
118 const QDateTime start = mTemporalExtents.begin();
119
120 if ( frame < 0 )
121 frame = 0;
122
123 const long long nextFrame = frame + 1;
124
125 QDateTime begin;
126 QDateTime end;
127 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
128 {
129 if ( mAllRanges.empty() )
130 return QgsDateTimeRange();
131
132 return frame < mAllRanges.size() ? mAllRanges.at( frame ) : mAllRanges.constLast();
133 }
134 else
135 {
136 begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
137 end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
138 }
139
140 QDateTime frameStart = begin;
141
142 if ( mCumulativeTemporalRange )
143 frameStart = start;
144
145 return QgsDateTimeRange( frameStart, end, true, false );
146}
147
149{
150 if ( mNavigationMode == mode )
151 return;
152
153 mNavigationMode = mode;
154 emit navigationModeChanged( mode );
155
156 if ( !mBlockUpdateTemporalRangeSignal )
157 {
158 switch ( mNavigationMode )
159 {
161 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
162 break;
164 emit updateTemporalRange( mTemporalExtents );
165 break;
169 break;
170 }
171 }
172}
173
175{
176 if ( mTemporalExtents == temporalExtents )
177 {
178 return;
179 }
181 mTemporalExtents = temporalExtents;
182 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
183 emit temporalExtentsChanged( mTemporalExtents );
184
185 switch ( mNavigationMode )
186 {
188 {
189 const long long currentFrameNumber = mCurrentFrameNumber;
190
191 // Force to emit signal if the current frame number doesn't change
192 if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
193 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
194 break;
195 }
197 if ( !mBlockUpdateTemporalRangeSignal )
198 emit updateTemporalRange( mTemporalExtents );
199 break;
202 break;
203 }
204
205}
206
208{
209 return mTemporalExtents;
210}
211
212void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
213{
214 mAllRanges = ranges;
215}
216
218{
219 return mAllRanges;
220}
221
223{
224 if ( mCurrentFrameNumber != frameNumber )
225 {
226 mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
227 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
228
229 if ( !mBlockUpdateTemporalRangeSignal )
230 emit updateTemporalRange( range );
231 }
232}
233
235{
236 return mCurrentFrameNumber;
237}
238
240{
241 if ( mFrameDuration == frameDuration )
242 {
243 return;
244 }
245
247 mFrameDuration = frameDuration;
248
249 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
250 emit temporalFrameDurationChanged( mFrameDuration );
251
252 // forcing an update of our views
253 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
254
255 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
256 emit updateTemporalRange( range );
257}
258
260{
261 return mFrameDuration;
262}
263
265{
266 if ( framesPerSeconds > 0 )
267 {
268 mFramesPerSecond = framesPerSeconds;
269 mNewFrameTimer->setInterval( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
270 }
271}
272
274{
275 return mFramesPerSecond;
276}
277
279{
280 if ( mCumulativeTemporalRange == state )
281 return;
282
283 mCumulativeTemporalRange = state;
284
285 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
286 {
287 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
288 }
289}
290
292{
293 return mCumulativeTemporalRange;
294}
295
297{
298 mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
299}
300
302{
303 mNewFrameTimer->stop();
305}
306
308{
309 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
310 {
311 // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
313 }
314
316 play();
317}
318
320{
321 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber <= 0 )
322 {
323 // 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
324 skipToEnd();
325 }
326
328 play();
329}
330
332{
333 setCurrentFrameNumber( mCurrentFrameNumber + 1 );
334}
335
337{
338 setCurrentFrameNumber( mCurrentFrameNumber - 1 );
339}
340
345
347{
348 const long long frame = totalFrameCount() - 1;
349 setCurrentFrameNumber( frame );
350}
351
353{
354 if ( mNavigationMode == Qgis::TemporalNavigationMode::Movie )
355 return mTotalMovieFrames;
356
357 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
358 {
359 return mAllRanges.count();
360 }
361 else
362 {
363 const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
364 return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
365 }
366}
367
369{
370 if ( mode != mPlayBackMode )
371 {
372 mPlayBackMode = mode;
373 emit stateChanged( mPlayBackMode );
374 }
375}
376
378{
379 return mPlayBackMode;
380}
381
382long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
383{
384 long long bestFrame = 0;
385 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
386 {
387 for ( const QgsDateTimeRange &range : mAllRanges )
388 {
389 if ( range.contains( frameStart ) )
390 return bestFrame;
391 else if ( range.begin() > frameStart )
392 // if we've gone past the target date, go back one frame if possible
393 return std::max( 0LL, bestFrame - 1 );
394 bestFrame++;
395 }
396 return mAllRanges.count() - 1;
397 }
398 else
399 {
400 const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
401 // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
402 long long roughFrameStart = 0;
403 long long roughFrameEnd = totalFrameCount();
404 // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
405 // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
406 if ( mFrameDuration.originalUnit() != Qgis::TemporalUnit::Months && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Years && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Decades && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Centuries )
407 {
408 // Only if we receive a valid frameStart, that is within current mTemporalExtents
409 // We tend to receive a framestart of 'now()' upon startup for example
410 if ( mTemporalExtents.contains( frameStart ) )
411 {
412 roughFrameStart = static_cast< long long >( std::floor( QgsInterval( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
413 }
414 roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
415 }
416 for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
417 {
419 if ( range.overlaps( testFrame ) )
420 {
421 bestFrame = i;
422 break;
423 }
424 }
425 return bestFrame;
426 }
427}
TemporalNavigationMode
Temporal navigation modes.
Definition qgis.h:2592
@ Animated
Temporal navigation relies on frames within a datetime range.
Definition qgis.h:2594
@ Movie
Movie mode – behaves like a video player, with a fixed frame duration and no temporal range.
Definition qgis.h:2596
@ FixedRange
Temporal navigation relies on a fixed datetime range.
Definition qgis.h:2595
@ Disabled
Temporal navigation is disabled.
Definition qgis.h:2593
@ IrregularStep
Special 'irregular step' time unit, used for temporal data which uses irregular, non-real-world unit ...
Definition qgis.h:5242
@ Centuries
Centuries.
Definition qgis.h:5241
@ Years
Years.
Definition qgis.h:5239
@ Decades
Decades.
Definition qgis.h:5240
@ Months
Months.
Definition qgis.h:5238
AnimationState
Animation states.
Definition qgis.h:2608
@ Forward
Animation is playing forward.
Definition qgis.h:2609
@ Reverse
Animation is playing in reverse.
Definition qgis.h:2610
@ Idle
Animation is paused.
Definition qgis.h:2611
Single scope for storing variables and functions for use within a QgsExpressionContext.
A representation of the interval between two datetime values.
Definition qgsinterval.h:50
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:449
bool overlaps(const QgsTemporalRange< T > &other) const
Returns true if this range overlaps another range.
Definition qgsrange.h:575
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:764