QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
27 QgsProfilePlotRenderer::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 
200 bool 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 
271 QImage 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 
289 void 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 
357 QVector<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 
376 QVector<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 
395 void QgsProfilePlotRenderer::onGeneratingFinished()
396 {
397  mStatus = Idle;
398  emit generationFinished();
399 }
400 
401 void 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 
QgsProfileIdentifyContext
Encapsulates the context of identifying profile results.
Definition: qgsabstractprofilegenerator.h:125
QgsProfilePlotRenderer::zRange
QgsDoubleRange zRange() const
Returns the limits of the retrieved elevation values.
Definition: qgsprofilerenderer.cpp:255
QgsRenderContext::setPainterFlagsUsingContext
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
Definition: qgsrendercontext.cpp:169
QgsProfilePlotRenderer::setContext
void setContext(const QgsProfileGenerationContext &context)
Sets the context in which the profile generation will occur.
Definition: qgsprofilerenderer.cpp:140
QgsProfilePlotRenderer::invalidateResults
bool invalidateResults(QgsAbstractProfileSource *source)
Invalidates the profile results from the source with matching ID.
Definition: qgsprofilerenderer.cpp:195
QgsProfileRenderContext::setElevationRange
void setElevationRange(const QgsDoubleRange &range)
Sets the range of elevations to include in the render.
Definition: qgsabstractprofilegenerator.cpp:52
QgsProfilePlotRenderer::generationFinished
void generationFinished()
Emitted when the profile generation is finished (or canceled).
QgsProfilePlotRenderer::~QgsProfilePlotRenderer
~QgsProfilePlotRenderer() override
Definition: qgsprofilerenderer.cpp:41
QgsProfileSnapResult
Encapsulates results of snapping a profile point.
Definition: qgsprofilesnapping.h:56
QgsRenderContext
Contains information about the context of a rendering operation.
Definition: qgsrendercontext.h:59
QgsProfileRenderContext::setDistanceRange
void setDistanceRange(const QgsDoubleRange &range)
Sets the range of distances to include in the render.
Definition: qgsabstractprofilegenerator.cpp:42
QgsProfileRenderContext
Abstract base class for storage of elevation profiles.
Definition: qgsabstractprofilegenerator.h:40
QgsProfilePlotRenderer::startGeneration
void startGeneration()
Start the generation job and immediately return.
Definition: qgsprofilerenderer.cpp:60
qgsabstractprofilesource.h
Qgis::ProfileGeneratorFlag::RespectsMaximumErrorMapUnit
@ RespectsMaximumErrorMapUnit
Generated profile respects the QgsProfileGenerationContext::maximumErrorMapUnits() property.
QgsProfileSnapContext
Encapsulates the context of snapping a profile point.
Definition: qgsprofilesnapping.h:30
QgsProfilePlotRenderer::sourceIds
QStringList sourceIds() const
Returns the ordered list of source IDs for the sources used by the renderer.
Definition: qgsprofilerenderer.cpp:49
QgsAbstractProfileSource::createProfileGenerator
virtual QgsAbstractProfileGenerator * createProfileGenerator(const QgsProfileRequest &request)=0
Given a profile request, returns a new profile generator ready for generating elevation profiles.
QgsProfileGenerationContext::elevationRange
QgsDoubleRange elevationRange() const
Returns the range of elevations to include in the generation.
Definition: qgsabstractprofilegenerator.h:325
QgsProfilePlotRenderer::snapPoint
QgsProfileSnapResult snapPoint(const QgsProfilePoint &point, const QgsProfileSnapContext &context)
Snap a point to the results.
Definition: qgsprofilerenderer.cpp:325
QgsProfilePlotRenderer::cancelGenerationWithoutBlocking
void cancelGenerationWithoutBlocking()
Triggers cancellation of the generation job without blocking.
Definition: qgsprofilerenderer.cpp:107
QgsProfilePoint
Encapsulates a point on a distance-elevation profile.
Definition: qgsprofilepoint.h:30
Qgis::ProfileGeneratorFlag::RespectsElevationRange
@ RespectsElevationRange
Generated profile respects the QgsProfileGenerationContext::elevationRange() property.
QgsProfileGenerationContext
Encapsulates the context in which an elevation profile is to be generated.
Definition: qgsabstractprofilegenerator.h:262
QgsProfilePlotRenderer::isActive
bool isActive() const
Returns true if the generation job is currently running in background.
Definition: qgsprofilerenderer.cpp:135
qgsprofilerenderer.h
QgsRange::lower
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:66
qgsprofilesnapping.h
QgsProfilePlotRenderer::regenerateInvalidatedResults
void regenerateInvalidatedResults()
Starts a background regeneration of any invalidated results and immediately returns.
Definition: qgsprofilerenderer.cpp:242
QgsProfileRequest::profileCurve
QgsCurve * profileCurve() const
Returns the cross section profile curve, which represents the line along which the profile should be ...
Definition: qgsprofilerequest.cpp:97
QgsProfilePlotRenderer::identify
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context)
Identify results visible at the specified profile point.
Definition: qgsprofilerenderer.cpp:357
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2265
QgsFeedback
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
QgsRenderContext::setFlag
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
Definition: qgsrendercontext.cpp:216
QgsRange::upper
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
QgsProfileGenerationContext::distanceRange
QgsDoubleRange distanceRange() const
Returns the range of distances to include in the generation.
Definition: qgsabstractprofilegenerator.h:307
QgsProfilePlotRenderer::render
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.
Definition: qgsprofilerenderer.cpp:289
QgsProfileRequest
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
Definition: qgsprofilerequest.h:37
QgsProfilePlotRenderer::QgsProfilePlotRenderer
QgsProfilePlotRenderer(const QList< QgsAbstractProfileSource * > &sources, const QgsProfileRequest &request)
Constructor for QgsProfilePlotRenderer, using the provided list of profile sources to generate the re...
Definition: qgsprofilerenderer.cpp:27
QgsProfilePlotRenderer::renderToImage
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.
Definition: qgsprofilerenderer.cpp:271
QgsProfileSnapResult::snappedPoint
QgsProfilePoint snappedPoint
Snapped point.
Definition: qgsprofilesnapping.h:61
QgsProfilePoint::distance
double distance() const SIP_HOLDGIL
Returns the distance of the point.
Definition: qgsprofilepoint.h:88
QgsProfileSnapResult::isValid
bool isValid() const
Returns true if the result is a valid point.
Definition: qgsprofilesnapping.h:66
Qgis::RenderContextFlag::Antialiasing
@ Antialiasing
Use antialiasing while drawing.
QgsDoubleRange
QgsRange which stores a range of double values.
Definition: qgsrange.h:202
Qgis::ProfileGeneratorFlag::RespectsDistanceRange
@ RespectsDistanceRange
Generated profile respects the QgsProfileGenerationContext::distanceRange() property.
qgscurve.h
QgsRenderContext::fromQPainter
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
Definition: qgsrendercontext.cpp:143
QgsProfileGenerationContext::maximumErrorMapUnits
double maximumErrorMapUnits() const
Returns the maximum allowed error in the generated result, in profile curve map units.
Definition: qgsabstractprofilegenerator.h:274
QgsProfilePlotRenderer::invalidateAllRefinableSources
void invalidateAllRefinableSources()
Invalidates previous results from all refinable sources.
Definition: qgsprofilerenderer.cpp:169
QgsProfilePlotRenderer::replaceSource
void replaceSource(QgsAbstractProfileSource *source)
Replaces the existing source with matching ID.
Definition: qgsprofilerenderer.cpp:190
QgsRenderContext::painter
QPainter * painter()
Returns the destination QPainter for the render operation.
Definition: qgsrendercontext.h:112
QgsProfilePlotRenderer::cancelGeneration
void cancelGeneration()
Stop the generation job - does not return until the job has terminated.
Definition: qgsprofilerenderer.cpp:84
QgsAbstractProfileGenerator::sourceId
virtual QString sourceId() const =0
Returns a unique identifier representing the source of the profile.
QgsProfilePlotRenderer::waitForFinished
void waitForFinished()
Block until the current job has finished.
Definition: qgsprofilerenderer.cpp:124
QgsAbstractProfileSource
Interface for classes which can generate elevation profiles.
Definition: qgsabstractprofilesource.h:33
QgsProfileRenderContext::setWorldTransform
void setWorldTransform(const QTransform &transform)
Sets the transform from world coordinates to painter coordinates.
Definition: qgsabstractprofilegenerator.cpp:32
qgsabstractprofilegenerator.h
qgsgeos.h
QgsProfilePoint::elevation
double elevation() const SIP_HOLDGIL
Returns the elevation of the point.
Definition: qgsprofilepoint.h:98
QgsProfileSnapContext::displayRatioElevationVsDistance
double displayRatioElevationVsDistance
Display ratio of elevation vs distance units.
Definition: qgsprofilesnapping.h:60