Skip to content

Commit

Permalink
[API] Throw IndexError on some QgsCurvePolygon methods when invalid
Browse files Browse the repository at this point in the history
interior ring index is requested

(cherry picked from commit 44fbb89)
  • Loading branch information
nyalldawson committed Jan 6, 2019
1 parent dc4e879 commit a180edc
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 10 deletions.
55 changes: 50 additions & 5 deletions python/core/auto_generated/geometry/qgscurvepolygon.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,43 @@ Curve polygon geometry type
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );



int numInteriorRings() const;
%Docstring
Returns the number of interior rings contained with the curve polygon.

.. seealso:: :py:func:`interiorRing`
%End

const QgsCurve *exteriorRing() const;
%Docstring
Returns the curve polygon's exterior ring.

.. seealso:: :py:func:`interiorRing`
%End


SIP_PYOBJECT interiorRing( int i ) /TypeHint="QgsCurve"/;
%Docstring
Retrieves an interior ring from the curve polygon. The first interior ring has index 0.

An IndexError will be raised if no interior ring with the specified index exists.

const QgsCurve *interiorRing( int i ) const;
.. seealso:: :py:func:`numInteriorRings`

.. seealso:: :py:func:`exteriorRing`
%End
%MethodCode
if ( a0 < 0 || a0 >= sipCpp->numInteriorRings() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return sipConvertFromType( const_cast< QgsCurve * >( sipCpp->interiorRing( a0 ) ), sipType_QgsCurve, NULL );
}
%End

virtual QgsPolygon *toPolygon( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
%Docstring
Expand Down Expand Up @@ -107,13 +139,27 @@ Sets all interior rings (takes ownership)
Adds an interior ring to the geometry (takes ownership)
%End

bool removeInteriorRing( int ringIndex );

bool removeInteriorRing( int i );
%Docstring
Removes an interior ring from the polygon. The first interior ring has index 0.
The corresponding ring is removed from the polygon and deleted. If a ring was successfully removed
the function will return true. It is not possible to remove the exterior ring using this method.
The corresponding ring is removed from the polygon and deleted.
It is not possible to remove the exterior ring using this method.

An IndexError will be raised if no interior ring with the specified index exists.

.. seealso:: :py:func:`removeInteriorRings`
%End
%MethodCode
if ( a0 < 0 || a0 >= sipCpp->numInteriorRings() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return PyBool_FromLong( sipCpp->removeInteriorRing( a0 ) );
}
%End

void removeInteriorRings( double minimumAllowedArea = -1 );
Expand All @@ -136,7 +182,6 @@ For example, this removes unclosed rings and rings with less than 4 vertices.
.. versionadded:: 3.0
%End


void forceRHR();
%Docstring
Forces the geometry to respect the Right-Hand-Rule, in which the area that is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ Returns the number of geometries within the collection.




SIP_PYOBJECT geometryN( int n ) /TypeHint="QgsAbstractGeometry"/;
%Docstring
Returns a geometry from within the collection.

:param n: index of geometry to return
:param n: index of geometry to return. An IndexError will be raised if no geometry with the specified index exists.
%End
%MethodCode
if ( a0 < 0 || a0 >= sipCpp->numGeometries() )
Expand Down Expand Up @@ -127,7 +128,7 @@ An IndexError will be raised if no geometry with the specified index exists.
}
else
{
sipCpp->removeGeometry( a0 );
return PyBool_FromLong( sipCpp->removeGeometry( a0 ) );
}
%End

Expand Down
69 changes: 68 additions & 1 deletion src/core/geometry/qgscurvepolygon.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,35 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits<double>::epsilon(), bool useZValues = false ) override;

//curve polygon interface

/**
* Returns the number of interior rings contained with the curve polygon.
*
* \see interiorRing()
*/
int numInteriorRings() const
{
return mInteriorRings.size();
}

/**
* Returns the curve polygon's exterior ring.
*
* \see interiorRing()
*/
const QgsCurve *exteriorRing() const
{
return mExteriorRing.get();
}

#ifndef SIP_RUN

/**
* Retrieves an interior ring from the curve polygon. The first interior ring has index 0.
*
* \see numInteriorRings()
* \see exteriorRing()
*/
const QgsCurve *interiorRing( int i ) const
{
if ( i < 0 || i >= mInteriorRings.size() )
Expand All @@ -84,6 +103,29 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
}
return mInteriorRings.at( i );
}
#else

/**
* Retrieves an interior ring from the curve polygon. The first interior ring has index 0.
*
* An IndexError will be raised if no interior ring with the specified index exists.
*
* \see numInteriorRings()
* \see exteriorRing()
*/
SIP_PYOBJECT interiorRing( int i ) SIP_TYPEHINT( QgsCurve );
% MethodCode
if ( a0 < 0 || a0 >= sipCpp->numInteriorRings() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return sipConvertFromType( const_cast< QgsCurve * >( sipCpp->interiorRing( a0 ) ), sipType_QgsCurve, NULL );
}
% End
#endif

