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