QGIS API Documentation 4.3.0-Master (0ff9723465c)
Loading...
Searching...
No Matches
qgstemporalutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstemporalutils.cpp
3 -----------------------
4 Date : March 2020
5 Copyright : (C) 2020 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgstemporalutils.h"
17
19#include "qgsmapdecoration.h"
22#include "qgsmapsettings.h"
23#include "qgsproject.h"
25
26#include <QRegularExpression>
27#include <QString>
28
29using namespace Qt::StringLiterals;
30
32{
33 const QMap<QString, QgsMapLayer *> mapLayers = project->mapLayers();
34 QDateTime minDate;
35 QDateTime maxDate;
36
37 for ( auto it = mapLayers.constBegin(); it != mapLayers.constEnd(); ++it )
38 {
39 QgsMapLayer *currentLayer = it.value();
40
41 if ( !currentLayer->temporalProperties() || !currentLayer->temporalProperties()->isActive() )
42 continue;
43 const QgsDateTimeRange layerRange = currentLayer->temporalProperties()->calculateTemporalExtent( currentLayer );
44
45 if ( layerRange.begin().isValid() && ( !minDate.isValid() || layerRange.begin() < minDate ) )
46 minDate = layerRange.begin();
47 if ( layerRange.end().isValid() && ( !maxDate.isValid() || layerRange.end() > maxDate ) )
48 maxDate = layerRange.end();
49 }
50
51 return QgsDateTimeRange( minDate, maxDate );
52}
53
55{
56 const QMap<QString, QgsMapLayer *> mapLayers = project->mapLayers();
57
58 QList< QgsDateTimeRange > ranges;
59 for ( auto it = mapLayers.constBegin(); it != mapLayers.constEnd(); ++it )
60 {
61 QgsMapLayer *currentLayer = it.value();
62
63 if ( !currentLayer->temporalProperties() || !currentLayer->temporalProperties()->isActive() )
64 continue;
65
66 ranges.append( currentLayer->temporalProperties()->allTemporalRanges( currentLayer ) );
67 }
68
69 return QgsDateTimeRange::mergeRanges( ranges );
70}
71
72bool QgsTemporalUtils::exportAnimation( const QgsMapSettings &mapSettings, const QgsTemporalUtils::AnimationExportSettings &settings, QString &error, QgsFeedback *feedback )
73{
74 if ( settings.fileNameTemplate.isEmpty() )
75 {
76 error = QObject::tr( "Filename template is empty" );
77 return false;
78 }
79 const int numberOfDigits = settings.fileNameTemplate.count( '#'_L1 );
80 if ( numberOfDigits < 0 )
81 {
82 error = QObject::tr( "Wrong filename template format (must contain #)" );
83 return false;
84 }
85 const QString token( numberOfDigits, '#'_L1 );
86 if ( !settings.fileNameTemplate.contains( token ) )
87 {
88 error = QObject::tr( "Filename template must contain all # placeholders in one continuous group." );
89 return false;
90 }
91 if ( !QDir().mkpath( settings.outputDirectory ) )
92 {
93 error = QObject::tr( "Output directory creation failure." );
94 return false;
95 }
96
98 navigator.setTemporalExtents( settings.animationRange );
99 navigator.setFrameDuration( settings.frameDuration );
100 navigator.setNavigationMode( settings.animationMode );
101 navigator.setTotalMovieFrames( settings.totalMovieFrames );
103 {
105 }
107
108 QgsMapSettings ms = mapSettings;
109 const QgsExpressionContext context = ms.expressionContext();
110 ms.setFrameRate( settings.frameRate );
111
112 const long long totalFrames = navigator.totalFrameCount();
113 long long currentFrame = 0;
114
115 while ( currentFrame < totalFrames )
116 {
117 if ( feedback )
118 {
119 if ( feedback->isCanceled() )
120 {
121 error = QObject::tr( "Export canceled" );
122 return false;
123 }
124 feedback->setProgress( currentFrame / static_cast<double>( totalFrames ) * 100 );
125 }
126
127 navigator.setCurrentFrameNumber( currentFrame );
128
129 ms.setIsTemporal( true );
130 ms.setTemporalRange( navigator.dateTimeRangeForFrameNumber( currentFrame ) );
131 ms.setCurrentFrame( currentFrame );
132
133 QgsExpressionContext frameContext = context;
134 frameContext.appendScope( navigator.createExpressionContextScope() );
136 ms.setExpressionContext( frameContext );
137
138 QString fileName( settings.fileNameTemplate );
139 const QString frameNoPaddedLeft( u"%1"_s.arg( currentFrame, numberOfDigits, 10, QChar( '0' ) ) ); // e.g. 0001
140 fileName.replace( token, frameNoPaddedLeft );
141 const QString path = QDir( settings.outputDirectory ).filePath( fileName );
142
143 QImage img = QImage( ms.outputSize() * ms.devicePixelRatio(), ms.outputImageFormat() );
144 img.setDevicePixelRatio( ms.devicePixelRatio() );
145 img.setDotsPerMeterX( 1000 * ms.outputDpi() / 25.4 );
146 img.setDotsPerMeterY( 1000 * ms.outputDpi() / 25.4 );
147 img.fill( ms.backgroundColor().rgb() );
148
149 QPainter p( &img );
150 QgsMapRendererCustomPainterJob job( ms, &p );
151 job.start();
152 job.waitForFinished();
153
155 context.setPainter( &p );
156
157 const auto constMDecorations = settings.decorations;
158 for ( QgsMapDecoration *decoration : constMDecorations )
159 {
160 decoration->render( ms, context );
161 }
162
163 p.end();
164
165 img.save( path );
166
167 ++currentFrame;
168 }
169
170 return true;
171}
172
173
174QDateTime QgsTemporalUtils::calculateFrameTime( const QDateTime &start, const long long frame, const QgsInterval &interval )
175{
176 double unused;
177 const bool isFractional = !qgsDoubleNear( fabs( modf( interval.originalDuration(), &unused ) ), 0.0 );
178
179 if ( isFractional || interval.originalUnit() == Qgis::TemporalUnit::Unknown )
180 {
181 const double duration = interval.seconds();
182 return start.addMSecs( frame * duration * 1000 );
183 }
184 else
185 {
186 switch ( interval.originalUnit() )
187 {
189 return start.addMSecs( frame * interval.originalDuration() );
191 return start.addSecs( frame * interval.originalDuration() );
193 return start.addSecs( 60 * frame * interval.originalDuration() );
195 return start.addSecs( 3600 * frame * interval.originalDuration() );
197 return start.addDays( frame * interval.originalDuration() );
199 return start.addDays( 7 * frame * interval.originalDuration() );
201 return start.addMonths( frame * interval.originalDuration() );
203 return start.addYears( frame * interval.originalDuration() );
205 return start.addYears( 10 * frame * interval.originalDuration() );
207 return start.addYears( 100 * frame * interval.originalDuration() );
209 // handled above
210 return QDateTime();
212 // not supported by this method
213 return QDateTime();
214 }
215 }
216 return QDateTime();
217}
218
219QList<QDateTime> QgsTemporalUtils::calculateDateTimesUsingDuration( const QDateTime &start, const QDateTime &end, const QString &duration, bool &ok, bool &maxValuesExceeded, int maxValues )
220{
221 ok = false;
222 const QgsTimeDuration timeDuration( QgsTimeDuration::fromString( duration, ok ) );
223 if ( !ok )
224 return {};
225
226 if ( timeDuration.years == 0 && timeDuration.months == 0 && timeDuration.weeks == 0 && timeDuration.days == 0 && timeDuration.hours == 0 && timeDuration.minutes == 0 && timeDuration.seconds == 0 )
227 {
228 ok = false;
229 return {};
230 }
231 return calculateDateTimesUsingDuration( start, end, timeDuration, maxValuesExceeded, maxValues );
232}
233
234QList<QDateTime> QgsTemporalUtils::calculateDateTimesUsingDuration( const QDateTime &start, const QDateTime &end, const QgsTimeDuration &timeDuration, bool &maxValuesExceeded, int maxValues )
235{
236 QList<QDateTime> res;
237 QDateTime current = start;
238 maxValuesExceeded = false;
239 while ( current <= end )
240 {
241 res << current;
242
243 if ( maxValues >= 0 && res.size() > maxValues )
244 {
245 maxValuesExceeded = true;
246 break;
247 }
248
249 if ( timeDuration.years )
250 current = current.addYears( timeDuration.years );
251 if ( timeDuration.months )
252 current = current.addMonths( timeDuration.months );
253 if ( timeDuration.weeks || timeDuration.days )
254 current = current.addDays( timeDuration.weeks * 7 + timeDuration.days );
255 if ( timeDuration.hours || timeDuration.minutes || timeDuration.seconds )
256 current = current.addSecs( timeDuration.hours * 60LL * 60 + timeDuration.minutes * 60 + timeDuration.seconds );
257 }
258 return res;
259}
260
261QList<QDateTime> QgsTemporalUtils::calculateDateTimesFromISO8601( const QString &string, bool &ok, bool &maxValuesExceeded, int maxValues )
262{
263 ok = false;
264 maxValuesExceeded = false;
265 const QStringList parts = string.split( '/' );
266 if ( parts.length() != 3 )
267 {
268 return {};
269 }
270
271 const QDateTime start = QDateTime::fromString( parts.at( 0 ), Qt::ISODate );
272 if ( !start.isValid() )
273 return {};
274 const QDateTime end = QDateTime::fromString( parts.at( 1 ), Qt::ISODate );
275 if ( !end.isValid() )
276 return {};
277
278 return calculateDateTimesUsingDuration( start, end, parts.at( 2 ), ok, maxValuesExceeded, maxValues );
279}
280
281//
282// QgsTimeDuration
283//
284
289
291{
292 QString text( "P" );
293
294 if ( years )
295 {
296 text.append( QString::number( years ) );
297 text.append( 'Y' );
298 }
299 if ( months )
300 {
301 text.append( QString::number( months ) );
302 text.append( 'M' );
303 }
304 if ( days )
305 {
306 text.append( QString::number( days ) );
307 text.append( 'D' );
308 }
309
310 if ( hours )
311 {
312 if ( !text.contains( 'T' ) )
313 text.append( 'T' );
314 text.append( QString::number( hours ) );
315 text.append( 'H' );
316 }
317 if ( minutes )
318 {
319 if ( !text.contains( 'T' ) )
320 text.append( 'T' );
321 text.append( QString::number( minutes ) );
322 text.append( 'M' );
323 }
324 if ( seconds )
325 {
326 if ( !text.contains( 'T' ) )
327 text.append( 'T' );
328 text.append( QString::number( seconds ) );
329 text.append( 'S' );
330 }
331 return text;
332}
333
335{
336 long long secs = 0.0;
337
338 if ( years )
339 secs += years * static_cast< long long >( QgsInterval::YEARS );
340 if ( months )
341 secs += months * static_cast< long long >( QgsInterval::MONTHS );
342 if ( days )
343 secs += days * static_cast< long long >( QgsInterval::DAY );
344 if ( hours )
345 secs += hours * static_cast< long long >( QgsInterval::HOUR );
346 if ( minutes )
347 secs += minutes * static_cast< long long >( QgsInterval::MINUTE );
348 if ( seconds )
349 secs += seconds;
350
351 return secs;
352}
353
354QDateTime QgsTimeDuration::addToDateTime( const QDateTime &dateTime ) const
355{
356 QDateTime resultDateTime = dateTime;
357
358 if ( years )
359 resultDateTime = resultDateTime.addYears( years );
360 if ( months )
361 resultDateTime = resultDateTime.addMonths( months );
362 if ( weeks || days )
363 resultDateTime = resultDateTime.addDays( weeks * 7 + days );
364 if ( hours || minutes || seconds )
365 resultDateTime = resultDateTime.addSecs( hours * 60LL * 60 + minutes * 60 + seconds );
366
367 return resultDateTime;
368}
369
370QgsTimeDuration QgsTimeDuration::fromString( const QString &string, bool &ok )
371{
372 ok = false;
373 thread_local const QRegularExpression sRx( QStringLiteral( R"(P(?:([\d]+)Y)?(?:([\d]+)M)?(?:([\d]+)W)?(?:([\d]+)D)?(?:T(?:([\d]+)H)?(?:([\d]+)M)?(?:([\d\.]+)S)?)?$)" ) );
374
375 const QRegularExpressionMatch match = sRx.match( string );
376 QgsTimeDuration duration;
377 if ( match.hasMatch() )
378 {
379 ok = true;
380 duration.years = match.captured( 1 ).toInt();
381 duration.months = match.captured( 2 ).toInt();
382 duration.weeks = match.captured( 3 ).toInt();
383 duration.days = match.captured( 4 ).toInt();
384 duration.hours = match.captured( 5 ).toInt();
385 duration.minutes = match.captured( 6 ).toInt();
386 duration.seconds = match.captured( 7 ).toDouble();
387 }
388 return duration;
389}
@ IrregularStep
Special 'irregular step' time unit, used for temporal data which uses irregular, non-real-world unit ...
Definition qgis.h:5725
@ Milliseconds
Milliseconds.
Definition qgis.h:5715
@ Hours
Hours.
Definition qgis.h:5718
@ Unknown
Unknown time unit.
Definition qgis.h:5726
@ Centuries
Centuries.
Definition qgis.h:5724
@ Seconds
Seconds.
Definition qgis.h:5716
@ Weeks
Weeks.
Definition qgis.h:5720
@ Years
Years.
Definition qgis.h:5722
@ Decades
Decades.
Definition qgis.h:5723
@ Months
Months.
Definition qgis.h:5721
@ Minutes
Minutes.
Definition qgis.h:5717
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
A representation of the interval between two datetime values.
Definition qgsinterval.h:52
static const int MINUTE
Seconds per minute.
Definition qgsinterval.h:67
double originalDuration() const
Returns the original interval duration.
static const int MONTHS
Seconds per month, based on 30 day month.
Definition qgsinterval.h:59
double seconds() const
Returns the interval duration in seconds.
Qgis::TemporalUnit originalUnit() const
Returns the original interval temporal unit.
static const int HOUR
Seconds per hour.
Definition qgsinterval.h:65
static const int DAY
Seconds per day.
Definition qgsinterval.h:63
static const int YEARS
Seconds per year (average).
Definition qgsinterval.h:57
Interface for map decorations.
virtual QgsDateTimeRange calculateTemporalExtent(QgsMapLayer *layer) const
Attempts to calculate the overall temporal extent for the specified layer, using the settings defined...
virtual QList< QgsDateTimeRange > allTemporalRanges(QgsMapLayer *layer) const
Attempts to calculate the overall list of all temporal extents which are contained in the specified l...
Base class for all map layer types.
Definition qgsmaplayer.h:83
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Job implementation that renders everything sequentially using a custom painter.
void waitForFinished() override
Block until the job has finished.
void start()
Start the rendering job and immediately return.
Contains configuration for rendering maps.
void setFrameRate(double rate)
Sets the frame rate of the map (in frames per second), for maps which are part of an animation.
QColor backgroundColor() const
Returns the background color of the map.
float devicePixelRatio() const
Returns the device pixel ratio.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QImage::Format outputImageFormat() const
format of internal QImage, default QImage::Format_ARGB32_Premultiplied
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setCurrentFrame(long long frame)
Sets the current frame of the map, for maps which are part of an animation.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
const QgsExpressionContext & expressionContext() const
Gets the expression context.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:114
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Contains information about the context of a rendering operation.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Implements a temporal controller based on a frame by frame navigation and animation.
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.
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 setCurrentFrameNumber(long long frame)
Sets the current animation frame number.
long long totalFrameCount() const
Returns the total number of frames for the navigation.
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.
bool isActive() const
Returns true if the temporal property is active.
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
T begin() const
Returns the beginning of the range.
Definition qgsrange.h:408
T end() const
Returns the upper bound of the range.
Definition qgsrange.h:415
static QList< QgsTemporalRange< QDateTime > > mergeRanges(const QList< QgsTemporalRange< QDateTime > > &ranges)
Definition qgsrange.h:636
static QList< QDateTime > calculateDateTimesUsingDuration(const QDateTime &start, const QDateTime &end, const QString &duration, bool &ok, bool &maxValuesExceeded, int maxValues=-1)
Calculates a complete list of datetimes between start and end, using the specified ISO8601 duration s...
static QgsDateTimeRange calculateTemporalRangeForProject(QgsProject *project)
Calculates the temporal range for a project.
static bool exportAnimation(const QgsMapSettings &mapSettings, const AnimationExportSettings &settings, QString &error, QgsFeedback *feedback=nullptr)
Exports animation frames by rendering the map to multiple destination images.
static QList< QgsDateTimeRange > usedTemporalRangesForProject(QgsProject *project)
Calculates all temporal ranges which are in use for a project.
static QDateTime calculateFrameTime(const QDateTime &start, const long long frame, const QgsInterval &interval)
Calculates the frame time for an animation.
static QList< QDateTime > calculateDateTimesFromISO8601(const QString &string, bool &ok, bool &maxValuesExceeded, int maxValues=-1)
Calculates a complete list of datetimes from a ISO8601 string containing a duration (eg "2021-03-23T0...
Contains utility methods for working with temporal layers and projects.
QDateTime addToDateTime(const QDateTime &dateTime) const
Adds this duration to a starting dateTime value.
long long toSeconds() const
Returns the total duration in seconds.
QString toString() const
Converts the duration to an ISO8601 duration string.
static QgsTimeDuration fromString(const QString &string, bool &ok)
Creates a QgsTimeDuration from a string value.
int minutes
Minutes.
double seconds
Seconds.
QgsInterval toInterval() const
Converts the duration to an interval value.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7693
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition qgsrange.h:705
Contains settings relating to exporting animations.
double frameRate
Target animation frame rate in frames per second.
bool temporalRangeCumulative
The animation temporal range cumulative settings.
QgsDateTimeRange animationRange
Dictates the overall temporal range of the animation.
QList< QgsDateTimeRange > availableTemporalRanges
Contains the list of all available temporal ranges which have data available.
QgsInterval frameDuration
Duration of individual export frames.
QString fileNameTemplate
The filename template for exporting the frames.
Qgis::TemporalNavigationMode animationMode
Animation mode.
QString outputDirectory
Destination directory for created image files.
long long totalMovieFrames
Total frame count, for Qgis::TemporalNavigationMode::Movie mode only.
QList< QgsMapDecoration * > decorations
List of decorations to draw onto exported frames.