Skip to content

Commit

Permalink
[FEATURE] Add option for shifting markers in marker line style a set …
Browse files Browse the repository at this point in the history
…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)
  • Loading branch information
nyalldawson committed May 4, 2014
1 parent 1b674b8 commit 26c6435
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 8 deletions.
39 changes: 39 additions & 0 deletions python/core/symbology-ng/qgslinesymbollayerv2.sip
Expand Up @@ -141,6 +141,45 @@ class QgsMarkerLineSymbolLayerV2 : QgsLineSymbolLayerV2


Placement placement() const; Placement placement() const;
void setPlacement( Placement p ); void setPlacement( Placement p );

/**Returns the offset along the line for the marker placement. For Interval placements, this is the distance
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
* distance between the marker and the start of the line or the end of the line respectively.
* This setting has no effect for Vertex or CentralPoint placements.
* @returns The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit.
* @note added in 2.3
* @see setOffsetAlongLine
* @see offsetAlongLineUnit
* @see placement
*/
double offsetAlongLine() const;

/**Sets the the offset along the line for the marker placement. For Interval placements, this is the distance
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
* distance between the marker and the start of the line or the end of the line respectively.
* This setting has no effect for Vertex or CentralPoint placements.
* @param offsetAlongLine Distance to offset markers along the line. The offset
* unit is set via setOffsetAlongLineUnit.
* @note added in 2.3
* @see offsetAlongLine
* @see setOffsetAlongLineUnit
* @see setPlacement
*/
void setOffsetAlongLine( double offsetAlongLine );

/**Returns the unit used for calculating the offset along line for markers.
* @returns Offset along line unit type.
* @see setOffsetAlongLineUnit
* @see offsetAlongLine
*/
QgsSymbolV2::OutputUnit offsetAlongLineUnit() const;

/**Sets the unit used for calculating the offset along line for markers.
* @param unit Offset along line unit type.
* @see offsetAlongLineUnit
* @see setOffsetAlongLine
*/
void setOffsetAlongLineUnit( QgsSymbolV2::OutputUnit unit );


QgsSymbolV2::OutputUnit intervalUnit() const; QgsSymbolV2::OutputUnit intervalUnit() const;
void setIntervalUnit( QgsSymbolV2::OutputUnit unit ); void setIntervalUnit( QgsSymbolV2::OutputUnit unit );
Expand Down
2 changes: 2 additions & 0 deletions python/gui/symbology-ng/qgssymbollayerv2widget.sip
Expand Up @@ -194,11 +194,13 @@ class QgsMarkerLineSymbolLayerV2Widget : QgsSymbolLayerV2Widget
public slots: public slots:


void setInterval( double val ); void setInterval( double val );
void setOffsetAlongLine( double val );
void setRotate(); void setRotate();
void setOffset(); void setOffset();
void setPlacement(); void setPlacement();
void on_mIntervalUnitComboBox_currentIndexChanged( int index ); void on_mIntervalUnitComboBox_currentIndexChanged( int index );
void on_mOffsetUnitComboBox_currentIndexChanged( int index ); void on_mOffsetUnitComboBox_currentIndexChanged( int index );
void on_mOffsetAlongLineUnitComboBox_currentIndexChanged( int index );
void on_mDataDefinedPropertiesButton_clicked(); void on_mDataDefinedPropertiesButton_clicked();


}; };
Expand Down
109 changes: 108 additions & 1 deletion src/core/symbology-ng/qgslinesymbollayerv2.cpp
Expand Up @@ -561,6 +561,8 @@ QgsMarkerLineSymbolLayerV2::QgsMarkerLineSymbolLayerV2( bool rotateMarker, doubl
mOffset = 0; mOffset = 0;
mOffsetUnit = QgsSymbolV2::MM; mOffsetUnit = QgsSymbolV2::MM;
mPlacement = Interval; mPlacement = Interval;
mOffsetAlongLine = 0;
mOffsetAlongLineUnit = QgsSymbolV2::MM;


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



if ( props.contains( "interval" ) ) if ( props.contains( "interval" ) )
interval = props["interval"].toDouble(); interval = props["interval"].toDouble();
if ( props.contains( "rotate" ) ) if ( props.contains( "rotate" ) )
Expand All @@ -593,6 +596,14 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::create( const QgsStringMap& props
{ {
x->setIntervalUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["interval_unit"] ) ); x->setIntervalUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["interval_unit"] ) );
} }
if ( props.contains( "offset_along_line" ) )
{
x->setOffsetAlongLine( props["offset_along_line"].toDouble() );
}
if ( props.contains( "offset_along_line_unit" ) )
{
x->setOffsetAlongLineUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_along_line_unit"] ) );
}


