Skip to content

Commit 44f7930

Browse files
committed
[FEATURE] Add follow point alignment for multiline labels
When set to this mode, text alignment for labels will be dependant on the final placement of the label relative to the point. Eg, if the label is placed to the left of the point then the label will be right aligned, and if it is placed to the right of the point then the label will be left aligned. (fix #11153)
1 parent 7622f5c commit 44f7930

File tree

8 files changed

+138
-11
lines changed

8 files changed

+138
-11
lines changed

python/core/qgspallabeling.sip

+3-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ class QgsPalLayerSettings
140140
{
141141
MultiLeft,
142142
MultiCenter,
143-
MultiRight
143+
MultiRight,
144+
MultiFollowPlacement /*< Alignment follows placement of label, eg labels to the left of a feature
145+
will be drawn with right alignment*/
144146
};
145147

146148
enum ShapeType

src/app/qgslabelinggui.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas,
141141
qFatal( "unknown geometry type unexpected" );
142142
}
143143

144+
if ( layer->geometryType() == QGis::Point )
145+
{
146+
// follow placement alignment is only valid for point layers
147+
mFontMultiLineAlignComboBox->addItem( tr( "Follow label placement" ) );
148+
}
149+
144150
// show/hide options based upon geometry type
145151
chkMergeLines->setVisible( layer->geometryType() == QGis::Line );
146152
mDirectSymbolsFrame->setVisible( layer->geometryType() == QGis::Line );
@@ -913,7 +919,7 @@ void QgsLabelingGui::populateDataDefinedButtons( QgsPalLayerSettings& s )
913919
mFontLineHeightDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::MultiLineHeight ),
914920
QgsDataDefinedButton::AnyType, tr( "double [0.0-10.0]" ) );
915921
mFontMultiLineAlignDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::MultiLineAlignment ),
916-
QgsDataDefinedButton::String, QgsDataDefinedButton::textHorzAlignDesc() );
922+
QgsDataDefinedButton::String, trString + "[<b>Left</b>|<b>Center</b>|<b>Right</b>|<b>Follow</b>]" );
917923

918924
mDirectSymbDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::DirSymbDraw ),
919925
QgsDataDefinedButton::AnyType, QgsDataDefinedButton::boolDesc() );

src/core/pal/feature.cpp

+64-3
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,55 @@ namespace pal
275275
return f->uid;
276276
}
277277

278+
LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
279+
{
280+
if ( f->quadOffsetX < 0 )
281+
{
282+
if ( f->quadOffsetY < 0 )
283+
{
284+
return LabelPosition::QuadrantAboveLeft;
285+
}
286+
else if ( f->quadOffsetY > 0 )
287+
{
288+
return LabelPosition::QuadrantBelowLeft;
289+
}
290+
else
291+
{
292+
return LabelPosition::QuadrantLeft;
293+
}
294+
}
295+
else if ( f->quadOffsetX > 0 )
296+
{
297+
if ( f->quadOffsetY < 0 )
298+
{
299+
return LabelPosition::QuadrantAboveRight;
300+
}
301+
else if ( f->quadOffsetY > 0 )
302+
{
303+
return LabelPosition::QuadrantBelowRight;
304+
}
305+
else
306+
{
307+
return LabelPosition::QuadrantRight;
308+
}
309+
}
310+
else
311+
{
312+
if ( f->quadOffsetY < 0 )
313+
{
314+
return LabelPosition::QuadrantAbove;
315+
}
316+
else if ( f->quadOffsetY > 0 )
317+
{
318+
return LabelPosition::QuadrantBelow;
319+
}
320+
else
321+
{
322+
return LabelPosition::QuadrantOver;
323+
}
324+
}
325+
}
326+
278327
int FeaturePart::setPositionOverPoint( double x, double y, double scale, LabelPosition ***lPos, double delta_width, double angle )
279328
{
280329
Q_UNUSED( scale );
@@ -322,6 +371,7 @@ namespace pal
322371
delete lp;
323372
}
324373

374+
LabelPosition::Quadrant quadrant = f->quadOffset ? quadrantFromOffset() : LabelPosition::QuadrantOver;
325375
if ( f->quadOffset )
326376
{
327377
if ( f->quadOffsetX != 0 )
@@ -349,7 +399,7 @@ namespace pal
349399
lx = x + xdiff;
350400
ly = y + ydiff;
351401

352-
( *lPos )[0] = new LabelPosition( id, lx, ly, label_x, label_y, angle, cost, this );
402+
( *lPos )[0] = new LabelPosition( id, lx, ly, label_x, label_y, angle, cost, this, false, quadrant );
353403
return nbp;
354404
}
355405

