1
0
mirror of https://github.com/qgis/QGIS.git synced 2025-04-17 00:04:02 -04:00

[FEATURE][callouts] Add anchor point position settings for polygon features

This commit is contained in:
nirvn 2019-07-30 15:21:06 +07:00 committed by Mathieu Pellerin
parent 794a8efc81
commit ec99bd6240
8 changed files with 227 additions and 2 deletions

@ -47,6 +47,7 @@ relevant symbology elements to render them.
OffsetFromAnchor,
OffsetFromLabel,
DrawCalloutToAllParts,
AnchorPointPosition,
};
enum DrawOrder
@ -55,6 +56,14 @@ relevant symbology elements to render them.
OrderBelowIndividualLabels,
};
enum AnchorPoint
{
PoleOfInaccessibility,
PointOnExterior,
PointOnSurface,
Centroid,
};
QgsCallout();
%Docstring
Constructor for QgsCallout.
@ -225,6 +234,42 @@ Any existing properties will be discarded.
static QgsPropertiesDefinition propertyDefinitions();
%Docstring
Returns the definitions for data defined properties available for use in callouts.
%End
AnchorPoint anchorPoint() const;
%Docstring
Returns the feature's anchor point position.
.. seealso:: :py:func:`setAnchorPoint`
%End
void setAnchorPoint( AnchorPoint anchor );
%Docstring
Sets the feature's ``anchor`` point position.
.. seealso:: :py:func:`drawCalloutToAllParts`
%End
static QString encodeAnchorPoint( AnchorPoint anchor );
%Docstring
Encodes an ``anchor`` point to its string representation.
:return: encoded string
.. seealso:: :py:func:`decodeAnchorPoint`
%End
static QgsCallout::AnchorPoint decodeAnchorPoint( const QString &name, bool *ok = 0 );
%Docstring
Attempts to decode a string representation of an anchoir point name to the corresponding
anchor point.
:param name: encoded anchoir point name
:param ok: if specified, will be set to ``True`` if the anchoir point was successfully decoded
:return: decoded name
.. seealso:: :py:func:`encodeAnchorPoint`
%End
protected:

@ -71,6 +71,8 @@ Returns the vector layer associated with the widget.
.. versionadded:: 2.12
%End
virtual void setGeometryType( QgsWkbTypes::GeometryType type ) = 0;
protected:
void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsCallout::Property key );

