Skip to content

Commit 938adb8

Browse files
committedJan 11, 2019
[api] Add PyQGIS helpers to QgsGeometry.asMultiPoint(), asMultiPolyline()
and asMultiPolygon() - raise ValueError when these methods are called with null geometries - raise TypeError when these methods are called with incompatible geometry types, instead of silently returning empty lists
1 parent 2664737 commit 938adb8

File tree

3 files changed

+264
-15
lines changed

3 files changed

+264
-15
lines changed
 

‎python/core/auto_generated/geometry/qgsgeometry.sip.in

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,22 +1515,96 @@ will be raised.
15151515
}
15161516
%End
15171517

1518-
QgsMultiPointXY asMultiPoint() const;
1518+
1519+
SIP_PYOBJECT asMultiPoint() const /TypeHint="QgsMultiPointXY"/;
15191520
%Docstring
1520-
Returns contents of the geometry as a multi point
1521-
if wkbType is WKBMultiPoint, otherwise an empty list
1521+
Returns the contents of the geometry as a multi-point.
1522+
1523+
Any z or m values present in the geometry will be discarded.
1524+
1525+
This method works only with multi-point geometry types. If the geometry
1526+
is not a multi-point type, a TypeError will be raised. If the geometry is null, a ValueError
1527+
will be raised.
1528+
%End
1529+
%MethodCode
1530+
const QgsWkbTypes::Type type = sipCpp->wkbType();
1531+
if ( sipCpp->isNull() )
1532+
{
1533+
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipoint." ).toUtf8().constData() );
1534+
sipIsErr = 1;
1535+
}
1536+
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PointGeometry || !QgsWkbTypes::isMultiType( type ) )
1537+
{
1538+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multipoint. Only multipoint types are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
1539+
sipIsErr = 1;
1540+
}
1541+
else
1542+
{
1543+
const sipMappedType *qvector_type = sipFindMappedType( "QVector< QgsPointXY >" );
1544+
sipRes = sipConvertFromNewType( new QgsPolylineXY( sipCpp->asMultiPoint() ), qvector_type, Py_None );
1545+
}
15221546
%End
15231547

1524-
QgsMultiPolylineXY asMultiPolyline() const;
1548+
1549+
SIP_PYOBJECT asMultiPolyline() const /TypeHint="QgsMultiPolylineXY"/;
15251550
%Docstring
1526-
Returns contents of the geometry as a multi linestring
1527-
if wkbType is WKBMultiLineString, otherwise an empty list
1551+
Returns the contents of the geometry as a multi-linestring.
1552+
1553+
Any z or m values present in the geometry will be discarded. If the geometry is a curved line type
1554+
(such as a MultiCurve), it will be automatically segmentized.
1555+
1556+
This method works only with multi-linestring (or multi-curve) geometry types. If the geometry
1557+
is not a multi-linestring type, a TypeError will be raised. If the geometry is null, a ValueError
1558+
will be raised.
1559+
%End
1560+
%MethodCode
1561+
const QgsWkbTypes::Type type = sipCpp->wkbType();
1562+
if ( sipCpp->isNull() )
1563+
{
1564+
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multilinestring." ).toUtf8().constData() );
1565+
sipIsErr = 1;
1566+
}
1567+
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::LineGeometry || !QgsWkbTypes::isMultiType( type ) )
1568+
{
1569+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multilinestring. Only multi linestring or curves are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
1570+
sipIsErr = 1;
1571+
}
1572+
else
1573+
{
1574+
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QgsPointXY>>" );
1575+
sipRes = sipConvertFromNewType( new QgsMultiPolylineXY( sipCpp->asMultiPolyline() ), qvector_type, Py_None );
1576+
}
15281577
%End
15291578

