QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsmeshvectorrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmeshvectorrenderer.cpp
3 -------------------------
4 begin : May 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#include "qgsrendercontext.h"
21#include "qgsmaptopixel.h"
22#include "qgsunittypes.h"
23#include "qgsmeshlayerutils.h"
25
26#include <cstdlib>
27#include <ctime>
28#include <algorithm>
29#include <QPen>
30#include <QPainter>
31#include <cmath>
32
34
35#ifndef M_DEG2RAD
36#define M_DEG2RAD 0.0174532925
37#endif
38
39inline double mag( double input )
40{
41 if ( input < 0.0 )
42 {
43 return -1.0;
44 }
45 return 1.0;
46}
47
48inline bool nodataValue( double x, double y )
49{
50 return ( std::isnan( x ) || std::isnan( y ) );
51}
52
53QgsMeshVectorArrowRenderer::QgsMeshVectorArrowRenderer(
54 const QgsTriangularMesh &m,
55 const QgsMeshDataBlock &datasetValues,
56 const QVector<double> &datasetValuesMag,
57 double datasetMagMaximumValue, double datasetMagMinimumValue,
59 const QgsMeshRendererVectorSettings &settings,
60 QgsRenderContext &context,
61 QSize size ) :
62 mTriangularMesh( m )
63 , mDatasetValues( datasetValues )
64 , mDatasetValuesMag( datasetValuesMag )
65 , mMinMag( datasetMagMinimumValue )
66 , mMaxMag( datasetMagMaximumValue )
67 , mContext( context )
68 , mCfg( settings )
69 , mDataType( dataType )
70 , mOutputSize( size )
71 , mBufferedExtent( context.mapExtent() )
72{
73 // should be checked in caller
74 Q_ASSERT( !mDatasetValuesMag.empty() );
75 Q_ASSERT( !std::isnan( mMinMag ) );
76 Q_ASSERT( !std::isnan( mMaxMag ) );
77 Q_ASSERT( mDatasetValues.isValid() );
78 Q_ASSERT( QgsMeshDataBlock::Vector2DDouble == mDatasetValues.type() );
79
80 // we need to expand out the extent so that it includes
81 // arrows which start or end up outside of the
82 // actual visible extent
83 const double extension = context.convertToMapUnits( calcExtentBufferSize(), QgsUnitTypes::RenderPixels );
84 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
85 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
86 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
87 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
88
89 mVectorColoring = settings.vectorStrokeColoring();
90}
91
92QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() = default;
93
94void QgsMeshVectorArrowRenderer::draw()
95{
96 // Set up the render configuration options
97 QPainter *painter = mContext.painter();
98
99 const QgsScopedQPainterState painterState( painter );
100 mContext.setPainterFlagsUsingContext( painter );
101
102 QPen pen = painter->pen();
103 pen.setCapStyle( Qt::FlatCap );
104 pen.setJoinStyle( Qt::MiterJoin );
105
106 const double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(),
107 QgsUnitTypes::RenderUnit::RenderMillimeters );
108 pen.setWidthF( penWidth );
109 painter->setPen( pen );
110
111 if ( mCfg.isOnUserDefinedGrid() )
112 {
113 drawVectorDataOnGrid( );
114 }
115 else if ( mDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVertices )
116 {
117 drawVectorDataOnVertices( );
118 }
119 else if ( mDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnFaces )
120 {
121 drawVectorDataOnFaces( );
122 }
123 else if ( mDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnEdges )
124 {
125 drawVectorDataOnEdges( );
126 }
127}
128
129bool QgsMeshVectorArrowRenderer::calcVectorLineEnd(
130 QgsPointXY &lineEnd,
131 double &vectorLength,
132 double &cosAlpha,
133 double &sinAlpha, //out
134 const QgsPointXY &lineStart,
135 double xVal,
136 double yVal,
137 double magnitude //in
138)
139{
140 // return true on error
141
142 if ( xVal == 0.0 && yVal == 0.0 )
143 return true;
144
145 // do not render if magnitude is outside of the filtered range (if filtering is enabled)
146 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
147 return true;
148 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
149 return true;
150
151 // Determine the angle of the vector, counter-clockwise, from east
152 // (and associated trigs)
153 const double vectorAngle = -1.0 * atan( ( -1.0 * yVal ) / xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD;
154
155 cosAlpha = cos( vectorAngle ) * mag( xVal );
156 sinAlpha = sin( vectorAngle ) * mag( xVal );
157
158 // Now determine the X and Y distances of the end of the line from the start
159 double xDist = 0.0;
160 double yDist = 0.0;
161 switch ( mCfg.arrowSettings().shaftLengthMethod() )
162 {
163 case QgsMeshRendererVectorArrowSettings::ArrowScalingMethod::MinMax:
164 {
165 const double minShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().minShaftLength(),
166 QgsUnitTypes::RenderUnit::RenderMillimeters );
167 const double maxShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
168 QgsUnitTypes::RenderUnit::RenderMillimeters );
169 const double minVal = mMinMag;
170 const double maxVal = mMaxMag;
171 const double k = ( magnitude - minVal ) / ( maxVal - minVal );
172 const double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
173 xDist = cosAlpha * L;
174 yDist = sinAlpha * L;
175 break;
176 }
177 case QgsMeshRendererVectorArrowSettings::ArrowScalingMethod::Scaled:
178 {
179 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
180 xDist = scaleFactor * xVal;
181 yDist = scaleFactor * yVal;
182 break;
183 }
184 case QgsMeshRendererVectorArrowSettings::ArrowScalingMethod::Fixed:
185 {
186 // We must be using a fixed length
187 const double fixedShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
188 QgsUnitTypes::RenderUnit::RenderMillimeters );
189 xDist = cosAlpha * fixedShaftLength;
190 yDist = sinAlpha * fixedShaftLength;
191 break;
192 }
193 }
194
195 // Flip the Y axis (pixel vs real-world axis)
196 yDist *= -1.0;
197
198 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
199 return true;
200
201 // Determine the line coords
202 lineEnd = QgsPointXY( lineStart.x() + xDist,
203 lineStart.y() + yDist );
204
205 vectorLength = sqrt( xDist * xDist + yDist * yDist );
206
207 // Check to see if both of the coords are outside the QImage area, if so, skip the whole vector
208 if ( ( lineStart.x() < 0 || lineStart.x() > mOutputSize.width() ||
209 lineStart.y() < 0 || lineStart.y() > mOutputSize.height() ) &&
210 ( lineEnd.x() < 0 || lineEnd.x() > mOutputSize.width() ||
211 lineEnd.y() < 0 || lineEnd.y() > mOutputSize.height() ) )
212 return true;
213
214 return false; //success
215}
216
217double QgsMeshVectorArrowRenderer::calcExtentBufferSize() const
218{
219 double buffer = 0;
220 switch ( mCfg.arrowSettings().shaftLengthMethod() )
221 {
222 case QgsMeshRendererVectorArrowSettings::ArrowScalingMethod::MinMax:
223 {
224 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
225 QgsUnitTypes::RenderUnit::RenderMillimeters );
226 break;
227 }
228 case QgsMeshRendererVectorArrowSettings::ArrowScalingMethod::Scaled:
229 {
230 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
231 break;
232 }
233 case QgsMeshRendererVectorArrowSettings::ArrowScalingMethod::Fixed:
234 {
235 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
236 QgsUnitTypes::RenderUnit::RenderMillimeters );
237 break;
238 }
239 }
240
241 if ( mCfg.filterMax() >= 0 && buffer > mCfg.filterMax() )
242 buffer = mCfg.filterMax();
243
244 if ( buffer < 0.0 )
245 buffer = 0.0;
246
247 return buffer;
248}
249
250
251void QgsMeshVectorArrowRenderer::drawVectorDataOnVertices()
252{
253 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
254 QSet<int> verticesToDraw;
255
256 // currently expecting that triangulation does not add any new extra vertices on the way
257 Q_ASSERT( mDatasetValuesMag.count() == vertices.count() );
258
259 // find all vertices from faces to render
260 {
261 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
262 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
263 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromTriangles( trianglesInExtent, triangles ) );
264 }
265
266 // find all vertices from edges to render
267 {
268 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
269 const QVector<QgsMeshEdge> &edges = mTriangularMesh.edges();
270 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromEdges( edgesInExtent, edges ) );
271 }
272
273 // render
274 drawVectorDataOnPoints( verticesToDraw, vertices );
275}
276
277void QgsMeshVectorArrowRenderer::drawVectorDataOnPoints( const QSet<int> indexesToRender, const QVector<QgsMeshVertex> &points )
278{
279 for ( const int i : indexesToRender )
280 {
281 if ( mContext.renderingStopped() )
282 break;
283
284 const QgsPointXY center = points.at( i );
285 if ( !mBufferedExtent.contains( center ) )
286 continue;
287
288 const QgsMeshDatasetValue val = mDatasetValues.value( i );
289 const double xVal = val.x();
290 const double yVal = val.y();
291 if ( nodataValue( xVal, yVal ) )
292 continue;
293
294 const double V = mDatasetValuesMag[i]; // pre-calculated magnitude
295 const QgsPointXY lineStart = mContext.mapToPixel().transform( center.x(), center.y() );
296
297 drawVectorArrow( lineStart, xVal, yVal, V );
298 }
299}
300
301void QgsMeshVectorArrowRenderer::drawVectorDataOnFaces( )
302{
303 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
304 const QVector<QgsMeshVertex> &centroids = mTriangularMesh.faceCentroids();
305 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent,
306 mTriangularMesh.trianglesToNativeFaces() );
307 drawVectorDataOnPoints( nativeFacesInExtent, centroids );
308}
309
310void QgsMeshVectorArrowRenderer::drawVectorDataOnEdges()
311{
312 const QList<int> egdesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
313 const QVector<QgsMeshVertex> &centroids = mTriangularMesh.edgeCentroids();
314 const QSet<int> nativeEdgesInExtent = QgsMeshUtils::nativeEdgesFromEdges( egdesInExtent,
315 mTriangularMesh.edgesToNativeEdges() );
316 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
317}
318
319void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid( )
320{
321 if ( mDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnEdges ||
322 mDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVolumes )
323 return;
324
325 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
326 const int cellx = mCfg.userGridCellWidth();
327 const int celly = mCfg.userGridCellHeight();
328
329 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
330 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
331
332 for ( const int i : trianglesInExtent )
333 {
334 if ( mContext.renderingStopped() )
335 break;
336
337 const QgsMeshFace &face = triangles[i];
338
339 const int v1 = face[0], v2 = face[1], v3 = face[2];
340 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
341
342 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
343
344 // Get the BBox of the element in pixels
345 const QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
346 int left, right, top, bottom;
347 QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
348
349 // Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
350 if ( left % cellx != 0 )
351 left += cellx - ( left % cellx );
352 if ( right % cellx != 0 )
353 right -= ( right % cellx );
354 if ( top % celly != 0 )
355 top += celly - ( top % celly );
356 if ( bottom % celly != 0 )
357 bottom -= ( bottom % celly );
358
359 for ( int y = top; y <= bottom; y += celly )
360 {
361 for ( int x = left; x <= right; x += cellx )
362 {
364 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
365
366 if ( mDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVertices )
367 {
368 const auto val1 = mDatasetValues.value( v1 );
369 const auto val2 = mDatasetValues.value( v2 );
370 const auto val3 = mDatasetValues.value( v3 );
371 val.setX(
372 QgsMeshLayerUtils::interpolateFromVerticesData(
373 p1, p2, p3,
374 val1.x(),
375 val2.x(),
376 val3.x(),
377 p )
378 );
379 val.setY(
380 QgsMeshLayerUtils::interpolateFromVerticesData(
381 p1, p2, p3,
382 val1.y(),
383 val2.y(),
384 val3.y(),
385 p )
386 );
387 }
388 else if ( mDataType == QgsMeshDatasetGroupMetadata::DataType::DataOnFaces )
389 {
390 const auto val1 = mDatasetValues.value( nativeFaceIndex );
391 val.setX(
392 QgsMeshLayerUtils::interpolateFromFacesData(
393 p1, p2, p3,
394 val1.x(),
395 p
396 )
397 );
398 val.setY(
399 QgsMeshLayerUtils::interpolateFromFacesData(
400 p1, p2, p3,
401 val1.y(),
402 p
403 )
404 );
405 }
406 if ( nodataValue( val.x(), val.y() ) )
407 continue;
408
409 const QgsPointXY lineStart( x, y );
410 drawVectorArrow( lineStart, val.x(), val.y(), val.scalar() );
411 }
412 }
413 }
414}
415
416void QgsMeshVectorArrowRenderer::drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
417{
418 QgsPointXY lineEnd;
419 double vectorLength;
420 double cosAlpha, sinAlpha;
421 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha,
422 lineStart, xVal, yVal, magnitude ) )
423 return;
424
425 // Make a set of vector head coordinates that we will place at the end of each vector,
426 // scale, translate and rotate.
427 QgsPointXY vectorHeadPoints[3];
428 QVector<QPointF> finalVectorHeadPoints( 3 );
429
430 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
431 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
432
433 // First head point: top of ->
434 vectorHeadPoints[0].setX( -1.0 * vectorHeadLengthRatio );
435 vectorHeadPoints[0].setY( vectorHeadWidthRatio * 0.5 );
436
437 // Second head point: right of ->
438 vectorHeadPoints[1].setX( 0.0 );
439 vectorHeadPoints[1].setY( 0.0 );
440
441 // Third head point: bottom of ->
442 vectorHeadPoints[2].setX( -1.0 * vectorHeadLengthRatio );
443 vectorHeadPoints[2].setY( -1.0 * vectorHeadWidthRatio * 0.5 );
444
445 // Determine the arrow head coords
446 for ( int j = 0; j < 3; j++ )
447 {
448 finalVectorHeadPoints[j].setX( lineEnd.x()
449 + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength )
450 - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength )
451 );
452
453 finalVectorHeadPoints[j].setY( lineEnd.y()
454 - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength )
455 - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength )
456 );
457 }
458
459 // Now actually draw the vector
460 QPen pen( mContext.painter()->pen() );
461 pen.setColor( mVectorColoring.color( magnitude ) );
462 mContext.painter()->setPen( pen );
463 mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
464 mContext.painter()->drawPolygon( finalVectorHeadPoints );
465}
466
467QgsMeshVectorRenderer::~QgsMeshVectorRenderer() = default;
468
469QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
470 const QgsTriangularMesh &m,
471 const QgsMeshDataBlock &datasetVectorValues,
472 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
473 const QVector<double> &datasetValuesMag,
474 double datasetMagMaximumValue,
475 double datasetMagMinimumValue,
477 const QgsMeshRendererVectorSettings &settings,
478 QgsRenderContext &context,
479 const QgsRectangle &layerExtent,
480 QSize size )
481{
482 QgsMeshVectorRenderer *renderer = nullptr;
483
484 switch ( settings.symbology() )
485 {
487 renderer = new QgsMeshVectorArrowRenderer(
488 m,
489 datasetVectorValues,
490 datasetValuesMag,
491 datasetMagMaximumValue,
492 datasetMagMinimumValue,
493 dataType,
494 settings,
495 context,
496 size );
497 break;
499 renderer = new QgsMeshVectorStreamlineRenderer(
500 m,
501 datasetVectorValues,
502 scalarActiveFaceFlagValues,
503 dataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVertices,
504 settings,
505 context,
506 layerExtent,
507 datasetMagMaximumValue );
508 break;
510 renderer = new QgsMeshVectorTraceRenderer(
511 m,
512 datasetVectorValues,
513 scalarActiveFaceFlagValues,
514 dataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVertices,
515 settings,
516 context,
517 layerExtent,
518 datasetMagMaximumValue );
519 break;
520 }
521
522 return renderer;
523}
524
525
QgsMeshDataBlock is a block of integers/doubles that can be used to retrieve: active flags (e....
@ Vector2DDouble
Vector double pairs (x1, y1, x2, y2, ... )
DataType
Location of where data is specified for datasets in the dataset group.
QgsMeshDatasetValue represents single dataset value.
void setY(double y)
Sets Y value.
double y() const
Returns y value.
double scalar() const
Returns magnitude of vector for vector data or scalar value for scalar data.
double x() const
Returns x value.
void setX(double x)
Sets X value.
Represents a renderer settings for vector datasets.
@ Traces
Displaying vector dataset with particle traces.
@ Arrows
Displaying vector dataset with arrows.
@ Streamlines
Displaying vector dataset with streamlines.
Symbology symbology() const
Returns the displaying method used to render vector datasets.
QgsInterpolatedLineColor vectorStrokeColoring() const
Returns the stroke coloring used to render vector datasets.
A class to represent a 2D point.
Definition: qgspointxy.h:59
void setX(double x) SIP_HOLDGIL
Sets the x value of the point.
Definition: qgspointxy.h:122
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
void setY(double y) SIP_HOLDGIL
Sets the y value of the point.
Definition: qgspointxy.h:132
QPointF toQPointF() const
Converts a point to a QPointF.
Definition: qgspointxy.h:169
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Contains information about the context of a rendering operation.
double convertToMapUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
Scoped object for saving and restoring a QPainter object's state.
Triangular/Derived Mesh is mesh with vertices in map coordinates.
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:171
CORE_EXPORT QSet< int > nativeEdgesFromEdges(const QList< int > &edgesIndexes, const QVector< int > &edgesToNativeEdges)
Returns unique native faces indexes from list of triangle indexes.
CORE_EXPORT QSet< int > nativeVerticesFromEdges(const QList< int > &edgesIndexes, const QVector< QgsMeshEdge > &edges)
Returns unique native faces indexes from list of vertices of triangles.
CORE_EXPORT QSet< int > nativeVerticesFromTriangles(const QList< int > &triangleIndexes, const QVector< QgsMeshFace > &triangles)
Returns unique native vertex indexes from list of vertices of triangles.
CORE_EXPORT QSet< int > nativeFacesFromTriangles(const QList< int > &triangleIndexes, const QVector< int > &trianglesToNativeFaces)
Returns unique native faces indexes from list of triangle indexes.
#define M_DEG2RAD
QVector< int > QgsMeshFace
List of vertex indexes.