Skip to content
Permalink
Browse files

Merge pull request #8002 from elpaso/make_features_compatible

Port makeFeaturesCompatible from python to C++
  • Loading branch information
elpaso committed Sep 25, 2018
2 parents 5173744 + b34c461 commit e8c98f89152370d415a251e2982456ed9d76aa89
@@ -133,7 +133,7 @@ Returns true if the attribute value is valid for the field. Any constraint failu
If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
%End

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

.. versionadded:: 3.4
%End

static QgsFeatureList makeFeatureCompatible( const QgsFeature &feature, const QgsVectorLayer *layer );
%Docstring
Converts input ``feature`` to be compatible with the given ``layer``.

This function returns a new list of transformed features compatible with the input
layer, note that the number of features returned might be greater than one when
converting a multi part geometry to single part

The following operations will be performed to convert the input features:
- convert single geometries to multi part
- drop additional attributes
- drop geometry if layer is geometry-less
- add missing attribute fields
- add back M/Z values (initialized to 0)
- drop Z/M
- convert multi part geometries to single part

.. versionadded:: 3.4
%End

static QgsFeatureList makeFeaturesCompatible( const QgsFeatureList &features, const QgsVectorLayer *layer );
%Docstring
Converts input ``features`` to be compatible with the given ``layer``.

This function returns a new list of transformed features compatible with the input
layer, note that the number of features returned might be greater than the number
of input features.

The following operations will be performed to convert the input features:
- convert single geometries to multi part
- drop additional attributes
- drop geometry if layer is geometry-less
- add missing attribute fields
- add back M/Z values (initialized to 0)
- drop Z/M
- convert multi part geometries to single part

.. versionadded:: 3.4
%End

};


@@ -70,85 +70,6 @@ def execute(alg, parameters, context=None, feedback=None):
return False, {}


def make_features_compatible(new_features, input_layer):
"""Try to make the new features compatible with old features by:
- converting single to multi part
- dropping additional attributes
- adding back M/Z values
- drop Z/M
- convert multi part to single part
:param new_features: new features
:type new_features: list of QgsFeatures
:param input_layer: input layer
:type input_layer: QgsVectorLayer
:return: modified features
:rtype: list of QgsFeatures
"""

input_wkb_type = input_layer.wkbType()
result_features = []
for new_f in new_features:
# Fix attributes
QgsVectorLayerUtils.matchAttributesToFields(new_f, input_layer.fields())

# Check if we need geometry manipulation
new_f_geom_type = QgsWkbTypes.geometryType(new_f.geometry().wkbType())
new_f_has_geom = new_f_geom_type not in (QgsWkbTypes.UnknownGeometry, QgsWkbTypes.NullGeometry)
input_layer_has_geom = input_wkb_type not in (QgsWkbTypes.NoGeometry, QgsWkbTypes.Unknown)

# Drop geometry if layer is geometry-less
if not input_layer_has_geom and new_f_has_geom:
f = QgsFeature(input_layer.fields())
f.setAttributes(new_f.attributes())
new_f = f
result_features.append(new_f)
continue # skip the rest