@@ -443,6 +493,8 @@ namespace pal
443493
if ( alpha > a360 )
444494
alpha -= a360;
445495

496+
LabelPosition::Quadrant quadrant = LabelPosition::QuadrantOver;
497+
446498
if ( alpha < gamma1 || alpha > a360 - gamma1 ) // on the right
447499
{
448500
lx += distlabel;
@@ -452,44 +504,53 @@ namespace pal
452504

453505
//ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
454506
ly += -yrm + yrm * iota / ( 2 * gamma1 );
507+
508+
quadrant = LabelPosition::QuadrantRight;
455509
}
456510
else if ( alpha < a90 - gamma2 ) // top-right
457511
{
458512
lx += distlabel * cos( alpha );
459513
ly += distlabel * sin( alpha );
514+
quadrant = LabelPosition::QuadrantAboveRight;
460515
}
461516
else if ( alpha < a90 + gamma2 ) // top
462517
{
463518
//lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
464519
lx += -xrm * ( alpha - a90 + gamma2 ) / ( 2 * gamma2 );
465520
ly += distlabel;
521+
quadrant = LabelPosition::QuadrantAbove;
466522
}
467523
else if ( alpha < a180 - gamma1 ) // top left
468524
{
469525
lx += distlabel * cos( alpha ) - xrm;
470526
ly += distlabel * sin( alpha );
527+
quadrant = LabelPosition::QuadrantAboveLeft;
471528
}
472529
else if ( alpha < a180 + gamma1 ) // left
473530
{
474531
lx += -distlabel - xrm;
475532
//ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
476533
ly += - ( alpha - a180 + gamma1 ) * yrm / ( 2 * gamma1 );
534+
quadrant = LabelPosition::QuadrantLeft;
477535
}
478536
else if ( alpha < a270 - gamma2 ) // down - left
479537
{
480538
lx += distlabel * cos( alpha ) - xrm;
481539
ly += distlabel * sin( alpha ) - yrm;
540+
quadrant = LabelPosition::QuadrantBelowLeft;
482541
}
483542
else if ( alpha < a270 + gamma2 ) // down
484543
{
485544
ly += -distlabel - yrm;
486545
//lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
487546
lx += -xrm + ( alpha - a270 + gamma2 ) * xrm / ( 2 * gamma2 );
547+
quadrant = LabelPosition::QuadrantBelow;
488548
}
489-
else if ( alpha < a360 )
549+
else if ( alpha < a360 ) // down - right
490550
{
491551
lx += distlabel * cos( alpha );
492552
ly += distlabel * sin( alpha ) - yrm;
553+
quadrant = LabelPosition::QuadrantBelowRight;
493554
}
494555

495556
double cost;
@@ -499,7 +560,7 @@ namespace pal
499560
else
500561
cost = 0.0001 + 0.0020 * double( icost ) / double( nbp - 1 );
501562

502-
( *lPos )[i] = new LabelPosition( i, lx, ly, xrm, yrm, angle, cost, this );
563+
( *lPos )[i] = new LabelPosition( i, lx, ly, xrm, yrm, angle, cost, this, false, quadrant );
503564

504565
icost += inc;
505566

src/core/pal/feature.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
#include "pointset.h"
4646
#include "util.h"
47-
47+
#include "labelposition.h"
4848

4949
namespace pal
5050
{
@@ -321,6 +321,9 @@ namespace pal
321321

322322
void addSizePenalty( int nbp, LabelPosition** lPos, double bbx[4], double bby[4] );
323323

324+
private:
325+
326+
LabelPosition::Quadrant quadrantFromOffset() const;
324327
};
325328

326329
} // end namespace pal

src/core/pal/labelposition.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@
5454

5555
namespace pal
5656
{
57-
LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, bool isReversed )
58-
: id( id ), cost( cost ), feature( feature ), probFeat( 0 ), nbOverlap( 0 ), alpha( alpha ), w( w ), h( h ), nextPart( NULL ), partId( -1 ), reversed( isReversed ), upsideDown( false )
57+
LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, bool isReversed, Quadrant quadrant )
58+
: id( id ), cost( cost ), feature( feature ), probFeat( 0 ), nbOverlap( 0 ), alpha( alpha ), w( w ), h( h ), nextPart( NULL ), partId( -1 ), reversed( isReversed ), upsideDown( false ),
59+
quadrant( quadrant )
5960
{
6061

6162
// alpha take his value bw 0 and 2*pi rad
@@ -166,6 +167,7 @@ namespace pal
166167
partId = other.partId;
167168
upsideDown = other.upsideDown;
168169
reversed = other.reversed;
170+
quadrant = other.quadrant;
169171
}
170172