/**
* Returns a new polygon geometry corresponding to a segmentized approximation
Expand All @@ -107,13 +149,39 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
//! Adds an interior ring to the geometry (takes ownership)
virtual void addInteriorRing( QgsCurve *ring SIP_TRANSFER );

#ifndef SIP_RUN

/**
* Removes an interior ring from the polygon. The first interior ring has index 0.
* The corresponding ring is removed from the polygon and deleted. If a ring was successfully removed
* the function will return true. It is not possible to remove the exterior ring using this method.
* \see removeInteriorRings()
*/
bool removeInteriorRing( int ringIndex );
#else

/**
* Removes an interior ring from the polygon. The first interior ring has index 0.
* The corresponding ring is removed from the polygon and deleted.
* It is not possible to remove the exterior ring using this method.
*
* An IndexError will be raised if no interior ring with the specified index exists.
*
* \see removeInteriorRings()
*/
bool removeInteriorRing( int i );
% MethodCode
if ( a0 < 0 || a0 >= sipCpp->numInteriorRings() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return PyBool_FromLong( sipCpp->removeInteriorRing( a0 ) );
}
% End
#endif

/**
* Removes the interior rings from the polygon. If the minimumAllowedArea
Expand All @@ -133,7 +201,6 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
*/
void removeInvalidRings();


/**
* Forces the geometry to respect the Right-Hand-Rule, in which the area that is
* bounded by the polygon is to the right of the boundary. In particular, the exterior
Expand Down
10 changes: 8 additions & 2 deletions src/core/geometry/qgsgeometrycollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,19 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
return mGeometries.value( n );
}

#ifndef SIP_RUN

/**
* Returns a geometry from within the collection.
* \param n index of geometry to return
*/
#ifndef SIP_RUN
QgsAbstractGeometry *geometryN( int n );
#else

/**
* Returns a geometry from within the collection.
* \param n index of geometry to return. An IndexError will be raised if no geometry with the specified index exists.
*/
SIP_PYOBJECT geometryN( int n ) SIP_TYPEHINT( QgsAbstractGeometry );
% MethodCode
if ( a0 < 0 || a0 >= sipCpp->numGeometries() )
Expand Down Expand Up @@ -151,7 +157,7 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
}
else
{
sipCpp->removeGeometry( a0 );
return PyBool_FromLong( sipCpp->removeGeometry( a0 ) );
}
% End
#endif
Expand Down
42 changes: 42 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,48 @@ def testGeometryCollectionPythonAdditions(self):
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), LineString(33 34, 44 45))')
self.assertEqual([p.asWkt() for p in g], ['Point (1 2)', 'Point (11 12)', 'LineString (33 34, 44 45)'])

def testCurvePolygonPythonAdditions(self):
"""
Tests Python specific additions to the QgsCurvePolygon API
"""
# interiorRing
g = QgsPolygon()
with self.assertRaises(IndexError):
g.interiorRing(-1)
with self.assertRaises(IndexError):
g.interiorRing(0)

g.fromWkt('Polygon((0 0, 1 0, 1 1, 0 0),(0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1),(0.8 0.8, 0.9 0.8, 0.9 0.9, 0.8 0.8))')
with self.assertRaises(IndexError):
g.interiorRing(-1)
with self.assertRaises(IndexError):
g.interiorRing(2)
self.assertEqual(g.interiorRing(0).asWkt(1), 'LineString (0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1)')
self.assertEqual(g.interiorRing(1).asWkt(1), 'LineString (0.8 0.8, 0.9 0.8, 0.9 0.9, 0.8 0.8)')

# removeInteriorRing
g = QgsPolygon()
with self.assertRaises(IndexError):
g.removeInteriorRing(-1)
with self.assertRaises(IndexError):
g.removeInteriorRing(0)

g.fromWkt(
'Polygon((0 0, 1 0, 1 1, 0 0),(0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1),(0.8 0.8, 0.9 0.8, 0.9 0.9, 0.8 0.8))')
with self.assertRaises(IndexError):
g.removeInteriorRing(-1)
with self.assertRaises(IndexError):
g.removeInteriorRing(2)

g.removeInteriorRing(1)
self.assertEqual(g.asWkt(1), 'Polygon ((0 0, 1 0, 1 1, 0 0),(0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1))')
with self.assertRaises(IndexError):
g.removeInteriorRing(1)
g.removeInteriorRing(0)
self.assertEqual(g.asWkt(1), 'Polygon ((0 0, 1 0, 1 1, 0 0))')
with self.assertRaises(IndexError):
g.removeInteriorRing(0)

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 a180edc

Please sign in to comment.