Default to a "Follow placement" mode for the text anchor point, which

means when the line anchor is < 25% then it will be matched to the
start of the text, when the line anchor is > 75% it will be matched
to the end of the text, otherwise match to center of text
This commit is contained in:
Nyall Dawson 2022-03-15 11:43:02 +10:00
parent 3924437947
commit 94f198b36b
9 changed files with 63 additions and 13 deletions

View File

@ -22,6 +22,7 @@ QgsLabelLineSettings.AnchorClipping.baseClass = QgsLabelLineSettings
QgsLabelLineSettings.AnchorTextPoint.StartOfText.__doc__ = "Anchor using start of text"
QgsLabelLineSettings.AnchorTextPoint.CenterOfText.__doc__ = "Anchor using center of text"
QgsLabelLineSettings.AnchorTextPoint.EndOfText.__doc__ = "Anchor using end of text"
QgsLabelLineSettings.AnchorTextPoint.FollowPlacement.__doc__ = "Automatically set the anchor point based on the lineAnchorPercent() value. Values <25% will use the start of text, values > 75% will use the end of text, and values in between will use the center of the text"
QgsLabelLineSettings.AnchorTextPoint.__doc__ = 'Anchor point of label text.\n\n.. versionadded:: 3.26\n\n' + '* ``StartOfText``: ' + QgsLabelLineSettings.AnchorTextPoint.StartOfText.__doc__ + '\n' + '* ``CenterOfText``: ' + QgsLabelLineSettings.AnchorTextPoint.CenterOfText.__doc__ + '\n' + '* ``EndOfText``: ' + QgsLabelLineSettings.AnchorTextPoint.EndOfText.__doc__ + '\n' + '* ``FollowPlacement``: ' + QgsLabelLineSettings.AnchorTextPoint.FollowPlacement.__doc__
# --
QgsLabelLineSettings.AnchorTextPoint.baseClass = QgsLabelLineSettings

View File

@ -52,6 +52,7 @@ a "perimeter" style mode).
StartOfText,
CenterOfText,
EndOfText,
FollowPlacement,
};
QgsLabeling::LinePlacementFlags placementFlags() const;

View File

