Skip to content
Permalink
Browse files
Merge pull request #44266 from domi4484/labelRotationUnit
[labeling] specify unit for data defined label rotation
  • Loading branch information
m-kuhn committed Sep 6, 2021
2 parents b16e705 + 3ce3a2b commit 0555d11042b790073b35e869dec06b0bc7dcebe7
@@ -11,7 +11,6 @@




class QgsPalLayerSettings
{
%Docstring(signature="appended")
@@ -395,6 +394,24 @@ Sets the polygon placement ``flags``, which dictate how polygon labels can be pl

bool preserveRotation;

QgsUnitTypes::AngleUnit rotationUnit() const;
%Docstring
Unit for rotation of labels.

.. seealso:: :py:func:`setRotationUnit`

.. versionadded:: 3.22
%End

void setRotationUnit( QgsUnitTypes::AngleUnit angleUnit );
%Docstring
Set unit for rotation of labels.

.. seealso:: :py:func:`rotationUnit`

.. versionadded:: 3.22
%End

double maxCurvedCharAngleIn;

double maxCurvedCharAngleOut;
@@ -583,13 +583,12 @@ Returns the conversion factor between the specified angular units.
Returns an angle formatted as a friendly string.

:param angle: angle to format
:param decimals: number of decimal places to show
:param decimals: number of decimal places to show. A value of -1 indicates that an appropriate number of decimal places should automatically be selected.
:param unit: unit of angle

:return: formatted angle string
%End


static QgsUnitTypes::DistanceValue scaledDistance( double distance, QgsUnitTypes::DistanceUnit unit, int decimals, bool keepBaseUnit = false );
%Docstring
Will convert a ``distance`` with a given ``unit`` to a distance value which is nice to display.
@@ -29,18 +29,13 @@

QgsMapToolRotateLabel::QgsMapToolRotateLabel( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDock )
: QgsMapToolLabel( canvas, cadDock )
, mStartRotation( 0.0 )
, mCurrentRotation( 0.0 )
, mCurrentMouseAzimuth( 0.0 )
, mCtrlPressed( false )
{
mPalProperties << QgsPalLayerSettings::LabelRotation;
}

QgsMapToolRotateLabel::~QgsMapToolRotateLabel()
{
delete mRotationItem;
delete mRotationPreviewBox;
}

void QgsMapToolRotateLabel::canvasMoveEvent( QgsMapMouseEvent *e )
@@ -161,14 +156,21 @@ void QgsMapToolRotateLabel::canvasPressEvent( QgsMapMouseEvent *e )
{
mCurrentRotation = 0;
}

// Convert to degree
mCurrentRotation = mCurrentRotation
* QgsUnitTypes::fromUnitToUnitFactor( mCurrentLabel.settings.rotationUnit(),
QgsUnitTypes::AngleDegrees );

mStartRotation = mCurrentRotation;
createRubberBands();

mRotationPreviewBox = createRotationPreviewBox();
createRotationPreviewBox();

mRotationItem = new QgsPointRotationItem( mCanvas );
mRotationItem->setOrientation( QgsPointRotationItem::Clockwise );
mRotationItem->setPointLocation( mRotationPoint );
mRotationItem->setRotationUnit( mCurrentLabel.settings.rotationUnit() );
mRotationItem->setSymbolRotation( static_cast< int >( mCurrentRotation ) );
}
}
@@ -183,8 +185,7 @@ void QgsMapToolRotateLabel::canvasPressEvent( QgsMapMouseEvent *e )
deleteRubberBands();
delete mRotationItem;
mRotationItem = nullptr;
delete mRotationPreviewBox;
mRotationPreviewBox = nullptr;
mRotationPreviewBox.reset();
return;
}

@@ -194,8 +195,7 @@ void QgsMapToolRotateLabel::canvasPressEvent( QgsMapMouseEvent *e )
deleteRubberBands();
delete mRotationItem;
mRotationItem = nullptr;
delete mRotationPreviewBox;
mRotationPreviewBox = nullptr;
mRotationPreviewBox.reset();

QgsVectorLayer *vlayer = mCurrentLabel.layer;
if ( !vlayer )
@@ -209,12 +209,16 @@ void QgsMapToolRotateLabel::canvasPressEvent( QgsMapMouseEvent *e )
return;
}