171173
bool LabelPosition::isIn( double *bbox )

src/core/pal/labelposition.h

+26-2
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,31 @@ namespace pal
4848

4949

5050
/**
51-
* \brief LabelPositon is a candidate feature label position
51+
* \brief LabelPosition is a candidate feature label position
5252
*/
5353
class CORE_EXPORT LabelPosition
5454
{
5555
friend class CostCalculator;
5656
friend class PolygonCostCalculator;
5757

58+
public:
59+
60+
/**
61+
* \brief Position of label candidate relative to feature.
62+
*/
63+
enum Quadrant
64+
{
65+
QuadrantAboveLeft,
66+
QuadrantAbove,
67+
QuadrantAboveRight,
68+
QuadrantLeft,
69+
QuadrantOver,
70+
QuadrantRight,
71+
QuadrantBelowLeft,
72+
QuadrantBelow,
73+
QuadrantBelowRight
74+
};
75+
5876
protected:
5977

6078
int id;
@@ -81,10 +99,13 @@ namespace pal
8199

82100
bool upsideDown;
83101

102+
LabelPosition::Quadrant quadrant;
103+
84104
bool isInConflictSinglePart( LabelPosition* lp );
85105
bool isInConflictMultiPart( LabelPosition* lp );
86106

87107
public:
108+
88109
/**
89110
* \brief create a new LabelPosition
90111
*
@@ -97,11 +118,12 @@ namespace pal
97118
* \param cost geographic cost
98119
* \param feature labelpos owners
99120
* \param isReversed label is reversed
121+
* \param quadrant relative position of label to feature
100122
*/
101123
LabelPosition( int id, double x1, double y1,
102124
double w, double h,
103125
double alpha, double cost,
104-
FeaturePart *feature, bool isReversed = false );
126+
FeaturePart *feature, bool isReversed = false, Quadrant quadrant = QuadrantOver );
105127

106128
/** copy constructor */
107129
LabelPosition( const LabelPosition& other );
@@ -215,6 +237,8 @@ namespace pal
215237
bool getReversed() const { return reversed; }
216238
bool getUpsideDown() const { return upsideDown; }
217239

240+
Quadrant getQuadrant() const { return quadrant; }
241+
218242
void print();
219243

220244
LabelPosition* getNextPart() const { return nextPart; }

src/core/qgspallabeling.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -2720,6 +2720,10 @@ void QgsPalLayerSettings::parseTextFormatting()
27202720
{
27212721
aligntype = QgsPalLayerSettings::MultiRight;
27222722
}
2723+
else if ( str.compare( "Follow", Qt::CaseInsensitive ) == 0 )
2724+
{
2725+
aligntype = QgsPalLayerSettings::MultiFollowPlacement;
2726+
}
27232727
dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant(( int )aligntype ) );
27242728
}
27252729
}
@@ -4038,6 +4042,29 @@ void QgsPalLabeling::drawLabeling( QgsRenderContext& context )
40384042
#endif
40394043
tmpLyr.textFont = dFont;
40404044

4045+
if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiFollowPlacement )
4046+
{
4047+
//calculate font alignment based on label quadrant
4048+
switch (( *it )->getQuadrant() )
4049+
{
4050+
case LabelPosition::QuadrantAboveLeft:
4051+
case LabelPosition::QuadrantLeft:
4052+
case LabelPosition::QuadrantBelowLeft:
4053+
tmpLyr.multilineAlign = QgsPalLayerSettings::MultiRight;
4054+
break;
4055+
case LabelPosition::QuadrantAbove:
4056+
case LabelPosition::QuadrantOver:
4057+
case LabelPosition::QuadrantBelow:
4058+
tmpLyr.multilineAlign = QgsPalLayerSettings::MultiCenter;
4059+
break;
4060+
case LabelPosition::QuadrantAboveRight:
4061+
case LabelPosition::QuadrantRight:
4062+
case LabelPosition::QuadrantBelowRight:
4063+
tmpLyr.multilineAlign = QgsPalLayerSettings::MultiLeft;
4064+
break;
4065+
}
4066+
}
4067+
40414068
// update tmpLyr with any data defined text style values
40424069
dataDefinedTextStyle( tmpLyr, ddValues );
40434070

src/core/qgspallabeling.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ class CORE_EXPORT QgsPalLayerSettings
120120
{
121121
MultiLeft = 0,
122122
MultiCenter,
123-
MultiRight
123+
MultiRight,
124+
MultiFollowPlacement /*< Alignment follows placement of label, eg labels to the left of a feature
125+
will be drawn with right alignment*/
124126
};
125127

126128
enum ShapeType

0 commit comments

Comments
 (0)