diff --git a/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in b/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in
index 1edb450b6378..5328203facf8 100644
--- a/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in
+++ b/python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in
@@ -2250,11 +2250,173 @@ Sets the marker clipping ``mode``, which defines how markers are clipped at the
.. seealso:: :py:func:`clipMode`
+.. versionadded:: 3.24
+%End
+
+ void setMaximumRandomDeviationX( double deviation );
+%Docstring
+Sets the maximum horizontal random ``deviation`` of points in the pattern.
+
+Units are set via :py:func:`~QgsPointPatternFillSymbolLayer.setMaximumRandomDeviationXUnit`.
+
+.. seealso:: :py:func:`maximumRandomDeviationX`
+
+.. seealso:: :py:func:`setMaximumRandomDeviationY`
+
+.. versionadded:: 3.24
+%End
+
+ double maximumRandomDeviationX() const;
+%Docstring
+Returns the maximum horizontal random deviation of points in the pattern.
+
+Units are retrieved via :py:func:`~QgsPointPatternFillSymbolLayer.maximumRandomDeviationXUnit`.
+
+.. seealso:: :py:func:`setMaximumRandomDeviationX`
+
+.. seealso:: :py:func:`maximumRandomDeviationY`
+
+.. versionadded:: 3.24
+%End
+
+ void setMaximumRandomDeviationY( double deviation );
+%Docstring
+Sets the maximum vertical random ``deviation`` of points in the pattern.
+
+Units are set via :py:func:`~QgsPointPatternFillSymbolLayer.setMaximumRandomDeviationYUnit`.
+
+.. seealso:: :py:func:`maximumRandomDeviationY`
+
+.. seealso:: :py:func:`setMaximumRandomDeviationX`
+
+.. versionadded:: 3.24
+%End
+
+ double maximumRandomDeviationY() const;
+%Docstring
+Returns the maximum vertical random deviation of points in the pattern.
+
+Units are retrieved via :py:func:`~QgsPointPatternFillSymbolLayer.maximumRandomDeviationYUnit`.
+
+.. seealso:: :py:func:`setMaximumRandomDeviationY`
+
+.. seealso:: :py:func:`maximumRandomDeviationX`
+
+.. versionadded:: 3.24
+%End
+
+ void setRandomDeviationXUnit( QgsUnitTypes::RenderUnit unit );
+%Docstring
+Sets the ``unit`` for the horizontal random deviation of points in the pattern.
+
+.. seealso:: :py:func:`randomDeviationXUnit`
+
+.. seealso:: :py:func:`setRandomDeviationYUnit`
+
+.. versionadded:: 3.24
+%End
+
+ QgsUnitTypes::RenderUnit randomDeviationXUnit() const;
+%Docstring
+Returns the units for the horizontal random deviation of points in the pattern.
+
+.. seealso:: :py:func:`setRandomDeviationXUnit`
+
+.. seealso:: :py:func:`randomDeviationYUnit`
+
+.. versionadded:: 3.24
+%End
+
+ void setRandomDeviationYUnit( QgsUnitTypes::RenderUnit unit );
+%Docstring
+Sets the ``unit`` for the vertical random deviation of points in the pattern.
+
+.. seealso:: :py:func:`randomDeviationYUnit`
+
+.. seealso:: :py:func:`setRandomDeviationXUnit`
+
+.. versionadded:: 3.24
+%End
+
+ QgsUnitTypes::RenderUnit randomDeviationYUnit() const;
+%Docstring
+Returns the units for the vertical random deviation of points in the pattern.
+
+.. seealso:: :py:func:`setRandomDeviationYUnit`
+
+.. seealso:: :py:func:`randomDeviationXUnit`
+
+.. versionadded:: 3.24
+%End
+
+ const QgsMapUnitScale &randomDeviationXMapUnitScale() const;
+%Docstring
+Returns the unit scale for the horizontal random deviation of points in the pattern.
+
+.. seealso:: :py:func:`setRandomDeviationXMapUnitScale`
+
+.. seealso:: :py:func:`randomDeviationYMapUnitScale`
+
+.. versionadded:: 3.24
+%End
+
+ const QgsMapUnitScale &randomDeviationYMapUnitScale() const;
+%Docstring
+Returns the unit scale for the vertical random deviation of points in the pattern.
+
+.. seealso:: :py:func:`setRandomDeviationXMapUnitScale`
+
+.. seealso:: :py:func:`randomDeviationXMapUnitScale`
+
+.. versionadded:: 3.24
+%End
+
+ void setRandomDeviationXMapUnitScale( const QgsMapUnitScale &scale );
+%Docstring
+Sets the unit ``scale`` for the horizontal random deviation of points in the pattern.
+
+.. seealso:: :py:func:`randomDeviationXMapUnitScale`
+
+.. seealso:: :py:func:`setRandomDeviationYMapUnitScale`
+
+.. versionadded:: 3.24
+%End
+
+ void setRandomDeviationYMapUnitScale( const QgsMapUnitScale &scale );
+%Docstring
+Sets the unit ``scale`` for the vertical random deviation of points in the pattern.
+
+.. seealso:: :py:func:`randomDeviationYMapUnitScale`
+
+.. seealso:: :py:func:`setRandomDeviationXMapUnitScale`
+
+.. versionadded:: 3.24
+%End
+
+ unsigned long seed() const;
+%Docstring
+Returns the random number seed to use when randomly shifting points, or 0 if
+a truly random sequence will be used (causing points to appear in different locations with every map refresh).
+
+.. seealso:: :py:func:`setSeed`
+
+.. versionadded:: 3.24
+%End
+
+ void setSeed( unsigned long seed );
+%Docstring
+Sets the random number ``seed`` to use when randomly shifting points, or 0 if
+a truly random sequence will be used on every rendering (causing points to appear
+in different locations with every map refresh).
+
+.. seealso:: :py:func:`seed`
+
.. versionadded:: 3.24
%End
protected:
+
virtual void applyDataDefinedSettings( QgsSymbolRenderContext &context );
diff --git a/python/core/auto_generated/symbology/qgssymbollayer.sip.in b/python/core/auto_generated/symbology/qgssymbollayer.sip.in
index 5e1b5f22db34..fb518d338040 100644
--- a/python/core/auto_generated/symbology/qgssymbollayer.sip.in
+++ b/python/core/auto_generated/symbology/qgssymbollayer.sip.in
@@ -157,6 +157,8 @@ class QgsSymbolLayer
PropertyLineStartColorValue,
PropertyLineEndColorValue,
PropertyMarkerClipping,
+ PropertyRandomOffsetX,
+ PropertyRandomOffsetY,
};
static const QgsPropertiesDefinition &propertyDefinitions();
diff --git a/src/core/symbology/qgsfillsymbollayer.cpp b/src/core/symbology/qgsfillsymbollayer.cpp
index 13d6d6947468..acca7d79c5c2 100644
--- a/src/core/symbology/qgsfillsymbollayer.cpp
+++ b/src/core/symbology/qgsfillsymbollayer.cpp
@@ -3233,6 +3233,12 @@ void QgsPointPatternFillSymbolLayer::setOutputUnit( QgsUnitTypes::RenderUnit uni
mDisplacementYUnit = unit;
mOffsetXUnit = unit;
mOffsetYUnit = unit;
+ // don't change "percentage" units -- since they adapt directly to whatever other unit is set
+ if ( mRandomDeviationXUnit != QgsUnitTypes::RenderPercentage )
+ mRandomDeviationXUnit = unit;
+ if ( mRandomDeviationYUnit != QgsUnitTypes::RenderPercentage )
+ mRandomDeviationYUnit = unit;
+
if ( mMarkerSymbol )
{
mMarkerSymbol->setOutputUnit( unit );
@@ -3242,7 +3248,14 @@ void QgsPointPatternFillSymbolLayer::setOutputUnit( QgsUnitTypes::RenderUnit uni
QgsUnitTypes::RenderUnit QgsPointPatternFillSymbolLayer::outputUnit() const
{
QgsUnitTypes::RenderUnit unit = QgsImageFillSymbolLayer::outputUnit();
- if ( mDistanceXUnit != unit || mDistanceYUnit != unit || mDisplacementXUnit != unit || mDisplacementYUnit != unit || mOffsetXUnit != unit || mOffsetYUnit != unit )
+ if ( mDistanceXUnit != unit ||
+ mDistanceYUnit != unit ||
+ mDisplacementXUnit != unit ||
+ mDisplacementYUnit != unit ||
+ mOffsetXUnit != unit ||
+ mOffsetYUnit != unit ||
+ ( mRandomDeviationXUnit != QgsUnitTypes::RenderPercentage && mRandomDeviationXUnit != unit ) ||
+ ( mRandomDeviationYUnit != QgsUnitTypes::RenderPercentage && mRandomDeviationYUnit != unit ) )
{
return QgsUnitTypes::RenderUnknownUnit;
}
@@ -3256,7 +3269,9 @@ bool QgsPointPatternFillSymbolLayer::usesMapUnits() const
|| mDisplacementXUnit == QgsUnitTypes::RenderMapUnits || mDisplacementXUnit == QgsUnitTypes::RenderMetersInMapUnits
|| mDisplacementYUnit == QgsUnitTypes::RenderMapUnits || mDisplacementYUnit == QgsUnitTypes::RenderMetersInMapUnits
|| mOffsetXUnit == QgsUnitTypes::RenderMapUnits || mOffsetXUnit == QgsUnitTypes::RenderMetersInMapUnits
- || mOffsetYUnit == QgsUnitTypes::RenderMapUnits || mOffsetYUnit == QgsUnitTypes::RenderMetersInMapUnits;
+ || mOffsetYUnit == QgsUnitTypes::RenderMapUnits || mOffsetYUnit == QgsUnitTypes::RenderMetersInMapUnits
+ || mRandomDeviationXUnit == QgsUnitTypes::RenderMapUnits || mRandomDeviationXUnit == QgsUnitTypes::RenderMetersInMapUnits
+ || mRandomDeviationYUnit == QgsUnitTypes::RenderMapUnits || mRandomDeviationYUnit == QgsUnitTypes::RenderMetersInMapUnits;
}
void QgsPointPatternFillSymbolLayer::setMapUnitScale( const QgsMapUnitScale &scale )
@@ -3268,6 +3283,8 @@ void QgsPointPatternFillSymbolLayer::setMapUnitScale( const QgsMapUnitScale &sca
mDisplacementYMapUnitScale = scale;
mOffsetXMapUnitScale = scale;
mOffsetYMapUnitScale = scale;
+ mRandomDeviationXMapUnitScale = scale;
+ mRandomDeviationYMapUnitScale = scale;
}
QgsMapUnitScale QgsPointPatternFillSymbolLayer::mapUnitScale() const
@@ -3277,7 +3294,9 @@ QgsMapUnitScale QgsPointPatternFillSymbolLayer::mapUnitScale() const
mDistanceYMapUnitScale == mDisplacementXMapUnitScale &&
mDisplacementXMapUnitScale == mDisplacementYMapUnitScale &&
mDisplacementYMapUnitScale == mOffsetXMapUnitScale &&
- mOffsetXMapUnitScale == mOffsetYMapUnitScale )
+ mOffsetXMapUnitScale == mOffsetYMapUnitScale &&
+ mRandomDeviationXMapUnitScale == mOffsetYMapUnitScale &&
+ mRandomDeviationYMapUnitScale == mRandomDeviationXMapUnitScale )
{
return mDistanceXMapUnitScale;
}
@@ -3361,6 +3380,44 @@ QgsSymbolLayer *QgsPointPatternFillSymbolLayer::create( const QVariantMap &prope
layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
}
+ if ( properties.contains( QStringLiteral( "random_deviation_x" ) ) )
+ {
+ layer->setMaximumRandomDeviationX( properties[QStringLiteral( "random_deviation_x" )].toDouble() );
+ }
+ if ( properties.contains( QStringLiteral( "random_deviation_y" ) ) )
+ {
+ layer->setMaximumRandomDeviationY( properties[QStringLiteral( "random_deviation_y" )].toDouble() );
+ }
+ if ( properties.contains( QStringLiteral( "random_deviation_x_unit" ) ) )
+ {
+ layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_x_unit" )].toString() ) );
+ }
+ if ( properties.contains( QStringLiteral( "random_deviation_x_map_unit_scale" ) ) )
+ {
+ layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_x_map_unit_scale" )].toString() ) );
+ }
+ if ( properties.contains( QStringLiteral( "random_deviation_y_unit" ) ) )
+ {
+ layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_y_unit" )].toString() ) );
+ }
+ if ( properties.contains( QStringLiteral( "random_deviation_y_map_unit_scale" ) ) )
+ {
+ layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_y_map_unit_scale" )].toString() ) );
+ }
+ unsigned long seed = 0;
+ if ( properties.contains( QStringLiteral( "seed" ) ) )
+ seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
+ else
+ {
+ // if we a creating a new point pattern fill from scratch, we default to a random seed
+ // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
+ std::random_device rd;
+ std::mt19937 mt( seed == 0 ? rd() : seed );
+ std::uniform_int_distribution<> uniformDist( 1, 999999999 );
+ seed = uniformDist( mt );
+ }
+ layer->setSeed( seed );
+
if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
{
layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
@@ -3487,7 +3544,11 @@ void QgsPointPatternFillSymbolLayer::startRender( QgsSymbolRenderContext &contex
mRenderUsingMarkers = context.renderContext().forceVectorOutput()
|| mMarkerSymbol->hasDataDefinedProperties()
|| mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyMarkerClipping )
- || mClipMode != Qgis::MarkerClipMode::Shape;
+ || mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyRandomOffsetX )
+ || mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyRandomOffsetY )
+ || mClipMode != Qgis::MarkerClipMode::Shape
+ || !qgsDoubleNear( mRandomDeviationX, 0 )
+ || !qgsDoubleNear( mRandomDeviationY, 0 );
if ( mRenderUsingMarkers )
{
@@ -3660,6 +3721,36 @@ void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, con
top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
}
+ unsigned long seed = mSeed;
+ if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyRandomSeed ) )
+ {
+ context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
+ seed = mDataDefinedProperties.valueAsInt( QgsSymbolLayer::PropertyRandomSeed, context.renderContext().expressionContext(), seed );
+ }
+
+ double maxRandomDeviationX = mRandomDeviationX;
+ if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyRandomOffsetX ) )
+ {
+ context.setOriginalValueVariable( maxRandomDeviationX );
+ maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
+ }
+ const double maxRandomDeviationPixelX = mRandomDeviationXUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationX * width / 100 )
+ : context.renderContext().convertToPainterUnits( maxRandomDeviationX, mRandomDeviationXUnit, mRandomDeviationXMapUnitScale );
+
+ double maxRandomDeviationY = mRandomDeviationY;
+ if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyRandomOffsetY ) )
+ {
+ context.setOriginalValueVariable( maxRandomDeviationY );
+ maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
+ }
+ const double maxRandomDeviationPixelY = mRandomDeviationYUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationY * height / 100 )
+ : context.renderContext().convertToPainterUnits( maxRandomDeviationY, mRandomDeviationYUnit, mRandomDeviationYMapUnitScale );
+
+ std::random_device rd;
+ std::mt19937 mt( seed == 0 ? rd() : seed );
+ std::uniform_real_distribution<> uniformDist( 0, 1 );
+ const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
+
QgsExpressionContextScope *scope = new QgsExpressionContextScope();
QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
int pointNum = 0;
@@ -3685,6 +3776,12 @@ void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, con
if ( !alternateColumn )
y -= displacementPixelY;
+ if ( useRandomShift )
+ {
+ x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
+ y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
+ }
+
if ( needsExpressionContext )
{
scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
@@ -3768,6 +3865,13 @@ QVariantMap QgsPointPatternFillSymbolLayer::properties() const
map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
+ map.insert( QStringLiteral( "random_deviation_x" ), QString::number( mRandomDeviationX ) );
+ map.insert( QStringLiteral( "random_deviation_y" ), QString::number( mRandomDeviationY ) );
+ map.insert( QStringLiteral( "random_deviation_x_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
+ map.insert( QStringLiteral( "random_deviation_y_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
+ 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 ) );
return map;
}
diff --git a/src/core/symbology/qgsfillsymbollayer.h b/src/core/symbology/qgsfillsymbollayer.h
index daab59f79658..ff4864e19846 100644
--- a/src/core/symbology/qgsfillsymbollayer.h
+++ b/src/core/symbology/qgsfillsymbollayer.h
@@ -2001,6 +2001,141 @@ class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer
*/
void setClipMode( Qgis::MarkerClipMode mode ) { mClipMode = mode; }
+ /**
+ * Sets the maximum horizontal random \a deviation of points in the pattern.
+ *
+ * Units are set via setMaximumRandomDeviationXUnit().
+ *
+ * \see maximumRandomDeviationX()
+ * \see setMaximumRandomDeviationY()
+ * \since QGIS 3.24
+ */
+ void setMaximumRandomDeviationX( double deviation ) { mRandomDeviationX = deviation; }
+
+ /**
+ * Returns the maximum horizontal random deviation of points in the pattern.
+ *
+ * Units are retrieved via maximumRandomDeviationXUnit().
+ *
+ * \see setMaximumRandomDeviationX()
+ * \see maximumRandomDeviationY()
+ * \since QGIS 3.24
+ */
+ double maximumRandomDeviationX() const { return mRandomDeviationX; }
+
+ /**
+ * Sets the maximum vertical random \a deviation of points in the pattern.
+ *
+ * Units are set via setMaximumRandomDeviationYUnit().
+ *
+ * \see maximumRandomDeviationY()
+ * \see setMaximumRandomDeviationX()
+ * \since QGIS 3.24
+ */
+ void setMaximumRandomDeviationY( double deviation ) { mRandomDeviationY = deviation; }
+
+ /**
+ * Returns the maximum vertical random deviation of points in the pattern.
+ *
+ * Units are retrieved via maximumRandomDeviationYUnit().
+ *
+ * \see setMaximumRandomDeviationY()
+ * \see maximumRandomDeviationX()
+ * \since QGIS 3.24
+ */
+ double maximumRandomDeviationY() const { return mRandomDeviationY; }
+
+ /**
+ * Sets the \a unit for the horizontal random deviation of points in the pattern.
+ *
+ * \see randomDeviationXUnit()
+ * \see setRandomDeviationYUnit()
+ * \since QGIS 3.24
+ */
+ void setRandomDeviationXUnit( QgsUnitTypes::RenderUnit unit ) { mRandomDeviationXUnit = unit; }
+
+ /**
+ * Returns the units for the horizontal random deviation of points in the pattern.
+ * \see setRandomDeviationXUnit()
+ * \see randomDeviationYUnit()
+ *
+ * \since QGIS 3.24
+ */
+ QgsUnitTypes::RenderUnit randomDeviationXUnit() const { return mRandomDeviationXUnit; }
+
+ /**
+ * Sets the \a unit for the vertical random deviation of points in the pattern.
+ *
+ * \see randomDeviationYUnit()
+ * \see setRandomDeviationXUnit()
+ * \since QGIS 3.24
+ */
+ void setRandomDeviationYUnit( QgsUnitTypes::RenderUnit unit ) { mRandomDeviationYUnit = unit; }
+
+ /**
+ * Returns the units for the vertical random deviation of points in the pattern.
+ *
+ * \see setRandomDeviationYUnit()
+ * \see randomDeviationXUnit()
+ *
+ * \since QGIS 3.24
+ */
+ QgsUnitTypes::RenderUnit randomDeviationYUnit() const { return mRandomDeviationYUnit; }
+
+ /**
+ * Returns the unit scale for the horizontal random deviation of points in the pattern.
+ *
+ * \see setRandomDeviationXMapUnitScale()
+ * \see randomDeviationYMapUnitScale()
+ * \since QGIS 3.24
+ */
+ const QgsMapUnitScale &randomDeviationXMapUnitScale() const { return mRandomDeviationXMapUnitScale; }
+
+ /**
+ * Returns the unit scale for the vertical random deviation of points in the pattern.
+ *
+ * \see setRandomDeviationXMapUnitScale()
+ * \see randomDeviationXMapUnitScale()
+ * \since QGIS 3.24
+ */
+ const QgsMapUnitScale &randomDeviationYMapUnitScale() const { return mRandomDeviationYMapUnitScale; }
+
+ /**
+ * Sets the unit \a scale for the horizontal random deviation of points in the pattern.
+ *
+ * \see randomDeviationXMapUnitScale()
+ * \see setRandomDeviationYMapUnitScale()
+ * \since QGIS 3.24
+ */
+ void setRandomDeviationXMapUnitScale( const QgsMapUnitScale &scale ) { mRandomDeviationXMapUnitScale = scale; }
+
+ /**
+ * Sets the unit \a scale for the vertical random deviation of points in the pattern.
+ *
+ * \see randomDeviationYMapUnitScale()
+ * \see setRandomDeviationXMapUnitScale()
+ * \since QGIS 3.24
+ */
+ void setRandomDeviationYMapUnitScale( const QgsMapUnitScale &scale ) { mRandomDeviationYMapUnitScale = scale; }
+
+ /**
+ * Returns the random number seed to use when randomly shifting points, or 0 if
+ * a truly random sequence will be used (causing points to appear in different locations with every map refresh).
+ * \see setSeed()
+ * \since QGIS 3.24
+ */
+ unsigned long seed() const { return mSeed; }
+
+ /**
+ * Sets the random number \a seed to use when randomly shifting points, or 0 if
+ * a truly random sequence will be used on every rendering (causing points to appear
+ * in different locations with every map refresh).
+ *
+ * \see seed()
+ * \since QGIS 3.24
+ */
+ void setSeed( unsigned long seed ) { mSeed = seed; }
+
protected:
std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol;
double mDistanceX = 15;
@@ -2022,6 +2157,14 @@ class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer
QgsUnitTypes::RenderUnit mOffsetYUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mOffsetYMapUnitScale;
+ double mRandomDeviationX = 0;
+ QgsUnitTypes::RenderUnit mRandomDeviationXUnit = QgsUnitTypes::RenderMillimeters;
+ QgsMapUnitScale mRandomDeviationXMapUnitScale;
+ double mRandomDeviationY = 0;
+ QgsUnitTypes::RenderUnit mRandomDeviationYUnit = QgsUnitTypes::RenderMillimeters;
+ QgsMapUnitScale mRandomDeviationYMapUnitScale;
+ unsigned long mSeed = 0;
+
void applyDataDefinedSettings( QgsSymbolRenderContext &context ) override;
private:
diff --git a/src/core/symbology/qgssymbollayer.cpp b/src/core/symbology/qgssymbollayer.cpp
index 4fc2e86045f4..082913f3e558 100644
--- a/src/core/symbology/qgssymbollayer.cpp
+++ b/src/core/symbology/qgssymbollayer.cpp
@@ -117,6 +117,8 @@ void QgsSymbolLayer::initPropertyDefinitions()
{ QgsSymbolLayer::PropertyLineStartColorValue, QgsPropertyDefinition( "lineStartColorValue", QObject::tr( "Line start color value" ), QgsPropertyDefinition::Double, origin )},
{ QgsSymbolLayer::PropertyLineEndColorValue, QgsPropertyDefinition( "lineEndColorValue", QObject::tr( "Line end color value" ), QgsPropertyDefinition::Double, origin )},
{ QgsSymbolLayer::PropertyMarkerClipping, QgsPropertyDefinition( "markerClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Marker clipping mode" ), QObject::tr( "string " ) + QLatin1String( "[no|shape|centroid_within|completely_within]" ), origin )},
+ { QgsSymbolLayer::PropertyRandomOffsetX, QgsPropertyDefinition( "randomOffsetX", QObject::tr( "Horizontal random offset" ), QgsPropertyDefinition::Double, origin )},
+ { QgsSymbolLayer::PropertyRandomOffsetY, QgsPropertyDefinition( "randomOffsetY", QObject::tr( "Vertical random offset" ), QgsPropertyDefinition::Double, origin )},
};
}
diff --git a/src/core/symbology/qgssymbollayer.h b/src/core/symbology/qgssymbollayer.h
index 5d5adcc9df24..bb410349c43b 100644
--- a/src/core/symbology/qgssymbollayer.h
+++ b/src/core/symbology/qgssymbollayer.h
@@ -201,6 +201,8 @@ class CORE_EXPORT QgsSymbolLayer
PropertyLineStartColorValue, //!< Start line color for interpolated line renderer (since QGIS 3.22)
PropertyLineEndColorValue, //!< End line color for interpolated line renderer (since QGIS 3.22)
PropertyMarkerClipping, //!< Marker clipping mode (since QGIS 3.24)
+ PropertyRandomOffsetX, //!< Random offset X (since QGIS 3.24)
+ PropertyRandomOffsetY, //!< Random offset Y (since QGIS 3.24)
};
/**
diff --git a/src/gui/symbology/qgssymbollayerwidget.cpp b/src/gui/symbology/qgssymbollayerwidget.cpp
index f86115d2cc2b..db27bbf698c0 100644
--- a/src/gui/symbology/qgssymbollayerwidget.cpp
+++ b/src/gui/symbology/qgssymbollayerwidget.cpp
@@ -3236,8 +3236,59 @@ QgsPointPatternFillSymbolLayerWidget::QgsPointPatternFillSymbolLayerWidget( QgsV
emit changed();
}
} );
-}
+ mSeedSpinBox->setShowClearButton( true );
+ mSeedSpinBox->setClearValue( 0 );
+ mRandomXSpinBox->setClearValue( 0 );
+ mRandomYSpinBox->setClearValue( 0 );
+
+ mRandomXOffsetUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
+ << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches << QgsUnitTypes::RenderPercentage );
+ mRandomYOffsetUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
+ << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches << QgsUnitTypes::RenderPercentage );
+ connect( mRandomXSpinBox, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, [ = ]( double d )
+ {
+ if ( mLayer )
+ {
+ mLayer->setMaximumRandomDeviationX( d );
+ emit changed();
+ }
+ } );
+ connect( mRandomYSpinBox, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, [ = ]( double d )
+ {
+ if ( mLayer )
+ {
+ mLayer->setMaximumRandomDeviationY( d );
+ emit changed();
+ }
+ } );
+ connect( mRandomXOffsetUnitWidget, &QgsUnitSelectionWidget::changed, this, [ = ]
+ {
+ if ( mLayer )
+ {
+ mLayer->setRandomDeviationXUnit( mRandomXOffsetUnitWidget->unit() );
+ mLayer->setRandomDeviationXMapUnitScale( mRandomXOffsetUnitWidget->getMapUnitScale() );
+ emit changed();
+ }
+ } );
+ connect( mRandomYOffsetUnitWidget, &QgsUnitSelectionWidget::changed, this, [ = ]
+ {
+ if ( mLayer )
+ {
+ mLayer->setRandomDeviationYUnit( mRandomYOffsetUnitWidget->unit() );
+ mLayer->setRandomDeviationYMapUnitScale( mRandomYOffsetUnitWidget->getMapUnitScale() );
+ emit changed();
+ }
+ } );
+ connect( mSeedSpinBox, qOverload< int > ( &QSpinBox::valueChanged ), this, [ = ]( int v )
+ {
+ if ( mLayer )
+ {
+ mLayer->setSeed( v );
+ emit changed();
+ }
+ } );
+}
void QgsPointPatternFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
{
@@ -3282,6 +3333,14 @@ void QgsPointPatternFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer
whileBlocking( mClipModeComboBox )->setCurrentIndex( mClipModeComboBox->findData( static_cast< int >( mLayer->clipMode() ) ) );
whileBlocking( mCoordinateReferenceComboBox )->setCurrentIndex( mCoordinateReferenceComboBox->findData( static_cast< int >( mLayer->coordinateReference() ) ) );
+ whileBlocking( mRandomXSpinBox )->setValue( mLayer->maximumRandomDeviationX() );
+ whileBlocking( mRandomYSpinBox )->setValue( mLayer->maximumRandomDeviationY() );
+ whileBlocking( mRandomXOffsetUnitWidget )->setUnit( mLayer->randomDeviationXUnit() );
+ whileBlocking( mRandomXOffsetUnitWidget )->setMapUnitScale( mLayer->randomDeviationXMapUnitScale() );
+ whileBlocking( mRandomYOffsetUnitWidget )->setUnit( mLayer->randomDeviationYUnit() );
+ whileBlocking( mRandomYOffsetUnitWidget )->setMapUnitScale( mLayer->randomDeviationYMapUnitScale() );
+ whileBlocking( mSeedSpinBox )->setValue( mLayer->seed() );
+
registerDataDefinedButton( mHorizontalDistanceDDBtn, QgsSymbolLayer::PropertyDistanceX );
registerDataDefinedButton( mVerticalDistanceDDBtn, QgsSymbolLayer::PropertyDistanceY );
registerDataDefinedButton( mHorizontalDisplacementDDBtn, QgsSymbolLayer::PropertyDisplacementX );
@@ -3290,6 +3349,9 @@ void QgsPointPatternFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer
registerDataDefinedButton( mVerticalOffsetDDBtn, QgsSymbolLayer::PropertyOffsetY );
registerDataDefinedButton( mClippingDDBtn, QgsSymbolLayer::PropertyMarkerClipping );
registerDataDefinedButton( mCoordinateReferenceDDBtn, QgsSymbolLayer::PropertyCoordinateMode );
+ registerDataDefinedButton( mRandomXDDBtn, QgsSymbolLayer::PropertyRandomOffsetX );
+ registerDataDefinedButton( mRandomYDDBtn, QgsSymbolLayer::PropertyRandomOffsetY );
+ registerDataDefinedButton( mSeedDdbtn, QgsSymbolLayer::PropertyRandomSeed );
}
QgsSymbolLayer *QgsPointPatternFillSymbolLayerWidget::symbolLayer()
diff --git a/src/ui/symbollayer/widget_pointpatternfill.ui b/src/ui/symbollayer/widget_pointpatternfill.ui
index 3aa7ba2fb9ec..fc91855e1b6e 100644
--- a/src/ui/symbollayer/widget_pointpatternfill.ui
+++ b/src/ui/symbollayer/widget_pointpatternfill.ui
@@ -7,7 +7,7 @@