Skip to content

Commit 26c6435

Browse files
committed
[FEATURE] Add option for shifting markers in marker line style a set distance along the line. Supports delaying the start of markers placed at intervals, or placing markers a set distance before/after the first/last vertex (fix #9000)
1 parent 1b674b8 commit 26c6435

File tree

7 files changed

+285
-8
lines changed

7 files changed

+285
-8
lines changed

python/core/symbology-ng/qgslinesymbollayerv2.sip

+39
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,45 @@ class QgsMarkerLineSymbolLayerV2 : QgsLineSymbolLayerV2
141141

142142
Placement placement() const;
143143
void setPlacement( Placement p );
144+
145+
/**Returns the offset along the line for the marker placement. For Interval placements, this is the distance
146+
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
147+
* distance between the marker and the start of the line or the end of the line respectively.
148+
* This setting has no effect for Vertex or CentralPoint placements.
149+
* @returns The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit.
150+
* @note added in 2.3
151+
* @see setOffsetAlongLine
152+
* @see offsetAlongLineUnit
153+
* @see placement
154+
*/
155+
double offsetAlongLine() const;
156+
157+
/**Sets the the offset along the line for the marker placement. For Interval placements, this is the distance
158+
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
159+
* distance between the marker and the start of the line or the end of the line respectively.
160+
* This setting has no effect for Vertex or CentralPoint placements.
161+
* @param offsetAlongLine Distance to offset markers along the line. The offset
162+
* unit is set via setOffsetAlongLineUnit.
163+
* @note added in 2.3
164+
* @see offsetAlongLine
165+
* @see setOffsetAlongLineUnit
166+
* @see setPlacement
167+
*/
168+
void setOffsetAlongLine( double offsetAlongLine );
169+
170+
/**Returns the unit used for calculating the offset along line for markers.
171+
* @returns Offset along line unit type.
172+
* @see setOffsetAlongLineUnit
173+
* @see offsetAlongLine
174+
*/
175+
QgsSymbolV2::OutputUnit offsetAlongLineUnit() const;
176+
177+
/**Sets the unit used for calculating the offset along line for markers.
178+
* @param unit Offset along line unit type.
179+
* @see offsetAlongLineUnit
180+
* @see setOffsetAlongLine
181+
*/
182+
void setOffsetAlongLineUnit( QgsSymbolV2::OutputUnit unit );
144183

145184
QgsSymbolV2::OutputUnit intervalUnit() const;
146185
void setIntervalUnit( QgsSymbolV2::OutputUnit unit );

python/gui/symbology-ng/qgssymbollayerv2widget.sip

+2
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,13 @@ class QgsMarkerLineSymbolLayerV2Widget : QgsSymbolLayerV2Widget
194194
public slots:
195195

196196
void setInterval( double val );
197+
void setOffsetAlongLine( double val );
197198
void setRotate();
198199
void setOffset();
199200
void setPlacement();
200201
void on_mIntervalUnitComboBox_currentIndexChanged( int index );
201202
void on_mOffsetUnitComboBox_currentIndexChanged( int index );
203+
void on_mOffsetAlongLineUnitComboBox_currentIndexChanged( int index );
202204
void on_mDataDefinedPropertiesButton_clicked();
203205

204206
};

src/core/symbology-ng/qgslinesymbollayerv2.cpp

+108-1
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,8 @@ QgsMarkerLineSymbolLayerV2::QgsMarkerLineSymbolLayerV2( bool rotateMarker, doubl
561561
mOffset = 0;
562562
mOffsetUnit = QgsSymbolV2::MM;
563563
mPlacement = Interval;
564+
mOffsetAlongLine = 0;
565+
mOffsetAlongLineUnit = QgsSymbolV2::MM;
564566

565567
setSubSymbol( new QgsMarkerSymbolV2() );
566568
}
@@ -575,6 +577,7 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::create( const QgsStringMap& props
575577
bool rotate = DEFAULT_MARKERLINE_ROTATE;
576578
double interval = DEFAULT_MARKERLINE_INTERVAL;
577579

580+
578581
if ( props.contains( "interval" ) )
579582
interval = props["interval"].toDouble();
580583
if ( props.contains( "rotate" ) )
@@ -593,6 +596,14 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::create( const QgsStringMap& props
593596
{
594597
x->setIntervalUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["interval_unit"] ) );
595598
}
599+
if ( props.contains( "offset_along_line" ) )
600+
{
601+
x->setOffsetAlongLine( props["offset_along_line"].toDouble() );
602+
}
603+
if ( props.contains( "offset_along_line_unit" ) )
604+
{
605+
x->setOffsetAlongLineUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_along_line_unit"] ) );
606+
}
596607

