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:
parent
794a8efc81
commit
ec99bd6240
python
src
core/callouts
gui
ui/callouts
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user