Skip to content

Commit

Permalink
[FEATURE][labeling] Permit data defined control over placing labels o…
Browse files Browse the repository at this point in the history
…utside

polygons

Options are:
- 'yes': allow placing outside the polygon if needed
- 'no': don't allow outside placements
- 'force': only ever put this label outside the polygon
  • Loading branch information
nyalldawson committed May 3, 2020
1 parent 51820ba commit 9fb85d3
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 42 deletions.
5 changes: 3 additions & 2 deletions python/core/auto_additions/qgslabeling.py
Expand Up @@ -7,6 +7,7 @@
QgsLabeling.LinePlacementFlag.__doc__ = 'Line placement flags, which control how candidates are generated for a linear feature.\n\n' + '* ``OnLine``: ' + QgsLabeling.LinePlacementFlag.OnLine.__doc__ + '\n' + '* ``AboveLine``: ' + QgsLabeling.LinePlacementFlag.AboveLine.__doc__ + '\n' + '* ``BelowLine``: ' + QgsLabeling.LinePlacementFlag.BelowLine.__doc__ + '\n' + '* ``MapOrientation``: ' + QgsLabeling.LinePlacementFlag.MapOrientation.__doc__
# --
# monkey patching scoped based enum
QgsLabeling.PolygonPlacementFlag.AllowPlacementOutsideOfPolygon.__doc__ = "Labels can be placed outside of a polygon feature if it was not possible to place them inside."
QgsLabeling.PolygonPlacementFlag.__doc__ = 'Polygon placement flags, which control how candidates are generated for a polygon feature.\n\n.. versionadded:: 3.14\n\n' + '* ``AllowPlacementOutsideOfPolygon``: ' + QgsLabeling.PolygonPlacementFlag.AllowPlacementOutsideOfPolygon.__doc__
QgsLabeling.PolygonPlacementFlag.AllowPlacementOutsideOfPolygon.__doc__ = "Labels can be placed outside of a polygon feature"
QgsLabeling.PolygonPlacementFlag.AllowPlacementInsideOfPolygon.__doc__ = "Labels can be placed inside a polygon feature"
QgsLabeling.PolygonPlacementFlag.__doc__ = 'Polygon placement flags, which control how candidates are generated for a polygon feature.\n\n.. versionadded:: 3.14\n\n' + '* ``AllowPlacementOutsideOfPolygon``: ' + QgsLabeling.PolygonPlacementFlag.AllowPlacementOutsideOfPolygon.__doc__ + '\n' + '* ``AllowPlacementInsideOfPolygon``: ' + QgsLabeling.PolygonPlacementFlag.AllowPlacementInsideOfPolygon.__doc__
# --
3 changes: 3 additions & 0 deletions python/core/auto_generated/labeling/qgslabeling.sip.in
Expand Up @@ -35,6 +35,7 @@ Contains constants and enums relating to labeling.
enum class PolygonPlacementFlag
{
AllowPlacementOutsideOfPolygon,
AllowPlacementInsideOfPolygon,
};
typedef QFlags<QgsLabeling::PolygonPlacementFlag> PolygonPlacementFlags;

Expand All @@ -43,6 +44,8 @@ Contains constants and enums relating to labeling.

QFlags<QgsLabeling::LinePlacementFlag> operator|(QgsLabeling::LinePlacementFlag f1, QFlags<QgsLabeling::LinePlacementFlag> f2);

QFlags<QgsLabeling::PolygonPlacementFlag> operator|(QgsLabeling::PolygonPlacementFlag f1, QFlags<QgsLabeling::PolygonPlacementFlag> f2);


/************************************************************************
* This file has been generated automatically from *
Expand Down
1 change: 1 addition & 0 deletions python/core/auto_generated/labeling/qgspallabeling.sip.in
Expand Up @@ -287,6 +287,7 @@ class QgsPalLayerSettings
LinePlacementOptions,
OverrunDistance,
LabelAllParts,
PolygonLabelOutside,

// rendering
ScaleVisibility,
Expand Down
4 changes: 3 additions & 1 deletion src/core/labeling/qgslabeling.h
Expand Up @@ -51,12 +51,14 @@ class CORE_EXPORT QgsLabeling
*/
enum class PolygonPlacementFlag : int
{
AllowPlacementOutsideOfPolygon = 1 << 0, //!< Labels can be placed outside of a polygon feature if it was not possible to place them inside.
AllowPlacementOutsideOfPolygon = 1 << 0, //!< Labels can be placed outside of a polygon feature
AllowPlacementInsideOfPolygon = 1 << 1, //!< Labels can be placed inside a polygon feature
};
Q_DECLARE_FLAGS( PolygonPlacementFlags, PolygonPlacementFlag )

};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsLabeling::LinePlacementFlags )
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsLabeling::PolygonPlacementFlags )

