Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE] Add option to simple line and marker line to only
render exterior ring or interior rings

This option is shown whenever a simple line symbol or
marker line symbol is used as part of a fill symbol for
rendering polygons.

The default behavior is to render both interior and exterior
rings, but this new setting allows users to set the symbol
layer to render only for the exterior ring OR only
for interior rings.

This allows for symbolisation which wasn't directly possible
before, such as a marker line with markers for interior
rings angled toward the interior of the polygon.

Sponsored by the German QGIS User Group

Fixes #12652

(cherry picked from commit 4c9537e)
  • Loading branch information
nyalldawson committed Dec 2, 2018
1 parent a5db9af commit a5b969f
Show file tree
Hide file tree
Showing 16 changed files with 818 additions and 268 deletions.
35 changes: 35 additions & 0 deletions python/core/auto_generated/symbology/qgssymbollayer.sip.in
Expand Up @@ -811,6 +811,14 @@ class QgsLineSymbolLayer : QgsSymbolLayer
#include "qgssymbollayer.h"
%End
public:

enum RenderRingFilter
{
AllRings,
ExteriorRingOnly,
InteriorRingsOnly,
};

virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) = 0;

virtual void renderPolygonStroke( const QPolygonF &points, QList<QPolygonF> *rings, QgsSymbolRenderContext &context );
Expand Down Expand Up @@ -875,9 +883,36 @@ Returns the units for the line's offset.
virtual double dxfWidth( const QgsDxfExport &e, QgsSymbolRenderContext &context ) const;


RenderRingFilter ringFilter() const;
%Docstring
Returns the line symbol layer's ring filter, which controls which rings are
rendered when the line symbol is being used to draw a polygon's rings.

This setting has no effect when the line symbol is not being rendered
for a polygon.

.. seealso:: :py:func:`setRingFilter`

.. versionadded:: 3.6
%End

void setRingFilter( QgsLineSymbolLayer::RenderRingFilter filter );
%Docstring
Sets the line symbol layer's ring ``filter``, which controls which rings are
rendered when the line symbol is being used to draw a polygon's rings.

This setting has no effect when the line symbol is not being rendered
for a polygon.

.. seealso:: :py:func:`ringFilter`

.. versionadded:: 3.6
%End

protected:
QgsLineSymbolLayer( bool locked = false );


};

class QgsFillSymbolLayer : QgsSymbolLayer
Expand Down
5 changes: 5 additions & 0 deletions src/core/symbology/qgsarrowsymbollayer.cpp
Expand Up @@ -97,6 +97,9 @@ QgsSymbolLayer *QgsArrowSymbolLayer::create( const QgsStringMap &props )
if ( props.contains( QStringLiteral( "offset_unit_scale" ) ) )
l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_unit_scale" )] ) );

if ( props.contains( QStringLiteral( "ring_filter" ) ) )
l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );

l->restoreOldDataDefinedProperties( props );

l->setSubSymbol( QgsFillSymbol::createSimple( props ) );
Expand Down Expand Up @@ -148,6 +151,8 @@ QgsStringMap QgsArrowSymbolLayer::properties() const
map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( offsetUnit() );
map[QStringLiteral( "offset_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetMapUnitScale() );

map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );

return map;
}

Expand Down
107 changes: 83 additions & 24 deletions src/core/symbology/qgslinesymbollayer.cpp
Expand Up @@ -171,6 +171,11 @@ QgsSymbolLayer *QgsSimpleLineSymbolLayer::create( const QgsStringMap &props )
l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
}

if ( props.contains( QStringLiteral( "ring_filter" ) ) )
{
l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
}

l->restoreOldDataDefinedProperties( props );

return l;
Expand Down Expand Up @@ -240,34 +245,58 @@ void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QLi
}

