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