Skip to content

Commit

Permalink
Port processing combineFields to c++
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Sep 22, 2017
1 parent cf636dc commit c41dca9
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 31 deletions.
9 changes: 9 additions & 0 deletions python/core/processing/qgsprocessingutils.sip
Expand Up @@ -193,6 +193,15 @@ class QgsProcessingUtils
:rtype: str
%End

static QgsFields combineFields( const QgsFields &fieldsA, const QgsFields &fieldsB );
%Docstring
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).

Duplicate field names will be altered to "name_2", "name_3", etc, finding the first
non-duplicate name.
:rtype: QgsFields
%End

};

class QgsProcessingFeatureSource : QgsFeatureSource
Expand Down
5 changes: 3 additions & 2 deletions python/plugins/processing/algs/qgis/HubLines.py
Expand Up @@ -37,7 +37,8 @@
QgsProcessingParameterField,
QgsProcessingParameterFeatureSink,
QgsProcessingException,
QgsExpression)
QgsExpression,
QgsProcessingUtils)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.tools import vector

Expand Down Expand Up @@ -89,7 +90,7 @@ def processAlgorithm(self, parameters, context, feedback):
field_spoke = self.parameterAsString(parameters, self.SPOKE_FIELD, context)
field_spoke_index = hub_source.fields().lookupField(field_spoke)

fields = vector.combineFields(hub_source.fields(), spoke_source.fields())
fields = QgsProcessingUtils.combineFields(hub_source.fields(), spoke_source.fields())

(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.LineString, hub_source.sourceCrs())
Expand Down
2 changes: 1 addition & 1 deletion python/plugins/processing/algs/qgis/JoinAttributes.py
Expand Up @@ -82,7 +82,7 @@ def processAlgorithm(self, parameters, context, feedback):
joinField1Index = input.fields().lookupField(field)
joinField2Index = input2.fields().lookupField(field2)

outFields = vector.combineFields(input.fields(), input2.fields())
outFields = QgsProcessingUtils.combineFields(input.fields(), input2.fields())

(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
outFields, input.wkbType(), input.sourceCrs())
Expand Down
3 changes: 2 additions & 1 deletion python/plugins/processing/algs/qgis/SpatialJoin.py
Expand Up @@ -37,6 +37,7 @@
QgsFeatureRequest,
QgsGeometry,
QgsProcessing,
QgsProcessingUtils,
QgsProcessingParameterBoolean,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterEnum,
Expand Down Expand Up @@ -148,7 +149,7 @@ def processAlgorithm(self, parameters, context, feedback):
if idx >= 0:
fields_to_join.append(join_source.fields().at(idx))

out_fields = vector.combineFields(source_fields, fields_to_join)
out_fields = QgsProcessingUtils.combineFields(source_fields, fields_to_join)

(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
out_fields, source.wkbType(), source.sourceCrs())
Expand Down
3 changes: 2 additions & 1 deletion python/plugins/processing/algs/qgis/SpatialJoinSummary.py
Expand Up @@ -43,6 +43,7 @@
QgsDateTimeStatisticalSummary,
QgsStringStatisticalSummary,
QgsProcessing,
QgsProcessingUtils,
QgsProcessingParameterBoolean,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterEnum,
Expand Down Expand Up @@ -247,7 +248,7 @@ def addField(original, stat, type):
else:
addFieldKeepType(join_field, f[0])

out_fields = vector.combineFields(source_fields, fields_to_join)
out_fields = QgsProcessingUtils.combineFields(source_fields, fields_to_join)

(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
out_fields, source.wkbType(), source.sourceCrs())
Expand Down
3 changes: 2 additions & 1 deletion python/plugins/processing/algs/qgis/SymmetricalDifference.py
Expand Up @@ -36,6 +36,7 @@
NULL,
QgsWkbTypes,
QgsMessageLog,
QgsProcessingUtils,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsSpatialIndex)
Expand Down Expand Up @@ -79,7 +80,7 @@ def processAlgorithm(self, parameters, context, feedback):
sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

geomType = QgsWkbTypes.multiType(sourceA.wkbType())
fields = vector.combineFields(sourceA.fields(), sourceB.fields())
fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields())

(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, geomType, sourceA.sourceCrs())
Expand Down
4 changes: 2 additions & 2 deletions python/plugins/processing/algs/qgis/Union.py
Expand Up @@ -34,7 +34,7 @@
QgsFeatureSink,
QgsGeometry,
QgsWkbTypes,
QgsMessageLog,
QgsProcessingUtils,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsSpatialIndex)
Expand Down Expand Up @@ -79,7 +79,7 @@ def processAlgorithm(self, parameters, context, feedback):
sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

