Skip to content

Commit a5b969f

Browse files
committedDec 2, 2018
[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)
1 parent a5db9af commit a5b969f

File tree

16 files changed

+818
-268
lines changed

16 files changed

+818
-268
lines changed
 

‎python/core/auto_generated/symbology/qgssymbollayer.sip.in‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,14 @@ class QgsLineSymbolLayer : QgsSymbolLayer
811811
#include "qgssymbollayer.h"
812812
%End
813813
public:
814+
815+
enum RenderRingFilter
816+
{
817+
AllRings,
818+
ExteriorRingOnly,
819+
InteriorRingsOnly,
820+
};
821+
814822
virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) = 0;
815823

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

877885

886+
RenderRingFilter ringFilter() const;
887+
%Docstring
888+
Returns the line symbol layer's ring filter, which controls which rings are
889+
rendered when the line symbol is being used to draw a polygon's rings.
890+
891+
This setting has no effect when the line symbol is not being rendered
892+
for a polygon.
893+
894+
.. seealso:: :py:func:`setRingFilter`
895+
896+
.. versionadded:: 3.6
897+
%End
898+
899+
void setRingFilter( QgsLineSymbolLayer::RenderRingFilter filter );
900+
%Docstring
901+
Sets the line symbol layer's ring ``filter``, which controls which rings are
902+
rendered when the line symbol is being used to draw a polygon's rings.
903+
904+
This setting has no effect when the line symbol is not being rendered
905+
for a polygon.
906+
907+
.. seealso:: :py:func:`ringFilter`
908+
909+
.. versionadded:: 3.6
910+
%End
911+
878912
protected:
879913
QgsLineSymbolLayer( bool locked = false );
880914

915+
881916
};
882917

883918
class QgsFillSymbolLayer : QgsSymbolLayer

‎src/core/symbology/qgsarrowsymbollayer.cpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ QgsSymbolLayer *QgsArrowSymbolLayer::create( const QgsStringMap &props )
9797
if ( props.contains( QStringLiteral( "offset_unit_scale" ) ) )
9898
l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_unit_scale" )] ) );
9999

100+
if ( props.contains( QStringLiteral( "ring_filter" ) ) )
101+
l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
102+
100103
l->restoreOldDataDefinedProperties( props );
101104

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

154+
map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
155+
151156
return map;
152157
}
153158

‎src/core/symbology/qgslinesymbollayer.cpp‎

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ QgsSymbolLayer *QgsSimpleLineSymbolLayer::create( const QgsStringMap &props )
171171
l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
172172
}
173173

174+
if ( props.contains( QStringLiteral( "ring_filter" ) ) )
175+
{
176+
l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
177+
}
178+
174179
l->restoreOldDataDefinedProperties( props );
175180

176181
return l;
@@ -240,34 +245,58 @@ void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QLi
240245
}
241246

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

249-
if ( rings )
250+
switch ( mRingFilter )
251+
{
252+
case AllRings:
253+
case ExteriorRingOnly:
250254
{
251-
//add polygon rings
252-
QList<QPolygonF>::const_iterator it = rings->constBegin();
253-
for ( ; it != rings->constEnd(); ++it )
255+
if ( mDrawInsidePolygon )
254256
{
255-
QPolygonF ring = *it;
256-
clipPath.addPolygon( ring );
257+
//only drawing the line on the interior of the polygon, so set clip path for painter
258+
QPainterPath clipPath;
259+
clipPath.addPolygon( points );
260+
261+
if ( rings )
262+
{
263+
//add polygon rings
264+
QList<QPolygonF>::const_iterator it = rings->constBegin();
265+
for ( ; it != rings->constEnd(); ++it )
266+
{
267+
QPolygonF ring = *it;
268+
clipPath.addPolygon( ring );
269+
}
270+
}
271+
272+
//use intersect mode, as a clip path may already exist (e.g., for composer maps)
273+
p->setClipPath( clipPath, Qt::IntersectClip );
257274
}
275+
276+
renderPolyline( points, context );
258277
}
278+
break;
259279

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

264-
renderPolyline( points, context );
265284
if ( rings )
266285
{
267-
mOffset = -mOffset; // invert the offset for rings!
268-
Q_FOREACH ( const QPolygonF &ring, *rings )
269-
renderPolyline( ring, context );
270-
mOffset = -mOffset;
286+
switch ( mRingFilter )
287+
{
288+
case AllRings:
289+
case InteriorRingsOnly:
290+
{
291+
mOffset = -mOffset; // invert the offset for rings!
292+
for ( const QPolygonF &ring : qgis::as_const( *rings ) )
293+
renderPolyline( ring, context );
294+
mOffset = -mOffset;
295+
}
296+
break;
297+
case ExteriorRingOnly:
298+
break;
299+
}
271300
}
272301

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

@@ -373,6 +403,7 @@ QgsSimpleLineSymbolLayer *QgsSimpleLineSymbolLayer::clone() const
373403
l->setUseCustomDashPattern( mUseCustomDashPattern );
374404
l->setCustomDashVector( mCustomDashVector );
375405
l->setDrawInsidePolygon( mDrawInsidePolygon );
406+
l->setRingFilter( mRingFilter );
376407
copyDataDefinedProperties( l );
377408
copyPaintEffect( l );
378409
return l;
@@ -781,6 +812,11 @@ QgsSymbolLayer *QgsMarkerLineSymbolLayer::create( const QgsStringMap &props )
781812
x->setPlacement( Interval );
782813
}
783814

815+
if ( props.contains( QStringLiteral( "ring_filter" ) ) )
816+
{
817+
x->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
818+
}
819+
784820
x->restoreOldDataDefinedProperties( props );
785821

786822
return x;
@@ -910,19 +946,39 @@ void QgsMarkerLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QLi
910946
{
911947
context.renderContext().setGeometry( curvePolygon->exteriorRing() );
912948
}
913-
renderPolyline( points, context );
949+
950+
switch ( mRingFilter )
951+
{
952+
case AllRings:
953+
case ExteriorRingOnly:
954+
renderPolyline( points, context );
955+
break;
956+
case InteriorRingsOnly:
957+
break;
958+
}
959+
914960
if ( rings )
915961
{
916-
mOffset = -mOffset; // invert the offset for rings!
917-
for ( int i = 0; i < rings->size(); ++i )
962+
switch ( mRingFilter )
918963
{
919-
if ( curvePolygon )
964+
case AllRings:
965+
case InteriorRingsOnly:
920966
{
921-
context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
967+
mOffset = -mOffset; // invert the offset for rings!
968+
for ( int i = 0; i < rings->size(); ++i )
969+
{
970+
if ( curvePolygon )
971+
{
972+
context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
973+
}
974+
renderPolyline( rings->at( i ), context );
975+
}
976+
mOffset = -mOffset;
922977
}
923-
renderPolyline( rings->at( i ), context );
978+
break;
979+
case ExteriorRingOnly:
980+
break;
924981
}
925-
mOffset = -mOffset;
926982
}
927983
}
928984

