QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 "qgsgeos.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
42{
43 if ( isActive() )
44 {
46 }
47}
48
50{
51 QStringList res;
52 res.reserve( mGenerators.size() );
53 for ( const auto &it : mGenerators )
54 {
55 res.append( it->sourceId() );
56 }
57 return res;
58}
59
61{
62 if ( isActive() )
63 return;
64
65 mStatus = Generating;
66
67 Q_ASSERT( mJobs.empty() );
68
69 mJobs.reserve( mGenerators.size() );
70 for ( const auto &it : mGenerators )
71 {
72 std::unique_ptr< ProfileJob > job = std::make_unique< ProfileJob >();
73 job->generator = it.get();
74 job->context = mContext;
75 mJobs.emplace_back( std::move( job ) );
76 }
77
78 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
79
80 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
81 mFutureWatcher.setFuture( mFuture );
82}
83
85{
86 if ( !isActive() )
87 return;
88
89 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
90
91 for ( const auto &job : mJobs )
92 {
93 if ( job->generator )
94 {
95 if ( QgsFeedback *feedback = job->generator->feedback() )
96 {
97 feedback->cancel();
98 }
99 }
100 }
101
102 mFutureWatcher.waitForFinished();
103
104 onGeneratingFinished();
105}
106
108{
109 if ( !isActive() )
110 return;
111
112 for ( const auto &job : mJobs )
113 {
114 if ( job->generator )
115 {
116 if ( QgsFeedback *feedback = job->generator->feedback() )
117 {
118 feedback->cancel();
119 }
120 }
121 }
122}
123
125{
126 if ( !isActive() )
127 return;
128
129 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
130 mFutureWatcher.waitForFinished();
131
132 onGeneratingFinished();
133}
134
136{
137 return mStatus != Idle;
138}
139
141{
142 if ( mContext == context )
143 return;
144
145 const double maxErrorChanged = !qgsDoubleNear( context.maximumErrorMapUnits(), mContext.maximumErrorMapUnits() );
146 const double distanceRangeChanged = context.distanceRange() != mContext.distanceRange();
147 const double elevationRangeChanged = context.elevationRange() != mContext.elevationRange();
148 mContext = context;
149
150 for ( auto &job : mJobs )
151 {
152 // regenerate only those results which are refinable
153 const bool jobNeedsRegeneration = ( maxErrorChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit ) )
154 || ( distanceRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange ) )
155 || ( elevationRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange ) );
156 if ( !jobNeedsRegeneration )
157 continue;
158
159 job->mutex.lock();
160 job->context = mContext;
161 if ( job->results && job->complete )
162 job->invalidatedResults = std::move( job->results );
163 job->results.reset();
164 job->complete = false;
165 job->mutex.unlock();
166 }
167}
168
170{
171 for ( auto &job : mJobs )
172 {
173 // regenerate only those results which are refinable
174 const bool jobNeedsRegeneration = ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit )
175 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange )
176 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange );
177 if ( !jobNeedsRegeneration )
178 continue;
179
180 job->mutex.lock();
181 job->context = mContext;
182 if ( job->results && job->complete )
183 job->invalidatedResults = std::move( job->results );
184 job->results.reset();
185 job->complete = false;
186 job->mutex.unlock();
187 }
188}
189
191{
192 replaceSourceInternal( source, false );
193}
194
196{
197 return replaceSourceInternal( source, true );
198}
199
200bool QgsProfilePlotRenderer::replaceSourceInternal( QgsAbstractProfileSource *source, bool clearPreviousResults )
201{
202 if ( !source )
203 return false;
204
205 std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) };
206 if ( !generator )
207 return false;
208
209 QString sourceId = generator->sourceId();
210 bool res = false;
211 for ( auto &job : mJobs )
212 {
213 if ( job->generator && job->generator->sourceId() == sourceId )
214 {
215 job->mutex.lock();
216 res = true;
217 if ( clearPreviousResults )
218 {
219 job->results.reset();
220 job->complete = false;
221 }
222 else if ( job->results )
223 {
224 job->results->copyPropertiesFromGenerator( generator.get() );
225 }
226 job->generator = generator.get();
227 job->mutex.unlock();
228
229 for ( auto it = mGenerators.begin(); it != mGenerators.end(); )
230 {
231 if ( ( *it )->sourceId() == sourceId )
232 it = mGenerators.erase( it );
233 else
234 it++;
235 }
236 mGenerators.emplace_back( std::move( generator ) );
237 }
238 }
239 return res;
240}
241
243{
244 if ( isActive() )
245 return;
246
247 mStatus = Generating;
248
249 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
250
251 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
252 mFutureWatcher.setFuture( mFuture );
253}
254
256{
257 double min = std::numeric_limits< double >::max();
258 double max = std::numeric_limits< double >::lowest();
259 for ( const auto &job : mJobs )
260 {
261 if ( job->complete && job->results )
262 {
263 const QgsDoubleRange jobRange = job->results->zRange();
264 min = std::min( min, jobRange.lower() );
265 max = std::max( max, jobRange.upper() );
266 }
267 }
268 return QgsDoubleRange( min, max );
269}
270
271QImage QgsProfilePlotRenderer::renderToImage( int width, int height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId )
272{
273 QImage res( width, height, QImage::Format_ARGB32_Premultiplied );
274 res.setDotsPerMeterX( 96 / 25.4 * 1000 );
275 res.setDotsPerMeterY( 96 / 25.4 * 1000 );
276 res.fill( Qt::transparent );
277
278 QPainter p( &res );
279
282 context.setPainterFlagsUsingContext( &p );
283 render( context, width, height, distanceMin, distanceMax, zMin, zMax, sourceId );
284 p.end();
285
286 return res;
287}
288
289void QgsProfilePlotRenderer::render( QgsRenderContext &context, double width, double height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId )
290{
291 QPainter *painter = context.painter();
292 if ( !painter )
293 return;
294
295 QgsProfileRenderContext profileRenderContext( context );
296
297 QTransform transform;
298 transform.translate( 0, height );
299 transform.scale( width / ( distanceMax - distanceMin ), -height / ( zMax - zMin ) );
300 transform.translate( -distanceMin, -zMin );
301 profileRenderContext.setWorldTransform( transform );
302
303 profileRenderContext.setDistanceRange( QgsDoubleRange( distanceMin, distanceMax ) );
304 profileRenderContext.setElevationRange( QgsDoubleRange( zMin, zMax ) );
305
306 for ( auto &job : mJobs )
307 {
308 if ( ( sourceId.isEmpty() || job->generator->sourceId() == sourceId ) )
309 {
310 job->mutex.lock();
311 if ( job->complete && job->results )
312 {
313 job->results->renderResults( profileRenderContext );
314 }
315 else if ( !job->complete && job->invalidatedResults )
316 {
317 // draw the outdated results while we wait for refinement to complete
318 job->invalidatedResults->renderResults( profileRenderContext );
319 }
320 job->mutex.unlock();
321 }
322 }
323}
324
326{
327 QgsProfileSnapResult bestSnapResult;
328 if ( !mRequest.profileCurve() )
329 return bestSnapResult;
330
331 double bestSnapDistance = std::numeric_limits< double >::max();
332
333 for ( const auto &job : mJobs )
334 {
335 job->mutex.lock();
336 if ( job->complete && job->results )
337 {
338 const QgsProfileSnapResult jobSnapResult = job->results->snapPoint( point, context );
339 if ( jobSnapResult.isValid() )
340 {
341 const double snapDistance = std::pow( point.distance() - jobSnapResult.snappedPoint.distance(), 2 )
342 + std::pow( ( point.elevation() - jobSnapResult.snappedPoint.elevation() ) / context.displayRatioElevationVsDistance, 2 );
343
344 if ( snapDistance < bestSnapDistance )
345 {
346 bestSnapDistance = snapDistance;
347 bestSnapResult = jobSnapResult;
348 }
349 }
350 }
351 job->mutex.unlock();
352 }
353
354 return bestSnapResult;
355}
356
357QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
358{
359 QVector<QgsProfileIdentifyResults> res;
360 if ( !mRequest.profileCurve() )
361 return res;
362
363 for ( const auto &job : mJobs )
364 {
365 job->mutex.lock();
366 if ( job->complete && job->results )
367 {
368 res.append( job->results->identify( point, context ) );
369 }
370 job->mutex.unlock();
371 }
372
373 return res;
374}
375
376QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext &context )
377{
378 QVector<QgsProfileIdentifyResults> res;
379 if ( !mRequest.profileCurve() )
380 return res;
381
382 for ( const auto &job : mJobs )
383 {
384 job->mutex.lock();
385 if ( job->complete && job->results )
386 {
387 res.append( job->results->identify( distanceRange, elevationRange, context ) );
388 }
389 job->mutex.unlock();
390 }
391
392 return res;
393}
394
395void QgsProfilePlotRenderer::onGeneratingFinished()
396{
397 mStatus = Idle;
398 emit generationFinished();
399}
400
401void QgsProfilePlotRenderer::generateProfileStatic( std::unique_ptr< ProfileJob > &job )
402{
403 if ( job->results )
404 return;
405
406 Q_ASSERT( job->generator );
407
408 job->generator->generateProfile( job->context );
409 job->mutex.lock();
410 job->results.reset( job->generator->takeResults() );
411 job->complete = true;
412 job->invalidatedResults.reset();
413 job->mutex.unlock();
414}
415
@ 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.
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
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.
QImage renderToImage(int width, int height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId=QString())
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 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.
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)
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:2527