Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[feature] Add an option to marker/has line symbol layers to control
whether the first/last vertex option should respect multipart geometries

The default is not to respect these, so first and last vertices
are symbolized for every part of a multi-part geometry (this matches
the old behaviour). By opting in to the "respect multipart geometries"
option, the symbols will only be placed on the VERY first or very LAST
vertex in the whole multi-part geometry.

Sponsored by North Road, thanks to SLYR
  • Loading branch information
nyalldawson committed Nov 12, 2021
1 parent 4ae14dc commit 9cfeb1e
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 227 deletions.
Expand Up @@ -2796,7 +2796,6 @@ Sets whether point markers should be ``clipped`` to the current part boundary on




private:
QgsCentroidFillSymbolLayer( const QgsCentroidFillSymbolLayer &other );
};
Expand Down
39 changes: 39 additions & 0 deletions python/core/auto_generated/symbology/qgslinesymbollayer.sip.in
Expand Up @@ -711,6 +711,40 @@ Sets the ``placement`` of the symbols.

.. seealso:: :py:func:`placements`

.. versionadded:: 3.24
%End

bool respectMultipart() const;
%Docstring
Returns ``True`` if the placement respects multi-part feature geometries.

The default is ``False``, which means that Qgis.MarkerLinePlacement.FirstVertex or
Qgis.MarkerLinePlacement.LastVertex placements will result in a symbol on
the first/last vertex of EVERY part of a multipart feature.

If ``True``, then Qgis.MarkerLinePlacement.FirstVertex or
Qgis.MarkerLinePlacement.LastVertex placements will result in a symbol on
the first/last vertex of the overall multipart geometry only.

.. seealso:: :py:func:`setRespectMultipart`

.. versionadded:: 3.24
%End

void setRespectMultipart( bool respect );
%Docstring
Sets whether the placement respects multi-part feature geometries.

The default is ``False``, which means that Qgis.MarkerLinePlacement.FirstVertex or
Qgis.MarkerLinePlacement.LastVertex placements will result in a symbol on
the first/last vertex of EVERY part of a multipart feature.

If ``True``, then Qgis.MarkerLinePlacement.FirstVertex or
Qgis.MarkerLinePlacement.LastVertex placements will result in a symbol on
the first/last vertex of the overall multipart geometry only.

.. seealso:: :py:func:`respectMultipart`

.. versionadded:: 3.24
%End

Expand Down Expand Up @@ -874,6 +908,11 @@ calculating individual symbol angles.
virtual bool canCauseArtifactsBetweenAdjacentTiles() const;


virtual void startFeatureRender( const QgsFeature &feature, QgsRenderContext &context );

virtual void stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context );


protected:

virtual void setSymbolLineAngle( double angle ) = 0;
Expand Down
3 changes: 0 additions & 3 deletions src/core/symbology/qgsfillsymbollayer.cpp
Expand Up @@ -4397,9 +4397,6 @@ QColor QgsCentroidFillSymbolLayer::color() const
void QgsCentroidFillSymbolLayer::startRender( QgsSymbolRenderContext &context )
{
mMarker->startRender( context.renderContext(), context.fields() );

mCurrentFeatureId = -1;
mBiggestPartIndex = 0;
}

void QgsCentroidFillSymbolLayer::stopRender( QgsSymbolRenderContext &context )
Expand Down
3 changes: 0 additions & 3 deletions src/core/symbology/qgsfillsymbollayer.h
Expand Up @@ -2522,9 +2522,6 @@ class CORE_EXPORT QgsCentroidFillSymbolLayer : public QgsFillSymbolLayer
bool mRenderingFeature = false;
double mFeatureSymbolOpacity = 1;

QgsFeatureId mCurrentFeatureId = -1;
int mBiggestPartIndex = -1;

private:
#ifdef SIP_RUN
QgsCentroidFillSymbolLayer( const QgsCentroidFillSymbolLayer &other );
Expand Down
70 changes: 59 additions & 11 deletions src/core/symbology/qgslinesymbollayer.cpp
Expand Up @@ -1260,6 +1260,14 @@ QgsTemplatedLineSymbolLayerBase::~QgsTemplatedLineSymbolLayerBase() = default;

