Skip to content
Permalink
Browse files
[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 9cfeb1efe6b808a6eae5753ebd3442cc9eb5d988
@@ -2796,7 +2796,6 @@ Sets whether point markers should be ``clipped`` to the current part boundary on




private:
QgsCentroidFillSymbolLayer( const QgsCentroidFillSymbolLayer &other );
};
@@ -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

@@ -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;
@@ -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 )
@@ -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 );
@@ -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 ) )
@@ -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
{
@@ -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 )
@@ -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() );
@@ -1508,6 +1547,7 @@ void QgsTemplatedLineSymbolLayerBase::copyTemplateSymbolProperties( QgsTemplated
destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
destLayer->setRingFilter( mRingFilter );
destLayer->setRespectMultipart( mRespectMultipart );
copyDataDefinedProperties( destLayer );
copyPaintEffect( destLayer );
}
@@ -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 );
}

@@ -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;
}
@@ -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() );
}
}

@@ -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;
@@ -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;
}

@@ -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;
}

@@ -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
@@ -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:

/**
@@ -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,
@@ -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;

@@ -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 )
@@ -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 );
@@ -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 )
@@ -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 );
Loading

0 comments on commit 9cfeb1e

Please sign in to comment.