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