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