Skip to content

Commit a21946a

Browse files
fritsvanveennyalldawson
authored andcommitted
Fix curved labels sometimes shown upside down
And improve handling of perimeter based labels
1 parent 10ee1c3 commit a21946a

File tree

3 files changed

+138
-102
lines changed

3 files changed

+138
-102
lines changed

src/core/pal/feature.cpp

Lines changed: 129 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -773,8 +773,7 @@ int FeaturePart::createCandidatesAlongLineNearStraightSegments( QList<LabelPosit
773773
{
774774
// find out whether the line direction for this candidate is from right to left
775775
bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
776-
// meaning of above/below may be reversed if using line position dependent orientation
777-
// and the line has right-to-left direction
776+
// meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
778777
bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
779778
bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
780779
bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
@@ -919,8 +918,7 @@ int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition*>& l
919918
{
920919
// find out whether the line direction for this candidate is from right to left
921920
bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
922-
// meaning of above/below may be reversed if using line position dependent orientation
923-
// and the line has right-to-left direction
921+
// meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
924922
bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
925923
bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
926924
bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
@@ -968,7 +966,7 @@ int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition*>& l
968966
}
969967

970968

971-
LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int& orientation, int index, double distance, bool& flip )
969+
LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int& orientation, int index, double distance, bool& reversed, bool& flip )
972970
{
973971
// Check that the given distance is on the given index and find the correct index and distance if not
974972
while ( distance < 0 && index > 1 )
@@ -996,14 +994,6 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d
996994
LabelInfo* li = mLF->curvedLabelInfo();
997995

998996
double string_height = li->label_height;
999-
double old_x = path_positions->x[index-1];
1000-
double old_y = path_positions->y[index-1];
1001-
1002-
double new_x = path_positions->x[index];
1003-
double new_y = path_positions->y[index];
1004-
1005-
double dx = new_x - old_x;
1006-
double dy = new_y - old_y;
1007997

1008998
double segment_length = path_distances[index];
1009999
if ( qgsDoubleNear( segment_length, 0.0 ) )
@@ -1012,78 +1002,70 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d
10121002
return nullptr;
10131003
}
10141004

1015-
LabelPosition* slp = nullptr;
1016-
LabelPosition* slp_tmp = nullptr;
1017-
double angle = atan2( -dy, dx );
1005+
if ( orientation == 0 ) // Must be map orientation
1006+
{
1007+
// Calculate the orientation based on the angle of the path segment under consideration
10181008

1019-
bool orientation_forced = ( orientation != 0 ); // Whether the orientation was set by the caller
1020-
if ( !orientation_forced )
1021-
orientation = ( angle > 0.55 * M_PI || angle < -0.45 * M_PI ? -1 : 1 );
1009+
double _distance = distance;
1010+
int endindex = index;
10221011

1023-
if ( !isUprightLabel() )
1012+
for ( int i = 0; i < li->char_num; i++ )
1013+
{
1014+
LabelInfo::CharacterInfo& ci = li->char_info[i];
1015+
double start_x, start_y, end_x, end_y;
1016+
if ( nextCharPosition( ci.width, path_distances[index], path_positions, endindex, _distance, start_x, start_y, end_x, end_y ) == false )
1017+
{
1018+
return nullptr;
1019+
}
1020+
}
1021+
1022+
// Determine the angle of the path segment under consideration
1023+
double dx = path_positions->x[endindex] - path_positions->x[index];
1024+
double dy = path_positions->y[endindex] - path_positions->y[index];
1025+
double line_angle = atan2( -dy, dx );
1026+
1027+
bool isRightToLeft = ( line_angle > 0.55 * M_PI || line_angle < -0.45 * M_PI );
1028+
reversed = isRightToLeft;
1029+
orientation = isRightToLeft ? -1 : 1;
1030+
}
1031+
1032+
if ( !showUprightLabels() )
10241033
{
1025-
if ( orientation != 1 )
1034+
if ( orientation < 0 )
1035+
{
10261036
flip = true; // Report to the caller, that the orientation is flipped
1027-
orientation = 1;
1037+
reversed = !reversed;
1038+
orientation = 1;
1039+
}
10281040
}
10291041

1042+
LabelPosition* slp = nullptr;
1043+
LabelPosition* slp_tmp = nullptr;
1044+
1045+
double old_x = path_positions->x[index-1];
1046+
double old_y = path_positions->y[index-1];
1047+
1048+
double new_x = path_positions->x[index];
1049+
double new_y = path_positions->y[index];
1050+
1051+
double dx = new_x - old_x;
1052+
double dy = new_y - old_y;
1053+
1054+
double angle = atan2( -dy, dx );
1055+
10301056
for ( int i = 0; i < li->char_num; i++ )
10311057
{
10321058
double last_character_angle = angle;
10331059

10341060
// grab the next character according to the orientation
10351061
LabelInfo::CharacterInfo& ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num-i-1] );
1036-
1037-
// Coordinates this character will start at
1038-
if ( qgsDoubleNear( segment_length, 0.0 ) )
1062+
double start_x, start_y, end_x, end_y;
1063+
if ( nextCharPosition( ci.width, path_distances[index], path_positions, index, distance, start_x, start_y, end_x, end_y ) == false )
10391064
{
1040-
// Not allowed to place across on 0 length segments or discontinuities
10411065
delete slp;
10421066
return nullptr;
10431067
}
10441068

