[feature][processing] Add "Keep disjoint features separate" option

for dissolve algorithm

If enabled, this option will cause disjoint features and parts to
be exported as separate features (instead of as parts of a
single multipart feature).
This commit is contained in:
Nyall Dawson 2022-03-17 15:29:09 +10:00
parent d156fdb893
commit 290154744b
6 changed files with 123 additions and 17 deletions

View File

@ -0,0 +1,13 @@
{
"type": "FeatureCollection",
"name": "overlapping_polys",
"features": [
{ "type": "Feature", "properties": { "fid": "overlapping_polys.0", "class": "a" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.0, -3.0 ], [ 0.0, -6.0 ], [ 3.0, -6.0 ], [ 3.0, -3.0 ], [ 0.0, -3.0 ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.1", "class": "a" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 2.0, -2.0 ], [ 2.0, -5.0 ], [ 5.0, -5.0 ], [ 5.0, -2.0 ], [ 2.0, -2.0 ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.2", "class": "a" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 8.0, -4.0 ], [ 8.0, -6.0 ], [ 11.0, -6.0 ], [ 11.0, -4.0 ], [ 8.0, -4.0 ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.3", "class": "a" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.0, -5.0 ], [ 7.0, -5.0 ], [ 7.0, -7.0 ], [ 9.0, -7.0 ], [ 9.0, -5.0 ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.4", "class": "a" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 7.0, -9.0 ], [ 9.0, -9.0 ], [ 9.0, -10.0 ], [ 7.0, -10.0 ], [ 7.0, -9.0 ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.5", "class": "b" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 6.0, -6.0 ], [ 8.0, -6.0 ], [ 8.0, -11.0 ], [ 6.0, -11.0 ], [ 6.0, -6.0 ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.6", "class": "b" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.0, -8.0 ], [ 3.0, -8.0 ], [ 3.0, -10.0 ], [ 0.0, -10.0 ], [ 0.0, -8.0 ] ] ] } }
]
}

View File

@ -0,0 +1,10 @@
{
"type": "FeatureCollection",
"name": "dissolve_disjoint",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "fid": "overlapping_polys.0", "class": "a" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 9.0, -10.0 ], [ 8.0, -10.0 ], [ 8.0, -11.0 ], [ 6.0, -11.0 ], [ 6.0, -6.0 ], [ 7.0, -6.0 ], [ 7.0, -5.0 ], [ 8.0, -5.0 ], [ 8.0, -4.0 ], [ 11.0, -4.0 ], [ 11.0, -6.0 ], [ 9.0, -6.0 ], [ 9.0, -7.0 ], [ 8.0, -7.0 ], [ 8.0, -9.0 ], [ 9.0, -9.0 ], [ 9.0, -10.0 ] ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.0", "class": "a" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 0.0, -10.0 ], [ 0.0, -8.0 ], [ 3.0, -8.0 ], [ 3.0, -10.0 ], [ 0.0, -10.0 ] ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.0", "class": "a" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 0.0, -3.0 ], [ 2.0, -3.0 ], [ 2.0, -2.0 ], [ 5.0, -2.0 ], [ 5.0, -5.0 ], [ 3.0, -5.0 ], [ 3.0, -6.0 ], [ 0.0, -6.0 ], [ 0.0, -3.0 ] ] ] ] } }
]
}

View File

@ -0,0 +1,12 @@
{
"type": "FeatureCollection",
"name": "dissolve_disjoint_by_class",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "fid": "overlapping_polys.0", "class": "a" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 9.0, -10.0 ], [ 7.0, -10.0 ], [ 7.0, -9.0 ], [ 9.0, -9.0 ], [ 9.0, -10.0 ] ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.0", "class": "a" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 11.0, -6.0 ], [ 9.0, -6.0 ], [ 9.0, -7.0 ], [ 7.0, -7.0 ], [ 7.0, -5.0 ], [ 8.0, -5.0 ], [ 8.0, -4.0 ], [ 11.0, -4.0 ], [ 11.0, -6.0 ] ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.0", "class": "a" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 0.0, -3.0 ], [ 2.0, -3.0 ], [ 2.0, -2.0 ], [ 5.0, -2.0 ], [ 5.0, -5.0 ], [ 3.0, -5.0 ], [ 3.0, -6.0 ], [ 0.0, -6.0 ], [ 0.0, -3.0 ] ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.5", "class": "b" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 3.0, -8.0 ], [ 3.0, -10.0 ], [ 0.0, -10.0 ], [ 0.0, -8.0 ], [ 3.0, -8.0 ] ] ] ] } },
{ "type": "Feature", "properties": { "fid": "overlapping_polys.5", "class": "b" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 8.0, -6.0 ], [ 8.0, -11.0 ], [ 6.0, -11.0 ], [ 6.0, -6.0 ], [ 8.0, -6.0 ] ] ] ] } }
]
}

View File

@ -759,6 +759,32 @@ tests:
geometry:
precision: 7
- algorithm: native:dissolve
name: Dissolve separate disjoint
params:
INPUT:
name: custom/overlapping_polys.geojson
type: vector
SEPARATE_DISJOINT: true
results:
OUTPUT:
name: expected/dissolve_disjoint.geojson
type: vector
- algorithm: native:dissolve
name: Dissolve separate disjoint with classes
params:
FIELD:
- class
INPUT:
name: custom/overlapping_polys.geojson
type: vector
SEPARATE_DISJOINT: true
results:
OUTPUT:
name: expected/dissolve_disjoint_by_class.geojson
type: vector
- algorithm: native:buffer
name: Basic polygon buffer
params:

