Skip to content

Commit

Permalink
[feature][symbology] Add optional rotation angle for point pattern
Browse files Browse the repository at this point in the history
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 8709402
Show file tree
Hide file tree
Showing 9 changed files with 455 additions and 205 deletions.
19 changes: 19 additions & 0 deletions python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in
Expand Up @@ -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 );


Expand Down
81 changes: 73 additions & 8 deletions src/core/symbology/qgsfillsymbollayer.cpp
Expand Up @@ -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();
Expand Down Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 ) )
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
18 changes: 18 additions & 0 deletions src/core/symbology/qgsfillsymbollayer.h
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down
13 changes: 13 additions & 0 deletions src/gui/symbology/qgssymbollayerwidget.cpp
Expand Up @@ -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 )
Expand All @@ -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() );
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 8709402

Please sign in to comment.