1 /***************************************************************************
2 qgsalgorithmexplodehstore.h
3 ---------------------
4 begin : September 2018
5 copyright : (C) 2018 by Etienne Trimaille
6 email : etienne dot trimaille 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 "qgis.h"
19 #include "qgsalgorithmexplodehstore.h"
20 #include "qgshstoreutils.h"
21 #include "qgsprocessingutils.h"
22
23 ///@cond PRIVATE
24
name() const25 QString QgsExplodeHstoreAlgorithm::name() const
26 {
27 return QStringLiteral( "explodehstorefield" );
28 }
29
displayName() const30 QString QgsExplodeHstoreAlgorithm::displayName() const
31 {
32 return QObject::tr( "Explode HStore Field" );
33 }
34
tags() const35 QStringList QgsExplodeHstoreAlgorithm::tags() const
36 {
37 return QObject::tr( "field,explode,hstore,osm,openstreetmap" ).split( ',' );
38 }
39
group() const40 QString QgsExplodeHstoreAlgorithm::group() const
41 {
42 return QObject::tr( "Vector table" );
43 }
44
groupId() const45 QString QgsExplodeHstoreAlgorithm::groupId() const
46 {
47 return QStringLiteral( "vectortable" );
48 }
49
shortHelpString() const50 QString QgsExplodeHstoreAlgorithm::shortHelpString() const
51 {
52 return QObject::tr( "This algorithm creates a copy of the input layer and adds a new field for every unique key in the HStore field.\n"
53 "The expected field list is an optional comma separated list. By default, all unique keys are added. If this list is specified, only these fields are added and the HStore field is updated." );
54 }
55
createInstance() const56 QgsProcessingAlgorithm *QgsExplodeHstoreAlgorithm::createInstance() const
57 {
58 return new QgsExplodeHstoreAlgorithm();
59 }
60
initAlgorithm(const QVariantMap &)61 void QgsExplodeHstoreAlgorithm::initAlgorithm( const QVariantMap & )
62 {
63 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
64 QObject::tr( "Input layer" ) ) );
65 addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ),
66 QObject::tr( "HStore field" ), QVariant(), QStringLiteral( "INPUT" ) ) );
67 addParameter( new QgsProcessingParameterString( QStringLiteral( "EXPECTED_FIELDS" ), QObject::tr( "Expected list of fields separated by a comma" ), QVariant(), false, true ) );
68 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Exploded" ) ) );
69 }
70
processAlgorithm(const QVariantMap & parameters,QgsProcessingContext & context,QgsProcessingFeedback * feedback)71 QVariantMap QgsExplodeHstoreAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
72 {
73 std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
74 if ( !source )
75 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
76 int attrSourceCount = source->fields().count();
77
78 QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
79 int fieldIndex = source->fields().lookupField( fieldName );
80 if ( fieldIndex < 0 )
81 throw QgsProcessingException( QObject::tr( "Invalid HStore field" ) );
82
83 QStringList expectedFields;
84 QString fieldList = parameterAsString( parameters, QStringLiteral( "EXPECTED_FIELDS" ), context );
85 if ( ! fieldList.trimmed().isEmpty() )
86 {
87 expectedFields = fieldList.split( ',' );
88 }
89
90 QList<QString> fieldsToAdd;
91 QHash<QgsFeatureId, QVariantMap> hstoreFeatures;
92 QList<QgsFeature> features;
93
94 double step = source->featureCount() > 0 ? 50.0 / source->featureCount() : 1;
95 int i = 0;
96 QgsFeatureIterator featIterator = source->getFeatures( );
97 QgsFeature feat;
98 while ( featIterator.nextFeature( feat ) )
99 {
100 i++;
101 if ( feedback->isCanceled() )
102 break;
103
104 double progress = i * step;
105 if ( progress >= 50 )
106 feedback->setProgress( 50.0 );
107 else
108 feedback->setProgress( progress );
109
110 QVariantMap currentHStore = QgsHstoreUtils::parse( feat.attribute( fieldName ).toString() );
111 for ( const QString &key : currentHStore.keys() )
112 {
113 if ( expectedFields.isEmpty() && ! fieldsToAdd.contains( key ) )
114 fieldsToAdd.insert( 0, key );
115 }
116 hstoreFeatures.insert( feat.id(), currentHStore );
117 features.append( feat );
118 }
119
120 if ( ! expectedFields.isEmpty() )
121 {
122 fieldsToAdd = expectedFields;
123 }
124
125 QgsFields hstoreFields;
126 for ( const QString &fieldName : fieldsToAdd )
127 {
128 hstoreFields.append( QgsField( fieldName, QVariant::String ) );
129 }
130
131 QgsFields outFields = QgsProcessingUtils::combineFields( source->fields(), hstoreFields );
132
133 QString sinkId;
134 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, sinkId, outFields, source->wkbType(), source->sourceCrs() ) );
135 if ( !sink )
136 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
137
138 QList<int> fieldIndicesInput = QgsProcessingUtils::fieldNamesToIndices( QStringList(), source->fields() );
139 int attrCount = attrSourceCount + fieldsToAdd.count();
140 QgsFeature outFeature;
141 step = !features.empty() ? 50.0 / features.count() : 1;
142 i = 0;
143 for ( const QgsFeature &feat : std::as_const( features ) )
144 {
145 i++;
146 if ( feedback->isCanceled() )
147 break;
148
149 feedback->setProgress( i * step + 50.0 );
150
151 QgsAttributes outAttributes( attrCount );
152
153 const QgsAttributes attrs( feat.attributes() );
154 for ( int i = 0; i < fieldIndicesInput.count(); ++i )
155 outAttributes[i] = attrs[fieldIndicesInput[i]];
156
157 QVariantMap currentHStore = hstoreFeatures.take( feat.id() );
158
159 QString current;
160 for ( int i = 0; i < fieldsToAdd.count(); ++i )
161 {
162 current = fieldsToAdd.at( i );
163 if ( currentHStore.contains( current ) )
164 {
165 outAttributes[attrSourceCount + i] = currentHStore.take( current );
166 }
167 }
168
169 if ( ! expectedFields.isEmpty() )
170 {
171 outAttributes[fieldIndex] = QgsHstoreUtils::build( currentHStore );
172 }
173
174 outFeature.setGeometry( QgsGeometry( feat.geometry() ) );
175 outFeature.setAttributes( outAttributes );
176 if ( !sink->addFeature( outFeature, QgsFeatureSink::FastInsert ) )
177 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
178 }
179
180 QVariantMap outputs;
181 outputs.insert( QStringLiteral( "OUTPUT" ), sinkId );
182 return outputs;
183 }
184
185 ///@endcond
186