QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 QgsMeshDataBlock &datasetValues,
50  const QVector<double> &datasetValuesMag,
51  double datasetMagMinimumValue,
52  double datasetMagMaximumValue,
53  bool dataIsOnVertices,
54  const QgsMeshRendererVectorSettings &settings,
55  QgsRenderContext &context, QSize size )
56  : mTriangularMesh( m )
57  , mDatasetValues( datasetValues )
58  , mDatasetValuesMag( datasetValuesMag )
59  , mMinMag( datasetMagMinimumValue )
60  , mMaxMag( datasetMagMaximumValue )
61  , mContext( context )
62  , mCfg( settings )
63  , mDataOnVertices( dataIsOnVertices )
64  , mOutputSize( size )
65  , mBufferedExtent( context.extent() )
66 {
67  // should be checked in caller
68  Q_ASSERT( !mDatasetValuesMag.empty() );
69  Q_ASSERT( !std::isnan( mMinMag ) );
70  Q_ASSERT( !std::isnan( mMaxMag ) );
71  Q_ASSERT( mDatasetValues.isValid() );
72  Q_ASSERT( QgsMeshDataBlock::Vector2DDouble == mDatasetValues.type() );
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  const QgsMeshDatasetValue val = mDatasetValues.value( i );
263  double xVal = val.x();
264  double yVal = val.y();
265  if ( nodataValue( xVal, yVal ) )
266  continue;
267 
268  double V = mDatasetValuesMag[i]; // pre-calculated magnitude
269  QgsPointXY lineStart = mContext.mapToPixel().transform( vertex.x(), vertex.y() );
270 
271  drawVectorArrow( lineStart, xVal, yVal, V );
272  }
273  }
274 }
275 
276 void QgsMeshVectorRenderer::drawVectorDataOnFaces( const QList<int> &trianglesInExtent )
277 {
278  const QVector<QgsMeshVertex> &centroids = mTriangularMesh.centroids();
279  const QList<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent,
280  mTriangularMesh.trianglesToNativeFaces() );
281  for ( int i : nativeFacesInExtent )
282  {
283  if ( mContext.renderingStopped() )
284  break;
285 
286  QgsPointXY center = centroids.at( i );
287  if ( !mBufferedExtent.contains( center ) )
288  continue;
289 
290  const QgsMeshDatasetValue val = mDatasetValues.value( i );
291  double xVal = val.x();
292  double yVal = val.y();
293  if ( nodataValue( xVal, yVal ) )
294  continue;
295 
296  double V = mDatasetValuesMag[i]; // pre-calculated magnitude
297  QgsPointXY lineStart = mContext.mapToPixel().transform( center.x(), center.y() );
298 
299  drawVectorArrow( lineStart, xVal, yVal, V );
300  }
301 }
302 
303 void QgsMeshVectorRenderer::drawVectorDataOnGrid( const QList<int> &trianglesInExtent )
304 {
305  int cellx = mCfg.userGridCellWidth();
306  int celly = mCfg.userGridCellHeight();
307 
308  const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
309  const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
310 
311  for ( const int i : trianglesInExtent )
312  {
313  if ( mContext.renderingStopped() )
314  break;
315 
316  const QgsMeshFace &face = triangles[i];
317 
318  const int v1 = face[0], v2 = face[1], v3 = face[2];
319  const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
320 
321  const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
322 
323  // Get the BBox of the element in pixels
324  QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
325  int left, right, top, bottom;
326  QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
327 
328  // Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
329  if ( left % cellx != 0 )
330  left += cellx - ( left % cellx );
331  if ( right % cellx != 0 )
332  right -= ( right % cellx );
333  if ( top % celly != 0 )
334  top += celly - ( top % celly );
335  if ( bottom % celly != 0 )
336  bottom -= ( bottom % celly );
337 
338  for ( int y = top; y <= bottom; y += celly )
339  {
340  for ( int x = left; x <= right; x += cellx )
341  {
343  const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
344 
345  if ( mDataOnVertices )
346  {
347  const auto val1 = mDatasetValues.value( v1 );
348  const auto val2 = mDatasetValues.value( v2 );
349  const auto val3 = mDatasetValues.value( v3 );
350  val.setX(
351  QgsMeshLayerUtils::interpolateFromVerticesData(
352  p1, p2, p3,
353  val1.x(),
354  val2.x(),
355  val3.x(),
356  p )
357  );
358  val.setY(
359  QgsMeshLayerUtils::interpolateFromVerticesData(
360  p1, p2, p3,
361  val1.y(),
362  val2.y(),
363  val3.y(),
364  p )
365  );
366  }
367  else
368  {
369  const auto val1 = mDatasetValues.value( nativeFaceIndex );
370  val.setX(
371  QgsMeshLayerUtils::interpolateFromFacesData(
372  p1, p2, p3,
373  val1.x(),
374  p
375  )
376  );
377  val.setY(
378  QgsMeshLayerUtils::interpolateFromFacesData(
379  p1, p2, p3,
380  val1.y(),
381  p
382  )
383  );
384  }
385 
386  if ( nodataValue( val.x(), val.y() ) )
387  continue;
388 
389  QgsPointXY lineStart( x, y );
390  drawVectorArrow( lineStart, val.x(), val.y(), val.scalar() );
391  }
392  }
393  }
394 }
395 
396 void QgsMeshVectorRenderer::drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
397 {
398  QgsPointXY lineEnd;
399  double vectorLength;
400  double cosAlpha, sinAlpha;
401  if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha,
402  lineStart, xVal, yVal, magnitude ) )
403  return;
404 
405  // Make a set of vector head coordinates that we will place at the end of each vector,
406  // scale, translate and rotate.
407  QgsPointXY vectorHeadPoints[3];
408  QVector<QPointF> finalVectorHeadPoints( 3 );
409 
410  double vectorHeadWidthRatio = mCfg.arrowHeadWidthRatio();
411  double vectorHeadLengthRatio = mCfg.arrowHeadLengthRatio();
412 
413  // First head point: top of ->
414  vectorHeadPoints[0].setX( -1.0 * vectorHeadLengthRatio );
415  vectorHeadPoints[0].setY( vectorHeadWidthRatio * 0.5 );
416 
417  // Second head point: right of ->
418  vectorHeadPoints[1].setX( 0.0 );
419  vectorHeadPoints[1].setY( 0.0 );
420 
421  // Third head point: bottom of ->
422  vectorHeadPoints[2].setX( -1.0 * vectorHeadLengthRatio );
423  vectorHeadPoints[2].setY( -1.0 * vectorHeadWidthRatio * 0.5 );
424 
425  // Determine the arrow head coords
426  for ( int j = 0; j < 3; j++ )
427  {
428  finalVectorHeadPoints[j].setX( lineEnd.x()
429  + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength )
430  - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength )
431  );
432 
433  finalVectorHeadPoints[j].setY( lineEnd.y()
434  - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength )
435  - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength )
436  );
437  }
438 
439  // Now actually draw the vector
440  mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
441  mContext.painter()->drawPolygon( finalVectorHeadPoints );
442 }
443 
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.
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 mesh renderer settings for vector datasets.
void setY(double y)
Sets the y value of the point.
Definition: qgspointxy.h:117
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
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.
void setX(double x)
Sets X value.
double x() const
Returns x value.
double x
Definition: qgspoint.h:41