const double rotation = mCtrlPressed ? roundTo15Degrees( mCurrentRotation ) : mCurrentRotation;
if ( qgsDoubleNear( rotation, mStartRotation ) ) //mouse button pressed / released, but no rotation
const double rotationDegree = mCtrlPressed ? roundTo15Degrees( mCurrentRotation ) : mCurrentRotation;
if ( qgsDoubleNear( rotationDegree, mStartRotation ) ) //mouse button pressed / released, but no rotation
{
return;
}

// Convert back to settings unit
const double rotation = rotationDegree * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::AngleDegrees,
mCurrentLabel.settings.rotationUnit() );

vlayer->beginEditCommand( tr( "Rotated label" ) + QStringLiteral( " '%1'" ).arg( currentLabelText( 24 ) ) );
if ( !vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, rotationCol, rotation ) )
{
@@ -272,8 +276,7 @@ void QgsMapToolRotateLabel::keyReleaseEvent( QKeyEvent *e )
deleteRubberBands();
delete mRotationItem;
mRotationItem = nullptr;
delete mRotationPreviewBox;
mRotationPreviewBox = nullptr;
mRotationPreviewBox.reset();
vlayer->triggerRepaint();
}
}
@@ -286,8 +289,7 @@ void QgsMapToolRotateLabel::keyReleaseEvent( QKeyEvent *e )
deleteRubberBands();
delete mRotationItem;
mRotationItem = nullptr;
delete mRotationPreviewBox;
mRotationPreviewBox = nullptr;
mRotationPreviewBox.reset();
}
}
}
@@ -316,20 +318,17 @@ double QgsMapToolRotateLabel::convertAzimuth( double a )
return ( a <= -180.0 ? 360 + a : a );
}

QgsRubberBand *QgsMapToolRotateLabel::createRotationPreviewBox()
void QgsMapToolRotateLabel::createRotationPreviewBox()
{
delete mRotationPreviewBox;
mRotationPreviewBox.reset();
const QVector< QgsPointXY > boxPoints = mCurrentLabel.pos.cornerPoints;
if ( boxPoints.empty() )
{
return nullptr;
}
return;

mRotationPreviewBox = new QgsRubberBand( mCanvas, QgsWkbTypes::LineGeometry );
mRotationPreviewBox.reset( new QgsRubberBand( mCanvas, QgsWkbTypes::LineGeometry ) );
mRotationPreviewBox->setColor( QColor( 0, 0, 255, 65 ) );
mRotationPreviewBox->setWidth( 3 );
setRotationPreviewBox( mCurrentRotation - mStartRotation );
return mRotationPreviewBox;
}

void QgsMapToolRotateLabel::setRotationPreviewBox( double rotation )
@@ -340,17 +339,13 @@ void QgsMapToolRotateLabel::setRotationPreviewBox( double rotation )
}

mRotationPreviewBox->reset();
const QVector< QgsPointXY > boxPoints = mCurrentLabel.pos.cornerPoints;
if ( boxPoints.empty() )
{
if ( mCurrentLabel.pos.cornerPoints.empty() )
return;
}

for ( int i = 0; i < boxPoints.size(); ++i )
{
mRotationPreviewBox->addPoint( rotatePointClockwise( boxPoints.at( i ), mRotationPoint, rotation ) );
}
mRotationPreviewBox->addPoint( rotatePointClockwise( boxPoints.at( 0 ), mRotationPoint, rotation ) );
const QVector< QgsPointXY > cornerPoints = mCurrentLabel.pos.cornerPoints;
for ( const QgsPointXY &cornerPoint : cornerPoints )
mRotationPreviewBox->addPoint( rotatePointClockwise( cornerPoint, mRotationPoint, rotation ) );
mRotationPreviewBox->addPoint( rotatePointClockwise( mCurrentLabel.pos.cornerPoints.at( 0 ), mRotationPoint, rotation ) );
mRotationPreviewBox->show();
}

@@ -20,6 +20,7 @@

#include "qgsmaptoollabel.h"
#include "qgis_app.h"
#include "qobjectuniqueptr.h"
class QgsPointRotationItem;

class APP_EXPORT QgsMapToolRotateLabel: public QgsMapToolLabel
@@ -42,21 +43,21 @@ class APP_EXPORT QgsMapToolRotateLabel: public QgsMapToolLabel
//! Converts azimuth value so that 0 is corresponds to East
static double convertAzimuth( double a );

