Skip to content

Commit

Permalink
'Using perimeter (curved)' labels on polygon layers will now respect …
Browse files Browse the repository at this point in the history
…'Show upside-down labels' setting.
  • Loading branch information
fritsvanveen authored and nyalldawson committed Aug 17, 2016
1 parent d6b419a commit 34e2bea
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 138 deletions.
232 changes: 127 additions & 105 deletions src/core/pal/feature.cpp
Expand Up @@ -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 )
Expand All @@ -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];
Expand All @@ -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++ )
{
Expand Down Expand Up @@ -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
Expand All @@ -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 );

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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++ )
Expand All @@ -1336,9 +1338,6 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
return nbp;
}




/*
* seg 2
* pt3 ____________pt2
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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;
}

33 changes: 25 additions & 8 deletions src/core/pal/feature.h
Expand Up @@ -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
Expand Down Expand Up @@ -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(); }
Expand All @@ -242,6 +257,8 @@ namespace pal
*/
double calculatePriority() const;

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

protected:

Expand Down

12 comments on commit 34e2bea

@fritsvanveen
Copy link
Contributor Author

@fritsvanveen fritsvanveen commented on 34e2bea Aug 25, 2016

Choose a reason for hiding this comment

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

@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
Copy link
Contributor Author

@fritsvanveen fritsvanveen commented on 34e2bea Aug 26, 2016

Choose a reason for hiding this comment

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

@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
Copy link
Collaborator

Choose a reason for hiding this comment

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

@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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

@fritsvanveen
Copy link
Contributor Author

@fritsvanveen fritsvanveen commented on 34e2bea Sep 2, 2016

Choose a reason for hiding this comment

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

@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
Copy link
Collaborator

@nyalldawson nyalldawson commented on 34e2bea Sep 3, 2016

Choose a reason for hiding this comment

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

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

@fritsvanveen
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

@fritsvanveen
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes - sounds like that comment is wrong

@fritsvanveen
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@fritsvanveen
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

@fritsvanveen
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nyalldawson Cleaned up code committed.

Please sign in to comment.