Skip to content

Commit

Permalink
Fix curved labels sometimes shown upside down
Browse files Browse the repository at this point in the history
And improve handling of perimeter based labels
  • Loading branch information
fritsvanveen authored and nyalldawson committed Sep 23, 2016
1 parent 10ee1c3 commit a21946a
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 102 deletions.
227 changes: 129 additions & 98 deletions src/core/pal/feature.cpp
Expand Up @@ -773,8 +773,7 @@ int FeaturePart::createCandidatesAlongLineNearStraightSegments( QList<LabelPosit
{
// find out whether the line direction for this candidate is from right to left
bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
// meaning of above/below may be reversed if using line position dependent orientation
// and the line has right-to-left direction
// meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
Expand Down Expand Up @@ -919,8 +918,7 @@ int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition*>& l
{
// find out whether the line direction for this candidate is from right to left
bool isRightToLeft = ( angle > M_PI / 2 || angle <= -M_PI / 2 );
// meaning of above/below may be reversed if using line position dependent orientation
// and the line has right-to-left direction
// meaning of above/below may be reversed if using map orientation and the line has right-to-left direction
bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );
bool aboveLine = ( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) );
bool belowLine = ( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) );
Expand Down Expand Up @@ -968,7 +966,7 @@ int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition*>& l
}


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

double string_height = li->label_height;
double old_x = path_positions->x[index-1];
double old_y = path_positions->y[index-1];

double new_x = path_positions->x[index];
double new_y = path_positions->y[index];

double dx = new_x - old_x;
double dy = new_y - old_y;

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

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

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

if ( !isUprightLabel() )
for ( int i = 0; i < li->char_num; i++ )
{
LabelInfo::CharacterInfo& ci = li->char_info[i];
double start_x, start_y, end_x, end_y;
if ( nextCharPosition( ci.width, path_distances[index], path_positions, endindex, _distance, start_x, start_y, end_x, end_y ) == false )
{
return nullptr;
}
}

// Determine the angle of the path segment under consideration
double dx = path_positions->x[endindex] - path_positions->x[index];
double dy = path_positions->y[endindex] - path_positions->y[index];
double line_angle = atan2( -dy, dx );

bool isRightToLeft = ( line_angle > 0.55 * M_PI || line_angle < -0.45 * M_PI );
reversed = isRightToLeft;
orientation = isRightToLeft ? -1 : 1;
}

if ( !showUprightLabels() )
{
if ( orientation != 1 )
if ( orientation < 0 )
{
flip = true; // Report to the caller, that the orientation is flipped
orientation = 1;
reversed = !reversed;
orientation = 1;
}
}

LabelPosition* slp = nullptr;
LabelPosition* slp_tmp = nullptr;

double old_x = path_positions->x[index-1];
double old_y = path_positions->y[index-1];

double new_x = path_positions->x[index];
double new_y = path_positions->y[index];

double dx = new_x - old_x;
double dy = new_y - old_y;

double angle = atan2( -dy, dx );

for ( int i = 0; i < li->char_num; i++ )
{
double last_character_angle = angle;

// grab the next character according to the orientation
LabelInfo::CharacterInfo& ci = ( orientation > 0 ? li->char_info[i] : li->char_info[li->char_num-i-1] );

// Coordinates this character will start at
if ( qgsDoubleNear( segment_length, 0.0 ) )
double start_x, start_y, end_x, end_y;
if ( nextCharPosition( ci.width, path_distances[index], path_positions, index, distance, start_x, start_y, end_x, end_y ) == false )
{
// Not allowed to place across on 0 length segments or discontinuities
delete slp;
return nullptr;
}

double start_x = old_x + dx * distance / segment_length;
double start_y = old_y + dy * distance / segment_length;
// Coordinates this character ends at, calculated below
double end_x = 0;
double end_y = 0;

if ( segment_length - distance >= ci.width )
{
// if the distance remaining in this segment is enough, we just go further along the segment
distance += ci.width;
end_x = old_x + dx * distance / segment_length;
end_y = old_y + dy * distance / segment_length;
}
else
{
// If there isn't enough distance left on this segment
// then we need to search until we find the line segment that ends further than ci.width away
do
{
old_x = new_x;
old_y = new_y;
index++;
if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
{
delete slp;
return nullptr;
}
new_x = path_positions->x[index];
new_y = path_positions->y[index];
dx = new_x - old_x;
dy = new_y - old_y;
segment_length = path_distances[index];
}
while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < ci.width ); // Distance from start_ to new_

// Calculate the position to place the end of the character on
GeomFunction::findLineCircleIntersection( start_x, start_y, ci.width, old_x, old_y, new_x, new_y, end_x, end_y );

// Need to calculate distance on the new segment
distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
}

