[FEATURE][API] Add some nice PyQGIS API for working with geometry collections

- Calling removeGeometry with an invalid index will now raise an IndexError
- Calling collection[0] will return the first geometry in the collection,
collection[1] the second, etc. And negative indices return from the end
of the collection, so collection[-1] returns the last geometry in the collection.
- Geometries can be deleted by calling `del collection[1]` (deletes the
second geometry from the collection). Also supports negative indices
to count from the end of the collection.
This commit is contained in:
Nyall Dawson 2018-12-06 09:47:45 +10:00
parent 31b82de5e3
commit 4bba8ae64d
3 changed files with 207 additions and 2 deletions

View File

@ -109,13 +109,26 @@ Inserts a geometry before a specified index and takes ownership. Returns true in
:param index: position to insert geometry before
%End
virtual bool removeGeometry( int nr );
%Docstring
Removes a geometry from the collection.
Removes a geometry from the collection by index.
:param nr: index of geometry to remove
An IndexError will be raised if no geometry with the specified index exists.
:return: true if removal was successful.
%End
%MethodCode
const int count = sipCpp->numGeometries();
if ( a0 < 0 || a0 >= count )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
sipCpp->removeGeometry( a0 );
}
%End
virtual void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, bool transformZ = false ) throw( QgsCsException );
@ -207,6 +220,56 @@ Returns a geometry without curves. Caller takes ownership
SIP_PYOBJECT __getitem__( int index );
%Docstring
Returns the geometry at the specified ``index``. An IndexError will be raised if no geometry with the specified ``index`` exists.
Indexes can be less than 0, in which case they correspond to geometries from the end of the collect. E.g. an index of -1
corresponds to the last geometry in the collection.
.. versionadded:: 3.6
%End
%MethodCode
const int count = sipCpp->numGeometries();
if ( a0 < -count || a0 >= count )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else if ( a0 >= 0 )
{
return sipConvertFromType( sipCpp->geometryN( a0 ), sipType_QgsAbstractGeometry, NULL );
}
else
{
return sipConvertFromType( sipCpp->geometryN( count + a0 ), sipType_QgsAbstractGeometry, NULL );
}
%End
void __delitem__( int index );
%Docstring
Deletes the geometry at the specified ``index``. A geometry at the ``index`` must already exist or an IndexError will be raised.
Indexes can be less than 0, in which case they correspond to geometries from the end of the collection. E.g. an index of -1
corresponds to the last geometry in the collection.
.. versionadded:: 3.6
%End
%MethodCode
const int count = sipCpp->numGeometries();
if ( a0 >= 0 && a0 < count )
sipCpp->removeGeometry( a0 );
else if ( a0 < 0 && a0 >= -count )
sipCpp->removeGeometry( count + a0 );
else
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
%End
virtual QgsGeometryCollection *createEmptyWithSameType() const /Factory/;

View File

