Skip to content

Commit

Permalink
[feature] Add option to trim the start and end of simple line symbol
Browse files Browse the repository at this point in the history
layers by a preset amount

This allows for the line rendering to trim off the first x mm
and last y mm from the actual line string when drawing the line.
It can be used eg when creating complex symbols where a line layer
should not overlap marker symbol layers placed at the start
and end of the line.

The start/end trim distance supports a range of units, including
percentage of the overall line length, and can be data defined
for extra control.
  • Loading branch information
nyalldawson committed Mar 18, 2021
1 parent 99c5ac3 commit a16ab72
Show file tree
Hide file tree
Showing 11 changed files with 858 additions and 169 deletions.
194 changes: 193 additions & 1 deletion python/core/auto_generated/symbology/qgslinesymbollayer.sip.in
Expand Up @@ -271,7 +271,7 @@ Returns the units for the dash pattern offset.

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

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

Expand All @@ -293,6 +293,198 @@ Sets the map unit ``scale`` for the dash pattern offset.
.. seealso:: :py:func:`setDashPatternOffsetUnit`

.. versionadded:: 3.16
%End

double trimDistanceStart() const;
%Docstring
Returns the trim distance for the start of the line, which dictates a length
from the start of the line at which the actual rendering should start.

Trim units can be retrieved by calling :py:func:`~QgsSimpleLineSymbolLayer.trimDistanceStartUnit`.

.. seealso:: :py:func:`setTrimDistanceStart`

.. seealso:: :py:func:`trimDistanceEnd`

.. seealso:: :py:func:`trimDistanceStartUnit`

.. seealso:: :py:func:`trimDistanceStartMapUnitScale`

.. versionadded:: 3.20
%End

void setTrimDistanceStart( double distance );
%Docstring
Sets the trim ``distance`` for the start of the line, which dictates a length
from the start of the line at which the actual rendering should start.

Trim units can be set by calling :py:func:`~QgsSimpleLineSymbolLayer.setTrimDistanceStartUnit`.

.. seealso:: :py:func:`trimDistanceStart`

.. seealso:: :py:func:`setTrimDistanceEnd`

.. seealso:: :py:func:`setTrimDistanceStartUnit`

.. seealso:: :py:func:`setTrimDistanceStartMapUnitScale`

.. versionadded:: 3.20
%End

void setTrimDistanceStartUnit( QgsUnitTypes::RenderUnit unit );
%Docstring
Sets the ``unit`` for the trim distance for the start of the line.

.. seealso:: :py:func:`trimDistanceStartUnit`

.. seealso:: :py:func:`setTrimDistanceEndUnit`

.. seealso:: :py:func:`setTrimDistanceStart`

.. seealso:: :py:func:`setTrimDistanceStartMapUnitScale`

.. versionadded:: 3.20
%End

QgsUnitTypes::RenderUnit trimDistanceStartUnit() const;
%Docstring
Returns the unit for the trim distance for the start of the line.

.. seealso:: :py:func:`setTrimDistanceStartUnit`

.. seealso:: :py:func:`trimDistanceEndUnit`

.. seealso:: :py:func:`trimDistanceStart`

.. seealso:: :py:func:`trimDistanceStartMapUnitScale`

.. versionadded:: 3.20
%End

const QgsMapUnitScale &trimDistanceStartMapUnitScale() const;
%Docstring
Returns the map unit scale for the trim distance for the start of the line.

.. seealso:: :py:func:`setTrimDistanceStartMapUnitScale`

.. seealso:: :py:func:`trimDistanceEndMapUnitUnit`

.. seealso:: :py:func:`trimDistanceStart`

.. seealso:: :py:func:`trimDistanceStartUnit`

.. versionadded:: 3.20
%End

void setTrimDistanceStartMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` for the trim distance for the start of the line.

.. seealso:: :py:func:`trimDistanceStartMapUnitScale`

.. seealso:: :py:func:`setTrimDistanceEndMapUnitUnit`

.. seealso:: :py:func:`setTrimDistanceStart`

.. seealso:: :py:func:`setTrimDistanceStartUnit`

.. versionadded:: 3.20
%End

double trimDistanceEnd() const;
%Docstring
Returns the trim distance for the end of the line, which dictates a length
from the end of the line at which the actual rendering should end.

Trim units can be retrieved by calling :py:func:`~QgsSimpleLineSymbolLayer.trimDistanceEndUnit`.

.. seealso:: :py:func:`setTrimDistanceEnd`

.. seealso:: :py:func:`trimDistanceStart`

.. seealso:: :py:func:`trimDistanceEndUnit`

.. seealso:: :py:func:`trimDistanceEndMapUnitScale`

.. versionadded:: 3.20
%End

void setTrimDistanceEnd( double distance );
%Docstring
Sets the trim ``distance`` for the end of the line, which dictates a length
from the end of the line at which the actual rendering should end.

Trim units can be set by calling :py:func:`~QgsSimpleLineSymbolLayer.setTrimDistanceEndUnit`.

.. seealso:: :py:func:`trimDistanceEnd`

.. seealso:: :py:func:`setTrimDistanceStart`

.. seealso:: :py:func:`setTrimDistanceEndUnit`

.. seealso:: :py:func:`setTrimDistanceEndMapUnitScale`

.. versionadded:: 3.20
%End

void setTrimDistanceEndUnit( QgsUnitTypes::RenderUnit unit );
%Docstring
Sets the ``unit`` for the trim distance for the end of the line.

.. seealso:: :py:func:`trimDistanceEndUnit`

.. seealso:: :py:func:`setTrimDistanceStartUnit`

.. seealso:: :py:func:`setTrimDistanceEnd`

.. seealso:: :py:func:`setTrimDistanceEndMapUnitScale`

.. versionadded:: 3.20
%End

QgsUnitTypes::RenderUnit trimDistanceEndUnit() const;
%Docstring
Returns the unit for the trim distance for the end of the line.

.. seealso:: :py:func:`setTrimDistanceEndUnit`

.. seealso:: :py:func:`trimDistanceStartUnit`

.. seealso:: :py:func:`trimDistanceEnd`

.. seealso:: :py:func:`trimDistanceEndMapUnitScale`

.. versionadded:: 3.20
%End

const QgsMapUnitScale &trimDistanceEndMapUnitScale() const;
%Docstring
Returns the map unit scale for the trim distance for the end of the line.

.. seealso:: :py:func:`setTrimDistanceEndMapUnitScale`

.. seealso:: :py:func:`trimDistanceStartMapUnitUnit`

.. seealso:: :py:func:`trimDistanceEnd`

.. seealso:: :py:func:`trimDistanceEndUnit`

.. versionadded:: 3.20
%End

void setTrimDistanceEndMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` for the trim distance for the end of the line.

