QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgsprofilerenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprofilerenderer.h
3 ---------------
4 begin : March 2022
5 copyright : (C) 2022 by Nyall Dawson
6 email : nyall dot dawson at gmail 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#include "qgsprofilerenderer.h"
20#include "qgscurve.h"
21#include "qgsprofilesnapping.h"
22
23#include <QtConcurrentMap>
24#include <QtConcurrentRun>
25
26QgsProfilePlotRenderer::QgsProfilePlotRenderer( const QList< QgsAbstractProfileSource * > &sources,
27 const QgsProfileRequest &request )
28 : mRequest( request )
29{
30 for ( QgsAbstractProfileSource *source : sources )
31 {
32 if ( source )
33 {
34 if ( std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) } )
35 mGenerators.emplace_back( std::move( generator ) );
36 }
37 }
38}
39
40QgsProfilePlotRenderer::QgsProfilePlotRenderer( std::vector<std::unique_ptr<QgsAbstractProfileGenerator> > generators, const QgsProfileRequest &request )
41 : mGenerators( std::move( generators ) )
42 , mRequest( request )
43{
44}
45
47{
48 if ( isActive() )
49 {
51 }
52}
53
55{
56 QStringList res;
57 res.reserve( mGenerators.size() );
58 for ( const auto &it : mGenerators )
59 {
60 res.append( it->sourceId() );
61 }
62 return res;
63}
64
66{
67 if ( isActive() )
68 return;
69
70 mStatus = Generating;
71
72 Q_ASSERT( mJobs.empty() );
73
74 mJobs.reserve( mGenerators.size() );
75 for ( const auto &it : mGenerators )
76 {
77 std::unique_ptr< ProfileJob > job = std::make_unique< ProfileJob >();
78 job->generator = it.get();
79 job->context = mContext;
80 mJobs.emplace_back( std::move( job ) );
81 }
82
83 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
84
85 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
86 mFutureWatcher.setFuture( mFuture );
87}
88
90{
91 if ( isActive() )
92 return;
93
94 mStatus = Generating;
95
96 Q_ASSERT( mJobs.empty() );
97 mJobs.reserve( mGenerators.size() );
98
99 for ( const auto &it : mGenerators )
100 {
101 std::unique_ptr< ProfileJob > job = std::make_unique< ProfileJob >();
102 job->generator = it.get();
103 job->context = mContext;
104 it.get()->generateProfile( job->context );
105 job->results.reset( job->generator->takeResults() );
106 job->complete = true;
107 job->invalidatedResults.reset();
108 mJobs.emplace_back( std::move( job ) );
109 }
110
111 mStatus = Idle;
112}
113
115{
116 if ( !isActive() )
117 return;
118
119 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
120
121 for ( const auto &job : mJobs )
122 {
123 if ( job->generator )
124 {
125 if ( QgsFeedback *feedback = job->generator->feedback() )
126 {
127 feedback->cancel();
128 }
129 }
130 }
131
132 mFutureWatcher.waitForFinished();
133
134 onGeneratingFinished();
135}
136
138{
139 if ( !isActive() )
140 return;
141
142 for ( const auto &job : mJobs )
143 {
144 if ( job->generator )
145 {
146 if ( QgsFeedback *feedback = job->generator->feedback() )
147 {
148 feedback->cancel();
149 }
150 }
151 }
152}
153
155{
156 if ( !isActive() )
157 return;
158
159 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
160 mFutureWatcher.waitForFinished();
161
162 onGeneratingFinished();
163}
164
166{
167 return mStatus != Idle;
168}
169
171{
172 if ( mContext == context )
173 return;
174
175 const double maxErrorChanged = !qgsDoubleNear( context.maximumErrorMapUnits(), mContext.maximumErrorMapUnits() );
176 const double distanceRangeChanged = context.distanceRange() != mContext.distanceRange();
177 const double elevationRangeChanged = context.elevationRange() != mContext.elevationRange();
178 mContext = context;
179
180 for ( auto &job : mJobs )
181 {
182 // regenerate only those results which are refinable
183 const bool jobNeedsRegeneration = ( maxErrorChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit ) )
184 || ( distanceRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange ) )
185 || ( elevationRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange ) );
186 if ( !jobNeedsRegeneration )
187 continue;
188
189 job->mutex.lock();
190 job->context = mContext;
191 if ( job->results && job->complete )
192 job->invalidatedResults = std::move( job->results );
193 job->results.reset();
194 job->complete = false;
195 job->mutex.unlock();
196 }
197}
198
200{
201 for ( auto &job : mJobs )
202 {
203 // regenerate only those results which are refinable
204 const bool jobNeedsRegeneration = ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit )
205 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange )
206 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange );
207 if ( !jobNeedsRegeneration )
208 continue;
209
210 job->mutex.lock();
211 job->context = mContext;
212 if ( job->results && job->complete )
213 job->invalidatedResults = std::move( job->results );
214 job->results.reset();
215 job->complete = false;
216 job->mutex.unlock();
217 }
218}
219
221{
222 replaceSourceInternal( source, false );
223}
224
226{
227 return replaceSourceInternal( source, true );
228}
229
230bool QgsProfilePlotRenderer::replaceSourceInternal( QgsAbstractProfileSource *source, bool clearPreviousResults )
231{
232 if ( !source )
233 return false;
234
235 std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) };
236 if ( !generator )
237 return false;
238
239 QString sourceId = generator->sourceId();
240 bool res = false;
241 for ( auto &job : mJobs )
242 {
243 if ( job->generator && job->generator->sourceId() == sourceId )
244 {
245 job->mutex.lock();
246 res = true;
247 if ( clearPreviousResults )
248 {
249 job->results.reset();
250 job->complete = false;
251 }
252 else if ( job->results )
253 {
254 job->results->copyPropertiesFromGenerator( generator.get() );
255 }
256 job->generator = generator.get();
257 job->mutex.unlock();
258
259 for ( auto it = mGenerators.begin(); it != mGenerators.end(); )
260 {
261 if ( ( *it )->sourceId() == sourceId )
262 it = mGenerators.erase( it );
263 else
264 it++;
265 }
266 mGenerators.emplace_back( std::move( generator ) );
267 }
268 }
269 return res;
270}
271
273{
274 if ( isActive() )
275 return;
276
277 mStatus = Generating;
278
279 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
280
281 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
282 mFutureWatcher.setFuture( mFuture );
283}
284
286{
287 double min = std::numeric_limits< double >::max();
288 double max = std::numeric_limits< double >::lowest();
289 for ( const auto &job : mJobs )
290 {
291 if ( job->complete && job->results )
292 {
293 const QgsDoubleRange jobRange = job->results->zRange();
294 min = std::min( min, jobRange.lower() );
295 max = std::max( max, jobRange.upper() );
296 }
297 }
298 return QgsDoubleRange( min, max );
299}
300
301QImage QgsProfilePlotRenderer::renderToImage( int width, int height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId, double devicePixelRatio )
302{
303 QImage res( width, height, QImage::Format_ARGB32_Premultiplied );
304 res.setDotsPerMeterX( 96 / 25.4 * 1000 );
305 res.setDotsPerMeterY( 96 / 25.4 * 1000 );
306 res.fill( Qt::transparent );
307
308 QPainter p( &res );
309
312 context.setPainterFlagsUsingContext( &p );
313 context.setDevicePixelRatio( devicePixelRatio );
314 const double mapUnitsPerPixel = ( distanceMax - distanceMin ) / width;
315 context.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
316
317 render( context, width, height, distanceMin, distanceMax, zMin, zMax, sourceId );
318 p.end();
319
320 return res;
321}
322
323void QgsProfilePlotRenderer::render( QgsRenderContext &context, double width, double height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId )
324{
325 QPainter *painter = context.painter();
326 if ( !painter )
327 return;
328
329 QgsProfileRenderContext profileRenderContext( context );
330
331 QTransform transform;
332 transform.translate( 0, height );
333 transform.scale( width / ( distanceMax - distanceMin ), -height / ( zMax - zMin ) );
334 transform.translate( -distanceMin, -zMin );
335 profileRenderContext.setWorldTransform( transform );
336
337 profileRenderContext.setDistanceRange( QgsDoubleRange( distanceMin, distanceMax ) );
338 profileRenderContext.setElevationRange( QgsDoubleRange( zMin, zMax ) );
339
340 for ( auto &job : mJobs )
341 {
342 if ( ( sourceId.isEmpty() || job->generator->sourceId() == sourceId ) )
343 {
344 job->mutex.lock();
345 if ( job->complete && job->results )
346 {
347 job->results->renderResults( profileRenderContext );
348 }
349 else if ( !job->complete && job->invalidatedResults )
350 {
351 // draw the outdated results while we wait for refinement to complete
352 job->invalidatedResults->renderResults( profileRenderContext );
353 }
354 job->mutex.unlock();
355 }
356 }
357}
358
360{
361 QgsProfileSnapResult bestSnapResult;
362 if ( !mRequest.profileCurve() )
363 return bestSnapResult;
364
365 double bestSnapDistance = std::numeric_limits< double >::max();
366
367 for ( const auto &job : mJobs )
368 {
369 job->mutex.lock();
370 if ( job->complete && job->results )
371 {
372 const QgsProfileSnapResult jobSnapResult = job->results->snapPoint( point, context );
373 if ( jobSnapResult.isValid() )
374 {
375 const double snapDistance = std::pow( point.distance() - jobSnapResult.snappedPoint.distance(), 2 )
376 + std::pow( ( point.elevation() - jobSnapResult.snappedPoint.elevation() ) / context.displayRatioElevationVsDistance, 2 );
377
378 if ( snapDistance < bestSnapDistance )
379 {
380 bestSnapDistance = snapDistance;
381 bestSnapResult = jobSnapResult;
382 }
383 }
384 }
385 job->mutex.unlock();
386 }
387
388 return bestSnapResult;
389}
390
391QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
392{
393 QVector<QgsProfileIdentifyResults> res;
394 if ( !mRequest.profileCurve() )
395 return res;
396
397 for ( const auto &job : mJobs )
398 {
399 job->mutex.lock();
400 if ( job->complete && job->results )
401 {
402 res.append( job->results->identify( point, context ) );
403 }
404 job->mutex.unlock();
405 }
406
407 return res;
408}
409
410QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext &context )
411{
412 QVector<QgsProfileIdentifyResults> res;
413 if ( !mRequest.profileCurve() )
414 return res;
415
416 for ( const auto &job : mJobs )
417 {
418 job->mutex.lock();
419 if ( job->complete && job->results )
420 {
421 res.append( job->results->identify( distanceRange, elevationRange, context ) );
422 }
423 job->mutex.unlock();
424 }
425
426 return res;
427}
428
429QVector<QgsAbstractProfileResults::Feature> QgsProfilePlotRenderer::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback )
430{
431 QVector<QgsAbstractProfileResults::Feature > res;
432 for ( const auto &job : mJobs )
433 {
434 if ( feedback && feedback->isCanceled() )
435 break;
436
437 job->mutex.lock();
438 if ( job->complete && job->results )
439 {
440 res.append( job->results->asFeatures( type, feedback ) );
441 }
442 job->mutex.unlock();
443 }
444 return res;
445}
446
447void QgsProfilePlotRenderer::onGeneratingFinished()
448{
449 mStatus = Idle;
450 emit generationFinished();
451}
452
453void QgsProfilePlotRenderer::generateProfileStatic( std::unique_ptr< ProfileJob > &job )
454{
455 if ( job->results )
456 return;
457
458 Q_ASSERT( job->generator );
459
460 job->generator->generateProfile( job->context );
461 job->mutex.lock();
462 job->results.reset( job->generator->takeResults() );
463 job->complete = true;
464 job->invalidatedResults.reset();
465 job->mutex.unlock();
466}
467
@ RespectsMaximumErrorMapUnit
Generated profile respects the QgsProfileGenerationContext::maximumErrorMapUnits() property.
@ RespectsElevationRange
Generated profile respects the QgsProfileGenerationContext::elevationRange() property.
@ RespectsDistanceRange
Generated profile respects the QgsProfileGenerationContext::distanceRange() property.
@ Antialiasing
Use antialiasing while drawing.
ProfileExportType
Types of export for elevation profiles.
Definition: qgis.h:2677
virtual QString sourceId() const =0
Returns a unique identifier representing the source of the profile.
Interface for classes which can generate elevation profiles.
virtual QgsAbstractProfileGenerator * createProfileGenerator(const QgsProfileRequest &request)=0
Given a profile request, returns a new profile generator ready for generating elevation profiles.
QgsRange which stores a range of double values.
Definition: qgsrange.h:203
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
Encapsulates the context in which an elevation profile is to be generated.
double maximumErrorMapUnits() const
Returns the maximum allowed error in the generated result, in profile curve map units.
QgsDoubleRange elevationRange() const
Returns the range of elevations to include in the generation.
QgsDoubleRange distanceRange() const
Returns the range of distances to include in the generation.
Encapsulates the context of identifying profile results.
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context)
Snap a point to the results.
void render(QgsRenderContext &context, double width, double height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId=QString())
Renders a portion of the profile using the specified render context.
void regenerateInvalidatedResults()
Starts a background regeneration of any invalidated results and immediately returns.
QVector< QgsAbstractProfileResults::Feature > asFeatures(Qgis::ProfileExportType type, QgsFeedback *feedback=nullptr)
Exports the profile results as a set of features.
QImage renderToImage(int width, int height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId=QString(), double devicePixelRatio=1.0)
Renders a portion of the profile to an image with the given width and height.
void cancelGenerationWithoutBlocking()
Triggers cancellation of the generation job without blocking.
void invalidateAllRefinableSources()
Invalidates previous results from all refinable sources.
void cancelGeneration()
Stop the generation job - does not return until the job has terminated.
QgsProfilePlotRenderer(const QList< QgsAbstractProfileSource * > &sources, const QgsProfileRequest &request)
Constructor for QgsProfilePlotRenderer, using the provided list of profile sources to generate the re...
void generateSynchronously()
Generate the profile results synchronously in this thread.
void startGeneration()
Start the generation job and immediately return.
QgsDoubleRange zRange() const
Returns the limits of the retrieved elevation values.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context)
Identify results visible at the specified profile point.
void waitForFinished()
Block until the current job has finished.
bool isActive() const
Returns true if the generation job is currently running in background.
QStringList sourceIds() const
Returns the ordered list of source IDs for the sources used by the renderer.
bool invalidateResults(QgsAbstractProfileSource *source)
Invalidates the profile results from the source with matching ID.
void replaceSource(QgsAbstractProfileSource *source)
Replaces the existing source with matching ID.
void setContext(const QgsProfileGenerationContext &context)
Sets the context in which the profile generation will occur.
void generationFinished()
Emitted when the profile generation is finished (or canceled).
Encapsulates a point on a distance-elevation profile.
double elevation() const SIP_HOLDGIL
Returns the elevation of the point.
double distance() const SIP_HOLDGIL
Returns the distance of the point.
Abstract base class for storage of elevation profiles.
void setWorldTransform(const QTransform &transform)
Sets the transform from world coordinates to painter coordinates.
void setDistanceRange(const QgsDoubleRange &range)
Sets the range of distances to include in the render.
void setElevationRange(const QgsDoubleRange &range)
Sets the range of elevations to include in the render.
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
QgsCurve * profileCurve() const
Returns the cross section profile curve, which represents the line along which the profile should be ...
Encapsulates the context of snapping a profile point.
double displayRatioElevationVsDistance
Display ratio of elevation vs distance units.
Encapsulates results of snapping a profile point.
bool isValid() const
Returns true if the result is a valid point.
QgsProfilePoint snappedPoint
Snapped point.
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:66
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
Contains information about the context of a rendering operation.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3988