1045-
double start_x = old_x + dx * distance / segment_length;
1046-
double start_y = old_y + dy * distance / segment_length;
1047-
// Coordinates this character ends at, calculated below
1048-
double end_x = 0;
1049-
double end_y = 0;
1050-
1051-
if ( segment_length - distance >= ci.width )
1052-
{
1053-
// if the distance remaining in this segment is enough, we just go further along the segment
1054-
distance += ci.width;
1055-
end_x = old_x + dx * distance / segment_length;
1056-
end_y = old_y + dy * distance / segment_length;
1057-
}
1058-
else
1059-
{
1060-
// If there isn't enough distance left on this segment
1061-
// then we need to search until we find the line segment that ends further than ci.width away
1062-
do
1063-
{
1064-
old_x = new_x;
1065-
old_y = new_y;
1066-
index++;
1067-
if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1068-
{
1069-
delete slp;
1070-
return nullptr;
1071-
}
1072-
new_x = path_positions->x[index];
1073-
new_y = path_positions->y[index];
1074-
dx = new_x - old_x;
1075-
dy = new_y - old_y;
1076-
segment_length = path_distances[index];
1077-
}
1078-
while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < ci.width ); // Distance from start_ to new_
1079-
1080-
// Calculate the position to place the end of the character on
1081-
GeomFunction::findLineCircleIntersection( start_x, start_y, ci.width, old_x, old_y, new_x, new_y, end_x, end_y );
1082-
1083-
// Need to calculate distance on the new segment
1084-
distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
1085-
}
1086-
10871069
// Calculate angle from the start of the character to the end based on start_/end_ position
10881070
angle = atan2( start_y - end_y, end_x - start_x );
10891071

@@ -1201,53 +1183,42 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
12011183
else
12021184
lineAngle = atan2( ey - by, ex - bx );
12031185

1204-
// find out whether the line direction for this candidate is from right to left
1205-
bool isRightToLeft = ( lineAngle > M_PI / 2 || lineAngle <= -M_PI / 2 );
1206-
12071186
QLinkedList<LabelPosition*> positions;
12081187
double delta = qMax( li->label_height, total_distance / mLF->layer()->pal->line_p );
12091188

12101189
unsigned long flags = mLF->layer()->arrangementFlags();
12111190
if ( flags == 0 )
12121191
flags = FLAG_ON_LINE; // default flag
1213-
// placements may need to be reversed if using line position dependent orientation
1214-
// and the line has right-to-left direction
1215-
bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
1216-
1217-
// an orientation of 0 means try both orientations and choose the best
1218-
int orientation = 0;
1219-
if ( !( flags & FLAG_MAP_ORIENTATION ) )
1220-
{
1221-
//... but if we are using line orientation flags, then we can only accept a single orientation,
1222-
// as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1223-
orientation = reversed ? -1 : 1;
1224-
}
12251192

12261193
// generate curved labels
1227-
for ( int i = 0; i*delta < total_distance; i++ )
1194+
for ( int i = 0; i * delta < total_distance; i++ )
12281195
{
12291196
bool flip = false;
1230-
bool orientation_forced = ( orientation != 0 ); // Whether the orientation was set by the caller
1231-
LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
1197+
// placements may need to be reversed if using map orientation and the line has right-to-left direction
1198+
bool reversed = false;
1199+
1200+
// an orientation of 0 means try both orientations and choose the best
1201+
int orientation = 0;
1202+
if ( !( flags & FLAG_MAP_ORIENTATION ) )
1203+
{
1204+
//... but if we are using line orientation flags, then we can only accept a single orientation,
1205+
// as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
1206+
orientation = 1;
1207+
}
1208+
1209+
LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, reversed, flip );
12321210
if ( slp == nullptr )
12331211
continue;
12341212

12351213
// If we placed too many characters upside down
12361214
if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
12371215
{
1238-
// if we auto-detected the orientation then retry with the opposite orientation
1239-
if ( !orientation_forced )
1216+
// if labels should be shown upright then retry with the opposite orientation
1217+
if (( showUprightLabels() && !flip ) )
12401218
{
1241-
orientation = -orientation;
12421219
delete slp;
1243-
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
1244-
}
1245-
else if ( isUprightLabel() && !flip )
1246-
{
1247-
// Retry with the opposite orientation
12481220
orientation = -orientation;
1249-
delete slp;
1250-
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
1221+
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, reversed, flip );
12511222
}
12521223
}
12531224
if ( slp == nullptr )
@@ -1402,7 +1373,6 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
14021373
dx = labelWidth / 2.0;
14031374
dy = labelHeight / 2.0;
14041375