@@ -1346,6 +1402,8 @@ QgsStringMap QgsMarkerLineSymbolLayer::properties() const
13461402
map[QStringLiteral( "placement" )] = QStringLiteral( "curvepoint" );
13471403
else
13481404
map[QStringLiteral( "placement" )] = QStringLiteral( "interval" );
1405+
1406+
map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
13491407
return map;
13501408
}
13511409

@@ -1380,6 +1438,7 @@ QgsMarkerLineSymbolLayer *QgsMarkerLineSymbolLayer::clone() const
13801438
x->setOffsetAlongLine( mOffsetAlongLine );
13811439
x->setOffsetAlongLineMapUnitScale( mOffsetAlongLineMapUnitScale );
13821440
x->setOffsetAlongLineUnit( mOffsetAlongLineUnit );
1441+
x->setRingFilter( mRingFilter );
13831442
copyDataDefinedProperties( x );
13841443
copyPaintEffect( x );
13851444
return x;

‎src/core/symbology/qgssymbollayer.cpp‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,16 @@ QgsLineSymbolLayer::QgsLineSymbolLayer( bool locked )
391391
{
392392
}
393393

394+
QgsLineSymbolLayer::RenderRingFilter QgsLineSymbolLayer::ringFilter() const
395+
{
396+
return mRingFilter;
397+
}
398+
399+
void QgsLineSymbolLayer::setRingFilter( const RenderRingFilter filter )
400+
{
401+
mRingFilter = filter;
402+
}
403+
394404
QgsFillSymbolLayer::QgsFillSymbolLayer( bool locked )
395405
: QgsSymbolLayer( QgsSymbol::Fill, locked )
396406
{
@@ -609,11 +619,30 @@ void QgsLineSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &context, QSize
609619

610620
void QgsLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QList<QPolygonF> *rings, QgsSymbolRenderContext &context )
611621
{
612-
renderPolyline( points, context );
622+
switch ( mRingFilter )
623+
{
624+
case AllRings:
625+
case ExteriorRingOnly:
626+
renderPolyline( points, context );
627+
break;
628+
case InteriorRingsOnly:
629+
break;
630+
}
631+
613632
if ( rings )
614633
{
615-
Q_FOREACH ( const QPolygonF &ring, *rings )
616-
renderPolyline( ring, context );
634+
switch ( mRingFilter )
635+
{
636+
case AllRings:
637+
case InteriorRingsOnly:
638+
{
639+
for ( const QPolygonF &ring : qgis::as_const( *rings ) )
640+
renderPolyline( ring, context );
641+
}
642+
break;
643+
case ExteriorRingOnly:
644+
break;
645+
}
617646
}
618647
}
619648

‎src/core/symbology/qgssymbollayer.h‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,15 @@ class CORE_EXPORT QgsMarkerSymbolLayer : public QgsSymbolLayer
764764
class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer
765765
{
766766
public:
767+
768+
//! Options for filtering rings when the line symbol layer is being used to render a polygon's rings.
769+
enum RenderRingFilter
770+
{
771+
AllRings, //!< Render both exterior and interior rings
772+
ExteriorRingOnly, //!< Render the exterior ring only
773+
InteriorRingsOnly, //!< Render the interior rings only
774+
};
775+
767776
virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) = 0;
768777

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

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

828+
/**
829+
* Returns the line symbol layer's ring filter, which controls which rings are
830+
* rendered when the line symbol is being used to draw a polygon's rings.
831+
*
832+
* This setting has no effect when the line symbol is not being rendered
833+
* for a polygon.
834+
*
835+
* \see setRingFilter()
836+
* \since QGIS 3.6
837+
*/
838+
RenderRingFilter ringFilter() const;
839+
840+
/**
841+
* Sets the line symbol layer's ring \a filter, which controls which rings are
842+
* rendered when the line symbol is being used to draw a polygon's rings.
843+
*
844+
* This setting has no effect when the line symbol is not being rendered
845+
* for a polygon.
846+
*
847+
* \see ringFilter()
848+
* \since QGIS 3.6
849+
*/
850+
void setRingFilter( QgsLineSymbolLayer::RenderRingFilter filter );
851+
819852
protected:
820853
QgsLineSymbolLayer( bool locked = false );
821854

@@ -825,6 +858,8 @@ class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer
825858
double mOffset = 0;
826859
QgsUnitTypes::RenderUnit mOffsetUnit = QgsUnitTypes::RenderMillimeters;
827860
QgsMapUnitScale mOffsetMapUnitScale;
861+
862+
RenderRingFilter mRingFilter = AllRings;
828863
};
829864

