29using namespace Qt::StringLiterals;
32#define M_DEG2RAD 0.0174532925
39 : mMinMag( datasetMagMinimumValue )
40 , mMaxMag( datasetMagMaximumValue )
43 , mVectorColoring( settings.vectorStrokeColoring() )
48 case Qgis::VectorFieldSymbology::WindBarbs:
50 const QgsCoordinateReferenceSystem mapCrs = mContext.distanceArea().sourceCrs();
51 mGeographicTransform = std::make_unique<QgsCoordinateTransform>( mapCrs, mapCrs.toGeographicCrs(), mContext.transformContext() );
61 QPainter *painter = mContext.painter();
63 mScopedPainterState = std::make_unique<QgsScopedQPainterState>( painter );
64 mContext.setPainterFlagsUsingContext( painter );
66 QPen pen = painter->pen();
67 pen.setCapStyle( Qt::FlatCap );
68 pen.setJoinStyle( Qt::MiterJoin );
71 pen.setWidthF( penWidth );
72 painter->setPen( pen );
80 switch ( mCfg.symbology() )
83 buffer = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(), mCfg.windBarbSettings().shaftLengthUnits() );
87 switch ( mCfg.arrowSettings().shaftLengthMethod() )
97 const double magnitude = mCfg.filterMax() >= 0 ? std::min( mMaxMag, mCfg.filterMax() ) : mMaxMag;
98 buffer = mCfg.arrowSettings().scaleFactor() * magnitude;
113 return std::max( 0.0, buffer );
118 switch ( mCfg.symbology() )
121 drawArrow( lineStart, xVal, yVal, magnitude );
124 drawWindBarb( lineStart, xVal, yVal, magnitude );
136 if ( extent.
isEmpty() || mOutputSize.width() <= 0 || mOutputSize.height() <= 0 )
137 return GlyphLayout();
139 const double mapUnitsPerPixelX = extent.
width() / mOutputSize.width();
140 const double mapUnitsPerPixelY = extent.
height() / mOutputSize.height();
145 layout.origin = anchor;
149 else if ( !source.
nativeLayout( layout.origin, layout.spacingX, layout.spacingY ) )
152 return GlyphLayout();
155 if ( !layout.isValid() )
156 return GlyphLayout();
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 );
171 switch ( mCfg.symbology() )
183 const GlyphLayout layout = glyphLayout( anchor, *source );
184 if ( !layout.isValid() )
187 source->setSamplingWindow( layout.spacingX, layout.spacingY );
190 const double mapUnitsPerPixelX = extent.
width() / mOutputSize.width();
191 const double mapUnitsPerPixelY = extent.
height() / mOutputSize.height();
196 bufferedExtent( extent.
xMinimum() - buffer * mapUnitsPerPixelX, extent.
yMinimum() - buffer * mapUnitsPerPixelY, extent.
xMaximum() + buffer * mapUnitsPerPixelX, extent.
yMaximum() + buffer * mapUnitsPerPixelY );
199 const double spacingX = layout.spacingX;
200 const double spacingY = layout.spacingY;
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 ) );
207 for (
long long row = firstRow; row <= lastRow; ++row )
212 const double y = origin.
y() - row * spacingY;
213 for (
long long column = firstColumn; column <= lastColumn; ++column )
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() ) )
220 drawGlyph( mContext.mapToPixel().transform( point ), value.
x(), value.
y(), value.
length() );
230 auto field = std::make_unique<QgsVectorFieldStreamlinesField>( std::move( source ), mContext, mVectorColoring, feedback );
232 field->updateSize( mContext );
233 field->setPixelFillingDensity( mCfg.streamLinesSettings().seedingDensity() );
235 field->setColor( mCfg.color() );
236 field->setFilter( mCfg.filterMin(), mCfg.filterMax() );
238 switch ( mCfg.streamLinesSettings().seedingMethod() )
241 if ( mCfg.isOnUserDefinedGrid() )
242 field->addGriddedTraces( mCfg.userGridCellWidth(), mCfg.userGridCellHeight() );
244 field->addTracesOnDataPoints( mContext.mapExtent() );
247 field->addRandomTraces();
251 if ( mContext.renderingStopped() )
255 mContext.painter()->drawImage( field->topLeft(), field->image() );
263 auto field = std::make_unique<QgsVectorFieldParticleTracesField>( std::move( source ), mContext, mVectorColoring );
265 field->updateSize( mContext );
267 field->setParticlesCount( mCfg.tracesSettings().particlesCount() );
268 field->setTailFactor( 1 );
269 field->setStumpParticleWithLifeTime(
false );
272 field->setTimeStep( mContext.convertToPainterUnits( mCfg.tracesSettings().maximumTailLength(), mCfg.tracesSettings().maximumTailLengthUnit() ) );
274 field->addRandomParticles();
275 field->moveParticles();
277 if ( mContext.renderingStopped() )
280 mContext.painter()->drawImage( field->topLeft(), field->image() );
283bool QgsVectorFieldEngine::calcVectorLineEnd(
285 double &vectorLength,
296 if ( xVal == 0.0 && yVal == 0.0 )
309 cosAlpha = cos( vectorAngle );
310 sinAlpha = sin( vectorAngle );
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;
331 const double scaleFactor = mCfg.arrowSettings().scaleFactor();
332 xDist = scaleFactor * xVal;
333 yDist = scaleFactor * yVal;
340 xDist = cosAlpha * fixedShaftLength;
341 yDist = sinAlpha * fixedShaftLength;
349 if ( std::abs( xDist ) < 1 && std::abs( yDist ) < 1 )
353 lineEnd = QgsPointXY( lineStart.
x() + xDist, lineStart.
y() + yDist );
355 vectorLength = sqrt( xDist * xDist + yDist * yDist );
358 if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
364void QgsVectorFieldEngine::drawArrow(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
368 double cosAlpha, sinAlpha;
369 if ( calcVectorLineEnd( lineEnd, vectorLength, cosAlpha, sinAlpha, lineStart, xVal, yVal, magnitude ) )
374 QgsPointXY vectorHeadPoints[3];
375 QVector<QPointF> finalVectorHeadPoints( 3 );
377 const double vectorHeadWidthRatio = mCfg.arrowSettings().arrowHeadWidthRatio();
378 const double vectorHeadLengthRatio = mCfg.arrowSettings().arrowHeadLengthRatio();
381 vectorHeadPoints[0].
setX( -1.0 * vectorHeadLengthRatio );
382 vectorHeadPoints[0].
setY( vectorHeadWidthRatio * 0.5 );
385 vectorHeadPoints[1].
setX( 0.0 );
386 vectorHeadPoints[1].
setY( 0.0 );
389 vectorHeadPoints[2].
setX( -1.0 * vectorHeadLengthRatio );
390 vectorHeadPoints[2].
setY( -1.0 * vectorHeadWidthRatio * 0.5 );
393 for (
int j = 0; j < 3; j++ )
395 finalVectorHeadPoints[j].setX( lineEnd.
x() + ( vectorHeadPoints[j].x() * cosAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * sinAlpha * vectorLength ) );
397 finalVectorHeadPoints[j].setY( lineEnd.
y() - ( vectorHeadPoints[j].x() * sinAlpha * vectorLength ) - ( vectorHeadPoints[j].y() * cosAlpha * vectorLength ) );
401 QPen pen( mContext.painter()->pen() );
402 pen.setColor( mVectorColoring.color( magnitude ) );
403 mContext.painter()->setPen( pen );
405 mContext.painter()->drawPolygon( finalVectorHeadPoints );
408void QgsVectorFieldEngine::drawWindBarb(
const QgsPointXY &lineStart,
double xVal,
double yVal,
double magnitude )
411 if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() )
413 if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() )
416 QPen pen( mContext.painter()->pen() );
417 pen.setColor( mVectorColoring.color( magnitude ) );
418 mContext.painter()->setPen( pen );
421 QBrush brush( pen.color() );
422 mContext.painter()->setBrush( brush );
424 const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(), mCfg.windBarbSettings().shaftLengthUnits() );
425 if ( shaftLength < 1 )
429 const QgsPointXY mapPoint = mContext.mapToPixel().toMapCoordinates( lineStart.
x(), lineStart.
y() );
430 bool isNorthHemisphere =
true;
433 const QgsPointXY geoPoint = mGeographicTransform->transform( mapPoint );
434 isNorthHemisphere = geoPoint.
y() >= 0;
436 catch ( QgsCsException & )
438 QgsDebugError( u
"Could not transform wind barb coordinates to geographic ones"_s );
441 const double d = shaftLength / 25;
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;
451 const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() *
M_DEG2RAD;
455 const double xDist = cos( vectorAngle ) * shaftLength;
456 const double yDist = -sin( vectorAngle ) * shaftLength;
459 const QgsPointXY lineEnd = QgsPointXY( lineStart.
x() - xDist, lineStart.
y() - yDist );
462 if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) )
466 double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier();
467 QgsPointXY nextLineOrigin = lineEnd;
472 mContext.painter()->setBrush( Qt::NoBrush );
473 mContext.painter()->drawEllipse( lineStart.
toQPointF(), zeroCircleRadius, zeroCircleRadius );
474 mContext.painter()->setBrush( brush );
478 const double azimuth = lineEnd.
azimuth( lineStart );
481 if ( knots < 47.5 && knots > 7.5 )
485 mContext.painter()->drawPolyline( pts );
486 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
496 mContext.painter()->drawEllipse( lineStart.
toQPointF(), centerRadius, centerRadius );
499 while ( knots > 47.5 )
501 const QVector< QPointF >
503 mContext.painter()->drawPolygon( pts );
508 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414, azimuth );
510 nextLineOrigin = nextLineOrigin.
project( barbLength / 1.414 + barbOffset, azimuth );
514 while ( knots > 7.5 )
516 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength, azimuth + barbAngle * sign ).
toQPointF() );
517 nextLineOrigin = nextLineOrigin.
project( barbOffset, azimuth );
525 if ( nextLineOrigin == lineEnd )
526 nextLineOrigin = nextLineOrigin.
project( barbLength / 2, azimuth );
528 mContext.painter()->drawLine( nextLineOrigin.
toQPointF(), nextLineOrigin.
project( barbLength / 2, azimuth + barbAngle * sign ).
toQPointF() );
@ Gridded
Seeds start points on data grid or user regular grid.
@ Random
Seeds start points randomly.
@ WindBarbs
Displaying vector dataset with wind barbs.
@ Arrows
Displaying vector dataset with arrows.
@ Traces
Displaying vector dataset with particle traces.
@ Streamlines
Displaying vector dataset with streamlines.
@ Millimeters
Millimeters.
@ Fixed
Use fixed length fixedShaftLength() regardless of vector's magnitude.
@ Scaled
Scale vector magnitude by factor scaleFactor().
@ MinMax
Scale vector magnitude linearly to fit in range of vectorFilterMin() and vectorFilterMax().
Base class for feedback objects to be used for cancellation of something running in a worker thread.
bool isCanceled() const
Tells whether the operation has been canceled already.
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
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.
double azimuth(const QgsPointXY &other) const
Calculates azimuth between this point and other one (clockwise in degree, starting from north).
void setX(double x)
Sets the x value of the point.
QPointF toQPointF() const
Converts a point to a QPointF.
Feedback object tailored for raster block reading.
A rectangle specified with double values.
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.
~QgsVectorFieldEngine()
Dtor.
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.
double y() const
Returns the vector's y-component.
double x() const
Returns the vector's x-component.
double length() const
Returns the length of the vector.
#define QgsDebugError(str)
constexpr qint64 VECTOR_FIELD_MAXIMUM_GLYPHS
Upper bound on the number of glyphs drawn in a single render.