// Calculate angle from the start of the character to the end based on start_/end_ position
angle = atan2( start_y - end_y, end_x - start_x );

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

// find out whether the line direction for this candidate is from right to left
bool isRightToLeft = ( lineAngle > M_PI / 2 || lineAngle <= -M_PI / 2 );

QLinkedList<LabelPosition*> positions;
double delta = qMax( li->label_height, total_distance / mLF->layer()->pal->line_p );

unsigned long flags = mLF->layer()->arrangementFlags();
if ( flags == 0 )
flags = FLAG_ON_LINE; // default flag
// placements may need to be reversed if using line position dependent orientation
// and the line has right-to-left direction
bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );

// an orientation of 0 means try both orientations and choose the best
int orientation = 0;
if ( !( flags & FLAG_MAP_ORIENTATION ) )
{
//... but if we are using line orientation flags, then we can only accept a single orientation,
// as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
orientation = reversed ? -1 : 1;
}

// generate curved labels
for ( int i = 0; i*delta < total_distance; i++ )
for ( int i = 0; i * delta < total_distance; i++ )
{
bool flip = false;
bool orientation_forced = ( orientation != 0 ); // Whether the orientation was set by the caller
LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
// placements may need to be reversed if using map orientation and the line has right-to-left direction
bool reversed = false;

// an orientation of 0 means try both orientations and choose the best
int orientation = 0;
if ( !( flags & FLAG_MAP_ORIENTATION ) )
{
//... but if we are using line orientation flags, then we can only accept a single orientation,
// as we need to ensure that the labels fall inside or outside the polyline or polygon (and not mixed)
orientation = 1;
}

LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, reversed, flip );
if ( slp == nullptr )
continue;

// If we placed too many characters upside down
if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
{
// if we auto-detected the orientation then retry with the opposite orientation
if ( !orientation_forced )
// if labels should be shown upright then retry with the opposite orientation
if (( showUprightLabels() && !flip ) )
{
orientation = -orientation;
delete slp;
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
}
else if ( isUprightLabel() && !flip )
{
// Retry with the opposite orientation
orientation = -orientation;
delete slp;
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, reversed, flip );
}
}
if ( slp == nullptr )
Expand Down Expand Up @@ -1402,7 +1373,6 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
dx = labelWidth / 2.0;
dy = labelHeight / 2.0;


int numTry = 0;

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

bool FeaturePart::isUprightLabel() const
bool FeaturePart::showUprightLabels() const
{
bool uprightLabel = false;

Expand All @@ -1796,3 +1766,64 @@ bool FeaturePart::isUprightLabel() const
return uprightLabel;
}

