QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
Loading...
Searching...
No Matches
qgsmeshvectorrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmeshvectorrenderer.cpp
3 -------------------------
4 begin : May 2018
5 copyright : (C) 2018 by Peter Petrik
6 email : zilolv at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "qgsrendercontext.h"
20#include "qgsmaptopixel.h"
21#include "qgsmeshlayerutils.h"
23
24#include <cstdlib>
25#include <ctime>
26#include <algorithm>
27#include <QPen>
28#include <QPainter>
29#include <cmath>
30
32
33#ifndef M_DEG2RAD
34#define M_DEG2RAD 0.0174532925
35#endif
36
37inline bool nodataValue( double x, double y )
38{
39 return ( std::isnan( x ) || std::isnan( y ) );
40}
41
42QgsMeshVectorArrowRenderer::QgsMeshVectorArrowRenderer(
43 const QgsTriangularMesh &m,
44 const QgsMeshDataBlock &datasetValues,
45 const QVector<double> &datasetValuesMag,
46 double datasetMagMaximumValue, double datasetMagMinimumValue,
48 const QgsMeshRendererVectorSettings &settings,
49 QgsRenderContext &context,
50 QSize size ) :
51 mTriangularMesh( m )
52 , mDatasetValues( datasetValues )
53 , mDatasetValuesMag( datasetValuesMag )
54 , mMinMag( datasetMagMinimumValue )
55 , mMaxMag( datasetMagMaximumValue )
56 , mDataType( dataType )
57 , mBufferedExtent( context.mapExtent() )
58 , mContext( context )
59 , mCfg( settings )
60 , mOutputSize( size )
61{
62 // should be checked in caller
63 Q_ASSERT( !mDatasetValuesMag.empty() );
64 Q_ASSERT( !std::isnan( mMinMag ) );
65 Q_ASSERT( !std::isnan( mMaxMag ) );
66 Q_ASSERT( mDatasetValues.isValid() );
67 Q_ASSERT( QgsMeshDataBlock::Vector2DDouble == mDatasetValues.type() );
68
69 // we need to expand out the extent so that it includes
70 // arrows which start or end up outside of the
71 // actual visible extent
72 const double extension = context.convertToMapUnits( calcExtentBufferSize(), Qgis::RenderUnit::Pixels );
73 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
74 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
75 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
76 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
77
78 mVectorColoring = settings.vectorStrokeColoring();
79}
80
81QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() = default;
82
83void QgsMeshVectorArrowRenderer::draw()
84{
85 // Set up the render configuration options
86 QPainter *painter = mContext.painter();
87
88 const QgsScopedQPainterState painterState( painter );
89 mContext.setPainterFlagsUsingContext( painter );
90
91 QPen pen = painter->pen();
92 pen.setCapStyle( Qt::FlatCap );
93 pen.setJoinStyle( Qt::MiterJoin );
94
95 const double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(),
97 pen.setWidthF( penWidth );
98 painter->setPen( pen );
99
100 if ( mCfg.isOnUserDefinedGrid() )
101 {
102 drawVectorDataOnGrid( );
103 }
105 {
106 drawVectorDataOnVertices( );
107 }
109 {
110 drawVectorDataOnFaces( );
111 }
113 {
114 drawVectorDataOnEdges( );
115 }
116}
117
118bool QgsMeshVectorArrowRenderer::calcVectorLineEnd(
119 QgsPointXY &lineEnd,
120 double &vectorLength,
121 double &cosAlpha,
122 double &sinAlpha, //out
123 const QgsPointXY &lineStart,
124 double xVal,
125 double yVal,
126 double magnitude //in
127)
128{
129 // return true on error
130
131 if ( xVal == 0.0 && yVal == 0.0 )
132 return true;
133
134 // do not render if magnitude is outside of the filtered range (if filtering is enabled)
135 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
136 return true;
137 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
138 return true;
139
140 // Determine the angle of the vector, counter-clockwise, from east
141 // (and associated trigs)
142 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD;
143
144 cosAlpha = cos( vectorAngle );
145 sinAlpha = sin( vectorAngle );
146
147 // Now determine the X and Y distances of the end of the line from the start
148 double xDist = 0.0;
149 double yDist = 0.0;
150 switch ( mCfg.arrowSettings().shaftLengthMethod() )
151 {
153 {
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;
164 break;
165 }
167 {
168 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
169 xDist = scaleFactor * xVal;
170 yDist = scaleFactor * yVal;
171 break;
172 }
174 {
175 // We must be using a fixed length
176 const double fixedShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
178 xDist = cosAlpha * fixedShaftLength;
179 yDist = sinAlpha * fixedShaftLength;
180 break;
181 }
182 }
183
184 // Flip the Y axis (pixel vs real-world axis)
185 yDist *= -1.0;
186
187 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
188 return true;
189
190 // Determine the line coords
191 lineEnd = QgsPointXY( lineStart.x() + xDist,
192 lineStart.y() + yDist );
193
194 vectorLength = sqrt( xDist * xDist + yDist * yDist );
195
196 // skip rendering if line bbox does not intersect the QImage area
197 if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
198 return true;
199
200 return false; //success
201}
202
203double QgsMeshVectorArrowRenderer::calcExtentBufferSize() const
204{
205 double buffer = 0;
206 switch ( mCfg.arrowSettings().shaftLengthMethod() )
207 {
209 {
210 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(),
212 break;
213 }
215 {
216 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
217 break;
218 }
220 {
221 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(),
223 break;
224 }
225 }
226
227 if ( mCfg.filterMax() >= 0 && buffer > mCfg.filterMax() )
228 buffer = mCfg.filterMax();
229
230 if ( buffer < 0.0 )
231 buffer = 0.0;
232
233 return buffer;
234}
235
236
237void QgsMeshVectorArrowRenderer::drawVectorDataOnVertices()
238{
239 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
240 QSet<int> verticesToDraw;
241
242 // currently expecting that triangulation does not add any new extra vertices on the way
243 Q_ASSERT( mDatasetValuesMag.count() == vertices.count() );
244
245 // find all vertices from faces to render
246 {
247 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
248 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
249 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromTriangles( trianglesInExtent, triangles ) );
250 }
251
252 // find all vertices from edges to render
253 {
254 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
255 const QVector<QgsMeshEdge> &edges = mTriangularMesh.edges();
256 verticesToDraw.unite( QgsMeshUtils::nativeVerticesFromEdges( edgesInExtent, edges ) );
257 }
258
259 // render
260 drawVectorDataOnPoints( verticesToDraw, vertices );
261}
262
263void QgsMeshVectorArrowRenderer::drawVectorDataOnPoints( const QSet<int> indexesToRender, const QVector<QgsMeshVertex> &points )
264{
265 for ( const int i : indexesToRender )
266 {
267 if ( mContext.renderingStopped() )
268 break;
269
270 const QgsPointXY center = points.at( i );
271 if ( !mBufferedExtent.contains( center ) )
272 continue;
273
274 const QgsMeshDatasetValue val = mDatasetValues.value( i );
275 const double xVal = val.x();
276 const double yVal = val.y();
277 if ( nodataValue( xVal, yVal ) )
278 continue;
279
280 const double V = mDatasetValuesMag[i]; // pre-calculated magnitude
281 const QgsPointXY lineStart = mContext.mapToPixel().transform( center.x(), center.y() );
282
283 drawVector( lineStart, xVal, yVal, V );
284 }
285}
286
287void QgsMeshVectorArrowRenderer::drawVectorDataOnFaces( )
288{
289 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
290 const QVector<QgsMeshVertex> &centroids = mTriangularMesh.faceCentroids();
291 const QSet<int> nativeFacesInExtent = QgsMeshUtils::nativeFacesFromTriangles( trianglesInExtent,
292 mTriangularMesh.trianglesToNativeFaces() );
293 drawVectorDataOnPoints( nativeFacesInExtent, centroids );
294}
295
296void QgsMeshVectorArrowRenderer::drawVectorDataOnEdges()
297{
298 const QList<int> edgesInExtent = mTriangularMesh.edgeIndexesForRectangle( mBufferedExtent );
299 const QVector<QgsMeshVertex> &centroids = mTriangularMesh.edgeCentroids();
300 const QSet<int> nativeEdgesInExtent = QgsMeshUtils::nativeEdgesFromEdges( edgesInExtent,
301 mTriangularMesh.edgesToNativeEdges() );
302 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
303}
304
305void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid( )
306{
309 return;
310
311 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
312 const int cellx = mCfg.userGridCellWidth();
313 const int celly = mCfg.userGridCellHeight();
314
315 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
316 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
317
318 for ( const int i : trianglesInExtent )
319 {
320 if ( mContext.renderingStopped() )
321 break;
322
323 const QgsMeshFace &face = triangles[i];
324
325 const int v1 = face[0], v2 = face[1], v3 = face[2];
326 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
327
328 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
329
330 // Get the BBox of the element in pixels
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 );
334
335 // Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
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 );
344
345 for ( int y = top; y <= bottom; y += celly )
346 {
347 for ( int x = left; x <= right; x += cellx )
348 {
350 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
351
353 {
354 const auto val1 = mDatasetValues.value( v1 );
355 const auto val2 = mDatasetValues.value( v2 );
356 const auto val3 = mDatasetValues.value( v3 );
357 val.setX(
358 QgsMeshLayerUtils::interpolateFromVerticesData(
359 p1, p2, p3,
360 val1.x(),
361 val2.x(),
362 val3.x(),
363 p )
364 );
365 val.setY(
366 QgsMeshLayerUtils::interpolateFromVerticesData(
367 p1, p2, p3,
368 val1.y(),
369 val2.y(),
370 val3.y(),
371 p )
372 );
373 }
375 {
376 const auto val1 = mDatasetValues.value( nativeFaceIndex );
377 val.setX(
378 QgsMeshLayerUtils::interpolateFromFacesData(
379 p1, p2, p3,
380 val1.x(),
381 p
382 )
383 );
384 val.setY(
385 QgsMeshLayerUtils::interpolateFromFacesData(
386 p1, p2, p3,
387 val1.y(),
388 p
389 )
390 );
391 }
392 if ( nodataValue( val.x(), val.y() ) )
393 continue;
394
395 const QgsPointXY lineStart( x, y );
396 drawVector( lineStart, val.x(), val.y(), val.scalar() );
397 }
398 }
399 }
400}
401
402void QgsMeshVectorArrowRenderer::drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
403{
404 QgsPointXY lineEnd;
405 double vectorLength;
406 double cosAlpha, sinAlpha;
407 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha,
408 lineStart, xVal, yVal, magnitude ) )
409 return;
410
411 // Make a set of vector head coordinates that we will place at the end of each vector,
412 // scale, translate and rotate.
413 QgsPointXY vectorHeadPoints[3];
414 QVector<QPointF> finalVectorHeadPoints( 3 );
415
416 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
417 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
418
419 // First head point: top of ->
420 vectorHeadPoints[0].setX( -1.0 * vectorHeadLengthRatio );
421 vectorHeadPoints[0].setY( vectorHeadWidthRatio * 0.5 );
422
423 // Second head point: right of ->
424 vectorHeadPoints[1].setX( 0.0 );
425 vectorHeadPoints[1].setY( 0.0 );
426
427 // Third head point: bottom of ->
428 vectorHeadPoints[2].setX( -1.0 * vectorHeadLengthRatio );
429 vectorHeadPoints[2].setY( -1.0 * vectorHeadWidthRatio * 0.5 );
430
431 // Determine the arrow head coords
432 for ( int j = 0; j < 3; j++ )
433 {
434 finalVectorHeadPoints[j].setX( lineEnd.x()
435 + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength )
436 - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength )
437 );
438
439 finalVectorHeadPoints[j].setY( lineEnd.y()
440 - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength )
441 - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength )
442 );
443 }
444
445 // Now actually draw the vector
446 QPen pen( mContext.painter()->pen() );
447 pen.setColor( mVectorColoring.color( magnitude ) );
448 mContext.painter()->setPen( pen );
449 mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
450 mContext.painter()->drawPolygon( finalVectorHeadPoints );
451}
452
453QgsMeshVectorRenderer::~QgsMeshVectorRenderer() = default;
454
455QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
456 const QgsTriangularMesh &m,
457 const QgsMeshDataBlock &datasetVectorValues,
458 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
459 const QVector<double> &datasetValuesMag,
460 double datasetMagMaximumValue,
461 double datasetMagMinimumValue,
463 const QgsMeshRendererVectorSettings &settings,
464 QgsRenderContext &context,
465 const QgsRectangle &layerExtent,
466 QgsMeshLayerRendererFeedback *feedBack,
467 const QSize &size )
468{
469 QgsMeshVectorRenderer *renderer = nullptr;
470
471 switch ( settings.symbology() )
472 {
474 renderer = new QgsMeshVectorArrowRenderer(
475 m,
476 datasetVectorValues,
477 datasetValuesMag,
478 datasetMagMaximumValue,
479 datasetMagMinimumValue,
480 dataType,
481 settings,
482 context,
483 size );
484 break;
486 renderer = new QgsMeshVectorStreamlineRenderer(
487 m,
488 datasetVectorValues,
489 scalarActiveFaceFlagValues,
490 datasetValuesMag,
492 settings,
493 context,
494 layerExtent,
495 feedBack,
496 datasetMagMaximumValue );
497 break;
499 renderer = new QgsMeshVectorTraceRenderer(
500 m,
501 datasetVectorValues,
502 scalarActiveFaceFlagValues,
504 settings,
505 context,
506 layerExtent,
507 datasetMagMaximumValue );
508 break;
510 renderer = new QgsMeshVectorWindBarbRenderer(
511 m,
512 datasetVectorValues,
513 datasetValuesMag,
514 datasetMagMaximumValue,
515 datasetMagMinimumValue,
516 dataType,
517 settings,
518 context,
519 size );
520 break;
521 }
522
523 return renderer;
524}
525
526
527QgsMeshVectorWindBarbRenderer::QgsMeshVectorWindBarbRenderer(
528 const QgsTriangularMesh &m,
529 const QgsMeshDataBlock &datasetValues,
530 const QVector<double> &datasetValuesMag,
531 double datasetMagMaximumValue, double datasetMagMinimumValue,
533 const QgsMeshRendererVectorSettings &settings,
534 QgsRenderContext &context,
535 QSize size ) : QgsMeshVectorArrowRenderer( m,
536 datasetValues,
537 datasetValuesMag,
538 datasetMagMinimumValue,
539 datasetMagMaximumValue,
540 dataType,
541 settings,
542 context,
543 size )
544{
545 const QgsCoordinateReferenceSystem mapCrs = mContext.coordinateTransform().destinationCrs();
546 mGeographicTransform = QgsCoordinateTransform( mapCrs, mapCrs.toGeographicCrs(), mContext.coordinateTransform().context() );
547}
548
549QgsMeshVectorWindBarbRenderer::~QgsMeshVectorWindBarbRenderer() = default;
550
551void QgsMeshVectorWindBarbRenderer::drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
552{
553 // do not render if magnitude is outside of the filtered range (if filtering is enabled)
554 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
555 return;
556 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
557 return;
558
559 QPen pen( mContext.painter()->pen() );
560 pen.setColor( mVectorColoring.color( magnitude ) );
561 mContext.painter()->setPen( pen );
562
563 // we need a brush to fill center circle and pennants
564 QBrush brush( pen.color() );
565 mContext.painter()->setBrush( brush );
566
567 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(),
568 mCfg.windBarbSettings().shaftLengthUnits() );
569 if ( shaftLength < 1 )
570 return;
571
572 // Check if barb is above or below the equinox
573 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.x(), lineStart.y() );
574 bool isNorthHemisphere = true;
575 try
576 {
577 const QgsPointXY geoPoint = mGeographicTransform.transform( mapPoint );
578 isNorthHemisphere = geoPoint.y() >= 0;
579 }
580 catch ( QgsCsException & )
581 {
582 QgsDebugError( QStringLiteral( "Could not transform wind barb coordinates to geographic ones" ) );
583 }
584
585 const double d = shaftLength / 25; // this is a magic number ratio between shaft length and other barb dimensions
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;
592
593 // Determine the angle of the vector, counter-clockwise, from east
594 // (and associated trigs)
595 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD;
596
597 // Now determine the X and Y distances of the end of the line from the start
598 // Flip the Y axis (pixel vs real-world axis)
599 const double xDist = cos( vectorAngle ) * shaftLength;
600 const double yDist = - sin( vectorAngle ) * shaftLength;
601
602 // Determine the line coords
603 const QgsPointXY lineEnd = QgsPointXY( lineStart.x() - xDist,
604 lineStart.y() - yDist );
605
606 // skip rendering if line bbox does not intersect the QImage area
607 if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
608 return;
609
610 // scale the magnitude to convert it to knots
611 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier() ;
612 QgsPointXY nextLineOrigin = lineEnd;
613
614 // special case for no wind, just an empty circle
615 if ( knots < 2.5 )
616 {
617 mContext.painter()->setBrush( Qt::NoBrush );
618 mContext.painter()->drawEllipse( lineStart.toQPointF(), zeroCircleRadius, zeroCircleRadius );
619 mContext.painter()->setBrush( brush );
620 return;
621 }
622
623 const double azimuth = lineEnd.azimuth( lineStart );
624
625 // conditionally draw the shaft
626 if ( knots < 47.5 && knots > 7.5 )
627 {
628 // When first barb is a '10', we want to draw the shaft and barb as a single polyline for a proper join
629 const QVector< QPointF > pts{ lineStart.toQPointF(),
630 lineEnd.toQPointF(),
631 nextLineOrigin.project( barbLength, azimuth + barbAngle * sign ).toQPointF() };
632 mContext.painter()->drawPolyline( pts );
633 nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth );
634 knots -= 10;
635 }
636 else
637 {
638 // draw just the shaft
639 mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
640 }
641
642 // draw the center circle
643 mContext.painter()->drawEllipse( lineStart.toQPointF(), centerRadius, centerRadius );
644
645 // draw pennants (50)
646 while ( knots > 47.5 )
647 {
648 const QVector< QPointF > pts{ nextLineOrigin.toQPointF(),
649 nextLineOrigin.project( barbLength / 1.414, azimuth + 90 * sign ).toQPointF(),
650 nextLineOrigin.project( barbLength / 1.414, azimuth ).toQPointF() };
651 mContext.painter()->drawPolygon( pts );
652 knots -= 50;
653
654 // don't use an offset for the next pennant
655 if ( knots > 47.5 )
656 nextLineOrigin = nextLineOrigin.project( barbLength / 1.414, azimuth );
657 else
658 nextLineOrigin = nextLineOrigin.project( barbLength / 1.414 + barbOffset, azimuth );
659 }
660
661 // draw large barbs (10)
662 while ( knots > 7.5 )
663 {
664 mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength, azimuth + barbAngle * sign ).toQPointF() );
665 nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth );
666 knots -= 10;
667 }
668
669 // draw small barb (5)
670 if ( knots > 2.5 )
671 {
672 // a single '5' barb should not start at the line end
673 if ( nextLineOrigin == lineEnd )
674 nextLineOrigin = nextLineOrigin.project( barbLength / 2, azimuth );
675
676 mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength / 2, azimuth + barbAngle * sign ).toQPointF() );
677 }
678}
@ Millimeters
Millimeters.
This class represents a coordinate reference system (CRS).
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
Class for doing transforms between two map coordinate systems.
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, ... )
DataType
Location of where data is specified for datasets in the dataset group.
@ DataOnEdges
Data is defined on edges.
@ DataOnFaces
Data is defined on faces.
@ DataOnVertices
Data is defined on vertices.
@ DataOnVolumes
Data is defined on volumes.
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.
Definition qgspointxy.h:60
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.
Definition qgspointxy.h:129
double azimuth(const QgsPointXY &other) const
Calculates azimuth between this point and other one (clockwise in degree, starting from north)
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:119
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:165
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
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 M_DEG2RAD
#define QgsDebugError(str)
Definition qgslogger.h:38
QVector< int > QgsMeshFace
List of vertex indexes.