Skip to content

Commit e8c98f8

Browse files
authored
Merge pull request #8002 from elpaso/make_features_compatible
Port makeFeaturesCompatible from python to C++
2 parents 5173744 + b34c461 commit e8c98f8

File tree

5 files changed

+204
-94
lines changed

5 files changed

+204
-94
lines changed

python/core/auto_generated/qgsvectorlayerutils.sip.in

+42-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Returns true if the attribute value is valid for the field. Any constraint failu
133133
If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
134134
%End
135135

136-
static QgsFeature createFeature( QgsVectorLayer *layer,
136+
static QgsFeature createFeature( const QgsVectorLayer *layer,
137137
const QgsGeometry &geometry = QgsGeometry(),
138138
const QgsAttributeMap &attributes = QgsAttributeMap(),
139139
QgsExpressionContext *context = 0 );
@@ -176,6 +176,47 @@ are padded with NULL values to match the required length).
176176

177177
.. versionadded:: 3.4
178178
%End
179+
180+
static QgsFeatureList makeFeatureCompatible( const QgsFeature &feature, const QgsVectorLayer *layer );
181+
%Docstring
182+
Converts input ``feature`` to be compatible with the given ``layer``.
183+
184+
This function returns a new list of transformed features compatible with the input
185+
layer, note that the number of features returned might be greater than one when
186+
converting a multi part geometry to single part
187+
188+
The following operations will be performed to convert the input features:
189+
- convert single geometries to multi part
190+
- drop additional attributes
191+
- drop geometry if layer is geometry-less
192+
- add missing attribute fields
193+
- add back M/Z values (initialized to 0)
194+
- drop Z/M
195+
- convert multi part geometries to single part
196+
197+
.. versionadded:: 3.4
198+
%End
199+
200+
static QgsFeatureList makeFeaturesCompatible( const QgsFeatureList &features, const QgsVectorLayer *layer );
201+
%Docstring
202+
Converts input ``features`` to be compatible with the given ``layer``.
203+
204+
This function returns a new list of transformed features compatible with the input
205+
layer, note that the number of features returned might be greater than the number
206+
of input features.
207+
208+
The following operations will be performed to convert the input features:
209+
- convert single geometries to multi part
210+
- drop additional attributes
211+
- drop geometry if layer is geometry-less
212+
- add missing attribute fields
213+
- add back M/Z values (initialized to 0)
214+
- drop Z/M
215+
- convert multi part geometries to single part
216+
217+
.. versionadded:: 3.4
218+
%End
219+
179220
};
180221

181222

python/plugins/processing/gui/AlgorithmExecutor.py

+3-81
Original file line numberDiff line numberDiff line change
@@ -70,85 +70,6 @@ def execute(alg, parameters, context=None, feedback=None):
7070
return False, {}
7171

7272