if ( mDrawInsidePolygon )
{
//only drawing the line on the interior of the polygon, so set clip path for painter
p->save();
QPainterPath clipPath;
clipPath.addPolygon( points );

if ( rings )
switch ( mRingFilter )
{
case AllRings:
case ExteriorRingOnly:
{
//add polygon rings
QList<QPolygonF>::const_iterator it = rings->constBegin();
for ( ; it != rings->constEnd(); ++it )
if ( mDrawInsidePolygon )
{
QPolygonF ring = *it;
clipPath.addPolygon( ring );
//only drawing the line on the interior of the polygon, so set clip path for painter
QPainterPath clipPath;
clipPath.addPolygon( points );

if ( rings )
{
//add polygon rings
QList<QPolygonF>::const_iterator it = rings->constBegin();
for ( ; it != rings->constEnd(); ++it )
{
QPolygonF ring = *it;
clipPath.addPolygon( ring );
}
}

//use intersect mode, as a clip path may already exist (e.g., for composer maps)
p->setClipPath( clipPath, Qt::IntersectClip );
}

renderPolyline( points, context );
}
break;

//use intersect mode, as a clip path may already exist (e.g., for composer maps)
p->setClipPath( clipPath, Qt::IntersectClip );
case InteriorRingsOnly:
break;
}

renderPolyline( points, context );
if ( rings )
{
mOffset = -mOffset; // invert the offset for rings!
Q_FOREACH ( const QPolygonF &ring, *rings )
renderPolyline( ring, context );
mOffset = -mOffset;
switch ( mRingFilter )
{
case AllRings:
case InteriorRingsOnly:
{
mOffset = -mOffset; // invert the offset for rings!
for ( const QPolygonF &ring : qgis::as_const( *rings ) )
renderPolyline( ring, context );
mOffset = -mOffset;
}
break;
case ExteriorRingOnly:
break;
}
}

if ( mDrawInsidePolygon )
Expand Down Expand Up @@ -355,6 +384,7 @@ QgsStringMap QgsSimpleLineSymbolLayer::properties() const
map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
return map;
}

Expand All @@ -373,6 +403,7 @@ QgsSimpleLineSymbolLayer *QgsSimpleLineSymbolLayer::clone() const
l->setUseCustomDashPattern( mUseCustomDashPattern );
l->setCustomDashVector( mCustomDashVector );
l->setDrawInsidePolygon( mDrawInsidePolygon );
l->setRingFilter( mRingFilter );
copyDataDefinedProperties( l );
copyPaintEffect( l );
return l;
Expand Down Expand Up @@ -781,6 +812,11 @@ QgsSymbolLayer *QgsMarkerLineSymbolLayer::create( const QgsStringMap &props )
x->setPlacement( Interval );
}

if ( props.contains( QStringLiteral( "ring_filter" ) ) )
{
x->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
}

x->restoreOldDataDefinedProperties( props );

return x;
Expand Down Expand Up @@ -910,19 +946,39 @@ void QgsMarkerLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QLi
{
context.renderContext().setGeometry( curvePolygon->exteriorRing() );
}
renderPolyline( points, context );

switch ( mRingFilter )
{
case AllRings:
case ExteriorRingOnly:
renderPolyline( points, context );
break;
case InteriorRingsOnly:
break;
}

if ( rings )
{
mOffset = -mOffset; // invert the offset for rings!
for ( int i = 0; i < rings->size(); ++i )
switch ( mRingFilter )
{
if ( curvePolygon )
case AllRings:
case InteriorRingsOnly:
{
context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
mOffset = -mOffset; // invert the offset for rings!
for ( int i = 0; i < rings->size(); ++i )
{
if ( curvePolygon )
{
context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
}
renderPolyline( rings->at( i ), context );
}
mOffset = -mOffset;
}
renderPolyline( rings->at( i ), context );
break;
case ExteriorRingOnly:
break;
}
mOffset = -mOffset;
}
}

Expand Down Expand Up @@ -1346,6 +1402,8 @@ QgsStringMap QgsMarkerLineSymbolLayer::properties() const
map[QStringLiteral( "placement" )] = QStringLiteral( "curvepoint" );
else
map[QStringLiteral( "placement" )] = QStringLiteral( "interval" );

