'Using perimeter (curved)' labels on polygon layers will now respect 'Show upside-down labels' setting.

This commit is contained in:
Frits van Veen 2016-08-04 14:53:19 +02:00 committed by Nyall Dawson
parent d6b419ab18
commit 34e2bea246
6 changed files with 177 additions and 146 deletions

View File

@ -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;
}
sin_avg += sin( tmp->getAlpha() );
cos_avg += cos( tmp->getAlpha() );
angle_last = tmp->getAlpha();
tmp = tmp->getNextPart();
orientation = -orientation;
delete slp;
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
}
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 );
// 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 )
else if ( isUprightLabel() && !flip )
{
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, -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 )
{
within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
currentPos = currentPos->getNextPart();
}
if ( !within )
{
delete p;
p = nullptr;
}
}
if ( p )
positions.append( p );
// Retry with the opposite orientation
orientation = -orientation;
delete slp;
slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta, flip );
}
// delete original candidate
delete slp;
}
}
if ( slp == nullptr )
continue;
// 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;
}
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 );
// 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 )
{
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 )
{
within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
currentPos = currentPos->getNextPart();
}
if ( !within )
{
delete p;
p = nullptr;
}
}
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;
}

View File

@ -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:

View File

@ -60,6 +60,7 @@ LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h,
, quadrant( quadrant )
, mCost( cost )
, mHasObstacleConflict( false )
, mUpsideDownCharCount( 0 )
{
type = GEOS_POLYGON;
nbPoints = 4;
@ -77,8 +78,6 @@ LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h,
double dx1, dx2, dy1, dy2;
double tx, ty;
dx1 = cos( this->alpha ) * w;
dy1 = sin( this->alpha ) * w;
@ -98,32 +97,14 @@ LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h,
y[3] = y1 + dy2;
// upside down ? (curved labels are always correct)
if ( feature->layer()->arrangement() != QgsPalLayerSettings::Curved &&
feature->layer()->arrangement() != QgsPalLayerSettings::PerimeterCurved &&
if ( !feature->layer()->isCurved() &&
this->alpha > M_PI / 2 && this->alpha <= 3*M_PI / 2 )
{
bool uprightLabel = false;
switch ( feature->layer()->upsidedownLabels() )
if ( feature->isUprightLabel() )
{
case Layer::Upright:
uprightLabel = true;
break;
case Layer::ShowDefined:
// upright only dynamic labels
if ( !feature->getFixedRotation() || ( !feature->getFixedPosition() && feature->getLabelAngle() == 0.0 ) )
{
uprightLabel = true;
}
break;
case Layer::ShowAll:
break;
default:
uprightLabel = true;
}
// Turn label upsidedown by inverting boundary points
double tx, ty;
if ( uprightLabel )
{
tx = x[0];
ty = y[0];
@ -184,6 +165,7 @@ LabelPosition::LabelPosition( const LabelPosition& other )
reversed = other.reversed;
quadrant = other.quadrant;
mHasObstacleConflict = other.mHasObstacleConflict;
mUpsideDownCharCount = other.mUpsideDownCharCount;
}
bool LabelPosition::isIn( double *bbox )

View File

@ -228,6 +228,11 @@ namespace pal
int getPartId() const { return partId; }
void setPartId( int id ) { partId = id; }
//! Increases the count of upside down characters for this label position
int incrementUpsideDownCharCount() { return ++mUpsideDownCharCount; }
//! Returns the number of upside down characters for this label position
int upsideDownCharCount() const { return mUpsideDownCharCount; }
void removeFromIndex( RTree<LabelPosition*, double, 2, double> *index );
void insertIntoIndex( RTree<LabelPosition*, double, 2, double> *index );
@ -296,6 +301,7 @@ namespace pal
private:
double mCost;
bool mHasObstacleConflict;
int mUpsideDownCharCount;
/** Calculates the total number of parts for this label position
*/

View File

@ -94,6 +94,10 @@ namespace pal
*/
QgsPalLayerSettings::Placement arrangement() const { return mArrangement; }
/** Returns true if the layer has curved labels
*/
bool isCurved() const { return mArrangement == QgsPalLayerSettings::Curved || mArrangement == QgsPalLayerSettings::PerimeterCurved; }
/** Sets the layer's arrangement policy.
* @param arrangement arrangement policy
* @see arrangement

View File

@ -2268,7 +2268,7 @@ QList<LabelPosition*> * Problem::getSolution( bool returnInactive )
}
else if ( returnInactive
|| mLabelPositions.at( featStartId[i] )->getFeaturePart()->layer()->displayAll()
|| mLabelPositions.at( featStartId[i] )->getFeaturePart()->getAlwaysShow() )
|| mLabelPositions.at( featStartId[i] )->getFeaturePart()->alwaysShow() )
{
solList->push_back( mLabelPositions.at( featStartId[i] ) ); // unplaced label
}