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