Skip to content

Commit 2edb434

Browse files
committedJan 6, 2019
[API] Backport 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. (cherry picked from commit 4bba8ae)
1 parent 3eea922 commit 2edb434

File tree

3 files changed

+207
-2
lines changed

3 files changed

+207
-2
lines changed
 

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,26 @@ Inserts a geometry before a specified index and takes ownership. Returns true in
109109
:param index: position to insert geometry before
110110
%End
111111

112+
112113
virtual bool removeGeometry( int nr );
113114
%Docstring
114-
Removes a geometry from the collection.
115+
Removes a geometry from the collection by index.
115116

116-
:param nr: index of geometry to remove
117+
An IndexError will be raised if no geometry with the specified index exists.
117118

118119
:return: true if removal was successful.
120+
%End
121+
%MethodCode
122+
const int count = sipCpp->numGeometries();
123+
if ( a0 < 0 || a0 >= count )
124+
{
125+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
126+
sipIsErr = 1;
127+
}
128+
else
129+
{
130+
sipCpp->removeGeometry( a0 );
131+
}
119132
%End
120133

121134
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
207220

208221

209222

223+
224+
225+
SIP_PYOBJECT __getitem__( int index );
226+
%Docstring
227+
Returns the geometry at the specified ``index``. An IndexError will be raised if no geometry with the specified ``index`` exists.
228+
229+
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
230+
corresponds to the last geometry in the collection.
231+
232+
.. versionadded:: 3.6
233+
%End
234+
%MethodCode
235+
const int count = sipCpp->numGeometries();
236+
if ( a0 < -count || a0 >= count )
237+
{
238+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
239+
sipIsErr = 1;
240+
}
241+
else if ( a0 >= 0 )
242+
{
243+
return sipConvertFromType( sipCpp->geometryN( a0 ), sipType_QgsAbstractGeometry, NULL );
244+
}
245+
else
246+
{
247+
return sipConvertFromType( sipCpp->geometryN( count + a0 ), sipType_QgsAbstractGeometry, NULL );
248+
}
249+
%End
250+
251+
void __delitem__( int index );
252+
%Docstring
253+
Deletes the geometry at the specified ``index``. A geometry at the ``index`` must already exist or an IndexError will be raised.
254+
255+
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
256+
corresponds to the last geometry in the collection.
257+
258+
.. versionadded:: 3.6
259+
%End
260+
%MethodCode
261+
const int count = sipCpp->numGeometries();
262+
if ( a0 >= 0 && a0 < count )
263+
sipCpp->removeGeometry( a0 );
264+
else if ( a0 < 0 && a0 >= -count )
265+
sipCpp->removeGeometry( count + a0 );
266+
else
267+
{
268+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
269+
sipIsErr = 1;
270+
}
271+
%End
272+
210273
virtual QgsGeometryCollection *createEmptyWithSameType() const /Factory/;
211274

212275

‎src/core/geometry/qgsgeometrycollection.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,37 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
124124
*/
125125
virtual bool insertGeometry( QgsAbstractGeometry *g SIP_TRANSFER, int index );
126126

127+
#ifndef SIP_RUN
128+
127129
/**
128130
* Removes a geometry from the collection.
129131
* \param nr index of geometry to remove
130132
* \returns true if removal was successful.
131133
*/
132134
virtual bool removeGeometry( int nr );
135+
#else
136+
137+
/**
138+
* Removes a geometry from the collection by index.
139+
*
140+
* An IndexError will be raised if no geometry with the specified index exists.
141+
*
142+
* \returns true if removal was successful.
143+
*/
144+
virtual bool removeGeometry( int nr );
145+
% MethodCode
146+
const int count = sipCpp->numGeometries();
147+
if ( a0 < 0 || a0 >= count )
148+
{
149+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
150+
sipIsErr = 1;
151+
}
152+
else
153+
{
154+
sipCpp->removeGeometry( a0 );
155+
}
156+
% End
157+
#endif
133158

134159
void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, bool transformZ = false ) override SIP_THROW( QgsCsException );
135160
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
202227
}
203228
#endif
204229

