diff --git a/src/app/labeling/qgsmaptoollabel.cpp b/src/app/labeling/qgsmaptoollabel.cpp index 5d416c5fd41b..53fa6b99ef23 100644 --- a/src/app/labeling/qgsmaptoollabel.cpp +++ b/src/app/labeling/qgsmaptoollabel.cpp @@ -203,7 +203,7 @@ void QgsMapToolLabel::createRubberBands() //fixpoint rubber band QgsPointXY fixPoint; - if ( currentLabelRotationPoint( fixPoint, false, false ) ) + if ( currentLabelRotationPoint( fixPoint, false ) ) { if ( mCanvas ) { @@ -389,7 +389,7 @@ bool QgsMapToolLabel::currentLabelPreserveRotation() return true; // default, so there is no accidental data loss } -bool QgsMapToolLabel::currentLabelRotationPoint( QgsPointXY &pos, bool ignoreUpsideDown, bool rotatingUnpinned ) +bool QgsMapToolLabel::currentLabelRotationPoint( QgsPointXY &pos, bool ignoreUpsideDown ) { QVector cornerPoints = mCurrentLabel.pos.cornerPoints; if ( cornerPoints.size() < 4 ) @@ -418,14 +418,6 @@ bool QgsMapToolLabel::currentLabelRotationPoint( QgsPointXY &pos, bool ignoreUps QString haliString, valiString; currentAlignment( haliString, valiString ); - // rotate unpinned labels (i.e. no hali/vali settings) as if hali/vali was Center/Half - if ( rotatingUnpinned ) - { - haliString = QStringLiteral( "Center" ); - valiString = QStringLiteral( "Half" ); - } - -// QFont labelFont = labelFontCurrentFeature(); QFontMetricsF labelFontMetrics( mCurrentLabel.pos.labelFont ); // NOTE: this assumes the label corner points comprise a rectangle and that the diff --git a/src/app/labeling/qgsmaptoollabel.h b/src/app/labeling/qgsmaptoollabel.h index 2b12078e9ea1..467c0f1d99e7 100644 --- a/src/app/labeling/qgsmaptoollabel.h +++ b/src/app/labeling/qgsmaptoollabel.h @@ -138,7 +138,7 @@ class APP_EXPORT QgsMapToolLabel: public QgsMapToolAdvancedDigitizing * \param ignoreUpsideDown treat label as right-side-up * \returns TRUE in case of success */ - bool currentLabelRotationPoint( QgsPointXY &pos, bool ignoreUpsideDown = false, bool rotatingUnpinned = false ); + bool currentLabelRotationPoint( QgsPointXY &pos, bool ignoreUpsideDown = false ); //! Creates label / feature / fixpoint rubber bands for the current label position void createRubberBands(); diff --git a/src/app/labeling/qgsmaptoolmovelabel.cpp b/src/app/labeling/qgsmaptoolmovelabel.cpp index 5da765f60dc1..9861f4b77c52 100644 --- a/src/app/labeling/qgsmaptoolmovelabel.cpp +++ b/src/app/labeling/qgsmaptoolmovelabel.cpp @@ -169,7 +169,6 @@ void QgsMapToolMoveLabel::cadCanvasPressEvent( QgsMapMouseEvent *e ) if ( !labelAtPosition( e, labelPos ) ) { mCurrentLabel = LabelDetails(); - return; } @@ -241,7 +240,7 @@ void QgsMapToolMoveLabel::cadCanvasPressEvent( QgsMapMouseEvent *e ) mStartPointMapCoords = e->mapPoint(); QgsPointXY referencePoint; - if ( !currentLabelRotationPoint( referencePoint, !currentLabelPreserveRotation(), false ) ) + if ( !currentLabelRotationPoint( referencePoint, !currentLabelPreserveRotation() ) ) { referencePoint.setX( mCurrentLabel.pos.labelRect.xMinimum() ); referencePoint.setY( mCurrentLabel.pos.labelRect.yMinimum() ); diff --git a/src/app/labeling/qgsmaptoolpinlabels.cpp b/src/app/labeling/qgsmaptoolpinlabels.cpp index f70bc58409e9..07a452bd0ada 100644 --- a/src/app/labeling/qgsmaptoolpinlabels.cpp +++ b/src/app/labeling/qgsmaptoolpinlabels.cpp @@ -380,7 +380,7 @@ bool QgsMapToolPinLabels::pinUnpinCurrentLabel( bool pin ) // QgsPointXY labelpoint = labelpos.cornerPoints.at( 0 ); QgsPointXY referencePoint; - if ( !currentLabelRotationPoint( referencePoint, !preserveRot, false ) ) + if ( !currentLabelRotationPoint( referencePoint, !preserveRot ) ) { referencePoint.setX( labelpos.labelRect.xMinimum() ); referencePoint.setY( labelpos.labelRect.yMinimum() ); diff --git a/src/app/labeling/qgsmaptoolrotatelabel.cpp b/src/app/labeling/qgsmaptoolrotatelabel.cpp index 4b350ecc3317..370e225d2211 100644 --- a/src/app/labeling/qgsmaptoolrotatelabel.cpp +++ b/src/app/labeling/qgsmaptoolrotatelabel.cpp @@ -104,19 +104,9 @@ void QgsMapToolRotateLabel::canvasPressEvent( QgsMapMouseEvent *e ) if ( !mCurrentLabel.valid ) return; - // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature - - if ( !mCurrentLabel.pos.isPinned - && mCurrentLabel.settings.placement != QgsPalLayerSettings::OverPoint ) - { - return; - } - - // rotate unpinned labels (i.e. no hali/vali settings) as if hali/vali was Center/Half - if ( !currentLabelRotationPoint( mRotationPoint, false, !mCurrentLabel.pos.isPinned ) ) - { + // Get label rotation point + if ( !currentLabelRotationPoint( mRotationPoint, false ) ) return; - } { mCurrentMouseAzimuth = convertAzimuth( mRotationPoint.azimuth( toMapCoordinates( e->pos() ) ) ); @@ -295,17 +285,6 @@ void QgsMapToolRotateLabel::keyReleaseEvent( QKeyEvent *e ) } } -bool QgsMapToolRotateLabel::canModifyLabel( const QgsMapToolLabel::LabelDetails &label ) -{ - // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature - - if ( !label.pos.isPinned - && label.settings.placement != QgsPalLayerSettings::OverPoint ) - return false; - - return true; -} - int QgsMapToolRotateLabel::roundTo15Degrees( double n ) { const int m = static_cast< int >( n / 15.0 + 0.5 ); diff --git a/src/app/labeling/qgsmaptoolrotatelabel.h b/src/app/labeling/qgsmaptoolrotatelabel.h index 54f30a3cde01..061f8dd686fd 100644 --- a/src/app/labeling/qgsmaptoolrotatelabel.h +++ b/src/app/labeling/qgsmaptoolrotatelabel.h @@ -37,8 +37,6 @@ class APP_EXPORT QgsMapToolRotateLabel: public QgsMapToolLabel protected: - bool canModifyLabel( const LabelDetails &label ) override; - static int roundTo15Degrees( double n ); //! Converts azimuth value so that 0 is corresponds to East static double convertAzimuth( double a ); diff --git a/src/core/labeling/qgspallabeling.cpp b/src/core/labeling/qgspallabeling.cpp index 6a8bd176ff09..dc994e736c92 100644 --- a/src/core/labeling/qgspallabeling.cpp +++ b/src/core/labeling/qgspallabeling.cpp @@ -2331,8 +2331,7 @@ std::unique_ptr QgsPalLayerSettings::registerFeatureWithDetails offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale ); // layer defined rotation? - // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature - if ( placement == QgsPalLayerSettings::OverPoint && !qgsDoubleNear( angleOffset, 0.0 ) ) + if ( !qgsDoubleNear( angleOffset, 0.0 ) ) { layerDefinedRotation = true; angle = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise @@ -2475,16 +2474,6 @@ std::unique_ptr QgsPalLayerSettings::registerFeatureWithDetails xPos += xdiff; yPos += ydiff; } - else - { - anchorPosition = QgsPointXY( xPos, yPos ); - - // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature - if ( dataDefinedRotation && placement != QgsPalLayerSettings::OverPoint ) - { - angle = 0.0; - } - } } } } diff --git a/src/core/pal/feature.cpp b/src/core/pal/feature.cpp index bbbcae52cd0c..805c172d82e0 100644 --- a/src/core/pal/feature.cpp +++ b/src/core/pal/feature.cpp @@ -430,11 +430,12 @@ std::unique_ptr FeaturePart::createCandidatePointOnSurface( Point return std::make_unique< LabelPosition >( 0, px, py, getLabelWidth(), getLabelHeight(), 0.0, 0.0, this, false, LabelPosition::QuadrantOver ); } -void createCandidateAtOrderedPositionOverPoint( double &labelX, double &labelY, LabelPosition::Quadrant &quadrant, double x, double y, double labelWidth, double labelHeight, QgsPalLayerSettings::PredefinedPointPosition position, double distanceToLabel, const QgsMargins &visualMargin, double symbolWidthOffset, double symbolHeightOffset ) +void createCandidateAtOrderedPositionOverPoint( double &labelX, double &labelY, LabelPosition::Quadrant &quadrant, double x, double y, double labelWidth, double labelHeight, QgsPalLayerSettings::PredefinedPointPosition position, double distanceToLabel, const QgsMargins &visualMargin, double symbolWidthOffset, double symbolHeightOffset, double angle ) { double alpha = 0.0; double deltaX = 0; double deltaY = 0; + switch ( position ) { case QgsPalLayerSettings::TopLeft: @@ -522,6 +523,12 @@ void createCandidateAtOrderedPositionOverPoint( double &labelX, double &labelY, break; } + // Take care of the label angle when creating candidates. See pr comments #44944 for details + // https://github.com/qgis/QGIS/pull/44944#issuecomment-914670088 + QTransform transformRotation; + transformRotation.rotate( angle * 180 / M_PI ); + transformRotation.map( deltaX, deltaY, &deltaX, &deltaY ); + //have bearing, distance - calculate reference point double referenceX = std::cos( alpha ) * distanceToLabel + x; double referenceY = std::sin( alpha ) * distanceToLabel + y; @@ -552,7 +559,7 @@ std::size_t FeaturePart::createCandidatesAtOrderedPositionsOverPoint( double x, double labelX = 0; double labelY = 0; - createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant, x, y, labelWidth, labelHeight, position, distanceToLabel, visualMargin, symbolWidthOffset, symbolHeightOffset ); + createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant, x, y, labelWidth, labelHeight, position, distanceToLabel, visualMargin, symbolWidthOffset, symbolHeightOffset, angle ); if ( ! mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) ) { @@ -615,8 +622,8 @@ std::size_t FeaturePart::createCandidatesAroundPoint( double x, double y, std::v double angleToCandidate; for ( i = 0, angleToCandidate = M_PI_4; i < maxNumberCandidates; i++, angleToCandidate += candidateAngleIncrement ) { - double labelX = x; - double labelY = y; + double deltaX = 0.0; + double deltaY = 0.0; if ( angleToCandidate > a360 ) angleToCandidate -= a360; @@ -625,62 +632,71 @@ std::size_t FeaturePart::createCandidatesAroundPoint( double x, double y, std::v if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right { - labelX += distanceToLabel; + deltaX = distanceToLabel; double iota = ( angleToCandidate + gamma1 ); if ( iota > a360 - gamma1 ) iota -= a360; //ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2); - labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 ); + deltaY = -labelHeight + labelHeight * iota / ( 2 * gamma1 ); quadrant = LabelPosition::QuadrantRight; } else if ( angleToCandidate < a90 - gamma2 ) // top-right { - labelX += distanceToLabel * std::cos( angleToCandidate ); - labelY += distanceToLabel * std::sin( angleToCandidate ); + deltaX = distanceToLabel * std::cos( angleToCandidate ); + deltaY = distanceToLabel * std::sin( angleToCandidate ); quadrant = LabelPosition::QuadrantAboveRight; } else if ( angleToCandidate < a90 + gamma2 ) // top { //lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2); - labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 ); - labelY += distanceToLabel; + deltaX = -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 ); + deltaY = distanceToLabel; quadrant = LabelPosition::QuadrantAbove; } else if ( angleToCandidate < a180 - gamma1 ) // top left { - labelX += distanceToLabel * std::cos( angleToCandidate ) - labelWidth; - labelY += distanceToLabel * std::sin( angleToCandidate ); + deltaX = distanceToLabel * std::cos( angleToCandidate ) - labelWidth; + deltaY = distanceToLabel * std::sin( angleToCandidate ); quadrant = LabelPosition::QuadrantAboveLeft; } else if ( angleToCandidate < a180 + gamma1 ) // left { - labelX += -distanceToLabel - labelWidth; + deltaX = -distanceToLabel - labelWidth; //ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2); - labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 ); + deltaY = - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 ); quadrant = LabelPosition::QuadrantLeft; } else if ( angleToCandidate < a270 - gamma2 ) // down - left { - labelX += distanceToLabel * std::cos( angleToCandidate ) - labelWidth; - labelY += distanceToLabel * std::sin( angleToCandidate ) - labelHeight; + deltaX = distanceToLabel * std::cos( angleToCandidate ) - labelWidth; + deltaY = distanceToLabel * std::sin( angleToCandidate ) - labelHeight; quadrant = LabelPosition::QuadrantBelowLeft; } else if ( angleToCandidate < a270 + gamma2 ) // down { - labelY += -distanceToLabel - labelHeight; + deltaY = -distanceToLabel - labelHeight; //lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2); - labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 ); + deltaX = -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 ); quadrant = LabelPosition::QuadrantBelow; } else if ( angleToCandidate < a360 ) // down - right { - labelX += distanceToLabel * std::cos( angleToCandidate ); - labelY += distanceToLabel * std::sin( angleToCandidate ) - labelHeight; + deltaX = distanceToLabel * std::cos( angleToCandidate ); + deltaY = distanceToLabel * std::sin( angleToCandidate ) - labelHeight; quadrant = LabelPosition::QuadrantBelowRight; } + // Take care of the label angle when creating candidates. See pr comments #44944 for details + // https://github.com/qgis/QGIS/pull/44944#issuecomment-914670088 + QTransform transformRotation; + transformRotation.rotate( angle * 180 / M_PI ); + transformRotation.map( deltaX, deltaY, &deltaX, &deltaY ); + + double labelX = x + deltaX; + double labelY = y + deltaY; + double cost; if ( maxNumberCandidates == 1 ) @@ -1826,7 +1842,7 @@ std::size_t FeaturePart::createCandidatesOutsidePolygon( std::vector candidate = std::make_unique< LabelPosition >( i, labelX, labelY, labelWidth, labelHeight, labelAngle, 0, this, false, quadrant ); if ( candidate->intersects( preparedBuffer.get() ) ) diff --git a/tests/testdata/control_images/labelingengine/expected_label_rotate_unit/expected_label_rotate_unit.png b/tests/testdata/control_images/labelingengine/expected_label_rotate_unit/expected_label_rotate_unit.png index b66b27ef7968..97b95cba2bf2 100644 Binary files a/tests/testdata/control_images/labelingengine/expected_label_rotate_unit/expected_label_rotate_unit.png and b/tests/testdata/control_images/labelingengine/expected_label_rotate_unit/expected_label_rotate_unit.png differ diff --git a/tests/testdata/control_images/labelingengine/expected_labeling_rotation_based_orientation_point/expected_labeling_rotation_based_orientation_point.png b/tests/testdata/control_images/labelingengine/expected_labeling_rotation_based_orientation_point/expected_labeling_rotation_based_orientation_point.png index 61489c05e809..fe71d4a0624b 100644 Binary files a/tests/testdata/control_images/labelingengine/expected_labeling_rotation_based_orientation_point/expected_labeling_rotation_based_orientation_point.png and b/tests/testdata/control_images/labelingengine/expected_labeling_rotation_based_orientation_point/expected_labeling_rotation_based_orientation_point.png differ