37#define M_DEG2RAD 0.0174532925
40inline bool nodataValue(
double x,
double y )
42 return ( std::isnan( x ) || std::isnan( y ) );
45QgsMeshVectorArrowRenderer::QgsMeshVectorArrowRenderer(
48 const QVector<double> &datasetValuesMag,
49 double datasetMagMaximumValue,
double datasetMagMinimumValue,
55 , mDatasetValues( datasetValues )
56 , mDatasetValuesMag( datasetValuesMag )
57 , mMinMag( datasetMagMinimumValue )
58 , mMaxMag( datasetMagMaximumValue )
59 , mDataType( dataType )
60 , mBufferedExtent( context.mapExtent() )
66 Q_ASSERT( !mDatasetValuesMag.empty() );
67 Q_ASSERT( !std::isnan( mMinMag ) );
68 Q_ASSERT( !std::isnan( mMaxMag ) );
69 Q_ASSERT( mDatasetValues.isValid() );
76 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
77 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
78 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
79 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
84QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() =
default;
86void QgsMeshVectorArrowRenderer::draw()
89 QPainter *painter = mContext.painter();
92 mContext.setPainterFlagsUsingContext( painter );
94 QPen pen = painter->pen();
95 pen.setCapStyle( Qt::FlatCap );
96 pen.setJoinStyle( Qt::MiterJoin );
98 const double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(),
100 pen.setWidthF( penWidth );
101 painter->setPen( pen );
103 if ( mCfg.isOnUserDefinedGrid() )
105 drawVectorDataOnGrid( );
109 drawVectorDataOnVertices( );
113 drawVectorDataOnFaces( );
117 drawVectorDataOnEdges( );
121bool QgsMeshVectorArrowRenderer::calcVectorLineEnd(
123 double &vectorLength,
134 if ( xVal == 0.0 && yVal == 0.0 )
138 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
140 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
145 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
147 cosAlpha = cos( vectorAngle );
148 sinAlpha = sin( vectorAngle );
153 switch ( mCfg.arrowSettings().shaftLengthMethod() )
157 const double minShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().minShaftLength(),
159 const double maxShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
161 const double minVal = mMinMag;
162 const double maxVal = mMaxMag;
163 const double k = ( magnitude - minVal ) / ( maxVal - minVal );
164 const double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
165 xDist = cosAlpha * L;
166 yDist = sinAlpha * L;
171 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
172 xDist = scaleFactor * xVal;
173 yDist = scaleFactor * yVal;
179 const double fixedShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
181 xDist = cosAlpha * fixedShaftLength;
182 yDist = sinAlpha * fixedShaftLength;
190 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
195 lineStart.
y() + yDist );
197 vectorLength = sqrt( xDist * xDist + yDist * yDist );
200 if ( !
QgsRectangle( lineStart, lineEnd ).intersects(
QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
206double QgsMeshVectorArrowRenderer::calcExtentBufferSize()
const
209 switch ( mCfg.arrowSettings().shaftLengthMethod() )
213 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
219 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
224 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
230 if ( mCfg.filterMax() >= 0 && buffer > mCfg.filterMax() )
231 buffer = mCfg.filterMax();
240void QgsMeshVectorArrowRenderer::drawVectorDataOnVertices()
242 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
243 QSet<int> verticesToDraw;
246 Q_ASSERT( mDatasetValuesMag.count() == vertices.count() );
250 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
251 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
257 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
258 const QVector<QgsMeshEdge> &edges = mTriangularMesh.edges();
263 drawVectorDataOnPoints( verticesToDraw, vertices );
266void QgsMeshVectorArrowRenderer::drawVectorDataOnPoints(
const QSet<int> indexesToRender,
const QVector<QgsMeshVertex> &points )
268 for (
const int i : indexesToRender )
270 if ( mContext.renderingStopped() )
274 if ( !mBufferedExtent.contains( center ) )
278 const double xVal = val.
x();
279 const double yVal = val.
y();
280 if ( nodataValue( xVal, yVal ) )
283 const double V = mDatasetValuesMag[i];
284 const QgsPointXY lineStart = mContext.mapToPixel().transform( center.
x(), center.
y() );
286 drawVector( lineStart, xVal, yVal, V );
290void QgsMeshVectorArrowRenderer::drawVectorDataOnFaces( )
292 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
293 const QVector<QgsMeshVertex> ¢roids = mTriangularMesh.faceCentroids();
295 mTriangularMesh.trianglesToNativeFaces() );
296 drawVectorDataOnPoints( nativeFacesInExtent, centroids );
299void QgsMeshVectorArrowRenderer::drawVectorDataOnEdges()
301 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
302 const QVector<QgsMeshVertex> ¢roids = mTriangularMesh.edgeCentroids();
304 mTriangularMesh.edgesToNativeEdges() );
305 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
308void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid( )
314 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
315 const int cellx = mCfg.userGridCellWidth();
316 const int celly = mCfg.userGridCellHeight();
318 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
319 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
321 for (
const int i : trianglesInExtent )
323 if ( mContext.renderingStopped() )
328 const int v1 = face[0], v2 = face[1], v3 = face[2];
329 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
331 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
334 const QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
335 int left, right, top, bottom;
336 QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
339 if ( left % cellx != 0 )
340 left += cellx - ( left % cellx );
341 if ( right % cellx != 0 )
342 right -= ( right % cellx );
343 if ( top % celly != 0 )
344 top += celly - ( top % celly );
345 if ( bottom % celly != 0 )
346 bottom -= ( bottom % celly );
348 for (
int y = top; y <= bottom; y += celly )
350 for (
int x = left; x <= right; x += cellx )
353 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
357 const auto val1 = mDatasetValues.value( v1 );
358 const auto val2 = mDatasetValues.value( v2 );
359 const auto val3 = mDatasetValues.value( v3 );
361 QgsMeshLayerUtils::interpolateFromVerticesData(
369 QgsMeshLayerUtils::interpolateFromVerticesData(
379 const auto val1 = mDatasetValues.value( nativeFaceIndex );
381 QgsMeshLayerUtils::interpolateFromFacesData(
388 QgsMeshLayerUtils::interpolateFromFacesData(
395 if ( nodataValue( val.
x(), val.
y() ) )
399 drawVector( lineStart, val.
x(), val.
y(), val.
scalar() );
405void QgsMeshVectorArrowRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
409 double cosAlpha, sinAlpha;
410 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha,
411 lineStart, xVal, yVal, magnitude ) )
417 QVector<QPointF> finalVectorHeadPoints( 3 );
419 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
420 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
423 vectorHeadPoints[0].
setX( -1.0 * vectorHeadLengthRatio );
424 vectorHeadPoints[0].
setY( vectorHeadWidthRatio * 0.5 );
427 vectorHeadPoints[1].
setX( 0.0 );
428 vectorHeadPoints[1].
setY( 0.0 );
431 vectorHeadPoints[2].
setX( -1.0 * vectorHeadLengthRatio );
432 vectorHeadPoints[2].
setY( -1.0 * vectorHeadWidthRatio * 0.5 );
435 for (
int j = 0; j < 3; j++ )
437 finalVectorHeadPoints[j].setX( lineEnd.
x()
438 + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength )
439 - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength )
442 finalVectorHeadPoints[j].setY( lineEnd.
y()
443 - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength )
444 - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength )
449 QPen pen( mContext.painter()->pen() );
450 pen.setColor( mVectorColoring.color( magnitude ) );
451 mContext.painter()->setPen( pen );
453 mContext.painter()->drawPolygon( finalVectorHeadPoints );
456QgsMeshVectorRenderer::~QgsMeshVectorRenderer() =
default;
458QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
462 const QVector<double> &datasetValuesMag,
463 double datasetMagMaximumValue,
464 double datasetMagMinimumValue,
469 QgsMeshLayerRendererFeedback *feedBack,
472 QgsMeshVectorRenderer *renderer =
nullptr;
477 renderer =
new QgsMeshVectorArrowRenderer(
481 datasetMagMaximumValue,
482 datasetMagMinimumValue,
489 renderer =
new QgsMeshVectorStreamlineRenderer(
492 scalarActiveFaceFlagValues,
499 datasetMagMaximumValue );
502 renderer =
new QgsMeshVectorTraceRenderer(
505 scalarActiveFaceFlagValues,
510 datasetMagMaximumValue );
513 renderer =
new QgsMeshVectorWindBarbRenderer(
517 datasetMagMaximumValue,
518 datasetMagMinimumValue,
530QgsMeshVectorWindBarbRenderer::QgsMeshVectorWindBarbRenderer(
533 const QVector<double> &datasetValuesMag,
534 double datasetMagMaximumValue,
double datasetMagMinimumValue,
538 QSize size ) : QgsMeshVectorArrowRenderer( m,
541 datasetMagMinimumValue,
542 datasetMagMaximumValue,
552QgsMeshVectorWindBarbRenderer::~QgsMeshVectorWindBarbRenderer() =
default;
554void QgsMeshVectorWindBarbRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
557 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
559 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
562 QPen pen( mContext.painter()->pen() );
563 pen.setColor( mVectorColoring.color( magnitude ) );
564 mContext.painter()->setPen( pen );
567 QBrush brush( pen.color() );
568 mContext.painter()->setBrush( brush );
570 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(),
571 mCfg.windBarbSettings().shaftLengthUnits() );
572 if ( shaftLength < 1 )
576 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.
x(), lineStart.
y() );
577 bool isNorthHemisphere =
true;
580 const QgsPointXY geoPoint = mGeographicTransform.transform( mapPoint );
581 isNorthHemisphere = geoPoint.
y() >= 0;
585 QgsDebugError( QStringLiteral(
"Could not transform wind barb coordinates to geographic ones" ) );
588 const double d = shaftLength / 25;
589 const double centerRadius = d;
590 const double zeroCircleRadius = 2 * d;
591 const double barbLength = 8 * d + pen.widthF();
592 const double barbAngle = 135;
593 const double barbOffset = 2 * d + pen.widthF();
594 const int sign = isNorthHemisphere ? 1 : -1;
598 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
602 const double xDist = cos( vectorAngle ) * shaftLength;
603 const double yDist = - sin( vectorAngle ) * shaftLength;
607 lineStart.
y() - yDist );
610 if ( !
QgsRectangle( lineStart, lineEnd ).intersects(
QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
614 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier() ;
620 mContext.painter()->setBrush( Qt::NoBrush );
621 mContext.painter()->drawEllipse( lineStart.
toQPointF(), zeroCircleRadius, zeroCircleRadius );
622 mContext.painter()->setBrush( brush );
626 const double azimuth = lineEnd.
azimuth( lineStart );
629 if ( knots < 47.5 && knots > 7.5 )
632 const QVector< QPointF > pts{ lineStart.
toQPointF(),
634 nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() };
635 mContext.painter()->drawPolyline( pts );
636 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
646 mContext.painter()->drawEllipse( lineStart.
toQPointF(), centerRadius, centerRadius );
649 while ( knots > 47.5 )
651 const QVector< QPointF > pts{ nextLineOrigin.
toQPointF(),
652 nextLineOrigin.
project( barbLength / 1.414, azimuth + 90 * sign ).
toQPointF(),
654 mContext.painter()->drawPolygon( pts );
659 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414, azimuth );
661 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414 + barbOffset, azimuth );
665 while ( knots > 7.5 )
667 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() );
668 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
676 if ( nextLineOrigin == lineEnd )
677 nextLineOrigin = nextLineOrigin.
project( barbLength / 2, azimuth );
679 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.