[API] Throw IndexError on some QgsCurvePolygon methods when invalid

interior ring index is requested
This commit is contained in:
Nyall Dawson 2018-12-10 12:02:02 +10:00
parent a49bf9f68f
commit 44fbb89450
5 changed files with 171 additions and 10 deletions

View File

@ -70,11 +70,43 @@ Curve polygon geometry type
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ); virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
int numInteriorRings() const; int numInteriorRings() const;
%Docstring
Returns the number of interior rings contained with the curve polygon.
.. seealso:: :py:func:`interiorRing`
%End
const QgsCurve *exteriorRing() const; const QgsCurve *exteriorRing() const;
%Docstring
Returns the curve polygon's exterior ring.
const QgsCurve *interiorRing( int i ) const; .. 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.
.. 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/; virtual QgsPolygon *toPolygon( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
%Docstring %Docstring
@ -107,13 +139,27 @@ Sets all interior rings (takes ownership)
Adds an interior ring to the geometry (takes ownership) Adds an interior ring to the geometry (takes ownership)
%End %End
bool removeInteriorRing( int ringIndex );
bool removeInteriorRing( int i );
%Docstring %Docstring
Removes an interior ring from the polygon. The first interior ring has index 0. 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 corresponding ring is removed from the polygon and deleted.
the function will return true. It is not possible to remove the exterior ring using this method. 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` .. 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 %End
void removeInteriorRings( double minimumAllowedArea = -1 ); void removeInteriorRings( double minimumAllowedArea = -1 );
@ -136,7 +182,6 @@ For example, this removes unclosed rings and rings with less than 4 vertices.
.. versionadded:: 3.0 .. versionadded:: 3.0
%End %End
void forceRHR(); void forceRHR();
%Docstring %Docstring
Forces the geometry to respect the Right-Hand-Rule, in which the area that is Forces the geometry to respect the Right-Hand-Rule, in which the area that is

View File

@ -58,11 +58,12 @@ Returns the number of geometries within the collection.
SIP_PYOBJECT geometryN( int n ) /TypeHint="QgsAbstractGeometry"/; SIP_PYOBJECT geometryN( int n ) /TypeHint="QgsAbstractGeometry"/;
%Docstring %Docstring
Returns a geometry from within the collection. 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 %End
%MethodCode %MethodCode
if ( a0 < 0 || a0 >= sipCpp->numGeometries() ) if ( a0 < 0 || a0 >= sipCpp->numGeometries() )
@ -127,7 +128,7 @@ An IndexError will be raised if no geometry with the specified index exists.
} }
else else
{ {
sipCpp->removeGeometry( a0 ); return PyBool_FromLong( sipCpp->removeGeometry( a0 ) );
} }
%End %End

View File

@ -66,16 +66,35 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits<double>::epsilon(), bool useZValues = false ) override; bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits<double>::epsilon(), bool useZValues = false ) override;
//curve polygon interface //curve polygon interface
/**
* Returns the number of interior rings contained with the curve polygon.
*
* \see interiorRing()
*/
int numInteriorRings() const int numInteriorRings() const
{ {
return mInteriorRings.size(); return mInteriorRings.size();
} }
/**
* Returns the curve polygon's exterior ring.
*
* \see interiorRing()
*/
const QgsCurve *exteriorRing() const const QgsCurve *exteriorRing() const
{ {
return mExteriorRing.get(); 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 const QgsCurve *interiorRing( int i ) const
{ {
if ( i < 0 || i >= mInteriorRings.size() ) if ( i < 0 || i >= mInteriorRings.size() )
@ -84,6 +103,29 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
} }
return mInteriorRings.at( i ); 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 * Returns a new polygon geometry corresponding to a segmentized approximation
@ -107,6 +149,8 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
//! Adds an interior ring to the geometry (takes ownership) //! Adds an interior ring to the geometry (takes ownership)
virtual void addInteriorRing( QgsCurve *ring SIP_TRANSFER ); virtual void addInteriorRing( QgsCurve *ring SIP_TRANSFER );
#ifndef SIP_RUN
/** /**
* Removes an interior ring from the polygon. The first interior ring has index 0. * 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 corresponding ring is removed from the polygon and deleted. If a ring was successfully removed
@ -114,6 +158,30 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
* \see removeInteriorRings() * \see removeInteriorRings()
*/ */
bool removeInteriorRing( int ringIndex ); 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 * Removes the interior rings from the polygon. If the minimumAllowedArea
@ -133,7 +201,6 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
*/ */
void removeInvalidRings(); void removeInvalidRings();
/** /**
* Forces the geometry to respect the Right-Hand-Rule, in which the area that is * 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 * bounded by the polygon is to the right of the boundary. In particular, the exterior

View File

@ -81,13 +81,19 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
return mGeometries.value( n ); return mGeometries.value( n );
} }
#ifndef SIP_RUN
/** /**
* Returns a geometry from within the collection. * Returns a geometry from within the collection.
* \param n index of geometry to return * \param n index of geometry to return
*/ */
#ifndef SIP_RUN
QgsAbstractGeometry *geometryN( int n ); QgsAbstractGeometry *geometryN( int n );
#else #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 ); SIP_PYOBJECT geometryN( int n ) SIP_TYPEHINT( QgsAbstractGeometry );
% MethodCode % MethodCode
if ( a0 < 0 || a0 >= sipCpp->numGeometries() ) if ( a0 < 0 || a0 >= sipCpp->numGeometries() )
@ -151,7 +157,7 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
} }
else else
{ {
sipCpp->removeGeometry( a0 ); return PyBool_FromLong( sipCpp->removeGeometry( a0 ) );
} }
% End % End
#endif #endif

View File

@ -504,6 +504,48 @@ class TestQgsGeometry(unittest.TestCase):
g.fromWkt('GeometryCollection( Point(1 2), Point(11 12), LineString(33 34, 44 45))') 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)']) 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): def testReferenceGeometry(self):
""" Test parsing a whole range of valid reference wkt formats and variants, and checking """ 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. expected values such as length, area, centroids, bounding boxes, etc of the resultant geometry.