Skip to content
Permalink
Browse files

'Using perimeter (curved)' labels on polygon layers will now respect …

…'Show upside-down labels' setting.
  • Loading branch information
fritsvanveen authored and nyalldawson committed Aug 4, 2016
1 parent d6b419a commit 34e2bea24635e15e981ef915219b1095f51bbe90
Showing with 169 additions and 138 deletions.
  1. +127 −105 src/core/pal/feature.cpp
  2. +25 −8 src/core/pal/feature.h
  3. +6 −24 src/core/pal/labelposition.cpp
  4. +6 −0 src/core/pal/labelposition.h
  5. +4 −0 src/core/pal/layer.h
  6. +1 −1 src/core/pal/problem.cpp
@@ -968,7 +968,7 @@ int FeaturePart::createCandidatesAlongLineNearMidpoint( QList<LabelPosition*>& l
}


LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int orientation, int index, double distance )
LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int& orientation, int index, double distance, 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 )
@@ -995,10 +995,6 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d

LabelInfo* li = mLF->curvedLabelInfo();

// Keep track of the initial index,distance incase we need to re-call get_placement_offset
int initial_index = index;
double initial_distance = distance;

double string_height = li->label_height;
double old_x = path_positions->x[index-1];
double old_y = path_positions->y[index-1];
@@ -1018,14 +1014,18 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d

LabelPosition* slp = nullptr;
LabelPosition* slp_tmp = nullptr;
// current_placement = placement_result()
double angle = atan2( -dy, dx );

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 );

int upside_down_char_count = 0; // Count of characters that are placed upside down.
if ( !isUprightLabel() )
{
if ( orientation != 1 )
flip = true; // Report to the caller, that the orientation is flipped
orientation = 1;
}

for ( int i = 0; i < li->char_num; i++ )
{
@@ -1086,7 +1086,6 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d

// 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 );
//angle = atan2(end_y-start_y, end_x-start_x);

// Test last_character_angle vs angle
// since our rendering angle has changed then check against our
@@ -1108,7 +1107,10 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d
// and we're calculating the mean line here
double dist = 0.9 * li->label_height / 2;
if ( orientation < 0 )
{
dist = -dist;
flip = true;
}
start_x += dist * cos( angle + M_PI_2 );
start_y -= dist * sin( angle + M_PI_2 );

@@ -1137,36 +1139,15 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d
slp_tmp->setNextPart( tmp );
slp_tmp = tmp;

//current_placement.add_node(ci.character,render_x, -render_y, render_angle);
//current_placement.add_node(ci.character,render_x - current_placement.starting_x, render_y - current_placement.starting_y, render_angle)

// Normalise to 0 <= angle < 2PI
while ( render_angle >= 2*M_PI ) render_angle -= 2 * M_PI;
while ( render_angle >= 2 * M_PI ) render_angle -= 2 * M_PI;
while ( render_angle < 0 ) render_angle += 2 * M_PI;

if ( render_angle > M_PI / 2 && render_angle < 1.5*M_PI )
upside_down_char_count++;
if ( render_angle > M_PI / 2 && render_angle < 1.5 * M_PI )
slp->incrementUpsideDownCharCount();
}
// END FOR

// If we placed too many characters upside down
if ( upside_down_char_count >= li->char_num / 2.0 )
{
// if we auto-detected the orientation then retry with the opposite orientation
if ( !orientation_forced )
{
orientation = -orientation;
delete slp;
slp = curvedPlacementAtOffset( path_positions, path_distances, orientation, initial_index, initial_distance );
}
else
{
// Otherwise we have failed to find a placement
delete slp;
return nullptr;
}
}

return slp;
}

@@ -1231,99 +1212,120 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
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 );
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 )
&& mLF->layer()->arrangement() == QgsPalLayerSettings::PerimeterCurved )
if ( !( flags & FLAG_MAP_ORIENTATION ) )
{
//... but if we are labeling the perimeter of a polygon and 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 polygon (and not mixed)
//... 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++ )
{
LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta );
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 );
if ( slp == nullptr )
continue;

if ( slp )
// If we placed too many characters upside down
if ( slp->upsideDownCharCount() >= li->char_num / 2.0 )
{
// evaluate cost
double angle_diff = 0.0, angle_last = 0.0, diff;
LabelPosition* tmp = slp;
double sin_avg = 0, cos_avg = 0;
while ( tmp )
// if we auto-detected the orientation then retry with the opposite orientation
if ( !orientation_forced )
{
if ( tmp != slp ) // not first?
{
diff = fabs( tmp->getAlpha() - angle_last );
if ( diff > 2*M_PI ) diff -= 2 * M_PI;
diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
angle_diff += diff;
}
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 );
}
}
if ( slp == nullptr )
continue;