@ -124,12 +124,37 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
*/
virtual bool insertGeometry( QgsAbstractGeometry *g SIP_TRANSFER, int index );
#ifndef SIP_RUN
/**
* Removes a geometry from the collection.
* \param nr index of geometry to remove
* \returns true if removal was successful.
*/
virtual bool removeGeometry( int nr );
#else
/**
* Removes a geometry from the collection by index.
*
* An IndexError will be raised if no geometry with the specified index exists.
*
* \returns true if removal was successful.
*/
virtual bool removeGeometry( int nr );
% MethodCode
const int count = sipCpp->numGeometries();
if ( a0 < 0 || a0 >= count )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
sipCpp->removeGeometry( a0 );
}
% End
#endif
void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, bool transformZ = false ) override SIP_THROW( QgsCsException );
void transform( const QTransform &t, double zTranslate = 0.0, double zScale = 1.0, double mTranslate = 0.0, double mScale = 1.0 ) override;
@ -202,6 +227,58 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
}
#endif
#ifdef SIP_RUN
/**
* Returns the geometry at the specified ``index``. An IndexError will be raised if no geometry with the specified ``index`` exists.
*
* Indexes can be less than 0, in which case they correspond to geometries from the end of the collect. E.g. an index of -1
* corresponds to the last geometry in the collection.
*
* \since QGIS 3.6
*/
SIP_PYOBJECT __getitem__( int index );
% MethodCode
const int count = sipCpp->numGeometries();
if ( a0 < -count || a0 >= count )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else if ( a0 >= 0 )
{
return sipConvertFromType( sipCpp->geometryN( a0 ), sipType_QgsAbstractGeometry, NULL );
}
else
{
return sipConvertFromType( sipCpp->geometryN( count + a0 ), sipType_QgsAbstractGeometry, NULL );
}
% End
/**
* Deletes the geometry at the specified ``index``. A geometry at the ``index`` must already exist or an IndexError will be raised.
*
* Indexes can be less than 0, in which case they correspond to geometries from the end of the collection. E.g. an index of -1
* corresponds to the last geometry in the collection.
*
* \since QGIS 3.6
*/
void __delitem__( int index );
% MethodCode
const int count = sipCpp->numGeometries();
if ( a0 >= 0 && a0 < count )
sipCpp->removeGeometry( a0 );
else if ( a0 < 0 && a0 >= -count )
sipCpp->removeGeometry( count + a0 );
else
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
% End
#endif
QgsGeometryCollection *createEmptyWithSameType() const override SIP_FACTORY;
protected:

View File

@ -433,6 +433,71 @@ class TestQgsGeometry(unittest.TestCase):
with self.assertRaises(IndexError):
del ls[-3]
def testGeometryCollectionPythonAdditions(self):
"""
Tests Python specific additions to the QgsGeometryCollection API
"""
g = QgsGeometryCollection()
self.assertTrue(bool(g))
self.assertEqual(len(g), 0)
g = QgsGeometryCollection()
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12))')
self.assertTrue(bool(g))
self.assertEqual(len(g), 2)
# pointN
with self.assertRaises(IndexError):
g.geometryN(-1)
with self.assertRaises(IndexError):
g.geometryN(2)
self.assertEqual(g.geometryN(0), QgsPoint(1, 2))
self.assertEqual(g.geometryN(1), QgsPoint(11, 12))
# removeGeometry
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), Point(33 34))')
with self.assertRaises(IndexError):
g.removeGeometry(-1)
with self.assertRaises(IndexError):
g.removeGeometry(3)
g.removeGeometry(1)
self.assertEqual(len(g), 2)
self.assertEqual(g.geometryN(0), QgsPoint(1, 2))
self.assertEqual(g.geometryN(1), QgsPoint(33, 34))
with self.assertRaises(IndexError):
g.removeGeometry(2)
g.fromWkt('GeometryCollection( Point(25 16 37 58), Point(26 22 47 68))')
# get item
with self.assertRaises(IndexError):
g[-3]
with self.assertRaises(IndexError):
g[2]
self.assertEqual(g[0], QgsPoint(25, 16, 37, 58))
self.assertEqual(g[1], QgsPoint(26, 22, 47, 68))
self.assertEqual(g[-2], QgsPoint(25, 16, 37, 58))
self.assertEqual(g[-1], QgsPoint(26, 22, 47, 68))
# del item
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), Point(33 34))')
with self.assertRaises(IndexError):
del g[-4]
with self.assertRaises(IndexError):
del g[3]
del g[1]
self.assertEqual(len(g), 2)
self.assertEqual(g[0], QgsPoint(1, 2))
self.assertEqual(g[1], QgsPoint(33, 34))
with self.assertRaises(IndexError):
del g[2]
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), Point(33 34))')
del g[-3]
self.assertEqual(len(g), 2)
self.assertEqual(g[0], QgsPoint(11, 12))
self.assertEqual(g[1], QgsPoint(33, 34))
with self.assertRaises(IndexError):
del g[-3]
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.