if input_layer_has_geom and new_f_has_geom and \
new_f.geometry().wkbType() != input_wkb_type: # Fix geometry
# Single -> Multi
if (QgsWkbTypes.isMultiType(input_wkb_type) and not
new_f.geometry().isMultipart()):
new_geom = new_f.geometry()
new_geom.convertToMultiType()
new_f.setGeometry(new_geom)
# Drop Z/M
if (new_f.geometry().constGet().is3D() and not QgsWkbTypes.hasZ(input_wkb_type)):
new_geom = new_f.geometry()
new_geom.get().dropZValue()
new_f.setGeometry(new_geom)
if (new_f.geometry().constGet().isMeasure() and not QgsWkbTypes.hasM(input_wkb_type)):
new_geom = new_f.geometry()
new_geom.get().dropMValue()
new_f.setGeometry(new_geom)
# Add Z/M back (set it to 0)
if (not new_f.geometry().constGet().is3D() and QgsWkbTypes.hasZ(input_wkb_type)):
new_geom = new_f.geometry()
new_geom.get().addZValue(0.0)
new_f.setGeometry(new_geom)
if (not new_f.geometry().constGet().isMeasure() and QgsWkbTypes.hasM(input_wkb_type)):
new_geom = new_f.geometry()
new_geom.get().addMValue(0.0)
new_f.setGeometry(new_geom)
# Multi -> Single
if (not QgsWkbTypes.isMultiType(input_wkb_type) and
new_f.geometry().isMultipart()):
g = new_f.geometry()
g2 = g.constGet()
for i in range(g2.partCount()):
# Clone or crash!
g4 = QgsGeometry(g2.geometryN(i).clone())
f = QgsVectorLayerUtils.createFeature(input_layer, g4, {i: new_f.attribute(i) for i in range(new_f.fields().count())})
result_features.append(f)
else:
result_features.append(new_f)
else:
result_features.append(new_f)
return result_features