230+
231+
#ifdef SIP_RUN
232+
233+
/**
234+
* Returns the geometry at the specified ``index``. An IndexError will be raised if no geometry with the specified ``index`` exists.
235+
*
236+
* 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
237+
* corresponds to the last geometry in the collection.
238+
*
239+
* \since QGIS 3.6
240+
*/
241+
SIP_PYOBJECT __getitem__( int index );
242+
% MethodCode
243+
const int count = sipCpp->numGeometries();
244+
if ( a0 < -count || a0 >= count )
245+
{
246+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
247+
sipIsErr = 1;
248+
}
249+
else if ( a0 >= 0 )
250+
{
251+
return sipConvertFromType( sipCpp->geometryN( a0 ), sipType_QgsAbstractGeometry, NULL );
252+
}
253+
else
254+
{
255+
return sipConvertFromType( sipCpp->geometryN( count + a0 ), sipType_QgsAbstractGeometry, NULL );
256+
}
257+
% End
258+
259+
/**
260+
* Deletes the geometry at the specified ``index``. A geometry at the ``index`` must already exist or an IndexError will be raised.
261+
*
262+
* 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
263+
* corresponds to the last geometry in the collection.
264+
*
265+
* \since QGIS 3.6
266+
*/
267+
void __delitem__( int index );
268+
% MethodCode
269+
const int count = sipCpp->numGeometries();
270+
if ( a0 >= 0 && a0 < count )
271+
sipCpp->removeGeometry( a0 );
272+
else if ( a0 < 0 && a0 >= -count )
273+
sipCpp->removeGeometry( count + a0 );
274+
else
275+
{
276+
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
277+
sipIsErr = 1;
278+
}
279+
% End
280+
#endif
281+
205282
QgsGeometryCollection *createEmptyWithSameType() const override SIP_FACTORY;
206283

207284
protected:

‎tests/src/python/test_qgsgeometry.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,71 @@ def testLineStringPythonAdditions(self):
488488
with self.assertRaises(IndexError):
489489
del ls[-3]
490490

491+
def testGeometryCollectionPythonAdditions(self):
492+
"""
493+
Tests Python specific additions to the QgsGeometryCollection API
494+
"""
495+
g = QgsGeometryCollection()
496+
self.assertTrue(bool(g))
497+
self.assertEqual(len(g), 0)
498+
g = QgsGeometryCollection()
499+
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12))')
500+
self.assertTrue(bool(g))
501+
self.assertEqual(len(g), 2)
502+
503+
# pointN
504+
with self.assertRaises(IndexError):
505+
g.geometryN(-1)
506+
with self.assertRaises(IndexError):
507+
g.geometryN(2)
508+
self.assertEqual(g.geometryN(0), QgsPoint(1, 2))
509+
self.assertEqual(g.geometryN(1), QgsPoint(11, 12))
510+
511+
# removeGeometry
512+
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), Point(33 34))')
513+
with self.assertRaises(IndexError):
514+
g.removeGeometry(-1)
515+
with self.assertRaises(IndexError):
516+
g.removeGeometry(3)
517+
g.removeGeometry(1)
518+
self.assertEqual(len(g), 2)
519+
self.assertEqual(g.geometryN(0), QgsPoint(1, 2))
520+
self.assertEqual(g.geometryN(1), QgsPoint(33, 34))
521+
with self.assertRaises(IndexError):
522+
g.removeGeometry(2)
523+
524+
g.fromWkt('GeometryCollection( Point(25 16 37 58), Point(26 22 47 68))')
525+
# get item
526+
with self.assertRaises(IndexError):
527+
g[-3]
528+
with self.assertRaises(IndexError):
529+
g[2]
530+
self.assertEqual(g[0], QgsPoint(25, 16, 37, 58))
531+
self.assertEqual(g[1], QgsPoint(26, 22, 47, 68))
532+
self.assertEqual(g[-2], QgsPoint(25, 16, 37, 58))
533+
self.assertEqual(g[-1], QgsPoint(26, 22, 47, 68))
534+
535+
# del item
536+
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), Point(33 34))')
537+
with self.assertRaises(IndexError):
538+
del g[-4]
539+
with self.assertRaises(IndexError):
540+
del g[3]
541+
del g[1]
542+
self.assertEqual(len(g), 2)
543+
self.assertEqual(g[0], QgsPoint(1, 2))
544+
self.assertEqual(g[1], QgsPoint(33, 34))
545+
with self.assertRaises(IndexError):
546+
del g[2]
547+
548+
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), Point(33 34))')
549+
del g[-3]
550+
self.assertEqual(len(g), 2)
551+
self.assertEqual(g[0], QgsPoint(11, 12))
552+
self.assertEqual(g[1], QgsPoint(33, 34))
553+
with self.assertRaises(IndexError):
554+
del g[-3]
555+
491556
def testReferenceGeometry(self):
492557
""" Test parsing a whole range of valid reference wkt formats and variants, and checking
493558
expected values such as length, area, centroids, bounding boxes, etc of the resultant geometry.

0 commit comments

Comments
 (0)
Please sign in to comment.