Skip to content
Permalink
Browse files

[feature][symbology] Expose option to offset simple line dash patterns

by a preset amount

Allows for tweaking the positioning of dashes/spaces in the line, so
that the dashes/spaces can be placed at nicer positions to account
for corners in the line (also can be used potentially to "align"
adjacent dash pattern borders)

Offset can be set in different units, including map units, and can
be data defined
  • Loading branch information
nyalldawson committed Jul 4, 2020
1 parent 1e3558a commit 329a0fc1100c4bc2e786c3eaa8d0138e5f9775a8
@@ -205,6 +205,90 @@ This setting is only used when :py:func:`~QgsSimpleLineSymbolLayer.useCustomDash
.. seealso:: :py:func:`setCustomDashPatternUnit`

.. seealso:: :py:func:`setUseCustomDashPattern`
%End

double dashPatternOffset() const;
%Docstring
Returns the dash pattern offset, which dictates how far along the dash pattern
the pattern should start rendering at.

Offset units can be retrieved by calling :py:func:`~QgsSimpleLineSymbolLayer.dashPatternOffsetUnit`.

.. seealso:: :py:func:`setDashPatternOffset`

.. seealso:: :py:func:`dashPatternOffsetUnit`

.. seealso:: :py:func:`dashPatternOffsetMapUnitScale`

.. versionadded:: 3.16
%End

void setDashPatternOffset( double offset );
%Docstring
Sets the dash pattern ``offset``, which dictates how far along the dash pattern
the pattern should start rendering at.

Offset units are set via :py:func:`~QgsSimpleLineSymbolLayer.setDashPatternOffsetUnit`.

.. seealso:: :py:func:`dashPatternOffset`

.. seealso:: :py:func:`setDashPatternOffsetUnit`

.. seealso:: :py:func:`setDashPatternOffsetMapUnitScale`

.. versionadded:: 3.16
%End

void setDashPatternOffsetUnit( QgsUnitTypes::RenderUnit unit );
%Docstring
Sets the ``unit`` for the dash pattern offset.

.. seealso:: :py:func:`dashPatternOffsetUnit`

.. seealso:: :py:func:`setDashPatternOffset`

.. seealso:: :py:func:`setDashPatternOffsetMapUnitScale`

.. versionadded:: 3.16
%End

QgsUnitTypes::RenderUnit dashPatternOffsetUnit() const;
%Docstring
Returns the units for the dash pattern offset.

.. seealso:: :py:func:`setDashPatternOffsetUnit`

.. seealso:: :py:func:`dashPatternOffset`

.. seealso:: :py:func:`dashPatternOffsetMapUnitScale`

.. versionadded:: 3.16
%End

const QgsMapUnitScale &dashPatternOffsetMapUnitScale() const;
%Docstring
Returns the map unit scale the dash pattern offset value.

.. seealso:: :py:func:`setDashPatternOffsetMapUnitScale`

.. seealso:: :py:func:`dashPatternOffsetUnit`

.. seealso:: :py:func:`dashPatternOffset`

.. versionadded:: 3.16
%End

void setDashPatternOffsetMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` for the dash pattern offset.

.. seealso:: :py:func:`dashPatternOffsetMapUnitScale`

.. seealso:: :py:func:`setDashPatternOffset`

.. seealso:: :py:func:`setDashPatternOffsetUnit`

.. versionadded:: 3.16
%End

bool drawInsidePolygon() const;
@@ -145,6 +145,7 @@ class QgsSymbolLayer
PropertyDensityArea,
PropertyFontFamily,
PropertyFontStyle,
PropertyDashPatternOffset,
};

static const QgsPropertiesDefinition &propertyDefinitions();
@@ -177,6 +177,13 @@ QgsSymbolLayer *QgsSimpleLineSymbolLayer::create( const QgsStringMap &props )
l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
}

if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )] ) );
if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] ) );

l->restoreOldDataDefinedProperties( props );

return l;
@@ -195,14 +202,15 @@ void QgsSimpleLineSymbolLayer::startRender( QgsSymbolRenderContext &context )
mPen.setColor( penColor );
double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
mPen.setWidthF( scaledWidth );

//note that Qt seems to have issues with scaling dash patterns with very small pen widths.
//treating the pen as having no less than a 1 pixel size avoids the worst of these issues
const double dashWidthDiv = std::max( 1.0, scaledWidth );
if ( mUseCustomDashPattern )
{
mPen.setStyle( Qt::CustomDashLine );

//scale pattern vector
//note that Qt seems to have issues with scaling dash patterns with very small pen widths.
//treating the pen as having no less than a 1 pixel size avoids the worst of these issues
const double dashWidthDiv = std::max( 1.0, scaledWidth );

QVector<qreal> scaledVector;
QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
@@ -217,6 +225,12 @@ void QgsSimpleLineSymbolLayer::startRender( QgsSymbolRenderContext &context )
{
mPen.setStyle( mPenStyle );
}

if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
{
mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
}

mPen.setJoinStyle( mPenJoinStyle );
mPen.setCapStyle( mPenCapStyle );

@@ -385,6 +399,9 @@ QgsStringMap QgsSimpleLineSymbolLayer::properties() const
map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
return map;
@@ -406,6 +423,9 @@ QgsSimpleLineSymbolLayer *QgsSimpleLineSymbolLayer::clone() const
l->setCustomDashVector( mCustomDashVector );
l->setDrawInsidePolygon( mDrawInsidePolygon );
l->setRingFilter( mRingFilter );
l->setDashPatternOffset( mDashPatternOffset );
l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
copyDataDefinedProperties( l );
copyPaintEffect( l );
return l;
@@ -537,12 +557,13 @@ void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext
}