if ( props.contains( "placement" ) ) if ( props.contains( "placement" ) )
{ {
Expand Down Expand Up @@ -621,6 +632,10 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::create( const QgsStringMap& props
{ {
x->setDataDefinedProperty( "placement", props["placement_expression"] ); x->setDataDefinedProperty( "placement", props["placement_expression"] );
} }
if ( props.contains( "offset_along_line_expression" ) )
{
x->setDataDefinedProperty( "offset_along_line", props["offset_along_line_expression"] );
}


return x; return x;
} }
Expand Down Expand Up @@ -723,7 +738,7 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineInterval( const QPolygonF& points


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


QgsRenderContext& rc = context.renderContext(); QgsRenderContext& rc = context.renderContext();
Expand All @@ -738,8 +753,15 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineInterval( const QPolygonF& points
{ {
interval = 0.1; interval = 0.1;
} }
double offsetAlongLine = mOffsetAlongLine;
QgsExpression* offsetAlongLineExpression = expression( "offset_along_line" );
if ( offsetAlongLineExpression )
{
offsetAlongLine = offsetAlongLineExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
}


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


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


double offsetAlongLine = mOffsetAlongLine;
QgsExpression* offsetAlongLineExpression = expression( "offset_along_line" );
if ( offsetAlongLineExpression )
{
offsetAlongLine = offsetAlongLineExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
}
if ( offsetAlongLine != 0 )
{
//scale offset along line
offsetAlongLine *= QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mOffsetAlongLineUnit );
}

if ( placement == FirstVertex ) if ( placement == FirstVertex )
{ {
i = 0; i = 0;
Expand All @@ -828,6 +862,16 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
isRing = true; isRing = true;
} }


if ( offsetAlongLine > 0 && ( placement == FirstVertex || placement == LastVertex ) )
{
double distance;
distance = placement == FirstVertex ? offsetAlongLine : -offsetAlongLine;
renderOffsetVertexAlongLine( points, i, distance, context );
// restore original rotation
mMarker->setAngle( origAngle );
return;
}

for ( ; i < maxCount; ++i ) for ( ; i < maxCount; ++i )
{ {
if ( isRing && placement == Vertex && i == points.count() - 1 ) if ( isRing && placement == Vertex && i == points.count() - 1 )
Expand Down Expand Up @@ -923,6 +967,65 @@ double QgsMarkerLineSymbolLayerV2::markerAngle( const QPolygonF& points, bool is
return angle; return angle;
} }


void QgsMarkerLineSymbolLayerV2::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolV2RenderContext& context )
{
if ( points.isEmpty() )
return;

QgsRenderContext& rc = context.renderContext();
double origAngle = mMarker->angle();
if ( distance == 0 )
{
// rotate marker (if desired)
if ( mRotateMarker )
{
bool isRing = false;
if ( points.first() == points.last() )
isRing = true;
double angle = markerAngle( points, isRing, vertex );
mMarker->setAngle( origAngle + angle * 180 / M_PI );
}
mMarker->renderPoint( points[vertex], context.feature(), rc, -1, context.selected() );
return;
}

int pointIncrement = distance > 0 ? 1 : -1;
QPointF previousPoint = points[vertex];
int startPoint = distance > 0 ? qMin( vertex + 1, points.count() - 1 ) : qMax( vertex - 1, 0 );
int endPoint = distance > 0 ? points.count() - 1 : 0;
double distanceLeft = qAbs( distance );

for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
{
const QPointF& pt = points[i];

if ( previousPoint == pt ) // must not be equal!
continue;

// create line segment
MyLine l( previousPoint, pt );

if ( distanceLeft < l.length() )
{
//destination point is in current segment
QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
// rotate marker (if desired)
if ( mRotateMarker )
{
mMarker->setAngle( origAngle + ( l.angle() * 180 / M_PI ) );
}
mMarker->renderPoint( markerPoint, context.feature(), rc, -1, context.selected() );
return;
}

distanceLeft -= l.length();
previousPoint = pt;
}

//didn't find point
return;
}

void QgsMarkerLineSymbolLayerV2::renderPolylineCentral( const QPolygonF& points, QgsSymbolV2RenderContext& context ) void QgsMarkerLineSymbolLayerV2::renderPolylineCentral( const QPolygonF& points, QgsSymbolV2RenderContext& context )
{ {
if ( points.size() > 0 ) if ( points.size() > 0 )
Expand Down Expand Up @@ -978,6 +1081,8 @@ QgsStringMap QgsMarkerLineSymbolLayerV2::properties() const
map["rotate"] = ( mRotateMarker ? "1" : "0" ); map["rotate"] = ( mRotateMarker ? "1" : "0" );
map["interval"] = QString::number( mInterval ); map["interval"] = QString::number( mInterval );
map["offset"] = QString::number( mOffset ); map["offset"] = QString::number( mOffset );
map["offset_along_line"] = QString::number( mOffsetAlongLine );
map["offset_along_line_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetAlongLineUnit );
map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetUnit ); map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetUnit );
map["interval_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mIntervalUnit ); map["interval_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mIntervalUnit );
if ( mPlacement == Vertex ) if ( mPlacement == Vertex )
Expand Down Expand Up @@ -1022,6 +1127,8 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::clone() const
x->setPlacement( mPlacement ); x->setPlacement( mPlacement );
x->setOffsetUnit( mOffsetUnit ); x->setOffsetUnit( mOffsetUnit );
x->setIntervalUnit( mIntervalUnit ); x->setIntervalUnit( mIntervalUnit );
x->setOffsetAlongLine( mOffsetAlongLine );
x->setOffsetAlongLineUnit( mOffsetAlongLineUnit );
copyDataDefinedProperties( x ); copyDataDefinedProperties( x );
return x; return x;
} }
Expand Down
55 changes: 55 additions & 0 deletions src/core/symbology-ng/qgslinesymbollayerv2.h
Expand Up @@ -195,6 +195,45 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2
Placement placement() const { return mPlacement; } Placement placement() const { return mPlacement; }
void setPlacement( Placement p ) { mPlacement = p; } void setPlacement( Placement p ) { mPlacement = p; }


/**Returns the offset along the line for the marker placement. For Interval placements, this is the distance
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
* distance between the marker and the start of the line or the end of the line respectively.
* This setting has no effect for Vertex or CentralPoint placements.
* @returns The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit.
* @note added in 2.3
* @see setOffsetAlongLine
* @see offsetAlongLineUnit
* @see placement
*/
double offsetAlongLine() const { return mOffsetAlongLine; }

/**Sets the the offset along the line for the marker placement. For Interval placements, this is the distance
* between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the
* distance between the marker and the start of the line or the end of the line respectively.
* This setting has no effect for Vertex or CentralPoint placements.
* @param offsetAlongLine Distance to offset markers along the line. The offset
* unit is set via setOffsetAlongLineUnit.
* @note added in 2.3
* @see offsetAlongLine
* @see setOffsetAlongLineUnit
* @see setPlacement
*/
void setOffsetAlongLine( double offsetAlongLine ) { mOffsetAlongLine = offsetAlongLine; }

/**Returns the unit used for calculating the offset along line for markers.
* @returns Offset along line unit type.
* @see setOffsetAlongLineUnit
* @see offsetAlongLine
*/
QgsSymbolV2::OutputUnit offsetAlongLineUnit() const { return mOffsetAlongLineUnit; }

/**Sets the unit used for calculating the offset along line for markers.
* @param unit Offset along line unit type.
* @see offsetAlongLineUnit
* @see setOffsetAlongLine
*/
void setOffsetAlongLineUnit( QgsSymbolV2::OutputUnit unit ) { mOffsetAlongLineUnit = unit; }

QgsSymbolV2::OutputUnit intervalUnit() const { return mIntervalUnit; } QgsSymbolV2::OutputUnit intervalUnit() const { return mIntervalUnit; }
void setIntervalUnit( QgsSymbolV2::OutputUnit unit ) { mIntervalUnit = unit; } void setIntervalUnit( QgsSymbolV2::OutputUnit unit ) { mIntervalUnit = unit; }


Expand All @@ -218,6 +257,22 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2
double mOffset; double mOffset;
QgsSymbolV2::OutputUnit mOffsetUnit; QgsSymbolV2::OutputUnit mOffsetUnit;
Placement mPlacement; Placement mPlacement;
double mOffsetAlongLine; //distance to offset along line before marker is drawn
QgsSymbolV2::OutputUnit mOffsetAlongLineUnit; //unit for offset along line

private:

/**Renders a marker by offseting a vertex along the line by a specified distance.
* @param points vertices making up the line
* @param vertex vertex number to begin offset at
* @param distance distance to offset from vertex. If distance is positive, offset is calculated
* moving forward along the line. If distance is negative, offset is calculated moving backward
* along the line's vertices.
* @param context render context
* @see setoffsetAlongLine
* @see setOffsetAlongLineUnit
*/
void renderOffsetVertexAlongLine( const QPolygonF& points, int vertex, double distance , QgsSymbolV2RenderContext &context );
}; };