1530-
QgsMultiPolygonXY asMultiPolygon() const;
1579+
1580+
SIP_PYOBJECT asMultiPolygon() const /TypeHint="QgsMultiPolygonXY"/;
15311581
%Docstring
1532-
Returns contents of the geometry as a multi polygon
1533-
if wkbType is WKBMultiPolygon, otherwise an empty list
1582+
Returns the contents of the geometry as a multi-polygon.
1583+
1584+
Any z or m values present in the geometry will be discarded. If the geometry is a curved polygon type
1585+
(such as a MultiSurface), it will be automatically segmentized.
1586+
1587+
This method works only with multi-polygon (or multi-curve polygon) geometry types. If the geometry
1588+
is not a multi-polygon type, a TypeError will be raised. If the geometry is null, a ValueError
1589+
will be raised.
1590+
%End
1591+
%MethodCode
1592+
const QgsWkbTypes::Type type = sipCpp->wkbType();
1593+
if ( sipCpp->isNull() )
1594+
{
1595+
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipolygon." ).toUtf8().constData() );
1596+
sipIsErr = 1;
1597+
}
1598+
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PolygonGeometry || !QgsWkbTypes::isMultiType( type ) )
1599+
{
1600+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multipolygon. Only multi polygon or curves are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
1601+
sipIsErr = 1;
1602+
}
1603+
else
1604+
{
1605+
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QVector<QgsPointXY>>>" );
1606+
sipRes = sipConvertFromNewType( new QgsMultiPolygonXY( sipCpp->asMultiPolygon() ), qvector_type, Py_None );
1607+
}
15341608
%End
15351609

15361610
QVector<QgsGeometry> asGeometryCollection() const;

‎src/core/geometry/qgsgeometry.h

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,23 +1552,135 @@ class CORE_EXPORT QgsGeometry
15521552
% End
15531553
#endif
15541554

1555+
#ifndef SIP_RUN
1556+
15551557
/**
1556-
* Returns contents of the geometry as a multi point
1557-
* if wkbType is WKBMultiPoint, otherwise an empty list
1558+
* Returns the contents of the geometry as a multi-point.
1559+
*
1560+
* Any z or m values present in the geometry will be discarded.
1561+
*
1562+
* \warning If the geometry is not a multi-point type, an empty list will be returned.
15581563
*/
15591564
QgsMultiPointXY asMultiPoint() const;
1565+
#else
1566+
1567+
/**
1568+
* Returns the contents of the geometry as a multi-point.
1569+
*
1570+
* Any z or m values present in the geometry will be discarded.
1571+
*
1572+
* This method works only with multi-point geometry types. If the geometry
1573+
* is not a multi-point type, a TypeError will be raised. If the geometry is null, a ValueError
1574+
* will be raised.
1575+
*/
1576+
SIP_PYOBJECT asMultiPoint() const SIP_TYPEHINT( QgsMultiPointXY );
1577+
% MethodCode
1578+
const QgsWkbTypes::Type type = sipCpp->wkbType();
1579+
if ( sipCpp->isNull() )
1580+
{
1581+
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipoint." ).toUtf8().constData() );
1582+
sipIsErr = 1;
1583+
}
1584+
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PointGeometry || !QgsWkbTypes::isMultiType( type ) )
1585+
{
1586+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multipoint. Only multipoint types are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
1587+
sipIsErr = 1;
1588+
}
1589+
else
1590+
{
1591+
const sipMappedType *qvector_type = sipFindMappedType( "QVector< QgsPointXY >" );
1592+
sipRes = sipConvertFromNewType( new QgsPolylineXY( sipCpp->asMultiPoint() ), qvector_type, Py_None );
1593+
}
1594+
% End
1595+
#endif
1596+
1597+
#ifndef SIP_RUN
15601598

15611599
/**
1562-
* Returns contents of the geometry as a multi linestring
1563-
* if wkbType is WKBMultiLineString, otherwise an empty list
1600+
* Returns the contents of the geometry as a multi-linestring.
1601+
*
1602+
* Any z or m values present in the geometry will be discarded. If the geometry is a curved line type
1603+
* (such as a MultiCurve), it will be automatically segmentized.
1604+
*
1605+
* \warning If the geometry is not a multi-linestring (or multi-curve linestring) type, an empty list will be returned.
15641606
*/
15651607
QgsMultiPolylineXY asMultiPolyline() const;
1608+
#else
15661609