73-
def make_features_compatible(new_features, input_layer):
74-
"""Try to make the new features compatible with old features by:
75-
76-
- converting single to multi part
77-
- dropping additional attributes
78-
- adding back M/Z values
79-
- drop Z/M
80-
- convert multi part to single part
81-
82-
:param new_features: new features
83-
:type new_features: list of QgsFeatures
84-
:param input_layer: input layer
85-
:type input_layer: QgsVectorLayer
86-
:return: modified features
87-
:rtype: list of QgsFeatures
88-
"""
89-
90-
input_wkb_type = input_layer.wkbType()
91-
result_features = []
92-
for new_f in new_features:
93-
# Fix attributes
94-
QgsVectorLayerUtils.matchAttributesToFields(new_f, input_layer.fields())
95-
96-
# Check if we need geometry manipulation
97-
new_f_geom_type = QgsWkbTypes.geometryType(new_f.geometry().wkbType())
98-
new_f_has_geom = new_f_geom_type not in (QgsWkbTypes.UnknownGeometry, QgsWkbTypes.NullGeometry)
99-
input_layer_has_geom = input_wkb_type not in (QgsWkbTypes.NoGeometry, QgsWkbTypes.Unknown)
100-
101-
# Drop geometry if layer is geometry-less
102-
if not input_layer_has_geom and new_f_has_geom:
103-
f = QgsFeature(input_layer.fields())
104-
f.setAttributes(new_f.attributes())
105-
new_f = f
106-
result_features.append(new_f)
107-
continue # skip the rest
108-
109-
if input_layer_has_geom and new_f_has_geom and \
110-
new_f.geometry().wkbType() != input_wkb_type: # Fix geometry
111-
# Single -> Multi
112-
if (QgsWkbTypes.isMultiType(input_wkb_type) and not
113-
new_f.geometry().isMultipart()):
114-
new_geom = new_f.geometry()
115-
new_geom.convertToMultiType()
116-
new_f.setGeometry(new_geom)
117-
# Drop Z/M
118-
if (new_f.geometry().constGet().is3D() and not QgsWkbTypes.hasZ(input_wkb_type)):
119-
new_geom = new_f.geometry()
120-
new_geom.get().dropZValue()
121-
new_f.setGeometry(new_geom)
122-
if (new_f.geometry().constGet().isMeasure() and not QgsWkbTypes.hasM(input_wkb_type)):
123-
new_geom = new_f.geometry()
124-
new_geom.get().dropMValue()
125-
new_f.setGeometry(new_geom)
126-
# Add Z/M back (set it to 0)
127-
if (not new_f.geometry().constGet().is3D() and QgsWkbTypes.hasZ(input_wkb_type)):
128-
new_geom = new_f.geometry()
129-
new_geom.get().addZValue(0.0)
130-
new_f.setGeometry(new_geom)
131-
if (not new_f.geometry().constGet().isMeasure() and QgsWkbTypes.hasM(input_wkb_type)):
132-
new_geom = new_f.geometry()
133-
new_geom.get().addMValue(0.0)
134-
new_f.setGeometry(new_geom)
135-
# Multi -> Single
136-
if (not QgsWkbTypes.isMultiType(input_wkb_type) and
137-
new_f.geometry().isMultipart()):
138-
g = new_f.geometry()
139-
g2 = g.constGet()
140-
for i in range(g2.partCount()):
141-
# Clone or crash!
142-
g4 = QgsGeometry(g2.geometryN(i).clone())
143-
f = QgsVectorLayerUtils.createFeature(input_layer, g4, {i: new_f.attribute(i) for i in range(new_f.fields().count())})
144-
result_features.append(f)
145-
else:
146-
result_features.append(new_f)
147-
else:
148-
result_features.append(new_f)
149-
return result_features
150-
151-
15273
def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
15374
"""Executes an algorithm modifying features in-place in the input layer.
15475
@@ -208,7 +129,7 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
208129
# a shallow copy from processFeature
209130
input_feature = QgsFeature(f)
210131
new_features = alg.processFeature(input_feature, context, feedback)
211-
new_features = make_features_compatible(new_features, active_layer)
132+
new_features = QgsVectorLayerUtils.makeFeaturesCompatible(new_features, active_layer)
212133
if len(new_features) == 0:
213134
active_layer.deleteFeature(f.id())
214135
elif len(new_features) == 1:
@@ -238,7 +159,8 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
238159
active_layer.deleteFeatures(active_layer.selectedFeatureIds())
239160
new_features = []
240161
for f in result_layer.getFeatures():
241-
new_features.extend(make_features_compatible([f], active_layer))
162+
new_features.extend(QgsVectorLayerUtils.
163+
makeFeaturesCompatible([f], active_layer))
242164

243165
# Get the new ids
244166
old_ids = set([f.id() for f in active_layer.getFeatures(req)])

src/core/qgsvectorlayerutils.cpp

+106-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsfeedback.h"
2626
#include "qgsvectorlayer.h"
2727
#include "qgsthreadingutils.h"
28+
#include "qgsgeometrycollection.h"
2829

2930
QgsFeatureIterator QgsVectorLayerUtils::getValuesIterator( const QgsVectorLayer *layer, const QString &fieldOrExpression, bool &ok, bool selectedOnly )
3031
{
@@ -348,7 +349,7 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer *layer, const
348349
return valid;
349350
}
350351

351-
QgsFeature QgsVectorLayerUtils::createFeature( QgsVectorLayer *layer, const QgsGeometry &geometry,
352+
QgsFeature QgsVectorLayerUtils::createFeature( const QgsVectorLayer *layer, const QgsGeometry &geometry,
352353
const QgsAttributeMap &attributes, QgsExpressionContext *context )
353354
{
354355
if ( !layer )
@@ -560,6 +561,110 @@ void QgsVectorLayerUtils::matchAttributesToFields( QgsFeature &feature, const Qg
560561
}
561562
}
562563

564+
QgsFeatureList QgsVectorLayerUtils::makeFeatureCompatible( const QgsFeature &feature, const QgsVectorLayer *layer )
565+
{
566+
QgsWkbTypes::Type inputWkbType( layer->wkbType( ) );
567+
QgsFeatureList resultFeatures;
568+
QgsFeature newF( feature );
569+
// Fix attributes
570+
QgsVectorLayerUtils::matchAttributesToFields( newF, layer->fields( ) );
571+
// Does geometry need transformations?
572+
QgsWkbTypes::GeometryType newFGeomType( QgsWkbTypes::geometryType( newF.geometry().wkbType() ) );
573+
bool newFHasGeom = newFGeomType !=
574+
QgsWkbTypes::GeometryType::UnknownGeometry &&
575+
newFGeomType != QgsWkbTypes::GeometryType::NullGeometry;
576+
bool layerHasGeom = inputWkbType !=
577+
QgsWkbTypes::Type::NoGeometry &&
578+
inputWkbType != QgsWkbTypes::Type::Unknown;
579+
// Drop geometry if layer is geometry-less
580+
if ( newFHasGeom && ! layerHasGeom )
581+
{
582+
QgsFeature _f = QgsFeature( layer->fields() );
583+
_f.setAttributes( newF.attributes() );
584+
resultFeatures.append( _f );
585+
}
586+
else
587+
{
588+
// Geometry need fixing
589+
if ( newFHasGeom && layerHasGeom && newF.geometry().wkbType() != inputWkbType )
590+
{
591+
// Single -> multi
592+
if ( QgsWkbTypes::isMultiType( inputWkbType ) && ! newF.geometry().isMultipart( ) )
593+
{
594+
QgsGeometry newGeom( newF.geometry( ) );
595+
newGeom.convertToMultiType();
596+
newF.setGeometry( newGeom );
597+
}
598+
// Drop Z/M
599+
if ( newF.geometry().constGet()->is3D() && ! QgsWkbTypes::hasZ( inputWkbType ) )
600+
{
601+
QgsGeometry newGeom( newF.geometry( ) );
602+
newGeom.get()->dropZValue();
603+
newF.setGeometry( newGeom );
604+
}
605+
if ( newF.geometry().constGet()->isMeasure() && ! QgsWkbTypes::hasM( inputWkbType ) )
606+
{
607+
QgsGeometry newGeom( newF.geometry( ) );
608+
newGeom.get()->dropMValue();
609+
newF.setGeometry( newGeom );
610+
}
611+
// Add Z/M back, set to 0
612+
if ( ! newF.geometry().constGet()->is3D() && QgsWkbTypes::hasZ( inputWkbType ) )
613+
{
614+
QgsGeometry newGeom( newF.geometry( ) );
615+
newGeom.get()->addZValue( 0.0 );
616+
newF.setGeometry( newGeom );
617+
}
618+
if ( ! newF.geometry().constGet()->isMeasure() && QgsWkbTypes::hasM( inputWkbType ) )
619+
{
620+
QgsGeometry newGeom( newF.geometry( ) );
621+
newGeom.get()->addMValue( 0.0 );
622+
newF.setGeometry( newGeom );
623+
}
624+
// Multi -> single
625+
if ( ! QgsWkbTypes::isMultiType( inputWkbType ) && newF.geometry().isMultipart( ) )
626+
{
627+
QgsGeometry newGeom( newF.geometry( ) );
628+
const QgsGeometryCollection *parts( static_cast< const QgsGeometryCollection * >( newGeom.constGet() ) );
629+
QgsAttributeMap attrMap;
630+
for ( int j = 0; j < newF.fields().count(); j++ )
631+
{
632+
attrMap[j] = newF.attribute( j );
633+
}
634+
for ( int i = 0; i < parts->partCount( ); i++ )
635+
{
636+
QgsGeometry g( parts->geometryN( i )->clone() );
637+
QgsFeature _f( createFeature( layer, g, attrMap ) );
638+
resultFeatures.append( _f );
639+
}
640+
}
641+
else
642+
{
643+
resultFeatures.append( newF );
644+
}
645+
}
646+
else
647+
{
648+
resultFeatures.append( newF );
649+
}
650+
}
651+
return resultFeatures;
652+
}
653+
654+
QgsFeatureList QgsVectorLayerUtils::makeFeaturesCompatible( const QgsFeatureList &features, const QgsVectorLayer *layer )
655+
{
656+
QgsFeatureList resultFeatures;
657+
for ( const QgsFeature &f : features )
658+
{
659+
const QgsFeatureList features( makeFeatureCompatible( f, layer ) );
660+
for ( const auto &_f : features )
661+
{
662+
resultFeatures.append( _f );
663+
}
664+
}
665+
return resultFeatures;
666+
}
667+
563668
QList<QgsVectorLayer *> QgsVectorLayerUtils::QgsDuplicateFeatureContext::layers() const
564669
{
565670
QList<QgsVectorLayer *> layers;

src/core/qgsvectorlayerutils.h

+42-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class CORE_EXPORT QgsVectorLayerUtils
140140
* assuming that they respect the layer's constraints. Note that the created feature is not
141141
* automatically inserted into the layer.
142142
*/
143-
static QgsFeature createFeature( QgsVectorLayer *layer,
143+
static QgsFeature createFeature( const QgsVectorLayer *layer,
144144
const QgsGeometry &geometry = QgsGeometry(),
145145
const QgsAttributeMap &attributes = QgsAttributeMap(),
146146
QgsExpressionContext *context = nullptr );
@@ -187,6 +187,47 @@ class CORE_EXPORT QgsVectorLayerUtils
187187
* \since QGIS 3.4
188188
*/
189189
static void matchAttributesToFields( QgsFeature &feature, const QgsFields &fields );
190+
191+
/**
192+
* Converts input \a feature to be compatible with the given \a layer.
193+
*
194+
* This function returns a new list of transformed features compatible with the input
195+
* layer, note that the number of features returned might be greater than one when
196+
* converting a multi part geometry to single part
197+
*
198+
* The following operations will be performed to convert the input features:
199+
* - convert single geometries to multi part
200+
* - drop additional attributes
201+
* - drop geometry if layer is geometry-less
202+
* - add missing attribute fields
203+
* - add back M/Z values (initialized to 0)
204+
* - drop Z/M
205+
* - convert multi part geometries to single part
206+
*
207+
* \since QGIS 3.4
208+
*/
209+
static QgsFeatureList makeFeatureCompatible( const QgsFeature &feature, const QgsVectorLayer *layer );
210+
211+
/**
212+
* Converts input \a features to be compatible with the given \a layer.
213+
*
214+
* This function returns a new list of transformed features compatible with the input
215+
* layer, note that the number of features returned might be greater than the number
216+
* of input features.
217+
*
218+
* The following operations will be performed to convert the input features:
219+
* - convert single geometries to multi part
220+
* - drop additional attributes
221+
* - drop geometry if layer is geometry-less
222+
* - add missing attribute fields
223+
* - add back M/Z values (initialized to 0)
224+
* - drop Z/M
225+
* - convert multi part geometries to single part
226+
*
227+
* \since QGIS 3.4
228+
*/
229+
static QgsFeatureList makeFeaturesCompatible( const QgsFeatureList &features, const QgsVectorLayer *layer );
230+
190231
};
191232

192233

0 commit comments

Comments
 (0)