Skip to content
Permalink
Browse files

[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 Nov 6, 2018
1 parent a5db9af commit a5b969f27cd2e8e275b45e9c7a2077f3c56f1e08
@@ -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 );
@@ -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
@@ -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 ) );
@@ -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;
}

@@ -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;
@@ -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 )
@@ -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;
}

@@ -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;
@@ -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;
@@ -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;
}
}

@@ -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;
}

@@ -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;
@@ -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 )
{
@@ -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;
}
}
}

@@ -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 );
@@ -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 );

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

RenderRingFilter mRingFilter = AllRings;
};

/**

0 comments on commit a5b969f

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