Navigation Menu

Skip to content

Commit

Permalink
Merge pull request #8002 from elpaso/make_features_compatible
Browse files Browse the repository at this point in the history
Port makeFeaturesCompatible from python to C++
  • Loading branch information
elpaso committed Sep 25, 2018
2 parents 5173744 + b34c461 commit e8c98f8
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 94 deletions.
43 changes: 42 additions & 1 deletion python/core/auto_generated/qgsvectorlayerutils.sip.in
Expand Up @@ -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 );
Expand Down Expand Up @@ -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

};


Expand Down
84 changes: 3 additions & 81 deletions python/plugins/processing/gui/AlgorithmExecutor.py
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)])
Expand Down
107 changes: 106 additions & 1 deletion src/core/qgsvectorlayerutils.cpp
Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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;
Expand Down
43 changes: 42 additions & 1 deletion src/core/qgsvectorlayerutils.h
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );

};


Expand Down

0 comments on commit e8c98f8

Please sign in to comment.