QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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.
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:349
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
@ VectorLayer
Vector layer.
const QgsCoordinateReferenceSystem & outputCrs