597608
if ( props.contains( "placement" ) )
598609
{
@@ -621,6 +632,10 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::create( const QgsStringMap& props
621632
{
622633
x->setDataDefinedProperty( "placement", props["placement_expression"] );
623634
}
635+
if ( props.contains( "offset_along_line_expression" ) )
636+
{
637+
x->setDataDefinedProperty( "offset_along_line", props["offset_along_line_expression"] );
638+
}
624639

625640
return x;
626641
}
@@ -723,7 +738,7 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineInterval( const QPolygonF& points
723738

724739
QPointF lastPt = points[0];
725740
double lengthLeft = 0; // how much is left until next marker
726-
bool first = true;
741+
bool first = mOffsetAlongLine ? false : true; //only draw marker at first vertex when no offset along line is set
727742
double origAngle = mMarker->angle();
728743

729744
QgsRenderContext& rc = context.renderContext();
@@ -738,8 +753,15 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineInterval( const QPolygonF& points
738753
{
739754
interval = 0.1;
740755
}
756+
double offsetAlongLine = mOffsetAlongLine;
757+
QgsExpression* offsetAlongLineExpression = expression( "offset_along_line" );
758+
if ( offsetAlongLineExpression )
759+
{
760+
offsetAlongLine = offsetAlongLineExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
761+
}
741762

742763
double painterUnitInterval = interval * QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mIntervalUnit );
764+
lengthLeft = painterUnitInterval - offsetAlongLine * QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mIntervalUnit );
743765

744766
for ( int i = 1; i < points.count(); ++i )
745767
{
@@ -810,6 +832,18 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
810832
int i, maxCount;
811833
bool isRing = false;
812834

835+
double offsetAlongLine = mOffsetAlongLine;
836+
QgsExpression* offsetAlongLineExpression = expression( "offset_along_line" );
837+
if ( offsetAlongLineExpression )
838+
{
839+
offsetAlongLine = offsetAlongLineExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
840+
}
841+
if ( offsetAlongLine != 0 )
842+
{
843+
//scale offset along line
844+
offsetAlongLine *= QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mOffsetAlongLineUnit );
845+
}
846+
813847
if ( placement == FirstVertex )
814848
{
815849
i = 0;
@@ -828,6 +862,16 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
828862
isRing = true;
829863
}
830864

865+
if ( offsetAlongLine > 0 && ( placement == FirstVertex || placement == LastVertex ) )
866+
{
867+
double distance;
868+
distance = placement == FirstVertex ? offsetAlongLine : -offsetAlongLine;
869+
renderOffsetVertexAlongLine( points, i, distance, context );
870+
// restore original rotation
871+
mMarker->setAngle( origAngle );
872+
return;
873+
}
874+
831875
for ( ; i < maxCount; ++i )
832876
{
833877
if ( isRing && placement == Vertex && i == points.count() - 1 )
@@ -923,6 +967,65 @@ double QgsMarkerLineSymbolLayerV2::markerAngle( const QPolygonF& points, bool is
923967
return angle;
924968
}
925969

970+
void QgsMarkerLineSymbolLayerV2::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolV2RenderContext& context )
971+
{
972+
if ( points.isEmpty() )
973+
return;
974+
975+
QgsRenderContext& rc = context.renderContext();
976+
double origAngle = mMarker->angle();
977+
if ( distance == 0 )
978+
{
979+
// rotate marker (if desired)
980+
if ( mRotateMarker )
981+
{
982+
bool isRing = false;
983+
if ( points.first() == points.last() )
984+
isRing = true;
985+
double angle = markerAngle( points, isRing, vertex );
986+
mMarker->setAngle( origAngle + angle * 180 / M_PI );
987+
}
988+
mMarker->renderPoint( points[vertex], context.feature(), rc, -1, context.selected() );
989+
return;
990+
}
991+
992+
int pointIncrement = distance > 0 ? 1 : -1;
993+
QPointF previousPoint = points[vertex];
994+
int startPoint = distance > 0 ? qMin( vertex + 1, points.count() - 1 ) : qMax( vertex - 1, 0 );
995+
int endPoint = distance > 0 ? points.count() - 1 : 0;
996+
double distanceLeft = qAbs( distance );
997+
998+
for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
999+
{
1000+
const QPointF& pt = points[i];
1001+
1002+
if ( previousPoint == pt ) // must not be equal!
1003+
continue;
1004+
1005+
// create line segment
1006+
MyLine l( previousPoint, pt );
1007+
1008+
if ( distanceLeft < l.length() )
1009+
{
1010+
//destination point is in current segment
1011+
QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
1012+
// rotate marker (if desired)
1013+
if ( mRotateMarker )
1014+
{
1015+
mMarker->setAngle( origAngle + ( l.angle() * 180 / M_PI ) );
1016+
}
1017+
mMarker->renderPoint( markerPoint, context.feature(), rc, -1, context.selected() );
1018+
return;
1019+
}
1020+
1021+
distanceLeft -= l.length();
1022+
previousPoint = pt;
1023+
}
1024+
1025+
//didn't find point
1026+
return;
1027+
}
1028+
9261029
void QgsMarkerLineSymbolLayerV2::renderPolylineCentral( const QPolygonF& points, QgsSymbolV2RenderContext& context )
9271030
{
9281031
if ( points.size() > 0 )
@@ -978,6 +1081,8 @@ QgsStringMap QgsMarkerLineSymbolLayerV2::properties() const
9781081
map["rotate"] = ( mRotateMarker ? "1" : "0" );
9791082
map["interval"] = QString::number( mInterval );
9801083
map["offset"] = QString::number( mOffset );
1084+
map["offset_along_line"] = QString::number( mOffsetAlongLine );
1085+
map["offset_along_line_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetAlongLineUnit );
9811086
map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetUnit );
9821087
map["interval_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mIntervalUnit );
9831088
if ( mPlacement == Vertex )
@@ -1022,6 +1127,8 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::clone() const
10221127
x->setPlacement( mPlacement );
10231128
x->setOffsetUnit( mOffsetUnit );
10241129
x->setIntervalUnit( mIntervalUnit );
1130+
x->setOffsetAlongLine( mOffsetAlongLine );
1131+
x->setOffsetAlongLineUnit( mOffsetAlongLineUnit );
10251132
copyDataDefinedProperties( x );
10261133
return x;
10271134
}

src/core/symbology-ng/qgslinesymbollayerv2.h

+55
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,45 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2
195195
Placement placement() const { return mPlacement; }
196196
void setPlacement( Placement p ) { mPlacement = p; }
197197

198+
/**Returns the offset along the line for the marker placement. For Interval placements, this is the distance
199+
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
200+
* distance between the marker and the start of the line or the end of the line respectively.
201+
* This setting has no effect for Vertex or CentralPoint placements.
202+
* @returns The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit.
203+
* @note added in 2.3
204+
* @see setOffsetAlongLine
205+
* @see offsetAlongLineUnit
206+
* @see placement
207+
*/
208+
double offsetAlongLine() const { return mOffsetAlongLine; }
209+
210+
/**Sets the the offset along the line for the marker placement. For Interval placements, this is the distance
211+
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
212+
* distance between the marker and the start of the line or the end of the line respectively.
213+
* This setting has no effect for Vertex or CentralPoint placements.
214+
* @param offsetAlongLine Distance to offset markers along the line. The offset
215+
* unit is set via setOffsetAlongLineUnit.
216+
* @note added in 2.3
217+
* @see offsetAlongLine
218+
* @see setOffsetAlongLineUnit
219+
* @see setPlacement
220+
*/
221+
void setOffsetAlongLine( double offsetAlongLine ) { mOffsetAlongLine = offsetAlongLine; }
222+
223+
/**Returns the unit used for calculating the offset along line for markers.
224+
* @returns Offset along line unit type.
225+
* @see setOffsetAlongLineUnit
226+
* @see offsetAlongLine
227+
*/
228+
QgsSymbolV2::OutputUnit offsetAlongLineUnit() const { return mOffsetAlongLineUnit; }
229+
230+
/**Sets the unit used for calculating the offset along line for markers.
231+
* @param unit Offset along line unit type.
232+
* @see offsetAlongLineUnit
233+
* @see setOffsetAlongLine
234+
*/
235+
void setOffsetAlongLineUnit( QgsSymbolV2::OutputUnit unit ) { mOffsetAlongLineUnit = unit; }
236+
198237
QgsSymbolV2::OutputUnit intervalUnit() const { return mIntervalUnit; }
199238
void setIntervalUnit( QgsSymbolV2::OutputUnit unit ) { mIntervalUnit = unit; }
200239

@@ -218,6 +257,22 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2
218257
double mOffset;
219258
QgsSymbolV2::OutputUnit mOffsetUnit;
220259
Placement mPlacement;
260+
double mOffsetAlongLine; //distance to offset along line before marker is drawn
261+
QgsSymbolV2::OutputUnit mOffsetAlongLineUnit; //unit for offset along line
262+
263+
private:
264+
265+
/**Renders a marker by offseting a vertex along the line by a specified distance.
266+
* @param points vertices making up the line
267+
* @param vertex vertex number to begin offset at
268+
* @param distance distance to offset from vertex. If distance is positive, offset is calculated
269+
* moving forward along the line. If distance is negative, offset is calculated moving backward
270+
* along the line's vertices.
271+
* @param context render context
272+
* @see setoffsetAlongLine
273+
* @see setOffsetAlongLineUnit
274+
*/
275+
void renderOffsetVertexAlongLine( const QPolygonF& points, int vertex, double distance , QgsSymbolV2RenderContext &context );
221276
};
222277

223278
#endif

src/gui/symbology-ng/qgssymbollayerv2widget.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,7 @@ QgsMarkerLineSymbolLayerV2Widget::QgsMarkerLineSymbolLayerV2Widget( const QgsVec
13111311
setupUi( this );
13121312

13131313
connect( spinInterval, SIGNAL( valueChanged( double ) ), this, SLOT( setInterval( double ) ) );
1314+
connect( mSpinOffsetAlongLine, SIGNAL( valueChanged( double ) ), this, SLOT( setOffsetAlongLine( double ) ) );
13141315
connect( chkRotateMarker, SIGNAL( clicked() ), this, SLOT( setRotate() ) );
13151316
connect( spinOffset, SIGNAL( valueChanged( double ) ), this, SLOT( setOffset() ) );
13161317
connect( radInterval, SIGNAL( clicked() ), this, SLOT( setPlacement() ) );
@@ -1332,6 +1333,9 @@ void QgsMarkerLineSymbolLayerV2Widget::setSymbolLayer( QgsSymbolLayerV2* layer )
13321333
spinInterval->blockSignals( true );
13331334
spinInterval->setValue( mLayer->interval() );
13341335
spinInterval->blockSignals( false );
1336+
mSpinOffsetAlongLine->blockSignals( true );
1337+
mSpinOffsetAlongLine->setValue( mLayer->offsetAlongLine() );
1338+
mSpinOffsetAlongLine->blockSignals( false );
13351339
chkRotateMarker->blockSignals( true );
13361340
chkRotateMarker->setChecked( mLayer->rotateMarker() );
13371341
chkRotateMarker->blockSignals( false );
@@ -1356,6 +1360,9 @@ void QgsMarkerLineSymbolLayerV2Widget::setSymbolLayer( QgsSymbolLayerV2* layer )
13561360
mOffsetUnitComboBox->blockSignals( true );
13571361
mOffsetUnitComboBox->setCurrentIndex( mLayer->offsetUnit() );
13581362
mOffsetUnitComboBox->blockSignals( false );
1363+
mOffsetAlongLineUnitComboBox->blockSignals( true );
1364+
mOffsetAlongLineUnitComboBox->setCurrentIndex( mLayer->offsetAlongLineUnit() );
1365+
mOffsetAlongLineUnitComboBox->blockSignals( false );
13591366

13601367
setPlacement(); // update gui
13611368
}
@@ -1371,6 +1378,12 @@ void QgsMarkerLineSymbolLayerV2Widget::setInterval( double val )
13711378
emit changed();
13721379
}
13731380

