Skip to content

Commit a9ed83f

Browse files
committedJan 15, 2020
[FEATURE][processing] New algorithm "Rename table field"
Takes an input layer, existing field and a new name for the field, and outputs a new layer with the selected field renamed. While this result could also be achieved with the Refactor Fields algorithm, Refactor Fields isn't particularly model friendly. It relies on a constant, fixed table structure, and can't adapt to input tables with different field structures. In constrast, this simple Rename Field algorithm adapts nicely for model use, because it operates on a single field only and leaves all the other fields untouched.
1 parent d3b9402 commit a9ed83f

File tree

8 files changed

+354
-1
lines changed

8 files changed

+354
-1
lines changed
 
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ renamed_field.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
10+
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:renamed_field fid="points.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:id>1</ogr:id>
18+
<ogr:id_renamed>2</ogr:id_renamed>
19+
</ogr:renamed_field>
20+
</gml:featureMember>
21+
<gml:featureMember>
22+
<ogr:renamed_field fid="points.1">
23+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
24+
<ogr:id>2</ogr:id>
25+
<ogr:id_renamed>1</ogr:id_renamed>
26+
</ogr:renamed_field>
27+
</gml:featureMember>
28+
<gml:featureMember>
29+
<ogr:renamed_field fid="points.2">
30+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
31+
<ogr:id>3</ogr:id>
32+
<ogr:id_renamed>0</ogr:id_renamed>
33+
</ogr:renamed_field>
34+
</gml:featureMember>
35+
<gml:featureMember>
36+
<ogr:renamed_field fid="points.3">
37+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
38+
<ogr:id>4</ogr:id>
39+
<ogr:id_renamed>2</ogr:id_renamed>
40+
</ogr:renamed_field>
41+
</gml:featureMember>
42+
<gml:featureMember>
43+
<ogr:renamed_field fid="points.4">
44+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
45+
<ogr:id>5</ogr:id>
46+
<ogr:id_renamed>1</ogr:id_renamed>
47+
</ogr:renamed_field>
48+
</gml:featureMember>
49+
<gml:featureMember>
50+
<ogr:renamed_field fid="points.5">
51+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
52+
<ogr:id>6</ogr:id>
53+
<ogr:id_renamed>0</ogr:id_renamed>
54+
</ogr:renamed_field>
55+
</gml:featureMember>
56+
<gml:featureMember>
57+
<ogr:renamed_field fid="points.6">
58+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
59+
<ogr:id>7</ogr:id>
60+
<ogr:id_renamed>0</ogr:id_renamed>
61+
</ogr:renamed_field>
62+
</gml:featureMember>
63+
<gml:featureMember>
64+
<ogr:renamed_field fid="points.7">
65+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
66+
<ogr:id>8</ogr:id>
67+
<ogr:id_renamed>0</ogr:id_renamed>
68+
</ogr:renamed_field>
69+
</gml:featureMember>
70+
<gml:featureMember>
71+
<ogr:renamed_field fid="points.8">
72+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
73+
<ogr:id>9</ogr:id>
74+
<ogr:id_renamed>0</ogr:id_renamed>
75+
</ogr:renamed_field>
76+
</gml:featureMember>
77+
</ogr:FeatureCollection>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="renamed_field" type="ogr:renamed_field_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="renamed_field_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="id" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:integer">
22+
<xs:totalDigits value="10"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
<xs:element name="id_renamed" nillable="true" minOccurs="0" maxOccurs="1">
27+
<xs:simpleType>
28+
<xs:restriction base="xs:integer">
29+
<xs:totalDigits value="10"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
</xs:element>
33+
</xs:sequence>
34+
</xs:extension>
35+
</xs:complexContent>
36+
</xs:complexType>
37+
</xs:schema>

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests4.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2326,5 +2326,18 @@ tests:
23262326
name: expected/affine_transform.gml
23272327
type: vector
23282328

2329+
- algorithm: native:renametablefield
2330+
name: Rename table field
2331+
params:
2332+
FIELD: id2
2333+
INPUT:
2334+
name: points.gml|layername=points
2335+
type: vector
2336+
NEW_NAME: id_renamed
2337+
results:
2338+
OUTPUT:
2339+
name: expected/renamed_field.gml
2340+
type: vector
2341+
23292342

23302343
# See ../README.md for a description of the file format

