diff --git a/src/core/geometry/qgsrectangle.cpp b/src/core/geometry/qgsrectangle.cpp index cdae2cb68dd..38084447034 100644 --- a/src/core/geometry/qgsrectangle.cpp +++ b/src/core/geometry/qgsrectangle.cpp @@ -254,7 +254,7 @@ QgsRectangle &QgsRectangle::operator+=( const QgsVector v ) bool QgsRectangle::isEmpty() const { - return mXmax <= mXmin || mYmax <= mYmin; + return mXmax < mXmin || mYmax < mYmin || qgsDoubleNear( mXmax, mXmin ) || qgsDoubleNear( mYmax, mYmin ); } bool QgsRectangle::isNull() const diff --git a/tests/src/core/testqgsrectangle.cpp b/tests/src/core/testqgsrectangle.cpp index bc1bd62a4f5..25350aea59f 100644 --- a/tests/src/core/testqgsrectangle.cpp +++ b/tests/src/core/testqgsrectangle.cpp @@ -25,13 +25,37 @@ class TestQgsRectangle: public QObject { Q_OBJECT private slots: + void isEmpty(); void manipulate(); void regression6194(); void operators(); void asVariant(); void referenced(); + void minimal(); + void grow(); + void include(); + void buffered(); + void isFinite(); + void dataStream(); }; +void TestQgsRectangle::isEmpty() +{ + QVERIFY( QgsRectangle().isEmpty() ); + QVERIFY( QgsRectangle( 1, 2, 1, 4 ).isEmpty() ); + QVERIFY( QgsRectangle( 2, 1, 4, 1 ).isEmpty() ); + //constructor should normalize + QVERIFY( !QgsRectangle( 2, 2, 1, 4 ).isEmpty() ); + QVERIFY( !QgsRectangle( 1, 2, 2, 1 ).isEmpty() ); + + QgsRectangle r( 2, 2, 3, 4 ); + r.setXMaximum( 1 ); + QVERIFY( r.isEmpty() ); + r = QgsRectangle( 2, 2, 3, 4 ); + r.setYMaximum( 1 ); + QVERIFY( r.isEmpty() ); +} + void TestQgsRectangle::manipulate() { // Set up two intersecting rectangles and normalize @@ -155,5 +179,111 @@ void TestQgsRectangle::referenced() QCOMPARE( rect2.crs().authid(), QStringLiteral( "EPSG:28356" ) ); } +void TestQgsRectangle::minimal() +{ + QgsRectangle rect1 = QgsRectangle( 10.0, 20.0, 110.0, 220.0 ); + rect1.setMinimal(); + QVERIFY( rect1.isEmpty() ); + QVERIFY( rect1.isNull() ); +} + +void TestQgsRectangle::grow() +{ + QgsRectangle rect1 = QgsRectangle( 10.0, 20.0, 110.0, 220.0 ); + rect1.grow( 11 ); + QCOMPARE( rect1.xMinimum(), -1.0 ); + QCOMPARE( rect1.yMinimum(), 9.0 ); + QCOMPARE( rect1.xMaximum(), 121.0 ); + QCOMPARE( rect1.yMaximum(), 231.0 ); + + QgsRectangle rect2 = QgsRectangle( -110.0, -220.0, -10.0, -20.0 ); + rect2.grow( 11 ); + QCOMPARE( rect2.xMinimum(), -121.0 ); + QCOMPARE( rect2.yMinimum(), -231.0 ); + QCOMPARE( rect2.xMaximum(), 1.0 ); + QCOMPARE( rect2.yMaximum(), -9.0 ); +} + +void TestQgsRectangle::include() +{ + QgsRectangle rect1 = QgsRectangle( 10.0, 20.0, 110.0, 220.0 ); + // inside + rect1.include( QgsPointXY( 15, 50 ) ); + QCOMPARE( rect1.xMinimum(), 10.0 ); + QCOMPARE( rect1.yMinimum(), 20.0 ); + QCOMPARE( rect1.xMaximum(), 110.0 ); + QCOMPARE( rect1.yMaximum(), 220.0 ); + + rect1.include( QgsPointXY( 5, 50 ) ); + QCOMPARE( rect1.xMinimum(), 5.0 ); + QCOMPARE( rect1.yMinimum(), 20.0 ); + QCOMPARE( rect1.xMaximum(), 110.0 ); + QCOMPARE( rect1.yMaximum(), 220.0 ); + + rect1.include( QgsPointXY( 15, 12 ) ); + QCOMPARE( rect1.xMinimum(), 5.0 ); + QCOMPARE( rect1.yMinimum(), 12.0 ); + QCOMPARE( rect1.xMaximum(), 110.0 ); + QCOMPARE( rect1.yMaximum(), 220.0 ); + + rect1.include( QgsPointXY( 115, 12 ) ); + QCOMPARE( rect1.xMinimum(), 5.0 ); + QCOMPARE( rect1.yMinimum(), 12.0 ); + QCOMPARE( rect1.xMaximum(), 115.0 ); + QCOMPARE( rect1.yMaximum(), 220.0 ); + + rect1.include( QgsPointXY( 115, 242 ) ); + QCOMPARE( rect1.xMinimum(), 5.0 ); + QCOMPARE( rect1.yMinimum(), 12.0 ); + QCOMPARE( rect1.xMaximum(), 115.0 ); + QCOMPARE( rect1.yMaximum(), 242.0 ); + +} + +void TestQgsRectangle::buffered() +{ + QgsRectangle rect = QgsRectangle( 10.0, 20.0, 110.0, 220.0 ); + QgsRectangle rect1 = rect.buffered( 11 ); + QCOMPARE( rect1.xMinimum(), -1.0 ); + QCOMPARE( rect1.yMinimum(), 9.0 ); + QCOMPARE( rect1.xMaximum(), 121.0 ); + QCOMPARE( rect1.yMaximum(), 231.0 ); + + rect = QgsRectangle( -110.0, -220.0, -10.0, -20.0 ); + QgsRectangle rect2 = rect.buffered( 11 ); + QCOMPARE( rect2.xMinimum(), -121.0 ); + QCOMPARE( rect2.yMinimum(), -231.0 ); + QCOMPARE( rect2.xMaximum(), 1.0 ); + QCOMPARE( rect2.yMaximum(), -9.0 ); +} + +void TestQgsRectangle::isFinite() +{ + QVERIFY( QgsRectangle( 1, 2, 3, 4 ).isFinite() ); + QVERIFY( !QgsRectangle( std::numeric_limits::infinity(), 2, 3, 4 ).isFinite() ); + QVERIFY( !QgsRectangle( 1, std::numeric_limits::infinity(), 3, 4 ).isFinite() ); + QVERIFY( !QgsRectangle( 1, 2, std::numeric_limits::infinity(), 4 ).isFinite() ); + QVERIFY( !QgsRectangle( 1, 2, 3, std::numeric_limits::infinity() ).isFinite() ); + QVERIFY( !QgsRectangle( std::numeric_limits::quiet_NaN(), 2, 3, 4 ).isFinite() ); + QVERIFY( !QgsRectangle( 1, std::numeric_limits::quiet_NaN(), 3, 4 ).isFinite() ); + QVERIFY( !QgsRectangle( 1, 2, std::numeric_limits::quiet_NaN(), 4 ).isFinite() ); + QVERIFY( !QgsRectangle( 1, 2, 3, std::numeric_limits::quiet_NaN() ).isFinite() ); +} + +void TestQgsRectangle::dataStream() +{ + QgsRectangle original( 10.1, 20.2, 110.3, 220.4 ); + + QByteArray ba; + QDataStream ds( &ba, QIODevice::ReadWrite ); + ds << original; + + QgsRectangle result; + ds.device()->seek( 0 ); + ds >> result; + + QCOMPARE( result, original ); +} + QGSTEST_MAIN( TestQgsRectangle ) #include "testqgsrectangle.moc" diff --git a/tests/src/python/test_qgsrectangle.py b/tests/src/python/test_qgsrectangle.py index 63c0f046103..195a7c0543f 100644 --- a/tests/src/python/test_qgsrectangle.py +++ b/tests/src/python/test_qgsrectangle.py @@ -24,17 +24,8 @@ start_app() class TestQgsRectangle(unittest.TestCase): - # Because isEmpty() is not returning expected result in 9b0fee3 - - @unittest.expectedFailure def testCtor(self): rect = QgsRectangle(5.0, 5.0, 10.0, 10.0) - - myExpectedResult = True - myResult = rect.isEmpty() - myMessage = ('Expected: %s Got: %s' % (myExpectedResult, myResult)) - assert rect.isEmpty(), myMessage - myMessage = ('Expected: %s\nGot: %s\n' % (5.0, rect.xMinimum())) assert rect.xMinimum() == 5.0, myMessage @@ -204,6 +195,7 @@ class TestQgsRectangle(unittest.TestCase): def testToString(self): """Test the different string representations""" + self.assertEqual(QgsRectangle().toString(), 'Empty') rect = QgsRectangle(0, 0.1, 0.2, 0.3) myExpectedString = '0.0000000000000000,0.1000000000000000 : 0.2000000000000000,0.3000000000000000' myString = rect.toString() @@ -211,6 +203,14 @@ class TestQgsRectangle(unittest.TestCase): (myExpectedString, myString)) assert myString == myExpectedString, myMessage + # can't test the actual result here, because floating point inaccuracies mean the result is unpredictable + # at this precision + self.assertEqual(len(rect.toString(20)), 93) + + myMessage = ('Expected: %s\nGot: %s\n' % + (myExpectedString, myString)) + assert myString == myExpectedString, myMessage + myExpectedString = '0,0 : 0,0' myString = rect.toString(0) myMessage = ('Expected: %s\nGot: %s\n' % @@ -242,6 +242,11 @@ class TestQgsRectangle(unittest.TestCase): (myExpectedString, myString)) assert myString == myExpectedString, myMessage + def testAsPolygon(self): + """Test string representation as polygon""" + self.assertEqual(QgsRectangle().asPolygon(), '0.00000000 0.00000000, 0.00000000 0.00000000, 0.00000000 0.00000000, 0.00000000 0.00000000, 0.00000000 0.00000000') + self.assertEqual(QgsRectangle(0, 0.1, 0.2, 0.3).asPolygon(), '0.00000000 0.10000000, 0.00000000 0.30000000, 0.20000000 0.30000000, 0.20000000 0.10000000, 0.00000000 0.10000000') + def testToBox3d(self): rect = QgsRectangle(0, 0.1, 0.2, 0.3) box = rect.toBox3d(0.4, 0.5) @@ -261,6 +266,14 @@ class TestQgsRectangle(unittest.TestCase): rect1 -= rect1.center() - QgsPointXY(0, 0) assert rect1.center() == QgsPointXY(0, 0) + def testInvert(self): + rect = QgsRectangle(0, 0.1, 0.2, 0.3) + rect.invert() + self.assertEqual(rect.xMinimum(), 0.1) + self.assertEqual(rect.yMinimum(), 0) + self.assertEqual(rect.xMaximum(), 0.3) + self.assertEqual(rect.yMaximum(), 0.2) + if __name__ == '__main__': unittest.main()