1381+
void QgsMarkerLineSymbolLayerV2Widget::setOffsetAlongLine( double val )
1382+
{
1383+
mLayer->setOffsetAlongLine( val );
1384+
emit changed();
1385+
}
1386+
13741387
void QgsMarkerLineSymbolLayerV2Widget::setRotate()
13751388
{
13761389
mLayer->setRotateMarker( chkRotateMarker->isChecked() );
@@ -1387,6 +1400,7 @@ void QgsMarkerLineSymbolLayerV2Widget::setPlacement()
13871400
{
13881401
bool interval = radInterval->isChecked();
13891402
spinInterval->setEnabled( interval );
1403+
mSpinOffsetAlongLine->setEnabled( radInterval->isChecked() || radVertexLast->isChecked() || radVertexFirst->isChecked() );
13901404
//mLayer->setPlacement( interval ? QgsMarkerLineSymbolLayerV2::Interval : QgsMarkerLineSymbolLayerV2::Vertex );
13911405
if ( radInterval->isChecked() )
13921406
mLayer->setPlacement( QgsMarkerLineSymbolLayerV2::Interval );
@@ -1420,6 +1434,15 @@ void QgsMarkerLineSymbolLayerV2Widget::on_mOffsetUnitComboBox_currentIndexChange
14201434
emit changed();
14211435
}
14221436

1437+
void QgsMarkerLineSymbolLayerV2Widget::on_mOffsetAlongLineUnitComboBox_currentIndexChanged( int index )
1438+
{
1439+
if ( mLayer )
1440+
{
1441+
mLayer->setOffsetAlongLineUnit(( QgsSymbolV2::OutputUnit ) index );
1442+
}
1443+
emit changed();
1444+
}
1445+
14231446
void QgsMarkerLineSymbolLayerV2Widget::on_mDataDefinedPropertiesButton_clicked()
14241447
{
14251448
if ( !mLayer )
@@ -1434,6 +1457,8 @@ void QgsMarkerLineSymbolLayerV2Widget::on_mDataDefinedPropertiesButton_clicked()
14341457
QgsDataDefinedSymbolDialog::doubleHelpText() );
14351458
dataDefinedProperties << QgsDataDefinedSymbolDialog::DataDefinedSymbolEntry( "placement", tr( "Placement" ), mLayer->dataDefinedPropertyString( "placement" ),
14361459
tr( "'vertex'|'lastvertex'|'firstvertex'|'centerpoint'" ) );
1460+
dataDefinedProperties << QgsDataDefinedSymbolDialog::DataDefinedSymbolEntry( "offset_along_line", tr( "Offset along line" ), mLayer->dataDefinedPropertyString( "offset_along_line" ),
1461+
QgsDataDefinedSymbolDialog::doubleHelpText() );
14371462
QgsDataDefinedSymbolDialog d( dataDefinedProperties, mVectorLayer );
14381463
if ( d.exec() == QDialog::Accepted )
14391464
{

0 commit comments

Comments
 (0)