QGIS API Documentation 4.3.0-Master (b46232e4f9a)
Loading...
Searching...
No Matches
qgsmeshlayerrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmeshlayerrenderer.cpp
3 ------------------------
4 begin : April 2018
5 copyright : (C) 2018 by Peter Petrik
6 email : zilolv 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
19
20#include <algorithm>
21#include <memory>
22
23#include "qgsapplication.h"
24#include "qgscolorrampshader.h"
26#include "qgslogger.h"
27#include "qgsmapclippingutils.h"
29#include "qgsmeshlayer.h"
34#include "qgsmeshlayerutils.h"
36#include "qgspainting.h"
37#include "qgspointxy.h"
38#include "qgsrastershader.h"
40#include "qgsruntimeprofiler.h"
42#include "qgsthreadingutils.h"
43
44#include <QBrush>
45#include <QElapsedTimer>
46#include <QLinearGradient>
47#include <QPair>
48#include <QPointer>
49#include <QSet>
50#include <QString>
51
52#include "moc_qgsmeshlayerrenderer.cpp"
53
54using namespace Qt::StringLiterals;
55
57 : QgsMapLayerRenderer( layer->id(), &context )
58 , mIsEditable( layer->isEditable() )
59 , mLayerName( layer->name() )
60 , mFeedback( new QgsMeshLayerRendererFeedback )
61 , mRendererSettings( layer->rendererSettings() )
62 , mEnableProfile( context.flags() & Qgis::RenderContextFlag::RecordProfile )
63 , mLayerOpacity( layer->opacity() )
64{
65 QElapsedTimer timer;
66 timer.start();
67
68 // make copies for mesh data
69 // cppcheck-suppress assertWithSideEffect
70 Q_ASSERT( layer->nativeMesh() );
71 // cppcheck-suppress assertWithSideEffect
72 Q_ASSERT( layer->triangularMesh() );
73 // cppcheck-suppress assertWithSideEffect
74 Q_ASSERT( layer->rendererCache() );
75 // cppcheck-suppress assertWithSideEffect
76 Q_ASSERT( layer->dataProvider() );
77
78 mReadyToCompose = false;
79
80 // copy native mesh
81 mNativeMesh = *( layer->nativeMesh() );
82 mLayerExtent = layer->extent();
83
84 if ( layer->elevationProperties() && layer->elevationProperties()->hasElevation() )
85 {
86 QgsMeshLayerElevationProperties *elevProp = qobject_cast<QgsMeshLayerElevationProperties *>( layer->elevationProperties() );
87
89 mElevationScale = elevProp->zScale();
90 mElevationOffset = elevProp->zOffset();
91
92 if ( !context.zRange().isInfinite() )
93 {
94 switch ( elevProp->mode() )
95 {
97 // don't need to handle anything here -- the layer renderer will never be created if the
98 // render context range doesn't match the layer's fixed elevation range
99 break;
100
102 {
103 // TODO -- filtering by mesh z values is not currently implemented
104 break;
105 }
106
108 {
109 // find the top-most group which matches the map range and parent group
110 int currentMatchingVectorGroup = -1;
111 int currentMatchingScalarGroup = -1;
112 QgsDoubleRange currentMatchingVectorRange;
113 QgsDoubleRange currentMatchingScalarRange;
114
115 const QMap<int, QgsDoubleRange > rangePerGroup = elevProp->fixedRangePerGroup();
116
117 const int activeVectorDatasetGroup = mRendererSettings.activeVectorDatasetGroup();
118 const int activeScalarDatasetGroup = mRendererSettings.activeScalarDatasetGroup();
119
120 for ( auto it = rangePerGroup.constBegin(); it != rangePerGroup.constEnd(); ++it )
121 {
122 if ( it.value().overlaps( context.zRange() ) )
123 {
124 const bool matchesVectorParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeVectorDatasetGroup ), QgsMeshDatasetIndex( it.key() ) );
125 const bool matchesScalarParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeScalarDatasetGroup ), QgsMeshDatasetIndex( it.key() ) );
126
127 if ( matchesVectorParentGroup && (
128 currentMatchingVectorRange.isInfinite()
129 || ( it.value().includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() )
130 || ( !currentMatchingVectorRange.includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() ) ) )
131 {
132 currentMatchingVectorGroup = it.key();
133 currentMatchingVectorRange = it.value();
134 }
135
136 if ( matchesScalarParentGroup && (
137 currentMatchingScalarRange.isInfinite()
138 || ( it.value().includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() )
139 || ( !currentMatchingScalarRange.includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() ) ) )
140 {
141 currentMatchingScalarGroup = it.key();
142 currentMatchingScalarRange = it.value();
143 }
144 }
145 }
146 if ( currentMatchingVectorGroup >= 0 )
147 mRendererSettings.setActiveVectorDatasetGroup( currentMatchingVectorGroup );
148 if ( currentMatchingScalarGroup >= 0 )
149 mRendererSettings.setActiveScalarDatasetGroup( currentMatchingScalarGroup );
150 }
151 }
152 }
153 }
154
155 // copy triangular mesh
156 copyTriangularMeshes( layer, context );
157
158 // copy datasets
159 copyScalarDatasetValues( layer );
160 copyVectorDatasetValues( layer );
161
162 calculateOutputSize();
163
164 QSet<QString> attrs;
165 prepareLabeling( layer, attrs );
166
168
170 {
171 const QgsMeshDatasetIndex activeDatasetIndex = layer->activeScalarDatasetIndex( context );
172
173 if ( activeDatasetIndex.isValid() )
174 {
175 QgsMeshRendererScalarSettings scalarRendererSettings = mRendererSettings.scalarSettings( activeDatasetIndex.group() );
176 const double previousMin = scalarRendererSettings.classificationMinimum();
177 const double previousMax = scalarRendererSettings.classificationMaximum();
178
179 if ( scalarRendererSettings.extent() == Qgis::MeshRangeExtent::UpdatedCanvas && scalarRendererSettings.limits() == Qgis::MeshRangeLimit::MinimumMaximum )
180 {
181 double min, max;
182
183 const bool found = layer->minimumMaximumActiveScalarDataset( context.extent(), activeDatasetIndex, min, max );
184
185 if ( found )
186 {
187 if ( previousMin != min || previousMax != max )
188 {
189 scalarRendererSettings.setClassificationMinimumMaximum( min, max );
190 mRendererSettings.setScalarSettings( activeDatasetIndex.group(), scalarRendererSettings );
191
192 QgsRenderedLayerStatistics *layerStatistics = new QgsRenderedLayerStatistics( layer->id(), previousMin, previousMax );
193
194 layerStatistics->setBoundingBox( context.extent() );
195 layerStatistics->setMaximum( 0, max );
196 layerStatistics->setMinimum( 0, min );
197
198 appendRenderedItemDetails( layerStatistics );
199 }
200 }
201 }
202 }
203 }
204
205 mPreparationTime = timer.elapsed();
206}
207
208void QgsMeshLayerRenderer::copyTriangularMeshes( QgsMeshLayer *layer, QgsRenderContext &context )
209{
210 // handle level of details of mesh
211 const QgsMeshSimplificationSettings simplificationSettings = layer->meshSimplificationSettings();
212 if ( simplificationSettings.isEnabled() )
213 {
214 const double triangleSize = simplificationSettings.meshResolution() * context.mapToPixel().mapUnitsPerPixel();
215 mTriangularMesh = *( layer->triangularMesh( triangleSize ) );
216 mIsMeshSimplificationActive = true;
217 }
218 else
219 {
220 mTriangularMesh = *( layer->triangularMesh() );
221 }
222}
223
225{
226 return mFeedback.get();
227}
228
229void QgsMeshLayerRenderer::calculateOutputSize()
230{
231 // figure out image size
232 const QgsRenderContext &context = *renderContext();
233 const QgsRectangle extent = context.mapExtent();
234 const QgsMapToPixel mapToPixel = context.mapToPixel();
235 const QgsRectangle screenBBox = QgsMeshLayerUtils::boundingBoxToScreenRectangle( mapToPixel, extent, context.devicePixelRatio() );
236 const int width = int( screenBBox.width() );
237 const int height = int( screenBBox.height() );
238 mOutputSize = QSize( width, height );
239}
240
241void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer )
242{
243 QgsMeshDatasetIndex datasetIndex;
244 if ( renderContext()->isTemporal() )
245 datasetIndex = layer->activeScalarDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeScalarDatasetGroup() );
246 else
247 datasetIndex = layer->staticScalarDatasetIndex( mRendererSettings.activeScalarDatasetGroup() );
248
249 // Find out if we can use cache up to date. If yes, use it and return
250 const int datasetGroupCount = layer->datasetGroupCount();
251 const QgsMeshRendererScalarSettings::DataResamplingMethod method = mRendererSettings.scalarSettings( datasetIndex.group() ).dataResamplingMethod();
252 QgsMeshLayerRendererCache *cache = layer->rendererCache();
253 if ( ( cache->mDatasetGroupsCount == datasetGroupCount )
254 && ( cache->mActiveScalarDatasetIndex == datasetIndex )
255 && ( cache->mDataInterpolationMethod == method )
256 && ( QgsMesh3DAveragingMethod::equals( cache->mScalarAveragingMethod.get(), mRendererSettings.averagingMethod() ) ) )
257 {
258 mScalarDatasetValues = cache->mScalarDatasetValues;
259 mScalarActiveFaceFlagValues = cache->mScalarActiveFaceFlagValues;
260 mScalarDataType = cache->mScalarDataType;
261 mScalarDatasetMinimum = cache->mScalarDatasetMinimum;
262 mScalarDatasetMaximum = cache->mScalarDatasetMaximum;
263 return;
264 }
265
266 // Cache is not up-to-date, gather data
267 if ( datasetIndex.isValid() )
268 {
269 const QgsMeshDatasetGroupMetadata metadata = layer->datasetGroupMetadata( datasetIndex.group() );
270 mScalarDataType = QgsMeshLayerUtils::datasetValuesType( metadata.dataType() );
271
272 // populate scalar values
273 const int count = QgsMeshLayerUtils::datasetValuesCount( &mNativeMesh, mScalarDataType );
274 const QgsMeshDataBlock vals = QgsMeshLayerUtils::datasetValues( layer, datasetIndex, 0, count );
275
276 if ( vals.isValid() )
277 {
278 // vals could be scalar or vectors, for contour rendering we want always magnitude
279 mScalarDatasetValues = QgsMeshLayerUtils::calculateMagnitudes( vals );
280 }
281 else
282 {
283 mScalarDatasetValues = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
284 }
285
286 // populate face active flag, always defined on faces
287 mScalarActiveFaceFlagValues = layer->areFacesActive( datasetIndex, 0, mNativeMesh.faces.count() );
288
289 // for data on faces, there could be request to interpolate the data to vertices
291 {
293 {
295 mScalarDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData( mScalarDatasetValues, &mNativeMesh, &mTriangularMesh, &mScalarActiveFaceFlagValues, method );
296 }
298 {
300 mScalarDatasetValues = QgsMeshLayerUtils::resampleFromVerticesToFaces( mScalarDatasetValues, &mNativeMesh, &mTriangularMesh, &mScalarActiveFaceFlagValues, method );
301 }
302 }
303
304 const QgsMeshDatasetMetadata datasetMetadata = layer->datasetMetadata( datasetIndex );
305 mScalarDatasetMinimum = datasetMetadata.minimum();
306 mScalarDatasetMaximum = datasetMetadata.maximum();
307 }
308
309 // update cache
310 cache->mDatasetGroupsCount = datasetGroupCount;
311 cache->mActiveScalarDatasetIndex = datasetIndex;
312 cache->mDataInterpolationMethod = method;
313 cache->mScalarDatasetValues = mScalarDatasetValues;
314 cache->mScalarActiveFaceFlagValues = mScalarActiveFaceFlagValues;
315 cache->mScalarDataType = mScalarDataType;
316 cache->mScalarDatasetMinimum = mScalarDatasetMinimum;
317 cache->mScalarDatasetMaximum = mScalarDatasetMaximum;
318 cache->mScalarAveragingMethod.reset( mRendererSettings.averagingMethod() ? mRendererSettings.averagingMethod()->clone() : nullptr );
319}
320
321
322void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer )
323{
324 QgsMeshDatasetIndex datasetIndex;
325 if ( renderContext()->isTemporal() )
326 datasetIndex = layer->activeVectorDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeVectorDatasetGroup() );
327 else
328 datasetIndex = layer->staticVectorDatasetIndex( mRendererSettings.activeVectorDatasetGroup() );
329
330 // Find out if we can use cache up to date. If yes, use it and return
331 const int datasetGroupCount = layer->datasetGroupCount();
332 QgsMeshLayerRendererCache *cache = layer->rendererCache();
333 if ( ( cache->mDatasetGroupsCount == datasetGroupCount )
334 && ( cache->mActiveVectorDatasetIndex == datasetIndex )
335 && ( QgsMesh3DAveragingMethod::equals( cache->mVectorAveragingMethod.get(), mRendererSettings.averagingMethod() ) ) )
336 {
337 mVectorDatasetValues = cache->mVectorDatasetValues;
338 mVectorDatasetValuesMag = cache->mVectorDatasetValuesMag;
339 mVectorDatasetMagMinimum = cache->mVectorDatasetMagMinimum;
340 mVectorDatasetMagMaximum = cache->mVectorDatasetMagMaximum;
341 mVectorDatasetGroupMagMinimum = cache->mVectorDatasetMagMinimum;
342 mVectorDatasetGroupMagMaximum = cache->mVectorDatasetMagMaximum;
343 mVectorActiveFaceFlagValues = cache->mVectorActiveFaceFlagValues;
344 mVectorDataType = cache->mVectorDataType;
345 return;
346 }
347
348 // Cache is not up-to-date, gather data
349 if ( datasetIndex.isValid() )
350 {
351 const QgsMeshDatasetGroupMetadata metadata = layer->datasetGroupMetadata( datasetIndex );
352
353 const bool isScalar = metadata.isScalar();
354 if ( isScalar )
355 {
356 QgsDebugError( u"Dataset has no vector values"_s );
357 }
358 else
359 {
360 mVectorDataType = QgsMeshLayerUtils::datasetValuesType( metadata.dataType() );
361
364
365 const int count = QgsMeshLayerUtils::datasetValuesCount( &mNativeMesh, mVectorDataType );
366 mVectorDatasetValues = QgsMeshLayerUtils::datasetValues( layer, datasetIndex, 0, count );
367
368 if ( mVectorDatasetValues.isValid() )
369 mVectorDatasetValuesMag = QgsMeshLayerUtils::calculateMagnitudes( mVectorDatasetValues );
370 else
371 mVectorDatasetValuesMag = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
372
373 // populate face active flag
374 mVectorActiveFaceFlagValues = layer->areFacesActive( datasetIndex, 0, mNativeMesh.faces.count() );
375
376 const QgsMeshDatasetMetadata datasetMetadata = layer->datasetMetadata( datasetIndex );
377 mVectorDatasetMagMinimum = datasetMetadata.minimum();
378 mVectorDatasetMagMaximum = datasetMetadata.maximum();
379 }
380 }
381
382 // update cache
383 cache->mDatasetGroupsCount = datasetGroupCount;
384 cache->mActiveVectorDatasetIndex = datasetIndex;
385 cache->mVectorDatasetValues = mVectorDatasetValues;
386 cache->mVectorDatasetValuesMag = mVectorDatasetValuesMag;
387 cache->mVectorDatasetMagMinimum = mVectorDatasetMagMinimum;
388 cache->mVectorDatasetMagMaximum = mVectorDatasetMagMaximum;
389 cache->mVectorDatasetGroupMagMinimum = mVectorDatasetMagMinimum;
390 cache->mVectorDatasetGroupMagMaximum = mVectorDatasetMagMaximum;
391 cache->mVectorActiveFaceFlagValues = mVectorActiveFaceFlagValues;
392 cache->mVectorDataType = mVectorDataType;
393 cache->mVectorAveragingMethod.reset( mRendererSettings.averagingMethod() ? mRendererSettings.averagingMethod()->clone() : nullptr );
394}
395
397{
398 QgsScopedThreadName threadName( u"render:%1"_s.arg( mLayerName ) );
399
400 std::unique_ptr< QgsScopedRuntimeProfile > profile;
401 std::unique_ptr< QgsScopedRuntimeProfile > preparingProfile;
402 if ( mEnableProfile )
403 {
404 profile = std::make_unique< QgsScopedRuntimeProfile >( mLayerName, u"rendering"_s, layerId() );
405 if ( mPreparationTime > 0 )
406 QgsApplication::profiler()->record( QObject::tr( "Create renderer" ), mPreparationTime / 1000.0, u"rendering"_s );
407 preparingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Preparing render" ), u"rendering"_s );
408 }
409
410 mReadyToCompose = false;
411 const QgsScopedQPainterState painterState( renderContext()->painter() );
412 if ( !mClippingRegions.empty() )
413 {
414 bool needsPainterClipPath = false;
415 const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), Qgis::LayerType::Mesh, needsPainterClipPath );
416 if ( needsPainterClipPath )
417 renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
418 }
419
420 preparingProfile.reset();
421
422 renderScalarDataset();
423 mReadyToCompose = true;
424 renderMesh();
425 renderVectorDataset();
426
427 registerLabelFeatures();
428
429 return !renderContext()->renderingStopped();
430}
431
433{
434 switch ( renderContext()->rasterizedRenderingPolicy() )
435 {
438 break;
439
441 return false;
442 }
443
444 return !qgsDoubleNear( mLayerOpacity, 1.0 );
445}
446
447void QgsMeshLayerRenderer::renderMesh()
448{
450 return;
451
452 std::unique_ptr< QgsScopedRuntimeProfile > renderProfile;
453 if ( mEnableProfile )
454 {
455 renderProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering mesh" ), u"rendering"_s );
456 }
457
458 // triangular mesh
459 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( renderContext()->mapExtent() );
460 if ( mRendererSettings.triangularMeshSettings().isEnabled() )
461 {
462 renderFaceMesh( mRendererSettings.triangularMeshSettings(), mTriangularMesh.triangles(), trianglesInExtent );
463 }
464
465 // native mesh
466 if ( ( mRendererSettings.nativeMeshSettings().isEnabled() || mIsEditable ) && mTriangularMesh.levelOfDetail() == 0 )
467 {
468 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent, mTriangularMesh.trianglesToNativeFaces() );
469
470 renderFaceMesh( mRendererSettings.nativeMeshSettings(), mNativeMesh.faces, nativeFacesInExtent.values() );
471 }
472
473 // edge mesh
474 if ( mRendererSettings.edgeMeshSettings().isEnabled() )
475 {
476 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( renderContext()->mapExtent() );
477 renderEdgeMesh( mRendererSettings.edgeMeshSettings(), edgesInExtent );
478 }
479}
480
481static QPainter *_painterForMeshFrame( QgsRenderContext &context, const QgsMeshRendererMeshSettings &settings )
482{
483 // Set up the render configuration options
484 QPainter *painter = context.painter();
485
486 context.setPainterFlagsUsingContext( painter );
487
488 QPen pen = painter->pen();
489 pen.setCapStyle( Qt::FlatCap );
490 pen.setJoinStyle( Qt::MiterJoin );
491
492 const double penWidth = context.convertToPainterUnits( settings.lineWidth(), settings.lineWidthUnit() );
493 pen.setWidthF( penWidth );
494 pen.setColor( settings.color() );
495 painter->setPen( pen );
496 return painter;
497}
498
499void QgsMeshLayerRenderer::renderEdgeMesh( const QgsMeshRendererMeshSettings &settings, const QList<int> &edgesInExtent )
500{
501 Q_ASSERT( settings.isEnabled() );
502
504 return;
505
506 QgsRenderContext &context = *renderContext();
507
508 QgsScopedQPainterState painterState( context.painter() );
509 QPainter *painter = _painterForMeshFrame( context, settings );
510
511 const QVector<QgsMeshEdge> edges = mTriangularMesh.edges();
512 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
513
514 for ( const int i : edgesInExtent )
515 {
516 if ( context.renderingStopped() )
517 break;
518
519 if ( i >= edges.size() )
520 continue;
521
522 const QgsMeshEdge &edge = edges[i];
523 const int startVertexIndex = edge.first;
524 const int endVertexIndex = edge.second;
525
526 if ( ( startVertexIndex >= vertices.size() ) || endVertexIndex >= vertices.size() )
527 continue;
528
529 const QgsMeshVertex &startVertex = vertices[startVertexIndex];
530 const QgsMeshVertex &endVertex = vertices[endVertexIndex];
531 const QgsPointXY lineStart = context.mapToPixel().transform( startVertex.x(), startVertex.y() );
532 const QgsPointXY lineEnd = context.mapToPixel().transform( endVertex.x(), endVertex.y() );
533 painter->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
534 }
535};
536
537void QgsMeshLayerRenderer::renderFaceMesh( const QgsMeshRendererMeshSettings &settings, const QVector<QgsMeshFace> &faces, const QList<int> &facesInExtent )
538{
539 Q_ASSERT( settings.isEnabled() || mIsEditable );
540
542 return;
543
544 QgsRenderContext &context = *renderContext();
545 QgsScopedQPainterState painterState( context.painter() );
546 QPainter *painter = _painterForMeshFrame( context, settings );
547
548 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices(); //Triangular mesh vertices contains also native mesh vertices
549 QSet<QPair<int, int>> drawnEdges;
550
551 for ( const int i : facesInExtent )
552 {
553 if ( context.renderingStopped() )
554 break;
555
556 if ( i < 0 || i >= faces.count() )
557 continue;
558
559 const QgsMeshFace &face = faces[i];
560 if ( face.size() < 2 )
561 continue;
562
563 for ( int j = 0; j < face.size(); ++j )
564 {
565 const int startVertexId = face[j];
566 const int endVertexId = face[( j + 1 ) % face.size()];
567 const QPair<int, int> thisEdge( startVertexId, endVertexId );
568 const QPair<int, int> thisEdgeReversed( endVertexId, startVertexId );
569 if ( drawnEdges.contains( thisEdge ) || drawnEdges.contains( thisEdgeReversed ) )
570 continue;
571 drawnEdges.insert( thisEdge );
572 drawnEdges.insert( thisEdgeReversed );
573
574 const QgsMeshVertex &startVertex = vertices[startVertexId];
575 const QgsMeshVertex &endVertex = vertices[endVertexId];
576 const QgsPointXY lineStart = context.mapToPixel().transform( startVertex.x(), startVertex.y() );
577 const QgsPointXY lineEnd = context.mapToPixel().transform( endVertex.x(), endVertex.y() );
578 painter->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
579 }
580 }
581}
582
583void QgsMeshLayerRenderer::renderScalarDataset()
584{
585 if ( mScalarDatasetValues.isEmpty() )
586 return; // activeScalarDataset == NO_ACTIVE_MESH_DATASET
587
588 if ( std::isnan( mScalarDatasetMinimum ) || std::isnan( mScalarDatasetMaximum ) )
589 return; // only NODATA values
590
591 const int groupIndex = mRendererSettings.activeScalarDatasetGroup();
592 if ( groupIndex < 0 )
593 return; // no shader
594
595 std::unique_ptr< QgsScopedRuntimeProfile > renderProfile;
596 if ( mEnableProfile )
597 {
598 renderProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering scalar datasets" ), u"rendering"_s );
599 }
600
601 const QgsMeshRendererScalarSettings scalarSettings = mRendererSettings.scalarSettings( groupIndex );
602
604 {
605 renderScalarDatasetOnFaces( scalarSettings );
606 }
607
609 {
610 renderScalarDatasetOnEdges( scalarSettings );
611 }
612}
613
614void QgsMeshLayerRenderer::renderScalarDatasetOnEdges( const QgsMeshRendererScalarSettings &scalarSettings )
615{
616 QgsRenderContext &context = *renderContext();
617 const QVector<QgsMeshEdge> edges = mTriangularMesh.edges();
618 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
619 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( context.mapExtent() );
620
621 QgsInterpolatedLineRenderer edgePlotter;
622 edgePlotter.setInterpolatedColor( QgsInterpolatedLineColor( scalarSettings.colorRampShader() ) );
623 edgePlotter.setInterpolatedWidth( QgsInterpolatedLineWidth( scalarSettings.edgeStrokeWidth() ) );
624 edgePlotter.setWidthUnit( scalarSettings.edgeStrokeWidthUnit() );
625
626 for ( const int i : edgesInExtent )
627 {
628 if ( context.renderingStopped() )
629 break;
630
631 if ( i >= edges.size() )
632 continue;
633
634 const QgsMeshEdge &edge = edges[i];
635 const int startVertexIndex = edge.first;
636 const int endVertexIndex = edge.second;
637
638 if ( ( startVertexIndex >= vertices.size() ) || endVertexIndex >= vertices.size() )
639 continue;
640
641 const QgsMeshVertex &startVertex = vertices[startVertexIndex];
642 const QgsMeshVertex &endVertex = vertices[endVertexIndex];
643
645 {
646 edgePlotter.render( mScalarDatasetValues[i], mScalarDatasetValues[i], startVertex, endVertex, context );
647 }
648 else
649 {
650 edgePlotter.render( mScalarDatasetValues[startVertexIndex], mScalarDatasetValues[endVertexIndex], startVertex, endVertex, context );
651 }
652 }
653}
654
655QColor QgsMeshLayerRenderer::colorAt( QgsColorRampShader *shader, double val ) const
656{
657 int r, g, b, a;
658 if ( shader->shade( val, &r, &g, &b, &a ) )
659 {
660 return QColor( r, g, b, a );
661 }
662 return QColor();
663}
664
665QgsPointXY QgsMeshLayerRenderer::fractionPoint( const QgsPointXY &p1, const QgsPointXY &p2, double fraction ) const
666{
667 const QgsPointXY pt( p1.x() + fraction * ( p2.x() - p1.x() ), p1.y() + fraction * ( p2.y() - p1.y() ) );
668 return pt;
669}
670
671void QgsMeshLayerRenderer::renderScalarDatasetOnFaces( const QgsMeshRendererScalarSettings &scalarSettings )
672{
673 QgsRenderContext &context = *renderContext();
674
675 QgsColorRampShader *fcn = new QgsColorRampShader( scalarSettings.colorRampShader() );
676 QgsRasterShader *sh = new QgsRasterShader();
677 sh->setRasterShaderFunction( fcn ); // takes ownership of fcn
678 QgsMeshLayerInterpolator interpolator( mTriangularMesh, mScalarDatasetValues, mScalarActiveFaceFlagValues, mScalarDataType, context, mOutputSize );
679 interpolator.setSpatialIndexActive( mIsMeshSimplificationActive );
680 interpolator.setElevationMapSettings( mRenderElevationMap, mElevationScale, mElevationOffset );
681 QgsSingleBandPseudoColorRenderer renderer( &interpolator, 0, sh ); // takes ownership of sh
682 renderer.setClassificationMin( scalarSettings.classificationMinimum() );
683 renderer.setClassificationMax( scalarSettings.classificationMaximum() );
684 renderer.setOpacity( scalarSettings.opacity() );
685
686 std::unique_ptr<QgsRasterBlock> bl( renderer.block( 0, context.mapExtent(), mOutputSize.width(), mOutputSize.height(), mFeedback.get() ) );
687 QImage img = bl->image();
688 img.setDevicePixelRatio( context.devicePixelRatio() );
689
690 context.painter()->drawImage( 0, 0, img );
691}
692
693void QgsMeshLayerRenderer::renderVectorDataset()
694{
695 const int groupIndex = mRendererSettings.activeVectorDatasetGroup();
696 if ( groupIndex < 0 )
697 return;
698
699 if ( !mVectorDatasetValues.isValid() )
700 return; // no data at all
701
702 if ( std::isnan( mVectorDatasetMagMinimum ) || std::isnan( mVectorDatasetMagMaximum ) )
703 return; // only NODATA values
704
705 if ( !( mVectorDatasetMagMaximum > 0 ) )
706 return; //all vector are null vector
707
708 std::unique_ptr< QgsScopedRuntimeProfile > renderProfile;
709 if ( mEnableProfile )
710 {
711 renderProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering vector datasets" ), u"rendering"_s );
712 }
713
714 std::unique_ptr<QgsMeshVectorRenderer> renderer(
715 QgsMeshVectorRenderer::makeVectorRenderer(
723 mRendererSettings.vectorSettings( groupIndex ),
724 *renderContext(),
726 mFeedback.get(),
728 )
729 );
730
731 if ( renderer )
732 renderer->draw();
733}
734
735void QgsMeshLayerRenderer::prepareLabeling( QgsMeshLayer *layer, QSet<QString> &attributeNames )
736{
737 QgsRenderContext &context = *renderContext();
738
739 if ( QgsLabelingEngine *engine = context.labelingEngine() )
740 {
741 if ( layer->labelsEnabled() )
742 {
743 mLabelProvider = layer->labeling()->provider( layer );
744 if ( mLabelProvider )
745 {
746 auto c = context.expressionContext();
747
749 c.lastScope()->setVariable( u"_native_mesh"_s, QVariant::fromValue( mNativeMesh ) );
750 context.setExpressionContext( c );
751
752 engine->addProvider( mLabelProvider );
753 if ( !mLabelProvider->prepare( context, attributeNames ) )
754 {
755 engine->removeProvider( mLabelProvider );
756 mLabelProvider = nullptr; // deleted by engine
757 }
758 }
759 }
760 }
761}
762
763void QgsMeshLayerRenderer::registerLabelFeatures()
764{
765 if ( !mLabelProvider )
766 return;
767
768 QgsRenderContext &context = *renderContext();
769
770 QgsExpressionContextScope *scope = context.expressionContext().activeScopeForVariable( u"_native_mesh"_s );
771
772 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( renderContext()->mapExtent() );
773
774 if ( mLabelProvider->labelFaces() )
775 {
777 return;
778 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent, mTriangularMesh.trianglesToNativeFaces() );
779
780 for ( const int i : nativeFacesInExtent )
781 {
782 if ( context.renderingStopped() )
783 break;
784
785 if ( i < 0 || i >= mNativeMesh.faces.count() )
786 continue;
787
788 scope->setVariable( u"_mesh_face_index"_s, i, false );
789
790 QgsFeature f( i );
791 QgsGeometry geom = QgsMeshUtils::toGeometry( mNativeMesh.face( i ), mNativeMesh.vertices );
792 f.setGeometry( geom );
793 mLabelProvider->registerFeature( f, context, QgsLabelFeatureDetails() );
794 }
795 }
796 else
797 {
799 return;
800 const QSet<int> nativeVerticesInExtent = QgsMeshUtils::nativeVerticesFromTriangles( trianglesInExtent, mTriangularMesh.triangles() );
801
802 for ( const int i : nativeVerticesInExtent )
803 {
804 if ( context.renderingStopped() )
805 break;
806
807 if ( i < 0 || i >= mNativeMesh.vertexCount() )
808 continue;
809
810 scope->setVariable( u"_mesh_vertex_index"_s, i, false );
811
812 QgsFeature f( i );
813 QgsGeometry geom = QgsGeometry( new QgsPoint( mNativeMesh.vertex( i ) ) );
814 f.setGeometry( geom );
815 mLabelProvider->registerFeature( f, context, QgsLabelFeatureDetails() );
816 }
817 }
818}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:62
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2917
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2918
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
Definition qgis.h:2919
@ MinimumMaximum
Real min-max values.
Definition qgis.h:6947
@ UpdatedCanvas
Constantly updated extent of the canvas is used to compute statistics.
Definition qgis.h:6960
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ RenderPreviewJob
Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering.
Definition qgis.h:2974
@ Render3DMap
Render is for a 3D map.
Definition qgis.h:2979
@ FromVertices
Elevation should be taken from mesh vertices.
Definition qgis.h:4300
@ FixedRangePerGroup
Layer has a fixed (manually specified) elevation range per group.
Definition qgis.h:4301
@ FixedElevationRange
Layer has a fixed elevation range.
Definition qgis.h:4299
virtual QgsMeshLayerLabelProvider * provider(QgsMeshLayer *layer) const
Factory for label provider implementation.
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
bool shade(double value, int *returnRedValue, int *returnGreenValue, int *returnBlueValue, int *returnAlphaValue) const override
Generates an new RGBA value based on one input value.
QgsRange which stores a range of double values.
Definition qgsrange.h:217
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:266
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
static QgsExpressionContextScope * meshExpressionScope(QgsMesh::ElementType elementType)
Creates a new scope which contains functions relating to mesh layer element elementType.
QgsExpressionContextScope * activeScopeForVariable(const QString &name)
Returns the currently active scope from the context for a specified variable name.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
void setInterpolatedColor(const QgsInterpolatedLineColor &strokeColoring)
Sets the stroke color used to render.
void setInterpolatedWidth(const QgsInterpolatedLineWidth &strokeWidth)
Sets the stroke width used to render.
void render(double value1, double value2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context) const
Renders a line in the context between point1 and point2 with color and width that vary depending on v...
void setWidthUnit(Qgis::RenderUnit strokeWidthUnit)
Sets the unit of the stroke width.
static QPainterPath calculatePainterClipRegion(const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, Qgis::LayerType layerType, bool &shouldClip)
Returns a QPainterPath representing the intersection of clipping regions from context which should be...
static QList< QgsMapClippingRegion > collectClippingRegionsForLayer(const QgsRenderContext &context, const QgsMapLayer *layer)
Collects the list of map clipping regions from a context which apply to a map layer.
double zScale() const
Returns the z scale, which is a scaling factor which should be applied to z values from the layer.
virtual bool hasElevation() const
Returns true if the layer has an elevation or z component.
double zOffset() const
Returns the z offset, which is a fixed offset amount which should be added to z values from the layer...
bool mReadyToCompose
The flag must be set to false in renderer's constructor if wants to use the smarter map redraws funct...
QString layerId() const
Gets access to the ID of the layer rendered by this class.
void appendRenderedItemDetails(QgsRenderedItemDetails *details)
Appends the details of a rendered item to the renderer.
virtual Qgis::MapLayerRendererFlags flags() const
Returns flags which control how the map layer rendering behaves.
QgsRenderContext * renderContext()
Returns the render context associated with the renderer.
QgsMapLayerRenderer(const QString &layerID, QgsRenderContext *context=nullptr)
Constructor for QgsMapLayerRenderer, with the associated layerID and render context.
QString id
Definition qgsmaplayer.h:86
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
static bool equals(const QgsMesh3DAveragingMethod *a, const QgsMesh3DAveragingMethod *b)
Returns whether two methods equal.
bool isValid() const
Whether the block is valid.
bool isScalar() const
Returns whether dataset group has scalar data.
DataType dataType() const
Returns whether dataset group data is defined on vertices or faces or volumes.
double minimum() const
Returns minimum scalar value/vector magnitude present for whole dataset group.
double maximum() const
Returns maximum scalar value/vector magnitude present for whole dataset group.
@ DataOnEdges
Data is defined on edges.
@ DataOnFaces
Data is defined on faces.
@ DataOnVertices
Data is defined on vertices.
An index that identifies the dataset group (e.g.
bool isValid() const
Returns whether index is valid, ie at least groups is set.
int group() const
Returns a group index.
double maximum() const
Returns maximum scalar value/vector magnitude present for the dataset.
double minimum() const
Returns minimum scalar value/vector magnitude present for the dataset.
Mesh layer specific subclass of QgsMapLayerElevationProperties.
Qgis::MeshElevationMode mode() const
Returns the elevation mode.
QMap< int, QgsDoubleRange > fixedRangePerGroup() const
Returns the fixed elevation range for each group.
QgsMeshDatasetGroupMetadata::DataType mScalarDataType
QgsTriangularMesh mTriangularMesh
QgsMeshLayerRenderer(QgsMeshLayer *layer, QgsRenderContext &context)
Ctor.
QVector< double > mScalarDatasetValues
std::unique_ptr< QgsMeshLayerRendererFeedback > mFeedback
feedback class for cancellation
QgsMeshDataBlock mScalarActiveFaceFlagValues
bool render() override
Do the rendering (based on data stored in the class).
QVector< double > mVectorDatasetValuesMag
QgsMeshRendererSettings mRendererSettings
bool forceRasterRender() const override
Returns true if the renderer must be rendered to a raster paint device (e.g.
QgsMeshDatasetGroupMetadata::DataType mVectorDataType
QList< QgsMapClippingRegion > mClippingRegions
QgsFeedback * feedback() const override
Access to feedback object of the layer renderer (may be nullptr).
QgsMeshDataBlock mVectorActiveFaceFlagValues
QgsMeshDataBlock mVectorDatasetValues
Represents a mesh layer supporting display of data on structured or unstructured meshes.
QgsRectangle extent() const override
Returns the extent of the layer.
QgsMeshDatasetIndex activeScalarDatasetIndex(QgsRenderContext &rendererContext)
Returns current active scalar dataset index for current renderer context.
QgsMeshDatasetIndex activeVectorDatasetAtTime(const QgsDateTimeRange &timeRange, int group=-1) const
Returns dataset index from active vector group depending on the time range If the temporal properties...
int datasetGroupCount() const
Returns the dataset groups count handle by the layer.
const QgsAbstractMeshLayerLabeling * labeling() const
Access to const labeling configuration.
QgsMeshDatasetIndex staticVectorDatasetIndex(int group=-1) const
Returns the static vector dataset index that is rendered if the temporal properties is not active.
QgsMesh * nativeMesh()
Returns native mesh (nullptr before rendering or calling to updateMesh).
bool minimumMaximumActiveScalarDataset(const QgsRectangle &extent, const QgsMeshDatasetIndex &datasetIndex, double &min, double &max)
Extracts minimum and maximum value for active scalar dataset on mesh faces.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
QgsMeshSimplificationSettings meshSimplificationSettings() const
Returns mesh simplification settings.
QgsMeshDataBlock areFacesActive(const QgsMeshDatasetIndex &index, int faceIndex, int count) const
Returns whether the faces are active for particular dataset.
QgsMeshDatasetMetadata datasetMetadata(const QgsMeshDatasetIndex &index) const
Returns the dataset metadata.
QgsMeshDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
QgsMeshDatasetIndex activeScalarDatasetAtTime(const QgsDateTimeRange &timeRange, int group=-1) const
Returns dataset index from active scalar group depending on the time range.
QgsTriangularMesh * triangularMesh(double minimumTriangleSize=0) const
Returns triangular mesh (nullptr before rendering or calling to updateMesh).
QgsMeshDatasetIndex staticScalarDatasetIndex(int group=-1) const
Returns the static scalar dataset index that is rendered if the temporal properties is not active.
QgsMeshDatasetGroupMetadata datasetGroupMetadata(const QgsMeshDatasetIndex &index) const
Returns the dataset groups metadata.
QgsMeshLayerRendererCache * rendererCache()
Returns native mesh (nullptr before rendering).
Represents a mesh renderer settings for mesh objects.
QColor color() const
Returns color used for rendering.
double lineWidth() const
Returns line width used for rendering (in millimeters).
Qgis::RenderUnit lineWidthUnit() const
Returns units of the width of the mesh frame.
bool isEnabled() const
Returns whether mesh structure rendering is enabled.
Represents a mesh renderer settings for scalar datasets.
void setClassificationMinimumMaximum(double minimum, double maximum)
Sets min/max values used for creation of the color ramp shader.
Qgis::MeshRangeExtent extent() const
Returns the mesh extent for minimum maximum calculation.
double opacity() const
Returns opacity.
Qgis::MeshRangeLimit limits() const
Returns the range limits type for minimum maximum calculation.
QgsColorRampShader colorRampShader() const
Returns color ramp shader function.
double classificationMinimum() const
Returns min value used for creation of the color ramp shader.
DataResamplingMethod
Resampling of value from dataset.
@ NoResampling
Does not use resampling.
Qgis::RenderUnit edgeStrokeWidthUnit() const
Returns the stroke width unit used to render edges scalar dataset.
double classificationMaximum() const
Returns max value used for creation of the color ramp shader.
QgsInterpolatedLineWidth edgeStrokeWidth() const
Returns the stroke width used to render edges scalar dataset.
QgsMeshRendererMeshSettings edgeMeshSettings() const
Returns edge mesh renderer settings.
QgsMeshRendererMeshSettings nativeMeshSettings() const
Returns native mesh renderer settings.
QgsMeshRendererMeshSettings triangularMeshSettings() const
Returns triangular mesh renderer settings.
Represents an overview renderer settings.
bool isEnabled() const
Returns if the overview is active.
int meshResolution() const
Returns the mesh resolution i.e., the minimum size (average) of triangles in pixels.
static QgsGeometry toGeometry(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns face as polygon geometry.
static QSet< int > nativeVerticesFromTriangles(const QList< int > &triangleIndexes, const QVector< QgsMeshFace > &triangles)
Returns unique native vertex indexes from list of vertices of triangles.
static QSet< int > nativeFacesFromTriangles(const QList< int > &triangleIndexes, const QVector< int > &trianglesToNativeFaces)
Returns unique native faces indexes from list of triangle indexes.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:168
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
bool includeUpper() const
Returns true if the upper bound is inclusive, or false if the upper bound is exclusive.
Definition qgsrange.h:102
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:86
void setRasterShaderFunction(QgsRasterShaderFunction *function)
A public method that allows the user to set their own shader function.
A rectangle specified with double values.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
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 ...
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
float devicePixelRatio() const
Returns the device pixel ratio.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QgsDoubleRange zRange() const
Returns the range of z-values which should be rendered.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QgsLabelingEngine * labelingEngine() const
Gets access to new labeling engine (may be nullptr).
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
void setBoundingBox(const QgsRectangle &bounds)
Sets the bounding box of the item (in map units).
Contains computed statistics for a layer render.
void setMaximum(QList< double > &maximum)
Sets the maximum values of the computed statistics.
void setMinimum(QList< double > &minimum)
Sets the minimum values of the computed statistics.
void record(const QString &name, double time, const QString &group="startup", const QString &id=QString())
Manually adds a profile event with the given name and total time (in seconds).
Scoped object for saving and restoring a QPainter object's state.
Scoped object for setting the current thread name.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7725
#define QgsDebugError(str)
Definition qgslogger.h:71
QVector< int > QgsMeshFace
List of vertex indexes.
QPair< int, int > QgsMeshEdge
Edge is a straight line seqment between 2 points.
QgsPoint QgsMeshVertex
xyz coords of vertex