Skip to content

Commit

Permalink
Add coverage union algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Oct 17, 2023
1 parent 343ac33 commit ed0766e
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 8 deletions.
14 changes: 14 additions & 0 deletions python/core/auto_generated/geometry/qgsgeometry.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,20 @@ This method requires a QGIS build based on GEOS 3.12 or later.

.. seealso:: :py:func:`validateCoverage`

.. versionadded:: 3.36
%End

QgsGeometry unionCoverage() const;
%Docstring
Optimized union algorithm for polygonal inputs that are correctly noded and do not overlap.
It may generate an error (returning a null geometry) for inputs that do not satisfy this constraint,
however this is not guaranteed.

The input geometry is the polygonal coverage to union, stored in a geometry collection.
All members must be POLYGON or MULTIPOLYGON.

.. seealso:: :py:func:`validateCoverage`

.. versionadded:: 3.36
%End

Expand Down
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ set(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmconverttocurves.cpp
processing/qgsalgorithmconvexhull.cpp
processing/qgsalgorithmcoveragesimplify.cpp
processing/qgsalgorithmcoverageunion.cpp
processing/qgsalgorithmcoveragevalidate.cpp
processing/qgsalgorithmcreatedirectory.cpp
processing/qgsalgorithmdbscanclustering.cpp
Expand Down
6 changes: 3 additions & 3 deletions src/analysis/processing/qgsalgorithmcoveragesimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ QStringList QgsCoverageSimplifyAlgorithm::tags() const

QString QgsCoverageSimplifyAlgorithm::group() const
{
return QObject::tr( "Vector geometry" );
return QObject::tr( "Vector coverage" );
}

QString QgsCoverageSimplifyAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeometry" );
return QStringLiteral( "vectorcoverage" );
}

