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 &parameters, 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