Skip to content

Commit

Permalink
[Symbology][Feature] Allow centroid fill markers to be clipped within…
Browse files Browse the repository at this point in the history
… current polygon or polygon part
  • Loading branch information
suricactus authored and nyalldawson committed May 26, 2020
1 parent 7a5af29 commit 899d3fe
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 35 deletions.
54 changes: 54 additions & 0 deletions python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in
Expand Up @@ -2183,17 +2183,71 @@ Caller takes ownership of the returned symbol layer.
%Docstring
Sets whether a point is drawn for all parts or only on the biggest part of multi-part features.

.. seealso:: :py:func:`pointOnAllParts`

.. versionadded:: 2.16
%End

bool pointOnAllParts() const;
%Docstring
Returns whether a point is drawn for all parts or only on the biggest part of multi-part features.

.. seealso:: :py:func:`setPointOnAllParts`

.. versionadded:: 2.16
%End

bool clipPoints() const;
%Docstring
Returns ``True`` if point markers should be clipped to the polygon boundary.

.. seealso:: :py:func:`setClipPoints`

.. versionadded:: 3.14
%End

void setClipPoints( bool clipPoints );
%Docstring
Sets whether point markers should be ``clipped`` to the polygon boundary.

.. seealso:: :py:func:`clipPoints`

.. versionadded:: 3.14
%End

bool clipOnCurrentPartOnly() const;
%Docstring
Returns ``True`` if point markers should be clipped to the current part boundary only.

.. seealso:: :py:func:`setClipPoints`

.. versionadded:: 3.14
%End

void setClipOnCurrentPartOnly( bool clipOnCurrentPartOnly );
%Docstring
Sets whether point markers should be ``clipped`` to the current part boundary only.

.. seealso:: :py:func:`clipOnCurrentPartOnly`

.. versionadded:: 3.14
%End

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

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


protected:
struct Part
{
QPolygonF exterior;
QList<QPolygonF> rings;
};

void render( QgsRenderContext &context, const QVector<Part> &parts, const QgsFeature &feature, bool selected );




private:
Expand Down
135 changes: 111 additions & 24 deletions src/core/symbology/qgsfillsymbollayer.cpp
Expand Up @@ -3491,6 +3491,10 @@ QgsSymbolLayer *QgsCentroidFillSymbolLayer::create( const QgsStringMap &properti
sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
if ( properties.contains( QStringLiteral( "clip_points" ) ) )
sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );

sl->restoreOldDataDefinedProperties( properties );

Expand Down Expand Up @@ -3529,41 +3533,118 @@ void QgsCentroidFillSymbolLayer::stopRender( QgsSymbolRenderContext &context )

void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
{
if ( !mPointOnAllParts )
Part part;
part.exterior = points;
if ( rings )
part.rings = *rings;

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
mCurrentParts << part;
}
else
{
// not rendering a feature, so we can just render the polygon immediately
render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
}
}

void QgsCentroidFillSymbolLayer::startFeatureRender( const QgsFeature &, QgsRenderContext & )
{
mRenderingFeature = true;
mCurrentParts.clear();
}

void QgsCentroidFillSymbolLayer::stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context )
{
mRenderingFeature = false;
render( context, mCurrentParts, feature, false );
}

void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
{
bool pointOnAllParts = mPointOnAllParts;
bool pointOnSurface = mPointOnSurface;
bool clipPoints = mClipPoints;
bool clipOnCurrentPartOnly = mClipOnCurrentPartOnly;

// TODO add expressions support

QVector< QgsGeometry > geometryParts;
geometryParts.reserve( parts.size() );
QPainterPath globalPath;

int maxArea = 0;
int maxAreaPartIdx = 0;

for ( int i = 0; i < parts.size(); i++ )
{
const QgsFeature *feature = context.feature();
if ( feature )
const Part part = parts[i];
QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );

if ( !geom.isNull() && !part.rings.empty() )
{
if ( feature->id() != mCurrentFeatureId )
QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );

if ( !pointOnAllParts )
{
mCurrentFeatureId = feature->id();
mBiggestPartIndex = 1;
int area = poly->area();

if ( context.geometryPartCount() > 1 )
if ( area > maxArea )
{
const QgsGeometry geom = feature->geometry();
const QgsGeometryCollection *geomCollection = static_cast<const QgsGeometryCollection *>( geom.constGet() );

double area = 0;
double areaBiggest = 0;
for ( int i = 0; i < context.geometryPartCount(); ++i )
{
area = geomCollection->geometryN( i )->area();
if ( area > areaBiggest )
{
areaBiggest = area;
mBiggestPartIndex = i + 1;
}
}
maxArea = area;
maxAreaPartIdx = i;
}
}
}