void QgsTemplatedLineSymbolLayerBase::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
{
if ( mRenderingFeature )
{
// in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
// until after we've received the final part
mFeatureSymbolOpacity = context.opacity();
mCurrentFeatureIsSelected = context.selected();
}

double offset = mOffset;

if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyOffset ) )
Expand Down Expand Up @@ -1333,16 +1341,20 @@ void QgsTemplatedLineSymbolLayerBase::renderPolyline( const QPolygonF &points, Q
renderPolylineCentral( points, context, averageOver );
if ( placements & Qgis::MarkerLinePlacement::Vertex )
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
if ( placements & Qgis::MarkerLinePlacement::FirstVertex
&& ( !mRespectMultipart || !mHasRenderedFirstPart ) )
{
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
mHasRenderedFirstPart = mRenderingFeature;
}
if ( placements & Qgis::MarkerLinePlacement::InnerVertices )
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
if ( placements & Qgis::MarkerLinePlacement::LastVertex )
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
if ( placements & Qgis::MarkerLinePlacement::FirstVertex )
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
if ( placements & Qgis::MarkerLinePlacement::CurvePoint )
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
if ( placements & Qgis::MarkerLinePlacement::SegmentCenter )
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
if ( placements & Qgis::MarkerLinePlacement::LastVertex )
renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
}
else
{
Expand All @@ -1363,8 +1375,12 @@ void QgsTemplatedLineSymbolLayerBase::renderPolyline( const QPolygonF &points, Q
renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
if ( placements & Qgis::MarkerLinePlacement::LastVertex )
renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
if ( placements & Qgis::MarkerLinePlacement::FirstVertex )
if ( placements & Qgis::MarkerLinePlacement::FirstVertex
&& ( !mRespectMultipart || !mHasRenderedFirstPart ) )
{
renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
mHasRenderedFirstPart = mRenderingFeature;
}
if ( placements & Qgis::MarkerLinePlacement::CurvePoint )
renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
if ( placements & Qgis::MarkerLinePlacement::SegmentCenter )
Expand Down Expand Up @@ -1482,16 +1498,39 @@ QVariantMap QgsTemplatedLineSymbolLayerBase::properties() const
map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );

map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
map[QStringLiteral( "respect_multipart" )] = mRespectMultipart;
return map;
}

bool QgsTemplatedLineSymbolLayerBase::canCauseArtifactsBetweenAdjacentTiles() const
{
return ( mPlacements & Qgis::MarkerLinePlacement::Interval )
return mRespectMultipart
|| ( mPlacements & Qgis::MarkerLinePlacement::Interval )
|| ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
|| ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
}

void QgsTemplatedLineSymbolLayerBase::startFeatureRender( const QgsFeature &, QgsRenderContext & )
{
mRenderingFeature = true;
mHasRenderedFirstPart = false;
}

void QgsTemplatedLineSymbolLayerBase::stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context )
{
mRenderingFeature = false;
if ( !mRespectMultipart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
return;

const double prevOpacity = subSymbol()->opacity();
subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );

// render final point
renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
mFeatureSymbolOpacity = 1;
subSymbol()->setOpacity( prevOpacity );
}

void QgsTemplatedLineSymbolLayerBase::copyTemplateSymbolProperties( QgsTemplatedLineSymbolLayerBase *destLayer ) const
{
destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
Expand All @@ -1508,6 +1547,7 @@ void QgsTemplatedLineSymbolLayerBase::copyTemplateSymbolProperties( QgsTemplated
destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
destLayer->setRingFilter( mRingFilter );
destLayer->setRespectMultipart( mRespectMultipart );
copyDataDefinedProperties( destLayer );
copyPaintEffect( destLayer );
}
Expand Down Expand Up @@ -1589,6 +1629,8 @@ void QgsTemplatedLineSymbolLayerBase::setCommonProperties( QgsTemplatedLineSymbo
destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
}

destLayer->setRespectMultipart( properties.value( QStringLiteral( "respect_multipart" ), false ).toBool() );

destLayer->restoreOldDataDefinedProperties( properties );
}