‎src/analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ SET(QGIS_ANALYSIS_SRCS
120120
processing/qgsalgorithmremoveholes.cpp
121121
processing/qgsalgorithmremovenullgeometry.cpp
122122
processing/qgsalgorithmrenamelayer.cpp
123+
processing/qgsalgorithmrenametablefield.cpp
123124
processing/qgsalgorithmrepairshapefile.cpp
124125
processing/qgsalgorithmreverselinedirection.cpp
125126
processing/qgsalgorithmrotate.cpp
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/***************************************************************************
2+
qgsalgorithmrenametablefield.cpp
3+
-----------------------------------
4+
begin : January 2020
5+
copyright : (C) 2020 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsalgorithmrenametablefield.h"
19+
20+
///@cond PRIVATE
21+
22+
QString QgsRenameTableFieldAlgorithm::name() const
23+
{
24+
return QStringLiteral( "renametablefield" );
25+
}
26+
27+
QString QgsRenameTableFieldAlgorithm::displayName() const
28+
{
29+
return QObject::tr( "Rename field" );
30+
}
31+
32+
QString QgsRenameTableFieldAlgorithm::shortHelpString() const
33+
{
34+
return QObject::tr( "This algorithm renames an existing field from a vector layer." );
35+
}
36+
37+
QString QgsRenameTableFieldAlgorithm::shortDescription() const
38+
{
39+
return QObject::tr( "Renames an existing field from a vector layer." );
40+
}
41+
42+
QStringList QgsRenameTableFieldAlgorithm::tags() const
43+
{
44+
return QObject::tr( "rename,attribute,fields,table,change" ).split( ',' );
45+
}
46+
47+
QString QgsRenameTableFieldAlgorithm::group() const
48+
{
49+
return QObject::tr( "Vector table" );
50+
}
51+
52+
QString QgsRenameTableFieldAlgorithm::groupId() const
53+
{
54+
return QStringLiteral( "vectortable" );
55+
}
56+
57+
QString QgsRenameTableFieldAlgorithm::outputName() const
58+
{
59+
return QObject::tr( "Renamed" );
60+
}
61+
62+
QList<int> QgsRenameTableFieldAlgorithm::inputLayerTypes() const
63+
{
64+
return QList<int>() << QgsProcessing::TypeVector;
65+
}
66+
67+
QgsProcessingFeatureSource::Flag QgsRenameTableFieldAlgorithm::sourceFlags() const
68+
{
69+
return QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks;
70+
}
71+
72+
QgsRenameTableFieldAlgorithm *QgsRenameTableFieldAlgorithm::createInstance() const
73+
{
74+
return new QgsRenameTableFieldAlgorithm();
75+
}
76+
77+
void QgsRenameTableFieldAlgorithm::initParameters( const QVariantMap & )
78+
{
79+
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Field to rename" ), QVariant(), QStringLiteral( "INPUT" ) ) );
80+
addParameter( new QgsProcessingParameterString( QStringLiteral( "NEW_NAME" ), QObject::tr( "New field name" ) ) );
81+
}
82+
83+
QgsFields QgsRenameTableFieldAlgorithm::outputFields( const QgsFields &inputFields ) const
84+
{
85+
QgsFields outFields = inputFields;
86+
const int index = outFields.lookupField( mOriginalName );
87+
if ( index < 0 )
88+
throw QgsProcessingException( QObject::tr( "Field %1 could not be found in input table" ).arg( mOriginalName ) );
89+
90+
if ( outFields.lookupField( mNewName ) >= 0 )
91+
throw QgsProcessingException( QObject::tr( "A field already exists with the name %1" ).arg( mNewName ) );
92+
93+
outFields[ index ].setName( mNewName );
94+
return outFields;
95+
}
96+
97+
bool QgsRenameTableFieldAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
98+
{
99+
mOriginalName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
100+
mNewName = parameterAsString( parameters, QStringLiteral( "NEW_NAME" ), context );
101+
return true;
102+
}
103+
104+
QgsFeatureList QgsRenameTableFieldAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback * )
105+
{
106+
return QgsFeatureList() << feature;
107+
}
108+
109+
bool QgsRenameTableFieldAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
110+
{
111+
Q_UNUSED( layer )
112+
return false;
113+
}
114+
115+
///@endcond
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/***************************************************************************
2+
qgsalgorithmrenametablefield.h
3+
---------------------
4+
begin : January 2020
5+
copyright : (C) 2020 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSALGORITHMRENAMETABLEFIELD_H
19+
#define QGSALGORITHMRENAMETABLEFIELD_H
20+
21+
#define SIP_NO_FILE
22+
23+
#include "qgis_sip.h"
24+
#include "qgsprocessingalgorithm.h"
25+
26+
///@cond PRIVATE
27+
28+
/**
29+
* Native add table field algorithm.
30+
*/
31+
class QgsRenameTableFieldAlgorithm : public QgsProcessingFeatureBasedAlgorithm
32+
{
33+
34+
public:
35+
36+
QgsRenameTableFieldAlgorithm() = default;
37+
QString name() const override;
38+
QString displayName() const override;
39+
QStringList tags() const override;
40+
QString group() const override;
41+
QString groupId() const override;
42+
QString shortHelpString() const override;
43+
QString shortDescription() const override;
44+
QList<int> inputLayerTypes() const override;
45+
QgsRenameTableFieldAlgorithm *createInstance() const override SIP_FACTORY;
46+
47+
protected:
48+
49+
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
50+
QString outputName() const override;
51+
QgsFields outputFields( const QgsFields &inputFields ) const override;
52+
QgsProcessingFeatureSource::Flag sourceFlags() const override;
53+
54+
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
55+
QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
56+
bool supportInPlaceEdit( const QgsMapLayer *layer ) const override;
57+
58+
private:
59+
60+
QString mOriginalName;
61+
QString mNewName;
62+
};
63+
64+
///@endcond PRIVATE
65+
66+
#endif // QGSALGORITHMRENAMETABLEFIELD_H

‎src/analysis/processing/qgsnativealgorithms.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
#include "qgsalgorithmremoveholes.h"
115115
#include "qgsalgorithmremovenullgeometry.h"
116116
#include "qgsalgorithmrenamelayer.h"
117+
#include "qgsalgorithmrenametablefield.h"
117118
#include "qgsalgorithmrepairshapefile.h"
118119
#include "qgsalgorithmreverselinedirection.h"
119120
#include "qgsalgorithmrotate.h"
@@ -309,6 +310,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
309310
addAlgorithm( new QgsRemoveHolesAlgorithm() );
310311
addAlgorithm( new QgsRemoveNullGeometryAlgorithm() );
311312
addAlgorithm( new QgsRenameLayerAlgorithm() );
313+
addAlgorithm( new QgsRenameTableFieldAlgorithm() );
312314
addAlgorithm( new QgsRepairShapefileAlgorithm() );
313315
addAlgorithm( new QgsReverseLineDirectionAlgorithm() );
314316
addAlgorithm( new QgsRotateFeaturesAlgorithm() );

‎tests/src/analysis/testqgsprocessingalgs.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class TestQgsProcessingAlgs: public QObject
100100
void layerToBookmarks();
101101

102102
void repairShapefile();
103+
void renameField();
103104

104105
private:
105106

@@ -1034,7 +1035,7 @@ void TestQgsProcessingAlgs::lineDensity()
10341035
{
10351036
double expectedValue = expectedRasterBlock->value( row, column );
10361037
double outputValue = outputRasterBlock->value( row, column );
1037-
QCOMPARE( outputValue, expectedValue );
1038+
QGSCOMPARENEAR( outputValue, expectedValue, 0.00000000001 );
10381039
}
10391040
}
10401041
}
@@ -1821,5 +1822,46 @@ void TestQgsProcessingAlgs::repairShapefile()
18211822
QVERIFY( layer->isValid() );
18221823
}
18231824

