34#define M_DEG2RAD 0.0174532925
37inline bool nodataValue(
double x,
double y )
39 return ( std::isnan( x ) || std::isnan( y ) );
42QgsMeshVectorArrowRenderer::QgsMeshVectorArrowRenderer(
45 const QVector<double> &datasetValuesMag,
46 double datasetMagMaximumValue,
double datasetMagMinimumValue,
52 , mDatasetValues( datasetValues )
53 , mDatasetValuesMag( datasetValuesMag )
54 , mMinMag( datasetMagMinimumValue )
55 , mMaxMag( datasetMagMaximumValue )
56 , mDataType( dataType )
57 , mBufferedExtent( context.mapExtent() )
63 Q_ASSERT( !mDatasetValuesMag.empty() );
64 Q_ASSERT( !std::isnan( mMinMag ) );
65 Q_ASSERT( !std::isnan( mMaxMag ) );
66 Q_ASSERT( mDatasetValues.isValid() );
73 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
74 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
75 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
76 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
81QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() =
default;
83void QgsMeshVectorArrowRenderer::draw()
86 QPainter *painter = mContext.painter();
89 mContext.setPainterFlagsUsingContext( painter );
91 QPen pen = painter->pen();
92 pen.setCapStyle( Qt::FlatCap );
93 pen.setJoinStyle( Qt::MiterJoin );
95 const double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(),
97 pen.setWidthF( penWidth );
98 painter->setPen( pen );
100 if ( mCfg.isOnUserDefinedGrid() )
102 drawVectorDataOnGrid( );
106 drawVectorDataOnVertices( );
110 drawVectorDataOnFaces( );
114 drawVectorDataOnEdges( );
118bool QgsMeshVectorArrowRenderer::calcVectorLineEnd(
120 double &vectorLength,
131 if ( xVal == 0.0 && yVal == 0.0 )
135 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
137 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
142 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
144 cosAlpha = cos( vectorAngle );
145 sinAlpha = sin( vectorAngle );
150 switch ( mCfg.arrowSettings().shaftLengthMethod() )
154 const double minShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().minShaftLength(),
156 const double maxShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
158 const double minVal = mMinMag;
159 const double maxVal = mMaxMag;
160 const double k = ( magnitude - minVal ) / ( maxVal - minVal );
161 const double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
162 xDist = cosAlpha * L;
163 yDist = sinAlpha * L;
168 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
169 xDist = scaleFactor * xVal;
170 yDist = scaleFactor * yVal;
176 const double fixedShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
178 xDist = cosAlpha * fixedShaftLength;
179 yDist = sinAlpha * fixedShaftLength;
187 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
192 lineStart.
y() + yDist );
194 vectorLength = sqrt( xDist * xDist + yDist * yDist );
197 if ( !
QgsRectangle( lineStart, lineEnd ).intersects(
QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
203double QgsMeshVectorArrowRenderer::calcExtentBufferSize()
const
206 switch ( mCfg.arrowSettings().shaftLengthMethod() )
210 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
216 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
221 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
227 if ( mCfg.filterMax() >= 0 && buffer > mCfg.filterMax() )
228 buffer = mCfg.filterMax();
237void QgsMeshVectorArrowRenderer::drawVectorDataOnVertices()
239 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
240 QSet<int> verticesToDraw;
243 Q_ASSERT( mDatasetValuesMag.count() == vertices.count() );
247 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
248 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
249 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromTriangles( trianglesInExtent, triangles ) );
254 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
255 const QVector<QgsMeshEdge> &edges = mTriangularMesh.edges();
256 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromEdges( edgesInExtent, edges ) );
260 drawVectorDataOnPoints( verticesToDraw, vertices );
263void QgsMeshVectorArrowRenderer::drawVectorDataOnPoints(
const QSet<int> indexesToRender,
const QVector<QgsMeshVertex> &points )
265 for (
const int i : indexesToRender )
267 if ( mContext.renderingStopped() )
271 if ( !mBufferedExtent.contains( center ) )
275 const double xVal = val.
x();
276 const double yVal = val.
y();
277 if ( nodataValue( xVal, yVal ) )
280 const double V = mDatasetValuesMag[i];
281 const QgsPointXY lineStart = mContext.mapToPixel().transform( center.
x(), center.
y() );
283 drawVector( lineStart, xVal, yVal, V );
287void QgsMeshVectorArrowRenderer::drawVectorDataOnFaces( )
289 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
290 const QVector<QgsMeshVertex> ¢roids = mTriangularMesh.faceCentroids();
291 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent,
292 mTriangularMesh.trianglesToNativeFaces() );
293 drawVectorDataOnPoints( nativeFacesInExtent, centroids );
296void QgsMeshVectorArrowRenderer::drawVectorDataOnEdges()
298 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
299 const QVector<QgsMeshVertex> ¢roids = mTriangularMesh.edgeCentroids();
300 const QSet<int> nativeEdgesInExtent = QgsMeshUtils::nativeEdgesFromEdges( edgesInExtent,
301 mTriangularMesh.edgesToNativeEdges() );
302 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
305void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid( )
311 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
312 const int cellx = mCfg.userGridCellWidth();
313 const int celly = mCfg.userGridCellHeight();
315 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
316 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
318 for (
const int i : trianglesInExtent )
320 if ( mContext.renderingStopped() )
325 const int v1 = face[0], v2 = face[1], v3 = face[2];
326 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
328 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
331 const QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
332 int left, right, top, bottom;
333 QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
336 if ( left % cellx != 0 )
337 left += cellx - ( left % cellx );
338 if ( right % cellx != 0 )
339 right -= ( right % cellx );
340 if ( top % celly != 0 )
341 top += celly - ( top % celly );
342 if ( bottom % celly != 0 )
343 bottom -= ( bottom % celly );
345 for (
int y = top; y <= bottom; y += celly )
347 for (
int x = left; x <= right; x += cellx )
350 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
354 const auto val1 = mDatasetValues.value( v1 );
355 const auto val2 = mDatasetValues.value( v2 );
356 const auto val3 = mDatasetValues.value( v3 );
358 QgsMeshLayerUtils::interpolateFromVerticesData(
366 QgsMeshLayerUtils::interpolateFromVerticesData(
376 const auto val1 = mDatasetValues.value( nativeFaceIndex );
378 QgsMeshLayerUtils::interpolateFromFacesData(
385 QgsMeshLayerUtils::interpolateFromFacesData(
392 if ( nodataValue( val.
x(), val.
y() ) )
396 drawVector( lineStart, val.
x(), val.
y(), val.
scalar() );
402void QgsMeshVectorArrowRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
406 double cosAlpha, sinAlpha;
407 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha,
408 lineStart, xVal, yVal, magnitude ) )
414 QVector<QPointF> finalVectorHeadPoints( 3 );
416 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
417 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
420 vectorHeadPoints[0].
setX( -1.0 * vectorHeadLengthRatio );
421 vectorHeadPoints[0].
setY( vectorHeadWidthRatio * 0.5 );
424 vectorHeadPoints[1].
setX( 0.0 );
425 vectorHeadPoints[1].
setY( 0.0 );
428 vectorHeadPoints[2].
setX( -1.0 * vectorHeadLengthRatio );
429 vectorHeadPoints[2].
setY( -1.0 * vectorHeadWidthRatio * 0.5 );
432 for (
int j = 0; j < 3; j++ )
434 finalVectorHeadPoints[j].setX( lineEnd.
x()
435 + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength )
436 - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength )
439 finalVectorHeadPoints[j].setY( lineEnd.
y()
440 - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength )
441 - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength )
446 QPen pen( mContext.painter()->pen() );
447 pen.setColor( mVectorColoring.color( magnitude ) );
448 mContext.painter()->setPen( pen );
450 mContext.painter()->drawPolygon( finalVectorHeadPoints );
453QgsMeshVectorRenderer::~QgsMeshVectorRenderer() =
default;
455QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
459 const QVector<double> &datasetValuesMag,
460 double datasetMagMaximumValue,
461 double datasetMagMinimumValue,
466 QgsMeshLayerRendererFeedback *feedBack,
469 QgsMeshVectorRenderer *renderer =
nullptr;
474 renderer =
new QgsMeshVectorArrowRenderer(
478 datasetMagMaximumValue,
479 datasetMagMinimumValue,
486 renderer =
new QgsMeshVectorStreamlineRenderer(
489 scalarActiveFaceFlagValues,
496 datasetMagMaximumValue );
499 renderer =
new QgsMeshVectorTraceRenderer(
502 scalarActiveFaceFlagValues,
507 datasetMagMaximumValue );
510 renderer =
new QgsMeshVectorWindBarbRenderer(
514 datasetMagMaximumValue,
515 datasetMagMinimumValue,
527QgsMeshVectorWindBarbRenderer::QgsMeshVectorWindBarbRenderer(
530 const QVector<double> &datasetValuesMag,
531 double datasetMagMaximumValue,
double datasetMagMinimumValue,
535 QSize size ) : QgsMeshVectorArrowRenderer( m,
538 datasetMagMinimumValue,
539 datasetMagMaximumValue,
549QgsMeshVectorWindBarbRenderer::~QgsMeshVectorWindBarbRenderer() =
default;
551void QgsMeshVectorWindBarbRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
554 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
556 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
559 QPen pen( mContext.painter()->pen() );
560 pen.setColor( mVectorColoring.color( magnitude ) );
561 mContext.painter()->setPen( pen );
564 QBrush brush( pen.color() );
565 mContext.painter()->setBrush( brush );
567 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(),
568 mCfg.windBarbSettings().shaftLengthUnits() );
569 if ( shaftLength < 1 )
573 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.
x(), lineStart.
y() );
574 bool isNorthHemisphere =
true;
577 const QgsPointXY geoPoint = mGeographicTransform.transform( mapPoint );
578 isNorthHemisphere = geoPoint.
y() >= 0;
582 QgsDebugError( QStringLiteral(
"Could not transform wind barb coordinates to geographic ones" ) );
585 const double d = shaftLength / 25;
586 const double centerRadius = d;
587 const double zeroCircleRadius = 2 * d;
588 const double barbLength = 8 * d + pen.widthF();
589 const double barbAngle = 135;
590 const double barbOffset = 2 * d + pen.widthF();
591 const int sign = isNorthHemisphere ? 1 : -1;
595 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
599 const double xDist = cos( vectorAngle ) * shaftLength;
600 const double yDist = - sin( vectorAngle ) * shaftLength;
604 lineStart.
y() - yDist );
607 if ( !
QgsRectangle( lineStart, lineEnd ).intersects(
QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
611 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier() ;
617 mContext.painter()->setBrush( Qt::NoBrush );
618 mContext.painter()->drawEllipse( lineStart.
toQPointF(), zeroCircleRadius, zeroCircleRadius );
619 mContext.painter()->setBrush( brush );
623 const double azimuth = lineEnd.
azimuth( lineStart );
626 if ( knots < 47.5 && knots > 7.5 )
629 const QVector< QPointF > pts{ lineStart.
toQPointF(),
631 nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() };
632 mContext.painter()->drawPolyline( pts );
633 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
643 mContext.painter()->drawEllipse( lineStart.
toQPointF(), centerRadius, centerRadius );
646 while ( knots > 47.5 )
648 const QVector< QPointF > pts{ nextLineOrigin.
toQPointF(),
649 nextLineOrigin.
project( barbLength / 1.414, azimuth + 90 * sign ).
toQPointF(),
651 mContext.painter()->drawPolygon( pts );
656 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414, azimuth );
658 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414 + barbOffset, azimuth );
662 while ( knots > 7.5 )
664 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() );
665 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
673 if ( nextLineOrigin == lineEnd )
674 nextLineOrigin = nextLineOrigin.
project( barbLength / 2, azimuth );
676 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength / 2, azimuth + barbAngle * sign ).
toQPointF() );
@ Millimeters
Millimeters.
This class 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.
QgsMeshDataBlock is a block of integers/doubles that can be used to retrieve: active flags (e....
@ Vector2DDouble
Vector double pairs (x1, y1, x2, y2, ... )
QgsMeshDatasetValue represents single 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.
A class to represent a 2D point.
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.
Triangular/Derived Mesh is mesh with vertices in map coordinates.
#define QgsDebugError(str)
QVector< int > QgsMeshFace
List of vertex indexes.