Skip to content

Commit

Permalink
[FEATURE] Add mode to apply label distance from symbol bounds
Browse files Browse the repository at this point in the history
(only works with Cartographic point label placement). When this
setting is active, the label distance applies from the bounds
of the rendered symbol for a point, instead of the point itself.
It's especially useful when the symbol size isn't fixed, eg if
it's set by a data defined size or when using different symbols
in a categorised renderer.

Sponsored by Andreas Neumann
  • Loading branch information
nyalldawson committed Jan 11, 2016
1 parent 6499439 commit 8b28c04
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 27 deletions.
11 changes: 11 additions & 0 deletions python/core/qgspallabeling.sip
Expand Up @@ -121,6 +121,14 @@ class QgsPalLayerSettings
BottomRight, //!< Label on bottom right of point
};

//! Behaviour modifier for label offset and distance, only applies in some
//! label placement modes.
enum OffsetType
{
FromPoint, //!< Offset distance applies from point geometry
FromSymbolBounds, //!< Offset distance applies from rendered symbol bounds
};

/** Line placement flags, which control how candidates are generated for a linear feature.
*/
enum LinePlacementFlags
Expand Down Expand Up @@ -469,6 +477,9 @@ class QgsPalLayerSettings
bool distInMapUnits; //true if distance is in map units (otherwise in mm)
QgsMapUnitScale distMapUnitScale;

//! Offset type for layer (only applies in certain placement modes)
OffsetType offsetType;

double repeatDistance;
SizeUnit repeatDistanceUnit;
QgsMapUnitScale repeatDistanceMapUnitScale;
Expand Down
8 changes: 8 additions & 0 deletions src/app/qgslabelinggui.cpp
Expand Up @@ -111,6 +111,9 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas,
mObstacleTypeComboBox->addItem( tr( "Over the feature's interior" ), QgsPalLayerSettings::PolygonInterior );
mObstacleTypeComboBox->addItem( tr( "Over the feature's boundary" ), QgsPalLayerSettings::PolygonBoundary );

mOffsetTypeComboBox->addItem( tr( "From point" ), QgsPalLayerSettings::FromPoint );
mOffsetTypeComboBox->addItem( tr( "From symbol bounds" ), QgsPalLayerSettings::FromSymbolBounds );

mCharDlg = new QgsCharacterSelectorDialog( this );

