Skip to content
Permalink
Browse files
Manual backport of #42383
  • Loading branch information
uclaros authored and nyalldawson committed May 1, 2021
1 parent 5920dd1 commit 3c71e947768340047fe75a7e55a0ae1d69d0f1d1
Showing with 101 additions and 71 deletions.
  1. +44 −53 src/app/qgsmaptoolrotatefeature.cpp
  2. +12 −8 src/app/qgsmaptoolrotatefeature.h
  3. +45 −10 tests/src/app/testqgsmaptoolrotatefeature.cpp
@@ -22,6 +22,7 @@
#include <limits>
#include <cmath>

#include "qgsadvanceddigitizingdockwidget.h"
#include "qgsmaptoolrotatefeature.h"
#include "qgsfeatureiterator.h"
#include "qgsgeometry.h"
@@ -33,6 +34,7 @@
#include "qgisapp.h"
#include "qgsspinbox.h"
#include "qgsdoublespinbox.h"
#include "qgssnapindicator.h"
#include "qgsmapmouseevent.h"


@@ -57,7 +59,7 @@ QgsAngleMagnetWidget::QgsAngleMagnetWidget( const QString &label, QWidget *paren
mAngleSpinBox->setSuffix( tr( "°" ) );
mAngleSpinBox->setSingleStep( 1 );
mAngleSpinBox->setValue( 0 );
mAngleSpinBox->setShowClearButton( false );
mAngleSpinBox->setClearValue( 0 );
mAngleSpinBox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
mLayout->addWidget( mAngleSpinBox );

@@ -139,24 +141,30 @@ void QgsAngleMagnetWidget::angleSpinBoxValueChanged( double angle )
//

QgsMapToolRotateFeature::QgsMapToolRotateFeature( QgsMapCanvas *canvas )
: QgsMapToolEdit( canvas )
: QgsMapToolAdvancedDigitizing( canvas, QgisApp::instance()->cadDockWidget() )
, mSnapIndicator( qgis::make_unique< QgsSnapIndicator>( canvas ) )
{
mToolName = tr( "Rotate feature" );
}

QgsMapToolRotateFeature::~QgsMapToolRotateFeature()
{
deleteRotationWidget();
mAnchorPoint.reset();
deleteRubberband();
mSnapIndicator->setMatch( QgsPointLocator::Match() );
}

void QgsMapToolRotateFeature::canvasMoveEvent( QgsMapMouseEvent *e )
void QgsMapToolRotateFeature::cadCanvasMoveEvent( QgsMapMouseEvent *e )
{
mSnapIndicator->setMatch( e->mapPointMatch() );

if ( mRotationActive )
{
const double XDistance = e->pos().x() - mStPoint.x();
const double YDistance = e->pos().y() - mStPoint.y();
double rotation = std::atan2( YDistance, XDistance ) * ( 180 / M_PI ) - mRotationOffset;
const QgsPointXY pt = e->mapPoint();
const double XDistance = pt.x() - mStartPointMapCoords.x();
const double YDistance = pt.y() - mStartPointMapCoords.y();
double rotation = std::atan2( XDistance, YDistance ) * ( 180 / M_PI ) - mRotationOffset;

if ( mRotationWidget )
{
@@ -174,7 +182,7 @@ void QgsMapToolRotateFeature::canvasMoveEvent( QgsMapMouseEvent *e )
}
}

void QgsMapToolRotateFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
void QgsMapToolRotateFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
{
if ( !mCanvas )
{
@@ -187,6 +195,8 @@ void QgsMapToolRotateFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
deleteRotationWidget();
deleteRubberband();
notifyNotVectorLayer();
mSnapIndicator->setMatch( QgsPointLocator::Match() );
mCadDockWidget->clear();
return;
}

@@ -204,9 +214,10 @@ void QgsMapToolRotateFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
mAnchorPoint = qgis::make_unique<QgsVertexMarker>( mCanvas );
mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
}
mAnchorPoint->setCenter( toMapCoordinates( e->pos() ) );
mStartPointMapCoords = toMapCoordinates( e->pos() );
mAnchorPoint->setCenter( e->mapPoint() );
mStartPointMapCoords = e->mapPoint();
mStPoint = e->pos();
cadDockWidget()->clear();
return;
}

@@ -220,15 +231,15 @@ void QgsMapToolRotateFeature::canvasReleaseEvent( QgsMapMouseEvent *e )

deleteRubberband();

mInitialPos = e->pos();
mInitialPos = e->mapPoint();

if ( !vlayer->isEditable() )
{
notifyNotEditableLayer();
return;
}

QgsPointXY layerCoords = toLayerCoordinates( vlayer, e->pos() );
QgsPointXY layerCoords = toLayerCoordinates( vlayer, e->mapPoint() );
double searchRadius = QgsTolerance::vertexSearchRadius( mCanvas->currentLayer(), mCanvas->mapSettings() );
QgsRectangle selectRect( layerCoords.x() - searchRadius, layerCoords.y() - searchRadius,
layerCoords.x() + searchRadius, layerCoords.y() + searchRadius );
@@ -312,9 +323,9 @@ void QgsMapToolRotateFeature::canvasReleaseEvent( QgsMapMouseEvent *e )

mRubberBand->show();

double XDistance = mInitialPos.x() - mAnchorPoint->x();
double YDistance = mInitialPos.y() - mAnchorPoint->y();
mRotationOffset = std::atan2( YDistance, XDistance ) * ( 180 / M_PI );
double XDistance = mInitialPos.x() - mAnchorPoint->center().x();
double YDistance = mInitialPos.y() - mAnchorPoint->center().y();
mRotationOffset = std::atan2( XDistance, YDistance ) * ( 180 / M_PI );

createRotationWidget();
if ( e->modifiers() & Qt::ShiftModifier )
@@ -343,6 +354,8 @@ void QgsMapToolRotateFeature::cancel()
mAnchorPoint.reset();
}
mRotationActive = false;
mSnapIndicator->setMatch( QgsPointLocator::Match() );
mCadDockWidget->clear();
}

