QGIS API Documentation 3.99.0-Master (d270888f95f)
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 "qgspointxy.h"
37#include "qgsrastershader.h"
39#include "qgsruntimeprofiler.h"
41#include "qgsthreadingutils.h"
42
43#include <QBrush>
44#include <QElapsedTimer>
45#include <QLinearGradient>
46#include <QPair>
47#include <QPointer>
48#include <QSet>
49#include <QString>
50
51#include "moc_qgsmeshlayerrenderer.cpp"
52
53using namespace Qt::StringLiterals;
54
56 QgsMeshLayer *layer,
57 QgsRenderContext &context )
58 : QgsMapLayerRenderer( layer->id(), &context )
59 , mIsEditable( layer->isEditable() )
60 , mLayerName( layer->name() )
61 , mFeedback( new QgsMeshLayerRendererFeedback )
62 , mRendererSettings( layer->rendererSettings() )
63 , mEnableProfile( context.flags() & Qgis::RenderContextFlag::RecordProfile )
64 , mLayerOpacity( layer->opacity() )
65{
66 QElapsedTimer timer;
67 timer.start();
68
69 // make copies for mesh data
70 // cppcheck-suppress assertWithSideEffect
71 Q_ASSERT( layer->nativeMesh() );
72 // cppcheck-suppress assertWithSideEffect
73 Q_ASSERT( layer->triangularMesh() );
74 // cppcheck-suppress assertWithSideEffect
75 Q_ASSERT( layer->rendererCache() );
76 // cppcheck-suppress assertWithSideEffect
77 Q_ASSERT( layer->dataProvider() );
78
79 mReadyToCompose = false;
80
81 // copy native mesh
82 mNativeMesh = *( layer->nativeMesh() );
83 mLayerExtent = layer->extent();
84
85 if ( layer->elevationProperties() && layer->elevationProperties()->hasElevation() )
86 {
87 QgsMeshLayerElevationProperties *elevProp = qobject_cast<QgsMeshLayerElevationProperties *>( layer->elevationProperties() );
88
90 mElevationScale = elevProp->zScale();
91 mElevationOffset = elevProp->zOffset();
92
93 if ( !context.zRange().isInfinite() )
94 {
95 switch ( elevProp->mode() )
96 {
98 // don't need to handle anything here -- the layer renderer will never be created if the
99 // render context range doesn't match the layer's fixed elevation range
100 break;
101
103 {
104 // TODO -- filtering by mesh z values is not currently implemented
105 break;
106 }
107
109 {
110 // find the top-most group which matches the map range and parent group
111 int currentMatchingVectorGroup = -1;
112 int currentMatchingScalarGroup = -1;
113 QgsDoubleRange currentMatchingVectorRange;
114 QgsDoubleRange currentMatchingScalarRange;
115
116 const QMap<int, QgsDoubleRange > rangePerGroup = elevProp->fixedRangePerGroup();
117
118 const int activeVectorDatasetGroup = mRendererSettings.activeVectorDatasetGroup();
119 const int activeScalarDatasetGroup = mRendererSettings.activeScalarDatasetGroup();
120
121 for ( auto it = rangePerGroup.constBegin(); it != rangePerGroup.constEnd(); ++it )
122 {
123 if ( it.value().overlaps( context.zRange() ) )
124 {
125 const bool matchesVectorParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeVectorDatasetGroup ), QgsMeshDatasetIndex( it.key() ) );
126 const bool matchesScalarParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeScalarDatasetGroup ), QgsMeshDatasetIndex( it.key() ) );
127
128 if ( matchesVectorParentGroup && (
129 currentMatchingVectorRange.isInfinite()
130 || ( it.value().includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() )
131 || ( !currentMatchingVectorRange.includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() ) ) )
132 {
133 currentMatchingVectorGroup = it.key();
134 currentMatchingVectorRange = it.value();
135 }
136
137 if ( matchesScalarParentGroup && (
138 currentMatchingScalarRange.isInfinite()
139 || ( it.value().includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() )
140 || ( !currentMatchingScalarRange.includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() ) ) )
141 {
142 currentMatchingScalarGroup = it.key();
143 currentMatchingScalarRange = it.value();
144 }
145 }
146 }
147 if ( currentMatchingVectorGroup >= 0 )
148 mRendererSettings.setActiveVectorDatasetGroup( currentMatchingVectorGroup );
149 if ( currentMatchingScalarGroup >= 0 )
150 mRendererSettings.setActiveScalarDatasetGroup( currentMatchingScalarGroup );
151 }
152 }
153 }
154 }
155
156 // copy triangular mesh
157 copyTriangularMeshes( layer, context );
158
159 // copy datasets
160 copyScalarDatasetValues( layer );
161 copyVectorDatasetValues( layer );
162
163 calculateOutputSize();
164
165 QSet<QString> attrs;
166 prepareLabeling( layer, attrs );
167
169
171 && !( context.flags() & Qgis::RenderContextFlag::Render3DMap ) )
172 {
173 const QgsMeshDatasetIndex activeDatasetIndex = layer->activeScalarDatasetIndex( context );
174
175 if ( activeDatasetIndex.isValid() )
176 {
177 QgsMeshRendererScalarSettings scalarRendererSettings = mRendererSettings.scalarSettings( activeDatasetIndex.group() );
178 const double previousMin = scalarRendererSettings.classificationMinimum();
179 const double previousMax = scalarRendererSettings.classificationMaximum();
180
181 if ( scalarRendererSettings.extent() == Qgis::MeshRangeExtent::UpdatedCanvas &&
182 scalarRendererSettings.limits() == Qgis::MeshRangeLimit::MinimumMaximum )
183 {
184 double min, max;
185
186 const bool found = layer->minimumMaximumActiveScalarDataset( context.extent(), activeDatasetIndex, min, max );
187
188 if ( found )
189 {
190 if ( previousMin != min || previousMax != max )
191 {
192
193 scalarRendererSettings.setClassificationMinimumMaximum( min, max );
194 mRendererSettings.setScalarSettings( activeDatasetIndex.group(), scalarRendererSettings );
195
196 QgsRenderedLayerStatistics *layerStatistics = new QgsRenderedLayerStatistics( layer->id(), previousMin, previousMax );
197
198 layerStatistics->setBoundingBox( context.extent() );
199 layerStatistics->setMaximum( 0, max );
200 layerStatistics->setMinimum( 0, min );
201
202 appendRenderedItemDetails( layerStatistics );
203 }
204 }
205 }
206 }
207 }
208
209 mPreparationTime = timer.elapsed();
210}
211
212void QgsMeshLayerRenderer::copyTriangularMeshes( QgsMeshLayer *layer, QgsRenderContext &context )
213{
214 // handle level of details of mesh
215 const QgsMeshSimplificationSettings simplificationSettings = layer->meshSimplificationSettings();
216 if ( simplificationSettings.isEnabled() )
217 {
218 const double triangleSize = simplificationSettings.meshResolution() * context.mapToPixel().mapUnitsPerPixel();
219 mTriangularMesh = *( layer->triangularMesh( triangleSize ) );
220 mIsMeshSimplificationActive = true;
221 }
222 else
223 {
224 mTriangularMesh = *( layer->triangularMesh() );
225 }
226}
227
229{
230 return mFeedback.get();
231}
232
233void QgsMeshLayerRenderer::calculateOutputSize()
234{
235 // figure out image size
236 const QgsRenderContext &context = *renderContext();
237 const QgsRectangle extent = context.mapExtent();
238 const QgsMapToPixel mapToPixel = context.mapToPixel();
239 const QgsRectangle screenBBox = QgsMeshLayerUtils::boundingBoxToScreenRectangle( mapToPixel, extent, context.devicePixelRatio() );
240 const int width = int( screenBBox.width() );
241 const int height = int( screenBBox.height() );
242 mOutputSize = QSize( width, height );
243}
244
245void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer )
246{
247 QgsMeshDatasetIndex datasetIndex;
248 if ( renderContext()->isTemporal() )
249 datasetIndex = layer->activeScalarDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeScalarDatasetGroup() );
250 else
251 datasetIndex = layer->staticScalarDatasetIndex( mRendererSettings.activeScalarDatasetGroup() );
252
253 // Find out if we can use cache up to date. If yes, use it and return
254 const int datasetGroupCount = layer->datasetGroupCount();
255 const QgsMeshRendererScalarSettings::DataResamplingMethod method = mRendererSettings.scalarSettings( datasetIndex.group() ).dataResamplingMethod();
256 QgsMeshLayerRendererCache *cache = layer->rendererCache();
257 if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) &&
258 ( cache->mActiveScalarDatasetIndex == datasetIndex ) &&
259 ( cache->mDataInterpolationMethod == method ) &&
260 ( QgsMesh3DAveragingMethod::equals( cache->mScalarAveragingMethod.get(), mRendererSettings.averagingMethod() ) )
261 )
262 {
263 mScalarDatasetValues = cache->mScalarDatasetValues;
264 mScalarActiveFaceFlagValues = cache->mScalarActiveFaceFlagValues;
265 mScalarDataType = cache->mScalarDataType;
266 mScalarDatasetMinimum = cache->mScalarDatasetMinimum;
267 mScalarDatasetMaximum = cache->mScalarDatasetMaximum;
268 return;
269 }
270
271 // Cache is not up-to-date, gather data
272 if ( datasetIndex.isValid() )
273 {
274 const QgsMeshDatasetGroupMetadata metadata = layer->datasetGroupMetadata( datasetIndex.group() );
275 mScalarDataType = QgsMeshLayerUtils::datasetValuesType( metadata.dataType() );
276
277 // populate scalar values
278 const int count = QgsMeshLayerUtils::datasetValuesCount( &mNativeMesh, mScalarDataType );
279 const QgsMeshDataBlock vals = QgsMeshLayerUtils::datasetValues(
280 layer,
281 datasetIndex,
282 0,
283 count );
284
285 if ( vals.isValid() )
286 {
287 // vals could be scalar or vectors, for contour rendering we want always magnitude
288 mScalarDatasetValues = QgsMeshLayerUtils::calculateMagnitudes( vals );
289 }
290 else
291 {
292 mScalarDatasetValues = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
293 }
294
295 // populate face active flag, always defined on faces
297 datasetIndex,
298 0,
299 mNativeMesh.faces.count() );
300
301 // for data on faces, there could be request to interpolate the data to vertices
303 {
305 {
307 mScalarDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData(
312 method
313 );
314 }
316 {
318 mScalarDatasetValues = QgsMeshLayerUtils::resampleFromVerticesToFaces(
323 method
324 );
325 }
326 }
327
328 const QgsMeshDatasetMetadata datasetMetadata = layer->datasetMetadata( datasetIndex );
329 mScalarDatasetMinimum = datasetMetadata.minimum();
330 mScalarDatasetMaximum = datasetMetadata.maximum();
331 }
332
333 // update cache
334 cache->mDatasetGroupsCount = datasetGroupCount;
335 cache->mActiveScalarDatasetIndex = datasetIndex;
336 cache->mDataInterpolationMethod = method;
337 cache->mScalarDatasetValues = mScalarDatasetValues;
338 cache->mScalarActiveFaceFlagValues = mScalarActiveFaceFlagValues;
339 cache->mScalarDataType = mScalarDataType;
340 cache->mScalarDatasetMinimum = mScalarDatasetMinimum;
341 cache->mScalarDatasetMaximum = mScalarDatasetMaximum;
342 cache->mScalarAveragingMethod.reset( mRendererSettings.averagingMethod() ? mRendererSettings.averagingMethod()->clone() : nullptr );
343}
344
345
346void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer )
347{
348 QgsMeshDatasetIndex datasetIndex;
349 if ( renderContext()->isTemporal() )
350 datasetIndex = layer->activeVectorDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeVectorDatasetGroup() );
351 else
352 datasetIndex = layer->staticVectorDatasetIndex( mRendererSettings.activeVectorDatasetGroup() );
353
354 // Find out if we can use cache up to date. If yes, use it and return
355 const int datasetGroupCount = layer->datasetGroupCount();
356 QgsMeshLayerRendererCache *cache = layer->rendererCache();
357 if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) &&
358 ( cache->mActiveVectorDatasetIndex == datasetIndex ) &&
359 ( QgsMesh3DAveragingMethod::equals( cache->mVectorAveragingMethod.get(), mRendererSettings.averagingMethod() ) )
360 )
361 {
362 mVectorDatasetValues = cache->mVectorDatasetValues;
363 mVectorDatasetValuesMag = cache->mVectorDatasetValuesMag;
364 mVectorDatasetMagMinimum = cache->mVectorDatasetMagMinimum;
365 mVectorDatasetMagMaximum = cache->mVectorDatasetMagMaximum;
366 mVectorDatasetGroupMagMinimum = cache->mVectorDatasetMagMinimum;
367 mVectorDatasetGroupMagMaximum = cache->mVectorDatasetMagMaximum;
368 mVectorActiveFaceFlagValues = cache->mVectorActiveFaceFlagValues;
369 mVectorDataType = cache->mVectorDataType;
370 return;
371 }
372
373 // Cache is not up-to-date, gather data
374 if ( datasetIndex.isValid() )
375 {
376 const QgsMeshDatasetGroupMetadata metadata = layer->datasetGroupMetadata( datasetIndex );
377
378 const bool isScalar = metadata.isScalar();
379 if ( isScalar )
380 {
381 QgsDebugError( u"Dataset has no vector values"_s );
382 }
383 else
384 {
385 mVectorDataType = QgsMeshLayerUtils::datasetValuesType( metadata.dataType() );
386
389
390 const int count = QgsMeshLayerUtils::datasetValuesCount( &mNativeMesh, mVectorDataType );
391 mVectorDatasetValues = QgsMeshLayerUtils::datasetValues(
392 layer,
393 datasetIndex,
394 0,
395 count );
396
397 if ( mVectorDatasetValues.isValid() )
398 mVectorDatasetValuesMag = QgsMeshLayerUtils::calculateMagnitudes( mVectorDatasetValues );
399 else
400 mVectorDatasetValuesMag = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
401
402 // populate face active flag
403 mVectorActiveFaceFlagValues = layer->areFacesActive( datasetIndex, 0, mNativeMesh.faces.count() );
404
405 const QgsMeshDatasetMetadata datasetMetadata = layer->datasetMetadata( datasetIndex );
406 mVectorDatasetMagMinimum = datasetMetadata.minimum();
407 mVectorDatasetMagMaximum = datasetMetadata.maximum();
408 }
409 }
410
411 // update cache
412 cache->mDatasetGroupsCount = datasetGroupCount;
413 cache->mActiveVectorDatasetIndex = datasetIndex;
414 cache->mVectorDatasetValues = mVectorDatasetValues;
415 cache->mVectorDatasetValuesMag = mVectorDatasetValuesMag;
416 cache->mVectorDatasetMagMinimum = mVectorDatasetMagMinimum;
417 cache->mVectorDatasetMagMaximum = mVectorDatasetMagMaximum;
418 cache->mVectorDatasetGroupMagMinimum = mVectorDatasetMagMinimum;
419 cache->mVectorDatasetGroupMagMaximum = mVectorDatasetMagMaximum;
420 cache->mVectorActiveFaceFlagValues = mVectorActiveFaceFlagValues;
421 cache->mVectorDataType = mVectorDataType;
422 cache->mVectorAveragingMethod.reset( mRendererSettings.averagingMethod() ? mRendererSettings.averagingMethod()->clone() : nullptr );
423}
424
426{
427 QgsScopedThreadName threadName( u"render:%1"_s.arg( mLayerName ) );
428
429 std::unique_ptr< QgsScopedRuntimeProfile > profile;
430 std::unique_ptr< QgsScopedRuntimeProfile > preparingProfile;
431 if ( mEnableProfile )
432 {
433 profile = std::make_unique< QgsScopedRuntimeProfile >( mLayerName, u"rendering"_s, layerId() );
434 if ( mPreparationTime > 0 )
435 QgsApplication::profiler()->record( QObject::tr( "Create renderer" ), mPreparationTime / 1000.0, u"rendering"_s );
436 preparingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Preparing render" ), u"rendering"_s );
437 }
438
439 mReadyToCompose = false;
440 const QgsScopedQPainterState painterState( renderContext()->painter() );
441 if ( !mClippingRegions.empty() )
442 {
443 bool needsPainterClipPath = false;
444 const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), Qgis::LayerType::Mesh, needsPainterClipPath );
445 if ( needsPainterClipPath )
446 renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
447 }
448
449 preparingProfile.reset();
450
451 renderScalarDataset();
452 mReadyToCompose = true;
453 renderMesh();
454 renderVectorDataset();
455
456 registerLabelFeatures();
457
458 return !renderContext()->renderingStopped();
459}
460
462{
463 switch ( renderContext()->rasterizedRenderingPolicy() )
464 {
467 break;
468
470 return false;
471 }
472
473 return !qgsDoubleNear( mLayerOpacity, 1.0 );
474}
475
476void QgsMeshLayerRenderer::renderMesh()
477{
478 if ( !mRendererSettings.nativeMeshSettings().isEnabled() && !mIsEditable &&
481 return;
482
483 std::unique_ptr< QgsScopedRuntimeProfile > renderProfile;
484 if ( mEnableProfile )
485 {
486 renderProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering mesh" ), u"rendering"_s );
487 }
488
489 // triangular mesh
490 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( renderContext()->mapExtent() );
491 if ( mRendererSettings.triangularMeshSettings().isEnabled() )
492 {
493 renderFaceMesh(
494 mRendererSettings.triangularMeshSettings(),
495 mTriangularMesh.triangles(),
496 trianglesInExtent );
497 }
498
499 // native mesh
500 if ( ( mRendererSettings.nativeMeshSettings().isEnabled() || mIsEditable ) &&
501 mTriangularMesh.levelOfDetail() == 0 )
502 {
503 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent,
504 mTriangularMesh.trianglesToNativeFaces() );
505
506 renderFaceMesh(
507 mRendererSettings.nativeMeshSettings(),
508 mNativeMesh.faces,
509 nativeFacesInExtent.values() );
510 }
511
512 // edge mesh
513 if ( mRendererSettings.edgeMeshSettings().isEnabled() )
514 {
515 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( renderContext()->mapExtent() );
516 renderEdgeMesh( mRendererSettings.edgeMeshSettings(), edgesInExtent );
517 }
518}
519
520static QPainter *_painterForMeshFrame( QgsRenderContext &context, const QgsMeshRendererMeshSettings &settings )
521{
522 // Set up the render configuration options
523 QPainter *painter = context.painter();
524
525 painter->save();
526 context.setPainterFlagsUsingContext( painter );
527
528 QPen pen = painter->pen();
529 pen.setCapStyle( Qt::FlatCap );
530 pen.setJoinStyle( Qt::MiterJoin );
531
532 const double penWidth = context.convertToPainterUnits( settings.lineWidth(), settings.lineWidthUnit() );
533 pen.setWidthF( penWidth );
534 pen.setColor( settings.color() );
535 painter->setPen( pen );
536 return painter;
537}
538
539void QgsMeshLayerRenderer::renderEdgeMesh( const QgsMeshRendererMeshSettings &settings, const QList<int> &edgesInExtent )
540{
541 Q_ASSERT( settings.isEnabled() );
542
544 return;
545
546 QgsRenderContext &context = *renderContext();
547 QPainter *painter = _painterForMeshFrame( context, settings );
548
549 const QVector<QgsMeshEdge> edges = mTriangularMesh.edges();
550 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
551
552 for ( const int i : edgesInExtent )
553 {
554 if ( context.renderingStopped() )
555 break;
556
557 if ( i >= edges.size() )
558 continue;
559
560 const QgsMeshEdge &edge = edges[i];
561 const int startVertexIndex = edge.first;
562 const int endVertexIndex = edge.second;
563
564 if ( ( startVertexIndex >= vertices.size() ) || endVertexIndex >= vertices.size() )
565 continue;
566
567 const QgsMeshVertex &startVertex = vertices[startVertexIndex];
568 const QgsMeshVertex &endVertex = vertices[endVertexIndex];
569 const QgsPointXY lineStart = context.mapToPixel().transform( startVertex.x(), startVertex.y() );
570 const QgsPointXY lineEnd = context.mapToPixel().transform( endVertex.x(), endVertex.y() );
571 painter->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
572 }
573 painter->restore();
574};
575
576void QgsMeshLayerRenderer::renderFaceMesh(
577 const QgsMeshRendererMeshSettings &settings,
578 const QVector<QgsMeshFace> &faces,
579 const QList<int> &facesInExtent )
580{
581 Q_ASSERT( settings.isEnabled() || mIsEditable );
582
584 return;
585
586 QgsRenderContext &context = *renderContext();
587 QPainter *painter = _painterForMeshFrame( context, settings );
588
589 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices(); //Triangular mesh vertices contains also native mesh vertices
590 QSet<QPair<int, int>> drawnEdges;
591
592 for ( const int i : facesInExtent )
593 {
594 if ( context.renderingStopped() )
595 break;
596
597 if ( i < 0 || i >= faces.count() )
598 continue;
599
600 const QgsMeshFace &face = faces[i];
601 if ( face.size() < 2 )
602 continue;
603
604 for ( int j = 0; j < face.size(); ++j )
605 {
606 const int startVertexId = face[j];
607 const int endVertexId = face[( j + 1 ) % face.size()];
608 const QPair<int, int> thisEdge( startVertexId, endVertexId );
609 const QPair<int, int> thisEdgeReversed( endVertexId, startVertexId );
610 if ( drawnEdges.contains( thisEdge ) || drawnEdges.contains( thisEdgeReversed ) )
611 continue;
612 drawnEdges.insert( thisEdge );
613 drawnEdges.insert( thisEdgeReversed );
614
615 const QgsMeshVertex &startVertex = vertices[startVertexId];
616 const QgsMeshVertex &endVertex = vertices[endVertexId];
617 const QgsPointXY lineStart = context.mapToPixel().transform( startVertex.x(), startVertex.y() );
618 const QgsPointXY lineEnd = context.mapToPixel().transform( endVertex.x(), endVertex.y() );
619 painter->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
620 }
621 }
622
623 painter->restore();
624}
625
626void QgsMeshLayerRenderer::renderScalarDataset()
627{
628 if ( mScalarDatasetValues.isEmpty() )
629 return; // activeScalarDataset == NO_ACTIVE_MESH_DATASET
630
631 if ( std::isnan( mScalarDatasetMinimum ) || std::isnan( mScalarDatasetMaximum ) )
632 return; // only NODATA values
633
634 const int groupIndex = mRendererSettings.activeScalarDatasetGroup();
635 if ( groupIndex < 0 )
636 return; // no shader
637
638 std::unique_ptr< QgsScopedRuntimeProfile > renderProfile;
639 if ( mEnableProfile )
640 {
641 renderProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering scalar datasets" ), u"rendering"_s );
642 }
643
644 const QgsMeshRendererScalarSettings scalarSettings = mRendererSettings.scalarSettings( groupIndex );
645
646 if ( ( mTriangularMesh.contains( QgsMesh::ElementType::Face ) ) &&
648 {
649 renderScalarDatasetOnFaces( scalarSettings );
650 }
651
652 if ( ( mTriangularMesh.contains( QgsMesh::ElementType::Edge ) ) &&
654 {
655 renderScalarDatasetOnEdges( scalarSettings );
656 }
657}
658
659void QgsMeshLayerRenderer::renderScalarDatasetOnEdges( const QgsMeshRendererScalarSettings &scalarSettings )
660{
661 QgsRenderContext &context = *renderContext();
662 const QVector<QgsMeshEdge> edges = mTriangularMesh.edges();
663 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
664 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( context.mapExtent() );
665
666 QgsInterpolatedLineRenderer edgePlotter;
667 edgePlotter.setInterpolatedColor( QgsInterpolatedLineColor( scalarSettings.colorRampShader() ) );
668 edgePlotter.setInterpolatedWidth( QgsInterpolatedLineWidth( scalarSettings.edgeStrokeWidth() ) );
669 edgePlotter.setWidthUnit( scalarSettings.edgeStrokeWidthUnit() );
670
671 for ( const int i : edgesInExtent )
672 {
673 if ( context.renderingStopped() )
674 break;
675
676 if ( i >= edges.size() )
677 continue;
678
679 const QgsMeshEdge &edge = edges[i];
680 const int startVertexIndex = edge.first;
681 const int endVertexIndex = edge.second;
682
683 if ( ( startVertexIndex >= vertices.size() ) || endVertexIndex >= vertices.size() )
684 continue;
685
686 const QgsMeshVertex &startVertex = vertices[startVertexIndex];
687 const QgsMeshVertex &endVertex = vertices[endVertexIndex];
688
690 {
691 edgePlotter.render( mScalarDatasetValues[i], mScalarDatasetValues[i], startVertex, endVertex, context );
692 }
693 else
694 {
695 edgePlotter.render( mScalarDatasetValues[startVertexIndex], mScalarDatasetValues[endVertexIndex], startVertex, endVertex, context );
696 }
697 }
698}
699
700QColor QgsMeshLayerRenderer::colorAt( QgsColorRampShader *shader, double val ) const
701{
702 int r, g, b, a;
703 if ( shader->shade( val, &r, &g, &b, &a ) )
704 {
705 return QColor( r, g, b, a );
706 }
707 return QColor();
708}
709
710QgsPointXY QgsMeshLayerRenderer::fractionPoint( const QgsPointXY &p1, const QgsPointXY &p2, double fraction ) const
711{
712 const QgsPointXY pt( p1.x() + fraction * ( p2.x() - p1.x() ),
713 p1.y() + fraction * ( p2.y() - p1.y() ) );
714 return pt;
715}
716
717void QgsMeshLayerRenderer::renderScalarDatasetOnFaces( const QgsMeshRendererScalarSettings &scalarSettings )
718{
719 QgsRenderContext &context = *renderContext();
720
721 QgsColorRampShader *fcn = new QgsColorRampShader( scalarSettings.colorRampShader() );
722 QgsRasterShader *sh = new QgsRasterShader();
723 sh->setRasterShaderFunction( fcn ); // takes ownership of fcn
724 QgsMeshLayerInterpolator interpolator( mTriangularMesh,
728 context,
729 mOutputSize );
730 interpolator.setSpatialIndexActive( mIsMeshSimplificationActive );
731 interpolator.setElevationMapSettings( mRenderElevationMap, mElevationScale, mElevationOffset );
732 QgsSingleBandPseudoColorRenderer renderer( &interpolator, 0, sh ); // takes ownership of sh
733 renderer.setClassificationMin( scalarSettings.classificationMinimum() );
734 renderer.setClassificationMax( scalarSettings.classificationMaximum() );
735 renderer.setOpacity( scalarSettings.opacity() );
736
737 std::unique_ptr<QgsRasterBlock> bl( renderer.block( 0, context.mapExtent(), mOutputSize.width(), mOutputSize.height(), mFeedback.get() ) );
738 QImage img = bl->image();
739 img.setDevicePixelRatio( context.devicePixelRatio() );
740
741 context.painter()->drawImage( 0, 0, img );
742}
743
744void QgsMeshLayerRenderer::renderVectorDataset()
745{
746 const int groupIndex = mRendererSettings.activeVectorDatasetGroup();
747 if ( groupIndex < 0 )
748 return;
749
750 if ( !mVectorDatasetValues.isValid() )
751 return; // no data at all
752
753 if ( std::isnan( mVectorDatasetMagMinimum ) || std::isnan( mVectorDatasetMagMaximum ) )
754 return; // only NODATA values
755
756 if ( !( mVectorDatasetMagMaximum > 0 ) )
757 return; //all vector are null vector
758
759 std::unique_ptr< QgsScopedRuntimeProfile > renderProfile;
760 if ( mEnableProfile )
761 {
762 renderProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering vector datasets" ), u"rendering"_s );
763 }
764
765 std::unique_ptr<QgsMeshVectorRenderer> renderer( QgsMeshVectorRenderer::makeVectorRenderer(
773 mRendererSettings.vectorSettings( groupIndex ),
774 *renderContext(),
776 mFeedback.get(),
777 mOutputSize ) );
778
779 if ( renderer )
780 renderer->draw();
781}
782
783void QgsMeshLayerRenderer::prepareLabeling( QgsMeshLayer *layer, QSet<QString> &attributeNames )
784{
785 QgsRenderContext &context = *renderContext();
786
787 if ( QgsLabelingEngine *engine = context.labelingEngine() )
788 {
789 if ( layer->labelsEnabled() )
790 {
791 mLabelProvider = layer->labeling()->provider( layer );
792 if ( mLabelProvider )
793 {
794 auto c = context.expressionContext();
795
797 c.lastScope()->setVariable( u"_native_mesh"_s, QVariant::fromValue( mNativeMesh ) );
798 context.setExpressionContext( c );
799
800 engine->addProvider( mLabelProvider );
801 if ( !mLabelProvider->prepare( context, attributeNames ) )
802 {
803 engine->removeProvider( mLabelProvider );
804 mLabelProvider = nullptr; // deleted by engine
805 }
806 }
807 }
808 }
809}
810
811void QgsMeshLayerRenderer::registerLabelFeatures()
812{
813 if ( !mLabelProvider )
814 return;
815
816 QgsRenderContext &context = *renderContext();
817
818 QgsExpressionContextScope *scope = context.expressionContext().activeScopeForVariable( u"_native_mesh"_s );
819
820 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( renderContext()->mapExtent() );
821
822 if ( mLabelProvider->labelFaces() )
823 {
825 return;
826 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent,
827 mTriangularMesh.trianglesToNativeFaces() );
828
829 for ( const int i : nativeFacesInExtent )
830 {
831 if ( context.renderingStopped() )
832 break;
833
834 if ( i < 0 || i >= mNativeMesh.faces.count() )
835 continue;
836
837 scope->setVariable( u"_mesh_face_index"_s, i, false );
838
839 QgsFeature f( i );
840 QgsGeometry geom = QgsMeshUtils::toGeometry( mNativeMesh.face( i ), mNativeMesh.vertices );
841 f.setGeometry( geom );
842 mLabelProvider->registerFeature( f, context );
843 }
844 }
845 else
846 {
848 return;
849 const QSet<int> nativeVerticesInExtent = QgsMeshUtils::nativeVerticesFromTriangles( trianglesInExtent,
850 mTriangularMesh.triangles() );
851
852 for ( const int i : nativeVerticesInExtent )
853 {
854 if ( context.renderingStopped() )
855 break;
856
857 if ( i < 0 || i >= mNativeMesh.vertexCount() )
858 continue;
859
860 scope->setVariable( u"_mesh_vertex_index"_s, i, false );
861
862 QgsFeature f( i );
863 QgsGeometry geom = QgsGeometry( new QgsPoint( mNativeMesh.vertex( i ) ) );
864 f.setGeometry( geom );
865 mLabelProvider->registerFeature( f, context );
866 }
867 }
868}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:59
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2762
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2763
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
Definition qgis.h:2764
@ MinimumMaximum
Real min-max values.
Definition qgis.h:6316
@ UpdatedCanvas
Constantly updated extent of the canvas is used to compute statistics.
Definition qgis.h:6329
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:197
@ RenderPreviewJob
Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering.
Definition qgis.h:2816
@ Render3DMap
Render is for a 3D map.
Definition qgis.h:2821
@ FromVertices
Elevation should be taken from mesh vertices.
Definition qgis.h:4096
@ FixedRangePerGroup
Layer has a fixed (manually specified) elevation range per group.
Definition qgis.h:4097
@ FixedElevationRange
Layer has a fixed elevation range.
Definition qgis.h:4095
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:236
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:290
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:167
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:104
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:88
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:6900
#define QgsDebugError(str)
Definition qgslogger.h:59
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