View File

@ -24,7 +24,7 @@
//
QVariantMap QgsCollectorAlgorithm::processCollection( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback,
const std::function<QgsGeometry( const QVector< QgsGeometry >& )> &collector, int maxQueueLength, QgsProcessingFeatureSource::Flags sourceFlags )
const std::function<QgsGeometry( const QVector< QgsGeometry >& )> &collector, int maxQueueLength, QgsProcessingFeatureSource::Flags sourceFlags, bool separateDisjoint )
{
std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
@ -83,15 +83,30 @@ QVariantMap QgsCollectorAlgorithm::processCollection( const QVariantMap &paramet
current++;
}
outputFeature.setGeometry( collector( geomQueue ) );
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
if ( !separateDisjoint )
{
outputFeature.setGeometry( collector( geomQueue ) );
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
}
else
{
const QgsGeometry combinedGeometry = collector( geomQueue );
for ( auto it = combinedGeometry.const_parts_begin(); it != combinedGeometry.const_parts_end(); ++it )
{
QgsGeometry partGeom( ( ( *it )->clone() ) );
partGeom.convertToMultiType();
outputFeature.setGeometry( partGeom );
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
}
}
}
else
{
QList< int > fieldIndexes;
const auto constFields = fields;
for ( const QString &field : constFields )
fieldIndexes.reserve( fields.size() );
for ( const QString &field : fields )
{
const int index = source->fields().lookupField( field );
if ( index >= 0 )
@ -109,8 +124,8 @@ QVariantMap QgsCollectorAlgorithm::processCollection( const QVariantMap &paramet
}
QVariantList indexAttributes;
const auto constFieldIndexes = fieldIndexes;
for ( const int index : constFieldIndexes )
indexAttributes.reserve( fieldIndexes.size() );
for ( const int index : std::as_const( fieldIndexes ) )
{
indexAttributes << f.attribute( index );
}
@ -137,18 +152,38 @@ QVariantMap QgsCollectorAlgorithm::processCollection( const QVariantMap &paramet
}
QgsFeature outputFeature;
if ( geometryHash.contains( attrIt.key() ) )
outputFeature.setAttributes( attrIt.value() );
auto geometryHashIt = geometryHash.find( attrIt.key() );
if ( geometryHashIt != geometryHash.end() )
{
QgsGeometry geom = collector( geometryHash.value( attrIt.key() ) );
QgsGeometry geom = collector( geometryHashIt.value() );
if ( !geom.isMultipart() )
{
geom.convertToMultiType();
}
outputFeature.setGeometry( geom );
if ( !separateDisjoint )
{
outputFeature.setGeometry( geom );
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
}
else
{
for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
{
QgsGeometry partGeom( ( ( *it )->clone() ) );
partGeom.convertToMultiType();
outputFeature.setGeometry( partGeom );
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
}
}
}
else
{
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
}
outputFeature.setAttributes( attrIt.value() );
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
feedback->setProgress( current * 100.0 / numberFeatures );
current++;
@ -197,6 +232,11 @@ void QgsDissolveAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Dissolve field(s)" ), QVariant(),
QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) );
std::unique_ptr< QgsProcessingParameterBoolean > disjointParam = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "SEPARATE_DISJOINT" ),
QObject::tr( "Keep disjoint features separate" ), false );
disjointParam->setFlags( disjointParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( disjointParam.release() );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Dissolved" ) ) );
}
@ -206,7 +246,9 @@ QString QgsDissolveAlgorithm::shortHelpString() const
"be specified to dissolve features belonging to the same class (having the same value for the specified attributes), alternatively "
"all features can be dissolved in a single one.\n\n"
"All output geometries will be converted to multi geometries. "
"In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased." );
"In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased.\n\n"
"If enabled, the optional \"Keep disjoing features separate\" setting will cause disjoint features and parts to be exported "
"as separate features (instead of parts of a single multipart feature)." );
}
QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
@ -216,6 +258,8 @@ QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
const bool separateDisjoint = parameterAsBool( parameters, QStringLiteral( "SEPARATE_DISJOINT" ), context );
return processCollection( parameters, context, feedback, [ & ]( const QVector< QgsGeometry > &parts )->QgsGeometry
{
QgsGeometry result( QgsGeometry::unaryUnion( parts ) );
@ -246,7 +290,7 @@ QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap &parameter
throw QgsProcessingException( QObject::tr( "The algorithm returned no output." ) );
}
return result;
}, 10000 );
}, 10000, QgsProcessingFeatureSource::Flags(), separateDisjoint );
}
//

View File

@ -34,7 +34,8 @@ class QgsCollectorAlgorithm : public QgsProcessingAlgorithm
protected:
QVariantMap processCollection( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback,
const std::function<QgsGeometry( const QVector<QgsGeometry>& )> &collector, int maxQueueLength = 0, QgsProcessingFeatureSource::Flags sourceFlags = QgsProcessingFeatureSource::Flags() );
const std::function<QgsGeometry( const QVector<QgsGeometry>& )> &collector, int maxQueueLength = 0, QgsProcessingFeatureSource::Flags sourceFlags = QgsProcessingFeatureSource::Flags(),
bool separateDisjoint = false );
};
/**