QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
25 #include <cstdlib>
26 #include <ctime>
27 #include <algorithm>
28 #include <QPen>
29 #include <QPainter>
30 #include <cmath>
31 
33 
34 inline double mag( double input )
35 {
36  if ( input < 0.0 )
37  {
38  return -1.0;
39  }
40  return 1.0;
41 }
42 
43 inline bool nodataValue( double x, double y )
44 {
45  return ( std::isnan( x ) || std::isnan( y ) );
46 }
47 
48 QgsMeshVectorRenderer::QgsMeshVectorRenderer( const QgsTriangularMesh &m,
49  const QVector<double> &datasetValuesX,
50  const QVector<double> &datasetValuesY,
51  const QVector<double> &datasetValuesMag,
52  double datasetMagMinimumValue,
53  double datasetMagMaximumValue,
54  bool dataIsOnVertices,
55  const QgsMeshRendererVectorSettings &settings,
56  QgsRenderContext &context, QSize size )
57  : mTriangularMesh( m )
58  , mDatasetValuesX( datasetValuesX )
59  , mDatasetValuesY( datasetValuesY )
60  , mDatasetValuesMag( datasetValuesMag )
61  , mMinMag( datasetMagMinimumValue )
62  , mMaxMag( datasetMagMaximumValue )
63  , mContext( context )
64  , mCfg( settings )
65  , mDataOnVertices( dataIsOnVertices )
66  , mOutputSize( size )
67  , mBufferedExtent( context.extent() )
68 {
69  // should be checked in caller
70  Q_ASSERT( !mDatasetValuesMag.empty() );
71  Q_ASSERT( !std::isnan( mMinMag ) );
72  Q_ASSERT( !std::isnan( mMaxMag ) );
73 
74  // we need to expand out the extent so that it includes
75  // arrows which start or end up outside of the
76  // actual visible extent
77  double extension = context.convertToMapUnits( calcExtentBufferSize(), QgsUnitTypes::RenderPixels );
78  mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
79  mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
80  mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
81  mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
82 }
83 
84 QgsMeshVectorRenderer::~QgsMeshVectorRenderer() = default;
85 
86 void QgsMeshVectorRenderer::draw()
87 {
88  // Set up the render configuration options
89  QPainter *painter = mContext.painter();
90  painter->save();
91  if ( mContext.flags() & QgsRenderContext::Antialiasing )
92  painter->setRenderHint( QPainter::Antialiasing, true );
93 
94  QPen pen = painter->pen();
95  pen.setCapStyle( Qt::FlatCap );
96  pen.setJoinStyle( Qt::MiterJoin );
97 
98  double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(),
99  QgsUnitTypes::RenderUnit::RenderMillimeters );
100  pen.setWidthF( penWidth );
101  pen.setColor( mCfg.color() );
102  painter->setPen( pen );
103 
104  const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
105 
106  if ( mCfg.isOnUserDefinedGrid() )
107  drawVectorDataOnGrid( trianglesInExtent );
108  else if ( mDataOnVertices )
109  drawVectorDataOnVertices( trianglesInExtent );
110  else
111  drawVectorDataOnFaces( trianglesInExtent );
112 
113  painter->restore();
114 }
115 
116 bool QgsMeshVectorRenderer::calcVectorLineEnd(
117  QgsPointXY &lineEnd,
118  double &vectorLength,
119  double &cosAlpha,
120  double &sinAlpha, //out
121  const QgsPointXY &lineStart,
122  double xVal,
123  double yVal,
124  double magnitude //in
125 )
126 {
127  // return true on error
128 
129  if ( xVal == 0.0 && yVal == 0.0 )
130  return true;
131 
132  // do not render if magnitude is outside of the filtered range (if filtering is enabled)
133  if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
134  return true;
135  if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
136  return true;
137 
138  // Determine the angle of the vector, counter-clockwise, from east
139  // (and associated trigs)
140  double vectorAngle = -1.0 * atan( ( -1.0 * yVal ) / xVal );
141  cosAlpha = cos( vectorAngle ) * mag( xVal );
142  sinAlpha = sin( vectorAngle ) * mag( xVal );
143 
144  // Now determine the X and Y distances of the end of the line from the start
145  double xDist = 0.0;
146  double yDist = 0.0;
147  switch ( mCfg.shaftLengthMethod() )
148  {
149  case QgsMeshRendererVectorSettings::ArrowScalingMethod::MinMax:
150  {
151  double minShaftLength = mContext.convertToPainterUnits( mCfg.minShaftLength(),
152  QgsUnitTypes::RenderUnit::RenderMillimeters );
153  double maxShaftLength = mContext.convertToPainterUnits( mCfg.maxShaftLength(),
154  QgsUnitTypes::RenderUnit::RenderMillimeters );
155  double minVal = mMinMag;
156  double maxVal = mMaxMag;
157  double k = ( magnitude - minVal ) / ( maxVal - minVal );
158  double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
159  xDist = cosAlpha * L;
160  yDist = sinAlpha * L;
161  break;
162  }
163  case QgsMeshRendererVectorSettings::ArrowScalingMethod::Scaled:
164  {
165  double scaleFactor = mCfg.scaleFactor();
166  xDist = scaleFactor * xVal;
167  yDist = scaleFactor * yVal;
168  break;
169  }
170  case QgsMeshRendererVectorSettings::ArrowScalingMethod::Fixed:
171  {
172  // We must be using a fixed length
173  double fixedShaftLength = mContext.convertToPainterUnits( mCfg.fixedShaftLength(),
174  QgsUnitTypes::RenderUnit::RenderMillimeters );
175  xDist = cosAlpha * fixedShaftLength;
176  yDist = sinAlpha * fixedShaftLength;
177  break;
178  }
179  }
180 
181  // Flip the Y axis (pixel vs real-world axis)
182  yDist *= -1.0;
183 
184  if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
185  return true;
186 
187  // Determine the line coords
188  lineEnd = QgsPointXY( lineStart.x() + xDist,
189  lineStart.y() + yDist );
190 
191  vectorLength = sqrt( xDist * xDist + yDist * yDist );
192 
193  // Check to see if both of the coords are outside the QImage area, if so, skip the whole vector
194  if ( ( lineStart.x() < 0 || lineStart.x() > mOutputSize.width() ||
195  lineStart.y() < 0 || lineStart.y() > mOutputSize.height() ) &&
196  ( lineEnd.x() < 0 || lineEnd.x() > mOutputSize.width() ||
197  lineEnd.y() < 0 || lineEnd.y() > mOutputSize.height() ) )
198  return true;
199 
200  return false; //success
201 }
202 
203 double QgsMeshVectorRenderer::calcExtentBufferSize() const
204 {
205  double buffer = 0;
206  switch ( mCfg.shaftLengthMethod() )
207  {
208  case QgsMeshRendererVectorSettings::ArrowScalingMethod::MinMax:
209  {
210  buffer = mContext.convertToPainterUnits( mCfg.maxShaftLength(),
211  QgsUnitTypes::RenderUnit::RenderMillimeters );
212  break;
213  }
214  case QgsMeshRendererVectorSettings::ArrowScalingMethod::Scaled:
215  {
216  buffer = mCfg.scaleFactor() * mMaxMag;
217  break;
218  }
219  case QgsMeshRendererVectorSettings::ArrowScalingMethod::Fixed:
220  {
221  buffer = mContext.convertToPainterUnits( mCfg.fixedShaftLength(),
222  QgsUnitTypes::RenderUnit::RenderMillimeters );
223  break;
224  }
225  }
226 
227  if ( mCfg.filterMax() >= 0 && buffer > mCfg.filterMax() )
228  buffer = mCfg.filterMax();
229 
230  if ( buffer < 0.0 )
231  buffer = 0.0;
232 
233  return buffer;
234 }
235 
236 
237 void QgsMeshVectorRenderer::drawVectorDataOnVertices( const QList<int> &trianglesInExtent )
238 {
239  const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
240  const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
241  QSet<int> drawnVertices;
242 
243  // currently expecting that triangulation does not add any new extra vertices on the way
244  Q_ASSERT( mDatasetValuesMag.count() == vertices.count() );
245 
246  for ( int triangleIndex : trianglesInExtent )
247  {
248  if ( mContext.renderingStopped() )
249  break;
250 
251  const QgsMeshFace triangle = triangles[triangleIndex];
252  for ( int i : triangle )
253  {
254  if ( drawnVertices.contains( i ) )
255  continue;
256  drawnVertices.insert( i );
257 
258  const QgsMeshVertex &vertex = vertices.at( i );
259  if ( !mBufferedExtent.contains( vertex ) )
260  continue;
261 
262  double xVal = mDatasetValuesX[i];
263  double yVal = mDatasetValuesY[i];
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 QgsMeshVectorRenderer::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  double xVal = mDatasetValuesX[i];
290  double yVal = mDatasetValuesY[i];
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 QgsMeshVectorRenderer::drawVectorDataOnGrid( const QList<int> &trianglesInExtent )
302 {
303  int cellx = mCfg.userGridCellWidth();
304  int celly = mCfg.userGridCellHeight();
305 
306  const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
307  const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
308 
309  for ( const int i : trianglesInExtent )
310  {
311  if ( mContext.renderingStopped() )
312  break;
313 
314  const QgsMeshFace &face = triangles[i];
315 
316  const int v1 = face[0], v2 = face[1], v3 = face[2];
317  const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
318 
319  const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
320 
321  // Get the BBox of the element in pixels
322  QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
323  int left, right, top, bottom;
324  QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
325 
326  // Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
327  if ( left % cellx != 0 )
328  left += cellx - ( left % cellx );
329  if ( right % cellx != 0 )
330  right -= ( right % cellx );
331  if ( top % celly != 0 )
332  top += celly - ( top % celly );
333  if ( bottom % celly != 0 )
334  bottom -= ( bottom % celly );
335 
336  for ( int y = top; y <= bottom; y += celly )
337  {
338  for ( int x = left; x <= right; x += cellx )
339  {
341  const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
342 
343  if ( mDataOnVertices )
344  {
345  val.setX(
346  QgsMeshLayerUtils::interpolateFromVerticesData(
347  p1, p2, p3,
348  mDatasetValuesX[v1],
349  mDatasetValuesX[v2],
350  mDatasetValuesX[v3],
351  p )
352  );
353  val.setY(
354  QgsMeshLayerUtils::interpolateFromVerticesData(
355  p1, p2, p3,
356  mDatasetValuesY[v1],
357  mDatasetValuesY[v2],
358  mDatasetValuesY[v3],
359  p )
360  );
361  }
362  else
363  {
364  val.setX(
365  QgsMeshLayerUtils::interpolateFromFacesData(
366  p1, p2, p3,
367  mDatasetValuesX[nativeFaceIndex],
368  p
369  )
370  );
371  val.setY(
372  QgsMeshLayerUtils::interpolateFromFacesData(
373  p1, p2, p3,
374  mDatasetValuesY[nativeFaceIndex],
375  p
376  )
377  );
378  }
379 
380  if ( nodataValue( val.x(), val.y() ) )
381  continue;
382 
383  QgsPointXY lineStart( x, y );
384  drawVectorArrow( lineStart, val.x(), val.y(), val.scalar() );
385  }
386  }
387  }
388 }
389 
390 void QgsMeshVectorRenderer::drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
391 {
392  QgsPointXY lineEnd;
393  double vectorLength;
394  double cosAlpha, sinAlpha;
395  if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha,
396  lineStart, xVal, yVal, magnitude ) )
397  return;
398 
399  // Make a set of vector head coordinates that we will place at the end of each vector,
400  // scale, translate and rotate.
401  QgsPointXY vectorHeadPoints[3];
402  QVector<QPointF> finalVectorHeadPoints( 3 );
403 
404  double vectorHeadWidthRatio = mCfg.arrowHeadWidthRatio();
405  double vectorHeadLengthRatio = mCfg.arrowHeadLengthRatio();
406 
407  // First head point: top of ->
408  vectorHeadPoints[0].setX( -1.0 * vectorHeadLengthRatio );
409  vectorHeadPoints[0].setY( vectorHeadWidthRatio * 0.5 );
410 
411  // Second head point: right of ->
412  vectorHeadPoints[1].setX( 0.0 );
413  vectorHeadPoints[1].setY( 0.0 );
414 
415  // Third head point: bottom of ->
416  vectorHeadPoints[2].setX( -1.0 * vectorHeadLengthRatio );
417  vectorHeadPoints[2].setY( -1.0 * vectorHeadWidthRatio * 0.5 );
418 
419  // Determine the arrow head coords
420  for ( int j = 0; j < 3; j++ )
421  {
422  finalVectorHeadPoints[j].setX( lineEnd.x()
423  + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength )
424  - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength )
425  );
426 
427  finalVectorHeadPoints[j].setY( lineEnd.y()
428  - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength )
429  - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength )
430  );
431  }
432 
433  // Now actually draw the vector
434  mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
435  mContext.painter()->drawPolygon( finalVectorHeadPoints );
436 }
437 
QPointF toQPointF() const
Converts a point to a QPointF.
Definition: qgspointxy.h:148
A rectangle specified with double values.
Definition: qgsrectangle.h:40
double y
Definition: qgspoint.h:42
Triangular/Derived Mesh is mesh with vertices in map coordinates.
Use antialiasing while drawing.
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
double convertToMapUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
void setY(double y)
Sets Y value.
Represents a mesh renderer settings for vector datasets.
void setY(double y)
Sets the y value of the point.
Definition: qgspointxy.h:113
double y() const
Returns y value.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
void setX(double x)
Sets the x value of the point.
Definition: qgspointxy.h:104
double scalar() const
Returns magnitude of vector for vector data or scalar value for scalar data.
double x
Definition: qgspointxy.h:47
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.
double x() const
Returns x value.
void setX(double x)
Sets X value.
double x
Definition: qgspoint.h:41