QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
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"
18#include "moc_qgsprofilerenderer.cpp"
21#include "qgscurve.h"
22#include "qgsprofilesnapping.h"
23
24#include <QtConcurrentMap>
25#include <QtConcurrentRun>
26
27QgsProfilePlotRenderer::QgsProfilePlotRenderer( const QList< QgsAbstractProfileSource * > &sources,
28 const QgsProfileRequest &request )
29 : mRequest( request )
30{
31 for ( QgsAbstractProfileSource *source : sources )
32 {
33 if ( source )
34 {
35 if ( std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) } )
36 mGenerators.emplace_back( std::move( generator ) );
37 }
38 }
39}
40
41QgsProfilePlotRenderer::QgsProfilePlotRenderer( std::vector<std::unique_ptr<QgsAbstractProfileGenerator> > generators, const QgsProfileRequest &request )
42 : mGenerators( std::move( generators ) )
43 , mRequest( request )
44{
45}
46
54
56{
57 QStringList res;
58 res.reserve( mGenerators.size() );
59 for ( const auto &it : mGenerators )
60 {
61 res.append( it->sourceId() );
62 }
63 return res;
64}
65
67{
68 if ( isActive() )
69 return;
70
71 mStatus = Generating;
72
73 Q_ASSERT( mJobs.empty() );
74
75 mJobs.reserve( mGenerators.size() );
76 for ( const auto &it : mGenerators )
77 {
78 std::unique_ptr< ProfileJob > job = std::make_unique< ProfileJob >();
79 job->generator = it.get();
80 job->context = mContext;
81 mJobs.emplace_back( std::move( job ) );
82 }
83
84 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
85
86 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
87 mFutureWatcher.setFuture( mFuture );
88}
89
91{
92 if ( isActive() )
93 return;
94
95 mStatus = Generating;
96
97 Q_ASSERT( mJobs.empty() );
98 mJobs.reserve( mGenerators.size() );
99
100 for ( const auto &it : mGenerators )
101 {
102 std::unique_ptr< ProfileJob > job = std::make_unique< ProfileJob >();
103 job->generator = it.get();
104 job->context = mContext;
105 it.get()->generateProfile( job->context );
106 job->results.reset( job->generator->takeResults() );
107 job->complete = true;
108 job->invalidatedResults.reset();
109 mJobs.emplace_back( std::move( job ) );
110 }
111
112 mStatus = Idle;
113}
114
116{
117 if ( !isActive() )
118 return;
119
120 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
121
122 for ( const auto &job : mJobs )
123 {
124 if ( job->generator )
125 {
126 if ( QgsFeedback *feedback = job->generator->feedback() )
127 {
128 feedback->cancel();
129 }
130 }
131 }
132
133 mFutureWatcher.waitForFinished();
134
135 onGeneratingFinished();
136}
137
139{
140 if ( !isActive() )
141 return;
142
143 for ( const auto &job : mJobs )
144 {
145 if ( job->generator )
146 {
147 if ( QgsFeedback *feedback = job->generator->feedback() )
148 {
149 feedback->cancel();
150 }
151 }
152 }
153}
154
156{
157 if ( !isActive() )
158 return;
159
160 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
161 mFutureWatcher.waitForFinished();
162
163 onGeneratingFinished();
164}
165
167{
168 return mStatus != Idle;
169}
170
172{
173 if ( mContext == context )
174 return;
175
176 const double maxErrorChanged = !qgsDoubleNear( context.maximumErrorMapUnits(), mContext.maximumErrorMapUnits() );
177 const double distanceRangeChanged = context.distanceRange() != mContext.distanceRange();
178 const double elevationRangeChanged = context.elevationRange() != mContext.elevationRange();
179 mContext = context;
180
181 for ( auto &job : mJobs )
182 {
183 // regenerate only those results which are refinable
184 const bool jobNeedsRegeneration = ( maxErrorChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit ) )
185 || ( distanceRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange ) )
186 || ( elevationRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange ) );
187 if ( !jobNeedsRegeneration )
188 continue;
189
190 job->mutex.lock();
191 job->context = mContext;
192 if ( job->results && job->complete )
193 job->invalidatedResults = std::move( job->results );
194 job->results.reset();
195 job->complete = false;
196 job->mutex.unlock();
197 }
198}
199
201{
202 for ( auto &job : mJobs )
203 {
204 // regenerate only those results which are refinable
205 const bool jobNeedsRegeneration = ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit )
206 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange )
207 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange );
208 if ( !jobNeedsRegeneration )
209 continue;
210
211 job->mutex.lock();
212 job->context = mContext;
213 if ( job->results && job->complete )
214 job->invalidatedResults = std::move( job->results );
215 job->results.reset();
216 job->complete = false;
217 job->mutex.unlock();
218 }
219}
220
222{
223 replaceSourceInternal( source, false );
224}
225
227{
228 return replaceSourceInternal( source, true );
229}
230
231bool QgsProfilePlotRenderer::replaceSourceInternal( QgsAbstractProfileSource *source, bool clearPreviousResults )
232{
233 if ( !source )
234 return false;
235
236 std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) };
237 if ( !generator )
238 return false;
239
240 QString sourceId = generator->sourceId();
241 bool res = false;
242 for ( auto &job : mJobs )
243 {
244 if ( job->generator && job->generator->sourceId() == sourceId )
245 {
246 job->mutex.lock();
247 res = true;
248 if ( clearPreviousResults )
249 {
250 job->results.reset();
251 job->complete = false;
252 }
253 else if ( job->results )
254 {
255 job->results->copyPropertiesFromGenerator( generator.get() );
256 }
257 job->generator = generator.get();
258 job->mutex.unlock();
259
260 for ( auto it = mGenerators.begin(); it != mGenerators.end(); )
261 {
262 if ( ( *it )->sourceId() == sourceId )
263 it = mGenerators.erase( it );
264 else
265 it++;
266 }
267 mGenerators.emplace_back( std::move( generator ) );
268 }
269 }
270 return res;
271}
272
274{
275 if ( isActive() )
276 return;
277
278 mStatus = Generating;
279
280 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
281
282 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
283 mFutureWatcher.setFuture( mFuture );
284}
285
287{
288 double min = std::numeric_limits< double >::max();
289 double max = std::numeric_limits< double >::lowest();
290 for ( const auto &job : mJobs )
291 {
292 if ( job->complete && job->results )
293 {
294 const QgsDoubleRange jobRange = job->results->zRange();
295 min = std::min( min, jobRange.lower() );
296 max = std::max( max, jobRange.upper() );
297 }
298 }
299 return QgsDoubleRange( min, max );
300}
301
302QImage QgsProfilePlotRenderer::renderToImage( int width, int height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId, double devicePixelRatio )
303{
304 QImage res( width, height, QImage::Format_ARGB32_Premultiplied );
305 res.setDotsPerMeterX( 96 / 25.4 * 1000 );
306 res.setDotsPerMeterY( 96 / 25.4 * 1000 );
307 res.fill( Qt::transparent );
308
309 QPainter p( &res );
310
313 context.setPainterFlagsUsingContext( &p );
314 context.setDevicePixelRatio( devicePixelRatio );
315 const double mapUnitsPerPixel = ( distanceMax - distanceMin ) / width;
316 context.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
317
318 render( context, width, height, distanceMin, distanceMax, zMin, zMax, sourceId );
319 p.end();
320
321 return res;
322}
323
324void QgsProfilePlotRenderer::render( QgsRenderContext &context, double width, double height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId )
325{
326 QPainter *painter = context.painter();
327 if ( !painter )
328 return;
329
330 QgsProfileRenderContext profileRenderContext( context );
331
332 QTransform transform;
333 transform.translate( 0, height );
334 transform.scale( width / ( distanceMax - distanceMin ), -height / ( zMax - zMin ) );
335 transform.translate( -distanceMin, -zMin );
336 profileRenderContext.setWorldTransform( transform );
337
338 profileRenderContext.setDistanceRange( QgsDoubleRange( distanceMin, distanceMax ) );
339 profileRenderContext.setElevationRange( QgsDoubleRange( zMin, zMax ) );
340
341 for ( auto &job : mJobs )
342 {
343 if ( ( sourceId.isEmpty() || job->generator->sourceId() == sourceId ) )
344 {
345 job->mutex.lock();
346 if ( job->complete && job->results )
347 {
348 job->results->renderResults( profileRenderContext );
349 }
350 else if ( !job->complete && job->invalidatedResults )
351 {
352 // draw the outdated results while we wait for refinement to complete
353 job->invalidatedResults->renderResults( profileRenderContext );
354 }
355 job->mutex.unlock();
356 }
357 }
358}
359
361{
362 QgsProfileSnapResult bestSnapResult;
363 if ( !mRequest.profileCurve() )
364 return bestSnapResult;
365
366 double bestSnapDistance = std::numeric_limits< double >::max();
367
368 for ( const auto &job : mJobs )
369 {
370 job->mutex.lock();
371 if ( job->complete && job->results )
372 {
373 const QgsProfileSnapResult jobSnapResult = job->results->snapPoint( point, context );
374 if ( jobSnapResult.isValid() )
375 {
376 const double snapDistance = std::pow( point.distance() - jobSnapResult.snappedPoint.distance(), 2 )
377 + std::pow( ( point.elevation() - jobSnapResult.snappedPoint.elevation() ) / context.displayRatioElevationVsDistance, 2 );
378
379 if ( snapDistance < bestSnapDistance )
380 {
381 bestSnapDistance = snapDistance;
382 bestSnapResult = jobSnapResult;
383 }
384 }
385 }
386 job->mutex.unlock();
387 }
388
389 return bestSnapResult;
390}
391
392QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
393{
394 QVector<QgsProfileIdentifyResults> res;
395 if ( !mRequest.profileCurve() )
396 return res;
397
398 for ( const auto &job : mJobs )
399 {
400 job->mutex.lock();
401 if ( job->complete && job->results )
402 {
403 res.append( job->results->identify( point, context ) );
404 }
405 job->mutex.unlock();
406 }
407
408 return res;
409}
410
411QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext &context )
412{
413 QVector<QgsProfileIdentifyResults> res;
414 if ( !mRequest.profileCurve() )
415 return res;
416
417 for ( const auto &job : mJobs )
418 {
419 job->mutex.lock();
420 if ( job->complete && job->results )
421 {
422 res.append( job->results->identify( distanceRange, elevationRange, context ) );
423 }
424 job->mutex.unlock();
425 }
426
427 return res;
428}
429
430QVector<QgsAbstractProfileResults::Feature> QgsProfilePlotRenderer::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback )
431{
432 QVector<QgsAbstractProfileResults::Feature > res;
433 for ( const auto &job : mJobs )
434 {
435 if ( feedback && feedback->isCanceled() )
436 break;
437
438 job->mutex.lock();
439 if ( job->complete && job->results )
440 {
441 res.append( job->results->asFeatures( type, feedback ) );
442 }
443 job->mutex.unlock();
444 }
445 return res;
446}
447
448void QgsProfilePlotRenderer::onGeneratingFinished()
449{
450 mStatus = Idle;
451 emit generationFinished();
452}
453
454void QgsProfilePlotRenderer::generateProfileStatic( std::unique_ptr< ProfileJob > &job )
455{
456 if ( job->results )
457 return;
458
459 Q_ASSERT( job->generator );
460
461 job->generator->generateProfile( job->context );
462 job->mutex.lock();
463 job->results.reset( job->generator->takeResults() );
464 job->complete = true;
465 job->invalidatedResults.reset();
466 job->mutex.unlock();
467}
468
@ 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:3937
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:231
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:53
Perform transforms between map coordinates and device coordinates.
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
Returns the elevation of the point.
double distance() const
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:78
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:85
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:5958