Skip to content
Permalink
Browse files
[feature][symbology] Add optional rotation angle for point pattern
fill

Allows the fill pattern to be rotated

Sponsored by North Road, thanks to SLYR
  • Loading branch information
nyalldawson committed Oct 24, 2021
1 parent 11992d6 commit 87094024afc99d604954409c83686f74d335e589
@@ -2411,12 +2411,31 @@ in different locations with every map refresh).

.. seealso:: :py:func:`seed`

.. versionadded:: 3.24
%End

double angle() const;
%Docstring
Returns the rotation angle of the pattern, in degrees clockwise.

.. seealso:: :py:func:`setAngle`

.. versionadded:: 3.24
%End

void setAngle( double angle );
%Docstring
Sets the rotation ``angle`` of the pattern, in degrees clockwise.

.. seealso:: :py:func:`angle`

.. versionadded:: 3.24
%End

protected:



virtual void applyDataDefinedSettings( QgsSymbolRenderContext &context );


@@ -3441,6 +3441,11 @@ QgsSymbolLayer *QgsPointPatternFillSymbolLayer::create( const QVariantMap &prope
layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
}

if ( properties.contains( QStringLiteral( "angle" ) ) )
{
layer->setAngle( properties[QStringLiteral( "angle" )].toDouble() );
}

layer->restoreOldDataDefinedProperties( properties );

return layer.release();
@@ -3555,7 +3560,9 @@ void QgsPointPatternFillSymbolLayer::startRender( QgsSymbolRenderContext &contex
|| mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyRandomOffsetY )
|| mClipMode != Qgis::MarkerClipMode::Shape
|| !qgsDoubleNear( mRandomDeviationX, 0 )
|| !qgsDoubleNear( mRandomDeviationY, 0 );
|| !qgsDoubleNear( mRandomDeviationY, 0 )
|| !qgsDoubleNear( mAngle, 0 )
|| mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyAngle );

if ( mRenderUsingMarkers )
{
@@ -3592,6 +3599,13 @@ void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, con
return;
}

double angle = mAngle;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyAngle ) )
{
context.setOriginalValueVariable( angle );
angle = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyAngle, context.renderContext().expressionContext(), angle );
}

double distanceX = mDistanceX;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDistanceX ) )
{
@@ -3707,15 +3721,58 @@ void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, con
}
}

const bool applyBrushTransform = applyBrushTransformFromContext( &context );
const QRectF boundingRect = points.boundingRect();
double left = boundingRect.left() - 2 * width;
double top = boundingRect.top() - 2 * height;
const double right = boundingRect.right() + 2 * width;
const double bottom = boundingRect.bottom() + 2 * height;
if ( !applyBrushTransformFromContext( &context ) )

QTransform invertedRotateTransform;
double left;
double top;
double right;
double bottom;

if ( !qgsDoubleNear( angle, 0 ) )
{
left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
QTransform transform;
if ( applyBrushTransform )
{
// rotation applies around center of feature
transform.translate( -boundingRect.center().x(),
-boundingRect.center().y() );
transform.rotate( -angle );
transform.translate( boundingRect.center().x(),
boundingRect.center().y() );
}
else
{
// rotation applies around top of viewport
transform.rotate( -angle );
}

const QRectF transformedBounds = transform.map( points ).boundingRect();
left = transformedBounds.left() - 2 * width;
top = transformedBounds.top() - 2 * height;
right = transformedBounds.right() + 2 * width;
bottom = transformedBounds.bottom() + 2 * height;
invertedRotateTransform = transform.inverted();

if ( !applyBrushTransform )
{
left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
}
}
else
{
left = boundingRect.left() - 2 * width;
top = boundingRect.top() - 2 * height;
right = boundingRect.right() + 2 * width;
bottom = boundingRect.bottom() + 2 * height;

if ( !applyBrushTransform )
{
left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
}
}

unsigned long seed = mSeed;
@@ -3776,6 +3833,13 @@ void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, con
if ( !alternateColumn )
y -= displacementPixelY;

if ( !qgsDoubleNear( angle, 0 ) )
{
double xx = x;
double yy = y;
invertedRotateTransform.map( xx, yy, &x, &y );
}

if ( useRandomShift )
{
x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
@@ -3862,6 +3926,7 @@ QVariantMap QgsPointPatternFillSymbolLayer::properties() const
map.insert( QStringLiteral( "random_deviation_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
map.insert( QStringLiteral( "random_deviation_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
map.insert( QStringLiteral( "angle" ), mAngle );
return map;
}

@@ -2139,6 +2139,22 @@ class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer
*/
void setSeed( unsigned long seed ) { mSeed = seed; }

/**
* Returns the rotation angle of the pattern, in degrees clockwise.
*
* \see setAngle()
* \since QGIS 3.24
*/
double angle() const { return mAngle; }

/**
* Sets the rotation \a angle of the pattern, in degrees clockwise.
*
* \see angle()
* \since QGIS 3.24
*/
void setAngle( double angle ) { mAngle = angle; }

protected:
std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol;
double mDistanceX = 15;
@@ -2168,6 +2184,8 @@ class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer
QgsMapUnitScale mRandomDeviationYMapUnitScale;
unsigned long mSeed = 0;

double mAngle = 0;

void applyDataDefinedSettings( QgsSymbolRenderContext &context ) override;

private:
@@ -3288,6 +3288,17 @@ QgsPointPatternFillSymbolLayerWidget::QgsPointPatternFillSymbolLayerWidget( QgsV
emit changed();
}
} );

mAngleSpinBox->setShowClearButton( true );
mAngleSpinBox->setClearValue( 0 );
connect( mAngleSpinBox, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, [ = ]( double d )
{
if ( mLayer )
{
mLayer->setAngle( d );
emit changed();
}
} );
}

void QgsPointPatternFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
@@ -3304,6 +3315,7 @@ void QgsPointPatternFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer
whileBlocking( mVerticalDisplacementSpinBox )->setValue( mLayer->displacementY() );
whileBlocking( mHorizontalOffsetSpinBox )->setValue( mLayer->offsetX() );
whileBlocking( mVerticalOffsetSpinBox )->setValue( mLayer->offsetY() );
whileBlocking( mAngleSpinBox )->setValue( mLayer->angle() );

mHorizontalDistanceUnitWidget->blockSignals( true );
mHorizontalDistanceUnitWidget->setUnit( mLayer->distanceXUnit() );
@@ -3352,6 +3364,7 @@ void QgsPointPatternFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer
registerDataDefinedButton( mRandomXDDBtn, QgsSymbolLayer::PropertyRandomOffsetX );
registerDataDefinedButton( mRandomYDDBtn, QgsSymbolLayer::PropertyRandomOffsetY );
registerDataDefinedButton( mSeedDdbtn, QgsSymbolLayer::PropertyRandomSeed );
registerDataDefinedButton( mAngleDDBtn, QgsSymbolLayer::PropertyAngle );
}

QgsSymbolLayer *QgsPointPatternFillSymbolLayerWidget::symbolLayer()

0 comments on commit 8709402

Please sign in to comment.