diff --git a/src/core/pal/feature.cpp b/src/core/pal/feature.cpp index 30e543e556d..198d233d377 100644 --- a/src/core/pal/feature.cpp +++ b/src/core/pal/feature.cpp @@ -968,7 +968,7 @@ int FeaturePart::createCandidatesAlongLineNearMidpoint( QList& 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; +} + diff --git a/src/core/pal/feature.h b/src/core/pal/feature.h index 104ae79f523..10e908d7f48 100644 --- a/src/core/pal/feature.h +++ b/src/core/pal/feature.h @@ -178,7 +178,7 @@ namespace pal int createCandidatesAlongLineNearMidpoint( QList &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: diff --git a/src/core/pal/labelposition.cpp b/src/core/pal/labelposition.cpp index 2e3473d02fa..cb4ae15d79b 100644 --- a/src/core/pal/labelposition.cpp +++ b/src/core/pal/labelposition.cpp @@ -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 ) diff --git a/src/core/pal/labelposition.h b/src/core/pal/labelposition.h index d032c5d6bbf..41a820d8289 100644 --- a/src/core/pal/labelposition.h +++ b/src/core/pal/labelposition.h @@ -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 *index ); void insertIntoIndex( RTree *index ); @@ -296,6 +301,7 @@ namespace pal private: double mCost; bool mHasObstacleConflict; + int mUpsideDownCharCount; /** Calculates the total number of parts for this label position */ diff --git a/src/core/pal/layer.h b/src/core/pal/layer.h index ce5731b794a..b91ca6e9aab 100644 --- a/src/core/pal/layer.h +++ b/src/core/pal/layer.h @@ -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 diff --git a/src/core/pal/problem.cpp b/src/core/pal/problem.cpp index 8f015a15756..9d1440eac8d 100644 --- a/src/core/pal/problem.cpp +++ b/src/core/pal/problem.cpp @@ -2268,7 +2268,7 @@ QList * 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 }