def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
"""Executes an algorithm modifying features in-place in the input layer.
@@ -208,7 +129,7 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
# a shallow copy from processFeature
input_feature = QgsFeature(f)
new_features = alg.processFeature(input_feature, context, feedback)
new_features = make_features_compatible(new_features, active_layer)
new_features = QgsVectorLayerUtils.makeFeaturesCompatible(new_features, active_layer)
if len(new_features) == 0:
active_layer.deleteFeature(f.id())
elif len(new_features) == 1:
@@ -238,7 +159,8 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
active_layer.deleteFeatures(active_layer.selectedFeatureIds())
new_features = []
for f in result_layer.getFeatures():
new_features.extend(make_features_compatible([f], active_layer))
new_features.extend(QgsVectorLayerUtils.
makeFeaturesCompatible([f], active_layer))

# Get the new ids
old_ids = set([f.id() for f in active_layer.getFeatures(req)])
@@ -25,6 +25,7 @@
#include "qgsfeedback.h"
#include "qgsvectorlayer.h"
#include "qgsthreadingutils.h"
#include "qgsgeometrycollection.h"

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

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

QgsFeatureList QgsVectorLayerUtils::makeFeatureCompatible( const QgsFeature &feature, const QgsVectorLayer *layer )
{
QgsWkbTypes::Type inputWkbType( layer->wkbType( ) );
QgsFeatureList resultFeatures;
QgsFeature newF( feature );
// Fix attributes
QgsVectorLayerUtils::matchAttributesToFields( newF, layer->fields( ) );
// Does geometry need transformations?
QgsWkbTypes::GeometryType newFGeomType( QgsWkbTypes::geometryType( newF.geometry().wkbType() ) );
bool newFHasGeom = newFGeomType !=
QgsWkbTypes::GeometryType::UnknownGeometry &&
newFGeomType != QgsWkbTypes::GeometryType::NullGeometry;
bool layerHasGeom = inputWkbType !=
QgsWkbTypes::Type::NoGeometry &&
inputWkbType != QgsWkbTypes::Type::Unknown;
// Drop geometry if layer is geometry-less
if ( newFHasGeom && ! layerHasGeom )
{
QgsFeature _f = QgsFeature( layer->fields() );
_f.setAttributes( newF.attributes() );
resultFeatures.append( _f );
}
else
{
// Geometry need fixing
if ( newFHasGeom && layerHasGeom && newF.geometry().wkbType() != inputWkbType )
{
// Single -> multi
if ( QgsWkbTypes::isMultiType( inputWkbType ) && ! newF.geometry().isMultipart( ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.convertToMultiType();
newF.setGeometry( newGeom );
}
// Drop Z/M
if ( newF.geometry().constGet()->is3D() && ! QgsWkbTypes::hasZ( inputWkbType ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->dropZValue();
newF.setGeometry( newGeom );
}
if ( newF.geometry().constGet()->isMeasure() && ! QgsWkbTypes::hasM( inputWkbType ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->dropMValue();
newF.setGeometry( newGeom );
}
// Add Z/M back, set to 0
if ( ! newF.geometry().constGet()->is3D() && QgsWkbTypes::hasZ( inputWkbType ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->addZValue( 0.0 );
newF.setGeometry( newGeom );
}
if ( ! newF.geometry().constGet()->isMeasure() && QgsWkbTypes::hasM( inputWkbType ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->addMValue( 0.0 );
newF.setGeometry( newGeom );
}
// Multi -> single
if ( ! QgsWkbTypes::isMultiType( inputWkbType ) && newF.geometry().isMultipart( ) )
{
QgsGeometry newGeom( newF.geometry( ) );
const QgsGeometryCollection *parts( static_cast< const QgsGeometryCollection * >( newGeom.constGet() ) );
QgsAttributeMap attrMap;
for ( int j = 0; j < newF.fields().count(); j++ )
{
attrMap[j] = newF.attribute( j );
}
for ( int i = 0; i < parts->partCount( ); i++ )
{
QgsGeometry g( parts->geometryN( i )->clone() );
QgsFeature _f( createFeature( layer, g, attrMap ) );
resultFeatures.append( _f );
}
}
else
{
resultFeatures.append( newF );
}
}
else
{
resultFeatures.append( newF );
}
}
return resultFeatures;
}

QgsFeatureList QgsVectorLayerUtils::makeFeaturesCompatible( const QgsFeatureList &features, const QgsVectorLayer *layer )
{
QgsFeatureList resultFeatures;
for ( const QgsFeature &f : features )
{
const QgsFeatureList features( makeFeatureCompatible( f, layer ) );
for ( const auto &_f : features )
{
resultFeatures.append( _f );
}
}
return resultFeatures;
}

QList<QgsVectorLayer *> QgsVectorLayerUtils::QgsDuplicateFeatureContext::layers() const
{
QList<QgsVectorLayer *> layers;
@@ -140,7 +140,7 @@ class CORE_EXPORT QgsVectorLayerUtils
* assuming that they respect the layer's constraints. Note that the created feature is not
* automatically inserted into the layer.
*/
static QgsFeature createFeature( QgsVectorLayer *layer,
static QgsFeature createFeature( const QgsVectorLayer *layer,
const QgsGeometry &geometry = QgsGeometry(),
const QgsAttributeMap &attributes = QgsAttributeMap(),
QgsExpressionContext *context = nullptr );
@@ -187,6 +187,47 @@ class CORE_EXPORT QgsVectorLayerUtils
* \since QGIS 3.4
*/
static void matchAttributesToFields( QgsFeature &feature, const QgsFields &fields );

/**
* Converts input \a feature to be compatible with the given \a layer.
*
* This function returns a new list of transformed features compatible with the input
* layer, note that the number of features returned might be greater than one when
* converting a multi part geometry to single part
*
* The following operations will be performed to convert the input features:
* - convert single geometries to multi part
* - drop additional attributes
* - drop geometry if layer is geometry-less
* - add missing attribute fields
* - add back M/Z values (initialized to 0)
* - drop Z/M
* - convert multi part geometries to single part
*
* \since QGIS 3.4
*/
static QgsFeatureList makeFeatureCompatible( const QgsFeature &feature, const QgsVectorLayer *layer );

/**
* Converts input \a features to be compatible with the given \a layer.
*
* This function returns a new list of transformed features compatible with the input
* layer, note that the number of features returned might be greater than the number
* of input features.
*
* The following operations will be performed to convert the input features:
* - convert single geometries to multi part
* - drop additional attributes
* - drop geometry if layer is geometry-less
* - add missing attribute fields
* - add back M/Z values (initialized to 0)
* - drop Z/M
* - convert multi part geometries to single part
*
* \since QGIS 3.4
*/
static QgsFeatureList makeFeaturesCompatible( const QgsFeatureList &features, const QgsVectorLayer *layer );

};


0 comments on commit e8c98f8

Please sign in to comment.
You can’t perform that action at this time.