if ( clipPoints && !clipOnCurrentPartOnly )
{
globalPath.addPolygon( part.exterior );
for ( const QPolygonF &ring : part.rings )
{
globalPath.addPolygon( ring );
}
}
}

if ( mPointOnAllParts || ( context.geometryPartNum() == mBiggestPartIndex ) )
for ( int i = 0; i < parts.size(); i++ )
{
QPointF centroid = mPointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( points, rings ) : QgsSymbolLayerUtils::polygonCentroid( points );
mMarker->renderPoint( centroid, context.feature(), context.renderContext(), -1, context.selected() );
if ( !pointOnAllParts && i != maxAreaPartIdx )
continue;

const Part part = parts[i];

if ( clipPoints )
{
QPainterPath path;

if ( clipOnCurrentPartOnly )
{
path.addPolygon( part.exterior );
for ( const QPolygonF &ring : part.rings )
{
path.addPolygon( ring );
}
}
else
{
path = globalPath;
}

context.painter()->save();
context.painter()->setClipPath( path );
}

QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );

if ( clipPoints )
{
context.painter()->restore();
}
}
}

Expand All @@ -3572,6 +3653,8 @@ QgsStringMap QgsCentroidFillSymbolLayer::properties() const
QgsStringMap map;
map[QStringLiteral( "point_on_surface" )] = QString::number( mPointOnSurface );
map[QStringLiteral( "point_on_all_parts" )] = QString::number( mPointOnAllParts );
map[QStringLiteral( "clip_points" )] = QString::number( mClipPoints );
map[QStringLiteral( "clip_on_current_part_only" )] = QString::number( mClipOnCurrentPartOnly );
return map;
}

Expand All @@ -3583,6 +3666,8 @@ QgsCentroidFillSymbolLayer *QgsCentroidFillSymbolLayer::clone() const
x->setSubSymbol( mMarker->clone() );
x->setPointOnSurface( mPointOnSurface );
x->setPointOnAllParts( mPointOnAllParts );
x->setClipPoints( mClipPoints );
x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
copyDataDefinedProperties( x.get() );
copyPaintEffect( x.get() );
return x.release();
Expand Down Expand Up @@ -4093,6 +4178,8 @@ void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QV

if ( clipPoints )
{
QgsLogger::warning( "down2" + QStringLiteral( __FILE__ ) + ": " + QString::number( __LINE__ ) );
qInfo() << path;
context.painter()->save();
context.painter()->setClipPath( path );
}
Expand Down
51 changes: 51 additions & 0 deletions src/core/symbology/qgsfillsymbollayer.h
Expand Up @@ -1970,18 +1970,68 @@ class CORE_EXPORT QgsCentroidFillSymbolLayer : public QgsFillSymbolLayer

/**
* Sets whether a point is drawn for all parts or only on the biggest part of multi-part features.
* \see pointOnAllParts()
* \since QGIS 2.16 */
void setPointOnAllParts( bool pointOnAllParts ) { mPointOnAllParts = pointOnAllParts; }

/**
* Returns whether a point is drawn for all parts or only on the biggest part of multi-part features.
* \see setPointOnAllParts()
* \since QGIS 2.16 */
bool pointOnAllParts() const { return mPointOnAllParts; }

/**
* Returns TRUE if point markers should be clipped to the polygon boundary.
*
* \see setClipPoints()
* \since 3.14
*/
bool clipPoints() const { return mClipPoints; }

/**
* Sets whether point markers should be \a clipped to the polygon boundary.
*
* \see clipPoints()
* \since 3.14
*/
void setClipPoints( bool clipPoints ) { mClipPoints = clipPoints; }

/**
* Returns TRUE if point markers should be clipped to the current part boundary only.
*
* \see setClipPoints()
* \since 3.14
*/
bool clipOnCurrentPartOnly() const { return mClipOnCurrentPartOnly; }

