QGIS API Documentation 3.99.0-Master (8e76e220402)
Loading...
Searching...
No Matches
qgsprofilerenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprofilerenderer.cpp
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
21#include "qgscurve.h"
22#include "qgslinesymbollayer.h"
23#include "qgsprofilesnapping.h"
24
25#include <QtConcurrentMap>
26
27#include "moc_qgsprofilerenderer.cpp"
28
29QgsProfilePlotRenderer::QgsProfilePlotRenderer( const QList< QgsAbstractProfileSource * > &sources,
30 const QgsProfileRequest &request )
31 : mRequest( request )
32{
33 for ( QgsAbstractProfileSource *source : sources )
34 {
35 if ( source )
36 {
37 if ( std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) } )
38 mGenerators.emplace_back( std::move( generator ) );
39 }
40 }
41}
42
43QgsProfilePlotRenderer::QgsProfilePlotRenderer( std::vector<std::unique_ptr<QgsAbstractProfileGenerator> > generators, const QgsProfileRequest &request )
44 : mGenerators( std::move( generators ) )
45 , mRequest( request )
46{
47}
48
56
58{
59 QStringList res;
60 res.reserve( mGenerators.size() );
61 for ( const auto &it : mGenerators )
62 {
63 res.append( it->sourceId() );
64 }
65 return res;
66}
67
69{
70 if ( isActive() )
71 return;
72
73 mStatus = Generating;
74
75 Q_ASSERT( mJobs.empty() );
76
77 mJobs.reserve( mGenerators.size() );
78 for ( const auto &it : mGenerators )
79 {
80 auto job = std::make_unique< ProfileJob >();
81 job->generator = it.get();
82 job->context = mContext;
83 mJobs.emplace_back( std::move( job ) );
84 }
85
86 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
87
88 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
89 mFutureWatcher.setFuture( mFuture );
90}
91
93{
94 if ( isActive() )
95 return;
96
97 mStatus = Generating;
98
99 Q_ASSERT( mJobs.empty() );
100 mJobs.reserve( mGenerators.size() );
101
102 for ( const auto &it : mGenerators )
103 {
104 auto job = std::make_unique< ProfileJob >();
105 job->generator = it.get();
106 job->context = mContext;
107 it.get()->generateProfile( job->context );
108 job->results.reset( job->generator->takeResults() );
109 job->complete = true;
110 job->invalidatedResults.reset();
111 mJobs.emplace_back( std::move( job ) );
112 }
113
114 mStatus = Idle;
115}
116
118{
119 if ( !isActive() )
120 return;
121
122 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
123
124 for ( const auto &job : mJobs )
125 {
126 if ( job->generator )
127 {
128 if ( QgsFeedback *feedback = job->generator->feedback() )
129 {
130 feedback->cancel();
131 }
132 }
133 }
134
135 mFutureWatcher.waitForFinished();
136
137 onGeneratingFinished();
138}
139
141{
142 if ( !isActive() )
143 return;
144
145 for ( const auto &job : mJobs )
146 {
147 if ( job->generator )
148 {
149 if ( QgsFeedback *feedback = job->generator->feedback() )
150 {
151 feedback->cancel();
152 }
153 }
154 }
155}
156
158{
159 if ( !isActive() )
160 return;
161
162 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
163 mFutureWatcher.waitForFinished();
164
165 onGeneratingFinished();
166}
167
169{
170 return mStatus != Idle;
171}
172
174{
175 if ( mContext == context )
176 return;
177
178 const double maxErrorChanged = !qgsDoubleNear( context.maximumErrorMapUnits(), mContext.maximumErrorMapUnits() );
179 const double distanceRangeChanged = context.distanceRange() != mContext.distanceRange();
180 const double elevationRangeChanged = context.elevationRange() != mContext.elevationRange();
181 mContext = context;
182
183 for ( auto &job : mJobs )
184 {
185 // regenerate only those results which are refinable
186 const bool jobNeedsRegeneration = ( maxErrorChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit ) )
187 || ( distanceRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange ) )
188 || ( elevationRangeChanged && ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange ) );
189 if ( !jobNeedsRegeneration )
190 continue;
191
192 job->mutex.lock();
193 job->context = mContext;
194 if ( job->results && job->complete )
195 job->invalidatedResults = std::move( job->results );
196 job->results.reset();
197 job->complete = false;
198 job->mutex.unlock();
199 }
200}
201
203{
204 for ( auto &job : mJobs )
205 {
206 // regenerate only those results which are refinable
207 const bool jobNeedsRegeneration = ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit )
208 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsDistanceRange )
209 || ( job->generator->flags() & Qgis::ProfileGeneratorFlag::RespectsElevationRange );
210 if ( !jobNeedsRegeneration )
211 continue;
212
213 job->mutex.lock();
214 job->context = mContext;
215 if ( job->results && job->complete )
216 job->invalidatedResults = std::move( job->results );
217 job->results.reset();
218 job->complete = false;
219 job->mutex.unlock();
220 }
221}
222
224{
225 replaceSourceInternal( source, false );
226}
227
229{
230 return replaceSourceInternal( source, true );
231}
232
233bool QgsProfilePlotRenderer::replaceSourceInternal( QgsAbstractProfileSource *source, bool clearPreviousResults )
234{
235 if ( !source )
236 return false;
237
238 std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) };
239 if ( !generator )
240 return false;
241
242 QString sourceId = generator->sourceId();
243 bool res = false;
244 for ( auto &job : mJobs )
245 {
246 if ( job->generator && job->generator->sourceId() == sourceId )
247 {
248 job->mutex.lock();
249 res = true;
250 if ( clearPreviousResults || job->generator->type() != generator->type() )
251 {
252 job->results.reset();
253 job->complete = false;
254 }
255 else if ( job->results )
256 {
257 job->results->copyPropertiesFromGenerator( generator.get() );
258 }
259 job->generator = generator.get();
260 job->mutex.unlock();
261
262 for ( auto it = mGenerators.begin(); it != mGenerators.end(); )
263 {
264 if ( ( *it )->sourceId() == sourceId )
265 it = mGenerators.erase( it );
266 else
267 it++;
268 }
269 mGenerators.emplace_back( std::move( generator ) );
270 }
271 }
272 return res;
273}
274
276{
277 if ( isActive() )
278 return;
279
280 mStatus = Generating;
281
282 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsProfilePlotRenderer::onGeneratingFinished );
283
284 mFuture = QtConcurrent::map( mJobs, generateProfileStatic );
285 mFutureWatcher.setFuture( mFuture );
286}
287
289{
290 double min = std::numeric_limits< double >::max();
291 double max = std::numeric_limits< double >::lowest();
292 for ( const auto &job : mJobs )
293 {
294 if ( job->complete && job->results )
295 {
296 const QgsDoubleRange jobRange = job->results->zRange();
297 min = std::min( min, jobRange.lower() );
298 max = std::max( max, jobRange.upper() );
299 }
300 }
301 return QgsDoubleRange( min, max );
302}
303
304QImage QgsProfilePlotRenderer::renderToImage( int width, int height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId, double devicePixelRatio )
305{
306 QImage res( width, height, QImage::Format_ARGB32_Premultiplied );
307 res.setDotsPerMeterX( 96 / 25.4 * 1000 );
308 res.setDotsPerMeterY( 96 / 25.4 * 1000 );
309 res.fill( Qt::transparent );
310
311 QPainter p( &res );
312
315 context.setPainterFlagsUsingContext( &p );
316 context.setDevicePixelRatio( devicePixelRatio );
317 const double mapUnitsPerPixel = ( distanceMax - distanceMin ) / width;
318 context.setMapToPixel( QgsMapToPixel( mapUnitsPerPixel ) );
319
320 render( context, width, height, distanceMin, distanceMax, zMin, zMax, sourceId );
321 QRectF plotArea( QPointF( 0, 0 ), QPointF( width, height ) );
322 renderSubsectionsIndicator( context, plotArea, distanceMin, distanceMax, zMin, zMax );
323 p.end();
324
325 return res;
326}
327
328QTransform QgsProfilePlotRenderer::computeRenderTransform( double width, double height, double distanceMin, double distanceMax, double zMin, double zMax )
329{
330 QTransform transform;
331 transform.translate( 0, height );
332 transform.scale( width / ( distanceMax - distanceMin ), -height / ( zMax - zMin ) );
333 transform.translate( -distanceMin, -zMin );
334
335 return transform;
336}
337
338void QgsProfilePlotRenderer::render( QgsRenderContext &context, double width, double height, double distanceMin, double distanceMax, double zMin, double zMax, const QString &sourceId )
339{
340 QPainter *painter = context.painter();
341 if ( !painter )
342 return;
343
344 QgsProfileRenderContext profileRenderContext( context );
345 profileRenderContext.setWorldTransform( computeRenderTransform( width, height, distanceMin, distanceMax, zMin, zMax ) );
346
347 profileRenderContext.setDistanceRange( QgsDoubleRange( distanceMin, distanceMax ) );
348 profileRenderContext.setElevationRange( QgsDoubleRange( zMin, zMax ) );
349
350 for ( auto &job : mJobs )
351 {
352 if ( ( sourceId.isEmpty() || job->generator->sourceId() == sourceId ) )
353 {
354 job->mutex.lock();
355 if ( job->complete && job->results )
356 {
357 job->results->renderResults( profileRenderContext );
358 }
359 else if ( !job->complete && job->invalidatedResults )
360 {
361 // draw the outdated results while we wait for refinement to complete
362 job->invalidatedResults->renderResults( profileRenderContext );
363 }
364 job->mutex.unlock();
365 }
366 }
367}
368
370{
371 auto subSections = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 255, 0, 0, 255 ), 0.5 );
372 subSections->setPenCapStyle( Qt::FlatCap );
373 return std::make_unique<QgsLineSymbol>( QgsSymbolLayerList() << subSections.release() );
374}
375
377{
378 mSubsectionsSymbol.reset( symbol );
379}
380
382{
383 return mSubsectionsSymbol.get();
384}
385
386void QgsProfilePlotRenderer::renderSubsectionsIndicator( QgsRenderContext &context, const QRectF &plotArea, double distanceMin, double distanceMax, double zMin, double zMax )
387{
388 QgsCurve *profileCurve = mRequest.profileCurve();
389 if ( !profileCurve || profileCurve->numPoints() < 3 || !mSubsectionsSymbol )
390 return;
391
392 QTransform transform = computeRenderTransform( plotArea.width(), plotArea.height(), distanceMin, distanceMax, zMin, zMax );
393
394 QgsPointSequence points;
395 profileCurve->points( points );
396 QgsPoint firstPoint = points.takeFirst();
397 points.removeLast();
398
399 mSubsectionsSymbol->startRender( context );
400 double accumulatedDistance = 0.;
401 for ( const QgsPoint &point : points )
402 {
403 accumulatedDistance += point.distance( firstPoint );
404 QPointF output = transform.map( QPointF( accumulatedDistance, 0. ) );
405 QPolygonF polyLine( QVector<QPointF> { QPointF( output.x() + plotArea.left(), plotArea.top() ), QPointF( output.x() + plotArea.left(), plotArea.bottom() ) } );
406 mSubsectionsSymbol->renderPolyline( polyLine, nullptr, context );
407 firstPoint = point;
408 }
409 mSubsectionsSymbol->stopRender( context );
410}
411
413{
414 QgsProfileSnapResult bestSnapResult;
415 if ( !mRequest.profileCurve() )
416 return bestSnapResult;
417
418 double bestSnapDistance = std::numeric_limits< double >::max();
419
420 for ( const auto &job : mJobs )
421 {
422 job->mutex.lock();
423 if ( job->complete && job->results )
424 {
425 const QgsProfileSnapResult jobSnapResult = job->results->snapPoint( point, context );
426 if ( jobSnapResult.isValid() )
427 {
428 const double snapDistance = std::pow( point.distance() - jobSnapResult.snappedPoint.distance(), 2 )
429 + std::pow( ( point.elevation() - jobSnapResult.snappedPoint.elevation() ) / context.displayRatioElevationVsDistance, 2 );
430
431 if ( snapDistance < bestSnapDistance )
432 {
433 bestSnapDistance = snapDistance;
434 bestSnapResult = jobSnapResult;
435 }
436 }
437 }
438 job->mutex.unlock();
439 }
440
441 return bestSnapResult;
442}
443
444QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
445{
446 QVector<QgsProfileIdentifyResults> res;
447 if ( !mRequest.profileCurve() )
448 return res;
449
450 for ( const auto &job : mJobs )
451 {
452 job->mutex.lock();
453 if ( job->complete && job->results )
454 {
455 res.append( job->results->identify( point, context ) );
456 }
457 job->mutex.unlock();
458 }
459
460 return res;
461}
462
463QVector<QgsProfileIdentifyResults> QgsProfilePlotRenderer::identify( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const QgsProfileIdentifyContext &context )
464{
465 QVector<QgsProfileIdentifyResults> res;
466 if ( !mRequest.profileCurve() )
467 return res;
468
469 for ( const auto &job : mJobs )
470 {
471 job->mutex.lock();
472 if ( job->complete && job->results )
473 {
474 res.append( job->results->identify( distanceRange, elevationRange, context ) );
475 }
476 job->mutex.unlock();
477 }
478
479 return res;
480}
481
482QVector<QgsAbstractProfileResults::Feature> QgsProfilePlotRenderer::asFeatures( Qgis::ProfileExportType type, QgsFeedback *feedback )
483{
484 QVector<QgsAbstractProfileResults::Feature > res;
485 for ( const auto &job : mJobs )
486 {
487 if ( feedback && feedback->isCanceled() )
488 break;
489
490 job->mutex.lock();
491 if ( job->complete && job->results )
492 {
493 res.append( job->results->asFeatures( type, feedback ) );
494 }
495 job->mutex.unlock();
496 }
497 return res;
498}
499
500void QgsProfilePlotRenderer::onGeneratingFinished()
501{
502 mStatus = Idle;
503 emit generationFinished();
504}
505
506void QgsProfilePlotRenderer::generateProfileStatic( std::unique_ptr< ProfileJob > &job )
507{
508 if ( job->results )
509 return;
510
511 Q_ASSERT( job->generator );
512
513 job->generator->generateProfile( job->context );
514 job->mutex.lock();
515 job->results.reset( job->generator->takeResults() );
516 job->complete = true;
517 job->invalidatedResults.reset();
518 job->mutex.unlock();
519}
@ RespectsMaximumErrorMapUnit
Generated profile respects the QgsProfileGenerationContext::maximumErrorMapUnits() property.
Definition qgis.h:4313
@ RespectsElevationRange
Generated profile respects the QgsProfileGenerationContext::elevationRange() property.
Definition qgis.h:4315
@ RespectsDistanceRange
Generated profile respects the QgsProfileGenerationContext::distanceRange() property.
Definition qgis.h:4314
@ Antialiasing
Use antialiasing while drawing.
Definition qgis.h:2814
ProfileExportType
Types of export for elevation profiles.
Definition qgis.h:4327
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.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual int numPoints() const =0
Returns the number of points in the curve.
virtual void points(QgsPointSequence &pt) const =0
Returns a list of points within the curve.
QgsRange which stores a range of double values.
Definition qgsrange.h:236
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:55
A line symbol type, for rendering LineString and MultiLineString geometries.
Perform transforms between map coordinates and device coordinates.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
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.
void setSubsectionsSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the subsections.
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 renderSubsectionsIndicator(QgsRenderContext &context, const QRectF &plotArea, double distanceMin, double distanceMax, double zMin, double zMax)
Renders the vertices of the profile curve as vertical lines 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.
QgsLineSymbol * subsectionsSymbol()
Returns the line symbol used to draw the subsections.
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).
static std::unique_ptr< QgsLineSymbol > defaultSubSectionsSymbol()
Returns the default line symbol to use for subsections lines.
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...
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:81
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:88
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:6935
QVector< QgsPoint > QgsPointSequence
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30