void QgsCoverageSimplifyAlgorithm::initAlgorithm( const QVariantMap & )
Expand Down Expand Up @@ -130,7 +130,7 @@ QVariantMap QgsCoverageSimplifyAlgorithm::processAlgorithm( const QVariantMap &p
std::unique_ptr< QgsAbstractGeometry > simplified;
try
{
simplified.reset( geos.simplifyCoverageVW( tolerance, preserveBoundary, &error ) );
simplified = geos.simplifyCoverageVW( tolerance, preserveBoundary, &error );
}
catch ( QgsNotSupportedException &e )
{
Expand Down
139 changes: 139 additions & 0 deletions src/analysis/processing/qgsalgorithmcoverageunion.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/***************************************************************************
qgsalgorithmcoverageunion.cpp
---------------------
begin : October 2023
copyright : (C) 2023 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/


#include "qgsalgorithmcoverageunion.h"
#include "qgsgeometrycollection.h"
#include "qgsgeos.h"


///@cond PRIVATE

QString QgsCoverageUnionAlgorithm::name() const
{
return QStringLiteral( "coverageunion" );
}

QString QgsCoverageUnionAlgorithm::displayName() const
{
return QObject::tr( "Dissolve coverage" );
}

QStringList QgsCoverageUnionAlgorithm::tags() const
{
return QObject::tr( "union,merge,topological,boundary" ).split( ',' );
}

QString QgsCoverageUnionAlgorithm::group() const
{
return QObject::tr( "Vector coverage" );
}

QString QgsCoverageUnionAlgorithm::groupId() const
{
return QStringLiteral( "vectorcoverage" );
}

void QgsCoverageUnionAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorPolygon ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Dissolved" ), QgsProcessing::TypeVectorPolygon ) );
}

QString QgsCoverageUnionAlgorithm::shortHelpString() const
{
return QObject::tr( "" );
}

QgsCoverageUnionAlgorithm *QgsCoverageUnionAlgorithm::createInstance() const
{
return new QgsCoverageUnionAlgorithm();
}

QVariantMap QgsCoverageUnionAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );

QString sinkId;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, sinkId, source->fields(), Qgis::WkbType::MultiPolygon, source->sourceCrs() ) );
if ( !sink )
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );

// we have to build up a list of geometries in advance
QgsGeometryCollection collection;

const long count = source->featureCount();
if ( count > 0 )
{
collection.reserve( count );
}

const double step = count > 0 ? 100.0 / count : 1;
int current = 0;

feedback->pushInfo( QObject::tr( "Collecting features" ) );

QgsFeature inFeature;
QgsFeatureRequest req;
req.setNoAttributes();
QgsFeatureIterator features = source->getFeatures( req );
while ( features.nextFeature( inFeature ) )
{
if ( feedback->isCanceled() )
{
break;
}

if ( inFeature.hasGeometry() )
{
collection.addGeometry( inFeature.geometry().constGet()->clone() );
}

feedback->setProgress( current * step * 0.2 );
current++;
}

feedback->pushInfo( QObject::tr( "Dissolving coverage" ) );

QgsGeos geos( &collection );
QString error;
std::unique_ptr< QgsAbstractGeometry > dissolved = geos.unionCoverage( &error );

if ( !dissolved )
{
if ( !error.isEmpty() )
throw QgsProcessingException( error );
else
throw QgsProcessingException( QObject::tr( "No geometry was returned for dissolved coverage" ) );
}

feedback->setProgress( 95 );

feedback->pushInfo( QObject::tr( "Storing output" ) );
QgsFeature outFeature( source->fields() );
outFeature.setGeometry( std::move( dissolved ) );
if ( !sink->addFeature( outFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );

QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), sinkId );
return outputs;
}

///@endcond
55 changes: 55 additions & 0 deletions src/analysis/processing/qgsalgorithmcoverageunion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/***************************************************************************
qgsalgorithmcoverageunion.h
---------------------
begin : October 2023
copyright : (C) 2023 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSALGORITHMCOVERAGEUNION_H
#define QGSALGORITHMCOVERAGEUNION_H

#define SIP_NO_FILE

#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"

///@cond PRIVATE

/**
* Native coverage union algorithm.
*/
class QgsCoverageUnionAlgorithm : public QgsProcessingAlgorithm
{
public:
QgsCoverageUnionAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QgsCoverageUnionAlgorithm *createInstance() const override SIP_FACTORY;

protected:

QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};


///@endcond PRIVATE

#endif // QGSALGORITHMCOVERAGEUNION_H


4 changes: 2 additions & 2 deletions src/analysis/processing/qgsalgorithmcoveragevalidate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ QStringList QgsCoverageValidateAlgorithm::tags() const

QString QgsCoverageValidateAlgorithm::group() const
{
return QObject::tr( "Vector geometry" );
return QObject::tr( "Vector coverage" );
}

QString QgsCoverageValidateAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeometry" );
return QStringLiteral( "vectorcoverage" );
}

void QgsCoverageValidateAlgorithm::initAlgorithm( const QVariantMap & )
Expand Down
2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "qgsalgorithmconverttocurves.h"
#include "qgsalgorithmconvexhull.h"
#include "qgsalgorithmcoveragesimplify.h"
#include "qgsalgorithmcoverageunion.h"
#include "qgsalgorithmcoveragevalidate.h"
#include "qgsalgorithmcreatedirectory.h"
#include "qgsalgorithmdbscanclustering.h"
Expand Down Expand Up @@ -316,6 +317,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsConvertToCurvesAlgorithm() );
addAlgorithm( new QgsConvexHullAlgorithm() );
addAlgorithm( new QgsCoverageSimplifyAlgorithm() );
addAlgorithm( new QgsCoverageUnionAlgorithm() );
addAlgorithm( new QgsCoverageValidateAlgorithm() );
addAlgorithm( new QgsCreateDirectoryAlgorithm() );
addAlgorithm( new QgsDbscanClusteringAlgorithm() );
Expand Down
14 changes: 14 additions & 0 deletions src/core/geometry/qgsgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2638,6 +2638,20 @@ QgsGeometry QgsGeometry::delaunayTriangulation( double tolerance, bool edgesOnly
return result;
}

QgsGeometry QgsGeometry::unionCoverage() const
{
if ( !d->geometry )
{
return QgsGeometry();
}

QgsGeos geos( d->geometry.get() );
mLastError.clear();
const QgsGeometry result = QgsGeometry( geos.unionCoverage( &mLastError ) );
result.mLastError = mLastError;
return result;
}