@ -98,6 +98,23 @@ void QgsLabelFeature::setOverrunSmoothDistance( double overrunSmoothDistance )
mOverrunSmoothDistance = overrunSmoothDistance;
}
QgsLabelLineSettings::AnchorTextPoint QgsLabelFeature::lineAnchorTextPoint() const
{
if ( mAnchorTextPoint == QgsLabelLineSettings::AnchorTextPoint::FollowPlacement )
{
if ( mLineAnchorPercent < 0.25 )
return QgsLabelLineSettings::AnchorTextPoint::StartOfText;
else if ( mLineAnchorPercent > 0.75 )
return QgsLabelLineSettings::AnchorTextPoint::EndOfText;
else
return QgsLabelLineSettings::AnchorTextPoint::CenterOfText;
}
else
{
return mAnchorTextPoint;
}
}
const QgsLabelObstacleSettings &QgsLabelFeature::obstacleSettings() const
{
return mObstacleSettings;

View File

@ -478,7 +478,7 @@ class CORE_EXPORT QgsLabelFeature
*
* \since QGIS 3.26
*/
QgsLabelLineSettings::AnchorTextPoint lineAnchorTextPoint() const { return mAnchorTextPoint; }
QgsLabelLineSettings::AnchorTextPoint lineAnchorTextPoint() const;
/**
* Sets the line anchor text \a point, which dictates which part of the label text

View File

@ -76,7 +76,9 @@ void QgsLabelLineSettings::updateDataDefinedProperties( const QgsPropertyCollect
const QString value = properties.valueAsString( QgsPalLayerSettings::LineAnchorTextPoint, context, QString(), &ok ).trimmed();
if ( ok )
{
if ( value.compare( QLatin1String( "start" ), Qt::CaseInsensitive ) == 0 )
if ( value.compare( QLatin1String( "follow" ), Qt::CaseInsensitive ) == 0 )
mAnchorTextPoint = AnchorTextPoint::FollowPlacement;
else if ( value.compare( QLatin1String( "start" ), Qt::CaseInsensitive ) == 0 )
mAnchorTextPoint = AnchorTextPoint::StartOfText;
else if ( value.compare( QLatin1String( "center" ), Qt::CaseInsensitive ) == 0 )
mAnchorTextPoint = AnchorTextPoint::CenterOfText;

View File

@ -85,6 +85,7 @@ class CORE_EXPORT QgsLabelLineSettings
StartOfText, //!< Anchor using start of text
CenterOfText, //!< Anchor using center of text
EndOfText, //!< Anchor using end of text
FollowPlacement, //!< Automatically set the anchor point based on the lineAnchorPercent() value. Values <25% will use the start of text, values > 75% will use the end of text, and values in between will use the center of the text
};
Q_ENUM( AnchorTextPoint )
@ -379,7 +380,7 @@ class CORE_EXPORT QgsLabelLineSettings
double mLineAnchorPercent = 0.5;
AnchorType mAnchorType = AnchorType::HintOnly;
AnchorClipping mAnchorClipping = AnchorClipping::UseVisiblePartsOfLine;
AnchorTextPoint mAnchorTextPoint = AnchorTextPoint::CenterOfText;
AnchorTextPoint mAnchorTextPoint = AnchorTextPoint::FollowPlacement;
};
#endif // QGSLABELLINESETTINGS_H

View File

@ -227,7 +227,7 @@ void QgsPalLayerSettings::initPropertyDefinitions()
{ QgsPalLayerSettings::LineAnchorPercent, QgsPropertyDefinition( "LineAnchorPercent", QObject::tr( "Line anchor percentage, as fraction from 0.0 to 1.0" ), QgsPropertyDefinition::Double0To1, origin ) },
{ QgsPalLayerSettings::LineAnchorClipping, QgsPropertyDefinition( "LineAnchorClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor clipping mode" ), QObject::tr( "string " ) + QStringLiteral( "[<b>visible</b>|<b>entire</b>]" ), origin ) },
{ QgsPalLayerSettings::LineAnchorType, QgsPropertyDefinition( "LineAnchorType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor type" ), QObject::tr( "string " ) + QStringLiteral( "[<b>hint</b>|<b>strict</b>]" ), origin ) },
{ QgsPalLayerSettings::LineAnchorTextPoint, QgsPropertyDefinition( "LineAnchorTextPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor text point" ), QObject::tr( "string " ) + QStringLiteral( "[<b>start</b>|<b>center</b>|<b>end</b>]" ), origin ) },
{ QgsPalLayerSettings::LineAnchorTextPoint, QgsPropertyDefinition( "LineAnchorTextPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor text point" ), QObject::tr( "string " ) + QStringLiteral( "[<b>follow</b>|<b>start</b>|<b>center</b>|<b>end</b>]" ), origin ) },
{ QgsPalLayerSettings::Priority, QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
{ QgsPalLayerSettings::IsObstacle, QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean, origin ) },
{ QgsPalLayerSettings::ObstacleFactor, QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ), origin ) },

View File

@ -806,6 +806,8 @@ std::size_t FeaturePart::createHorizontalCandidatesAlongLine( std::vector<std::u
break;
}
const QgsLabelLineSettings::AnchorTextPoint textPoint = mLF->lineAnchorTextPoint();
double candidateCenterX, candidateCenterY;
int i = 0;
while ( currentDistanceAlongLine <= totalLineLength )
@ -822,7 +824,7 @@ std::size_t FeaturePart::createHorizontalCandidatesAlongLine( std::vector<std::u
cost /= 1000; // < 0, 0.0005 >
double labelX = 0;
switch ( mLF->lineAnchorTextPoint() )
switch ( textPoint )
{
case QgsLabelLineSettings::AnchorTextPoint::StartOfText:
labelX = candidateCenterX;
@ -833,6 +835,9 @@ std::size_t FeaturePart::createHorizontalCandidatesAlongLine( std::vector<std::u
case QgsLabelLineSettings::AnchorTextPoint::EndOfText:
labelX = candidateCenterX - labelWidth;
break;
case QgsLabelLineSettings::AnchorTextPoint::FollowPlacement:
// not possible here
break;
}
lPos.emplace_back( std::make_unique< LabelPosition >( i, labelX, candidateCenterY - labelHeight / 2, labelWidth, labelHeight, 0, cost, this, false, LabelPosition::QuadrantOver ) );
@ -938,6 +943,8 @@ std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vec
return 0; //createCandidatesAlongLineNearMidpoint will be more appropriate
}
const QgsLabelLineSettings::AnchorTextPoint textPoint = mLF->lineAnchorTextPoint();
const std::size_t candidateTargetCount = maximumLineCandidates();
double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
@ -997,7 +1004,7 @@ std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vec
const double labelCenter = currentDistanceAlongLine + labelWidth / 2.0;
double labelTextAnchor = 0;
switch ( mLF->lineAnchorTextPoint() )
switch ( textPoint )
{
case QgsLabelLineSettings::AnchorTextPoint::StartOfText:
labelTextAnchor = currentDistanceAlongLine;
@ -1008,6 +1015,9 @@ std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vec
case QgsLabelLineSettings::AnchorTextPoint::EndOfText:
labelTextAnchor = currentDistanceAlongLine + labelWidth;
break;
case QgsLabelLineSettings::AnchorTextPoint::FollowPlacement:
// not possible here
break;
}
const bool placementIsFlexible = mLF->lineAnchorPercent() > 0.1 && mLF->lineAnchorPercent() < 0.9;
@ -1133,6 +1143,8 @@ std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std
double lineStepDistance = ( totalLineLength - labelWidth ); // distance to move along line with each candidate
double currentDistanceAlongLine = 0;
const QgsLabelLineSettings::AnchorTextPoint textPoint = mLF->lineAnchorTextPoint();
const std::size_t candidateTargetCount = maximumLineCandidates();
if ( totalLineLength > labelWidth )
@ -1159,7 +1171,7 @@ std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std
break;
case QgsLabelLineSettings::AnchorType::Strict:
switch ( mLF->lineAnchorTextPoint() )
switch ( textPoint )
{
case QgsLabelLineSettings::AnchorTextPoint::StartOfText:
currentDistanceAlongLine = std::min( lineAnchorPoint, totalLineLength * 0.99 - labelWidth );
@ -1170,6 +1182,9 @@ std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std
case QgsLabelLineSettings::AnchorTextPoint::EndOfText:
currentDistanceAlongLine = std::min( lineAnchorPoint - labelWidth, totalLineLength * 0.99 - labelWidth );
break;
case QgsLabelLineSettings::AnchorTextPoint::FollowPlacement:
// not possible here
break;
}
lineStepDistance = -1;
break;
@ -1212,7 +1227,7 @@ std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std
// penalize positions which are further from the line's anchor point
double textAnchorPoint = 0;
switch ( mLF->lineAnchorTextPoint() )
switch ( textPoint )
{
case QgsLabelLineSettings::AnchorTextPoint::StartOfText:
textAnchorPoint = currentDistanceAlongLine;
@ -1223,6 +1238,9 @@ std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std
case QgsLabelLineSettings::AnchorTextPoint::EndOfText:
textAnchorPoint = currentDistanceAlongLine + labelWidth;
break;
case QgsLabelLineSettings::AnchorTextPoint::FollowPlacement:
// not possible here
break;
}
double costCenter = std::fabs( lineAnchorPoint - textAnchorPoint ) / totalLineLength; // <0, 0.5>
cost += costCenter / 1000; // < 0, 0.0005 >
@ -1482,16 +1500,19 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
break;
case QgsLabelLineSettings::AnchorType::Strict:
switch ( mLF->lineAnchorTextPoint() )
switch ( textPoint )
{
case QgsLabelLineSettings::AnchorTextPoint::StartOfText:
distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint, 0.0, totalDistance * 0.99 );
distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint, 0.0, totalDistance * 0.999 );
break;
case QgsLabelLineSettings::AnchorTextPoint::CenterOfText:
distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint - getLabelWidth() / 2, 0.0, totalDistance * 0.99 - getLabelWidth() );
distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint - getLabelWidth() / 2, 0.0, totalDistance * 0.999 - getLabelWidth() / 2 );
break;
case QgsLabelLineSettings::AnchorTextPoint::EndOfText:
distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint - getLabelWidth(), 0.0, totalDistance * 0.99 ) ;
distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint - getLabelWidth(), 0.0, totalDistance * 0.999 - getLabelWidth() ) ;
break;
case QgsLabelLineSettings::AnchorTextPoint::FollowPlacement:
// not possible here
break;
}
singleCandidateOnly = true;
@ -1553,7 +1574,7 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
// penalize positions which are further from the line's anchor point
double labelTextAnchor = 0;
switch ( mLF->lineAnchorTextPoint() )
switch ( textPoint )
{
case QgsLabelLineSettings::AnchorTextPoint::StartOfText:
labelTextAnchor = distanceAlongLineToStartCandidate;
@ -1564,6 +1585,9 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
case QgsLabelLineSettings::AnchorTextPoint::EndOfText:
labelTextAnchor = distanceAlongLineToStartCandidate + getLabelWidth();
break;
case QgsLabelLineSettings::AnchorTextPoint::FollowPlacement:
// not possible here
break;
}
double costCenter = std::fabs( lineAnchorPoint - labelTextAnchor ) / totalDistance; // <0, 0.5>
cost += costCenter / ( anchorIsFlexiblePlacement ? 100 : 10 ); // < 0, 0.005 >, or <0, 0.05> if preferring placement close to start/end of line

View File

@ -36,6 +36,7 @@ QgsLabelLineAnchorWidget::QgsLabelLineAnchorWidget( QWidget *parent, QgsVectorLa
mAnchorTypeComboBox->addItem( tr( "Preferred Placement Hint" ), static_cast< int >( QgsLabelLineSettings::AnchorType::HintOnly ) );
mAnchorTypeComboBox->addItem( tr( "Strict" ), static_cast< int >( QgsLabelLineSettings::AnchorType::Strict ) );
mAnchorTextPointComboBox->addItem( tr( "Automatic" ), static_cast< int >( QgsLabelLineSettings::AnchorTextPoint::FollowPlacement ) );
mAnchorTextPointComboBox->addItem( tr( "Start of Text" ), static_cast< int >( QgsLabelLineSettings::AnchorTextPoint::StartOfText ) );
mAnchorTextPointComboBox->addItem( tr( "Center of Text" ), static_cast< int >( QgsLabelLineSettings::AnchorTextPoint::CenterOfText ) );
mAnchorTextPointComboBox->addItem( tr( "End of Text" ), static_cast< int >( QgsLabelLineSettings::AnchorTextPoint::EndOfText ) );
@ -170,6 +171,9 @@ void QgsLabelLineAnchorWidget::updateAnchorTextPointHint()
case QgsLabelLineSettings::AnchorTextPoint::EndOfText:
hint = tr( "Labels are placed so that the end of their text is placed at the anchor point." );
break;
case QgsLabelLineSettings::AnchorTextPoint::FollowPlacement:
hint = tr( "The text justification is determined based on the anchor point. Anchors close to the start of the line will use the start of the text, anchors close to the end will use the end of the text, and central values will use the center of the text." );
break;
}
mAnchorTextPointHintLabel->setText( hint );
}