sin_avg += sin( tmp->getAlpha() );
cos_avg += cos( tmp->getAlpha() );
angle_last = tmp->getAlpha();
tmp = tmp->getNextPart();
// evaluate cost
double angle_diff = 0.0, angle_last = 0.0, diff;
LabelPosition* tmp = slp;
double sin_avg = 0, cos_avg = 0;
while ( tmp )
{
if ( tmp != slp ) // not first?
{
diff = fabs( tmp->getAlpha() - angle_last );
if ( diff > 2*M_PI ) diff -= 2 * M_PI;
diff = qMin( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
angle_diff += diff;
}

double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
if ( cost < 0.0001 ) cost = 0.0001;
sin_avg += sin( tmp->getAlpha() );
cos_avg += cos( tmp->getAlpha() );
angle_last = tmp->getAlpha();
tmp = tmp->getNextPart();
}

double angle_diff_avg = li->char_num > 1 ? ( angle_diff / ( li->char_num - 1 ) ) : 0; // <0, pi> but pi/8 is much already
double cost = angle_diff_avg / 100; // <0, 0.031 > but usually <0, 0.003 >
if ( cost < 0.0001 ) cost = 0.0001;

// penalize positions which are further from the line's midpoint
double labelCenter = ( i * delta ) + getLabelWidth() / 2;
double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
cost += costCenter / 1000; // < 0, 0.0005 >
slp->setCost( cost );
// penalize positions which are further from the line's midpoint
double labelCenter = ( i * delta ) + getLabelWidth() / 2;
double costCenter = qAbs( total_distance / 2 - labelCenter ) / total_distance; // <0, 0.5>
cost += costCenter / 1000; // < 0, 0.0005 >
slp->setCost( cost );

// average angle is calculated with respect to periodicity of angles
double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
// displacement - we loop through 3 times, generating above, online then below line placements successively
for ( int i = 0; i <= 2; ++i )
// average angle is calculated with respect to periodicity of angles
double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
bool localreversed = flip ? !reversed : reversed;
// displacement - we loop through 3 times, generating above, online then below line placements successively
for ( int i = 0; i <= 2; ++i )
{
LabelPosition* p = nullptr;
if ( i == 0 && (( !localreversed && ( flags & FLAG_ABOVE_LINE ) ) || ( localreversed && ( flags & FLAG_BELOW_LINE ) ) ) )
p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
if ( i == 1 && flags & FLAG_ON_LINE )
{
LabelPosition* p = nullptr;
if ( i == 0 && (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) ) )
p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
if ( i == 1 && flags & FLAG_ON_LINE )
{
p = _createCurvedCandidate( slp, angle_avg, 0 );
p->setCost( p->cost() + 0.002 );
}
if ( i == 2 && (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
p = _createCurvedCandidate( slp, angle_avg, 0 );
p->setCost( p->cost() + 0.002 );
}
if ( i == 2 && (( !localreversed && ( flags & FLAG_BELOW_LINE ) ) || ( localreversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
{
p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
p->setCost( p->cost() + 0.001 );
}

if ( p && mLF->permissibleZonePrepared() )
{
bool within = true;
LabelPosition* currentPos = p;
while ( within && currentPos )
{
p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
p->setCost( p->cost() + 0.001 );
within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
currentPos = currentPos->getNextPart();
}

if ( p && mLF->permissibleZonePrepared() )
if ( !within )
{
bool within = true;
LabelPosition* currentPos = p;
while ( within && currentPos )
{
within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
currentPos = currentPos->getNextPart();
}
if ( !within )
{
delete p;
p = nullptr;
}
delete p;
p = nullptr;
}

if ( p )
positions.append( p );
}
// delete original candidate
delete slp;

if ( p )
positions.append( p );
}
}

// delete original candidate
delete slp;
}

int nbp = positions.size();
for ( int i = 0; i < nbp; i++ )
@@ -1336,9 +1338,6 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
return nbp;
}




/*
* seg 2
* pt3 ____________pt2
@@ -1588,9 +1587,7 @@ int FeaturePart::createCandidates( QList< LabelPosition*>& lPos,
createCandidatesAroundPoint( x[0], y[0], lPos, angle );
break;
case GEOS_LINESTRING:
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Curved )
createCurvedCandidatesAlongLine( lPos, mapShape );
else if ( mLF->layer()->arrangement() == QgsPalLayerSettings::PerimeterCurved )
if ( mLF->layer()->isCurved() )
createCurvedCandidatesAlongLine( lPos, mapShape );
else
createCandidatesAlongLine( lPos, mapShape );
@@ -1774,3 +1771,28 @@ double FeaturePart::calculatePriority() const

return mLF->priority() >= 0 ? mLF->priority() : mLF->layer()->priority();
}

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

switch ( mLF->layer()->upsidedownLabels() )
{
case Layer::Upright:
uprightLabel = true;
break;
case Layer::ShowDefined:
// upright only dynamic labels
if ( !hasFixedRotation() || ( !hasFixedPosition() && fixedAngle() == 0.0 ) )
{
uprightLabel = true;
}
break;
case Layer::ShowAll:
break;
default:
uprightLabel = true;
}
return uprightLabel;
}

@@ -178,7 +178,7 @@ namespace pal
int createCandidatesAlongLineNearMidpoint( QList<LabelPosition *> &lPos, PointSet *mapShape, double initialCost = 0.0 );

LabelPosition* curvedPlacementAtOffset( PointSet* path_positions, double* path_distances,
int orientation, int index, double distance );
int& orientation, int index, double distance, bool& flip );

/** Generate curved candidates for line features.
* @param lPos pointer to an array of candidates, will be filled by generated candidates
@@ -213,13 +213,28 @@ namespace pal
double getLabelHeight() const { return mLF->size().height(); }
double getLabelDistance() const { return mLF->distLabel(); }

bool getFixedRotation() { return mLF->hasFixedAngle(); }
double getLabelAngle() { return mLF->fixedAngle(); }
bool getFixedPosition() { return mLF->hasFixedPosition(); }
bool getAlwaysShow() { return mLF->alwaysShow(); }
bool isObstacle() { return mLF->isObstacle(); }
double obstacleFactor() { return mLF->obstacleFactor(); }
double repeatDistance() { return mLF->repeatDistance(); }
//! Returns true if the feature's label has a fixed rotation
bool hasFixedRotation() const { return mLF->hasFixedAngle(); }

//! Returns the fixed angle for the feature's label
double fixedAngle() const { return mLF->fixedAngle(); }

//! Returns true if the feature's label has a fixed position
bool hasFixedPosition() const { return mLF->hasFixedPosition(); }

//! Returns true if the feature's label should always been shown,
//! even when it collides with other labels
bool alwaysShow() const { return mLF->alwaysShow(); }

//! Returns true if the feature should act as an obstacle to labels
bool isObstacle() const { return mLF->isObstacle(); }

//! Returns the feature's obstacle factor, which represents the penalty
//! incurred for a label to overlap the feature
double obstacleFactor() const { return mLF->obstacleFactor(); }

//! Returns the distance between repeating labels for this feature
double repeatDistance() const { return mLF->repeatDistance(); }

//! Get number of holes (inner rings) - they are considered as obstacles
int getNumSelfObstacles() const { return mHoles.count(); }
@@ -242,6 +257,8 @@ namespace pal
*/
double calculatePriority() const;

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

protected:

12 comments on commit 34e2bea

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Aug 25, 2016

@nyalldawson
labels on wrong side of polygon
I have one layer, where the labels are shown on the wrong size of the polygon. They should be on the inside. Is there such a thing as an inverted polygon in a database? I've also drawn an inner glow, and that is shown correctly. I can see in QgsSymbol::renderFeature that a MultiPolygon is drawn, but that is also true for other layers, which are ok. I'm a bit at a loss here.

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Aug 26, 2016

@nyalldawson
2016-08-26 09_19_21-qgis 2 16 1-nodebo - test grenzen
2016-08-26 09_19_36-qgis 2 99 0 - test grenzend
I've attached some more examples. Only Katwijk is displayed correctly in 2.99.0, that is with labels inside the polygon. In 2.16.1 the polygons which are cutoff are also displayed with labels inside. Can you provide some hints how I can proceed to investigate this?
Oh, curved or straight labels exhibit the same problem.

@nyalldawson

This comment has been minimized.

Copy link
Collaborator

@nyalldawson nyalldawson replied Aug 29, 2016

@fritsvanveen my usual approach here is to enable labels both above and below the line, and then enable the option to only draw labels which fit inside the polygon.

BTW - i've noticed since your last commit re upside down labels that many curved labels for line geometry layers are now drawn incorrectly upside down. Are you able to look into this?

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Aug 29, 2016

@nyalldawson Also for polygon layers. I'll look into it.

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Sep 2, 2016

@nyalldawson Just a quick update. The upsidedown bug is fixed. Variable 'orientation' should have been primed inside the for loop. However, with 'Line orientation dependent position' off, some labels are plotted on the wrong side of the line.

@nyalldawson

This comment has been minimized.

Copy link
Collaborator

@nyalldawson nyalldawson replied Sep 3, 2016

@fritsvanveen great! is there an updated commit I should be looking at, or will you update the PR?

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Sep 3, 2016

@nyalldawson Not yet, let's wait until I fixed the other bug.

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Sep 3, 2016

@nyalldawson I'm confused about these lines in createCandidatesAlongLineNearStraightSegment

    // meaning of above/below may be reversed if using line position dependent orientation
    // and the line has right-to-left direction
    bool reversed = (( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );

Should the comment not read 'using map orientation'

@nyalldawson

This comment has been minimized.

Copy link
Collaborator

@nyalldawson nyalldawson replied Sep 3, 2016

Yes - sounds like that comment is wrong

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Sep 4, 2016

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Sep 4, 2016

@nyalldawson Made another commit on my fork https://github.com/fritsvanveen/QGIS
Ik works now, but I want to clean up the code.

@fritsvanveen

This comment has been minimized.

Copy link
Contributor Author

@fritsvanveen fritsvanveen replied Sep 5, 2016

@nyalldawson Cleaned up code committed.

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