void QgsMapToolRotateFeature::updateRubberband( double rotation )
@@ -373,54 +386,31 @@ void QgsMapToolRotateFeature::applyRotation( double rotation )
{
deleteRubberband();
notifyNotVectorLayer();
mSnapIndicator->setMatch( QgsPointLocator::Match() );
mCadDockWidget->clear();
return;
}

//calculations for affine transformation
double angle = -1 * mRotation * ( M_PI / 180 );
QgsPointXY anchorPoint = toLayerCoordinates( vlayer, mStartPointMapCoords );
double a = std::cos( angle );
double b = -1 * std::sin( angle );
double c = anchorPoint.x() - std::cos( angle ) * anchorPoint.x() + std::sin( angle ) * anchorPoint.y();
double d = std::sin( angle );
double ee = std::cos( angle );
double f = anchorPoint.y() - std::sin( angle ) * anchorPoint.x() - std::cos( angle ) * anchorPoint.y();

vlayer->beginEditCommand( tr( "Features Rotated" ) );

int start;
if ( vlayer->geometryType() == 2 )
{
start = 1;
}
else
QgsFeatureRequest request;
request.setFilterFids( mRotatedFeatures ).setNoAttributes();
QgsFeatureIterator fi = vlayer->getFeatures( request );
QgsFeature f;
while ( fi.nextFeature( f ) )
{
start = 0;
}

int i = 0;
const auto constMRotatedFeatures = mRotatedFeatures;
for ( QgsFeatureId id : constMRotatedFeatures )
{
QgsFeature feat;
vlayer->getFeatures( QgsFeatureRequest().setFilterFid( id ) ).nextFeature( feat );
QgsGeometry geom = feat.geometry();
i = start;

QgsPointXY vertex = geom.vertexAt( i );
while ( !vertex.isEmpty() )
{
double newX = a * vertex.x() + b * vertex.y() + c;
double newY = d * vertex.x() + ee * vertex.y() + f;

vlayer->moveVertex( newX, newY, id, i );
i = i + 1;
vertex = geom.vertexAt( i );
}
QgsFeatureId id = f.id();
QgsGeometry geom = f.geometry();
geom.rotate( mRotation, anchorPoint );
vlayer->changeGeometry( id, geom );
}

deleteRotationWidget();
deleteRubberband();
mSnapIndicator->setMatch( QgsPointLocator::Match() );
mCadDockWidget->clear();