1405-
14061376
int numTry = 0;
14071377

14081378
//fit in polygon only mode slows down calculation a lot, so if it's enabled
@@ -1772,7 +1742,7 @@ double FeaturePart::calculatePriority() const
17721742
return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
17731743
}
17741744

1775-
bool FeaturePart::isUprightLabel() const
1745+
bool FeaturePart::showUprightLabels() const
17761746
{
17771747
bool uprightLabel = false;
17781748

@@ -1796,3 +1766,64 @@ bool FeaturePart::isUprightLabel() const
17961766
return uprightLabel;
17971767
}
17981768

1769+
bool FeaturePart::nextCharPosition( int charWidth, double segment_length, PointSet* path_positions, int& index, double& distance,
1770+
double& start_x, double& start_y, double& end_x, double& end_y ) const
1771+
{
1772+
// Coordinates this character will start at
1773+
if ( qgsDoubleNear( segment_length, 0.0 ) )
1774+
{
1775+
// Not allowed to place across on 0 length segments or discontinuities
1776+
return false;
1777+
}
1778+
1779+
double old_x = path_positions->x[index-1];
1780+
double old_y = path_positions->y[index-1];
1781+
1782+
double new_x = path_positions->x[index];
1783+
double new_y = path_positions->y[index];
1784+
1785+
double dx = new_x - old_x;
1786+
double dy = new_y - old_y;
1787+
1788+
start_x = old_x + dx * distance / segment_length;
1789+
start_y = old_y + dy * distance / segment_length;
1790+
1791+
// Coordinates this character ends at, calculated below
1792+
end_x = 0;
1793+
end_y = 0;
1794+
1795+
if ( segment_length - distance >= charWidth )
1796+
{
1797+
// if the distance remaining in this segment is enough, we just go further along the segment
1798+
distance += charWidth;
1799+
end_x = old_x + dx * distance / segment_length;
1800+
end_y = old_y + dy * distance / segment_length;
1801+
}
1802+
else
1803+
{
1804+
// If there isn't enough distance left on this segment
1805+
// then we need to search until we find the line segment that ends further than ci.width away
1806+
do
1807+
{
1808+
old_x = new_x;
1809+
old_y = new_y;
1810+
index++;
1811+
if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
1812+
{
1813+
return false;
1814+
}
1815+
new_x = path_positions->x[index];
1816+
new_y = path_positions->y[index];
1817+
dx = new_x - old_x;
1818+
dy = new_y - old_y;
1819+
}
1820+
while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < charWidth ); // Distance from start_ to new_
1821+
1822+
// Calculate the position to place the end of the character on
1823+
GeomFunction::findLineCircleIntersection( start_x, start_y, charWidth, old_x, old_y, new_x, new_y, end_x, end_y );
1824+
1825+
// Need to calculate distance on the new segment
1826+
distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
1827+
}
1828+
return true;
1829+
}

src/core/pal/feature.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,12 @@ namespace pal
183183
* @param orientation can be 0 for automatic calculation of orientation, or -1/+1 for a specific label orientation
184184
* @param index
185185
* @param distance distance to offset label along curve by
186-
* @param flip
186+
* @param reversed if true label is reversed from lefttoright to righttoleft
187+
* @param flip if true label is placed on the other side of the line
187188
* @returns calculated label position
188189
*/
189190
LabelPosition* curvedPlacementAtOffset( PointSet* path_positions, double* path_distances,
190-
int& orientation, int index, double distance, bool& flip );
191+
int& orientation, int index, double distance, bool& reversed, bool& flip );
191192

192193
/** Generate curved candidates for line features.
193194
* @param lPos pointer to an array of candidates, will be filled by generated candidates
@@ -267,7 +268,11 @@ namespace pal
267268
double calculatePriority() const;
268269

269270
//! Returns true if feature's label must be displayed upright
270-
bool isUprightLabel() const;
271+
bool showUprightLabels() const;
272+
273+
//! Returns true if the next char position is found. The referenced parameters are updated.
274+
bool nextCharPosition( int charWidth, double segment_length, PointSet* path_positions, int& index, double& distance,
275+
double& start_x, double& start_y, double& end_x, double& end_y ) const;
271276

272277
protected:
273278

src/core/pal/labelposition.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h,
100100
if ( !feature->layer()->isCurved() &&
101101
this->alpha > M_PI / 2 && this->alpha <= 3*M_PI / 2 )
102102
{
103-
if ( feature->isUprightLabel() )
103+
if ( feature->showUprightLabels() )
104104
{
105105
// Turn label upsidedown by inverting boundary points
106106
double tx, ty;

0 commit comments

Comments
 (0)