QgsRubberBand *createRotationPreviewBox();
void createRotationPreviewBox();
void setRotationPreviewBox( double rotation );

//! Rotates input point clockwise around centerPoint
QgsPointXY rotatePointClockwise( const QgsPointXY &input, const QgsPointXY &centerPoint, double degrees ) const;

double mStartRotation; //rotation value prior to start rotating
double mCurrentRotation;
double mCurrentMouseAzimuth;
double mStartRotation = 0.0; //rotation value prior to start rotating
double mCurrentRotation = 0.0;
double mCurrentMouseAzimuth = 0.0;
QgsPointXY mRotationPoint;
QgsPointRotationItem *mRotationItem = nullptr;
QgsRubberBand *mRotationPreviewBox = nullptr;
QObjectUniquePtr<QgsRubberBand> mRotationPreviewBox;

//! True if ctrl was pressed during the last mouse move event
bool mCtrlPressed;
bool mCtrlPressed = false;
};

#endif // QGSMAPTOOLROTATELABEL_H
@@ -21,8 +21,6 @@

QgsPointRotationItem::QgsPointRotationItem( QgsMapCanvas *canvas )
: QgsMapCanvasItem( canvas )
, mOrientation( Clockwise )
, mRotation( 0.0 )
{
//setup font
mFont.setPointSize( 12 );
@@ -78,7 +76,12 @@ void QgsPointRotationItem::paint( QPainter *painter )
bufferPen.setWidthF( QgsGuiUtils::scaleIconSize( 4 ) );
const QFontMetricsF fm( mFont );
QPainterPath label;
label.addText( mPixmap.width(), mPixmap.height() / 2.0 + fm.height() / 2.0, mFont, QLocale().toString( mRotation ) );
const double rotationText = mRotation * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::AngleDegrees,
mRotationUnit );
label.addText( mPixmap.width(),
mPixmap.height() / 2.0 + fm.height() / 2.0,
mFont,
QgsUnitTypes::formatAngle( rotationText, -1, mRotationUnit ) );
painter->setPen( bufferPen );
painter->setBrush( Qt::NoBrush );
painter->drawPath( label );
@@ -95,13 +98,18 @@ void QgsPointRotationItem::setPointLocation( const QgsPointXY &p )
setPos( transformedPoint.x() - mPixmap.width() / 2.0, transformedPoint.y() - mPixmap.height() / 2.0 );
}

void QgsPointRotationItem::setRotationUnit( const QgsUnitTypes::AngleUnit &rotationUnit )
{
mRotationUnit = rotationUnit;
}

void QgsPointRotationItem::setSymbol( const QImage &symbolImage )
{
mPixmap = QPixmap::fromImage( symbolImage );
const QFontMetricsF fm( mFont );

//set item size
mItemSize.setWidth( mPixmap.width() + fm.horizontalAdvance( QStringLiteral( "360" ) ) );
//set item size: 6283 millirad arcseconds = 360°
mItemSize.setWidth( mPixmap.width() + fm.horizontalAdvance( QStringLiteral( "6283 millirad" ) ) );
const double pixmapHeight = mPixmap.height();
const double fontHeight = fm.height();
if ( pixmapHeight >= fontHeight )
@@ -17,6 +17,7 @@
#define QGSPOINTROTATIONITEM_H

#include "qgsmapcanvasitem.h"
#include "qgsunittypes.h"
#include <QFontMetricsF>
#include <QPixmap>
#include "qgis_app.h"
@@ -40,11 +41,17 @@ class APP_EXPORT QgsPointRotationItem: public QgsMapCanvasItem
void setPointLocation( const QgsPointXY &p );

/**
* Sets the rotation of the symbol and displays the new rotation number.
* Sets the rotation of the symbol.
* Units are degrees, starting from north direction, clockwise direction.
*/
void setSymbolRotation( int r ) {mRotation = r;}

/**
* Sets the rotation unit.
* \since QGIS 3.22
*/
void setRotationUnit( const QgsUnitTypes::AngleUnit &rotationUnit );

//! Sets rotation symbol from image (takes ownership)
void setSymbol( const QImage &symbolImage );

@@ -56,14 +63,14 @@ class APP_EXPORT QgsPointRotationItem: public QgsMapCanvasItem
//! Converts rotation into QPainter rotation considering mOrientation
int painterRotation( int rotation ) const;
//! Clockwise (default) or counterclockwise
Orientation mOrientation;
Orientation mOrientation = Clockwise;
//! Font to display the numerical rotation values
QFont mFont;
//! Symbol pixmap
QPixmap mPixmap;
int mRotation;
int mRotation = 0.0;
QgsUnitTypes::AngleUnit mRotationUnit = QgsUnitTypes::AngleDegrees;
QPainterPath mArrowPath;

};

