From 33f31d826a984f3566f6f0f9614c656915d76de5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 2 Jul 2020 18:51:07 +1000 Subject: [PATCH] Fix QgsGeometry::asQPolygonF doesn't handle multipolygon inputs gracefully --- .../geometry/qgsgeometry.sip.in | 11 +++-- src/core/geometry/qgsgeometry.cpp | 40 ++++++------------- src/core/geometry/qgsgeometry.h | 12 ++++-- tests/src/core/testqgsgeometry.cpp | 28 +++++++++++++ 4 files changed, 58 insertions(+), 33 deletions(-) diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index d26e1c7690c..0b0458e72c0 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -1823,9 +1823,14 @@ otherwise returns a null QPointF. QPolygonF asQPolygonF() const; %Docstring -Returns contents of the geometry as a QPolygonF. If geometry is a linestring, -then the result will be an open QPolygonF. If the geometry is a polygon, -then the result will be a closed QPolygonF of the geometry's exterior ring. +Returns contents of the geometry as a QPolygonF. + +If geometry is a linestring, then the result will be an open QPolygonF. +If the geometry is a polygon, then the result will be a closed QPolygonF +of the geometry's exterior ring. + +If the geometry is a multi-part geometry, then only the first part will +be considered when converting to a QPolygonF. .. versionadded:: 2.7 %End diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index a873a29f297..f4742a8ccc7 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -2558,36 +2558,22 @@ QPointF QgsGeometry::asQPointF() const QPolygonF QgsGeometry::asQPolygonF() const { - const QgsWkbTypes::Type type = wkbType(); - const QgsLineString *line = nullptr; - if ( QgsWkbTypes::flatType( type ) == QgsWkbTypes::LineString ) + const QgsAbstractGeometry *part = constGet(); + + // if a geometry collection, get first part only + if ( const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection *>( part ) ) { - line = qgsgeometry_cast< const QgsLineString * >( constGet() ); - } - else if ( QgsWkbTypes::flatType( type ) == QgsWkbTypes::Polygon ) - { - const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( constGet() ); - if ( polygon ) - line = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ); + if ( collection->numGeometries() > 0 ) + part = collection->geometryN( 0 ); + else + return QPolygonF(); } - if ( line ) - { - const double *srcX = line->xData(); - const double *srcY = line->yData(); - const int count = line->numPoints(); - QPolygonF res( count ); - QPointF *dest = res.data(); - for ( int i = 0; i < count; ++i ) - { - *dest++ = QPointF( *srcX++, *srcY++ ); - } - return res; - } - else - { - return QPolygonF(); - } + if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( part ) ) + return curve->asQPolygonF(); + else if ( const QgsCurvePolygon *polygon = qgsgeometry_cast< const QgsCurvePolygon * >( part ) ) + return polygon->exteriorRing() ? polygon->exteriorRing()->asQPolygonF() : QPolygonF(); + return QPolygonF(); } bool QgsGeometry::deleteRing( int ringNum, int partNum ) diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 5c243a55c68..8df622a1ebe 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -1917,9 +1917,15 @@ class CORE_EXPORT QgsGeometry QPointF asQPointF() const; /** - * Returns contents of the geometry as a QPolygonF. If geometry is a linestring, - * then the result will be an open QPolygonF. If the geometry is a polygon, - * then the result will be a closed QPolygonF of the geometry's exterior ring. + * Returns contents of the geometry as a QPolygonF. + * + * If geometry is a linestring, then the result will be an open QPolygonF. + * If the geometry is a polygon, then the result will be a closed QPolygonF + * of the geometry's exterior ring. + * + * If the geometry is a multi-part geometry, then only the first part will + * be considered when converting to a QPolygonF. + * * \since QGIS 2.7 */ QPolygonF asQPolygonF() const; diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index b0394cd8270..09860cb56d5 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -16590,6 +16590,34 @@ void TestQgsGeometry::asQPolygonF() QgsGeometry badGeom( QgsGeometry::fromPointXY( mPoint1 ) ); QPolygonF fromBad = badGeom.asQPolygonF(); QVERIFY( fromBad.isEmpty() ); + + // test a multipolygon + QPolygonF res = QgsGeometry::fromWkt( QStringLiteral( "MultiPolygon (((0 0, 10 0, 10 10, 0 10, 0 0 )),((2 2, 4 2, 4 4, 2 4, 2 2)))" ) ).asQPolygonF(); + QVERIFY( res.isClosed() ); + QCOMPARE( res.size(), 5 ); + QCOMPARE( res.at( 0 ).x(), 0.0 ); + QCOMPARE( res.at( 0 ).y(), 0.0 ); + QCOMPARE( res.at( 1 ).x(), 10.0 ); + QCOMPARE( res.at( 1 ).y(), 0.0 ); + QCOMPARE( res.at( 2 ).x(), 10.0 ); + QCOMPARE( res.at( 2 ).y(), 10.0 ); + QCOMPARE( res.at( 3 ).x(), 0.0 ); + QCOMPARE( res.at( 3 ).y(), 10.0 ); + QCOMPARE( res.at( 4 ).x(), 0.0 ); + QCOMPARE( res.at( 4 ).y(), 0.0 ); + + // test a multilinestring + res = QgsGeometry::fromWkt( QStringLiteral( "MultiLineString((0 0, 10 0, 10 10, 0 10 ),(2 2, 4 2, 4 4, 2 4))" ) ).asQPolygonF(); + QVERIFY( !res.isClosed() ); + QCOMPARE( res.size(), 4 ); + QCOMPARE( res.at( 0 ).x(), 0.0 ); + QCOMPARE( res.at( 0 ).y(), 0.0 ); + QCOMPARE( res.at( 1 ).x(), 10.0 ); + QCOMPARE( res.at( 1 ).y(), 0.0 ); + QCOMPARE( res.at( 2 ).x(), 10.0 ); + QCOMPARE( res.at( 2 ).y(), 10.0 ); + QCOMPARE( res.at( 3 ).x(), 0.0 ); + QCOMPARE( res.at( 3 ).y(), 10.0 ); } void TestQgsGeometry::comparePolylines()