if ( mAutoSetAnchorPoint )
mAnchorPoint.reset();
@@ -436,7 +426,7 @@ void QgsMapToolRotateFeature::keyReleaseEvent( QKeyEvent *e )
cancel();
return;
}
QgsMapTool::keyReleaseEvent( e );
QgsMapToolAdvancedDigitizing::keyReleaseEvent( e );
}

void QgsMapToolRotateFeature::activate()
@@ -463,7 +453,7 @@ void QgsMapToolRotateFeature::activate()

mStPoint = toCanvasCoordinates( mStartPointMapCoords );
}
QgsMapTool::activate();
QgsMapToolAdvancedDigitizing::activate();
}

void QgsMapToolRotateFeature::deleteRubberband()
@@ -479,7 +469,8 @@ void QgsMapToolRotateFeature::deactivate()
mRotationOffset = 0;
mAnchorPoint.reset();
deleteRubberband();
QgsMapTool::deactivate();
mSnapIndicator->setMatch( QgsPointLocator::Match() );
QgsMapToolAdvancedDigitizing::deactivate();
}

void QgsMapToolRotateFeature::createRotationWidget()
@@ -18,7 +18,7 @@

#include <QWidget>

#include "qgsmaptooledit.h"
#include "qgsmaptooladvanceddigitizing.h"
#include "qgsvertexmarker.h"
#include "qgis_app.h"
#include "qgsgeometry.h"
@@ -27,6 +27,7 @@
class QgsDoubleSpinBox;
class QHBoxLayout;
class QgsSpinBox;
class QgsSnapIndicator;

class APP_EXPORT QgsAngleMagnetWidget : public QWidget
{
@@ -65,16 +66,16 @@ class APP_EXPORT QgsAngleMagnetWidget : public QWidget


//! Map tool to rotate features
class APP_EXPORT QgsMapToolRotateFeature: public QgsMapToolEdit
class APP_EXPORT QgsMapToolRotateFeature: public QgsMapToolAdvancedDigitizing
{
Q_OBJECT
public:
QgsMapToolRotateFeature( QgsMapCanvas *canvas );
~QgsMapToolRotateFeature() override;

void canvasMoveEvent( QgsMapMouseEvent *e ) override;
void cadCanvasMoveEvent( QgsMapMouseEvent *e ) override;

void canvasReleaseEvent( QgsMapMouseEvent *e ) override;
void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) override;

//! called when map tool is being deactivated
void deactivate() override;
@@ -98,14 +99,17 @@ class APP_EXPORT QgsMapToolRotateFeature: public QgsMapToolEdit
void createRotationWidget();
void deleteRotationWidget();

//! Start point of the move in map coordinates
//! Start point of the rotation in map coordinates
QgsPointXY mStartPointMapCoords;
QPointF mInitialPos;
QgsPointXY mInitialPos;

//! Rubberband that shows the feature being moved
//! Rubberband that shows the feature being rotated
QgsRubberBand *mRubberBand = nullptr;

//! Id of moved feature
//! Snapping indicators
std::unique_ptr<QgsSnapIndicator> mSnapIndicator;

//! Id of rotated feature
QgsFeatureIds mRotatedFeatures;
double mRotation = 0;
double mRotationOffset = 0;
@@ -47,6 +47,7 @@ class TestQgsMapToolRotateFeature: public QObject
void testRotateFeatureManualAnchor();
void testCancelManualAnchor();
void testRotateFeatureManualAnchorAfterStartRotate();
void testRotateFeatureManualAnchorSnapping();

private:
QgisApp *mQgisApp = nullptr;
@@ -89,10 +90,10 @@ void TestQgsMapToolRotateFeature::initTestCase()
QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << mLayerBase );

mLayerBase->startEditing();
QString wkt1 = QStringLiteral( "Polygon ((0 0, 0 1, 1 1, 1 0))" );
QString wkt1 = QStringLiteral( "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))" );
QgsFeature f1;
f1.setGeometry( QgsGeometry::fromWkt( wkt1 ) );
QString wkt2 = QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0))" );
QString wkt2 = QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0, 1.1 0))" );
QgsFeature f2;
f2.setGeometry( QgsGeometry::fromWkt( wkt2 ) );