Expand Down Expand Up @@ -1883,7 +1925,7 @@ void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &poi
{
double distance;
distance = placement == Qgis::MarkerLinePlacement::FirstVertex ? offsetAlongLine : -offsetAlongLine;
renderOffsetVertexAlongLine( points, i, distance, context );
renderOffsetVertexAlongLine( points, i, distance, context, placement );

return;
}
Expand Down Expand Up @@ -1927,7 +1969,9 @@ void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &poi
}
}

renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
mFinalVertex = symbolPoint;
if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || !mRespectMultipart || !mRenderingFeature )
renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
}
}

Expand Down Expand Up @@ -2006,7 +2050,7 @@ double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bo
return angle;
}

void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context )
void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
{
if ( points.isEmpty() )
return;
Expand All @@ -2023,7 +2067,9 @@ void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygo
double angle = markerAngle( points, isRing, vertex );
setSymbolLineAngle( angle * 180 / M_PI );
}
renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
mFinalVertex = points[vertex];
if ( placement != Qgis::MarkerLinePlacement::LastVertex || !mRespectMultipart || !mRenderingFeature )
renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
return;
}

Expand Down Expand Up @@ -2052,7 +2098,9 @@ void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygo
{
setSymbolLineAngle( l.angle() * 180 / M_PI );
}
renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
mFinalVertex = markerPoint;
if ( placement != Qgis::MarkerLinePlacement::LastVertex || !mRespectMultipart || !mRenderingFeature )
renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
return;
}

Expand Down
46 changes: 45 additions & 1 deletion src/core/symbology/qgslinesymbollayer.h
Expand Up @@ -651,6 +651,38 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
*/
void setPlacements( Qgis::MarkerLinePlacements placements ) { mPlacements = placements; }

/**
* Returns TRUE if the placement respects multi-part feature geometries.
*
* The default is FALSE, which means that Qgis::MarkerLinePlacement::FirstVertex or
* Qgis::MarkerLinePlacement::LastVertex placements will result in a symbol on
* the first/last vertex of EVERY part of a multipart feature.
*
* If TRUE, then Qgis::MarkerLinePlacement::FirstVertex or
* Qgis::MarkerLinePlacement::LastVertex placements will result in a symbol on
* the first/last vertex of the overall multipart geometry only.
*
* \see setRespectMultipart()
* \since QGIS 3.24
*/
bool respectMultipart() const { return mRespectMultipart; }

/**
* Sets whether the placement respects multi-part feature geometries.
*
* The default is FALSE, which means that Qgis::MarkerLinePlacement::FirstVertex or
* Qgis::MarkerLinePlacement::LastVertex placements will result in a symbol on
* the first/last vertex of EVERY part of a multipart feature.
*
* If TRUE, then Qgis::MarkerLinePlacement::FirstVertex or
* Qgis::MarkerLinePlacement::LastVertex placements will result in a symbol on
* the first/last vertex of the overall multipart geometry only.
*
* \see respectMultipart()
* \since QGIS 3.24
*/
void setRespectMultipart( bool respect ) { mRespectMultipart = respect; }

/**
* Returns the offset along the line for the symbol placement. For Interval placements, this is the distance
* between the start of the line and the first symbol. For FirstVertex and LastVertex placements, this is the
Expand Down Expand Up @@ -776,6 +808,9 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
QVariantMap properties() const override;
bool canCauseArtifactsBetweenAdjacentTiles() const override;

void startFeatureRender( const QgsFeature &feature, QgsRenderContext &context ) override;
void stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context ) override;

protected:

/**
Expand Down Expand Up @@ -836,10 +871,12 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
* moving forward along the line. If distance is negative, offset is calculated moving backward
* along the line's vertices.
* \param context render context
* \param placement marker placement
* \see setoffsetAlongLine
* \see setOffsetAlongLineUnit
*/
void renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context );
void renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context,
Qgis::MarkerLinePlacement placement );


