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