mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-15 00:02:52 -04:00
[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:
parent
d156fdb893
commit
290154744b
13
python/plugins/processing/tests/testdata/custom/overlapping_polys.geojson
vendored
Normal file
13
python/plugins/processing/tests/testdata/custom/overlapping_polys.geojson
vendored
Normal 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 ] ] ] } }
|
||||
]
|
||||
}
|
10
python/plugins/processing/tests/testdata/expected/dissolve_disjoint.geojson
vendored
Normal file
10
python/plugins/processing/tests/testdata/expected/dissolve_disjoint.geojson
vendored
Normal 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 ] ] ] ] } }
|
||||
]
|
||||
}
|
12
python/plugins/processing/tests/testdata/expected/dissolve_disjoint_by_class.geojson
vendored
Normal file
12
python/plugins/processing/tests/testdata/expected/dissolve_disjoint_by_class.geojson
vendored
Normal 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 ] ] ] ] } }
|
||||
]
|
||||
}
|
@ -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:
|
||||
|
@ -24,7 +24,7 @@
|
||||
//
|
||||
|
||||
QVariantMap QgsCollectorAlgorithm::processCollection( const QVariantMap ¶meters, 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 ¶met
|
||||
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 ¶met
|
||||
}
|
||||
|
||||
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 ¶met
|
||||
}
|
||||
|
||||
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 ¶meters, 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 ¶meter
|
||||
throw QgsProcessingException( QObject::tr( "The algorithm returned no output." ) );
|
||||
}
|
||||
return result;
|
||||
}, 10000 );
|
||||
}, 10000, QgsProcessingFeatureSource::Flags(), separateDisjoint );
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -34,7 +34,8 @@ class QgsCollectorAlgorithm : public QgsProcessingAlgorithm
|
||||
protected:
|
||||
|
||||
QVariantMap processCollection( const QVariantMap ¶meters, 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 );
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user