map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
return map;
}

Expand Down Expand Up @@ -1380,6 +1438,7 @@ QgsMarkerLineSymbolLayer *QgsMarkerLineSymbolLayer::clone() const
x->setOffsetAlongLine( mOffsetAlongLine );
x->setOffsetAlongLineMapUnitScale( mOffsetAlongLineMapUnitScale );
x->setOffsetAlongLineUnit( mOffsetAlongLineUnit );
x->setRingFilter( mRingFilter );
copyDataDefinedProperties( x );
copyPaintEffect( x );
return x;
Expand Down
35 changes: 32 additions & 3 deletions src/core/symbology/qgssymbollayer.cpp
Expand Up @@ -391,6 +391,16 @@ QgsLineSymbolLayer::QgsLineSymbolLayer( bool locked )
{
}

QgsLineSymbolLayer::RenderRingFilter QgsLineSymbolLayer::ringFilter() const
{
return mRingFilter;
}

void QgsLineSymbolLayer::setRingFilter( const RenderRingFilter filter )
{
mRingFilter = filter;
}

QgsFillSymbolLayer::QgsFillSymbolLayer( bool locked )
: QgsSymbolLayer( QgsSymbol::Fill, locked )
{
Expand Down Expand Up @@ -609,11 +619,30 @@ void QgsLineSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &context, QSize

void QgsLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QList<QPolygonF> *rings, QgsSymbolRenderContext &context )
{
renderPolyline( points, context );
switch ( mRingFilter )
{
case AllRings:
case ExteriorRingOnly:
renderPolyline( points, context );
break;
case InteriorRingsOnly:
break;
}

if ( rings )
{
Q_FOREACH ( const QPolygonF &ring, *rings )
renderPolyline( ring, context );
switch ( mRingFilter )
{
case AllRings:
case InteriorRingsOnly:
{
for ( const QPolygonF &ring : qgis::as_const( *rings ) )
renderPolyline( ring, context );
}
break;
case ExteriorRingOnly:
break;
}
}
}

Expand Down
35 changes: 35 additions & 0 deletions src/core/symbology/qgssymbollayer.h
Expand Up @@ -764,6 +764,15 @@ class CORE_EXPORT QgsMarkerSymbolLayer : public QgsSymbolLayer
class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer
{
public:

//! Options for filtering rings when the line symbol layer is being used to render a polygon's rings.
enum RenderRingFilter
{
AllRings, //!< Render both exterior and interior rings
ExteriorRingOnly, //!< Render the exterior ring only
InteriorRingsOnly, //!< Render the interior rings only
};

virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) = 0;

virtual void renderPolygonStroke( const QPolygonF &points, QList<QPolygonF> *rings, QgsSymbolRenderContext &context );
Expand Down Expand Up @@ -816,6 +825,30 @@ class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer

double dxfWidth( const QgsDxfExport &e, QgsSymbolRenderContext &context ) const override;

/**
* Returns the line symbol layer's ring filter, which controls which rings are
* rendered when the line symbol is being used to draw a polygon's rings.
*
* This setting has no effect when the line symbol is not being rendered
* for a polygon.
*
* \see setRingFilter()
* \since QGIS 3.6
*/
RenderRingFilter ringFilter() const;

/**
* Sets the line symbol layer's ring \a filter, which controls which rings are
* rendered when the line symbol is being used to draw a polygon's rings.
*
* This setting has no effect when the line symbol is not being rendered
* for a polygon.
*
* \see ringFilter()
* \since QGIS 3.6
*/
void setRingFilter( QgsLineSymbolLayer::RenderRingFilter filter );

protected:
QgsLineSymbolLayer( bool locked = false );

Expand All @@ -825,6 +858,8 @@ class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer
double mOffset = 0;
QgsUnitTypes::RenderUnit mOffsetUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mOffsetMapUnitScale;

RenderRingFilter mRingFilter = AllRings;
};

/**
Expand Down

0 comments on commit a5b969f

Please sign in to comment.