#endif #endif
25 changes: 25 additions & 0 deletions src/gui/symbology-ng/qgssymbollayerv2widget.cpp
Expand Up @@ -1311,6 +1311,7 @@ QgsMarkerLineSymbolLayerV2Widget::QgsMarkerLineSymbolLayerV2Widget( const QgsVec
setupUi( this ); setupUi( this );


connect( spinInterval, SIGNAL( valueChanged( double ) ), this, SLOT( setInterval( double ) ) ); connect( spinInterval, SIGNAL( valueChanged( double ) ), this, SLOT( setInterval( double ) ) );
connect( mSpinOffsetAlongLine, SIGNAL( valueChanged( double ) ), this, SLOT( setOffsetAlongLine( double ) ) );
connect( chkRotateMarker, SIGNAL( clicked() ), this, SLOT( setRotate() ) ); connect( chkRotateMarker, SIGNAL( clicked() ), this, SLOT( setRotate() ) );
connect( spinOffset, SIGNAL( valueChanged( double ) ), this, SLOT( setOffset() ) ); connect( spinOffset, SIGNAL( valueChanged( double ) ), this, SLOT( setOffset() ) );
connect( radInterval, SIGNAL( clicked() ), this, SLOT( setPlacement() ) ); connect( radInterval, SIGNAL( clicked() ), this, SLOT( setPlacement() ) );
Expand All @@ -1332,6 +1333,9 @@ void QgsMarkerLineSymbolLayerV2Widget::setSymbolLayer( QgsSymbolLayerV2* layer )
spinInterval->blockSignals( true ); spinInterval->blockSignals( true );
spinInterval->setValue( mLayer->interval() ); spinInterval->setValue( mLayer->interval() );
spinInterval->blockSignals( false ); spinInterval->blockSignals( false );
mSpinOffsetAlongLine->blockSignals( true );
mSpinOffsetAlongLine->setValue( mLayer->offsetAlongLine() );
mSpinOffsetAlongLine->blockSignals( false );
chkRotateMarker->blockSignals( true ); chkRotateMarker->blockSignals( true );
chkRotateMarker->setChecked( mLayer->rotateMarker() ); chkRotateMarker->setChecked( mLayer->rotateMarker() );
chkRotateMarker->blockSignals( false ); chkRotateMarker->blockSignals( false );
Expand All @@ -1356,6 +1360,9 @@ void QgsMarkerLineSymbolLayerV2Widget::setSymbolLayer( QgsSymbolLayerV2* layer )
mOffsetUnitComboBox->blockSignals( true ); mOffsetUnitComboBox->blockSignals( true );
mOffsetUnitComboBox->setCurrentIndex( mLayer->offsetUnit() ); mOffsetUnitComboBox->setCurrentIndex( mLayer->offsetUnit() );
mOffsetUnitComboBox->blockSignals( false ); mOffsetUnitComboBox->blockSignals( false );
mOffsetAlongLineUnitComboBox->blockSignals( true );
mOffsetAlongLineUnitComboBox->setCurrentIndex( mLayer->offsetAlongLineUnit() );
mOffsetAlongLineUnitComboBox->blockSignals( false );


setPlacement(); // update gui setPlacement(); // update gui
} }
Expand All @@ -1371,6 +1378,12 @@ void QgsMarkerLineSymbolLayerV2Widget::setInterval( double val )
emit changed(); emit changed();
} }


