Navigation Menu

Skip to content

Commit

Permalink
[api] Add PyQGIS helpers to QgsGeometry.asMultiPoint(), asMultiPolyli…
Browse files Browse the repository at this point in the history
…ne()

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
  • Loading branch information
nyalldawson committed Jan 11, 2019
1 parent 2664737 commit 938adb8
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 15 deletions.
92 changes: 83 additions & 9 deletions python/core/auto_generated/geometry/qgsgeometry.sip.in
Expand Up @@ -1515,22 +1515,96 @@ will be raised.
}
%End

QgsMultiPointXY asMultiPoint() const;

SIP_PYOBJECT asMultiPoint() const /TypeHint="QgsMultiPointXY"/;
%Docstring
Returns contents of the geometry as a multi point
if wkbType is WKBMultiPoint, otherwise an empty list
Returns the contents of the geometry as a multi-point.

Any z or m values present in the geometry will be discarded.

This method works only with multi-point geometry types. If the geometry
is not a multi-point type, a TypeError will be raised. If the geometry is null, a ValueError
will be raised.
%End
%MethodCode
const QgsWkbTypes::Type type = sipCpp->wkbType();
if ( sipCpp->isNull() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipoint." ).toUtf8().constData() );
sipIsErr = 1;
}
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PointGeometry || !QgsWkbTypes::isMultiType( type ) )
{
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multipoint. Only multipoint types are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
sipIsErr = 1;
}
else
{
const sipMappedType *qvector_type = sipFindMappedType( "QVector< QgsPointXY >" );
sipRes = sipConvertFromNewType( new QgsPolylineXY( sipCpp->asMultiPoint() ), qvector_type, Py_None );
}
%End

QgsMultiPolylineXY asMultiPolyline() const;

SIP_PYOBJECT asMultiPolyline() const /TypeHint="QgsMultiPolylineXY"/;
%Docstring
Returns contents of the geometry as a multi linestring
if wkbType is WKBMultiLineString, otherwise an empty list
Returns the contents of the geometry as a multi-linestring.

Any z or m values present in the geometry will be discarded. If the geometry is a curved line type
(such as a MultiCurve), it will be automatically segmentized.

This method works only with multi-linestring (or multi-curve) geometry types. If the geometry
is not a multi-linestring type, a TypeError will be raised. If the geometry is null, a ValueError
will be raised.
%End
%MethodCode
const QgsWkbTypes::Type type = sipCpp->wkbType();
if ( sipCpp->isNull() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multilinestring." ).toUtf8().constData() );
sipIsErr = 1;
}
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::LineGeometry || !QgsWkbTypes::isMultiType( type ) )
{
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() );
sipIsErr = 1;
}
else
{
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QgsPointXY>>" );
sipRes = sipConvertFromNewType( new QgsMultiPolylineXY( sipCpp->asMultiPolyline() ), qvector_type, Py_None );
}
%End

QgsMultiPolygonXY asMultiPolygon() const;

SIP_PYOBJECT asMultiPolygon() const /TypeHint="QgsMultiPolygonXY"/;
%Docstring
Returns contents of the geometry as a multi polygon
if wkbType is WKBMultiPolygon, otherwise an empty list
Returns the contents of the geometry as a multi-polygon.

Any z or m values present in the geometry will be discarded. If the geometry is a curved polygon type
(such as a MultiSurface), it will be automatically segmentized.

This method works only with multi-polygon (or multi-curve polygon) geometry types. If the geometry
is not a multi-polygon type, a TypeError will be raised. If the geometry is null, a ValueError
will be raised.
%End
%MethodCode
const QgsWkbTypes::Type type = sipCpp->wkbType();
if ( sipCpp->isNull() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipolygon." ).toUtf8().constData() );
sipIsErr = 1;
}
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PolygonGeometry || !QgsWkbTypes::isMultiType( type ) )
{
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() );
sipIsErr = 1;
}
else
{
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QVector<QgsPointXY>>>" );
sipRes = sipConvertFromNewType( new QgsMultiPolygonXY( sipCpp->asMultiPolygon() ), qvector_type, Py_None );
}
%End

QVector<QgsGeometry> asGeometryCollection() const;
Expand Down
124 changes: 118 additions & 6 deletions src/core/geometry/qgsgeometry.h
Expand Up @@ -1552,23 +1552,135 @@ class CORE_EXPORT QgsGeometry
% End
#endif

#ifndef SIP_RUN

