Skip to content
Permalink
Browse files

[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)
  • Loading branch information
nyalldawson committed May 4, 2014
1 parent 1b674b8 commit 26c6435a85b9360ad7f4e38c7878d8d8f38c908b
@@ -141,6 +141,45 @@ class QgsMarkerLineSymbolLayerV2 : QgsLineSymbolLayerV2

Placement placement() const;
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;
void setIntervalUnit( QgsSymbolV2::OutputUnit unit );
@@ -194,11 +194,13 @@ class QgsMarkerLineSymbolLayerV2Widget : QgsSymbolLayerV2Widget
public slots:

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

};
@@ -561,6 +561,8 @@ QgsMarkerLineSymbolLayerV2::QgsMarkerLineSymbolLayerV2( bool rotateMarker, doubl
mOffset = 0;
mOffsetUnit = QgsSymbolV2::MM;
mPlacement = Interval;
mOffsetAlongLine = 0;
mOffsetAlongLineUnit = QgsSymbolV2::MM;

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


if ( props.contains( "interval" ) )
interval = props["interval"].toDouble();
if ( props.contains( "rotate" ) )
@@ -593,6 +596,14 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::create( const QgsStringMap& props
{
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" ) )
{
@@ -621,6 +632,10 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::create( const QgsStringMap& props
{
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;
}
@@ -723,7 +738,7 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineInterval( const QPolygonF& points

QPointF lastPt = points[0];
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();

QgsRenderContext& rc = context.renderContext();
@@ -738,8 +753,15 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineInterval( const QPolygonF& points
{
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 );
lengthLeft = painterUnitInterval - offsetAlongLine * QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mIntervalUnit );

for ( int i = 1; i < points.count(); ++i )
{
@@ -810,6 +832,18 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
int i, maxCount;
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 )
{
i = 0;
@@ -828,6 +862,16 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
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 )
{
if ( isRing && placement == Vertex && i == points.count() - 1 )
@@ -923,6 +967,65 @@ double QgsMarkerLineSymbolLayerV2::markerAngle( const QPolygonF& points, bool is
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 )
{
if ( points.size() > 0 )
@@ -978,6 +1081,8 @@ QgsStringMap QgsMarkerLineSymbolLayerV2::properties() const
map["rotate"] = ( mRotateMarker ? "1" : "0" );
map["interval"] = QString::number( mInterval );
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["interval_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mIntervalUnit );
if ( mPlacement == Vertex )
@@ -1022,6 +1127,8 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::clone() const
x->setPlacement( mPlacement );
x->setOffsetUnit( mOffsetUnit );
x->setIntervalUnit( mIntervalUnit );
x->setOffsetAlongLine( mOffsetAlongLine );
x->setOffsetAlongLineUnit( mOffsetAlongLineUnit );
copyDataDefinedProperties( x );
return x;
}
@@ -195,6 +195,45 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2
Placement placement() const { return mPlacement; }
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; }
void setIntervalUnit( QgsSymbolV2::OutputUnit unit ) { mIntervalUnit = unit; }

@@ -218,6 +257,22 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2
double mOffset;
QgsSymbolV2::OutputUnit mOffsetUnit;
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
@@ -1311,6 +1311,7 @@ QgsMarkerLineSymbolLayerV2Widget::QgsMarkerLineSymbolLayerV2Widget( const QgsVec
setupUi( this );

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

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

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

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

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

void QgsMarkerLineSymbolLayerV2Widget::on_mDataDefinedPropertiesButton_clicked()
{
if ( !mLayer )
@@ -1434,6 +1457,8 @@ void QgsMarkerLineSymbolLayerV2Widget::on_mDataDefinedPropertiesButton_clicked()
QgsDataDefinedSymbolDialog::doubleHelpText() );
dataDefinedProperties << QgsDataDefinedSymbolDialog::DataDefinedSymbolEntry( "placement", tr( "Placement" ), mLayer->dataDefinedPropertyString( "placement" ),
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 );
if ( d.exec() == QDialog::Accepted )
{

0 comments on commit 26c6435

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