//dash dot vector

//note that Qt seems to have issues with scaling dash patterns with very small pen widths.
//treating the pen as having no less than a 1 pixel size avoids the worst of these issues
const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );

if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyCustomDash ) )
{
//note that Qt seems to have issues with scaling dash patterns with very small pen widths.
//treating the pen as having no less than a 1 pixel size avoids the worst of these issues
const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );

QVector<qreal> dashVector;
QVariant exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyCustomDash, context.renderContext().expressionContext() );
if ( exprVal.isValid() )
@@ -557,6 +578,15 @@ void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext
}
}

// dash pattern offset
double patternOffset = mDashPatternOffset;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
{
context.setOriginalValueVariable( mDashPatternOffset );
patternOffset = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyDashPatternOffset, context.renderContext().expressionContext(), mDashPatternOffset );
pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
}

//line style
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeStyle ) )
{
@@ -194,6 +194,78 @@ class CORE_EXPORT QgsSimpleLineSymbolLayer : public QgsLineSymbolLayer
*/
void setCustomDashVector( const QVector<qreal> &vector ) { mCustomDashVector = vector; }

/**
* Returns the dash pattern offset, which dictates how far along the dash pattern
* the pattern should start rendering at.
*
* Offset units can be retrieved by calling dashPatternOffsetUnit().
*
* \see setDashPatternOffset()
* \see dashPatternOffsetUnit()
* \see dashPatternOffsetMapUnitScale()
*
* \since QGIS 3.16
*/
double dashPatternOffset() const { return mDashPatternOffset; }

/**
* Sets the dash pattern \a offset, which dictates how far along the dash pattern
* the pattern should start rendering at.
*
* Offset units are set via setDashPatternOffsetUnit().
*
* \see dashPatternOffset()
* \see setDashPatternOffsetUnit()
* \see setDashPatternOffsetMapUnitScale()
*
* \since QGIS 3.16
*/
void setDashPatternOffset( double offset ) { mDashPatternOffset = offset; }

/**
* Sets the \a unit for the dash pattern offset.
*
* \see dashPatternOffsetUnit()
* \see setDashPatternOffset()
* \see setDashPatternOffsetMapUnitScale()
*
* \since QGIS 3.16
*/
void setDashPatternOffsetUnit( QgsUnitTypes::RenderUnit unit ) { mDashPatternOffsetUnit = unit; }

/**
* Returns the units for the dash pattern offset.
*
* \see setDashPatternOffsetUnit()
* \see dashPatternOffset()
* \see dashPatternOffsetMapUnitScale()
*
* \since QGIS 3.16
*/
QgsUnitTypes::RenderUnit dashPatternOffsetUnit() const { return mDashPatternOffsetUnit; }

/**
* Returns the map unit scale the dash pattern offset value.
*
* \see setDashPatternOffsetMapUnitScale()
* \see dashPatternOffsetUnit()
* \see dashPatternOffset()
*
* \since QGIS 3.16
*/
const QgsMapUnitScale &dashPatternOffsetMapUnitScale() const { return mDashPatternOffsetMapUnitScale; }

/**
* Sets the map unit \a scale for the dash pattern offset.
*
* \see dashPatternOffsetMapUnitScale()
* \see setDashPatternOffset()
* \see setDashPatternOffsetUnit()
*
* \since QGIS 3.16
*/
void setDashPatternOffsetMapUnitScale( const QgsMapUnitScale &scale ) { mDashPatternOffsetMapUnitScale = scale; }

/**
* Returns TRUE if the line should only be drawn inside polygons, and any portion
* of the line which falls outside the polygon should be clipped away.
@@ -228,6 +300,10 @@ class CORE_EXPORT QgsSimpleLineSymbolLayer : public QgsLineSymbolLayer
QgsUnitTypes::RenderUnit mCustomDashPatternUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mCustomDashPatternMapUnitScale;

double mDashPatternOffset = 0;
QgsUnitTypes::RenderUnit mDashPatternOffsetUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mDashPatternOffsetMapUnitScale;

//! Vector with an even number of entries for the
QVector<qreal> mCustomDashVector;

@@ -106,6 +106,7 @@ void QgsSymbolLayer::initPropertyDefinitions()
{ QgsSymbolLayer::PropertyRandomSeed, QgsPropertyDefinition( "randomSeed", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Random number seed" ), QObject::tr( "integer > 0, or 0 for completely random sequence" ), origin )},
{ QgsSymbolLayer::PropertyClipPoints, QgsPropertyDefinition( "clipPoints", QObject::tr( "Clip markers" ), QgsPropertyDefinition::Boolean, origin )},
{ QgsSymbolLayer::PropertyClipPoints, QgsPropertyDefinition( "densityArea", QObject::tr( "Density area" ), QgsPropertyDefinition::DoublePositive, origin )},
{ QgsSymbolLayer::PropertyDashPatternOffset, QgsPropertyDefinition( "dashPatternOffset", QObject::tr( "Dash pattern offset" ), QgsPropertyDefinition::DoublePositive, origin )},
};
}

@@ -185,9 +185,10 @@ class CORE_EXPORT QgsSymbolLayer
PropertyPointCount, //!< Point count
PropertyRandomSeed, //!< Random number seed
PropertyClipPoints, //!< Whether markers should be clipped to polygon boundaries
PropertyDensityArea, //<! Density area
PropertyDensityArea, //!< Density area
PropertyFontFamily, //!< Font family
PropertyFontStyle, //!< Font style
PropertyDashPatternOffset, //!< Dash pattern offset
};

/**

0 comments on commit 329a0fc

Please sign in to comment.
You can’t perform that action at this time.