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() );
@ 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.