QGIS API Documentation 4.3.0-Master (c8cdfe19d7b)
Loading...
Searching...
No Matches
qgsvectorfieldengine.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorfieldengine.cpp
3 ---------------------
4 begin : September 2026
5 copyright : (C) 2026 by Stefanos Natsis
6 email : uclaros at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include <algorithm>
19#include <cmath>
20
21#include "qgsfeedback.h"
22#include "qgspainting.h"
23#include "qgsrendercontext.h"
26
27#include <QString>
28
29using namespace Qt::StringLiterals;
30
31#ifndef M_DEG2RAD
32#define M_DEG2RAD 0.0174532925
33#endif
34
36constexpr qint64 VECTOR_FIELD_MAXIMUM_GLYPHS = 100'000;
37
38QgsVectorFieldEngine::QgsVectorFieldEngine( double datasetMagMaximumValue, double datasetMagMinimumValue, const QgsVectorFieldSettings &settings, QgsRenderContext &context, QSize size )
39 : mMinMag( datasetMagMinimumValue )
40 , mMaxMag( datasetMagMaximumValue )
41 , mContext( context )
42 , mCfg( settings )
43 , mVectorColoring( settings.vectorStrokeColoring() )
44 , mOutputSize( size )
45{
46 switch ( settings.symbology() )
47 {
48 case Qgis::VectorFieldSymbology::WindBarbs:
49 {
50 const QgsCoordinateReferenceSystem mapCrs = mContext.distanceArea().sourceCrs();
51 mGeographicTransform = std::make_unique<QgsCoordinateTransform>( mapCrs, mapCrs.toGeographicCrs(), mContext.transformContext() );
52 break;
53 }
57 break;
58 }
59
60 // Set up the render configuration options
61 QPainter *painter = mContext.painter();
62
63 mScopedPainterState = std::make_unique<QgsScopedQPainterState>( painter );
64 mContext.setPainterFlagsUsingContext( painter );
65
66 QPen pen = painter->pen();
67 pen.setCapStyle( Qt::FlatCap );
68 pen.setJoinStyle( Qt::MiterJoin );
69
70 const double penWidth = mContext.convertToPainterUnits( mCfg.lineWidth(), Qgis::RenderUnit::Millimeters );
71 pen.setWidthF( penWidth );
72 painter->setPen( pen );
73}
74
76
78{
79 double buffer = 0;
80 switch ( mCfg.symbology() )
81 {
83 buffer = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(), mCfg.windBarbSettings().shaftLengthUnits() );
84 break;
85
87 switch ( mCfg.arrowSettings().shaftLengthMethod() )
88 {
90 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(), Qgis::RenderUnit::Millimeters );
91 break;
92
94 {
95 // the shaft length follows the magnitude, so the longest shaft which is actually drawn is
96 // the one of the largest magnitude which passes the filter
97 const double magnitude = mCfg.filterMax() >= 0 ? std::min( mMaxMag, mCfg.filterMax() ) : mMaxMag;
98 buffer = mCfg.arrowSettings().scaleFactor() * magnitude;
99 break;
100 }
101
103 buffer = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(), Qgis::RenderUnit::Millimeters );
104 break;
105 }
106 break;
107
110 break;
111 }
112
113 return std::max( 0.0, buffer );
114}
115
116void QgsVectorFieldEngine::drawGlyph( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
117{
118 switch ( mCfg.symbology() )
119 {
121 drawArrow( lineStart, xVal, yVal, magnitude );
122 break;
124 drawWindBarb( lineStart, xVal, yVal, magnitude );
125 break;
128 // not drawn one glyph at a time, see drawStreamlines() and drawTraces()
129 break;
130 }
131}
132
133QgsVectorFieldEngine::GlyphLayout QgsVectorFieldEngine::glyphLayout( const QgsPointXY &anchor, const QgsVectorFieldValueSource &source ) const
134{
135 const QgsRectangle extent = mContext.mapExtent();
136 if ( extent.isEmpty() || mOutputSize.width() <= 0 || mOutputSize.height() <= 0 )
137 return GlyphLayout();
138
139 const double mapUnitsPerPixelX = extent.width() / mOutputSize.width();
140 const double mapUnitsPerPixelY = extent.height() / mOutputSize.height();
141
142 GlyphLayout layout;
143 if ( mCfg.isOnUserDefinedGrid() )
144 {
145 layout.origin = anchor;
146 layout.spacingX = mCfg.userGridCellWidth() * mapUnitsPerPixelX;
147 layout.spacingY = mCfg.userGridCellHeight() * mapUnitsPerPixelY;
148 }
149 else if ( !source.nativeLayout( layout.origin, layout.spacingX, layout.spacingY ) )
150 {
151 // one glyph per data position was asked for, but the data has no regular layout to place them on
152 return GlyphLayout();
153 }
154
155 if ( !layout.isValid() )
156 return GlyphLayout();
157
158 // safety belt to avoid rendering huge amount of overlapping glyphs
159 const double minimumSpacing = std::max( 1.0, std::sqrt( static_cast<double>( mOutputSize.width() ) * mOutputSize.height() / VECTOR_FIELD_MAXIMUM_GLYPHS ) );
160 layout.spacingX = std::max( layout.spacingX, minimumSpacing * mapUnitsPerPixelX );
161 layout.spacingY = std::max( layout.spacingY, minimumSpacing * mapUnitsPerPixelY );
162
163 return layout;
164}
165
166void QgsVectorFieldEngine::drawGlyphs( std::unique_ptr<QgsVectorFieldValueSource> source, const QgsPointXY &anchor, QgsFeedback *feedback )
167{
168 if ( !source )
169 return;
170
171 switch ( mCfg.symbology() )
172 {
175 // not drawn one glyph at a time, see drawStreamlines() and drawTraces()
176 return;
177
180 break;
181 }
182
183 const GlyphLayout layout = glyphLayout( anchor, *source );
184 if ( !layout.isValid() )
185 return;
186
187 source->setSamplingWindow( layout.spacingX, layout.spacingY );
188
189 const QgsRectangle extent = mContext.mapExtent();
190 const double mapUnitsPerPixelX = extent.width() / mOutputSize.width();
191 const double mapUnitsPerPixelY = extent.height() / mOutputSize.height();
192
193 // grown so that glyphs centered just outside of the extent are still drawn
194 const double buffer = glyphExtentBuffer();
195 const QgsRectangle
196 bufferedExtent( extent.xMinimum() - buffer * mapUnitsPerPixelX, extent.yMinimum() - buffer * mapUnitsPerPixelY, extent.xMaximum() + buffer * mapUnitsPerPixelX, extent.yMaximum() + buffer * mapUnitsPerPixelY );
197
198 const QgsPointXY origin = layout.origin;
199 const double spacingX = layout.spacingX;
200 const double spacingY = layout.spacingY;
201
202 const long long firstColumn = static_cast<long long>( std::ceil( ( bufferedExtent.xMinimum() - origin.x() ) / spacingX ) );
203 const long long lastColumn = static_cast<long long>( std::floor( ( bufferedExtent.xMaximum() - origin.x() ) / spacingX ) );
204 const long long firstRow = static_cast<long long>( std::ceil( ( origin.y() - bufferedExtent.yMaximum() ) / spacingY ) );
205 const long long lastRow = static_cast<long long>( std::floor( ( origin.y() - bufferedExtent.yMinimum() ) / spacingY ) );
206
207 for ( long long row = firstRow; row <= lastRow; ++row )
208 {
209 if ( feedback && feedback->isCanceled() )
210 break;
211
212 const double y = origin.y() - row * spacingY;
213 for ( long long column = firstColumn; column <= lastColumn; ++column )
214 {
215 const QgsPointXY point( origin.x() + column * spacingX, y );
216 const QgsVector value = source->vectorValue( point );
217 if ( std::isnan( value.x() ) || std::isnan( value.y() ) )
218 continue;
219
220 drawGlyph( mContext.mapToPixel().transform( point ), value.x(), value.y(), value.length() );
221 }
222 }
223}
224
225void QgsVectorFieldEngine::drawStreamlines( std::unique_ptr<QgsVectorFieldValueSource> source, QgsRasterBlockFeedback *feedback )
226{
227 if ( !source )
228 return;
229
230 auto field = std::make_unique<QgsVectorFieldStreamlinesField>( std::move( source ), mContext, mVectorColoring, feedback );
231
232 field->updateSize( mContext );
233 field->setPixelFillingDensity( mCfg.streamLinesSettings().seedingDensity() );
234 field->setLineWidth( mContext.convertToPainterUnits( mCfg.lineWidth(), Qgis::RenderUnit::Millimeters ) );
235 field->setColor( mCfg.color() );
236 field->setFilter( mCfg.filterMin(), mCfg.filterMax() );
237
238 switch ( mCfg.streamLinesSettings().seedingMethod() )
239 {
241 if ( mCfg.isOnUserDefinedGrid() )
242 field->addGriddedTraces( mCfg.userGridCellWidth(), mCfg.userGridCellHeight() );
243 else
244 field->addTracesOnDataPoints( mContext.mapExtent() );
245 break;
247 field->addRandomTraces();
248 break;
249 }
250
251 if ( mContext.renderingStopped() )
252 return;
253
254 field->compose();
255 mContext.painter()->drawImage( field->topLeft(), field->image() );
256}
257
258void QgsVectorFieldEngine::drawTraces( std::unique_ptr<QgsVectorFieldValueSource> source )
259{
260 if ( !source )
261 return;
262
263 auto field = std::make_unique<QgsVectorFieldParticleTracesField>( std::move( source ), mContext, mVectorColoring );
264
265 field->updateSize( mContext );
266 field->setParticleSize( mContext.convertToPainterUnits( mCfg.lineWidth(), Qgis::RenderUnit::Millimeters ) );
267 field->setParticlesCount( mCfg.tracesSettings().particlesCount() );
268 field->setTailFactor( 1 );
269 field->setStumpParticleWithLifeTime( false );
270
271 // as the particles go through 1 pixel for dt=1 and Vmax, the maximum tail length is the time step
272 field->setTimeStep( mContext.convertToPainterUnits( mCfg.tracesSettings().maximumTailLength(), mCfg.tracesSettings().maximumTailLengthUnit() ) );
273
274 field->addRandomParticles();
275 field->moveParticles();
276
277 if ( mContext.renderingStopped() )
278 return;
279
280 mContext.painter()->drawImage( field->topLeft(), field->image() );
281}
282
283bool QgsVectorFieldEngine::calcVectorLineEnd(
284 QgsPointXY &lineEnd,
285 double &vectorLength,
286 double &cosAlpha,
287 double &sinAlpha, //out
288 const QgsPointXY &lineStart,
289 double xVal,
290 double yVal,
291 double magnitude //in
292)
293{
294 // return true on error
295
296 if ( xVal == 0.0 && yVal == 0.0 )
297 return true;
298
299 // do not render if magnitude is outside of the filtered range (if filtering is enabled)
300 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
301 return true;
302 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
303 return true;
304
305 // Determine the angle of the vector, counter-clockwise, from east
306 // (and associated trigs)
307 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD;
308
309 cosAlpha = cos( vectorAngle );
310 sinAlpha = sin( vectorAngle );
311
312 // Now determine the X and Y distances of the end of the line from the start
313 double xDist = 0.0;
314 double yDist = 0.0;
315 switch ( mCfg.arrowSettings().shaftLengthMethod() )
316 {
318 {
319 const double minShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().minShaftLength(), Qgis::RenderUnit::Millimeters );
320 const double maxShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().maxShaftLength(), Qgis::RenderUnit::Millimeters );
321 const double minVal = mMinMag;
322 const double maxVal = mMaxMag;
323 const double k = ( magnitude - minVal ) / ( maxVal - minVal );
324 const double L = minShaftLength + k * ( maxShaftLength - minShaftLength );
325 xDist = cosAlpha * L;
326 yDist = sinAlpha * L;
327 break;
328 }
330 {
331 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
332 xDist = scaleFactor * xVal;
333 yDist = scaleFactor * yVal;
334 break;
335 }
337 {
338 // We must be using a fixed length
339 const double fixedShaftLength = mContext.convertToPainterUnits( mCfg.arrowSettings().fixedShaftLength(), Qgis::RenderUnit::Millimeters );
340 xDist = cosAlpha * fixedShaftLength;
341 yDist = sinAlpha * fixedShaftLength;
342 break;
343 }
344 }
345
346 // Flip the Y axis (pixel vs real-world axis)
347 yDist *= -1.0;
348
349 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
350 return true;
351
352 // Determine the line coords
353 lineEnd = QgsPointXY( lineStart.x() + xDist, lineStart.y() + yDist );
354
355 vectorLength = sqrt( xDist * xDist + yDist * yDist );
356
357 // skip rendering if line bbox does not intersect the QImage area
358 if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
359 return true;
360
361 return false; //success
362}
363
364void QgsVectorFieldEngine::drawArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
365{
366 QgsPointXY lineEnd;
367 double vectorLength;
368 double cosAlpha, sinAlpha;
369 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha, lineStart, xVal, yVal, magnitude ) )
370 return;
371
372 // Make a set of vector head coordinates that we will place at the end of each vector,
373 // scale, translate and rotate.
374 QgsPointXY vectorHeadPoints[3];
375 QVector<QPointF> finalVectorHeadPoints( 3 );
376
377 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
378 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
379
380 // First head point: top of ->
381 vectorHeadPoints[0].setX( -1.0 * vectorHeadLengthRatio );
382 vectorHeadPoints[0].setY( vectorHeadWidthRatio * 0.5 );
383
384 // Second head point: right of ->
385 vectorHeadPoints[1].setX( 0.0 );
386 vectorHeadPoints[1].setY( 0.0 );
387
388 // Third head point: bottom of ->
389 vectorHeadPoints[2].setX( -1.0 * vectorHeadLengthRatio );
390 vectorHeadPoints[2].setY( -1.0 * vectorHeadWidthRatio * 0.5 );
391
392 // Determine the arrow head coords
393 for ( int j = 0; j < 3; j++ )
394 {
395 finalVectorHeadPoints[j].setX( lineEnd.x() + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength ) );
396
397 finalVectorHeadPoints[j].setY( lineEnd.y() - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength ) );
398 }
399
400 // Now actually draw the vector
401 QPen pen( mContext.painter()->pen() );
402 pen.setColor( mVectorColoring.color( magnitude ) );
403 mContext.painter()->setPen( pen );
404 mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
405 mContext.painter()->drawPolygon( finalVectorHeadPoints );
406}
407
408void QgsVectorFieldEngine::drawWindBarb( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude )
409{
410 // do not render if magnitude is outside of the filtered range (if filtering is enabled)
411 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
412 return;
413 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
414 return;
415
416 QPen pen( mContext.painter()->pen() );
417 pen.setColor( mVectorColoring.color( magnitude ) );
418 mContext.painter()->setPen( pen );
419
420 // we need a brush to fill center circle and pennants
421 QBrush brush( pen.color() );
422 mContext.painter()->setBrush( brush );
423
424 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(), mCfg.windBarbSettings().shaftLengthUnits() );
425 if ( shaftLength < 1 )
426 return;
427
428 // Check if barb is above or below the equinox
429 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.x(), lineStart.y() );
430 bool isNorthHemisphere = true;
431 try
432 {
433 const QgsPointXY geoPoint = mGeographicTransform->transform( mapPoint );
434 isNorthHemisphere = geoPoint.y() >= 0;
435 }
436 catch ( QgsCsException & )
437 {
438 QgsDebugError( u"Could not transform wind barb coordinates to geographic ones"_s );
439 }
440
441 const double d = shaftLength / 25; // this is a magic number ratio between shaft length and other barb dimensions
442 const double centerRadius = d;
443 const double zeroCircleRadius = 2 * d;
444 const double barbLength = 8 * d + pen.widthF();
445 const double barbAngle = 135;
446 const double barbOffset = 2 * d + pen.widthF();
447 const int sign = isNorthHemisphere ? 1 : -1;
448
449 // Determine the angle of the vector, counter-clockwise, from east
450 // (and associated trigs)
451 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD;
452
453 // Now determine the X and Y distances of the end of the line from the start
454 // Flip the Y axis (pixel vs real-world axis)
455 const double xDist = cos( vectorAngle ) * shaftLength;
456 const double yDist = -sin( vectorAngle ) * shaftLength;
457
458 // Determine the line coords
459 const QgsPointXY lineEnd = QgsPointXY( lineStart.x() - xDist, lineStart.y() - yDist );
460
461 // skip rendering if line bbox does not intersect the QImage area
462 if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
463 return;
464
465 // scale the magnitude to convert it to knots
466 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier();
467 QgsPointXY nextLineOrigin = lineEnd;
468
469 // special case for no wind, just an empty circle
470 if ( knots < 2.5 )
471 {
472 mContext.painter()->setBrush( Qt::NoBrush );
473 mContext.painter()->drawEllipse( lineStart.toQPointF(), zeroCircleRadius, zeroCircleRadius );
474 mContext.painter()->setBrush( brush );
475 return;
476 }
477
478 const double azimuth = lineEnd.azimuth( lineStart );
479
480 // conditionally draw the shaft
481 if ( knots < 47.5 && knots > 7.5 )
482 {
483 // When first barb is a '10', we want to draw the shaft and barb as a single polyline for a proper join
484 const QVector< QPointF > pts { lineStart.toQPointF(), lineEnd.toQPointF(), nextLineOrigin.project( barbLength, azimuth + barbAngle * sign ).toQPointF() };
485 mContext.painter()->drawPolyline( pts );
486 nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth );
487 knots -= 10;
488 }
489 else
490 {
491 // draw just the shaft
492 mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() );
493 }
494
495 // draw the center circle
496 mContext.painter()->drawEllipse( lineStart.toQPointF(), centerRadius, centerRadius );
497
498 // draw pennants (50)
499 while ( knots > 47.5 )
500 {
501 const QVector< QPointF >
502 pts { nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength / 1.414, azimuth + 90 * sign ).toQPointF(), nextLineOrigin.project( barbLength / 1.414, azimuth ).toQPointF() };
503 mContext.painter()->drawPolygon( pts );
504 knots -= 50;
505
506 // don't use an offset for the next pennant
507 if ( knots > 47.5 )
508 nextLineOrigin = nextLineOrigin.project( barbLength / 1.414, azimuth );
509 else
510 nextLineOrigin = nextLineOrigin.project( barbLength / 1.414 + barbOffset, azimuth );
511 }
512
513 // draw large barbs (10)
514 while ( knots > 7.5 )
515 {
516 mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength, azimuth + barbAngle * sign ).toQPointF() );
517 nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth );
518 knots -= 10;
519 }
520
521 // draw small barb (5)
522 if ( knots > 2.5 )
523 {
524 // a single '5' barb should not start at the line end
525 if ( nextLineOrigin == lineEnd )
526 nextLineOrigin = nextLineOrigin.project( barbLength / 2, azimuth );
527
528 mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength / 2, azimuth + barbAngle * sign ).toQPointF() );
529 }
530}
@ Gridded
Seeds start points on data grid or user regular grid.
Definition qgis.h:7263
@ Random
Seeds start points randomly.
Definition qgis.h:7264
@ WindBarbs
Displaying vector dataset with wind barbs.
Definition qgis.h:7250
@ Arrows
Displaying vector dataset with arrows.
Definition qgis.h:7247
@ Traces
Displaying vector dataset with particle traces.
Definition qgis.h:7249
@ Streamlines
Displaying vector dataset with streamlines.
Definition qgis.h:7248
@ Millimeters
Millimeters.
Definition qgis.h:5771
@ Fixed
Use fixed length fixedShaftLength() regardless of vector's magnitude.
Definition qgis.h:7234
@ Scaled
Scale vector magnitude by factor scaleFactor().
Definition qgis.h:7233
@ MinMax
Scale vector magnitude linearly to fit in range of vectorFilterMin() and vectorFilterMax().
Definition qgis.h:7232
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
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
Feedback object tailored for raster block reading.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
double minShaftLength() const
Returns mininimum shaft length (in millimeters).
Qgis::VectorFieldArrowScalingMethod shaftLengthMethod() const
Returns method used for drawing arrows.
double maxShaftLength() const
Returns maximum shaft length (in millimeters).
QgsVectorFieldEngine(double datasetMagMaximumValue, double datasetMagMinimumValue, const QgsVectorFieldSettings &settings, QgsRenderContext &context, QSize size)
Ctor.
void drawGlyph(const QgsPointXY &lineStart, double xVal, double yVal, double magnitude)
Draws a single glyph at lineStart, in painter coordinates, using the symbology of the settings the en...
void drawStreamlines(std::unique_ptr< QgsVectorFieldValueSource > source, QgsRasterBlockFeedback *feedback=nullptr)
Integrates and draws streamlines over the whole rendered extent, sampling the vector field from sourc...
void drawGlyphs(std::unique_ptr< QgsVectorFieldValueSource > source, const QgsPointXY &anchor, QgsFeedback *feedback=nullptr)
Draws one glyph per position of the placement grid described by the settings the engine was construct...
void drawTraces(std::unique_ptr< QgsVectorFieldValueSource > source)
Seeds particles over the whole rendered extent, moves them one time step and draws their traces,...
double glyphExtentBuffer() const
Returns the distance, in painter units, by which the rendered extent must be grown so that glyphs cen...
Represents a renderer settings for vector datasets.
double filterMin() const
Returns filter value for vector magnitudes.
QgsVectorFieldArrowSettings arrowSettings() const
Returns settings for vector rendered with arrows.
int userGridCellWidth() const
Returns width in pixels of user grid cell.
Qgis::VectorFieldSymbology symbology() const
Returns the displaying method used to render vector datasets.
bool isOnUserDefinedGrid() const
Returns whether vectors are drawn on user-defined grid.
int userGridCellHeight() const
Returns height in pixels of user grid cell.
double filterMax() const
Returns filter value for vector magnitudes.
Abstract source of vector (u/v) values sampled at arbitrary map positions.
virtual bool nativeLayout(QgsPointXY &origin, double &spacingX, double &spacingY) const
Returns the grid on which the data itself is sampled through origin, spacingX and spacingY,...
Represent a 2-dimensional vector.
Definition qgsvector.h:34
double y() const
Returns the vector's y-component.
Definition qgsvector.h:155
double x() const
Returns the vector's x-component.
Definition qgsvector.h:146
double length() const
Returns the length of the vector.
Definition qgsvector.h:127
#define M_DEG2RAD
#define QgsDebugError(str)
Definition qgslogger.h:71
constexpr qint64 VECTOR_FIELD_MAXIMUM_GLYPHS
Upper bound on the number of glyphs drawn in a single render.