QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
18 #include "qgsmeshvectorrenderer.h"
19 #include "qgsrendercontext.h"
20 #include "qgscoordinatetransform.h"
21 #include "qgsmaptopixel.h"
22 #include "qgsunittypes.h"
23 #include "qgsmeshlayerutils.h"
24 #include "qgsmeshtracerenderer.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 
39 inline double mag( double input )
40 {
41  if ( input < 0.0 )
42  {
43  return -1.0;
44  }
45  return 1.0;
46 }
47 
48 inline bool nodataValue( double x, double y )
49 {
50  return ( std::isnan( x ) || std::isnan( y ) );
51 }
52 
53 QgsMeshVectorArrowRenderer::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  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 
92 QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() = default;
93 
94 void QgsMeshVectorArrowRenderer::draw()
95 {
96  // Set up the render configuration options
97  QPainter *painter = mContext.painter();
98 
99  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  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 
129 bool 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  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  double minShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().minShaftLength(),
166  QgsUnitTypes::RenderUnit::RenderMillimeters );
167  double maxShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
168  QgsUnitTypes::RenderUnit::RenderMillimeters );
169  double minVal = mMinMag;
170  double maxVal = mMaxMag;
171  double k = ( magnitude - minVal ) / ( maxVal - minVal );
172  double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
173  xDist = cosAlpha * L;
174  yDist = sinAlpha * L;
175  break;
176  }
177  case QgsMeshRendererVectorArrowSettings::ArrowScalingMethod::Scaled:
178  {
179  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  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 
217 double 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 
251 void 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 
277 void QgsMeshVectorArrowRenderer::drawVectorDataOnPoints( const QSet<int> indexesToRender, const QVector<QgsMeshVertex> &points )
278 {
279  for ( int i : indexesToRender )
280  {
281  if ( mContext.renderingStopped() )
282  break;
283 
284  QgsPointXY center = points.at( i );
285  if ( !mBufferedExtent.contains( center ) )
286  continue;
287 
288  const QgsMeshDatasetValue val = mDatasetValues.value( i );
289  double xVal = val.x();
290  double yVal = val.y();
291  if ( nodataValue( xVal, yVal ) )
292  continue;
293 
294  double V = mDatasetValuesMag[i]; // pre-calculated magnitude
295  QgsPointXY lineStart = mContext.mapToPixel().transform( center.x(), center.y() );
296 
297  drawVectorArrow( lineStart, xVal, yVal, V );
298  }
299 }
300 
301 void 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 
310 void 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 
319 void 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  int cellx = mCfg.userGridCellWidth();
327  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  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  QgsPointXY lineStart( x, y );
410  drawVectorArrow( lineStart, val.x(), val.y(), val.scalar() );
411  }
412  }
413  }
414 }
415 
416 void 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  double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
431  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 
467 QgsMeshVectorRenderer::~QgsMeshVectorRenderer() = default;
468 
469 QgsMeshVectorRenderer *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.