QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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, this, &QgsTemporalNavigationObject::timerTimeout );
36}
37
38void QgsTemporalNavigationObject::timerTimeout()
39{
40 switch ( mPlayBackMode )
41 {
43 next();
44 if ( mCurrentFrameNumber >= totalFrameCount() - 1 )
45 {
46 if ( mLoopAnimation )
47 mCurrentFrameNumber = -1; // we don't jump immediately to frame 0, instead we delay that till the next timeout
48 else
49 pause();
50 }
51 break;
52
54 previous();
55 if ( mCurrentFrameNumber <= 0 )
56 {
57 if ( mLoopAnimation )
58 mCurrentFrameNumber = totalFrameCount(); // we don't jump immediately to real last frame..., instead we delay that till the next timeout
59 else
60 pause();
61 }
62 break;
63
65 // should not happen - in an idle state the timeout won't occur
66 break;
67 }
68}
69
71{
72 return mTotalMovieFrames;
73}
74
76{
77 if ( frames == mTotalMovieFrames )
78 return;
79
80 mTotalMovieFrames = frames;
81
82 emit totalMovieFramesChanged( mTotalMovieFrames );
83 emit temporalExtentsChanged( mTemporalExtents );
84}
85
87{
88 return mLoopAnimation;
89}
90
92{
93 mLoopAnimation = loopAnimation;
94}
95
97{
98 auto scope = std::make_unique< QgsExpressionContextScope >( u"temporal"_s );
99 scope->setVariable( u"frame_rate"_s, mFramesPerSecond, true );
100 scope->setVariable( u"frame_number"_s, mCurrentFrameNumber, true );
101 scope->setVariable( u"frame_duration"_s, mFrameDuration, true );
102 scope->setVariable( u"frame_timestep"_s, mFrameDuration.originalDuration(), true );
103 scope->setVariable( u"frame_timestep_unit"_s, static_cast< int >( mFrameDuration.originalUnit() ), true );
104 scope->setVariable( u"frame_timestep_units"_s, QgsUnitTypes::toString( mFrameDuration.originalUnit() ), true );
105 scope->setVariable( u"animation_start_time"_s, mTemporalExtents.begin(), true );
106 scope->setVariable( u"animation_end_time"_s, mTemporalExtents.end(), true );
107 scope->setVariable( u"animation_interval"_s, QgsInterval( mTemporalExtents.end() - mTemporalExtents.begin() ), true );
108 scope->setVariable( u"total_frame_count"_s, totalFrameCount() );
109
110 scope->addHiddenVariable( u"frame_timestep_unit"_s );
111
112 return scope.release();
113}
114
116{
117 const QDateTime start = mTemporalExtents.begin();
118
119 if ( frame < 0 )
120 frame = 0;
121
122 const long long nextFrame = frame + 1;
123
124 QDateTime begin;
125 QDateTime end;
126 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
127 {
128 if ( mAllRanges.empty() )
129 return QgsDateTimeRange();
130
131 return frame < mAllRanges.size() ? mAllRanges.at( frame ) : mAllRanges.constLast();
132 }
133 else
134 {
135 begin = QgsTemporalUtils::calculateFrameTime( start, frame, mFrameDuration );
136 end = QgsTemporalUtils::calculateFrameTime( start, nextFrame, mFrameDuration );
137 }
138
139 QDateTime frameStart = begin;
140
141 if ( mCumulativeTemporalRange )
142 frameStart = start;
143
144 return QgsDateTimeRange( frameStart, end, true, false );
145}
146
148{
149 if ( mNavigationMode == mode )
150 return;
151
152 mNavigationMode = mode;
153 emit navigationModeChanged( mode );
154
155 if ( !mBlockUpdateTemporalRangeSignal )
156 {
157 switch ( mNavigationMode )
158 {
160 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
161 break;
163 emit updateTemporalRange( mTemporalExtents );
164 break;
168 break;
169 }
170 }
171}
172
174{
175 if ( mTemporalExtents == temporalExtents )
176 {
177 return;
178 }
180 mTemporalExtents = temporalExtents;
181 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
182 emit temporalExtentsChanged( mTemporalExtents );
183
184 switch ( mNavigationMode )
185 {
187 {
188 const long long currentFrameNumber = mCurrentFrameNumber;
189
190 // Force to emit signal if the current frame number doesn't change
191 if ( currentFrameNumber == mCurrentFrameNumber && !mBlockUpdateTemporalRangeSignal )
192 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
193 break;
194 }
196 if ( !mBlockUpdateTemporalRangeSignal )
197 emit updateTemporalRange( mTemporalExtents );
198 break;
201 break;
202 }
203}
204
206{
207 return mTemporalExtents;
208}
209
210void QgsTemporalNavigationObject::setAvailableTemporalRanges( const QList<QgsDateTimeRange> &ranges )
211{
212 mAllRanges = ranges;
213}
214
216{
217 return mAllRanges;
218}
219
221{
222 if ( mCurrentFrameNumber != frameNumber )
223 {
224 mCurrentFrameNumber = std::max( 0LL, std::min( frameNumber, totalFrameCount() - 1 ) );
225 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
226
227 if ( !mBlockUpdateTemporalRangeSignal )
228 emit updateTemporalRange( range );
229 }
230}
231
233{
234 return mCurrentFrameNumber;
235}
236
238{
239 if ( mFrameDuration == frameDuration )
240 {
241 return;
242 }
243
245 mFrameDuration = frameDuration;
246
247 mCurrentFrameNumber = findBestFrameNumberForFrameStart( oldFrame.begin() );
248 emit temporalFrameDurationChanged( mFrameDuration );
249
250 // forcing an update of our views
251 const QgsDateTimeRange range = dateTimeRangeForFrameNumber( mCurrentFrameNumber );
252
253 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
254 emit updateTemporalRange( range );
255}
256
258{
259 return mFrameDuration;
260}
261
263{
264 if ( framesPerSeconds > 0 )
265 {
266 mFramesPerSecond = framesPerSeconds;
267 mNewFrameTimer->setInterval( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
268 }
269}
270
272{
273 return mFramesPerSecond;
274}
275
277{
278 if ( mCumulativeTemporalRange == state )
279 return;
280
281 mCumulativeTemporalRange = state;
282
283 if ( !mBlockUpdateTemporalRangeSignal && mNavigationMode == Qgis::TemporalNavigationMode::Animated )
284 {
285 emit updateTemporalRange( dateTimeRangeForFrameNumber( mCurrentFrameNumber ) );
286 }
287}
288
290{
291 return mCumulativeTemporalRange;
292}
293
295{
296 mNewFrameTimer->start( static_cast< int >( ( 1.0 / mFramesPerSecond ) * 1000 ) );
297}
298
300{
301 mNewFrameTimer->stop();
303}
304
306{
307 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber >= totalFrameCount() - 1 )
308 {
309 // if we are paused at the end of the video, and the user hits play, we automatically rewind and play again
311 }
312
314 play();
315}
316
318{
319 if ( mPlayBackMode == Qgis::AnimationState::Idle && mCurrentFrameNumber <= 0 )
320 {
321 // 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
322 skipToEnd();
323 }
324
326 play();
327}
328
330{
331 setCurrentFrameNumber( mCurrentFrameNumber + 1 );
332}
333
335{
336 setCurrentFrameNumber( mCurrentFrameNumber - 1 );
337}
338
343
345{
346 const long long frame = totalFrameCount() - 1;
347 setCurrentFrameNumber( frame );
348}
349
351{
352 if ( mNavigationMode == Qgis::TemporalNavigationMode::Movie )
353 return mTotalMovieFrames;
354
355 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
356 {
357 return mAllRanges.count();
358 }
359 else
360 {
361 const QgsInterval totalAnimationLength = mTemporalExtents.end() - mTemporalExtents.begin();
362 return static_cast< long long >( std::ceil( totalAnimationLength.seconds() / mFrameDuration.seconds() ) );
363 }
364}
365
367{
368 if ( mode != mPlayBackMode )
369 {
370 mPlayBackMode = mode;
371 emit stateChanged( mPlayBackMode );
372 }
373}
374
376{
377 return mPlayBackMode;
378}
379
380long long QgsTemporalNavigationObject::findBestFrameNumberForFrameStart( const QDateTime &frameStart ) const
381{
382 long long bestFrame = 0;
383 if ( mFrameDuration.originalUnit() == Qgis::TemporalUnit::IrregularStep )
384 {
385 for ( const QgsDateTimeRange &range : mAllRanges )
386 {
387 if ( range.contains( frameStart ) )
388 return bestFrame;
389 else if ( range.begin() > frameStart )
390 // if we've gone past the target date, go back one frame if possible
391 return std::max( 0LL, bestFrame - 1 );
392 bestFrame++;
393 }
394 return mAllRanges.count() - 1;
395 }
396 else
397 {
398 const QgsDateTimeRange testFrame = QgsDateTimeRange( frameStart, frameStart ); // creating an 'instant' Range
399 // Earlier we looped from frame 0 till totalFrameCount() here, but this loop grew potentially gigantic
400 long long roughFrameStart = 0;
401 long long roughFrameEnd = totalFrameCount();
402 // For the smaller step frames we calculate an educated guess, to prevent the loop becoming too
403 // large, freezing the ui (eg having a mTemporalExtents of several months and the user selects milliseconds)
404 if ( mFrameDuration.originalUnit() != Qgis::TemporalUnit::Months
405 && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Years
406 && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Decades
407 && mFrameDuration.originalUnit() != Qgis::TemporalUnit::Centuries )
408 {
409 // Only if we receive a valid frameStart, that is within current mTemporalExtents
410 // We tend to receive a framestart of 'now()' upon startup for example
411 if ( mTemporalExtents.contains( frameStart ) )
412 {
413 roughFrameStart = static_cast< long long >( std::floor( QgsInterval( frameStart - mTemporalExtents.begin() ).seconds() / mFrameDuration.seconds() ) );
414 }
415 roughFrameEnd = roughFrameStart + 100; // just in case we miss the guess
416 }
417 for ( long long i = roughFrameStart; i < roughFrameEnd; ++i )
418 {
420 if ( range.overlaps( testFrame ) )
421 {
422 bestFrame = i;
423 break;
424 }
425 }
426 return bestFrame;
427 }
428}
TemporalNavigationMode
Temporal navigation modes.
Definition qgis.h:2623
@ Animated
Temporal navigation relies on frames within a datetime range.
Definition qgis.h:2625
@ Movie
Movie mode – behaves like a video player, with a fixed frame duration and no temporal range.
Definition qgis.h:2627
@ FixedRange
Temporal navigation relies on a fixed datetime range.
Definition qgis.h:2626
@ Disabled
Temporal navigation is disabled.
Definition qgis.h:2624
@ IrregularStep
Special 'irregular step' time unit, used for temporal data which uses irregular, non-real-world unit ...
Definition qgis.h:5327
@ Centuries
Centuries.
Definition qgis.h:5326
@ Years
Years.
Definition qgis.h:5324
@ Decades
Decades.
Definition qgis.h:5325
@ Months
Months.
Definition qgis.h:5323
AnimationState
Animation states.
Definition qgis.h:2639
@ Forward
Animation is playing forward.
Definition qgis.h:2640
@ Reverse
Animation is playing in reverse.
Definition qgis.h:2641
@ Idle
Animation is paused.
Definition qgis.h:2642
Single scope for storing variables and functions for use within a QgsExpressionContext.
A representation of the interval between two datetime values.
Definition qgsinterval.h:52
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:408
bool overlaps(const QgsTemporalRange< T > &other) const
Returns true if this range overlaps another range.
Definition qgsrange.h:525
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:705