QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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,
53 double datasetMagMinimumValue,
55 const QgsMeshRendererVectorSettings &settings,
56 QgsRenderContext &context,
57 QSize size
58)
59 : mTriangularMesh( m )
60 , mDatasetValues( datasetValues )
61 , mDatasetValuesMag( datasetValuesMag )
62 , mMinMag( datasetMagMinimumValue )
63 , mMaxMag( datasetMagMaximumValue )
64 , mDataType( dataType )
65 , mBufferedExtent( context.mapExtent() )
66 , mContext( context )
67 , mCfg( settings )
68 , mOutputSize( size )
69{
70 // should be checked in caller
71 Q_ASSERT( !mDatasetValuesMag.empty() );
72 Q_ASSERT( !std::isnan( mMinMag ) );
73 Q_ASSERT( !std::isnan( mMaxMag ) );
74 Q_ASSERT( mDatasetValues.isValid() );
75 Q_ASSERT( QgsMeshDataBlock::Vector2DDouble == mDatasetValues.type() );
76
77 // we need to expand out the extent so that it includes
78 // arrows which start or end up outside of the
79 // actual visible extent
80 const double extension = context.convertToMapUnits( calcExtentBufferSize(), Qgis::RenderUnit::Pixels );
81 mBufferedExtent.setXMinimum( mBufferedExtent.xMinimum() - extension );
82 mBufferedExtent.setXMaximum( mBufferedExtent.xMaximum() + extension );
83 mBufferedExtent.setYMinimum( mBufferedExtent.yMinimum() - extension );
84 mBufferedExtent.setYMaximum( mBufferedExtent.yMaximum() + extension );
85
86 mVectorColoring = settings.vectorStrokeColoring();
87}
88
89QgsMeshVectorArrowRenderer::~QgsMeshVectorArrowRenderer() = default;
90
91void QgsMeshVectorArrowRenderer::draw()
92{
93 // Set up the render configuration options
94 QPainter *painter = mContext.painter();
95
96 const QgsScopedQPainterState painterState( painter );
97 mContext.setPainterFlagsUsingContext( painter );
98
99 QPen pen = painter->pen();
100 pen.setCapStyle( Qt::FlatCap );
101 pen.setJoinStyle( Qt::MiterJoin );
102
103 const double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(), Qgis::RenderUnit::Millimeters );
104 pen.setWidthF( penWidth );
105 painter->setPen( pen );
106
107 if ( mCfg.isOnUserDefinedGrid() )
108 {
109 drawVectorDataOnGrid();
110 }
112 {
113 drawVectorDataOnVertices();
114 }
116 {
117 drawVectorDataOnFaces();
118 }
120 {
121 drawVectorDataOnEdges();
122 }
123}
124
125bool QgsMeshVectorArrowRenderer::calcVectorLineEnd(
126 QgsPointXY &lineEnd,
127 double &vectorLength,
128 double &cosAlpha,
129 double &sinAlpha, //out
130 const QgsPointXY &lineStart,
131 double xVal,
132 double yVal,
133 double magnitude //in
134)
135{
136 // return true on error
137
138 if ( xVal == 0.0 && yVal == 0.0 )
139 return true;
140
141 // do not render if magnitude is outside of the filtered range (if filtering is enabled)
142 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
143 return true;
144 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
145 return true;
146
147 // Determine the angle of the vector, counter-clockwise, from east
148 // (and associated trigs)
149 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD;
150
151 cosAlpha = cos( vectorAngle );
152 sinAlpha = sin( vectorAngle );
153
154 // Now determine the X and Y distances of the end of the line from the start
155 double xDist = 0.0;
156 double yDist = 0.0;
157 switch ( mCfg.arrowSettings().shaftLengthMethod() )
158 {
160 {
161 const double minShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().minShaftLength(), Qgis::RenderUnit::Millimeters );
162 const double maxShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(), Qgis::RenderUnit::Millimeters );
163 const double minVal = mMinMag;
164 const double maxVal = mMaxMag;
165 const double k = ( magnitude - minVal ) / ( maxVal - minVal );
166 const double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
167 xDist = cosAlpha * L;
168 yDist = sinAlpha * L;
169 break;
170 }
172 {
173 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
174 xDist = scaleFactor * xVal;
175 yDist = scaleFactor * yVal;
176 break;
177 }
179 {
180 // We must be using a fixed length
181 const double fixedShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(), Qgis::RenderUnit::Millimeters );
182 xDist = cosAlpha * fixedShaftLength;
183 yDist = sinAlpha * fixedShaftLength;
184 break;
185 }
186 }
187
188 // Flip the Y axis (pixel vs real-world axis)
189 yDist *= -1.0;
190
191 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
192 return true;
193
194 // Determine the line coords
195 lineEnd = QgsPointXY( lineStart.x() + xDist, 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(), Qgis::RenderUnit::Millimeters );
214 break;
215 }
217 {
218 buffer = mCfg.arrowSettings().scaleFactor() * mMaxMag;
219 break;
220 }
222 {
223 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(), Qgis::RenderUnit::Millimeters );
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, 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, mTriangularMesh.edgesToNativeEdges() );
301 drawVectorDataOnPoints( nativeEdgesInExtent, centroids );
302}
303
304void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid()
305{
307 return;
308
309 const QList<int> trianglesInExtent = mTriangularMesh.faceIndexesForRectangle( mBufferedExtent );
310 const int cellx = mCfg.userGridCellWidth();
311 const int celly = mCfg.userGridCellHeight();
312
313 const QVector<QgsMeshFace> &triangles = mTriangularMesh.triangles();
314 const QVector<QgsMeshVertex> &vertices = mTriangularMesh.vertices();
315
316 for ( const int i : trianglesInExtent )
317 {
318 if ( mContext.renderingStopped() )
319 break;
320
321 const QgsMeshFace &face = triangles[i];
322
323 const int v1 = face[0], v2 = face[1], v3 = face[2];
324 const QgsPoint p1 = vertices[v1], p2 = vertices[v2], p3 = vertices[v3];
325
326 const int nativeFaceIndex = mTriangularMesh.trianglesToNativeFaces()[i];
327
328 // Get the BBox of the element in pixels
329 const QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( p1, p2, p3 );
330 int left, right, top, bottom;
331 QgsMeshLayerUtils::boundingBoxToScreenRectangle( mContext.mapToPixel(), mOutputSize, bbox, left, right, top, bottom );
332
333 // Align rect to the grid (e.g. interval <13, 36> with grid cell 10 will be trimmed to <20,30>
334 if ( left % cellx != 0 )
335 left += cellx - ( left % cellx );
336 if ( right % cellx != 0 )
337 right -= ( right % cellx );
338 if ( top % celly != 0 )
339 top += celly - ( top % celly );
340 if ( bottom % celly != 0 )
341 bottom -= ( bottom % celly );
342
343 for ( int y = top; y <= bottom; y += celly )
344 {
345 for ( int x = left; x <= right; x += cellx )
346 {
348 const QgsPointXY p = mContext.mapToPixel().toMapCoordinates( x, y );
349
351 {
352 const auto val1 = mDatasetValues.value( v1 );
353 const auto val2 = mDatasetValues.value( v2 );
354 const auto val3 = mDatasetValues.value( v3 );
355 val.setX( QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.x(), val2.x(), val3.x(), p ) );
356 val.setY( QgsMeshLayerUtils::interpolateFromVerticesData( p1, p2, p3, val1.y(), val2.y(), val3.y(), p ) );
357 }
359 {
360 const auto val1 = mDatasetValues.value( nativeFaceIndex );
361 val.setX( QgsMeshLayerUtils::interpolateFromFacesData( p1, p2, p3, val1.x(), p ) );
362 val.setY( QgsMeshLayerUtils::interpolateFromFacesData( p1, p2, p3, val1.y(), p ) );
363 }
364 if ( nodataValue( val.x(), val.y() ) )
365 continue;
366
367 const QgsPointXY lineStart( x, y );
368 drawVector( lineStart, val.x(), val.y(), val.scalar() );
369 }
370 }
371 }
372}
373
374void QgsMeshVectorArrowRenderer::drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
375{
376 QgsPointXY lineEnd;
377 double vectorLength;
378 double cosAlpha, sinAlpha;
379 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha, lineStart, xVal, yVal, magnitude ) )
380 return;
381
382 // Make a set of vector head coordinates that we will place at the end of each vector,
383 // scale, translate and rotate.
384 QgsPointXY vectorHeadPoints[3];
385 QVector<QPointF> finalVectorHeadPoints( 3 );
386
387 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
388 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
389
390 // First head point: top of ->
391 vectorHeadPoints[0].setX( -1.0 * vectorHeadLengthRatio );
392 vectorHeadPoints[0].setY( vectorHeadWidthRatio * 0.5 );
393
394 // Second head point: right of ->
395 vectorHeadPoints[1].setX( 0.0 );
396 vectorHeadPoints[1].setY( 0.0 );
397
398 // Third head point: bottom of ->
399 vectorHeadPoints[2].setX( -1.0 * vectorHeadLengthRatio );
400 vectorHeadPoints[2].setY( -1.0 * vectorHeadWidthRatio * 0.5 );
401
402 // Determine the arrow head coords
403 for ( int j = 0; j < 3; j++ )
404 {
405 finalVectorHeadPoints[j].setX( lineEnd.x() + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength ) );
406
407 finalVectorHeadPoints[j].setY( lineEnd.y() - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength ) );
408 }
409
410 // Now actually draw the vector
411 QPen pen( mContext.painter()->pen() );
412 pen.setColor( mVectorColoring.color( magnitude ) );
413 mContext.painter()->setPen( pen );
414 mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
415 mContext.painter()->drawPolygon( finalVectorHeadPoints );
416}
417
418QgsMeshVectorRenderer::~QgsMeshVectorRenderer() = default;
419
420QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer(
421 const QgsTriangularMesh &m,
422 const QgsMeshDataBlock &datasetVectorValues,
423 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
424 const QVector<double> &datasetValuesMag,
425 double datasetMagMaximumValue,
426 double datasetMagMinimumValue,
428 const QgsMeshRendererVectorSettings &settings,
429 QgsRenderContext &context,
430 const QgsRectangle &layerExtent,
431 QgsMeshLayerRendererFeedback *feedBack,
432 const QSize &size
433)
434{
435 QgsMeshVectorRenderer *renderer = nullptr;
436
437 switch ( settings.symbology() )
438 {
440 renderer = new QgsMeshVectorArrowRenderer( m, datasetVectorValues, datasetValuesMag, datasetMagMaximumValue, datasetMagMinimumValue, dataType, settings, context, size );
441 break;
443 renderer
444 = new QgsMeshVectorStreamlineRenderer( m, datasetVectorValues, scalarActiveFaceFlagValues, datasetValuesMag, dataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVertices, settings, context, layerExtent, feedBack, datasetMagMaximumValue );
445 break;
447 renderer
448 = new QgsMeshVectorTraceRenderer( m, datasetVectorValues, scalarActiveFaceFlagValues, dataType == QgsMeshDatasetGroupMetadata::DataType::DataOnVertices, settings, context, layerExtent, datasetMagMaximumValue );
449 break;
451 renderer = new QgsMeshVectorWindBarbRenderer( m, datasetVectorValues, datasetValuesMag, datasetMagMaximumValue, datasetMagMinimumValue, dataType, settings, context, size );
452 break;
453 }
454
455 return renderer;
456}
457
458
459QgsMeshVectorWindBarbRenderer::QgsMeshVectorWindBarbRenderer(
460 const QgsTriangularMesh &m,
461 const QgsMeshDataBlock &datasetValues,
462 const QVector<double> &datasetValuesMag,
463 double datasetMagMaximumValue,
464 double datasetMagMinimumValue,
466 const QgsMeshRendererVectorSettings &settings,
467 QgsRenderContext &context,
468 QSize size
469)
470 : QgsMeshVectorArrowRenderer( m, datasetValues, datasetValuesMag, datasetMagMinimumValue, datasetMagMaximumValue, dataType, settings, context, size )
471{
472 const QgsCoordinateReferenceSystem mapCrs = mContext.coordinateTransform().destinationCrs();
473 mGeographicTransform = QgsCoordinateTransform( mapCrs, mapCrs.toGeographicCrs(), mContext.coordinateTransform().context() );
474}
475
476QgsMeshVectorWindBarbRenderer::~QgsMeshVectorWindBarbRenderer() = default;
477
478void QgsMeshVectorWindBarbRenderer::drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
479{
480 // do not render if magnitude is outside of the filtered range (if filtering is enabled)
481 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
482 return;
483 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
484 return;
485
486 QPen pen( mContext.painter()->pen() );
487 pen.setColor( mVectorColoring.color( magnitude ) );
488 mContext.painter()->setPen( pen );
489
490 // we need a brush to fill center circle and pennants
491 QBrush brush( pen.color() );
492 mContext.painter()->setBrush( brush );
493
494 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(), mCfg.windBarbSettings().shaftLengthUnits() );
495 if ( shaftLength < 1 )
496 return;
497
498 // Check if barb is above or below the equinox
499 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.x(), lineStart.y() );
500 bool isNorthHemisphere = true;
501 try
502 {
503 const QgsPointXY geoPoint = mGeographicTransform.transform( mapPoint );
504 isNorthHemisphere = geoPoint.y() >= 0;
505 }
506 catch ( QgsCsException & )
507 {
508 QgsDebugError( u"Could not transform wind barb coordinates to geographic ones"_s );
509 }
510
511 const double d = shaftLength / 25; // this is a magic number ratio between shaft length and other barb dimensions
512 const double centerRadius = d;
513 const double zeroCircleRadius = 2 * d;
514 const double barbLength = 8 * d + pen.widthF();
515 const double barbAngle = 135;
516 const double barbOffset = 2 * d + pen.widthF();
517 const int sign = isNorthHemisphere ? 1 : -1;
518
519 // Determine the angle of the vector, counter-clockwise, from east
520 // (and associated trigs)
521 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD;
522
523 // Now determine the X and Y distances of the end of the line from the start
524 // Flip the Y axis (pixel vs real-world axis)
525 const double xDist = cos( vectorAngle ) * shaftLength;
526 const double yDist = -sin( vectorAngle ) * shaftLength;
527
528 // Determine the line coords
529 const QgsPointXY lineEnd = QgsPointXY( lineStart.x() - xDist, lineStart.y() - yDist );
530
531 // skip rendering if line bbox does not intersect the QImage area
532 if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
533 return;
534
535 // scale the magnitude to convert it to knots
536 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier();
537 QgsPointXY nextLineOrigin = lineEnd;
538
539 // special case for no wind, just an empty circle
540 if ( knots < 2.5 )
541 {
542 mContext.painter()->setBrush( Qt::NoBrush );
543 mContext.painter()->drawEllipse( lineStart.toQPointF(), zeroCircleRadius, zeroCircleRadius );
544 mContext.painter()->setBrush( brush );
545 return;
546 }
547
548 const double azimuth = lineEnd.azimuth( lineStart );
549
550 // conditionally draw the shaft
551 if ( knots < 47.5 && knots > 7.5 )
552 {
553 // When first barb is a '10', we want to draw the shaft and barb as a single polyline for a proper join
554 const QVector< QPointF > pts { lineStart.toQPointF(), lineEnd.toQPointF(), nextLineOrigin.project( barbLength, azimuth + barbAngle * sign ).toQPointF() };
555 mContext.painter()->drawPolyline( pts );
556 nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth );
557 knots -= 10;
558 }
559 else
560 {
561 // draw just the shaft
562 mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
563 }
564
565 // draw the center circle
566 mContext.painter()->drawEllipse( lineStart.toQPointF(), centerRadius, centerRadius );
567
568 // draw pennants (50)
569 while ( knots > 47.5 )
570 {
571 const QVector< QPointF >
572 pts { nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength / 1.414, azimuth + 90 * sign ).toQPointF(), nextLineOrigin.project( barbLength / 1.414, azimuth ).toQPointF() };
573 mContext.painter()->drawPolygon( pts );
574 knots -= 50;
575
576 // don't use an offset for the next pennant
577 if ( knots > 47.5 )
578 nextLineOrigin = nextLineOrigin.project( barbLength / 1.414, azimuth );
579 else
580 nextLineOrigin = nextLineOrigin.project( barbLength / 1.414 + barbOffset, azimuth );
581 }
582
583 // draw large barbs (10)
584 while ( knots > 7.5 )
585 {
586 mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength, azimuth + barbAngle * sign ).toQPointF() );
587 nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth );
588 knots -= 10;
589 }
590
591 // draw small barb (5)
592 if ( knots > 2.5 )
593 {
594 // a single '5' barb should not start at the line end
595 if ( nextLineOrigin == lineEnd )
596 nextLineOrigin = nextLineOrigin.project( barbLength / 2, azimuth );
597
598 mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength / 2, azimuth + barbAngle * sign ).toQPointF() );
599 }
600}
@ Millimeters
Millimeters.
Definition qgis.h:5341
@ Pixels
Pixels.
Definition qgis.h:5343
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:132
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:122
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:168
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.