/**
* Sets whether point markers should be \a clipped to the current part boundary only.
*
* \see clipOnCurrentPartOnly()
* \since 3.14
*/
void setClipOnCurrentPartOnly( bool clipOnCurrentPartOnly ) { mClipOnCurrentPartOnly = clipOnCurrentPartOnly; }

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

protected:
struct Part
{
QPolygonF exterior;
QList<QPolygonF> rings;
};

void render( QgsRenderContext &context, const QVector<Part> &parts, const QgsFeature &feature, bool selected );

std::unique_ptr< QgsMarkerSymbol > mMarker;
bool mPointOnSurface = false;
bool mPointOnAllParts = true;
bool mClipPoints = false;
bool mClipOnCurrentPartOnly = false;

QVector<Part> mCurrentParts;
bool mRenderingFeature = false;

QgsFeatureId mCurrentFeatureId = -1;
int mBiggestPartIndex = -1;
Expand All @@ -1990,6 +2040,7 @@ class CORE_EXPORT QgsCentroidFillSymbolLayer : public QgsFillSymbolLayer
#ifdef SIP_RUN
QgsCentroidFillSymbolLayer( const QgsCentroidFillSymbolLayer &other );
#endif

};

#endif
Expand Down
16 changes: 16 additions & 0 deletions src/gui/symbology/qgssymbollayerwidget.cpp
Expand Up @@ -3635,6 +3635,8 @@ QgsCentroidFillSymbolLayerWidget::QgsCentroidFillSymbolLayerWidget( QgsVectorLay
setupUi( this );
connect( mDrawInsideCheckBox, &QCheckBox::stateChanged, this, &QgsCentroidFillSymbolLayerWidget::mDrawInsideCheckBox_stateChanged );
connect( mDrawAllPartsCheckBox, &QCheckBox::stateChanged, this, &QgsCentroidFillSymbolLayerWidget::mDrawAllPartsCheckBox_stateChanged );
connect( mClipPointsCheckBox, &QCheckBox::stateChanged, this, &QgsCentroidFillSymbolLayerWidget::mClipPointsCheckBox_stateChanged );
connect( mClipOnCurrentPartOnlyCheckBox, &QCheckBox::stateChanged, this, &QgsCentroidFillSymbolLayerWidget::mClipOnCurrentPartOnlyCheckBox_stateChanged );
}

void QgsCentroidFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
Expand All @@ -3648,6 +3650,8 @@ void QgsCentroidFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
// set values
whileBlocking( mDrawInsideCheckBox )->setChecked( mLayer->pointOnSurface() );
whileBlocking( mDrawAllPartsCheckBox )->setChecked( mLayer->pointOnAllParts() );
whileBlocking( mClipPointsCheckBox )->setChecked( mLayer->clipPoints() );
whileBlocking( mClipOnCurrentPartOnlyCheckBox )->setChecked( mLayer->clipOnCurrentPartOnly() );
}

QgsSymbolLayer *QgsCentroidFillSymbolLayerWidget::symbolLayer()
Expand All @@ -3667,6 +3671,18 @@ void QgsCentroidFillSymbolLayerWidget::mDrawAllPartsCheckBox_stateChanged( int s
emit changed();
}

void QgsCentroidFillSymbolLayerWidget::mClipPointsCheckBox_stateChanged( int state )
{
mLayer->setClipPoints( state == Qt::Checked );
emit changed();
}

void QgsCentroidFillSymbolLayerWidget::mClipOnCurrentPartOnlyCheckBox_stateChanged( int state )
{
mLayer->setClipOnCurrentPartOnly( state == Qt::Checked );
emit changed();
}

///////////

QgsRasterMarkerSymbolLayerWidget::QgsRasterMarkerSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent )
Expand Down
3 changes: 2 additions & 1 deletion src/gui/symbology/qgssymbollayerwidget.h
Expand Up @@ -1073,7 +1073,8 @@ class GUI_EXPORT QgsCentroidFillSymbolLayerWidget : public QgsSymbolLayerWidget,
private slots:
void mDrawInsideCheckBox_stateChanged( int state );
void mDrawAllPartsCheckBox_stateChanged( int state );

void mClipPointsCheckBox_stateChanged( int state );
void mClipOnCurrentPartOnlyCheckBox_stateChanged( int state );
};


Expand Down

0 comments on commit 899d3fe

Please sign in to comment.