From afa82976865b8d4d113741111f5e212280380d59 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 16 Jul 2024 10:21:06 +1000 Subject: [PATCH] Add QgsMapToPixel method to transform bounding boxes --- .../core/auto_generated/qgsmaptopixel.sip.in | 10 ++++++ .../core/auto_generated/qgsmaptopixel.sip.in | 10 ++++++ src/core/qgsmaptopixel.h | 32 +++++++++++++++++-- tests/src/core/testqgsmaptopixel.cpp | 21 ++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in b/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in index db22d7b7b26e..486cdfc5d28c 100644 --- a/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in +++ b/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in @@ -100,6 +100,16 @@ This method modifies the given coordinates in place. It is intended as a fast wa transform. %End + QRectF transformBounds( const QRectF &bounds ) const; +%Docstring +Transforms a bounding box from map coordinates to device coordinates. + +The returns bounding box will always completely enclose the transformed input bounding box (i.e. this +method will grow the bounds wherever required). + +.. versionadded:: 3.40 +%End + QgsPointXY toMapCoordinates( int x, int y ) const; diff --git a/python/core/auto_generated/qgsmaptopixel.sip.in b/python/core/auto_generated/qgsmaptopixel.sip.in index db22d7b7b26e..486cdfc5d28c 100644 --- a/python/core/auto_generated/qgsmaptopixel.sip.in +++ b/python/core/auto_generated/qgsmaptopixel.sip.in @@ -100,6 +100,16 @@ This method modifies the given coordinates in place. It is intended as a fast wa transform. %End + QRectF transformBounds( const QRectF &bounds ) const; +%Docstring +Transforms a bounding box from map coordinates to device coordinates. + +The returns bounding box will always completely enclose the transformed input bounding box (i.e. this +method will grow the bounds wherever required). + +.. versionadded:: 3.40 +%End + QgsPointXY toMapCoordinates( int x, int y ) const; diff --git a/src/core/qgsmaptopixel.h b/src/core/qgsmaptopixel.h index 09dae6441d1d..d35b55e52321 100644 --- a/src/core/qgsmaptopixel.h +++ b/src/core/qgsmaptopixel.h @@ -20,12 +20,10 @@ #include "qgis_core.h" #include "qgis_sip.h" #include -#include #include "qgis.h" #include "qgspointxy.h" #include -#include class QPoint; @@ -131,6 +129,36 @@ class CORE_EXPORT QgsMapToPixel y = my; } + /** + * Transforms a bounding box from map coordinates to device coordinates. + * + * The returns bounding box will always completely enclose the transformed input bounding box (i.e. this + * method will grow the bounds wherever required). + * + * \since QGIS 3.40 + */ + QRectF transformBounds( const QRectF &bounds ) const + { + QPointF topLeft = bounds.topLeft(); + QPointF topRight = bounds.topRight(); + QPointF bottomLeft = bounds.bottomLeft(); + QPointF bottomRight = bounds.bottomRight(); + + transformInPlace( topLeft.rx(), topLeft.ry() ); + transformInPlace( topRight.rx(), topRight.ry() ); + transformInPlace( bottomLeft.rx(), bottomLeft.ry() ); + transformInPlace( bottomRight.rx(), bottomRight.ry() ); + + auto minMaxX = std::minmax( { topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x() } ); + auto minMaxY = std::minmax( { topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y() } ); + + const double left = minMaxX.first; + const double right = minMaxX.second; + const double top = minMaxY.first; + const double bottom = minMaxY.second; + return QRectF( left, top, right - left, bottom - top ); + } + /** * Transforms device coordinates to map coordinates. * diff --git a/tests/src/core/testqgsmaptopixel.cpp b/tests/src/core/testqgsmaptopixel.cpp index f5ba8dad34eb..d29f82fcc0bb 100644 --- a/tests/src/core/testqgsmaptopixel.cpp +++ b/tests/src/core/testqgsmaptopixel.cpp @@ -30,6 +30,7 @@ class TestQgsMapToPixel: public QObject void fromScale(); void equality(); void toMapCoordinates(); + void transformBounds(); }; void TestQgsMapToPixel::isValid() @@ -184,6 +185,26 @@ void TestQgsMapToPixel::toMapCoordinates() QCOMPARE( p, QgsPointXY( 20, 20 ) ); } +void TestQgsMapToPixel::transformBounds() +{ + // no rotation + const QgsMapToPixel m2p( 1.5, 5, 15, 20, 10, 0 ); + QRectF result = m2p.transformBounds( QRectF( 10, 20, 30, 40 ) ); + + QGSCOMPARENEAR( result.left(), 13.3333333333, 6 ); + QGSCOMPARENEAR( result.right(), 33.3333333333, 6 ); + QCOMPARE( result.top(), -25 ); + QGSCOMPARENEAR( result.bottom(), 1.66666666667, 6 ); + + // with rotation + const QgsMapToPixel m2pRotated( 1.5, 5, 15, 20, 10, 45 ); + result = m2pRotated.transformBounds( QRectF( 10, 20, 30, 40 ) ); + QGSCOMPARENEAR( result.left(), 14.7140452079, 6 ); + QGSCOMPARENEAR( result.right(), 47.7123616633, 6 ); + QGSCOMPARENEAR( result.top(), -13.8561808316, 6 ); + QGSCOMPARENEAR( result.bottom(), 19.1421356237, 6 ); +} + QGSTEST_MAIN( TestQgsMapToPixel ) #include "testqgsmaptopixel.moc"