Skip to content

Commit

Permalink
Fix crashes on newer GEOS with empty polygon rings
Browse files Browse the repository at this point in the history
There was a previous fix for this protecting some geos calls, but
on newer GEOS versions we get crashes with other methods (eg
calculating centroid) when using geos geometries with empty
interior rings.

Avoid this by ALWAYS defaulting to skipping empty rings when
creating GEOS polygons, UNLESS explicitly asked to. Then, only
explicitly ask to do this when we are using GEOS to validate
a geometry. In all other cases we don't need or want empty rings.
  • Loading branch information
nyalldawson committed Oct 25, 2024
1 parent b56115d commit e87a1e7
Show file tree
Hide file tree
Showing 12 changed files with 21 additions and 15 deletions.
4 changes: 3 additions & 1 deletion python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -2957,7 +2957,7 @@ geometry will retain the same dimensionality as the input geometry.
:param maxAngle: maximum angle at node (0-180) at which smoothing will be applied
%End

static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0 ) /Factory/;
static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) /Factory/;
%Docstring
Creates and returns a new geometry engine representing the specified ``geometry`` using ``precision`` on a grid. The ``precision`` argument was added in 3.36.

Expand All @@ -2972,6 +2972,8 @@ Many methods available in the :py:class:`QgsGeometryEngine` class can benefit fr
a large number of spatial relationships will be tested (such as calling :py:func:`~QgsGeometry.intersects`, :py:func:`~QgsGeometry.within`, etc) then the
geometry should first be prepared by calling :py:func:`~QgsGeometry.prepareGeometry` before performing the tests.

The ``flags`` argument was added in QGIS 3.40 to allow control over the resultant GEOS geometry.

Example
-------------------------------------

Expand Down
4 changes: 3 additions & 1 deletion python/core/auto_generated/geometry/qgsgeometry.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -2957,7 +2957,7 @@ geometry will retain the same dimensionality as the input geometry.
:param maxAngle: maximum angle at node (0-180) at which smoothing will be applied
%End

static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0 ) /Factory/;
static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) /Factory/;
%Docstring
Creates and returns a new geometry engine representing the specified ``geometry`` using ``precision`` on a grid. The ``precision`` argument was added in 3.36.

Expand All @@ -2972,6 +2972,8 @@ Many methods available in the :py:class:`QgsGeometryEngine` class can benefit fr
a large number of spatial relationships will be tested (such as calling :py:func:`~QgsGeometry.intersects`, :py:func:`~QgsGeometry.within`, etc) then the
geometry should first be prepared by calling :py:func:`~QgsGeometry.prepareGeometry` before performing the tests.

The ``flags`` argument was added in QGIS 3.40 to allow control over the resultant GEOS geometry.

Example
-------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,9 @@ QgsGeometryCheckerUtils::LayerFeatures::iterator QgsGeometryCheckerUtils::LayerF

/////////////////////////////////////////////////////////////////////////////

std::unique_ptr<QgsGeometryEngine> QgsGeometryCheckerUtils::createGeomEngine( const QgsAbstractGeometry *geometry, double tolerance )
std::unique_ptr<QgsGeometryEngine> QgsGeometryCheckerUtils::createGeomEngine( const QgsAbstractGeometry *geometry, double tolerance, Qgis::GeosCreationFlags flags )
{
return std::make_unique<QgsGeos>( geometry, tolerance );
return std::make_unique<QgsGeos>( geometry, tolerance, flags );
}

QgsAbstractGeometry *QgsGeometryCheckerUtils::getGeomPart( QgsAbstractGeometry *geom, int partIdx )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils

#ifndef SIP_RUN

static std::unique_ptr<QgsGeometryEngine> createGeomEngine( const QgsAbstractGeometry *geometry, double tolerance );
static std::unique_ptr<QgsGeometryEngine> createGeomEngine( const QgsAbstractGeometry *geometry, double tolerance, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings );

static QgsAbstractGeometry *getGeomPart( QgsAbstractGeometry *geom, int partIdx );
static const QgsAbstractGeometry *getGeomPart( const QgsAbstractGeometry *geom, int partIdx );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void QgsGeometryDuplicateCheck::collectErrors( const QMap<QString, QgsFeaturePoo

const QgsGeometry geomA = layerFeatureA.geometry();
const QgsRectangle bboxA = geomA.boundingBox();
std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( geomA.constGet(), mContext->tolerance );
std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( geomA.constGet(), mContext->tolerance, Qgis::GeosCreationFlags() );
if ( !geomEngineA->isValid() )
{
messages.append( tr( "Duplicate check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) );
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgscurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ bool QgsCurve::isValid( QString &error, Qgis::GeometryValidityFlags flags ) cons
return error.isEmpty();
}

const QgsGeos geos( this );
const QgsGeos geos( this, 0, Qgis::GeosCreationFlags() );
const bool res = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
if ( flags == 0 )
{
Expand Down
6 changes: 3 additions & 3 deletions src/core/geometry/qgsgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3483,7 +3483,7 @@ void QgsGeometry::validateGeometry( QVector<QgsGeometry::Error> &errors, const Q

case Qgis::GeometryValidationEngine::Geos:
{
QgsGeos geos( d->geometry.get() );
QgsGeos geos( d->geometry.get(), 0, Qgis::GeosCreationFlags() );
QString error;
QgsGeometry errorLoc;
if ( !geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, &errorLoc ) )
Expand Down Expand Up @@ -4467,9 +4467,9 @@ QgsGeometry QgsGeometry::convertToPolygon( bool destMultipart ) const
}
}

QgsGeometryEngine *QgsGeometry::createGeometryEngine( const QgsAbstractGeometry *geometry, double precision )
QgsGeometryEngine *QgsGeometry::createGeometryEngine( const QgsAbstractGeometry *geometry, double precision, Qgis::GeosCreationFlags flags )
{
return new QgsGeos( geometry, precision );
return new QgsGeos( geometry, precision, flags );
}

QDataStream &operator<<( QDataStream &out, const QgsGeometry &geometry )
Expand Down
4 changes: 3 additions & 1 deletion src/core/geometry/qgsgeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -3137,6 +3137,8 @@ class CORE_EXPORT QgsGeometry
* a large number of spatial relationships will be tested (such as calling intersects(), within(), etc) then the
* geometry should first be prepared by calling prepareGeometry() before performing the tests.
*
* The \a flags argument was added in QGIS 3.40 to allow control over the resultant GEOS geometry.
*
* ### Example
*
* \code{.py}
Expand All @@ -3160,7 +3162,7 @@ class CORE_EXPORT QgsGeometry
*
* QgsGeometryEngine operations are backed by the GEOS library (https://trac.osgeo.org/geos/).
*/
static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0 ) SIP_FACTORY;
static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) SIP_FACTORY;

/**
* Upgrades a point list from QgsPointXY to QgsPoint
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgsgeos.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
* \param flags GEOS creation flags (since QGIS 3.40)
* \note The third parameter was prior to QGIS 3.40 a boolean which has been incorporated into the flag
*/
QgsGeos( const QgsAbstractGeometry *geometry, double precision = 0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() );
QgsGeos( const QgsAbstractGeometry *geometry, double precision = 0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings );

/**
* Creates a new QgsGeometry object, feeding in a geometry in GEOS format.
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgspolyhedralsurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,7 @@ bool QgsPolyhedralSurface::isValid( QString &error, Qgis::GeometryValidityFlags
// GEOS does not handle PolyhedralSurface, check the polygons one by one
for ( int i = 0; i < mPatches.size(); ++i )
{
const QgsGeos geos( mPatches.at( i ) );
const QgsGeos geos( mPatches.at( i ), 0, Qgis::GeosCreationFlags() );
const bool valid = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
if ( !valid )
{
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgssurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ bool QgsSurface::isValid( QString &error, Qgis::GeometryValidityFlags flags ) co
return error.isEmpty();
}

const QgsGeos geos( this );
const QgsGeos geos( this, 0, Qgis::GeosCreationFlags() );
const bool res = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
if ( flags == 0 )
{
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsgeometryvalidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ void QgsGeometryValidator::run()
return;
}

const QgsGeos geos( mGeometry.constGet() );
const QgsGeos geos( mGeometry.constGet(), 0, Qgis::GeosCreationFlags() );
QString error;
QgsGeometry errorLoc;
if ( !geos.isValid( &error, true, &errorLoc ) )
Expand Down

0 comments on commit e87a1e7

Please sign in to comment.