QGIS API Documentation 4.3.0-Master (ffcfc20b9b4)
Loading...
Searching...
No Matches
qgsalgorithmmergevector.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmmergevector.cpp
3 ------------------
4 begin : December 2017
5 copyright : (C) 2017 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
19
21#include "qgsvectorlayer.h"
22
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
28
29QString QgsMergeVectorAlgorithm::name() const
30{
31 return u"mergevectorlayers"_s;
32}
33
34QString QgsMergeVectorAlgorithm::displayName() const
35{
36 return QObject::tr( "Merge vector layers" );
37}
38
39QStringList QgsMergeVectorAlgorithm::tags() const
40{
41 return QObject::tr( "vector,layers,collect,merge,combine" ).split( ',' );
42}
43
44QString QgsMergeVectorAlgorithm::group() const
45{
46 return QObject::tr( "Vector general" );
47}
48
49QString QgsMergeVectorAlgorithm::groupId() const
50{
51 return u"vectorgeneral"_s;
52}
53
54void QgsMergeVectorAlgorithm::initAlgorithm( const QVariantMap & )
55{
56 addParameter( new QgsProcessingParameterMultipleLayers( u"LAYERS"_s, QObject::tr( "Input layers" ), Qgis::ProcessingSourceType::Vector ) );
57 addParameter( new QgsProcessingParameterCrs( u"CRS"_s, QObject::tr( "Destination CRS" ), QVariant(), true ) );
58 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Merged" ) ) );
59
60 // new boolean parameter to add source layer information
61 addParameter( new QgsProcessingParameterBoolean( u"ADD_SOURCE_FIELDS"_s, QObject::tr( "Add source layer information (layer name and path)" ), true ) );
62}
63
64QString QgsMergeVectorAlgorithm::shortDescription() const
65{
66 return QObject::tr( "Combines multiple vector layers of the same geometry type into a single one." );
67}
68
69QString QgsMergeVectorAlgorithm::shortHelpString() const
70{
71 return QObject::tr(
72 "This algorithm combines multiple vector layers of the same geometry type into a single one.\n\n"
73 "The attribute table of the resulting layer will contain the fields from all input layers. "
74 "If fields with the same name but different types are found then the exported field will be automatically converted into a string type field. "
75 "Optionally, new fields storing the original layer name and source can be added.\n\n"
76 "If any input layers contain Z or M values, then the output layer will also contain these values. Similarly, "
77 "if any of the input layers are multi-part, the output layer will also be a multi-part layer.\n\n"
78 "Optionally, the destination coordinate reference system (CRS) for the merged layer can be set. If it is not set, the CRS will be "
79 "taken from the first input layer. All layers will all be reprojected to match this CRS."
80 );
81}
82
83Qgis::ProcessingAlgorithmDocumentationFlags QgsMergeVectorAlgorithm::documentationFlags() const
84{
86}
87
88QgsMergeVectorAlgorithm *QgsMergeVectorAlgorithm::createInstance() const
89{
90 return new QgsMergeVectorAlgorithm();
91}
92
93QVariantMap QgsMergeVectorAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
94{
95 QGS_MARK_ALGORITHM_SOURCE
96
97 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, u"LAYERS"_s, context );
98
99 const bool addSourceFields = parameterAsBool( parameters, u"ADD_SOURCE_FIELDS"_s, context );
100
101 QgsFields outputFields;
102 long totalFeatureCount = 0;
104 QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, u"CRS"_s, context );
105
106 if ( outputCrs.isValid() )
107 feedback->pushInfo( QObject::tr( "Using specified destination CRS %1" ).arg( outputCrs.authid() ) );
108
109 bool errored = false;
110
111 // loop through input layers and determine geometry type, crs, fields, total feature count,...
112 long i = 0;
113 for ( QgsMapLayer *layer : layers )
114 {
115 i++;
116
117 if ( feedback->isCanceled() )
118 break;
119
120 if ( !layer )
121 {
122 feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
123 errored = true;
124 continue;
125 }
126
127 if ( layer->type() != Qgis::LayerType::Vector )
128 throw QgsProcessingException( QObject::tr( "All layers must be vector layers!" ) );
129
130 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
131
132 const Qgis::WkbType layerWkbType = vl->wkbType();
133 const QgsCoordinateReferenceSystem layerCrs = vl->crs();
134 const QString layerName = vl->name();
135
136 if ( !outputCrs.isValid() && layerCrs.isValid() )
137 {
138 outputCrs = layerCrs;
139 feedback->pushInfo( QObject::tr( "Taking destination CRS %1 from layer" ).arg( outputCrs.authid() ) );
140 }
141
142 // check wkb type
143 if ( outputType != Qgis::WkbType::Unknown && outputType != Qgis::WkbType::NoGeometry )
144 {
145 if ( QgsWkbTypes::geometryType( outputType ) != QgsWkbTypes::geometryType( layerWkbType ) )
147 QObject::tr( "All layers must have same geometry type! Encountered a %1 layer when expecting a %2 layer." )
149 );
150
151 if ( QgsWkbTypes::hasM( layerWkbType ) && !QgsWkbTypes::hasM( outputType ) )
152 {
153 outputType = QgsWkbTypes::addM( outputType );
154 feedback->pushInfo( QObject::tr( "Found a layer with M values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
155 }
156 if ( QgsWkbTypes::hasZ( layerWkbType ) && !QgsWkbTypes::hasZ( outputType ) )
157 {
158 outputType = QgsWkbTypes::addZ( outputType );
159 feedback->pushInfo( QObject::tr( "Found a layer with Z values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
160 }
161 if ( QgsWkbTypes::isMultiType( layerWkbType ) && !QgsWkbTypes::isMultiType( outputType ) )
162 {
163 outputType = QgsWkbTypes::multiType( outputType );
164 feedback->pushInfo( QObject::tr( "Found a layer with multiparts, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
165 }
166 }
167 else
168 {
169 outputType = layerWkbType;
170 feedback->pushInfo( QObject::tr( "Setting output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
171 }
172
173 totalFeatureCount += vl->featureCount();
174
175 // check field type
176 for ( const QgsField &sourceField : vl->fields() )
177 {
178 bool found = false;
179 for ( QgsField &destField : outputFields )
180 {
181 if ( destField.name().compare( sourceField.name(), Qt::CaseInsensitive ) == 0 )
182 {
183 found = true;
184 if ( destField.type() != sourceField.type() )
185 {
186 feedback->pushWarning(
187 QObject::tr(
188 "%1 field in layer %2 has different data type than the destination layer (%3 instead of %4). "
189 "%1 field will be converted to string type."
190 )
191 .arg( sourceField.name(), layerName, sourceField.typeName(), destField.typeName() )
192 );
193 destField.setType( QMetaType::Type::QString );
194 destField.setSubType( QMetaType::Type::UnknownType );
195 destField.setLength( 0 );
196 destField.setPrecision( 0 );
197 }
198 else if ( destField.type() == QMetaType::Type::QString && destField.length() < sourceField.length() )
199 {
200 feedback->pushWarning(
201 QObject::tr(
202 "%1 field in layer %2 has different field length than the destination layer (%3 vs %4). "
203 "%1 field length will be extended to match the larger of the two."
204 )
205 .arg( sourceField.name(), layerName, QString::number( sourceField.length() ), QString::number( destField.length() ) )
206 );
207 destField.setLength( sourceField.length() );
208 }
209 else if ( destField.type() == QMetaType::Type::Double && destField.precision() < sourceField.precision() )
210 {
211 feedback->pushWarning(
212 QObject::tr(
213 "%1 field in layer %2 has different field precision than the destination layer (%3 vs %4). "
214 "%1 field precision will be extended to match the larger of the two."
215 )
216 .arg( sourceField.name(), layerName, QString::number( sourceField.length() ), QString::number( destField.length() ) )
217 );
218 destField.setPrecision( sourceField.precision() );
219 }
220 break;
221 }
222 }
223
224 if ( !found )
225 outputFields.append( sourceField );
226 }
227 }
228
229 bool addLayerField = false;
230 bool addPathField = false;
231 if ( addSourceFields ) // add source layer information
232 {
233 if ( outputFields.lookupField( u"layer"_s ) < 0 )
234 {
235 outputFields.append( QgsField( u"layer"_s, QMetaType::Type::QString, QString() ) );
236 addLayerField = true;
237 }
238
239 if ( outputFields.lookupField( u"path"_s ) < 0 )
240 {
241 outputFields.append( QgsField( u"path"_s, QMetaType::Type::QString, QString() ) );
242 addPathField = true;
243 }
244 }
245
246 QString dest;
247 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, outputFields, outputType, outputCrs, QgsFeatureSink::RegeneratePrimaryKey ) );
248 if ( !sink )
249 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
250
251 const bool hasZ = QgsWkbTypes::hasZ( outputType );
252 const bool hasM = QgsWkbTypes::hasM( outputType );
253 const bool isMulti = QgsWkbTypes::isMultiType( outputType );
254 const double step = totalFeatureCount > 0 ? 100.0 / totalFeatureCount : 1;
255 i = 0;
256 int layerNumber = 0;
257 for ( QgsMapLayer *layer : layers )
258 {
259 layerNumber++;
260 if ( !layer )
261 continue;
262
263 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
264 if ( !vl )
265 continue;
266
267 const QString layerName = layer->name();
268 const QString layerSource = layer->publicSource();
269 const QgsFields layerFields = vl->fields();
270
271 feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( layerNumber ).arg( layers.count() ).arg( layerName ) );
272
273 QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs, context.transformContext() ) );
274 QgsFeature f;
275 while ( it.nextFeature( f ) )
276 {
277 if ( feedback->isCanceled() )
278 break;
279
280 // ensure feature geometry is of correct type
281 if ( f.hasGeometry() )
282 {
283 bool changed = false;
284 QgsGeometry g = f.geometry();
285 if ( hasZ && !g.constGet()->is3D() )
286 {
287 g.get()->addZValue( 0 );
288 changed = true;
289 }
290 if ( hasM && !g.constGet()->isMeasure() )
291 {
292 g.get()->addMValue( 0 );
293 changed = true;
294 }
295 if ( isMulti && !g.isMultipart() )
296 {
298 changed = true;
299 }
300 if ( changed )
301 f.setGeometry( g );
302 }
303
304 // process feature attributes
305 QgsAttributes destAttributes;
306 for ( const QgsField &destField : outputFields )
307 {
308 if ( addLayerField && destField.name() == "layer"_L1 )
309 {
310 destAttributes.append( layerName );
311 continue;
312 }
313 else if ( addPathField && destField.name() == "path"_L1 )
314 {
315 destAttributes.append( layerSource );
316 continue;
317 }
318
319 QVariant destAttribute;
320 const int sourceIndex = layerFields.lookupField( destField.name() );
321 if ( sourceIndex >= 0 )
322 {
323 destAttribute = f.attributes().at( sourceIndex );
324 }
325 destAttributes.append( destAttribute );
326 }
327 f.setAttributes( destAttributes );
328
329 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
330 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
331 else
332 feedback->featureAddedToSink( u"OUTPUT"_s );
333 i += 1;
334 feedback->setProgress( i * step );
335 }
336 }
337
338 if ( errored )
339 throw QgsProcessingException( QObject::tr( "Error obtained while merging one or more layers." ) );
340
341 sink->finalize();
342 feedback->featureSinkFinalized( u"OUTPUT"_s );
343
344 QVariantMap outputs;
345 outputs.insert( u"OUTPUT"_s, dest );
346 return outputs;
347}
348
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3755
@ RegeneratesPrimaryKey
Algorithm always drops any existing primary keys or FID values and regenerates them in outputs.
Definition qgis.h:3836
@ Vector
Vector layer.
Definition qgis.h:207
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3847
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ NoGeometry
No geometry.
Definition qgis.h:312
@ Unknown
Unknown.
Definition qgis.h:295
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
A vector of attributes.
Represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
@ RegeneratePrimaryKey
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsAttributes attributes
Definition qgsfeature.h:64
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:66
bool hasGeometry() const
Returns true if the feature has an associated geometry.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:45
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A geometry is the spatial representation of a feature.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
A boolean parameter for processing algorithms.
A coordinate reference system parameter for processing algorithms.
A feature sink output for processing algorithms.
A parameter for processing algorithms which accepts multiple map layers.
Represents a vector layer which manages a vector based dataset.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
Q_INVOKABLE Qgis::WkbType wkbType() const final
Returns the WKBType or WKBUnknown in case of error.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Q_INVOKABLE QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Qgis::WkbType addM(Qgis::WkbType type)
Adds the m dimension to a WKB type and returns the new type.
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Q_INVOKABLE QString geometryDisplayString(Qgis::GeometryType type)
Returns a display string for a geometry type.
static Qgis::WkbType multiType(Qgis::WkbType type)
Returns the multi type for a WKB type.
static Q_INVOKABLE bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.