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