/**
* Returns contents of the geometry as a multi point
* if wkbType is WKBMultiPoint, otherwise an empty list
* Returns the contents of the geometry as a multi-point.
*
* Any z or m values present in the geometry will be discarded.
*
* \warning If the geometry is not a multi-point type, an empty list will be returned.
*/
QgsMultiPointXY asMultiPoint() const;
#else

/**
* Returns the contents of the geometry as a multi-point.
*
* Any z or m values present in the geometry will be discarded.
*
* This method works only with multi-point geometry types. If the geometry
* is not a multi-point type, a TypeError will be raised. If the geometry is null, a ValueError
* will be raised.
*/
SIP_PYOBJECT asMultiPoint() const SIP_TYPEHINT( QgsMultiPointXY );
% MethodCode
const QgsWkbTypes::Type type = sipCpp->wkbType();
if ( sipCpp->isNull() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipoint." ).toUtf8().constData() );
sipIsErr = 1;
}
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PointGeometry || !QgsWkbTypes::isMultiType( type ) )
{
PyErr_SetString( PyExc_TypeError, QStringLiteral( "%1 geometry cannot be converted to a multipoint. Only multipoint types are permitted." ).arg( QgsWkbTypes::displayString( type ) ).toUtf8().constData() );
sipIsErr = 1;
}
else
{
const sipMappedType *qvector_type = sipFindMappedType( "QVector< QgsPointXY >" );
sipRes = sipConvertFromNewType( new QgsPolylineXY( sipCpp->asMultiPoint() ), qvector_type, Py_None );
}
% End
#endif

#ifndef SIP_RUN

/**
* Returns contents of the geometry as a multi linestring
* if wkbType is WKBMultiLineString, otherwise an empty list
* Returns the contents of the geometry as a multi-linestring.
*
* Any z or m values present in the geometry will be discarded. If the geometry is a curved line type
* (such as a MultiCurve), it will be automatically segmentized.
*
* \warning If the geometry is not a multi-linestring (or multi-curve linestring) type, an empty list will be returned.
*/
QgsMultiPolylineXY asMultiPolyline() const;
#else

/**
* Returns contents of the geometry as a multi polygon
* if wkbType is WKBMultiPolygon, otherwise an empty list
* Returns the contents of the geometry as a multi-linestring.
*
* Any z or m values present in the geometry will be discarded. If the geometry is a curved line type
* (such as a MultiCurve), it will be automatically segmentized.
*
* This method works only with multi-linestring (or multi-curve) geometry types. If the geometry
* is not a multi-linestring type, a TypeError will be raised. If the geometry is null, a ValueError
* will be raised.
*/
SIP_PYOBJECT asMultiPolyline() const SIP_TYPEHINT( QgsMultiPolylineXY );
% MethodCode
const QgsWkbTypes::Type type = sipCpp->wkbType();
if ( sipCpp->isNull() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multilinestring." ).toUtf8().constData() );
sipIsErr = 1;
}
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::LineGeometry || !QgsWkbTypes::isMultiType( type ) )
{
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() );
sipIsErr = 1;
}
else
{
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QgsPointXY>>" );
sipRes = sipConvertFromNewType( new QgsMultiPolylineXY( sipCpp->asMultiPolyline() ), qvector_type, Py_None );
}
% End
#endif

#ifndef SIP_RUN

/**
* Returns the contents of the geometry as a multi-polygon.
*
* Any z or m values present in the geometry will be discarded. If the geometry is a curved polygon type
* (such as a MultiSurface), it will be automatically segmentized.
*
* \warning If the geometry is not a multi-polygon (or multi-curve polygon) type, an empty list will be returned.
*/
QgsMultiPolygonXY asMultiPolygon() const;
#else

/**
* Returns the contents of the geometry as a multi-polygon.
*
* Any z or m values present in the geometry will be discarded. If the geometry is a curved polygon type
* (such as a MultiSurface), it will be automatically segmentized.
*
* This method works only with multi-polygon (or multi-curve polygon) geometry types. If the geometry
* is not a multi-polygon type, a TypeError will be raised. If the geometry is null, a ValueError
* will be raised.
*/
SIP_PYOBJECT asMultiPolygon() const SIP_TYPEHINT( QgsMultiPolygonXY );
% MethodCode
const QgsWkbTypes::Type type = sipCpp->wkbType();
if ( sipCpp->isNull() )
{
PyErr_SetString( PyExc_ValueError, QStringLiteral( "Null geometry cannot be converted to a multipolygon." ).toUtf8().constData() );
sipIsErr = 1;
}
else if ( QgsWkbTypes::geometryType( type ) != QgsWkbTypes::PolygonGeometry || !QgsWkbTypes::isMultiType( type ) )
{
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() );
sipIsErr = 1;
}
else
{
const sipMappedType *qvector_type = sipFindMappedType( "QVector<QVector<QVector<QgsPointXY>>>" );
sipRes = sipConvertFromNewType( new QgsMultiPolygonXY( sipCpp->asMultiPolygon() ), qvector_type, Py_None );
}
% End
#endif

