35using namespace Qt::StringLiterals;
40#define M_DEG2RAD 0.0174532925
43inline bool nodataValue(
double x,
double y )
45 return ( std::isnan( x ) || std::isnan( y ) );
48QgsMeshVectorArrowRenderer::QgsMeshVectorArrowRenderer(
51 const QVector<double> &datasetValuesMag,
52 double datasetMagMaximumValue,
53 double datasetMagMinimumValue,
59 : mTriangularMesh( m )
60 , mDatasetValues( datasetValues )
61 , mDatasetValuesMag( datasetValuesMag )
62 , mMinMag( datasetMagMinimumValue )
63 , mMaxMag( datasetMagMaximumValue )
64 , mDataType( dataType )
65 , mBufferedExtent( context.mapExtent() )
71 Q_ASSERT( !mDatasetValuesMag.empty() );
72 Q_ASSERT( !std::isnan( mMinMag ) );
73 Q_ASSERT( !std::isnan( mMaxMag ) );
74 Q_ASSERT( mDatasetValues.isValid() );
81 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
82 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
83 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
84 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
89QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() =
default;
91void QgsMeshVectorArrowRenderer::draw()
94 QPainter *painter = mContext.painter();
97 mContext.setPainterFlagsUsingContext( painter );
99 QPen pen = painter->pen();
100 pen.setCapStyle( Qt::FlatCap );
101 pen.setJoinStyle( Qt::MiterJoin );
104 pen.setWidthF( penWidth );
105 painter->setPen( pen );
107 if ( mCfg.isOnUserDefinedGrid() )
109 drawVectorDataOnGrid();
113 drawVectorDataOnVertices();
117 drawVectorDataOnFaces();
121 drawVectorDataOnEdges();
125bool QgsMeshVectorArrowRenderer::calcVectorLineEnd(
127 double &vectorLength,
138 if ( xVal == 0.0 && yVal == 0.0 )
142 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
144 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
149 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
151 cosAlpha = cos( vectorAngle );
152 sinAlpha = sin( vectorAngle );
157 switch ( mCfg.arrowSettings().shaftLengthMethod() )
163 const double minVal = mMinMag;
164 const double maxVal = mMaxMag;
165 const double k = ( magnitude - minVal ) / ( maxVal - minVal );
166 const double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
167 xDist = cosAlpha * L;
168 yDist = sinAlpha * L;
173 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
174 xDist = scaleFactor * xVal;
175 yDist = scaleFactor * yVal;
182 xDist = cosAlpha * fixedShaftLength;
183 yDist = sinAlpha * fixedShaftLength;
191 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
195 lineEnd =
QgsPointXY( lineStart.
x() + xDist, 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() )
218 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
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 drawVectorDataOnPoints( nativeFacesInExtent, centroids );
296void QgsMeshVectorArrowRenderer::drawVectorDataOnEdges()
298 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
299 const QVector<QgsMeshVertex> ¢roids = mTriangularMesh.edgeCentroids();
301 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
304void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid()
309 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
310 const int cellx = mCfg.userGridCellWidth();
311 const int celly = mCfg.userGridCellHeight();
313 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
314 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
316 for (
const int i : trianglesInExtent )
318 if ( mContext.renderingStopped() )
323 const int v1 = face[0], v2 = face[1], v3 = face[2];
324 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
326 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
329 const QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
330 int left, right, top, bottom;
331 QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
334 if ( left % cellx != 0 )
335 left += cellx - ( left % cellx );
336 if ( right % cellx != 0 )
337 right -= ( right % cellx );
338 if ( top % celly != 0 )
339 top += celly - ( top % celly );
340 if ( bottom % celly != 0 )
341 bottom -= ( bottom % celly );
343 for (
int y = top; y <= bottom; y += celly )
345 for (
int x = left; x <= right; x += cellx )
348 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
352 const auto val1 = mDatasetValues.value( v1 );
353 const auto val2 = mDatasetValues.value( v2 );
354 const auto val3 = mDatasetValues.value( v3 );
355 val.
setX( QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), p ) );
356 val.
setY( QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), p ) );
360 const auto val1 = mDatasetValues.value( nativeFaceIndex );
361 val.
setX( QgsMeshLayerUtils::interpolateFromFacesData( p1, p2, p3, val1.x(), p ) );
362 val.
setY( QgsMeshLayerUtils::interpolateFromFacesData( p1, p2, p3, val1.y(), p ) );
364 if ( nodataValue( val.
x(), val.
y() ) )
368 drawVector( lineStart, val.
x(), val.
y(), val.
scalar() );
374void QgsMeshVectorArrowRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
378 double cosAlpha, sinAlpha;
379 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha, lineStart, xVal, yVal, magnitude ) )
385 QVector<QPointF> finalVectorHeadPoints( 3 );
387 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
388 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
391 vectorHeadPoints[0].
setX( -1.0 * vectorHeadLengthRatio );
392 vectorHeadPoints[0].
setY( vectorHeadWidthRatio * 0.5 );
395 vectorHeadPoints[1].
setX( 0.0 );
396 vectorHeadPoints[1].
setY( 0.0 );
399 vectorHeadPoints[2].
setX( -1.0 * vectorHeadLengthRatio );
400 vectorHeadPoints[2].
setY( -1.0 * vectorHeadWidthRatio * 0.5 );
403 for (
int j = 0; j < 3; j++ )
405 finalVectorHeadPoints[j].setX( lineEnd.
x() + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength ) );
407 finalVectorHeadPoints[j].setY( lineEnd.
y() - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength ) );
411 QPen pen( mContext.painter()->pen() );
412 pen.setColor( mVectorColoring.color( magnitude ) );
413 mContext.painter()->setPen( pen );
415 mContext.painter()->drawPolygon( finalVectorHeadPoints );
418QgsMeshVectorRenderer::~QgsMeshVectorRenderer() =
default;
420QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
424 const QVector<double> &datasetValuesMag,
425 double datasetMagMaximumValue,
426 double datasetMagMinimumValue,
431 QgsMeshLayerRendererFeedback *feedBack,
435 QgsMeshVectorRenderer *renderer =
nullptr;
440 renderer =
new QgsMeshVectorArrowRenderer( m, datasetVectorValues, datasetValuesMag, datasetMagMaximumValue, datasetMagMinimumValue, dataType, settings, context, size );
451 renderer =
new QgsMeshVectorWindBarbRenderer( m, datasetVectorValues, datasetValuesMag, datasetMagMaximumValue, datasetMagMinimumValue, dataType, settings, context, size );
459QgsMeshVectorWindBarbRenderer::QgsMeshVectorWindBarbRenderer(
462 const QVector<double> &datasetValuesMag,
463 double datasetMagMaximumValue,
464 double datasetMagMinimumValue,
470 : QgsMeshVectorArrowRenderer( m, datasetValues, datasetValuesMag, datasetMagMinimumValue, datasetMagMaximumValue, dataType, settings, context, size )
476QgsMeshVectorWindBarbRenderer::~QgsMeshVectorWindBarbRenderer() =
default;
478void QgsMeshVectorWindBarbRenderer::drawVector(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
481 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
483 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
486 QPen pen( mContext.painter()->pen() );
487 pen.setColor( mVectorColoring.color( magnitude ) );
488 mContext.painter()->setPen( pen );
491 QBrush brush( pen.color() );
492 mContext.painter()->setBrush( brush );
494 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(), mCfg.windBarbSettings().shaftLengthUnits() );
495 if ( shaftLength < 1 )
499 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.
x(), lineStart.
y() );
500 bool isNorthHemisphere =
true;
503 const QgsPointXY geoPoint = mGeographicTransform.transform( mapPoint );
504 isNorthHemisphere = geoPoint.
y() >= 0;
508 QgsDebugError( u
"Could not transform wind barb coordinates to geographic ones"_s );
511 const double d = shaftLength / 25;
512 const double centerRadius = d;
513 const double zeroCircleRadius = 2 * d;
514 const double barbLength = 8 * d + pen.widthF();
515 const double barbAngle = 135;
516 const double barbOffset = 2 * d + pen.widthF();
517 const int sign = isNorthHemisphere ? 1 : -1;
521 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
525 const double xDist = cos( vectorAngle ) * shaftLength;
526 const double yDist = -sin( vectorAngle ) * shaftLength;
532 if ( !
QgsRectangle( lineStart, lineEnd ).intersects(
QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
536 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier();
542 mContext.painter()->setBrush( Qt::NoBrush );
543 mContext.painter()->drawEllipse( lineStart.
toQPointF(), zeroCircleRadius, zeroCircleRadius );
544 mContext.painter()->setBrush( brush );
548 const double azimuth = lineEnd.
azimuth( lineStart );
551 if ( knots < 47.5 && knots > 7.5 )
555 mContext.painter()->drawPolyline( pts );
556 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
566 mContext.painter()->drawEllipse( lineStart.
toQPointF(), centerRadius, centerRadius );
569 while ( knots > 47.5 )
571 const QVector< QPointF >
573 mContext.painter()->drawPolygon( pts );
578 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414, azimuth );
580 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414 + barbOffset, azimuth );
584 while ( knots > 7.5 )
586 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() );
587 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
595 if ( nextLineOrigin == lineEnd )
596 nextLineOrigin = nextLineOrigin.
project( barbLength / 2, azimuth );
598 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength / 2, azimuth + barbAngle * sign ).
toQPointF() );
@ WindBarbs
Displaying vector dataset with wind barbs.
@ Arrows
Displaying vector dataset with arrows.
@ Traces
Displaying vector dataset with particle traces.
@ Streamlines
Displaying vector dataset with streamlines.
@ Millimeters
Millimeters.
@ Fixed
Use fixed length fixedShaftLength() regardless of vector's magnitude.
@ Scaled
Scale vector magnitude by factor scaleFactor().
@ MinMax
Scale vector magnitude linearly to fit in range of vectorFilterMin() and vectorFilterMax().
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.
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.
Represents a renderer settings for vector datasets.
Qgis::VectorFieldSymbology symbology() const
Returns the displaying method used to render vector datasets.
QgsInterpolatedLineColor vectorStrokeColoring() const
Returns the stroke coloring used to render vector datasets.
#define QgsDebugError(str)
QVector< int > QgsMeshFace
List of vertex indexes.