QGIS API Documentation 4.3.0-Master (0cfde48c85b)
Loading...
Searching...
No Matches
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
20#include <cmath>
21
22#include "qgsmaptopixel.h"
23#include "qgsmeshlayerutils.h"
25#include "qgsrendercontext.h"
26#include "qgstriangularmesh.h"
27
28#include <QPainter>
29#include <QPen>
30
32
33inline bool nodataValue( double x, double y )
34{
35 return ( std::isnan( x ) || std::isnan( y ) );
36}
37
38QgsMeshVectorRenderer::~QgsMeshVectorRenderer() = default;
39
40QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
41 const QgsTriangularMesh &m,
42 const QgsMeshDataBlock &datasetVectorValues,
43 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
44 const QVector<double> &datasetValuesMag,
45 double datasetMagMaximumValue,
46 double datasetMagMinimumValue,
48 const QgsVectorFieldSettings &settings,
49 QgsRenderContext &context,
50 const QgsRectangle &layerExtent,
51 QgsRasterBlockFeedback *feedBack,
52 const QSize &size
53)
54{
55 QgsMeshVectorRenderer *renderer = nullptr;
56
57 switch ( settings.symbology() )
58 {
61 renderer = new QgsMeshVectorGlyphRenderer( m, datasetVectorValues, datasetValuesMag, datasetMagMaximumValue, datasetMagMinimumValue, dataType, settings, context, size );
62 break;
65 renderer
66 = new QgsMeshVectorStreamlineRenderer( m, datasetVectorValues, scalarActiveFaceFlagValues, datasetValuesMag, datasetMagMaximumValue, datasetMagMinimumValue, dataType, settings, context, layerExtent, feedBack, size );
67 break;
68 }
69
70 return renderer;
71}
72
73QgsMeshVectorGlyphRenderer::QgsMeshVectorGlyphRenderer(
74 const QgsTriangularMesh &m,
75 const QgsMeshDataBlock &datasetValues,
76 const QVector<double> &datasetValuesMag,
77 double datasetMagMaximumValue,
78 double datasetMagMinimumValue,
80 const QgsVectorFieldSettings &settings,
81 QgsRenderContext &context,
82 QSize size
83)
84 : mTriangularMesh( m )
85 , mDatasetValues( datasetValues )
86 , mDatasetValuesMag( datasetValuesMag )
87 , mMinMag( datasetMagMinimumValue )
88 , mMaxMag( datasetMagMaximumValue )
89 , mDataType( dataType )
90 , mBufferedExtent( context.mapExtent() )
91 , mContext( context )
92 , mCfg( settings )
93 , mOutputSize( size )
94 , mEngine( datasetMagMaximumValue, datasetMagMinimumValue, settings, context, size )
95{
96 // should be checked in caller
97 Q_ASSERT( !mDatasetValuesMag.empty() );
98 Q_ASSERT( !std::isnan( mMinMag ) );
99 Q_ASSERT( !std::isnan( mMaxMag ) );
100 Q_ASSERT( mDatasetValues.isValid() );
101 Q_ASSERT( QgsMeshDataBlock::Vector2DDouble == mDatasetValues.type() );
102
103 // we need to expand out the extent so that it includes
104 // arrows which start or end up outside of the
105 // actual visible extent
106 const double extension = context.convertToMapUnits( calcExtentBufferSize(), Qgis::RenderUnit::Pixels );
107 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
108 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
109 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
110 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
111}
112
113QgsMeshVectorGlyphRenderer::~QgsMeshVectorGlyphRenderer() = default;
114
115void QgsMeshVectorGlyphRenderer::draw()
116{
117 if ( mCfg.isOnUserDefinedGrid() )
118 {
119 drawVectorDataOnGrid();
120 }
122 {
123 drawVectorDataOnVertices();
124 }
126 {
127 drawVectorDataOnFaces();
128 }
130 {
131 drawVectorDataOnEdges();
132 }
133}
134
135double QgsMeshVectorGlyphRenderer::calcExtentBufferSize() const
136{
137 double buffer = 0;
138 switch ( mCfg.arrowSettings().shaftLengthMethod() )
139 {
141 {
142 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(), Qgis::RenderUnit::Millimeters );
143 break;
144 }
146 {
147 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
148 break;
149 }
151 {
152 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(), Qgis::RenderUnit::Millimeters );
153 break;
154 }
155 }
156
157 if ( mCfg.filterMax() >= 0 && buffer > mCfg.filterMax() )
158 buffer = mCfg.filterMax();
159
160 if ( buffer < 0.0 )
161 buffer = 0.0;
162
163 return buffer;
164}
165
166void QgsMeshVectorGlyphRenderer::drawVectorDataOnVertices()
167{
168 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
169 QSet<int> verticesToDraw;
170
171 // currently expecting that triangulation does not add any new extra vertices on the way
172 Q_ASSERT( mDatasetValuesMag.count() == vertices.count() );
173
174 // find all vertices from faces to render
175 {
176 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
177 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
178 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromTriangles( trianglesInExtent, triangles ) );
179 }
180
181 // find all vertices from edges to render
182 {
183 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
184 const QVector<QgsMeshEdge> &edges = mTriangularMesh.edges();
185 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromEdges( edgesInExtent, edges ) );
186 }
187
188 // render
189 drawVectorDataOnPoints( verticesToDraw, vertices );
190}
191
192void QgsMeshVectorGlyphRenderer::drawVectorDataOnPoints( const QSet<int> indexesToRender, const QVector<QgsMeshVertex> &points )
193{
194 for ( const int i : indexesToRender )
195 {
196 if ( mContext.renderingStopped() )
197 break;
198
199 const QgsPointXY center = points.at( i );
200 if ( !mBufferedExtent.contains( center ) )
201 continue;
202
203 const QgsMeshDatasetValue val = mDatasetValues.value( i );
204 const double xVal = val.x();
205 const double yVal = val.y();
206 if ( nodataValue( xVal, yVal ) )
207 continue;
208
209 const double V = mDatasetValuesMag[i]; // pre-calculated magnitude
210 const QgsPointXY lineStart = mContext.mapToPixel().transform( center.x(), center.y() );
211
212 mEngine.drawGlyph( lineStart, xVal, yVal, V );
213 }
214}
215
216void QgsMeshVectorGlyphRenderer::drawVectorDataOnFaces()
217{
218 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
219 const QVector<QgsMeshVertex> &centroids = mTriangularMesh.faceCentroids();
220 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent, mTriangularMesh.trianglesToNativeFaces() );
221 drawVectorDataOnPoints( nativeFacesInExtent, centroids );
222}
223
224void QgsMeshVectorGlyphRenderer::drawVectorDataOnEdges()
225{
226 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
227 const QVector<QgsMeshVertex> &centroids = mTriangularMesh.edgeCentroids();
228 const QSet<int> nativeEdgesInExtent = QgsMeshUtils::nativeEdgesFromEdges( edgesInExtent, mTriangularMesh.edgesToNativeEdges() );
229 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
230}
231
232void QgsMeshVectorGlyphRenderer::drawVectorDataOnGrid()
233{
235 return;
236
237 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
238 const int cellx = mCfg.userGridCellWidth();
239 const int celly = mCfg.userGridCellHeight();
240
241 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
242 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
243
244 for ( const int i : trianglesInExtent )
245 {
246 if ( mContext.renderingStopped() )
247 break;
248
249 const QgsMeshFace &face = triangles[i];
250
251 const int v1 = face[0], v2 = face[1], v3 = face[2];
252 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
253
254 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
255
256 // Get the BBox of the element in pixels
257 const QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
258 int left, right, top, bottom;
259 QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
260
261 // Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
262 if ( left % cellx != 0 )
263 left += cellx - ( left % cellx );
264 if ( right % cellx != 0 )
265 right -= ( right % cellx );
266 if ( top % celly != 0 )
267 top += celly - ( top % celly );
268 if ( bottom % celly != 0 )
269 bottom -= ( bottom % celly );
270
271 for ( int y = top; y <= bottom; y += celly )
272 {
273 for ( int x = left; x <= right; x += cellx )
274 {
276 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
277
279 {
280 const auto val1 = mDatasetValues.value( v1 );
281 const auto val2 = mDatasetValues.value( v2 );
282 const auto val3 = mDatasetValues.value( v3 );
283 val.setX( QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), p ) );
284 val.setY( QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), p ) );
285 }
287 {
288 const auto val1 = mDatasetValues.value( nativeFaceIndex );
289 val.setX( QgsMeshLayerUtils::interpolateFromFacesData( p1, p2, p3, val1.x(), p ) );
290 val.setY( QgsMeshLayerUtils::interpolateFromFacesData( p1, p2, p3, val1.y(), p ) );
291 }
292 if ( nodataValue( val.x(), val.y() ) )
293 continue;
294
295 const QgsPointXY lineStart( x, y );
296 mEngine.drawGlyph( lineStart, val.x(), val.y(), val.scalar() );
297 }
298 }
299 }
300}
301
302QgsMeshVectorStreamlineRenderer::QgsMeshVectorStreamlineRenderer(
303 const QgsTriangularMesh &m,
304 const QgsMeshDataBlock &datasetValues,
305 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
306 const QVector<double> &datasetValuesMag,
307 double datasetMagMaximumValue,
308 double datasetMagMinimumValue,
310 const QgsVectorFieldSettings &settings,
311 QgsRenderContext &context,
312 const QgsRectangle &layerExtent,
313 QgsRasterBlockFeedback *feedBack,
314 QSize size
315)
316 : mCfg( settings )
317 , mFeedBack( feedBack )
318 , mSource( QgsMeshVectorFieldValueSource::create( m, datasetValues, scalarActiveFaceFlagValues, datasetValuesMag, dataType, layerExtent, datasetMagMaximumValue ) )
319 , mEngine( datasetMagMaximumValue, datasetMagMinimumValue, settings, context, size )
320{}
321
322QgsMeshVectorStreamlineRenderer::~QgsMeshVectorStreamlineRenderer() = default;
323
324void QgsMeshVectorStreamlineRenderer::draw()
325{
326 switch ( mCfg.symbology() )
327 {
329 mEngine.drawStreamlines( std::move( mSource ), mFeedBack );
330 break;
332 mEngine.drawTraces( std::move( mSource ) );
333 break;
336 // drawn glyph by glyph, see QgsMeshVectorGlyphRenderer
337 break;
338 }
339}
340
@ WindBarbs
Displaying vector dataset with wind barbs.
Definition qgis.h:7238
@ Arrows
Displaying vector dataset with arrows.
Definition qgis.h:7235
@ Traces
Displaying vector dataset with particle traces.
Definition qgis.h:7237
@ Streamlines
Displaying vector dataset with streamlines.
Definition qgis.h:7236
@ Millimeters
Millimeters.
Definition qgis.h:5759
@ Pixels
Pixels.
Definition qgis.h:5761
@ Fixed
Use fixed length fixedShaftLength() regardless of vector's magnitude.
Definition qgis.h:7222
@ Scaled
Scale vector magnitude by factor scaleFactor().
Definition qgis.h:7221
@ MinMax
Scale vector magnitude linearly to fit in range of vectorFilterMin() and vectorFilterMax().
Definition qgis.h:7220
A block of integers/doubles from a mesh dataset.
@ Vector2DDouble
Vector double pairs (x1, y1, x2, y2, ... ).
DataType
Location of where data is specified for datasets in the dataset group.
@ DataOnEdges
Data is defined on edges.
@ DataOnFaces
Data is defined on faces.
@ DataOnVertices
Data is defined on vertices.
@ DataOnVolumes
Data is defined on volumes.
Represents a single mesh 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.
static QSet< int > nativeEdgesFromEdges(const QList< int > &edgesIndexes, const QVector< int > &edgesToNativeEdges)
Returns unique native faces indexes from list of triangle indexes.
static QSet< int > nativeVerticesFromEdges(const QList< int > &edgesIndexes, const QVector< QgsMeshEdge > &edges)
Returns unique native faces indexes from list of vertices of triangles.
static QSet< int > nativeVerticesFromTriangles(const QList< int > &triangleIndexes, const QVector< QgsMeshFace > &triangles)
Returns unique native vertex indexes from list of vertices of triangles.
static QSet< int > nativeFacesFromTriangles(const QList< int > &triangleIndexes, const QVector< int > &trianglesToNativeFaces)
Returns unique native faces indexes from list of triangle indexes.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
Feedback object tailored for raster block reading.
A rectangle specified with double values.
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.
A triangular/derived mesh with vertices in map coordinates.
Represents a renderer settings for vector datasets.
Qgis::VectorFieldSymbology symbology() const
Returns the displaying method used to render vector datasets.
QVector< int > QgsMeshFace
List of vertex indexes.