QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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#include "qgsvectorlayer.h"
20
22
23QString QgsMergeVectorAlgorithm::name() const
24{
25 return QStringLiteral( "mergevectorlayers" );
26}
27
28QString QgsMergeVectorAlgorithm::displayName() const
29{
30 return QObject::tr( "Merge vector layers" );
31}
32
33QStringList QgsMergeVectorAlgorithm::tags() const
34{
35 return QObject::tr( "vector,layers,collect,merge,combine" ).split( ',' );
36}
37
38QString QgsMergeVectorAlgorithm::group() const
39{
40 return QObject::tr( "Vector general" );
41}
42
43QString QgsMergeVectorAlgorithm::groupId() const
44{
45 return QStringLiteral( "vectorgeneral" );
46}
47
48void QgsMergeVectorAlgorithm::initAlgorithm( const QVariantMap & )
49{
50 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), Qgis::ProcessingSourceType::Vector ) );
51 addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS" ), QObject::tr( "Destination CRS" ), QVariant(), true ) );
52 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Merged" ) ) );
53}
54
55QString QgsMergeVectorAlgorithm::shortDescription() const
56{
57 return QObject::tr( "Combines multiple vector layers of the same geometry type into a single one." );
58}
59
60QString QgsMergeVectorAlgorithm::shortHelpString() const
61{
62 return QObject::tr( "This algorithm combines multiple vector layers of the same geometry type into a single one.\n\n"
63 "The attribute table of the resulting layer will contain the fields from all input layers. "
64 "If fields with the same name but different types are found then the exported field will be automatically converted into a string type field. "
65 "New fields storing the original layer name and source are also added.\n\n"
66 "If any input layers contain Z or M values, then the output layer will also contain these values. Similarly, "
67 "if any of the input layers are multi-part, the output layer will also be a multi-part layer.\n\n"
68 "Optionally, the destination coordinate reference system (CRS) for the merged layer can be set. If it is not set, the CRS will be "
69 "taken from the first input layer. All layers will all be reprojected to match this CRS." );
70}
71
72QgsMergeVectorAlgorithm *QgsMergeVectorAlgorithm::createInstance() const
73{
74 return new QgsMergeVectorAlgorithm();
75}
76
77QVariantMap QgsMergeVectorAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
78{
79 const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
80
81 QgsFields outputFields;
82 long totalFeatureCount = 0;
84 QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context );
85
86 if ( outputCrs.isValid() )
87 feedback->pushInfo( QObject::tr( "Using specified destination CRS %1" ).arg( outputCrs.authid() ) );
88
89 bool errored = false;
90
91 // loop through input layers and determine geometry type, crs, fields, total feature count,...
92 long i = 0;
93 for ( QgsMapLayer *layer : layers )
94 {
95 i++;
96
97 if ( feedback->isCanceled() )
98 break;
99
100 if ( !layer )
101 {
102 feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
103 errored = true;
104 continue;
105 }
106
107 if ( layer->type() != Qgis::LayerType::Vector )
108 throw QgsProcessingException( QObject::tr( "All layers must be vector layers!" ) );
109
110 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
111
112 const Qgis::WkbType layerWkbType = vl->wkbType();
113 const QgsCoordinateReferenceSystem layerCrs = vl->crs();
114 const QString layerName = vl->name();
115
116 if ( !outputCrs.isValid() && layerCrs.isValid() )
117 {
118 outputCrs = layerCrs;
119 feedback->pushInfo( QObject::tr( "Taking destination CRS %1 from layer" ).arg( outputCrs.authid() ) );
120 }
121
122 // check wkb type
123 if ( outputType != Qgis::WkbType::Unknown && outputType != Qgis::WkbType::NoGeometry )
124 {
125 if ( QgsWkbTypes::geometryType( outputType ) != QgsWkbTypes::geometryType( layerWkbType ) )
126 throw QgsProcessingException( QObject::tr( "All layers must have same geometry type! Encountered a %1 layer when expecting a %2 layer." )
129
130 if ( QgsWkbTypes::hasM( layerWkbType ) && !QgsWkbTypes::hasM( outputType ) )
131 {
132 outputType = QgsWkbTypes::addM( outputType );
133 feedback->pushInfo( QObject::tr( "Found a layer with M values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
134 }
135 if ( QgsWkbTypes::hasZ( layerWkbType ) && !QgsWkbTypes::hasZ( outputType ) )
136 {
137 outputType = QgsWkbTypes::addZ( outputType );
138 feedback->pushInfo( QObject::tr( "Found a layer with Z values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
139 }
140 if ( QgsWkbTypes::isMultiType( layerWkbType ) && !QgsWkbTypes::isMultiType( outputType ) )
141 {
142 outputType = QgsWkbTypes::multiType( outputType );
143 feedback->pushInfo( QObject::tr( "Found a layer with multiparts, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
144 }
145 }
146 else
147 {
148 outputType = layerWkbType;
149 feedback->pushInfo( QObject::tr( "Setting output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
150 }
151
152 totalFeatureCount += vl->featureCount();
153
154 // check field type
155 for ( const QgsField &sourceField : vl->fields() )
156 {
157 bool found = false;
158 for ( QgsField &destField : outputFields )
159 {
160 if ( destField.name().compare( sourceField.name(), Qt::CaseInsensitive ) == 0 )
161 {
162 found = true;
163 if ( destField.type() != sourceField.type() )
164 {
165 feedback->pushWarning( QObject::tr( "%1 field in layer %2 has different data type than the destination layer (%3 instead of %4). "
166 "%1 field will be converted to string type." )
167 .arg( sourceField.name(), layerName, sourceField.typeName(), destField.typeName() ) );
168 destField.setType( QVariant::String );
169 destField.setSubType( QVariant::Invalid );
170 destField.setLength( 0 );
171 destField.setPrecision( 0 );
172 }
173 break;
174 }
175 }
176
177 if ( !found )
178 outputFields.append( sourceField );
179 }
180 }
181
182 bool addLayerField = false;
183 if ( outputFields.lookupField( QStringLiteral( "layer" ) ) < 0 )
184 {
185 outputFields.append( QgsField( QStringLiteral( "layer" ), QVariant::String, QString() ) );
186 addLayerField = true;
187 }
188 bool addPathField = false;
189 if ( outputFields.lookupField( QStringLiteral( "path" ) ) < 0 )
190 {
191 outputFields.append( QgsField( QStringLiteral( "path" ), QVariant::String, QString() ) );
192 addPathField = true;
193 }
194
195 QString dest;
196 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs, QgsFeatureSink::RegeneratePrimaryKey ) );
197 if ( !sink )
198 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
199
200 const bool hasZ = QgsWkbTypes::hasZ( outputType );
201 const bool hasM = QgsWkbTypes::hasM( outputType );
202 const bool isMulti = QgsWkbTypes::isMultiType( outputType );
203 const double step = totalFeatureCount > 0 ? 100.0 / totalFeatureCount : 1;
204 i = 0;
205 int layerNumber = 0;
206 for ( QgsMapLayer *layer : layers )
207 {
208 layerNumber++;
209 if ( !layer )
210 continue;
211
212 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
213 if ( !vl )
214 continue;
215
216 const QString layerName = layer->name();
217 const QString layerSource = layer->publicSource();
218 const QgsFields layerFields = vl->fields();
219
220 feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( layerNumber ).arg( layers.count() ).arg( layerName ) );
221
222 QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs, context.transformContext() ) );
223 QgsFeature f;
224 while ( it.nextFeature( f ) )
225 {
226 if ( feedback->isCanceled() )
227 break;
228
229 // ensure feature geometry is of correct type
230 if ( f.hasGeometry() )
231 {
232 bool changed = false;
233 QgsGeometry g = f.geometry();
234 if ( hasZ && !g.constGet()->is3D() )
235 {
236 g.get()->addZValue( 0 );
237 changed = true;
238 }
239 if ( hasM && !g.constGet()->isMeasure() )
240 {
241 g.get()->addMValue( 0 );
242 changed = true;
243 }
244 if ( isMulti && !g.isMultipart() )
245 {
247 changed = true;
248 }
249 if ( changed )
250 f.setGeometry( g );
251 }
252
253 // process feature attributes
254 QgsAttributes destAttributes;
255 for ( const QgsField &destField : outputFields )
256 {
257 if ( addLayerField && destField.name() == QLatin1String( "layer" ) )
258 {
259 destAttributes.append( layerName );
260 continue;
261 }
262 else if ( addPathField && destField.name() == QLatin1String( "path" ) )
263 {
264 destAttributes.append( layerSource );
265 continue;
266 }
267
268 QVariant destAttribute;
269 const int sourceIndex = layerFields.lookupField( destField.name() );
270 if ( sourceIndex >= 0 )
271 {
272 destAttribute = f.attributes().at( sourceIndex );
273 }
274 destAttributes.append( destAttribute );
275 }
276 f.setAttributes( destAttributes );
277
278 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
279 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
280 i += 1;
281 feedback->setProgress( i * step );
282 }
283 }
284
285 if ( errored )
286 throw QgsProcessingException( QObject::tr( "Error obtained while merging one or more layers." ) );
287
288 QVariantMap outputs;
289 outputs.insert( QStringLiteral( "OUTPUT" ), dest );
290 return outputs;
291}
292
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
@ Vector
Vector layer.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:182
@ NoGeometry
No geometry.
@ Unknown
Unknown.
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.
Definition: qgsattributes.h:59
This class 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.
This class 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:56
QgsAttributes attributes
Definition: qgsfeature.h:65
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:230
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:61
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
Container of fields for a vector layer.
Definition: qgsfields.h:45
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:359
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
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:75
QString name
Definition: qgsmaplayer.h:78
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:81
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.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
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.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
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 data sets.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
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...
Definition: qgswkbtypes.h:862
static bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:758
static 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.
Definition: qgswkbtypes.h:1092
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1068
static QString geometryDisplayString(Qgis::GeometryType type)
Returns a display string for a geometry type.
static bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:973
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1023
static Qgis::WkbType multiType(Qgis::WkbType type)
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:200
const QgsCoordinateReferenceSystem & outputCrs