#endif // QGSPOINTROTATIONITEM_H
@@ -332,6 +332,7 @@ QgsPalLayerSettings &QgsPalLayerSettings::operator=( const QgsPalLayerSettings &
distMapUnitScale = s.distMapUnitScale;
angleOffset = s.angleOffset;
preserveRotation = s.preserveRotation;
mRotationUnit = s.mRotationUnit;
maxCurvedCharAngleIn = s.maxCurvedCharAngleIn;
maxCurvedCharAngleOut = s.maxCurvedCharAngleOut;
priority = s.priority;
@@ -606,6 +607,16 @@ QgsExpression *QgsPalLayerSettings::getLabelExpression()
return expression;
}

QgsUnitTypes::AngleUnit QgsPalLayerSettings::rotationUnit() const
{
return mRotationUnit;
}

void QgsPalLayerSettings::setRotationUnit( QgsUnitTypes::AngleUnit angleUnit )
{
mRotationUnit = angleUnit;
}

QString updateDataDefinedString( const QString &value )
{
// TODO: update or remove this when project settings for labeling are migrated to better XML layout
@@ -797,6 +808,7 @@ void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer )
}

preserveRotation = layer->customProperty( QStringLiteral( "labeling/preserveRotation" ), QVariant( true ) ).toBool();
mRotationUnit = layer->customEnumProperty( QStringLiteral( "labeling/rotationUnit" ), QgsUnitTypes::AngleDegrees );
maxCurvedCharAngleIn = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleIn" ), QVariant( 25.0 ) ).toDouble();
maxCurvedCharAngleOut = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleOut" ), QVariant( -25.0 ) ).toDouble();
priority = layer->customProperty( QStringLiteral( "labeling/priority" ) ).toInt();
@@ -1024,6 +1036,7 @@ void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteCo
}

preserveRotation = placementElem.attribute( QStringLiteral( "preserveRotation" ), QStringLiteral( "1" ) ).toInt();
mRotationUnit = qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "rotationUnit" ), qgsEnumValueToKey( QgsUnitTypes::AngleDegrees ) ), QgsUnitTypes::AngleDegrees );
maxCurvedCharAngleIn = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleIn" ), QStringLiteral( "25" ) ).toDouble();
maxCurvedCharAngleOut = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleOut" ), QStringLiteral( "-25" ) ).toDouble();
priority = placementElem.attribute( QStringLiteral( "priority" ) ).toInt();
@@ -1213,6 +1226,7 @@ QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWrite
placementElem.setAttribute( QStringLiteral( "labelOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
placementElem.setAttribute( QStringLiteral( "rotationAngle" ), angleOffset );
placementElem.setAttribute( QStringLiteral( "preserveRotation" ), preserveRotation );
placementElem.setAttribute( QStringLiteral( "rotationUnit" ), qgsEnumValueToKey( mRotationUnit ) );
placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleIn" ), maxCurvedCharAngleIn );
placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleOut" ), maxCurvedCharAngleOut );
placementElem.setAttribute( QStringLiteral( "priority" ), priority );
@@ -2333,14 +2347,18 @@ std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails
if ( !exprVal.isNull() )
{
bool ok;
double rotD = exprVal.toDouble( &ok );
const double rotation = exprVal.toDouble( &ok );
if ( ok )
{
dataDefinedRotation = true;

double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
QgsUnitTypes::AngleDegrees );

// TODO: add setting to disable having data defined rotation follow
// map rotation ?
rotD += m2p.mapRotation();
angle = ( 360 - rotD ) * M_PI / 180.0;
rotationDegrees += m2p.mapRotation();
angle = ( 360 - rotationDegrees ) * M_PI / 180.0;
}
}
}

0 comments on commit 0555d11

Please sign in to comment.