/**
* Returns contents of the geometry as a list of geometries
Expand Down
63 changes: 63 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -601,6 +601,69 @@ def testPointXY(self):
with self.assertRaises(ValueError):
QgsGeometry().asPolygon()

# as multipoint
self.assertEqual(QgsGeometry.fromWkt('MultiPoint(11 13,14 15)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
self.assertEqual(QgsGeometry.fromWkt('MultiPointZ(11 13 1,14 15 2)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
self.assertEqual(QgsGeometry.fromWkt('MultiPointM(11 13 1,14 15 2)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
self.assertEqual(QgsGeometry.fromWkt('MultiPointZM(11 13 1 2,14 15 3 4)').asMultiPoint(), [QgsPointXY(11, 13), QgsPointXY(14, 15)])
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('Point(11 13)').asMultiPoint()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('LineString(11 13,14 15)').asMultiPoint()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('MultiLineString((11 13, 14 15),(1 2, 3 4))').asMultiPoint()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('Polygon((11 13,14 15, 14 13, 11 13))').asMultiPoint()
with self.assertRaises(ValueError):
QgsGeometry().asMultiPoint()

# as multilinestring
self.assertEqual(QgsGeometry.fromWkt('MultiLineString((11 13,14 15, 11 15, 11 13))').asMultiPolyline(),
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
self.assertEqual(QgsGeometry.fromWkt('MultiLineStringZ((11 13 1,14 15 2, 11 15 3, 11 13 1))').asMultiPolyline(),
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
self.assertEqual(QgsGeometry.fromWkt('MultiLineStringM((11 13 1,14 15 2, 11 15 3, 11 13 1))').asMultiPolyline(),
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
self.assertEqual(QgsGeometry.fromWkt('MultiLineStringZM((11 13 1 11,14 15 2 12 , 11 15 3 13 , 11 13 1 11))').asMultiPolyline(),
[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]])
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('Point(11 13)').asMultiPolyline()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('MultiPoint(11 13,14 15)').asMultiPolyline()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('Polygon((11 13, 14 15, 17 18, 11 13))').asMultiPolyline()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('LineString(11 13,14 15)').asPolygon()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('MultiPolygon(((11 13,14 15, 11 15, 11 13)))').asMultiPolyline()
with self.assertRaises(ValueError):
QgsGeometry().asPolygon()

# as multipolygon
self.assertEqual(QgsGeometry.fromWkt('MultiPolygon(((11 13,14 15, 11 15, 11 13)))').asMultiPolygon(),
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
self.assertEqual(
QgsGeometry.fromWkt('MultiPolygonZ(((11 13 1,14 15 2, 11 15 3 , 11 13 1)))').asMultiPolygon(),
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
self.assertEqual(
QgsGeometry.fromWkt('MultiPolygonM(((11 13 1,14 15 2, 11 15 3 , 11 13 1)))').asMultiPolygon(),
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
self.assertEqual(QgsGeometry.fromWkt(
'MultiPolygonZM(((11 13 1 11,14 15 2 12, 11 15 3 13, 11 13 1 11)))').asMultiPolygon(),
[[[QgsPointXY(11, 13), QgsPointXY(14, 15), QgsPointXY(11, 15), QgsPointXY(11, 13)]]])
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('Point(11 13)').asMultiPolygon()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('MultiPoint(11 13,14 15)').asMultiPolygon()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('Polygon((11 13, 14 15, 17 18, 11 13))').asMultiPolygon()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('LineString(11 13,14 15)').asPolygon()
with self.assertRaises(TypeError):
QgsGeometry.fromWkt('MultiLineString((11 13,14 15, 11 15, 11 13))').asMultiPolygon()
with self.assertRaises(ValueError):
QgsGeometry().asPolygon()

def testReferenceGeometry(self):
""" Test parsing a whole range of valid reference wkt formats and variants, and checking
expected values such as length, area, centroids, bounding boxes, etc of the resultant geometry.
Expand Down

0 comments on commit 938adb8

Please sign in to comment.