#endif // QGSLABELING_H
46 changes: 38 additions & 8 deletions src/core/labeling/qgspallabeling.cpp
Expand Up @@ -228,6 +228,7 @@ void QgsPalLayerSettings::initPropertyDefinitions()
+ QStringLiteral( "[<b>OL</b>=On line|<b>AL</b>=Above line|<b>BL</b>=Below line|<br>"
"<b>LO</b>=Respect line orientation]" ), origin )
},
{ QgsPalLayerSettings::PolygonLabelOutside, QgsPropertyDefinition( "PolygonLabelOutside", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label outside polygons" ), QObject::tr( "string " ) + "[<b>yes</b> (allow placing outside)|<b>no</b> (never place outside)|<b>force</b> (always place outside)]", origin ) },
{ QgsPalLayerSettings::PositionX, QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double, origin ) },
{ QgsPalLayerSettings::PositionY, QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double, origin ) },
{ QgsPalLayerSettings::Hali, QgsPropertyDefinition( "Hali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Horizontal alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]", origin ) },
Expand Down Expand Up @@ -934,7 +935,7 @@ void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteCo
QDomElement placementElem = elem.firstChildElement( QStringLiteral( "placement" ) );
placement = static_cast< Placement >( placementElem.attribute( QStringLiteral( "placement" ) ).toInt() );
placementFlags = placementElem.attribute( QStringLiteral( "placementFlags" ) ).toUInt();
mPolygonPlacementFlags = static_cast< QgsLabeling::PolygonPlacementFlags >( placementElem.attribute( QStringLiteral( "polygonPlacementFlags" ) ).toInt() );
mPolygonPlacementFlags = static_cast< QgsLabeling::PolygonPlacementFlags >( placementElem.attribute( QStringLiteral( "polygonPlacementFlags" ), QString::number( static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon ) ) ).toInt() );

centroidWhole = placementElem.attribute( QStringLiteral( "centroidWhole" ), QStringLiteral( "0" ) ).toInt();
centroidInside = placementElem.attribute( QStringLiteral( "centroidInside" ), QStringLiteral( "0" ) ).toInt();
Expand Down Expand Up @@ -1972,16 +1973,45 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext


QgsLabeling::PolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LinePlacementOptions ) )
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PolygonLabelOutside ) )
{
#if 0
context.expressionContext().setOriginalValueVariable( QgsLabelingUtils::encodeLinePlacementFlags( featureArrangementFlags ) );
const QString dataDefinedLineArrangement = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::LinePlacementOptions, context.expressionContext() );
if ( !dataDefinedLineArrangement.isEmpty() )
const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::PolygonLabelOutside, context.expressionContext() );
if ( dataDefinedOutside.isValid() )
{
featureArrangementFlags = QgsLabelingUtils::decodeLinePlacementFlags( dataDefinedLineArrangement );
if ( dataDefinedOutside.type() == QVariant::String )
{
const QString value = dataDefinedOutside.toString().trimmed();
if ( value.compare( QLatin1String( "force" ), Qt::CaseInsensitive ) == 0 )
{
// forced outside placement -- remove inside flag, add outside flag
polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon );
polygonPlacement |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
}
else if ( value.compare( QLatin1String( "yes" ), Qt::CaseInsensitive ) == 0 )
{
// permit outside placement
polygonPlacement |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
}
else if ( value.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
{
// block outside placement
polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
}
}
else
{
if ( dataDefinedOutside.toBool() )
{
// permit outside placement
polygonPlacement |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
}
else
{
// block outside placement
polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
}
}
}
#endif
}

// if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
Expand Down
3 changes: 2 additions & 1 deletion src/core/labeling/qgspallabeling.h
Expand Up @@ -442,6 +442,7 @@ class CORE_EXPORT QgsPalLayerSettings
LinePlacementOptions = 99, //!< Line placement flags
OverrunDistance = 102, //!< Distance which labels can extend past either end of linear features
LabelAllParts = 103, //!< Whether all parts of multi-part features should be labeled
PolygonLabelOutside = 109, //!< Whether labels outside a polygon feature are permitted, or should be forced

// rendering
ScaleVisibility = 23,
Expand Down Expand Up @@ -1181,7 +1182,7 @@ class CORE_EXPORT QgsPalLayerSettings
QgsLabelObstacleSettings mObstacleSettings;
QgsLabelThinningSettings mThinningSettings;

QgsLabeling::PolygonPlacementFlags mPolygonPlacementFlags = nullptr;
QgsLabeling::PolygonPlacementFlags mPolygonPlacementFlags = QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon;

QgsExpression mGeometryGeneratorExpression;