geomType = QgsWkbTypes.multiType(sourceA.wkbType())
fields = vector.combineFields(sourceA.fields(), sourceB.fields())
fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields())

(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, geomType, sourceA.sourceCrs())
Expand Down
23 changes: 0 additions & 23 deletions python/plugins/processing/tools/vector.py
Expand Up @@ -174,29 +174,6 @@ def extractPoints(geom):
return points


def combineFields(fieldsA, fieldsB):
"""Create single field map from two input field maps.
"""
fields = []
fields.extend(fieldsA)
namesA = [str(f.name()).lower() for f in fieldsA]
for field in fieldsB:
name = str(field.name()).lower()
if name in namesA:
idx = 2
newName = name + '_' + str(idx)
while newName in namesA:
idx += 1
newName = name + '_' + str(idx)
field = QgsField(newName, field.type(), field.typeName())
fields.append(field)

real_fields = QgsFields()
for f in fields:
real_fields.append(f)
return real_fields


def checkMinDistance(point, index, distance, points):
"""Check if distance from given point to all other points is greater
than given value.
Expand Down
35 changes: 35 additions & 0 deletions src/core/processing/qgsprocessingutils.cpp
Expand Up @@ -555,6 +555,41 @@ QString QgsProcessingUtils::convertToCompatibleFormat( const QgsVectorLayer *vl,
}
}

QgsFields QgsProcessingUtils::combineFields( const QgsFields &fieldsA, const QgsFields &fieldsB )
{
QgsFields outFields = fieldsA;
QSet< QString > usedNames;
for ( const QgsField &f : fieldsA )
{
usedNames.insert( f.name().toLower() );
}

for ( const QgsField &f : fieldsB )
{
if ( usedNames.contains( f.name().toLower() ) )
{
int idx = 2;
QString newName = f.name() + '_' + QString::number( idx );
while ( usedNames.contains( newName.toLower() ) )
{
idx++;
newName = f.name() + '_' + QString::number( idx );
}
QgsField newField = f;
newField.setName( newName );
outFields.append( newField );
usedNames.insert( newName.toLower() );
}
else
{
usedNames.insert( f.name().toLower() );
outFields.append( f );
}
}

return outFields;
}


//
// QgsProcessingFeatureSource
Expand Down
8 changes: 8 additions & 0 deletions src/core/processing/qgsprocessingutils.h
Expand Up @@ -224,6 +224,14 @@ class CORE_EXPORT QgsProcessingUtils
QgsProcessingContext &context,
QgsProcessingFeedback *feedback );

/**
* Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
*
* Duplicate field names will be altered to "name_2", "name_3", etc, finding the first
* non-duplicate name.
*/
static QgsFields combineFields( const QgsFields &fieldsA, const QgsFields &fieldsB );

private:

static bool canUseLayer( const QgsRasterLayer *layer );
Expand Down
36 changes: 36 additions & 0 deletions tests/src/core/testqgsprocessing.cpp
Expand Up @@ -352,6 +352,7 @@ class TestQgsProcessing: public QObject
void tempUtils();
void convertCompatible();
void create();
void combineFields();

private:

Expand Down Expand Up @@ -5443,5 +5444,40 @@ void TestQgsProcessing::create()
QCOMPARE( newInstance->provider(), &p );
}

void TestQgsProcessing::combineFields()
{
QgsFields a;
QgsFields b;
// combine empty fields
QgsFields res = QgsProcessingUtils::combineFields( a, b );
QVERIFY( res.isEmpty() );

// fields in a
a.append( QgsField( "name" ) );
res = QgsProcessingUtils::combineFields( a, b );
QCOMPARE( res.count(), 1 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
b.append( QgsField( "name" ) );
res = QgsProcessingUtils::combineFields( a, b );
QCOMPARE( res.count(), 2 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
QCOMPARE( res.at( 1 ).name(), QStringLiteral( "name_2" ) );

a.append( QgsField( "NEW" ) );
res = QgsProcessingUtils::combineFields( a, b );
QCOMPARE( res.count(), 3 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
QCOMPARE( res.at( 1 ).name(), QStringLiteral( "NEW" ) );
QCOMPARE( res.at( 2 ).name(), QStringLiteral( "name_2" ) );

b.append( QgsField( "new" ) );
res = QgsProcessingUtils::combineFields( a, b );
QCOMPARE( res.count(), 4 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
QCOMPARE( res.at( 1 ).name(), QStringLiteral( "NEW" ) );
QCOMPARE( res.at( 2 ).name(), QStringLiteral( "name_2" ) );
QCOMPARE( res.at( 3 ).name(), QStringLiteral( "new_2" ) );
}

QGSTEST_MAIN( TestQgsProcessing )
#include "testqgsprocessing.moc"

0 comments on commit c41dca9

Please sign in to comment.