@ -38,6 +38,7 @@ void QgsCallout::initPropertyDefinitions()
{ QgsCallout::OffsetFromAnchor, QgsPropertyDefinition( "OffsetFromAnchor", QObject::tr( "Offset from feature" ), QgsPropertyDefinition::DoublePositive, origin ) },
{ QgsCallout::OffsetFromLabel, QgsPropertyDefinition( "OffsetFromLabel", QObject::tr( "Offset from label" ), QgsPropertyDefinition::DoublePositive, origin ) },
{ QgsCallout::DrawCalloutToAllParts, QgsPropertyDefinition( "DrawCalloutToAllParts", QObject::tr( "Draw lines to all feature parts" ), QgsPropertyDefinition::Boolean, origin ) },
{ QgsCallout::AnchorPointPosition, QgsPropertyDefinition( "AnchorPointPosition", QObject::tr( "Feature's anchor point position" ), QgsPropertyDefinition::String, origin ) },
};
}
@ -50,6 +51,7 @@ QVariantMap QgsCallout::properties( const QgsReadWriteContext & ) const
{
QVariantMap props;
props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
props.insert( QStringLiteral( "anchorPoint" ), encodeAnchorPoint( mAnchorPoint ) );
props.insert( QStringLiteral( "ddProperties" ), mDataDefinedProperties.toVariant( propertyDefinitions() ) );
return props;
}
@ -57,6 +59,7 @@ QVariantMap QgsCallout::properties( const QgsReadWriteContext & ) const
void QgsCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext & )
{
mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
mAnchorPoint = decodeAnchorPoint( props.value( QStringLiteral( "anchorPoint" ), QString( "" ) ).toString() );
mDataDefinedProperties.loadVariant( props.value( QStringLiteral( "ddProperties" ) ), propertyDefinitions() );
}
@ -71,6 +74,7 @@ bool QgsCallout::saveProperties( QDomDocument &doc, QDomElement &element, const
QDomElement calloutElement = doc.createElement( QStringLiteral( "callout" ) );
calloutElement.setAttribute( QStringLiteral( "type" ), type() );
calloutElement.setAttribute( QStringLiteral( "anchorPoint" ), encodeAnchorPoint( mAnchorPoint ) );
calloutElement.appendChild( calloutPropsElement );
element.appendChild( calloutElement );
@ -145,6 +149,41 @@ QgsPropertiesDefinition QgsCallout::propertyDefinitions()
return sPropertyDefinitions;
}
QgsCallout::AnchorPoint QgsCallout::decodeAnchorPoint( const QString &name, bool *ok )
{
if ( ok )
*ok = true;
QString cleaned = name.toLower().trimmed();
if ( cleaned == QLatin1String( "pole_of_inaccessibility" ) )
return PoleOfInaccessibility;
else if ( cleaned == QLatin1String( "point_on_exterior" ) )
return PointOnExterior;
else if ( cleaned == QLatin1String( "point_on_surface" ) )
return PointOnSurface;
else if ( cleaned == QLatin1String( "centroid" ) )
return Centroid;
if ( ok )
*ok = false;
return PoleOfInaccessibility;
}
QString QgsCallout::encodeAnchorPoint( AnchorPoint anchor )
{
switch ( anchor )
{
case PoleOfInaccessibility:
return QStringLiteral( "pole_of_inaccessibility" );
case PointOnExterior:
return QStringLiteral( "point_on_exterior" );
case PointOnSurface:
return QStringLiteral( "point_on_surface" );
case Centroid:
return QStringLiteral( "centroid" );
}
return QString();
}
//
// QgsSimpleLineCallout
@ -280,6 +319,13 @@ void QgsSimpleLineCallout::draw( QgsRenderContext &context, QRectF rect, const d
auto drawCalloutLine = [this, &context, &label]( const QgsGeometry & partAnchor )
{
QgsGeometry line;
AnchorPoint anchor = anchorPoint();
if ( dataDefinedProperties().isActive( QgsCallout::AnchorPointPosition ) )
{
QString encodedAnchor = encodeAnchorPoint( anchor );
context.expressionContext().setOriginalValueVariable( encodedAnchor );
anchor = decodeAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::AnchorPointPosition, context.expressionContext(), encodedAnchor ) );
}
switch ( partAnchor.type() )
{
case QgsWkbTypes::PointGeometry:
@ -294,7 +340,22 @@ void QgsSimpleLineCallout::draw( QgsRenderContext &context, QRectF rect, const d
if ( label.intersects( partAnchor ) )
return;
line = label.shortestLine( partAnchor.poleOfInaccessibility( std::max( partAnchor.boundingBox().width(), partAnchor.boundingBox().height() ) / 20.0 ) ); // really rough (but quick) pole of inaccessibility
switch ( anchor )
{
case QgsCallout::PoleOfInaccessibility:
line = label.shortestLine( partAnchor.poleOfInaccessibility( std::max( partAnchor.boundingBox().width(), partAnchor.boundingBox().height() ) / 20.0 ) ); // really rough (but quick) pole of inaccessibility
break;
case QgsCallout::PointOnSurface:
line = label.shortestLine( partAnchor.pointOnSurface() );
break;
case QgsCallout::PointOnExterior:
line = label.shortestLine( partAnchor );
break;
case QgsCallout::Centroid:
default:
line = label.shortestLine( partAnchor.centroid() );
break;
}
break;
case QgsWkbTypes::NullGeometry:
@ -398,6 +459,13 @@ void QgsManhattanLineCallout::draw( QgsRenderContext &context, QRectF rect, cons
auto drawCalloutLine = [this, &context, &label]( const QgsGeometry & partAnchor )
{
QgsGeometry line;
AnchorPoint anchor = anchorPoint();
if ( dataDefinedProperties().isActive( QgsCallout::AnchorPointPosition ) )
{
QString encodedAnchor = encodeAnchorPoint( anchor );
context.expressionContext().setOriginalValueVariable( encodedAnchor );
anchor = decodeAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::AnchorPointPosition, context.expressionContext(), encodedAnchor ) );
}
switch ( partAnchor.type() )
{
case QgsWkbTypes::PointGeometry:
@ -412,7 +480,22 @@ void QgsManhattanLineCallout::draw( QgsRenderContext &context, QRectF rect, cons
if ( label.intersects( partAnchor ) )
return;
line = label.shortestLine( partAnchor.poleOfInaccessibility( std::max( partAnchor.boundingBox().width(), partAnchor.boundingBox().height() ) / 20.0 ) ); // really rough (but quick) pole of inaccessibility
switch ( anchor )
{
case QgsCallout::PoleOfInaccessibility:
line = label.shortestLine( partAnchor.poleOfInaccessibility( std::max( partAnchor.boundingBox().width(), partAnchor.boundingBox().height() ) / 20.0 ) ); // really rough (but quick) pole of inaccessibility
break;
case QgsCallout::PointOnSurface:
line = label.shortestLine( partAnchor.pointOnSurface() );
break;
case QgsCallout::PointOnExterior:
line = label.shortestLine( partAnchor );
break;
case QgsCallout::Centroid:
default:
line = label.shortestLine( partAnchor.centroid() );
break;
}
break;
case QgsWkbTypes::NullGeometry:

@ -72,6 +72,7 @@ class CORE_EXPORT QgsCallout
OffsetFromAnchor, //!< Distance to offset lines from anchor points
OffsetFromLabel, //!< Distance to offset lines from label area
DrawCalloutToAllParts, //!< Whether callout lines should be drawn to all feature parts
AnchorPointPosition, //!< Feature's anchor point position
};
//! Options for draw order (stacking) of callouts
@ -81,6 +82,15 @@ class CORE_EXPORT QgsCallout
OrderBelowIndividualLabels, //!< Render callouts below their individual associated labels, some callouts may be drawn over other labels
};
//! Feature's anchor point position
enum AnchorPoint
{
PoleOfInaccessibility = 0, //!< The surface's pole of inaccessibility used as anchor
PointOnExterior, //!< A point on the surface's outline closest to the label is used as anchor
PointOnSurface, //!< A point guaranteed to be on the surface is used as anchor
Centroid, //!< The surface's centroid is used as anchor
};
/**
* Constructor for QgsCallout.
*/
@ -252,6 +262,37 @@ class CORE_EXPORT QgsCallout
*/
static QgsPropertiesDefinition propertyDefinitions();
/**
* Returns the feature's anchor point position.
*
* \see setAnchorPoint()
*/
AnchorPoint anchorPoint() const { return mAnchorPoint; }
/**
* Sets the feature's \a anchor point position.
*
* \see drawCalloutToAllParts()
*/
void setAnchorPoint( AnchorPoint anchor ) { mAnchorPoint = anchor; }
/**
* Encodes an \a anchor point to its string representation.
* \returns encoded string
* \see decodeAnchorPoint()
*/
static QString encodeAnchorPoint( AnchorPoint anchor );
/**
* Attempts to decode a string representation of an anchoir point name to the corresponding
* anchor point.
* \param name encoded anchoir point name
* \param ok if specified, will be set to TRUE if the anchoir point was successfully decoded
* \returns decoded name
* \see encodeAnchorPoint()
*/
static QgsCallout::AnchorPoint decodeAnchorPoint( const QString &name, bool *ok = nullptr );
protected:
/**
@ -278,6 +319,8 @@ class CORE_EXPORT QgsCallout
bool mEnabled = false;
AnchorPoint mAnchorPoint = PoleOfInaccessibility;
//! Property collection for data defined callout settings
QgsPropertyCollection mDataDefinedProperties;

@ -160,6 +160,13 @@ QgsSimpleLineCalloutWidget::QgsSimpleLineCalloutWidget( QgsVectorLayer *vl, QWid
connect( mDrawToAllPartsCheck, &QCheckBox::toggled, this, &QgsSimpleLineCalloutWidget::drawToAllPartsToggled );
// Anchor point options
mAnchorPointComboBox->addItem( tr( "Pole of inaccessibility" ), static_cast< int >( QgsCallout::PoleOfInaccessibility ) );
mAnchorPointComboBox->addItem( tr( "Point on exterior" ), static_cast< int >( QgsCallout::PointOnExterior ) );
mAnchorPointComboBox->addItem( tr( "Point on surface" ), static_cast< int >( QgsCallout::PointOnSurface ) );
mAnchorPointComboBox->addItem( tr( "Centroid" ), static_cast< int >( QgsCallout::Centroid ) );
connect( mAnchorPointComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsSimpleLineCalloutWidget::mAnchorPointComboBox_currentIndexChanged );
connect( mCalloutLineStyleButton, &QgsSymbolButton::changed, this, &QgsSimpleLineCalloutWidget::lineSymbolChanged );
}
@ -194,10 +201,19 @@ void QgsSimpleLineCalloutWidget::setCallout( QgsCallout *callout )
whileBlocking( mDrawToAllPartsCheck )->setChecked( mCallout->drawCalloutToAllParts() );
whileBlocking( mAnchorPointComboBox )->setCurrentIndex( mAnchorPointComboBox->findData( static_cast< int >( callout->anchorPoint() ) ) );
registerDataDefinedButton( mMinCalloutLengthDDBtn, QgsCallout::MinimumCalloutLength );
registerDataDefinedButton( mOffsetFromAnchorDDBtn, QgsCallout::OffsetFromAnchor );
registerDataDefinedButton( mOffsetFromLabelDDBtn, QgsCallout::OffsetFromLabel );
registerDataDefinedButton( mDrawToAllPartsDDBtn, QgsCallout::DrawCalloutToAllParts );
registerDataDefinedButton( mAnchorPointDDBtn, QgsCallout::AnchorPointPosition );
}
void QgsSimpleLineCalloutWidget::setGeometryType( QgsWkbTypes::GeometryType type )
{
mAnchorPointComboBox->setEnabled( type == QgsWkbTypes::PolygonGeometry );
mAnchorPointDDBtn->setEnabled( type == QgsWkbTypes::PolygonGeometry );
}
QgsCallout *QgsSimpleLineCalloutWidget::callout()
@ -250,6 +266,12 @@ void QgsSimpleLineCalloutWidget::lineSymbolChanged()
emit changed();
}
void QgsSimpleLineCalloutWidget::mAnchorPointComboBox_currentIndexChanged( int index )
{
mCallout->setAnchorPoint( static_cast<QgsCallout::AnchorPoint>( mAnchorPointComboBox->itemData( index ).toInt() ) );
emit changed();
}
void QgsSimpleLineCalloutWidget::drawToAllPartsToggled( bool active )
{
mCallout->setDrawCalloutToAllParts( active );

@ -81,6 +81,8 @@ class GUI_EXPORT QgsCalloutWidget : public QWidget, protected QgsExpressionConte
*/
const QgsVectorLayer *vectorLayer() const { return mVectorLayer; }
virtual void setGeometryType( QgsWkbTypes::GeometryType type ) = 0;
protected:
/**
@ -135,8 +137,11 @@ class GUI_EXPORT QgsSimpleLineCalloutWidget : public QgsCalloutWidget, private U
static QgsCalloutWidget *create( QgsVectorLayer *vl ) SIP_FACTORY { return new QgsSimpleLineCalloutWidget( vl ); }
void setCallout( QgsCallout *callout ) override;
QgsCallout *callout() override;
void setGeometryType( QgsWkbTypes::GeometryType type ) override;
private slots:
void minimumLengthChanged();
@ -146,6 +151,7 @@ class GUI_EXPORT QgsSimpleLineCalloutWidget : public QgsCalloutWidget, private U
void offsetFromLabelUnitWidgetChanged();
void offsetFromLabelChanged();
void lineSymbolChanged();
void mAnchorPointComboBox_currentIndexChanged( int index );
void drawToAllPartsToggled( bool active );
private:

@ -103,6 +103,13 @@ void QgsLabelingGui::updateCalloutWidget( QgsCallout *callout )
{
if ( QgsCalloutWidget *w = am->createCalloutWidget( mLayer ) )
{
QgsWkbTypes::GeometryType geometryType = mGeomType;
if ( mGeometryGeneratorGroupBox->isChecked() )
geometryType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
else if ( mLayer )
geometryType = mLayer->geometryType();
w->setGeometryType( geometryType );
w->setCallout( callout );
w->setContext( context() );

@ -224,6 +224,23 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_48">
<property name="text">
<string>Anchor point</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QComboBox" name="mAnchorPointComboBox"></widget>
</item>
<item row="5" column="3">
<widget class="QgsPropertyOverrideButton" name="mAnchorPointDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</item>
<item>