void QgsMarkerLineSymbolLayerV2Widget::setOffsetAlongLine( double val )
{
mLayer->setOffsetAlongLine( val );
emit changed();
}

void QgsMarkerLineSymbolLayerV2Widget::setRotate() void QgsMarkerLineSymbolLayerV2Widget::setRotate()
{ {
mLayer->setRotateMarker( chkRotateMarker->isChecked() ); mLayer->setRotateMarker( chkRotateMarker->isChecked() );
Expand All @@ -1387,6 +1400,7 @@ void QgsMarkerLineSymbolLayerV2Widget::setPlacement()
{ {
bool interval = radInterval->isChecked(); bool interval = radInterval->isChecked();
spinInterval->setEnabled( interval ); spinInterval->setEnabled( interval );
mSpinOffsetAlongLine->setEnabled( radInterval->isChecked() || radVertexLast->isChecked() || radVertexFirst->isChecked() );
//mLayer->setPlacement( interval ? QgsMarkerLineSymbolLayerV2::Interval : QgsMarkerLineSymbolLayerV2::Vertex ); //mLayer->setPlacement( interval ? QgsMarkerLineSymbolLayerV2::Interval : QgsMarkerLineSymbolLayerV2::Vertex );
if ( radInterval->isChecked() ) if ( radInterval->isChecked() )
mLayer->setPlacement( QgsMarkerLineSymbolLayerV2::Interval ); mLayer->setPlacement( QgsMarkerLineSymbolLayerV2::Interval );
Expand Down Expand Up @@ -1420,6 +1434,15 @@ void QgsMarkerLineSymbolLayerV2Widget::on_mOffsetUnitComboBox_currentIndexChange
emit changed(); emit changed();
} }


void QgsMarkerLineSymbolLayerV2Widget::on_mOffsetAlongLineUnitComboBox_currentIndexChanged( int index )
{
if ( mLayer )
{
mLayer->setOffsetAlongLineUnit(( QgsSymbolV2::OutputUnit ) index );
}
emit changed();
}

void QgsMarkerLineSymbolLayerV2Widget::on_mDataDefinedPropertiesButton_clicked() void QgsMarkerLineSymbolLayerV2Widget::on_mDataDefinedPropertiesButton_clicked()
{ {
if ( !mLayer ) if ( !mLayer )
Expand All @@ -1434,6 +1457,8 @@ void QgsMarkerLineSymbolLayerV2Widget::on_mDataDefinedPropertiesButton_clicked()
QgsDataDefinedSymbolDialog::doubleHelpText() ); QgsDataDefinedSymbolDialog::doubleHelpText() );
dataDefinedProperties << QgsDataDefinedSymbolDialog::DataDefinedSymbolEntry( "placement", tr( "Placement" ), mLayer->dataDefinedPropertyString( "placement" ), dataDefinedProperties << QgsDataDefinedSymbolDialog::DataDefinedSymbolEntry( "placement", tr( "Placement" ), mLayer->dataDefinedPropertyString( "placement" ),
tr( "'vertex'|'lastvertex'|'firstvertex'|'centerpoint'" ) ); tr( "'vertex'|'lastvertex'|'firstvertex'|'centerpoint'" ) );
dataDefinedProperties << QgsDataDefinedSymbolDialog::DataDefinedSymbolEntry( "offset_along_line", tr( "Offset along line" ), mLayer->dataDefinedPropertyString( "offset_along_line" ),
QgsDataDefinedSymbolDialog::doubleHelpText() );
QgsDataDefinedSymbolDialog d( dataDefinedProperties, mVectorLayer ); QgsDataDefinedSymbolDialog d( dataDefinedProperties, mVectorLayer );
if ( d.exec() == QDialog::Accepted ) if ( d.exec() == QDialog::Accepted )
{ {
Expand Down

0 comments on commit 26c6435

Please sign in to comment.