Qgis::CoverageValidityResult QgsGeometry::validateCoverage( double gapWidth, QgsGeometry *invalidEdges ) const
{
if ( !d->geometry )
Expand Down
13 changes: 13 additions & 0 deletions src/core/geometry/qgsgeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1874,6 +1874,19 @@ class CORE_EXPORT QgsGeometry
*/
QgsGeometry simplifyCoverageVW( double tolerance, bool preserveBoundary ) const SIP_THROW( QgsNotSupportedException );

/**
* Optimized union algorithm for polygonal inputs that are correctly noded and do not overlap.
* It may generate an error (returning a null geometry) for inputs that do not satisfy this constraint,
* however this is not guaranteed.
*
* The input geometry is the polygonal coverage to union, stored in a geometry collection.
* All members must be POLYGON or MULTIPOLYGON.
*
* \see validateCoverage()
* \since QGIS 3.36
*/
QgsGeometry unionCoverage() const;

/**
* Returns a (Multi)LineString representing the fully noded version of a collection of linestrings.
*
Expand Down
22 changes: 20 additions & 2 deletions src/core/geometry/qgsgeos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2087,7 +2087,7 @@ Qgis::CoverageValidityResult QgsGeos::validateCoverage( double gapWidth, std::un
#endif
}

QgsAbstractGeometry *QgsGeos::simplifyCoverageVW( double tolerance, bool preserveBoundary, QString *errorMsg ) const
std::unique_ptr<QgsAbstractGeometry> QgsGeos::simplifyCoverageVW( double tolerance, bool preserveBoundary, QString *errorMsg ) const
{
#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<12
( void )tolerance;
Expand All @@ -2106,12 +2106,30 @@ QgsAbstractGeometry *QgsGeos::simplifyCoverageVW( double tolerance, bool preserv
{
geos::unique_ptr simplified( GEOSCoverageSimplifyVW_r( geosinit()->ctxt, mGeos.get(), tolerance, preserveBoundary ? 1 : 0 ) );
std::unique_ptr< QgsAbstractGeometry > simplifiedGeom = fromGeos( simplified.get() );
return simplifiedGeom.release();
return simplifiedGeom;
}
CATCH_GEOS_WITH_ERRMSG( nullptr )
#endif
}

std::unique_ptr<QgsAbstractGeometry> QgsGeos::unionCoverage( QString *errorMsg ) const
{
if ( !mGeos )
{
if ( errorMsg )
*errorMsg = QStringLiteral( "Input geometry was not set" );
return nullptr;
}

try
{
geos::unique_ptr unioned( GEOSCoverageUnion_r( geosinit()->ctxt, mGeos.get() ) );
std::unique_ptr< QgsAbstractGeometry > result = fromGeos( unioned.get() );
return result;
}
CATCH_GEOS_WITH_ERRMSG( nullptr )
}

bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles, QgsGeometry *errorLoc ) const
{
if ( !mGeos )
Expand Down
15 changes: 14 additions & 1 deletion src/core/geometry/qgsgeos.h
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,20 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
* \see validateCoverage()
* \since QGIS 3.36
*/
QgsAbstractGeometry *simplifyCoverageVW( double tolerance, bool preserveBoundary, QString *errorMsg = nullptr ) const;
std::unique_ptr< QgsAbstractGeometry > simplifyCoverageVW( double tolerance, bool preserveBoundary, QString *errorMsg = nullptr ) const;

/**
* Optimized union algorithm for polygonal inputs that are correctly noded and do not overlap.
* It may generate an error (returns NULLPTR) for inputs that do not satisfy this constraint,
* however this is not guaranteed.
*
* The input geometry is the polygonal coverage to union, stored in a geometry collection.
* All members must be POLYGON or MULTIPOLYGON.
*
* \see validateCoverage()
* \since QGIS 3.36
*/
std::unique_ptr< QgsAbstractGeometry > unionCoverage( QString *errorMsg = nullptr ) const;

/**
* Create a geometry from a GEOSGeometry
Expand Down

0 comments on commit ed0766e

Please sign in to comment.