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.