bool FeaturePart::nextCharPosition( int charWidth, double segment_length, PointSet* path_positions, int& index, double& distance,
double& start_x, double& start_y, double& end_x, double& end_y ) const
{
// Coordinates this character will start at
if ( qgsDoubleNear( segment_length, 0.0 ) )
{
// Not allowed to place across on 0 length segments or discontinuities
return false;
}

double old_x = path_positions->x[index-1];
double old_y = path_positions->y[index-1];

double new_x = path_positions->x[index];
double new_y = path_positions->y[index];

double dx = new_x - old_x;
double dy = new_y - old_y;

start_x = old_x + dx * distance / segment_length;
start_y = old_y + dy * distance / segment_length;

// Coordinates this character ends at, calculated below
end_x = 0;
end_y = 0;

if ( segment_length - distance >= charWidth )
{
// if the distance remaining in this segment is enough, we just go further along the segment
distance += charWidth;
end_x = old_x + dx * distance / segment_length;
end_y = old_y + dy * distance / segment_length;
}
else
{
// If there isn't enough distance left on this segment
// then we need to search until we find the line segment that ends further than ci.width away
do
{
old_x = new_x;
old_y = new_y;
index++;
if ( index >= path_positions->nbPoints ) // Bail out if we run off the end of the shape
{
return false;
}
new_x = path_positions->x[index];
new_y = path_positions->y[index];
dx = new_x - old_x;
dy = new_y - old_y;
}
while ( sqrt( pow( start_x - new_x, 2 ) + pow( start_y - new_y, 2 ) ) < charWidth ); // Distance from start_ to new_

// Calculate the position to place the end of the character on
GeomFunction::findLineCircleIntersection( start_x, start_y, charWidth, old_x, old_y, new_x, new_y, end_x, end_y );

// Need to calculate distance on the new segment
distance = sqrt( pow( old_x - end_x, 2 ) + pow( old_y - end_y, 2 ) );
}
return true;
}
11 changes: 8 additions & 3 deletions src/core/pal/feature.h
Expand Up @@ -183,11 +183,12 @@ namespace pal
* @param orientation can be 0 for automatic calculation of orientation, or -1/+1 for a specific label orientation
* @param index
* @param distance distance to offset label along curve by
* @param flip
* @param reversed if true label is reversed from lefttoright to righttoleft
* @param flip if true label is placed on the other side of the line
* @returns calculated label position
*/
LabelPosition* curvedPlacementAtOffset( PointSet* path_positions, double* path_distances,
int& orientation, int index, double distance, bool& flip );
int& orientation, int index, double distance, bool& reversed, bool& flip );

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

//! Returns true if feature's label must be displayed upright
bool isUprightLabel() const;
bool showUprightLabels() const;

//! Returns true if the next char position is found. The referenced parameters are updated.
bool nextCharPosition( int charWidth, double segment_length, PointSet* path_positions, int& index, double& distance,
double& start_x, double& start_y, double& end_x, double& end_y ) const;

protected:

Expand Down
2 changes: 1 addition & 1 deletion src/core/pal/labelposition.cpp
Expand Up @@ -100,7 +100,7 @@ LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h,
if ( !feature->layer()->isCurved() &&
this->alpha > M_PI / 2 && this->alpha <= 3*M_PI / 2 )
{
if ( feature->isUprightLabel() )
if ( feature->showUprightLabels() )
{
// Turn label upsidedown by inverting boundary points
double tx, ty;
Expand Down

4 comments on commit a21946a

@nirvn
Copy link
Contributor

@nirvn nirvn commented on a21946a Sep 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fritsvanveen , @nyalldawson , a regression slipped in master with regards to curved labels, see this issue (http://hub.qgis.org/issues/15639) and the video showcasing the problem (http://hub.qgis.org/attachments/10423/curved_bug.mp4)

@fritsvanveen
Copy link
Contributor Author

@fritsvanveen fritsvanveen commented on a21946a Sep 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nyalldawson I'd like to look into this, but my (Windows) build environment is broken, since Qt5 was mandatory. I might need some help there. I emailed Jürgen, but he hasn't responded yet. Do you know someone, who can help me?

@jef-n
Copy link
Member

@jef-n jef-n commented on a21946a Sep 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a qt5 environment on windows either yet.

@fritsvanveen
Copy link
Contributor Author

@fritsvanveen fritsvanveen commented on a21946a Oct 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nyalldawson @jef-n After three days of struggling, I did it! I have QGIS building with Qt5. Not all projects build and I get Python errors, bit I can at least do something. Do the versions look good to you?
2016-10-06 11_00_49-qgis 2 99 0-master

Please sign in to comment.