15671610
/**
1568-
* Returns contents of the geometry as a multi polygon
1569-
* if wkbType is WKBMultiPolygon, otherwise an empty list
1611+
* Returns the contents of the geometry as a multi-linestring.
1612+
*
1613+
* Any z or m values present in the geometry will be discarded. If the geometry is a curved line type
1614+
* (such as a MultiCurve), it will be automatically segmentized.
1615+
*
1616+
* This method works only with multi-linestring (or multi-curve) geometry types. If the geometry
1617+
* is not a multi-linestring type, a TypeError will be raised. If the geometry is null, a ValueError
1618+
* will be raised.
1619+
*/
1620+
SIP_PYOBJECT asMultiPolyline() const SIP_TYPEHINT( QgsMultiPolylineXY );
1621+
% MethodCode
1622+
const QgsWkbTypes::Type type = sipCpp->wkbType();
1623+
if ( sipCpp->isNull() )
1624+
{
1625+
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multilinestring." ).toUtf8().constData() );
1626+
sipIsErr = 1;
1627+
}
1628+
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::LineGeometry || !QgsWkbTypes::isMultiType( type ) )
1629+
{
1630+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multilinestring. Only multi linestring or curves are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
1631+
sipIsErr = 1;
1632+
}
1633+
else
1634+
{
1635+
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QgsPointXY>>" );
1636+
sipRes = sipConvertFromNewType( new QgsMultiPolylineXY( sipCpp->asMultiPolyline() ), qvector_type, Py_None );
1637+
}
1638+
% End
1639+
#endif
1640+
1641+
#ifndef SIP_RUN
1642+
1643+
/**
1644+
* Returns the contents of the geometry as a multi-polygon.
1645+
*
1646+
* Any z or m values present in the geometry will be discarded. If the geometry is a curved polygon type
1647+
* (such as a MultiSurface), it will be automatically segmentized.
1648+
*
1649+
* \warning If the geometry is not a multi-polygon (or multi-curve polygon) type, an empty list will be returned.
15701650
*/
15711651
QgsMultiPolygonXY asMultiPolygon() const;
1652+
#else
1653+
1654+
/**
1655+
* Returns the contents of the geometry as a multi-polygon.
1656+
*
1657+
* Any z or m values present in the geometry will be discarded. If the geometry is a curved polygon type
1658+
* (such as a MultiSurface), it will be automatically segmentized.
1659+
*
1660+
* This method works only with multi-polygon (or multi-curve polygon) geometry types. If the geometry
1661+
* is not a multi-polygon type, a TypeError will be raised. If the geometry is null, a ValueError
1662+
* will be raised.
1663+
*/
1664+
SIP_PYOBJECT asMultiPolygon() const SIP_TYPEHINT( QgsMultiPolygonXY );
1665+
% MethodCode
1666+
const QgsWkbTypes::Type type = sipCpp->wkbType();
1667+
if ( sipCpp->isNull() )
1668+
{
1669+
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipolygon." ).toUtf8().constData() );
1670+
sipIsErr = 1;
1671+
}
1672+
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PolygonGeometry || !QgsWkbTypes::isMultiType( type ) )
1673+
{
1674+
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multipolygon. Only multi polygon or curves are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
1675+
sipIsErr = 1;
1676+
}
1677+
else
1678+
{
1679+
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QVector<QgsPointXY>>>" );
1680+
sipRes = sipConvertFromNewType( new QgsMultiPolygonXY( sipCpp->asMultiPolygon() ), qvector_type, Py_None );
1681+
}
1682+
% End
1683+
#endif
15721684