@@ -112,6 +113,7 @@ void TestQgsMapToolRotateFeature::initTestCase()

mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerBase );
mCanvas->setCurrentLayer( mLayerBase );
mCanvas->snappingUtils()->locatorForLayer( mLayerBase )->init();

// create the tool
mRotateTool = new QgsMapToolRotateFeature( mCanvas );
@@ -137,8 +139,8 @@ void TestQgsMapToolRotateFeature::testRotateFeature()
utils.mouseMove( 2, 1 );
utils.mouseClick( 2, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );

QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((0.72 -0.17, 0.28 1.17, 1.17 0.72, 0.72 -0.17))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0))" ) );
QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((-0.17 0.28, 0.28 1.17, 1.17 0.72, 0.72 -0.17, -0.17 0.28))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0, 1.1 0))" ) );

mLayerBase->undoStack()->undo();
}
@@ -155,8 +157,8 @@ void TestQgsMapToolRotateFeature::testRotateFeatureManualAnchor()
utils.mouseMove( 2, 1 );
utils.mouseClick( 2, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );

QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((2.06 0.34, 0.87 1.1, 1.84 1.31, 2.06 0.34))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0))" ) );
QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.08 0.12, 0.87 1.1, 1.84 1.31, 2.06 0.34, 1.08 0.12))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0, 1.1 0))" ) );

mLayerBase->undoStack()->undo();
}
@@ -177,8 +179,8 @@ void TestQgsMapToolRotateFeature::testCancelManualAnchor()
utils.mouseMove( 2, 1 );
utils.mouseClick( 2, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );

QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((0.72 -0.17, 0.28 1.17, 1.17 0.72, 0.72 -0.17))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0))" ) );
QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((-0.17 0.28, 0.28 1.17, 1.17 0.72, 0.72 -0.17, -0.17 0.28))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0, 1.1 0))" ) );

mLayerBase->undoStack()->undo();
}
@@ -199,12 +201,45 @@ void TestQgsMapToolRotateFeature::testRotateFeatureManualAnchorAfterStartRotate(
utils.mouseMove( 2, 1 );
utils.mouseClick( 2, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );

QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((-5.06 5.63, -3.79 6.26, -4.11 5.32, -5.06 5.63))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0))" ) );
QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((-4.74 6.58, -3.79 6.26, -4.11 5.32, -5.06 5.63, -4.74 6.58))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0, 1.1 0))" ) );

mLayerBase->undoStack()->undo();
}

void TestQgsMapToolRotateFeature::testRotateFeatureManualAnchorSnapping()
{
// test rotating around a fixed anchor point
TestQgsMapToolUtils utils( mRotateTool );

QgsSnappingConfig cfg = mCanvas->snappingUtils()->config();
const double tolerance = cfg.tolerance();
const QgsTolerance::UnitType units = cfg.units();
cfg.setTolerance( 0.5 );
cfg.setUnits( QgsTolerance::LayerUnits );
mCanvas->snappingUtils()->setConfig( cfg );

// set anchor point, should snap to (1.1, 5)
utils.mouseMove( 1, 5.1 );
utils.mouseClick( 1, 5.1, Qt::LeftButton, Qt::ControlModifier, true );

// source point should snap to (1, 1)
utils.mouseMove( 0.9, 0.9 );
utils.mouseClick( 0.9, 0.9, Qt::LeftButton, Qt::KeyboardModifiers(), true );
// target point should snap to (2.1, 1)
utils.mouseMove( 2, 1 );
utils.mouseClick( 2, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true );

QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.37 -0.11, 1.11 0.85, 2.07 1.12, 2.34 0.15, 1.37 -0.11))" ) );
QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), QStringLiteral( "Polygon ((1.1 0, 1.1 5, 2.1 5, 2.1 0, 1.1 0))" ) );

mLayerBase->undoStack()->undo();

// restore tolerance setting
cfg.setTolerance( tolerance );
cfg.setUnits( units );
mCanvas->snappingUtils()->setConfig( cfg );
}

QGSTEST_MAIN( TestQgsMapToolRotateFeature )
#include "testqgsmaptoolrotatefeature.moc"

0 comments on commit 3c71e94

Please sign in to comment.