mRefFont = lblFontPreview->font();
Expand Down Expand Up @@ -336,6 +339,7 @@ void QgsLabelingGui::init()
mLineDistanceSpnBx->setValue( lyr.dist );
mLineDistanceUnitWidget->setUnit( lyr.distInMapUnits ? QgsSymbolV2::MapUnit : QgsSymbolV2::MM );
mLineDistanceUnitWidget->setMapUnitScale( lyr.distMapUnitScale );
mOffsetTypeComboBox->setCurrentIndex( mOffsetTypeComboBox->findData( lyr.offsetType ) );
mQuadrantBtnGrp->button(( int )lyr.quadOffset )->setChecked( true );
mPointOffsetXSpinBox->setValue( lyr.xOffset );
mPointOffsetYSpinBox->setValue( lyr.yOffset );
Expand Down Expand Up @@ -639,6 +643,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
lyr.dist = mLineDistanceSpnBx->value();
lyr.distInMapUnits = ( mLineDistanceUnitWidget->unit() == QgsSymbolV2::MapUnit );
lyr.distMapUnitScale = mLineDistanceUnitWidget->getMapUnitScale();
lyr.offsetType = static_cast< QgsPalLayerSettings::OffsetType >( mOffsetTypeComboBox->itemData( mOffsetTypeComboBox->currentIndex() ).toInt() );
lyr.quadOffset = ( QgsPalLayerSettings::QuadrantPosition )mQuadrantBtnGrp->checkedId();
lyr.xOffset = mPointOffsetXSpinBox->value();
lyr.yOffset = mPointOffsetYSpinBox->value();
Expand Down Expand Up @@ -1368,6 +1373,7 @@ void QgsLabelingGui::updatePlacementWidgets()
bool showQuadrantFrame = false;
bool showFixedQuadrantFrame = false;
bool showPlacementPriorityFrame = false;
bool showOffsetTypeFrame = false;
bool showOffsetFrame = false;
bool showDistanceFrame = false;
bool showRotationFrame = false;
Expand Down Expand Up @@ -1399,6 +1405,7 @@ void QgsLabelingGui::updatePlacementWidgets()
{
showDistanceFrame = true;
showPlacementPriorityFrame = true;
showOffsetTypeFrame = true;
}
else if (( curWdgt == pageLine && radLineParallel->isChecked() )
|| ( curWdgt == pagePolygon && radPolygonPerimeter->isChecked() )
Expand All @@ -1424,6 +1431,7 @@ void QgsLabelingGui::updatePlacementWidgets()
mPlacementCartographicFrame->setVisible( showPlacementPriorityFrame );
mPlacementOffsetFrame->setVisible( showOffsetFrame );
mPlacementDistanceFrame->setVisible( showDistanceFrame );
mPlacementOffsetTypeFrame->setVisible( showOffsetTypeFrame );
mPlacementRotationFrame->setVisible( showRotationFrame );
mPlacementRepeatDistanceFrame->setVisible( curWdgt == pageLine || ( curWdgt == pagePolygon && radPolygonPerimeter->isChecked() ) );
mPlacementMaxCharAngleFrame->setVisible( showMaxCharAngleFrame );
Expand Down
35 changes: 19 additions & 16 deletions src/core/pal/feature.cpp
Expand Up @@ -314,6 +314,9 @@ int FeaturePart::createCandidatesAtOrderedPositionsOverPoint( double x, double y
double distanceToLabel = getLabelDistance();
const QgsLabelFeature::VisualMargin& visualMargin = mLF->visualMargin();

double symbolWidthOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().width() / 2.0 : 0.0 );
double symbolHeightOffset = ( mLF->offsetType() == QgsPalLayerSettings::FromSymbolBounds ? mLF->symbolSize().height() / 2.0 : 0.0 );

double cost = 0.0001;
int i = 0;
Q_FOREACH ( QgsPalLayerSettings::PredefinedPointPosition position, positions )
Expand All @@ -327,85 +330,85 @@ int FeaturePart::createCandidatesAtOrderedPositionsOverPoint( double x, double y
case QgsPalLayerSettings::TopLeft:
quadrant = LabelPosition::QuadrantAboveLeft;
alpha = 2.3561944902; //315 degrees
deltaX = -labelWidth + visualMargin.right;
deltaY = -visualMargin.bottom;
deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
deltaY = -visualMargin.bottom + symbolHeightOffset;
break;

case QgsPalLayerSettings::TopSlightlyLeft:
quadrant = LabelPosition::QuadrantAboveRight; //right quadrant, so labels are left-aligned
alpha = 1.5707963268; //0 degrees;
deltaX = -labelWidth / 4.0 - visualMargin.left;
deltaY = -visualMargin.bottom;
deltaY = -visualMargin.bottom + symbolHeightOffset;
break;

case QgsPalLayerSettings::TopMiddle:
quadrant = LabelPosition::QuadrantAbove;
alpha = 1.5707963268; //0 degrees
deltaX = -labelWidth / 2.0;
deltaY = -visualMargin.bottom;
deltaY = -visualMargin.bottom + symbolHeightOffset;
break;

case QgsPalLayerSettings::TopSlightlyRight:
quadrant = LabelPosition::QuadrantAboveLeft; //left quadrant, so labels are right-aligned
alpha = 1.5707963268; //0 degrees
deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
deltaY = -visualMargin.bottom;
deltaY = -visualMargin.bottom + symbolHeightOffset;
break;

case QgsPalLayerSettings::TopRight:
quadrant = LabelPosition::QuadrantAboveRight;
alpha = 0.7853981634; // 45.0 degrees
deltaX = - visualMargin.left;
deltaY = -visualMargin.bottom;
deltaX = - visualMargin.left + symbolWidthOffset;
deltaY = -visualMargin.bottom + symbolHeightOffset;
break;

case QgsPalLayerSettings::MiddleLeft:
quadrant = LabelPosition::QuadrantLeft;
alpha = 3.1415926536; // 270.0 degrees
deltaX = -labelWidth + visualMargin.right;
deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
break;

case QgsPalLayerSettings::MiddleRight:
quadrant = LabelPosition::QuadrantRight;
alpha = 0.0; // 90.0 degrees
deltaX = -visualMargin.left;
deltaX = -visualMargin.left + symbolWidthOffset;
deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin??
break;

case QgsPalLayerSettings::BottomLeft:
quadrant = LabelPosition::QuadrantBelowLeft;
alpha = 3.926990817; // 225.0 degrees
deltaX = -labelWidth + visualMargin.right;
deltaY = -labelHeight + visualMargin.top;
deltaX = -labelWidth + visualMargin.right - symbolWidthOffset;
deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
break;

case QgsPalLayerSettings::BottomSlightlyLeft:
quadrant = LabelPosition::QuadrantBelowRight; //right quadrant, so labels are left-aligned
alpha = 4.7123889804; // 180.0 degrees
deltaX = -labelWidth / 4.0 - visualMargin.left;
deltaY = -labelHeight + visualMargin.top;
deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
break;

case QgsPalLayerSettings::BottomMiddle:
quadrant = LabelPosition::QuadrantBelow;
alpha = 4.7123889804; // 180.0 degrees
deltaX = -labelWidth / 2.0;
deltaY = -labelHeight + visualMargin.top;
deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
break;

case QgsPalLayerSettings::BottomSlightlyRight:
quadrant = LabelPosition::QuadrantBelowLeft; //left quadrant, so labels are right-aligned
alpha = 4.7123889804; // 180.0 degrees
deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.right;
deltaY = -labelHeight + visualMargin.top;
deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
break;

case QgsPalLayerSettings::BottomRight:
quadrant = LabelPosition::QuadrantBelowRight;
alpha = 5.4977871438; // 135.0 degrees
deltaX = -visualMargin.left;
deltaY = -labelHeight + visualMargin.top;
deltaX = -visualMargin.left + symbolWidthOffset;
deltaY = -labelHeight + visualMargin.top - symbolHeightOffset;
break;
}

Expand Down
1 change: 1 addition & 0 deletions src/core/qgslabelfeature.cpp
Expand Up @@ -30,6 +30,7 @@ QgsLabelFeature::QgsLabelFeature( QgsFeatureId id, GEOSGeometry* geometry, const
, mFixedAngle( 0 )
, mHasFixedQuadrant( false )
, mDistLabel( 0 )
, mOffsetType( QgsPalLayerSettings::FromPoint )
, mRepeatDistance( 0 )
, mAlwaysShow( false )
, mIsObstacle( false )
Expand Down
34 changes: 34 additions & 0 deletions src/core/qgslabelfeature.h
Expand Up @@ -126,6 +126,21 @@ class CORE_EXPORT QgsLabelFeature
*/
const VisualMargin& visualMargin() const { return mVisualMargin; }

/** Sets the size of the rendered symbol associated with this feature. This size is taken into
* account in certain label placement modes to avoid placing labels over the rendered
* symbol for this feature.
* @see symbolSize()
*/
void setSymbolSize( const QSizeF& size ) { mSymbolSize = size; }

/** Returns the size of the rendered symbol associated with this feature, if applicable.
* This size is taken into account in certain label placement modes to avoid placing labels over
* the rendered symbol for this feature. The size will only be set for labels associated
* with a point feature.
* @see symbolSize()
*/
const QSizeF& symbolSize() const { return mSymbolSize; }

/** Returns the feature's labeling priority.
* @returns feature's priority, as a value between 0 (highest priority)
* and 1 (lowest priority). Returns -1.0 if feature will use the layer's default priority.
Expand Down Expand Up @@ -199,6 +214,21 @@ class CORE_EXPORT QgsLabelFeature
//! Applies only to "offset from point" placement strategy.
//! Set what offset (in map units) to use from the point
void setPositionOffset( const QgsPoint& offset ) { mPositionOffset = offset; }

/** Returns the offset type, which determines how offsets and distance to label
* behaves. Support depends on which placement mode is used for generating
* label candidates.
* @see setOffsetType()
*/
QgsPalLayerSettings::OffsetType offsetType() const { return mOffsetType; }

/** Sets the offset type, which determines how offsets and distance to label
* behaves. Support depends on which placement mode is used for generating
* label candidates.
* @see offsetType()
*/
void setOffsetType( QgsPalLayerSettings::OffsetType type ) { mOffsetType = type; }

//! Applies to "around point" placement strategy or linestring features.
//! Distance of the label from the feature (in map units)
double distLabel() const { return mDistLabel; }
Expand Down Expand Up @@ -290,6 +320,8 @@ class CORE_EXPORT QgsLabelFeature
QSizeF mSize;
//! Visual margin of label contents
VisualMargin mVisualMargin;
//! Size of associated rendered symbol, if applicable
QSizeF mSymbolSize;
//! Priority of the label
double mPriority;
//! Z-index of label (higher z-index labels are rendered on top of lower z-index labels)
Expand All @@ -310,6 +342,8 @@ class CORE_EXPORT QgsLabelFeature
QgsPoint mPositionOffset;
//! distance of label from the feature (only for "around point" placement or linestrings)
double mDistLabel;
//! Offset type for certain placement modes
QgsPalLayerSettings::OffsetType mOffsetType;
//! Ordered list of predefined positions for label (only for OrderedPositionsAroundPoint placement)
QVector< QgsPalLayerSettings::PredefinedPointPosition > mPredefinedPositionOrder;
//! distance after which label should be repeated (only for linestrings)
Expand Down
20 changes: 17 additions & 3 deletions src/core/qgspallabeling.cpp
Expand Up @@ -200,6 +200,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
labelOffsetInMapUnits = true;
dist = 0;
distInMapUnits = false;
offsetType = FromPoint;
angleOffset = 0;
preserveRotation = true;
maxCurvedCharAngleIn = 20.0;
Expand Down Expand Up @@ -424,6 +425,7 @@ QgsPalLayerSettings& QgsPalLayerSettings::operator=( const QgsPalLayerSettings &
labelOffsetInMapUnits = s.labelOffsetInMapUnits;
labelOffsetMapUnitScale = s.labelOffsetMapUnitScale;
dist = s.dist;
offsetType = s.offsetType;
distInMapUnits = s.distInMapUnits;
distMapUnitScale = s.distMapUnitScale;
angleOffset = s.angleOffset;
Expand Down Expand Up @@ -948,6 +950,7 @@ void QgsPalLayerSettings::readFromLayer( QgsVectorLayer* layer )
distInMapUnits = layer->customProperty( "labeling/distInMapUnits" ).toBool();
distMapUnitScale.minScale = layer->customProperty( "labeling/distMapUnitMinScale", 0.0 ).toDouble();
distMapUnitScale.maxScale = layer->customProperty( "labeling/distMapUnitMaxScale", 0.0 ).toDouble();
offsetType = static_cast< OffsetType >( layer->customProperty( "labeling/offsetType", QVariant( FromPoint ) ).toUInt() );
quadOffset = static_cast< QuadrantPosition >( layer->customProperty( "labeling/quadOffset", QVariant( QuadrantOver ) ).toUInt() );
xOffset = layer->customProperty( "labeling/xOffset", QVariant( 0.0 ) ).toDouble();
yOffset = layer->customProperty( "labeling/yOffset", QVariant( 0.0 ) ).toDouble();
Expand Down Expand Up @@ -1124,6 +1127,7 @@ void QgsPalLayerSettings::writeToLayer( QgsVectorLayer* layer )
layer->setCustomProperty( "labeling/distInMapUnits", distInMapUnits );
layer->setCustomProperty( "labeling/distMapUnitMinScale", distMapUnitScale.minScale );
layer->setCustomProperty( "labeling/distMapUnitMaxScale", distMapUnitScale.maxScale );
layer->setCustomProperty( "labeling/offsetType", static_cast< unsigned int >( offsetType ) );
layer->setCustomProperty( "labeling/quadOffset", static_cast< unsigned int >( quadOffset ) );
layer->setCustomProperty( "labeling/xOffset", xOffset );
layer->setCustomProperty( "labeling/yOffset", yOffset );
Expand Down Expand Up @@ -1326,6 +1330,7 @@ void QgsPalLayerSettings::readXml( QDomElement& elem )
distInMapUnits = placementElem.attribute( "distInMapUnits" ).toInt();
distMapUnitScale.minScale = placementElem.attribute( "distMapUnitMinScale", "0" ).toDouble();
distMapUnitScale.maxScale = placementElem.attribute( "distMapUnitMaxScale", "0" ).toDouble();
offsetType = static_cast< OffsetType >( placementElem.attribute( "offsetType", QString::number( FromPoint ) ).toUInt() );
quadOffset = static_cast< QuadrantPosition >( placementElem.attribute( "quadOffset", QString::number( QuadrantOver ) ).toUInt() );
xOffset = placementElem.attribute( "xOffset", "0" ).toDouble();
yOffset = placementElem.attribute( "yOffset", "0" ).toDouble();
Expand Down Expand Up @@ -1488,6 +1493,7 @@ QDomElement QgsPalLayerSettings::writeXml( QDomDocument& doc )
placementElem.setAttribute( "distInMapUnits", distInMapUnits );
placementElem.setAttribute( "distMapUnitMinScale", distMapUnitScale.minScale );
placementElem.setAttribute( "distMapUnitMaxScale", distMapUnitScale.maxScale );
placementElem.setAttribute( "offsetType", static_cast< unsigned int >( offsetType ) );
placementElem.setAttribute( "quadOffset", static_cast< unsigned int >( quadOffset ) );
placementElem.setAttribute( "xOffset", xOffset );
placementElem.setAttribute( "yOffset", yOffset );
Expand Down Expand Up @@ -2299,9 +2305,9 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
if ( obstacleGeometry && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : nullptr ) )
{
scopedObstacleGeom.reset( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : nullptr ) );
geosObstacleGeom = scopedObstacleGeom.data()->asGeos();
obstacleGeometry = scopedObstacleGeom.data();
}
else if ( obstacleGeometry )
if ( obstacleGeometry )
{
geosObstacleGeom = obstacleGeometry->asGeos();
}
Expand Down Expand Up @@ -2670,12 +2676,20 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
( *labelFeature )->setFixedAngle( angle );
( *labelFeature )->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
( *labelFeature )->setPositionOffset( QgsPoint( offsetX, offsetY ) );
( *labelFeature )->setOffsetType( offsetType );
( *labelFeature )->setAlwaysShow( alwaysShow );
( *labelFeature )->setRepeatDistance( repeatDist );
( *labelFeature )->setLabelText( labelText );
if ( geosObstacleGeomClone )
{
( *labelFeature )->setObstacleGeometry( geosObstacleGeomClone );

if ( geom->type() == QGis::Point )
{
//register symbol size
( *labelFeature )->setSymbolSize( QSizeF( obstacleGeometry->boundingBox().width(),
obstacleGeometry->boundingBox().height() ) );
}
}

//set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
Expand All @@ -2684,7 +2698,7 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
double bottomMargin = 1.0 + labelFontMetrics->descent();
QgsLabelFeature::VisualMargin vm( topMargin, 0.0, bottomMargin, 0.0 );
vm *= xform->mapUnitsPerPixel() / rasterCompressFactor;
( *labelFeature )->setVisualMargin( vm );
( *labelFeature )->setVisualMargin( vm );

// store the label's calculated font for later use during painting
QgsDebugMsgLevel( QString( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
Expand Down
11 changes: 11 additions & 0 deletions src/core/qgspallabeling.h
Expand Up @@ -106,6 +106,15 @@ class CORE_EXPORT QgsPalLayerSettings
BottomRight, //!< Label on bottom right of point
};

//! Behaviour modifier for label offset and distance, only applies in some
//! label placement modes.
//TODO QGIS 3.0 - move to QgsLabelingEngineV2
enum OffsetType
{
FromPoint, //!< Offset distance applies from point geometry
FromSymbolBounds, //!< Offset distance applies from rendered symbol bounds
};

/** Line placement flags, which control how candidates are generated for a linear feature.
*/
//TODO QGIS 3.0 - move to QgsLabelingEngineV2, rename to LinePlacementFlag, use Q_DECLARE_FLAGS to make
Expand Down Expand Up @@ -456,6 +465,8 @@ class CORE_EXPORT QgsPalLayerSettings
double dist; // distance from the feature (in mm)
bool distInMapUnits; //true if distance is in map units (otherwise in mm)
QgsMapUnitScale distMapUnitScale;
//! Offset type for layer (only applies in certain placement modes)
OffsetType offsetType;

double repeatDistance;
SizeUnit repeatDistanceUnit;
Expand Down

0 comments on commit 8b28c04

Please sign in to comment.