15731685
/**
15741686
* Returns contents of the geometry as a list of geometries

‎tests/src/python/test_qgsgeometry.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,69 @@ def testPointXY(self):
601601
with self.assertRaises(ValueError):
602602
QgsGeometry().asPolygon()
603603

604+
# as multipoint
605+
self.assertEqual(QgsGeometry.fromWkt('MultiPoint(11 13,14 15)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
606+
self.assertEqual(QgsGeometry.fromWkt('MultiPointZ(11 13 1,14 15 2)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
607+
self.assertEqual(QgsGeometry.fromWkt('MultiPointM(11 13 1,14 15 2)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
608+
self.assertEqual(QgsGeometry.fromWkt('MultiPointZM(11 13 1 2,14 15 3 4)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
609+
with self.assertRaises(TypeError):
610+
QgsGeometry.fromWkt('Point(11 13)').asMultiPoint()
611+
with self.assertRaises(TypeError):
612+
QgsGeometry.fromWkt('LineString(11 13,14 15)').asMultiPoint()
613+
with self.assertRaises(TypeError):
614+
QgsGeometry.fromWkt('MultiLineString((11 13, 14 15),(1 2, 3 4))').asMultiPoint()
615+
with self.assertRaises(TypeError):
616+
QgsGeometry.fromWkt('Polygon((11 13,14 15, 14 13, 11 13))').asMultiPoint()
617+
with self.assertRaises(ValueError):
618+
QgsGeometry().asMultiPoint()
619+
620+
# as multilinestring
621+
self.assertEqual(QgsGeometry.fromWkt('MultiLineString((11 13,14 15, 11 15, 11 13))').asMultiPolyline(),
622+
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
623+
self.assertEqual(QgsGeometry.fromWkt('MultiLineStringZ((11 13 1,14 15 2, 11 15 3, 11 13 1))').asMultiPolyline(),
624+
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
625+
self.assertEqual(QgsGeometry.fromWkt('MultiLineStringM((11 13 1,14 15 2, 11 15 3, 11 13 1))').asMultiPolyline(),
626+
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
627+
self.assertEqual(QgsGeometry.fromWkt('MultiLineStringZM((11 13 1 11,14 15 2 12 , 11 15 3 13 , 11 13 1 11))').asMultiPolyline(),
628+
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
629+
with self.assertRaises(TypeError):
630+
QgsGeometry.fromWkt('Point(11 13)').asMultiPolyline()
631+
with self.assertRaises(TypeError):
632+
QgsGeometry.fromWkt('MultiPoint(11 13,14 15)').asMultiPolyline()
633+
with self.assertRaises(TypeError):
634+
QgsGeometry.fromWkt('Polygon((11 13, 14 15, 17 18, 11 13))').asMultiPolyline()
635+
with self.assertRaises(TypeError):
636+
QgsGeometry.fromWkt('LineString(11 13,14 15)').asPolygon()
637+
with self.assertRaises(TypeError):
638+
QgsGeometry.fromWkt('MultiPolygon(((11 13,14 15, 11 15, 11 13)))').asMultiPolyline()
639+
with self.assertRaises(ValueError):
640+
QgsGeometry().asPolygon()
641+
642+
# as multipolygon
643+
self.assertEqual(QgsGeometry.fromWkt('MultiPolygon(((11 13,14 15, 11 15, 11 13)))').asMultiPolygon(),
644+
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
645+
self.assertEqual(
646+
QgsGeometry.fromWkt('MultiPolygonZ(((11 13 1,14 15 2, 11 15 3 , 11 13 1)))').asMultiPolygon(),
647+
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
648+
self.assertEqual(
649+
QgsGeometry.fromWkt('MultiPolygonM(((11 13 1,14 15 2, 11 15 3 , 11 13 1)))').asMultiPolygon(),
650+
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
651+
self.assertEqual(QgsGeometry.fromWkt(
652+
'MultiPolygonZM(((11 13 1 11,14 15 2 12, 11 15 3 13, 11 13 1 11)))').asMultiPolygon(),
653+
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
654+
with self.assertRaises(TypeError):
655+
QgsGeometry.fromWkt('Point(11 13)').asMultiPolygon()
656+
with self.assertRaises(TypeError):
657+
QgsGeometry.fromWkt('MultiPoint(11 13,14 15)').asMultiPolygon()
658+
with self.assertRaises(TypeError):
659+
QgsGeometry.fromWkt('Polygon((11 13, 14 15, 17 18, 11 13))').asMultiPolygon()
660+
with self.assertRaises(TypeError):
661+
QgsGeometry.fromWkt('LineString(11 13,14 15)').asPolygon()
662+
with self.assertRaises(TypeError):
663+
QgsGeometry.fromWkt('MultiLineString((11 13,14 15, 11 15, 11 13))').asMultiPolygon()
664+
with self.assertRaises(ValueError):
665+
QgsGeometry().asPolygon()
666+
604667
def testReferenceGeometry(self):
605668
""" Test parsing a whole range of valid reference wkt formats and variants, and checking
606669
expected values such as length, area, centroids, bounding boxes, etc of the resultant geometry.

0 commit comments

Comments
 (0)
Please sign in to comment.