QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 
23 QString QgsMergeVectorAlgorithm::name() const
24 {
25  return QStringLiteral( "mergevectorlayers" );
26 }
27 
28 QString QgsMergeVectorAlgorithm::displayName() const
29 {
30  return QObject::tr( "Merge vector layers" );
31 }
32 
33 QStringList QgsMergeVectorAlgorithm::tags() const
34 {
35  return QObject::tr( "vector,layers,collect,merge,combine" ).split( ',' );
36 }
37 
38 QString QgsMergeVectorAlgorithm::group() const
39 {
40  return QObject::tr( "Vector general" );
41 }
42 
43 QString QgsMergeVectorAlgorithm::groupId() const
44 {
45  return QStringLiteral( "vectorgeneral" );
46 }
47 
48 void QgsMergeVectorAlgorithm::initAlgorithm( const QVariantMap & )
49 {
50  addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
51  addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS" ), QObject::tr( "Destination CRS" ), QVariant(), true ) );
52  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Merged" ) ) );
53 }
54 
55 QString QgsMergeVectorAlgorithm::shortDescription() const
56 {
57  return QObject::tr( "Combines multiple vector layers of the same geometry type into a single one." );
58 }
59 
60 QString 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 
72 QgsMergeVectorAlgorithm *QgsMergeVectorAlgorithm::createInstance() const
73 {
74  return new QgsMergeVectorAlgorithm();
75 }
76 
77 QVariantMap 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() != QgsMapLayerType::VectorLayer )
108  throw QgsProcessingException( QObject::tr( "All layers must be vector layers!" ) );
109 
110  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
111 
112  if ( !outputCrs.isValid() && vl->crs().isValid() )
113  {
114  outputCrs = vl->crs();
115  feedback->pushInfo( QObject::tr( "Taking destination CRS %1 from layer" ).arg( outputCrs.authid() ) );
116  }
117 
118  // check wkb type
119  if ( outputType != QgsWkbTypes::Unknown && outputType != QgsWkbTypes::NoGeometry )
120  {
121  if ( QgsWkbTypes::geometryType( outputType ) != QgsWkbTypes::geometryType( vl->wkbType() ) )
122  throw QgsProcessingException( QObject::tr( "All layers must have same geometry type! Encountered a %1 layer when expecting a %2 layer." )
125 
126  if ( QgsWkbTypes::hasM( vl->wkbType() ) && !QgsWkbTypes::hasM( outputType ) )
127  {
128  outputType = QgsWkbTypes::addM( outputType );
129  feedback->pushInfo( QObject::tr( "Found a layer with M values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
130  }
131  if ( QgsWkbTypes::hasZ( vl->wkbType() ) && !QgsWkbTypes::hasZ( outputType ) )
132  {
133  outputType = QgsWkbTypes::addZ( outputType );
134  feedback->pushInfo( QObject::tr( "Found a layer with Z values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
135  }
136  if ( QgsWkbTypes::isMultiType( vl->wkbType() ) && !QgsWkbTypes::isMultiType( outputType ) )
137  {
138  outputType = QgsWkbTypes::multiType( outputType );
139  feedback->pushInfo( QObject::tr( "Found a layer with multiparts, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
140  }
141  }
142  else
143  {
144  outputType = vl->wkbType();
145  feedback->pushInfo( QObject::tr( "Setting output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
146  }
147 
148  totalFeatureCount += vl->featureCount();
149 
150  // check field type
151  for ( const QgsField &sourceField : vl->fields() )
152  {
153  bool found = false;
154  for ( QgsField &destField : outputFields )
155  {
156  if ( destField.name().compare( sourceField.name(), Qt::CaseInsensitive ) == 0 )
157  {
158  found = true;
159  if ( destField.type() != sourceField.type() )
160  {
161  feedback->pushWarning( QObject::tr( "%1 field in layer %2 has different data type than the destination layer (%3 instead of %4). "
162  "%1 field will be converted to string type." )
163  .arg( sourceField.name(), vl->name(), sourceField.typeName(), destField.typeName() ) );
164  destField.setType( QVariant::String );
165  destField.setSubType( QVariant::Invalid );
166  destField.setLength( 0 );
167  destField.setPrecision( 0 );
168  }
169  break;
170  }
171  }
172 
173  if ( !found )
174  outputFields.append( sourceField );
175  }
176  }
177 
178  bool addLayerField = false;
179  if ( outputFields.lookupField( QStringLiteral( "layer" ) ) < 0 )
180  {
181  outputFields.append( QgsField( QStringLiteral( "layer" ), QVariant::String, QString() ) );
182  addLayerField = true;
183  }
184  bool addPathField = false;
185  if ( outputFields.lookupField( QStringLiteral( "path" ) ) < 0 )
186  {
187  outputFields.append( QgsField( QStringLiteral( "path" ), QVariant::String, QString() ) );
188  addPathField = true;
189  }
190 
191  QString dest;
192  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs, QgsFeatureSink::RegeneratePrimaryKey ) );
193  if ( !sink )
194  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
195 
196  const bool hasZ = QgsWkbTypes::hasZ( outputType );
197  const bool hasM = QgsWkbTypes::hasM( outputType );
198  const bool isMulti = QgsWkbTypes::isMultiType( outputType );
199  const double step = totalFeatureCount > 0 ? 100.0 / totalFeatureCount : 1;
200  i = 0;
201  int layerNumber = 0;
202  for ( QgsMapLayer *layer : layers )
203  {
204  layerNumber++;
205  if ( !layer )
206  continue;
207 
208  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
209  if ( !vl )
210  continue;
211 
212  feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( layerNumber ).arg( layers.count() ).arg( layer->name() ) );
213 
214  QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs, context.transformContext() ) );
215  QgsFeature f;
216  while ( it.nextFeature( f ) )
217  {
218  if ( feedback->isCanceled() )
219  break;
220 
221  // ensure feature geometry is of correct type
222  if ( f.hasGeometry() )
223  {
224  bool changed = false;
225  QgsGeometry g = f.geometry();
226  if ( hasZ && !g.constGet()->is3D() )
227  {
228  g.get()->addZValue( 0 );
229  changed = true;
230  }
231  if ( hasM && !g.constGet()->isMeasure() )
232  {
233  g.get()->addMValue( 0 );
234  changed = true;
235  }
236  if ( isMulti && !g.isMultipart() )
237  {
238  g.convertToMultiType();
239  changed = true;
240  }
241  if ( changed )
242  f.setGeometry( g );
243  }
244 
245  // process feature attributes
246  QgsAttributes destAttributes;
247  for ( const QgsField &destField : outputFields )
248  {
249  if ( addLayerField && destField.name() == QLatin1String( "layer" ) )
250  {
251  destAttributes.append( layer->name() );
252  continue;
253  }
254  else if ( addPathField && destField.name() == QLatin1String( "path" ) )
255  {
256  destAttributes.append( layer->publicSource() );
257  continue;
258  }
259 
260  QVariant destAttribute;
261  const int sourceIndex = vl->fields().lookupField( destField.name() );
262  if ( sourceIndex >= 0 )
263  {
264  destAttribute = f.attributes().at( sourceIndex );
265  }
266  destAttributes.append( destAttribute );
267  }
268  f.setAttributes( destAttributes );
269 
270  if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
271  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
272  i += 1;
273  feedback->setProgress( i * step );
274  }
275  }
276 
277  if ( errored )
278  throw QgsProcessingException( QObject::tr( "Error obtained while merging one or more layers." ) );
279 
280  QVariantMap outputs;
281  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
282  return outputs;
283 }
284 
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
bool is3D() const SIP_HOLDGIL
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.
bool isMeasure() const SIP_HOLDGIL
Returns true if the geometry contains m values.
A vector of attributes.
Definition: qgsattributes.h:58
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString authid() const
Returns the authority identifier for the CRS.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
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:153
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:223
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
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:344
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
bool isMultipart() const SIP_HOLDGIL
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:73
QString name
Definition: qgsmaplayer.h:76
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
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.
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
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.
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:968
static QString geometryDisplayString(GeometryType type) SIP_HOLDGIL
Returns a display string for a geometry type.
static bool isMultiType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:862
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1130
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Type multiType(Type type) SIP_HOLDGIL
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:304
static Type addZ(Type type) SIP_HOLDGIL
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1176
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1080
static Type addM(Type type) SIP_HOLDGIL
Adds the m dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1201
const QgsCoordinateReferenceSystem & outputCrs