static void collectOffsetPoints( const QVector< QPointF> &points,
Expand All @@ -857,6 +894,13 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
double mAverageAngleLength = 4;
QgsUnitTypes::RenderUnit mAverageAngleLengthUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mAverageAngleLengthMapUnitScale;
bool mRespectMultipart = false;

bool mRenderingFeature = false;
bool mHasRenderedFirstPart = false;
QPointF mFinalVertex;
bool mCurrentFeatureIsSelected = false;
double mFeatureSymbolOpacity = 1;

friend class TestQgsMarkerLineSymbol;

Expand Down
19 changes: 19 additions & 0 deletions src/gui/symbology/qgssymbollayerwidget.cpp
Expand Up @@ -1934,6 +1934,14 @@ QgsMarkerLineSymbolLayerWidget::QgsMarkerLineSymbolLayerWidget( QgsVectorLayer *
connect( mCheckCentralPoint, &QCheckBox::toggled, this, &QgsMarkerLineSymbolLayerWidget::setPlacement );
connect( mCheckCurvePoint, &QCheckBox::toggled, this, &QgsMarkerLineSymbolLayerWidget::setPlacement );
connect( mCheckSegmentCentralPoint, &QCheckBox::toggled, this, &QgsMarkerLineSymbolLayerWidget::setPlacement );
connect( mCheckRespectMultipart, &QCheckBox::toggled, this, [ = ]
{
if ( mLayer )
{
mLayer->setRespectMultipart( mCheckRespectMultipart->isChecked() );
emit changed();
}
} );
}

void QgsMarkerLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
Expand Down Expand Up @@ -1968,6 +1976,7 @@ void QgsMarkerLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
whileBlocking( mCheckCentralPoint )->setChecked( mLayer->placements() & Qgis::MarkerLinePlacement::CentralPoint );
whileBlocking( mCheckCurvePoint )->setChecked( mLayer->placements() & Qgis::MarkerLinePlacement::CurvePoint );
whileBlocking( mCheckSegmentCentralPoint )->setChecked( mLayer->placements() & Qgis::MarkerLinePlacement::SegmentCenter );
whileBlocking( mCheckRespectMultipart )->setChecked( mLayer->respectMultipart() );

// set units
mIntervalUnitWidget->blockSignals( true );
Expand Down Expand Up @@ -2184,6 +2193,15 @@ QgsHashedLineSymbolLayerWidget::QgsHashedLineSymbolLayerWidget( QgsVectorLayer *
connect( mCheckCentralPoint, &QCheckBox::toggled, this, &QgsHashedLineSymbolLayerWidget::setPlacement );
connect( mCheckCurvePoint, &QCheckBox::toggled, this, &QgsHashedLineSymbolLayerWidget::setPlacement );
connect( mCheckSegmentCentralPoint, &QCheckBox::toggled, this, &QgsHashedLineSymbolLayerWidget::setPlacement );

connect( mCheckRespectMultipart, &QCheckBox::toggled, this, [ = ]
{
if ( mLayer )
{
mLayer->setRespectMultipart( mCheckRespectMultipart->isChecked() );
emit changed();
}
} );
}

void QgsHashedLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
Expand Down Expand Up @@ -2220,6 +2238,7 @@ void QgsHashedLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
whileBlocking( mCheckCentralPoint )->setChecked( mLayer->placements() & Qgis::MarkerLinePlacement::CentralPoint );
whileBlocking( mCheckCurvePoint )->setChecked( mLayer->placements() & Qgis::MarkerLinePlacement::CurvePoint );
whileBlocking( mCheckSegmentCentralPoint )->setChecked( mLayer->placements() & Qgis::MarkerLinePlacement::SegmentCenter );
whileBlocking( mCheckRespectMultipart )->setChecked( mLayer->respectMultipart() );

// set units
mIntervalUnitWidget->blockSignals( true );
Expand Down

0 comments on commit 9cfeb1e

Please sign in to comment.