QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  }
168  break;
169  }
170  }
171 
172  if ( !found )
173  outputFields.append( sourceField );
174  }
175  }
176 
177  bool addLayerField = false;
178  if ( outputFields.lookupField( QStringLiteral( "layer" ) ) < 0 )
179  {
180  outputFields.append( QgsField( QStringLiteral( "layer" ), QVariant::String, QString() ) );
181  addLayerField = true;
182  }
183  bool addPathField = false;
184  if ( outputFields.lookupField( QStringLiteral( "path" ) ) < 0 )
185  {
186  outputFields.append( QgsField( QStringLiteral( "path" ), QVariant::String, QString() ) );
187  addPathField = true;
188  }
189 
190  QString dest;
191  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs, QgsFeatureSink::RegeneratePrimaryKey ) );
192  if ( !sink )
193  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
194 
195  bool hasZ = QgsWkbTypes::hasZ( outputType );
196  bool hasM = QgsWkbTypes::hasM( outputType );
197  bool isMulti = QgsWkbTypes::isMultiType( outputType );
198  double step = totalFeatureCount > 0 ? 100.0 / totalFeatureCount : 1;
199  i = 0;
200  int layerNumber = 0;
201  for ( QgsMapLayer *layer : layers )
202  {
203  layerNumber++;
204  if ( !layer )
205  continue;
206 
207  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
208  if ( !vl )
209  continue;
210 
211  feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( layerNumber ).arg( layers.count() ).arg( layer->name() ) );
212 
213  QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs, context.transformContext() ) );
214  QgsFeature f;
215  while ( it.nextFeature( f ) )
216  {
217  if ( feedback->isCanceled() )
218  break;
219 
220  // ensure feature geometry is of correct type
221  if ( f.hasGeometry() )
222  {
223  bool changed = false;
224  QgsGeometry g = f.geometry();
225  if ( hasZ && !g.constGet()->is3D() )
226  {
227  g.get()->addZValue( 0 );
228  changed = true;
229  }
230  if ( hasM && !g.constGet()->isMeasure() )
231  {
232  g.get()->addMValue( 0 );
233  changed = true;
234  }
235  if ( isMulti && !g.isMultipart() )
236  {
237  g.convertToMultiType();
238  changed = true;
239  }
240  if ( changed )
241  f.setGeometry( g );
242  }
243 
244  // process feature attributes
245  QgsAttributes destAttributes;
246  for ( const QgsField &destField : outputFields )
247  {
248  if ( addLayerField && destField.name() == QLatin1String( "layer" ) )
249  {
250  destAttributes.append( layer->name() );
251  continue;
252  }
253  else if ( addPathField && destField.name() == QLatin1String( "path" ) )
254  {
255  destAttributes.append( layer->publicSource() );
256  continue;
257  }
258 
259  QVariant destAttribute;
260  int sourceIndex = vl->fields().lookupField( destField.name() );
261  if ( sourceIndex >= 0 )
262  {
263  destAttribute = f.attributes().at( sourceIndex );
264  }
265  destAttributes.append( destAttribute );
266  }
267  f.setAttributes( destAttributes );
268 
269  sink->addFeature( f, QgsFeatureSink::FastInsert );
270  i += 1;
271  feedback->setProgress( i * step );
272  }
273  }
274 
275  if ( errored )
276  throw QgsProcessingException( QObject::tr( "Error obtained while merging one or more layers." ) );
277 
278  QVariantMap outputs;
279  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
280  return outputs;
281 }
282 
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:135
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:205
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:145
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:124
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:70
QString name
Definition: qgsmaplayer.h:73
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:76
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:938
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:832
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1100
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:302
static Type addZ(Type type) SIP_HOLDGIL
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1146
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1050
static Type addM(Type type) SIP_HOLDGIL
Adds the m dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1171
const QgsCoordinateReferenceSystem & outputCrs