.. seealso:: :py:func:`trimDistanceEndMapUnitScale`

.. seealso:: :py:func:`setTrimDistanceStartMapUnitUnit`

.. seealso:: :py:func:`setTrimDistanceEnd`

.. seealso:: :py:func:`setTrimDistanceEndUnit`

.. versionadded:: 3.20
%End

bool drawInsidePolygon() const;
Expand Down
2 changes: 2 additions & 0 deletions python/core/auto_generated/symbology/qgssymbollayer.sip.in
Expand Up @@ -146,6 +146,8 @@ class QgsSymbolLayer
PropertyFontFamily,
PropertyFontStyle,
PropertyDashPatternOffset,
PropertyTrimStart,
PropertyTrimEnd,
};

static const QgsPropertiesDefinition &propertyDefinitions();
Expand Down
72 changes: 68 additions & 4 deletions src/core/symbology/qgslinesymbollayer.cpp
Expand Up @@ -191,6 +191,19 @@ QgsSymbolLayer *QgsSimpleLineSymbolLayer::create( const QVariantMap &props )
if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );

if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );

if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );

Expand All @@ -202,7 +215,6 @@ QgsSymbolLayer *QgsSimpleLineSymbolLayer::create( const QVariantMap &props )
return l;
}


QString QgsSimpleLineSymbolLayer::layerType() const
{
return QStringLiteral( "SimpleLine" );
Expand Down Expand Up @@ -329,14 +341,54 @@ void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, con

}

void QgsSimpleLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
void QgsSimpleLineSymbolLayer::renderPolyline( const QPolygonF &pts, QgsSymbolRenderContext &context )
{
QPainter *p = context.renderContext().painter();
if ( !p )
{
return;
}

QPolygonF points = pts;

double startTrim = mTrimDistanceStart;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyTrimStart ) )
{
context.setOriginalValueVariable( startTrim );
startTrim = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyTrimStart, context.renderContext().expressionContext(), mTrimDistanceStart );
}
double endTrim = mTrimDistanceEnd;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyTrimEnd ) )
{
context.setOriginalValueVariable( endTrim );
endTrim = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyTrimEnd, context.renderContext().expressionContext(), mTrimDistanceEnd );
}

double totalLength = -1;
if ( mTrimDistanceStartUnit == QgsUnitTypes::RenderPercentage )
{
totalLength = QgsSymbolLayerUtils::polylineLength( points );
startTrim = startTrim * 0.01 * totalLength;
}
else
{
startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
}
if ( mTrimDistanceEndUnit == QgsUnitTypes::RenderPercentage )
{
if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
totalLength = QgsSymbolLayerUtils::polylineLength( points );
endTrim = endTrim * 0.01 * totalLength;
}
else
{
endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
}
if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
{
points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
}

QColor penColor = mColor;
penColor.setAlphaF( mColor.alphaF() * context.opacity() );
mPen.setColor( penColor );
Expand Down Expand Up @@ -424,6 +476,12 @@ QVariantMap QgsSimpleLineSymbolLayer::properties() const
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( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
Expand All @@ -450,6 +508,12 @@ QgsSimpleLineSymbolLayer *QgsSimpleLineSymbolLayer::clone() const
l->setDashPatternOffset( mDashPatternOffset );
l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
l->setTrimDistanceStart( mTrimDistanceStart );
l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
l->setTrimDistanceEnd( mTrimDistanceEnd );
l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
l->setAlignDashPattern( mAlignDashPattern );
l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );

Expand Down Expand Up @@ -612,7 +676,7 @@ void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext
//re-scale pattern vector after data defined pen width was applied

QVector<qreal> scaledVector;
for ( double v : mCustomDashVector )
for ( double v : qgis::as_const( mCustomDashVector ) )
{
//the dash is specified in terms of pen widths, therefore the division
scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
Expand All @@ -624,7 +688,7 @@ void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext
double patternOffset = mDashPatternOffset;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
{
context.setOriginalValueVariable( mDashPatternOffset );
context.setOriginalValueVariable( patternOffset );
patternOffset = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyDashPatternOffset, context.renderContext().expressionContext(), mDashPatternOffset );
pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
}
Expand Down

0 comments on commit a16ab72

Please sign in to comment.