1825+
void TestQgsProcessingAlgs::renameField()
1826+
{
1827+
QgsProject p;
1828+
QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
1829+
QVERIFY( layer->isValid() );
1830+
p.addMapLayer( layer );
1831+
1832+
std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:renametablefield" ) ) );
1833+
QVERIFY( alg != nullptr );
1834+
1835+
QVariantMap parameters;
1836+
parameters.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( layer ) );
1837+
parameters.insert( QStringLiteral( "FIELD" ), QStringLiteral( "doesntexist" ) );
1838+
parameters.insert( QStringLiteral( "NEW_NAME" ), QStringLiteral( "newname" ) );
1839+
1840+
bool ok = false;
1841+
QgsProcessingFeedback feedback;
1842+
std::unique_ptr< QgsProcessingContext > context = qgis::make_unique< QgsProcessingContext >();
1843+
1844+
QVariantMap results;
1845+
results = alg->run( parameters, *context, &feedback, &ok );
1846+
// field doesn't exist
1847+
QVERIFY( !ok );
1848+
1849+
parameters.insert( QStringLiteral( "FIELD" ), QStringLiteral( "col1" ) );
1850+
parameters.insert( QStringLiteral( "NEW_NAME" ), QStringLiteral( "pk" ) );
1851+
1852+
ok = false;
1853+
results = alg->run( parameters, *context, &feedback, &ok );
1854+
// already a field with this name
1855+
QVERIFY( !ok );
1856+
1857+
parameters.insert( QStringLiteral( "NEW_NAME" ), QStringLiteral( "newname" ) );
1858+
parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
1859+
results = alg->run( parameters, *context, &feedback, &ok );
1860+
QVERIFY( ok );
1861+
1862+
QCOMPARE( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) )->fields().at( 1 ).name(), QStringLiteral( "newname" ) );
1863+
1864+
}
1865+
18241866
QGSTEST_MAIN( TestQgsProcessingAlgs )
18251867
#include "testqgsprocessingalgs.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.