Expand Down
67 changes: 38 additions & 29 deletions src/core/pal/feature.cpp
Expand Up @@ -2031,55 +2031,64 @@ std::vector< std::unique_ptr< LabelPosition > > FeaturePart::createCandidates( P
const double labelHeight = getLabelHeight();

const bool allowOutside = mLF->polygonPlacementFlags() & QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
const bool allowInside = mLF->polygonPlacementFlags() & QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon;
//check width/height of bbox is sufficient for label

if ( allowOutside && ( std::fabs( xmax - xmin ) < labelWidth ||
std::fabs( ymax - ymin ) < labelHeight ) )
if ( allowOutside && !allowInside )
{
//no way label can fit in this polygon -- label outside
// only allowed to place outside of polygon
createCandidatesOutsidePolygon( lPos, pal );
}
else if ( allowOutside && ( std::fabs( xmax - xmin ) < labelWidth ||
std::fabs( ymax - ymin ) < labelHeight ) )
{
//no way label can fit in this polygon -- shortcut and only place label outside
createCandidatesOutsidePolygon( lPos, pal );
}
else
{
std::size_t created = 0;
switch ( mLF->layer()->arrangement() )
if ( allowInside )
{
case QgsPalLayerSettings::AroundPoint:
{
double cx, cy;
getCentroid( cx, cy, mLF->layer()->centroidInside() );
if ( qgsDoubleNear( mLF->distLabel(), 0.0 ) )
created += createCandidateCenteredOverPoint( cx, cy, lPos, angle );
created += createCandidatesAroundPoint( cx, cy, lPos, angle );
break;
}
case QgsPalLayerSettings::OverPoint:
switch ( mLF->layer()->arrangement() )
{
double cx, cy;
getCentroid( cx, cy, mLF->layer()->centroidInside() );
created += createCandidatesOverPoint( cx, cy, lPos, angle );
break;
case QgsPalLayerSettings::AroundPoint:
{
double cx, cy;
getCentroid( cx, cy, mLF->layer()->centroidInside() );
if ( qgsDoubleNear( mLF->distLabel(), 0.0 ) )
created += createCandidateCenteredOverPoint( cx, cy, lPos, angle );
created += createCandidatesAroundPoint( cx, cy, lPos, angle );
break;
}
case QgsPalLayerSettings::OverPoint:
{
double cx, cy;
getCentroid( cx, cy, mLF->layer()->centroidInside() );
created += createCandidatesOverPoint( cx, cy, lPos, angle );
break;
}
case QgsPalLayerSettings::Line:
created += createCandidatesAlongLine( lPos, this, false, pal );
break;
case QgsPalLayerSettings::PerimeterCurved:
created += createCurvedCandidatesAlongLine( lPos, this, false, pal );
break;
default:
created += createCandidatesForPolygon( lPos, this, pal );
break;
}
case QgsPalLayerSettings::Line:
created += createCandidatesAlongLine( lPos, this, false, pal );
break;
case QgsPalLayerSettings::PerimeterCurved:
created += createCurvedCandidatesAlongLine( lPos, this, false, pal );
break;
default:
created += createCandidatesForPolygon( lPos, this, pal );
break;
}

if ( allowOutside )
{
// add fallback for labels outside the polygon
createCandidatesOutsidePolygon( lPos, pal );

// increase cost for these
if ( created > 0 )
{

// TODO (maybe) increase cost for outside placements (i.e. positions at indices >= created)?
// From my initial testing this doesn't seem necessary
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/gui/labeling/qgslabelinggui.cpp
Expand Up @@ -464,7 +464,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
lyr.dist = 0;
lyr.placementFlags = 0;

QgsLabeling::PolygonPlacementFlags polygonPlacementFlags = nullptr;
QgsLabeling::PolygonPlacementFlags polygonPlacementFlags = QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon;
if ( mCheckAllowLabelsOutsidePolygons->isChecked() )
polygonPlacementFlags |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
lyr.setPolygonPlacementFlags( polygonPlacementFlags );
Expand Down
1 change: 1 addition & 0 deletions src/gui/qgstextformatwidget.cpp
Expand Up @@ -761,6 +761,7 @@ void QgsTextFormatWidget::populateDataDefinedButtons()
registerDataDefinedButton( mLineDistanceDDBtn, QgsPalLayerSettings::LabelDistance );
registerDataDefinedButton( mLineDistanceUnitDDBtn, QgsPalLayerSettings::DistanceUnits );
registerDataDefinedButton( mPriorityDDBtn, QgsPalLayerSettings::Priority );
registerDataDefinedButton( mAllowOutsidePolygonsDDBtn, QgsPalLayerSettings::PolygonLabelOutside );

// TODO: is this necessary? maybe just use the data defined-only rotation?
//mPointAngleDDBtn, QgsPalLayerSettings::OffsetRotation,
Expand Down

0 comments on commit 9fb85d3

Please sign in to comment.