Skip to content
Permalink
Browse files

[FEATURE] Add mode to apply label distance from symbol bounds

(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 8b28c040d1a2dff016538bcae6661992f9a39607
@@ -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
@@ -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;
@@ -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();
@@ -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 );
@@ -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();
@@ -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;
@@ -1399,6 +1405,7 @@ void QgsLabelingGui::updatePlacementWidgets()
{
showDistanceFrame = true;
showPlacementPriorityFrame = true;
showOffsetTypeFrame = true;
}
else if (( curWdgt == pageLine && radLineParallel->isChecked() )
|| ( curWdgt == pagePolygon && radPolygonPerimeter->isChecked() )
@@ -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 );
@@ -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 )
@@ -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;
}

@@ -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 )
@@ -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.
@@ -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; }
@@ -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)
@@ -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)
@@ -200,6 +200,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
labelOffsetInMapUnits = true;
dist = 0;
distInMapUnits = false;
offsetType = FromPoint;
angleOffset = 0;
preserveRotation = true;
maxCurvedCharAngleIn = 20.0;
@@ -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;
@@ -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();
@@ -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 );
@@ -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();
@@ -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 );
@@ -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();
}
@@ -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
@@ -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 );
@@ -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
@@ -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;

0 comments on commit 8b28c04

Please sign in to comment.
You can’t perform that action at this time.