35#define M_DEG2RAD 0.0174532925
38inline bool nodataValue(
double x,
double y )
40 return ( std::isnan( x ) || std::isnan( y ) );
43QgsMeshVectorArrowRenderer::QgsMeshVectorArrowRenderer(
46 const QVector<double> &datasetValuesMag,
47 double datasetMagMaximumValue,
double datasetMagMinimumValue,
53 , mDatasetValues( datasetValues )
54 , mDatasetValuesMag( datasetValuesMag )
55 , mMinMag( datasetMagMinimumValue )
56 , mMaxMag( datasetMagMaximumValue )
57 , mDataType( dataType )
58 , mBufferedExtent( context.mapExtent() )
64 Q_ASSERT( !mDatasetValuesMag.empty() );
65 Q_ASSERT( !std::isnan( mMinMag ) );
66 Q_ASSERT( !std::isnan( mMaxMag ) );
67 Q_ASSERT( mDatasetValues.isValid() );
74 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
75 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
76 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
77 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
82QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() =
default;
84void QgsMeshVectorArrowRenderer::draw()
87 QPainter *painter = mContext.painter();
90 mContext.setPainterFlagsUsingContext( painter );
92 QPen pen = painter->pen();
93 pen.setCapStyle( Qt::FlatCap );
94 pen.setJoinStyle( Qt::MiterJoin );
96 const double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(),
98 pen.setWidthF( penWidth );
99 painter->setPen( pen );
101 if ( mCfg.isOnUserDefinedGrid() )
103 drawVectorDataOnGrid( );
107 drawVectorDataOnVertices( );
111 drawVectorDataOnFaces( );
115 drawVectorDataOnEdges( );
119bool QgsMeshVectorArrowRenderer::calcVectorLineEnd(
121 double &vectorLength,
132 if ( xVal == 0.0 && yVal == 0.0 )
136 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
138 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
143 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
145 cosAlpha = cos( vectorAngle );
146 sinAlpha = sin( vectorAngle );
151 switch ( mCfg.arrowSettings().shaftLengthMethod() )
155 const double minShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().minShaftLength(),
157 const double maxShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
159 const double minVal = mMinMag;
160 const double maxVal = mMaxMag;
161 const double k = ( magnitude - minVal ) / ( maxVal - minVal );
162 const double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
163 xDist = cosAlpha * L;
164 yDist = sinAlpha * L;
169 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
170 xDist = scaleFactor * xVal;
171 yDist = scaleFactor * yVal;
177 const double fixedShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
179 xDist = cosAlpha * fixedShaftLength;
180 yDist = sinAlpha * fixedShaftLength;
188 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
193 lineStart.
y() + yDist );
195 vectorLength = sqrt( xDist * xDist + yDist * yDist );
198 if ( !
QgsRectangle( lineStart, lineEnd ).intersects(
QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
204double QgsMeshVectorArrowRenderer::calcExtentBufferSize()
const
207 switch ( mCfg.arrowSettings().shaftLengthMethod() )
211 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
217 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
222 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
228 if ( mCfg.filterMax() >= 0 && buffer > mCfg.filterMax() )
229 buffer = mCfg.filterMax();
238void QgsMeshVectorArrowRenderer::drawVectorDataOnVertices()
240 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
241 QSet<int> verticesToDraw;
244 Q_ASSERT( mDatasetValuesMag.count() == vertices.count() );
248 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
249 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
255 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
256 const QVector<QgsMeshEdge> &edges = mTriangularMesh.edges();
261 drawVectorDataOnPoints( verticesToDraw, vertices );
264void QgsMeshVectorArrowRenderer::drawVectorDataOnPoints(
const QSet<int> indexesToRender,
const QVector<QgsMeshVertex> &points )
266 for (
const int i : indexesToRender )
268 if ( mContext.renderingStopped() )
272 if ( !mBufferedExtent.contains( center ) )
276 const double xVal = val.
x();
277 const double yVal = val.
y();
278 if ( nodataValue( xVal, yVal ) )
281 const double V = mDatasetValuesMag[i];
282 const QgsPointXY lineStart = mContext.mapToPixel().transform( center.
x(), center.
y() );
284 drawVector( lineStart, xVal, yVal, V );
288void QgsMeshVectorArrowRenderer::drawVectorDataOnFaces( )
290 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
291 const QVector<QgsMeshVertex> ¢roids = mTriangularMesh.faceCentroids();
293 mTriangularMesh.trianglesToNativeFaces() );
294 drawVectorDataOnPoints( nativeFacesInExtent, centroids );
297void QgsMeshVectorArrowRenderer::drawVectorDataOnEdges()
299 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
300 const QVector<QgsMeshVertex> ¢roids = mTriangularMesh.edgeCentroids();
302 mTriangularMesh.edgesToNativeEdges() );
303 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
306void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid( )
312 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
313 const int cellx = mCfg.userGridCellWidth();
314 const int celly = mCfg.userGridCellHeight();
316 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
317 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
319 for (
const int i : trianglesInExtent )
321 if ( mContext.renderingStopped() )
326 const int v1 = face[0], v2 = face[1], v3 = face[2];
327 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
329 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
332 const QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
333 int left, right, top, bottom;
334 QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
337 if ( left % cellx != 0 )
338 left += cellx - ( left % cellx );
339 if ( right % cellx != 0 )
340 right -= ( right % cellx );
341 if ( top % celly != 0 )
342 top += celly - ( top % celly );
343 if ( bottom % celly != 0 )
344 bottom -= ( bottom % celly );
346 for (
int y = top; y <= bottom; y += celly )
348 for (
int x = left; x <= right; x += cellx )
351 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
355 const auto val1 = mDatasetValues.value( v1 );
356 const auto val2 = mDatasetValues.value( v2 );
357 const auto val3 = mDatasetValues.value( v3 );
359 QgsMeshLayerUtils::interpolateFromVerticesData(
367 QgsMeshLayerUtils::interpolateFromVerticesData(
377 const auto val1 = mDatasetValues.value( nativeFaceIndex );
379 QgsMeshLayerUtils::interpolateFromFacesData(
386 QgsMeshLayerUtils::interpolateFromFacesData(
393 if ( nodataValue( val.
x(), val.
y() ) )
397 drawVector( lineStart, val.
x(), val.
y(), val.
scalar() );
403void QgsMeshVectorArrowRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
407 double cosAlpha, sinAlpha;
408 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha,
409 lineStart, xVal, yVal, magnitude ) )
415 QVector<QPointF> finalVectorHeadPoints( 3 );
417 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
418 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
421 vectorHeadPoints[0].
setX( -1.0 * vectorHeadLengthRatio );
422 vectorHeadPoints[0].
setY( vectorHeadWidthRatio * 0.5 );
425 vectorHeadPoints[1].
setX( 0.0 );
426 vectorHeadPoints[1].
setY( 0.0 );
429 vectorHeadPoints[2].
setX( -1.0 * vectorHeadLengthRatio );
430 vectorHeadPoints[2].
setY( -1.0 * vectorHeadWidthRatio * 0.5 );
433 for (
int j = 0; j < 3; j++ )
435 finalVectorHeadPoints[j].setX( lineEnd.
x()
436 + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength )
437 - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength )
440 finalVectorHeadPoints[j].setY( lineEnd.
y()
441 - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength )
442 - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength )
447 QPen pen( mContext.painter()->pen() );
448 pen.setColor( mVectorColoring.color( magnitude ) );
449 mContext.painter()->setPen( pen );
451 mContext.painter()->drawPolygon( finalVectorHeadPoints );
454QgsMeshVectorRenderer::~QgsMeshVectorRenderer() =
default;
456QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
460 const QVector<double> &datasetValuesMag,
461 double datasetMagMaximumValue,
462 double datasetMagMinimumValue,
467 QgsMeshLayerRendererFeedback *feedBack,
470 QgsMeshVectorRenderer *renderer =
nullptr;
475 renderer =
new QgsMeshVectorArrowRenderer(
479 datasetMagMaximumValue,
480 datasetMagMinimumValue,
487 renderer =
new QgsMeshVectorStreamlineRenderer(
490 scalarActiveFaceFlagValues,
497 datasetMagMaximumValue );
500 renderer =
new QgsMeshVectorTraceRenderer(
503 scalarActiveFaceFlagValues,
508 datasetMagMaximumValue );
511 renderer =
new QgsMeshVectorWindBarbRenderer(
515 datasetMagMaximumValue,
516 datasetMagMinimumValue,
528QgsMeshVectorWindBarbRenderer::QgsMeshVectorWindBarbRenderer(
531 const QVector<double> &datasetValuesMag,
532 double datasetMagMaximumValue,
double datasetMagMinimumValue,
536 QSize size ) : QgsMeshVectorArrowRenderer( m,
539 datasetMagMinimumValue,
540 datasetMagMaximumValue,
550QgsMeshVectorWindBarbRenderer::~QgsMeshVectorWindBarbRenderer() =
default;
552void QgsMeshVectorWindBarbRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
555 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
557 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
560 QPen pen( mContext.painter()->pen() );
561 pen.setColor( mVectorColoring.color( magnitude ) );
562 mContext.painter()->setPen( pen );
565 QBrush brush( pen.color() );
566 mContext.painter()->setBrush( brush );
568 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(),
569 mCfg.windBarbSettings().shaftLengthUnits() );
570 if ( shaftLength < 1 )
574 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.
x(), lineStart.
y() );
575 bool isNorthHemisphere =
true;
578 const QgsPointXY geoPoint = mGeographicTransform.transform( mapPoint );
579 isNorthHemisphere = geoPoint.
y() >= 0;
583 QgsDebugError( QStringLiteral(
"Could not transform wind barb coordinates to geographic ones" ) );
586 const double d = shaftLength / 25;
587 const double centerRadius = d;
588 const double zeroCircleRadius = 2 * d;
589 const double barbLength = 8 * d + pen.widthF();
590 const double barbAngle = 135;
591 const double barbOffset = 2 * d + pen.widthF();
592 const int sign = isNorthHemisphere ? 1 : -1;
596 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
600 const double xDist = cos( vectorAngle ) * shaftLength;
601 const double yDist = - sin( vectorAngle ) * shaftLength;
605 lineStart.
y() - yDist );
608 if ( !
QgsRectangle( lineStart, lineEnd ).intersects(
QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
612 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier() ;
618 mContext.painter()->setBrush( Qt::NoBrush );
619 mContext.painter()->drawEllipse( lineStart.
toQPointF(), zeroCircleRadius, zeroCircleRadius );
620 mContext.painter()->setBrush( brush );
624 const double azimuth = lineEnd.
azimuth( lineStart );
627 if ( knots < 47.5 && knots > 7.5 )
630 const QVector< QPointF > pts{ lineStart.
toQPointF(),
632 nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() };
633 mContext.painter()->drawPolyline( pts );
634 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
644 mContext.painter()->drawEllipse( lineStart.
toQPointF(), centerRadius, centerRadius );
647 while ( knots > 47.5 )
649 const QVector< QPointF > pts{ nextLineOrigin.
toQPointF(),
650 nextLineOrigin.
project( barbLength / 1.414, azimuth + 90 * sign ).
toQPointF(),
652 mContext.painter()->drawPolygon( pts );
657 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414, azimuth );
659 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414 + barbOffset, azimuth );
663 while ( knots > 7.5 )
665 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() );
666 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
674 if ( nextLineOrigin == lineEnd )
675 nextLineOrigin = nextLineOrigin.
project( barbLength / 2, azimuth );
677 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength / 2, azimuth + barbAngle * sign ).
toQPointF() );
@ Millimeters
Millimeters.
Represents a coordinate reference system (CRS).
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
Custom exception class for Coordinate Reference System related exceptions.
A block of integers/doubles from a mesh dataset.
@ Vector2DDouble
Vector double pairs (x1, y1, x2, y2, ... )
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.
@ Scaled
Scale vector magnitude by factor scaleFactor()
@ MinMax
Scale vector magnitude linearly to fit in range of vectorFilterMin() and vectorFilterMax()
@ Fixed
Use fixed length fixedShaftLength() regardless of vector's magnitude.
Represents a renderer settings for vector datasets.
@ Traces
Displaying vector dataset with particle traces.
@ Arrows
Displaying vector dataset with arrows.
@ WindBarbs
Displaying vector dataset with wind barbs.
@ 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.
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.
QgsPointXY project(double distance, double bearing) const
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
void setY(double y)
Sets the y value of the point.
double azimuth(const QgsPointXY &other) const
Calculates azimuth between this point and other one (clockwise in degree, starting from north)
void setX(double x)
Sets the x value of the point.
QPointF toQPointF() const
Converts a point to a QPointF.
Point geometry type, with support for z-dimension and m-values.
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.
Scoped object for saving and restoring a QPainter object's state.
A triangular/derived mesh with vertices in map coordinates.
#define QgsDebugError(str)
QVector< int > QgsMeshFace
List of vertex indexes.