830865
/**

‎src/gui/symbology/qgssymbollayerwidget.cpp‎

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,26 @@ QgsSimpleLineSymbolLayerWidget::QgsSimpleLineSymbolLayerWidget( QgsVectorLayer *
194194
btnChangeColor->setColorDialogTitle( tr( "Select Line Color" ) );
195195
btnChangeColor->setContext( QStringLiteral( "symbology" ) );
196196

197+
mRingFilterComboBox->addItem( tr( "All Rings" ), QgsLineSymbolLayer::AllRings );
198+
mRingFilterComboBox->addItem( tr( "Exterior Ring Only" ), QgsLineSymbolLayer::ExteriorRingOnly );
199+
mRingFilterComboBox->addItem( tr( "Interior Rings Only" ), QgsLineSymbolLayer::InteriorRingsOnly );
200+
connect( mRingFilterComboBox, qgis::overload< int >::of( &QComboBox::currentIndexChanged ), this, [ = ]( int )
201+
{
202+
if ( mLayer )
203+
{
204+
mLayer->setRingFilter( static_cast< QgsLineSymbolLayer::RenderRingFilter >( mRingFilterComboBox->currentData().toInt() ) );
205+
emit changed();
206+
}
207+
} );
208+
197209
spinOffset->setClearValue( 0.0 );
198210

199211
if ( vl && vl->geometryType() != QgsWkbTypes::PolygonGeometry )
200212
{
201213
//draw inside polygon checkbox only makes sense for polygon layers
202214
mDrawInsideCheckBox->hide();
215+
mRingFilterComboBox->hide();
216+
mRingsLabel->hide();
203217
}
204218

205219
//make a temporary symbol for the size assistant preview
@@ -286,10 +300,10 @@ void QgsSimpleLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
286300
mCustomCheckBox->blockSignals( false );
287301

288302
//draw inside polygon?
289-
bool drawInsidePolygon = mLayer->drawInsidePolygon();
290-
mDrawInsideCheckBox->blockSignals( true );
291-
mDrawInsideCheckBox->setCheckState( drawInsidePolygon ? Qt::Checked : Qt::Unchecked );
292-
mDrawInsideCheckBox->blockSignals( false );
303+
const bool drawInsidePolygon = mLayer->drawInsidePolygon();
304+
whileBlocking( mDrawInsideCheckBox )->setCheckState( drawInsidePolygon ? Qt::Checked : Qt::Unchecked );
305+
306+
whileBlocking( mRingFilterComboBox )->setCurrentIndex( mRingFilterComboBox->findData( mLayer->ringFilter() ) );
293307

294308
updatePatternIcon();
295309

@@ -1647,8 +1661,27 @@ QgsMarkerLineSymbolLayerWidget::QgsMarkerLineSymbolLayerWidget( QgsVectorLayer *
16471661
mOffsetAlongLineUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
16481662
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
16491663

1664+
mRingFilterComboBox->addItem( tr( "All Rings" ), QgsLineSymbolLayer::AllRings );
1665+
mRingFilterComboBox->addItem( tr( "Exterior Ring Only" ), QgsLineSymbolLayer::ExteriorRingOnly );
1666+
mRingFilterComboBox->addItem( tr( "Interior Rings Only" ), QgsLineSymbolLayer::InteriorRingsOnly );
1667+
connect( mRingFilterComboBox, qgis::overload< int >::of( &QComboBox::currentIndexChanged ), this, [ = ]( int )
1668+
{
1669+
if ( mLayer )
1670+
{
1671+
mLayer->setRingFilter( static_cast< QgsLineSymbolLayer::RenderRingFilter >( mRingFilterComboBox->currentData().toInt() ) );
1672+
emit changed();
1673+
}
1674+
} );
1675+
16501676
spinOffset->setClearValue( 0.0 );
16511677

1678+
1679+
if ( vl && vl->geometryType() != QgsWkbTypes::PolygonGeometry )
1680+
{
1681+
mRingFilterComboBox->hide();
1682+
mRingsLabel->hide();
1683+
}
1684+
16521685
connect( spinInterval, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsMarkerLineSymbolLayerWidget::setInterval );
16531686
connect( mSpinOffsetAlongLine, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsMarkerLineSymbolLayerWidget::setOffsetAlongLine );
16541687
connect( chkRotateMarker, &QAbstractButton::clicked, this, &QgsMarkerLineSymbolLayerWidget::setRotate );
@@ -1709,6 +1742,8 @@ void QgsMarkerLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
17091742
mOffsetAlongLineUnitWidget->setMapUnitScale( mLayer->offsetAlongLineMapUnitScale() );
17101743
mOffsetAlongLineUnitWidget->blockSignals( false );
17111744

1745+
whileBlocking( mRingFilterComboBox )->setCurrentIndex( mRingFilterComboBox->findData( mLayer->ringFilter() ) );
1746+
17121747
setPlacement(); // update gui
17131748

17141749
registerDataDefinedButton( mIntervalDDBtn, QgsSymbolLayer::PropertyInterval );

‎src/ui/symbollayer/widget_markerline.ui‎

Lines changed: 141 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<x>0</x>
88
<y>0</y>
99
<width>371</width>
10-
<height>338</height>
10+
<height>348</height>
1111
</rect>
1212
</property>
1313
<property name="windowTitle">
@@ -23,132 +23,6 @@
2323
<property name="bottomMargin">
2424
<number>1</number>
2525
</property>
26-
<item row="7" column="1" colspan="2">
27-
<spacer name="verticalSpacer">
28-
<property name="orientation">
29-
<enum>Qt::Vertical</enum>
30-
</property>
31-
<property name="sizeHint" stdset="0">
32-
<size>
33-
<width>20</width>
34-
<height>40</height>
35-
</size>
36-
</property>
37-
</spacer>
38-
</item>
39-
<item row="6" column="0" colspan="3">
40-
<layout class="QHBoxLayout" name="horizontalLayout_3">
41-
<item>
42-
<widget class="QLabel" name="label_3">
43-
<property name="text">
44-
<string>Line offset</string>
45-
</property>
46-
</widget>
47-
</item>
48-
<item>
49-
<widget class="QgsDoubleSpinBox" name="spinOffset">
50-
<property name="sizePolicy">
51-
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
52-
<horstretch>1</horstretch>
53-
<verstretch>0</verstretch>
54-
</sizepolicy>
55-
</property>
56-
<property name="decimals">
57-
<number>6</number>
58-
</property>
59-
<property name="minimum">
60-
<double>-999999999.000000000000000</double>
61-
</property>
62-
<property name="maximum">
63-
<double>999999999.000000000000000</double>
64-
</property>
65-
<property name="singleStep">
66-
<double>0.200000000000000</double>
67-
</property>
68-
</widget>
69-
</item>
70-
<item>
71-
<widget class="QgsUnitSelectionWidget" name="mOffsetUnitWidget" native="true">
72-
<property name="minimumSize">
73-
<size>
74-
<width>0</width>
75-
<height>0</height>
76-
</size>
77-
</property>
78-
<property name="focusPolicy">
79-
<enum>Qt::TabFocus</enum>
80-
</property>
81-
</widget>
82-
</item>
83-
<item>
84-
<widget class="QgsPropertyOverrideButton" name="mLineOffsetDDBtn">
85-
<property name="text">
86-
<string>…</string>
87-
</property>
88-
</widget>
89-
</item>
90-
</layout>
91-
</item>
92-
<item row="5" column="0" colspan="3">
93-
<widget class="QCheckBox" name="chkRotateMarker">
94-
<property name="text">
95-
<string>Rotate marker</string>
96-
</property>
97-
</widget>
98-
</item>
99-
<item row="4" column="0" colspan="3">
100-
<layout class="QHBoxLayout" name="horizontalLayout">
101-
<item>
102-
<widget class="QLabel" name="label">
103-
<property name="text">
104-
<string>Offset along line</string>
105-
</property>
106-
</widget>
107-
</item>
108-
<item>
109-
<widget class="QgsDoubleSpinBox" name="mSpinOffsetAlongLine">
110-
<property name="sizePolicy">
111-
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
112-
<horstretch>1</horstretch>
113-
<verstretch>0</verstretch>
114-
</sizepolicy>
115-
</property>
116-
<property name="decimals">
117-
<number>6</number>
118-
</property>
119-
<property name="maximum">
120-
<double>10000000.000000000000000</double>
121-
</property>
122-
<property name="singleStep">
123-
<double>0.200000000000000</double>
124-
</property>
125-
<property name="value">
126-
<double>1.000000000000000</double>
127-
</property>
128-
</widget>
129-
</item>
130-
<item>
131-
<widget class="QgsUnitSelectionWidget" name="mOffsetAlongLineUnitWidget" native="true">
132-
<property name="minimumSize">
133-
<size>
134-
<width>0</width>
135-
<height>0</height>
136-
</size>
137-
</property>
138-
<property name="focusPolicy">
139-
<enum>Qt::TabFocus</enum>
140-
</property>
141-
</widget>
142-
</item>
143-
<item>
144-
<widget class="QgsPropertyOverrideButton" name="mOffsetAlongLineDDBtn">
145-
<property name="text">
146-
<string>…</string>
147-
</property>
148-
</widget>
149-
</item>
150-
</layout>
151-
</item>
15226
<item row="0" column="0">
15327
<widget class="QLabel" name="label_2">
15428
<property name="text">
@@ -279,12 +153,18 @@
279153
</item>
280154
</layout>
281155
</item>
282-
<item row="0" column="1">
283-
<widget class="QgsPropertyOverrideButton" name="mPlacementDDBtn">
284-
<property name="text">
285-
<string>…</string>
156+
<item row="5" column="1" colspan="2">
157+
<spacer name="verticalSpacer">
158+
<property name="orientation">
159+
<enum>Qt::Vertical</enum>
286160
</property>
287-
</widget>
161+
<property name="sizeHint" stdset="0">
162+
<size>
163+
<width>20</width>
164+
<height>40</height>
165+
</size>
166+
</property>
167+
</spacer>
288168
</item>
289169
<item row="0" column="2">
290170
<spacer name="horizontalSpacer">
@@ -299,6 +179,135 @@
299179
</property>
300180
</spacer>
301181
</item>
182+
<item row="0" column="1">
183+
<widget class="QgsPropertyOverrideButton" name="mPlacementDDBtn">
184+
<property name="text">
185+
<string>…</string>
186+
</property>
187+
</widget>
188+
</item>
189+
<item row="2" column="0" colspan="3">
190+
<layout class="QGridLayout" name="gridLayout">
191+
<property name="topMargin">
192+
<number>0</number>
193+
</property>
194+
<item row="0" column="1">
195+
<widget class="QgsDoubleSpinBox" name="mSpinOffsetAlongLine">
196+
<property name="sizePolicy">
197+
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
198+
<horstretch>1</horstretch>
199+
<verstretch>0</verstretch>
200+
</sizepolicy>
201+
</property>
202+
<property name="decimals">
203+
<number>6</number>
204+
</property>
205+
<property name="maximum">
206+
<double>10000000.000000000000000</double>
207+
</property>
208+
<property name="singleStep">
209+
<double>0.200000000000000</double>
210+
</property>
211+
<property name="value">
212+
<double>1.000000000000000</double>
213+
</property>
214+
</widget>
215+
</item>
216+
<item row="0" column="3">
217+
<widget class="QgsPropertyOverrideButton" name="mOffsetAlongLineDDBtn">
218+
<property name="text">
219+
<string>…</string>
220+
</property>
221+
</widget>
222+
</item>
223+
<item row="0" column="0">
224+
<widget class="QLabel" name="label">
225+
<property name="text">
226+
<string>Offset along line</string>
227+
</property>
228+
</widget>
229+
</item>
230+
<item row="2" column="3">
231+
<widget class="QgsPropertyOverrideButton" name="mLineOffsetDDBtn">
232+
<property name="text">
233+
<string>…</string>
234+
</property>
235+
</widget>
236+
</item>
237+
<item row="0" column="2">
238+
<widget class="QgsUnitSelectionWidget" name="mOffsetAlongLineUnitWidget" native="true">
239+
<property name="minimumSize">
240+
<size>
241+
<width>0</width>
242+
<height>0</height>
243+
</size>
244+
</property>
245+
<property name="focusPolicy">
246+
<enum>Qt::TabFocus</enum>
247+
</property>
248+
</widget>
249+
</item>
250+
<item row="2" column="0">
251+
<widget class="QLabel" name="label_3">
252+
<property name="text">
253+
<string>Line offset</string>
254+
</property>
255+
</widget>
256+
</item>
257+
<item row="2" column="2">
258+
<widget class="QgsUnitSelectionWidget" name="mOffsetUnitWidget" native="true">
259+
<property name="minimumSize">
260+
<size>
261+
<width>0</width>
262+
<height>0</height>
263+
</size>
264+
</property>
265+
<property name="focusPolicy">
266+
<enum>Qt::TabFocus</enum>
267+
</property>
268+
</widget>
269+
</item>
270+
<item row="2" column="1">
271+
<widget class="QgsDoubleSpinBox" name="spinOffset">
272+
<property name="sizePolicy">
273+
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
274+
<horstretch>1</horstretch>
275+
<verstretch>0</verstretch>
276+
</sizepolicy>
277+
</property>
278+
<property name="decimals">
279+
<number>6</number>
280+
</property>
281+
<property name="minimum">
282+
<double>-999999999.000000000000000</double>
283+
</property>
284+
<property name="maximum">
285+
<double>999999999.000000000000000</double>
286+
</property>
287+
<property name="singleStep">
288+
<double>0.200000000000000</double>
289+
</property>
290+
</widget>
291+
</item>
292+
<item row="1" column="0" colspan="4">
293+
<widget class="QCheckBox" name="chkRotateMarker">
294+
<property name="text">
295+
<string>Rotate marker</string>
296+
</property>
297+
</widget>
298+
</item>
299+
<item row="3" column="0">
300+
<widget class="QLabel" name="mRingsLabel">
301+
<property name="text">
302+
<string>Rings</string>
303+
</property>
304+
</widget>
305+
</item>
306+
<item row="3" column="1" colspan="3">
307+
<widget class="QComboBox" name="mRingFilterComboBox"/>
308+
</item>
309+
</layout>
310+
</item>
302311
</layout>
303312
</widget>
304313
<customwidgets>
@@ -330,13 +339,6 @@
330339
<tabstop>radVertexFirst</tabstop>
331340
<tabstop>radCentralPoint</tabstop>
332341
<tabstop>radCurvePoint</tabstop>
333-
<tabstop>mSpinOffsetAlongLine</tabstop>
334-
<tabstop>mOffsetAlongLineUnitWidget</tabstop>
335-
<tabstop>mOffsetAlongLineDDBtn</tabstop>
336-
<tabstop>chkRotateMarker</tabstop>
337-
<tabstop>spinOffset</tabstop>
338-
<tabstop>mOffsetUnitWidget</tabstop>
339-
<tabstop>mLineOffsetDDBtn</tabstop>
340342
</tabstops>
341343
<resources/>
342344
<connections/>

‎src/ui/symbollayer/widget_simpleline.ui‎

Lines changed: 108 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,31 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>290</width>
9+
<width>300</width>
1010
<height>384</height>
1111
</rect>
1212
</property>
1313
<property name="windowTitle">
1414
<string>Form</string>
1515
</property>
1616
<layout class="QGridLayout" name="gridLayout">
17+
<item row="0" column="0">
18+
<widget class="QLabel" name="label">
19+
<property name="text">
20+
<string>Color</string>
21+
</property>
22+
</widget>
23+
</item>
24+
<item row="5" column="2">
25+
<widget class="QgsPenCapStyleComboBox" name="cboCapStyle"/>
26+
</item>
27+
<item row="8" column="0" colspan="3">
28+
<widget class="QCheckBox" name="mDrawInsideCheckBox">
29+
<property name="text">
30+
<string>Draw line only inside polygon</string>
31+
</property>
32+
</widget>
33+
</item>
1734
<item row="1" column="2">
1835
<layout class="QHBoxLayout" name="horizontalLayout_2">
1936
<item>
@@ -59,24 +76,38 @@
5976
</item>
6077
</layout>
6178
</item>
62-
<item row="4" column="3">
63-
<widget class="QgsPropertyOverrideButton" name="mJoinStyleDDBtn">
79+
<item row="2" column="0">
80+
<widget class="QLabel" name="label_4">
6481
<property name="text">
65-
<string></string>
82+
<string>Offset</string>
6683
</property>
6784
</widget>
6885
</item>
69-
<item row="4" column="2">
70-
<widget class="QgsPenJoinStyleComboBox" name="cboJoinStyle"/>
86+
<item row="6" column="0" colspan="3">
87+
<widget class="QCheckBox" name="mCustomCheckBox">
88+
<property name="text">
89+
<string>Use custom dash pattern</string>
90+
</property>
91+
</widget>
7192
</item>
72-
<item row="5" column="0">
73-
<widget class="QLabel" name="label_6">
93+
<item row="3" column="2">
94+
<widget class="QgsPenStyleComboBox" name="cboPenStyle"/>
95+
</item>
96+
<item row="1" column="0">
97+
<widget class="QLabel" name="label_2">
7498
<property name="text">
75-
<string>Cap style</string>
99+
<string>Stroke width</string>
76100
</property>
77101
</widget>
78102
</item>
79-
<item row="9" column="0">
103+
<item row="1" column="3">
104+
<widget class="QgsPropertyOverrideButton" name="mPenWidthDDBtn">
105+
<property name="text">
106+
<string>…</string>
107+
</property>
108+
</widget>
109+
</item>
110+
<item row="10" column="0">
80111
<spacer name="verticalSpacer">
81112
<property name="orientation">
82113
<enum>Qt::Vertical</enum>
@@ -89,10 +120,10 @@
89120
</property>
90121
</spacer>
91122
</item>
92-
<item row="4" column="0">
93-
<widget class="QLabel" name="label_5">
123+
<item row="2" column="3">
124+
<widget class="QgsPropertyOverrideButton" name="mOffsetDDBtn">
94125
<property name="text">
95-
<string>Join style</string>
126+
<string></string>
96127
</property>
97128
</widget>
98129
</item>
@@ -121,78 +152,23 @@
121152
</property>
122153
</widget>
123154
</item>
124-
<item row="8" column="0" colspan="3">
125-
<widget class="QCheckBox" name="mDrawInsideCheckBox">
126-
<property name="text">
127-
<string>Draw line only inside polygon</string>
128-
</property>
129-
</widget>
130-
</item>
131-
<item row="2" column="0">
132-
<widget class="QLabel" name="label_4">
133-
<property name="text">
134-
<string>Offset</string>
135-
</property>
136-
</widget>
137-
</item>
138-
<item row="7" column="2">
139-
<layout class="QHBoxLayout" name="horizontalLayout_12">
140-
<item>
141-
<widget class="QPushButton" name="mChangePatternButton">
142-
<property name="sizePolicy">
143-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
144-
<horstretch>0</horstretch>
145-
<verstretch>0</verstretch>
146-
</sizepolicy>
147-
</property>
148-
<property name="text">
149-
<string>Change</string>
150-
</property>
151-
</widget>
152-
</item>
153-
<item>
154-
<widget class="QgsUnitSelectionWidget" name="mDashPatternUnitWidget" native="true">
155-
<property name="focusPolicy">
156-
<enum>Qt::StrongFocus</enum>
157-
</property>
158-
</widget>
159-
</item>
160-
</layout>
161-
</item>
162-
<item row="3" column="2">
163-
<widget class="QgsPenStyleComboBox" name="cboPenStyle"/>
164-
</item>
165-
<item row="5" column="2">
166-
<widget class="QgsPenCapStyleComboBox" name="cboCapStyle"/>
167-
</item>
168-
<item row="0" column="0">
169-
<widget class="QLabel" name="label">
170-
<property name="text">
171-
<string>Color</string>
172-
</property>
173-
</widget>
174-
</item>
175-
<item row="1" column="0">
176-
<widget class="QLabel" name="label_2">
177-
<property name="text">
178-
<string>Stroke width</string>
179-
</property>
180-
</widget>
181-
</item>
182-
<item row="7" column="3">
183-
<widget class="QgsPropertyOverrideButton" name="mDashPatternDDBtn">
155+
<item row="5" column="3">
156+
<widget class="QgsPropertyOverrideButton" name="mCapStyleDDBtn">
184157
<property name="text">
185158
<string>…</string>
186159
</property>
187160
</widget>
188161
</item>
189-
<item row="3" column="0">
190-
<widget class="QLabel" name="label_3">
162+
<item row="5" column="0">
163+
<widget class="QLabel" name="label_6">
191164
<property name="text">
192-
<string>Stroke style</string>
165+
<string>Cap style</string>
193166
</property>
194167
</widget>
195168
</item>
169+
<item row="4" column="2">
170+
<widget class="QgsPenJoinStyleComboBox" name="cboJoinStyle"/>
171+
</item>
196172
<item row="2" column="2">
197173
<layout class="QHBoxLayout" name="horizontalLayout_3">
198174
<item>
@@ -235,22 +211,46 @@
235211
</item>
236212
</layout>
237213
</item>
238-
<item row="5" column="3">
239-
<widget class="QgsPropertyOverrideButton" name="mCapStyleDDBtn">
214+
<item row="7" column="3">
215+
<widget class="QgsPropertyOverrideButton" name="mDashPatternDDBtn">
240216
<property name="text">
241217
<string>…</string>
242218
</property>
243219
</widget>
244220
</item>
245-
<item row="0" column="3">
246-
<widget class="QgsPropertyOverrideButton" name="mColorDDBtn">
221+
<item row="4" column="0">
222+
<widget class="QLabel" name="label_5">
247223
<property name="text">
248-
<string></string>
224+
<string>Join style</string>
249225
</property>
250226
</widget>
251227
</item>
252-
<item row="1" column="3">
253-
<widget class="QgsPropertyOverrideButton" name="mPenWidthDDBtn">
228+
<item row="7" column="2">
229+
<layout class="QHBoxLayout" name="horizontalLayout_12">
230+
<item>
231+
<widget class="QPushButton" name="mChangePatternButton">
232+
<property name="sizePolicy">
233+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
234+
<horstretch>0</horstretch>
235+
<verstretch>0</verstretch>
236+
</sizepolicy>
237+
</property>
238+
<property name="text">
239+
<string>Change</string>
240+
</property>
241+
</widget>
242+
</item>
243+
<item>
244+
<widget class="QgsUnitSelectionWidget" name="mDashPatternUnitWidget" native="true">
245+
<property name="focusPolicy">
246+
<enum>Qt::StrongFocus</enum>
247+
</property>
248+
</widget>
249+
</item>
250+
</layout>
251+
</item>
252+
<item row="4" column="3">
253+
<widget class="QgsPropertyOverrideButton" name="mJoinStyleDDBtn">
254254
<property name="text">
255255
<string>…</string>
256256
</property>
@@ -263,43 +263,53 @@
263263
</property>
264264
</widget>
265265
</item>
266-
<item row="2" column="3">
267-
<widget class="QgsPropertyOverrideButton" name="mOffsetDDBtn">
266+
<item row="3" column="0">
267+
<widget class="QLabel" name="label_3">
268+
<property name="text">
269+
<string>Stroke style</string>
270+
</property>
271+
</widget>
272+
</item>
273+
<item row="0" column="3">
274+
<widget class="QgsPropertyOverrideButton" name="mColorDDBtn">
268275
<property name="text">
269276
<string>…</string>
270277
</property>
271278
</widget>
272279
</item>
273-
<item row="6" column="0" colspan="3">
274-
<widget class="QCheckBox" name="mCustomCheckBox">
280+
<item row="9" column="0">
281+
<widget class="QLabel" name="mRingsLabel">
275282
<property name="text">
276-
<string>Use custom dash pattern</string>
283+
<string>Rings</string>
277284
</property>
278285
</widget>
279286
</item>
287+
<item row="9" column="2" colspan="2">
288+
<widget class="QComboBox" name="mRingFilterComboBox"/>
289+
</item>
280290
</layout>
281291
</widget>
282292
<customwidgets>
293+
<customwidget>
294+
<class>QgsPropertyOverrideButton</class>
295+
<extends>QToolButton</extends>
296+
<header>qgspropertyoverridebutton.h</header>
297+
</customwidget>
283298
<customwidget>
284299
<class>QgsDoubleSpinBox</class>
285300
<extends>QDoubleSpinBox</extends>
286301
<header>qgsdoublespinbox.h</header>
287302
</customwidget>
288303
<customwidget>
289-
<class>QgsColorButton</class>
290-
<extends>QToolButton</extends>
291-
<header>qgscolorbutton.h</header>
304+
<class>QgsUnitSelectionWidget</class>
305+
<extends>QWidget</extends>
306+
<header>qgsunitselectionwidget.h</header>
292307
<container>1</container>
293308
</customwidget>
294309
<customwidget>
295-
<class>QgsPropertyOverrideButton</class>
310+
<class>QgsColorButton</class>
296311
<extends>QToolButton</extends>
297-
<header>qgspropertyoverridebutton.h</header>
298-
</customwidget>
299-
<customwidget>
300-
<class>QgsUnitSelectionWidget</class>
301-
<extends>QWidget</extends>
302-
<header>qgsunitselectionwidget.h</header>
312+
<header>qgscolorbutton.h</header>
303313
<container>1</container>
304314
</customwidget>
305315
<customwidget>

‎tests/src/core/testqgsmarkerlinesymbol.cpp‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,14 @@ class TestQgsMarkerLineSymbol : public QObject
6060
void lineOffset();
6161
void pointNumInterval();
6262
void pointNumVertex();
63+
void ringFilter();
6364

6465
private:
6566
bool render( const QString &fileName );
6667

6768
QString mTestDataDir;
6869
QgsVectorLayer *mLinesLayer = nullptr;
70+
QgsVectorLayer *mPolygonsLayer = nullptr;
6971
QgsMapSettings *mMapSettings = nullptr;
7072
QString mReport;
7173
};
@@ -197,6 +199,35 @@ void TestQgsMarkerLineSymbol::pointNumVertex()
197199
QVERIFY( render( "point_num_vertex" ) );
198200
}
199201

202+
void TestQgsMarkerLineSymbol::ringFilter()
203+
{
204+
mMapSettings->setLayers( QList<QgsMapLayer *>() << mLinesLayer );
205+
206+
QgsMarkerLineSymbolLayer *ml = new QgsMarkerLineSymbolLayer();
207+
ml->setPlacement( QgsMarkerLineSymbolLayer::Vertex );
208+
QgsLineSymbol *lineSymbol = new QgsLineSymbol();
209+
lineSymbol->changeSymbolLayer( 0, ml );
210+
QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( lineSymbol );
211+
212+
// make sub-symbol
213+
QgsStringMap props;
214+
props[QStringLiteral( "color" )] = QStringLiteral( "255,0,0" );
215+
props[QStringLiteral( "size" )] = QStringLiteral( "2" );
216+
props[QStringLiteral( "outline_style" )] = QStringLiteral( "no" );
217+
QgsSimpleMarkerSymbolLayer *marker = static_cast< QgsSimpleMarkerSymbolLayer * >( QgsSimpleMarkerSymbolLayer::create( props ) );
218+
219+
marker->setDataDefinedProperty( QgsSymbolLayer::PropertySize, QgsProperty::fromExpression( QStringLiteral( "@geometry_point_num * 2" ) ) );
220+
221+
QgsMarkerSymbol *subSymbol = new QgsMarkerSymbol();
222+
subSymbol->changeSymbolLayer( 0, marker );
223+
ml->setSubSymbol( subSymbol );
224+
225+
mLinesLayer->setRenderer( r );
226+
227+
mMapSettings->setExtent( QgsRectangle( -140, -140, 140, 140 ) );
228+
QVERIFY( render( "point_num_vertex" ) );
229+
}
230+
200231
bool TestQgsMarkerLineSymbol::render( const QString &testType )
201232
{
202233
mReport += "<h2>" + testType + "</h2>\n";

‎tests/src/python/CMakeLists.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ ADD_PYTHON_TEST(PyQgsMapRendererCache test_qgsmaprenderercache.py)
117117
ADD_PYTHON_TEST(PyQgsMapThemeCollection test_qgsmapthemecollection.py)
118118
ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py)
119119
ADD_PYTHON_TEST(PyQgsMargins test_qgsmargins.py)
120+
ADD_PYTHON_TEST(PyQgsMarkerLineSymbolLayer test_qgsmarkerlinesymbollayer.py)
120121
ADD_PYTHON_TEST(PyQgsMessageLog test_qgsmessagelog.py)
121122
ADD_PYTHON_TEST(PyQgsMetadataBase test_qgsmetadatabase.py)
122123
ADD_PYTHON_TEST(PyQgsMetadataWidget test_qgsmetadatawidget.py)
@@ -176,6 +177,7 @@ ADD_PYTHON_TEST(PyQgsReadWriteContext test_qgsreadwritecontext.py)
176177
ADD_PYTHON_TEST(PyQgsSearchWidgetToolButton test_qgssearchwidgettoolbutton.py)
177178
ADD_PYTHON_TEST(PyQgsSearchWidgetWrapper test_qgssearchwidgetwrapper.py)
178179
ADD_PYTHON_TEST(PyQgsShortcutsManager test_qgsshortcutsmanager.py)
180+
ADD_PYTHON_TEST(PyQgsSimpleLineSymbolLayer test_qgssimplelinesymbollayer.py)
179181
ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py)
180182
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py)
181183
ADD_PYTHON_TEST(PyQgsSQLStatement test_qgssqlstatement.py)
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
test_qgsmarkerlinesymbollayer.py
6+
---------------------
7+
Date : November 2018
8+
Copyright : (C) 2018 by Nyall Dawson
9+
Email : nyall dot dawson at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Nyall Dawson'
21+
__date__ = 'November 2018'
22+
__copyright__ = '(C) 2018, Nyall Dawson'
23+
# This will get replaced with a git SHA1 when you do a git archive
24+
__revision__ = '$Format:%H$'
25+
26+
import qgis # NOQA
27+
28+
from utilities import unitTestDataPath
29+
30+
from qgis.PyQt.QtCore import QDir, Qt
31+
from qgis.PyQt.QtGui import QImage, QColor, QPainter
32+
from qgis.PyQt.QtXml import QDomDocument
33+
34+
from qgis.core import (QgsGeometry,
35+
QgsFillSymbol,
36+
QgsRenderContext,
37+
QgsFeature,
38+
QgsMapSettings,
39+
QgsRenderChecker,
40+
QgsReadWriteContext,
41+
QgsSymbolLayerUtils,
42+
QgsSimpleMarkerSymbolLayer,
43+
QgsLineSymbolLayer,
44+
QgsMarkerLineSymbolLayer,
45+
QgsMarkerSymbol
46+
)
47+
48+
from qgis.testing import unittest, start_app
49+
50+
start_app()
51+
TEST_DATA_DIR = unitTestDataPath()
52+
53+
54+
class TestQgsMarkerLineSymbolLayer(unittest.TestCase):
55+
56+
def setUp(self):
57+
self.report = "<h1>Python QgsMarkerLineSymbolLayer Tests</h1>\n"
58+
59+
def tearDown(self):
60+
report_file_path = "%s/qgistest.html" % QDir.tempPath()
61+
with open(report_file_path, 'a') as report_file:
62+
report_file.write(self.report)
63+
64+
def testRingFilter(self):
65+
# test filtering rings during rendering
66+
67+
s = QgsFillSymbol()
68+
s.deleteSymbolLayer(0)
69+
70+
marker_line = QgsMarkerLineSymbolLayer(True)
71+
marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex)
72+
marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
73+
marker.setColor(QColor(255, 0, 0))
74+
marker.setStrokeStyle(Qt.NoPen)
75+
marker_symbol = QgsMarkerSymbol()
76+
marker_symbol.changeSymbolLayer(0, marker)
77+
marker_line.setSubSymbol(marker_symbol)
78+
79+
s.appendSymbolLayer(marker_line.clone())
80+
self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.AllRings)
81+
s.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly)
82+
self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
83+
84+
s2 = s.clone()
85+
self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
86+
87+
doc = QDomDocument()
88+
context = QgsReadWriteContext()
89+
element = QgsSymbolLayerUtils.saveSymbol('test', s, doc, context)
90+
91+
s2 = QgsSymbolLayerUtils.loadSymbol(element, context)
92+
self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
93+
94+
# rendering test
95+
s3 = QgsFillSymbol()
96+
s3.deleteSymbolLayer(0)
97+
s3.appendSymbolLayer(
98+
QgsMarkerLineSymbolLayer())
99+
s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly)
100+
101+
g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
102+
rendered_image = self.renderGeometry(s3, g)
103+
assert self.imageCheck('markerline_exterioronly', 'markerline_exterioronly', rendered_image)
104+
105+
s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.InteriorRingsOnly)
106+
g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
107+
rendered_image = self.renderGeometry(s3, g)
108+
assert self.imageCheck('markerline_interioronly', 'markerline_interioronly', rendered_image)
109+
110+
def renderGeometry(self, symbol, geom):
111+
f = QgsFeature()
112+
f.setGeometry(geom)
113+
114+
image = QImage(200, 200, QImage.Format_RGB32)
115+
116+
painter = QPainter()
117+
ms = QgsMapSettings()
118+
extent = geom.get().boundingBox()
119+
# buffer extent by 10%
120+
if extent.width() > 0:
121+
extent = extent.buffered((extent.height() + extent.width()) / 20.0)
122+
else:
123+
extent = extent.buffered(10)
124+
125+
ms.setExtent(extent)
126+
ms.setOutputSize(image.size())
127+
context = QgsRenderContext.fromMapSettings(ms)
128+
context.setPainter(painter)
129+
context.setScaleFactor(96 / 25.4) # 96 DPI
130+
131+
painter.begin(image)
132+
try:
133+
image.fill(QColor(0, 0, 0))
134+
symbol.startRender(context)
135+
symbol.renderFeature(f, context)
136+
symbol.stopRender(context)
137+
finally:
138+
painter.end()
139+
140+
return image
141+
142+
def imageCheck(self, name, reference_image, image):
143+
self.report += "<h2>Render {}</h2>\n".format(name)
144+
temp_dir = QDir.tempPath() + '/'
145+
file_name = temp_dir + 'symbol_' + name + ".png"
146+
image.save(file_name, "PNG")
147+
checker = QgsRenderChecker()
148+
checker.setControlPathPrefix("symbol_markerline")
149+
checker.setControlName("expected_" + reference_image)
150+
checker.setRenderedImage(file_name)
151+
checker.setColorTolerance(2)
152+
result = checker.compareImages(name, 20)
153+
self.report += checker.report()
154+
print((self.report))
155+
return result
156+
157+
158+
if __name__ == '__main__':
159+
unittest.main()
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
test_qgssimplelinesymbollayer.py
6+
---------------------
7+
Date : November 2018
8+
Copyright : (C) 2018 by Nyall Dawson
9+
Email : nyall dot dawson at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Nyall Dawson'
21+
__date__ = 'November 2018'
22+
__copyright__ = '(C) 2018, Nyall Dawson'
23+
# This will get replaced with a git SHA1 when you do a git archive
24+
__revision__ = '$Format:%H$'
25+
26+
import qgis # NOQA
27+
28+
from utilities import unitTestDataPath
29+
30+
from qgis.PyQt.QtCore import QDir
31+
from qgis.PyQt.QtGui import QImage, QColor, QPainter
32+
from qgis.PyQt.QtXml import QDomDocument
33+
34+
from qgis.core import (QgsGeometry,
35+
QgsFillSymbol,
36+
QgsRenderContext,
37+
QgsFeature,
38+
QgsMapSettings,
39+
QgsRenderChecker,
40+
QgsReadWriteContext,
41+
QgsSymbolLayerUtils,
42+
QgsSimpleLineSymbolLayer,
43+
QgsLineSymbolLayer
44+
)
45+
46+
from qgis.testing import unittest, start_app
47+
48+
start_app()
49+
TEST_DATA_DIR = unitTestDataPath()
50+
51+
52+
class TestQgsSimpleLineSymbolLayer(unittest.TestCase):
53+
54+
def setUp(self):
55+
self.report = "<h1>Python QgsSimpleLineSymbolLayer Tests</h1>\n"
56+
57+
def tearDown(self):
58+
report_file_path = "%s/qgistest.html" % QDir.tempPath()
59+
with open(report_file_path, 'a') as report_file:
60+
report_file.write(self.report)
61+
62+
def testRingFilter(self):
63+
# test filtering rings during rendering
64+
65+
s = QgsFillSymbol()
66+
s.deleteSymbolLayer(0)
67+
s.appendSymbolLayer(
68+
QgsSimpleLineSymbolLayer(color=QColor(255, 0, 0), width=2))
69+
self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.AllRings)
70+
s.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly)
71+
self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
72+
73+
s2 = s.clone()
74+
self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
75+
76+
doc = QDomDocument()
77+
context = QgsReadWriteContext()
78+
element = QgsSymbolLayerUtils.saveSymbol('test', s, doc, context)
79+
80+
s2 = QgsSymbolLayerUtils.loadSymbol(element, context)
81+
self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
82+
83+
# rendering test
84+
s3 = QgsFillSymbol()
85+
s3.deleteSymbolLayer(0)
86+
s3.appendSymbolLayer(
87+
QgsSimpleLineSymbolLayer(color=QColor(255, 0, 0), width=2))
88+
s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly)
89+
90+
g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
91+
rendered_image = self.renderGeometry(s3, g)
92+
assert self.imageCheck('simpleline_exterioronly', 'simpleline_exterioronly', rendered_image)
93+
94+
s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.InteriorRingsOnly)
95+
g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
96+
rendered_image = self.renderGeometry(s3, g)
97+
assert self.imageCheck('simpleline_interioronly', 'simpleline_interioronly', rendered_image)
98+
99+
def renderGeometry(self, symbol, geom):
100+
f = QgsFeature()
101+
f.setGeometry(geom)
102+
103+
image = QImage(200, 200, QImage.Format_RGB32)
104+
105+
painter = QPainter()
106+
ms = QgsMapSettings()
107+
extent = geom.get().boundingBox()
108+
# buffer extent by 10%
109+
if extent.width() > 0:
110+
extent = extent.buffered((extent.height() + extent.width()) / 20.0)
111+
else:
112+
extent = extent.buffered(10)
113+
114+
ms.setExtent(extent)
115+
ms.setOutputSize(image.size())
116+
context = QgsRenderContext.fromMapSettings(ms)
117+
context.setPainter(painter)
118+
context.setScaleFactor(96 / 25.4) # 96 DPI
119+
120+
painter.begin(image)
121+
try:
122+
image.fill(QColor(0, 0, 0))
123+
symbol.startRender(context)
124+
symbol.renderFeature(f, context)
125+
symbol.stopRender(context)
126+
finally:
127+
painter.end()
128+
129+
return image
130+
131+
def imageCheck(self, name, reference_image, image):
132+
self.report += "<h2>Render {}</h2>\n".format(name)
133+
temp_dir = QDir.tempPath() + '/'
134+
file_name = temp_dir + 'symbol_' + name + ".png"
135+
image.save(file_name, "PNG")
136+
checker = QgsRenderChecker()
137+
checker.setControlPathPrefix("symbol_simpleline")
138+
checker.setControlName("expected_" + reference_image)
139+
checker.setRenderedImage(file_name)
140+
checker.setColorTolerance(2)
141+
result = checker.compareImages(name, 20)
142+
self.report += checker.report()
143+
print((self.report))
144+
return result
145+
146+
147+
if __name__ == '__main__':
148+
unittest.main()
1.14 KB
Loading
684 Bytes
Loading
619 Bytes
Loading
429 Bytes
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.