QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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::shortHelpString() const
56 {
57  return QObject::tr( "This algorithm combines multiple vector layers of the same geometry type into a single one.\n\n"
58  "If attributes tables are different, the attribute table of the resulting layer will contain the attributes "
59  "from all input layers. New attributes will be added for the original layer name and source.\n\n"
60  "If any input layers contain Z or M values, then the output layer will also contain these values. Similarly, "
61  "if any of the input layers are multi-part, the output layer will also be a multi-part layer.\n\n"
62  "Optionally, the destination coordinate reference system (CRS) for the merged layer can be set. If it is not set, the CRS will be "
63  "taken from the first input layer. All layers will all be reprojected to match this CRS." );
64 }
65 
66 QgsMergeVectorAlgorithm *QgsMergeVectorAlgorithm::createInstance() const
67 {
68  return new QgsMergeVectorAlgorithm();
69 }
70 
71 QVariantMap QgsMergeVectorAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
72 {
73  const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
74 
75  QgsFields outputFields;
76  long totalFeatureCount = 0;
78  QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context );
79 
80  if ( outputCrs.isValid() )
81  feedback->pushInfo( QObject::tr( "Using specified destination CRS %1" ).arg( outputCrs.authid() ) );
82 
83  bool errored = false;
84 
85  // loop through input layers and determine geometry type, crs, fields, total feature count,...
86  long i = 0;
87  for ( QgsMapLayer *layer : layers )
88  {
89  i++;
90 
91  if ( feedback->isCanceled() )
92  break;
93 
94  if ( !layer )
95  {
96  feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
97  errored = true;
98  continue;
99  }
100 
101  if ( layer->type() != QgsMapLayerType::VectorLayer )
102  throw QgsProcessingException( QObject::tr( "All layers must be vector layers!" ) );
103 
104  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
105 
106  if ( !outputCrs.isValid() && vl->crs().isValid() )
107  {
108  outputCrs = vl->crs();
109  feedback->pushInfo( QObject::tr( "Taking destination CRS %1 from layer" ).arg( outputCrs.authid() ) );
110  }
111 
112  // check wkb type
113  if ( outputType != QgsWkbTypes::Unknown && outputType != QgsWkbTypes::NoGeometry )
114  {
115  if ( QgsWkbTypes::geometryType( outputType ) != QgsWkbTypes::geometryType( vl->wkbType() ) )
116  throw QgsProcessingException( QObject::tr( "All layers must have same geometry type! Encountered a %1 layer when expecting a %2 layer." )
119 
120  if ( QgsWkbTypes::hasM( vl->wkbType() ) && !QgsWkbTypes::hasM( outputType ) )
121  {
122  outputType = QgsWkbTypes::addM( outputType );
123  feedback->pushInfo( QObject::tr( "Found a layer with M values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
124  }
125  if ( QgsWkbTypes::hasZ( vl->wkbType() ) && !QgsWkbTypes::hasZ( outputType ) )
126  {
127  outputType = QgsWkbTypes::addZ( outputType );
128  feedback->pushInfo( QObject::tr( "Found a layer with Z values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
129  }
130  if ( QgsWkbTypes::isMultiType( vl->wkbType() ) && !QgsWkbTypes::isMultiType( outputType ) )
131  {
132  outputType = QgsWkbTypes::multiType( outputType );
133  feedback->pushInfo( QObject::tr( "Found a layer with multiparts, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
134  }
135  }
136  else
137  {
138  outputType = vl->wkbType();
139  feedback->pushInfo( QObject::tr( "Setting output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
140  }
141 
142  totalFeatureCount += vl->featureCount();
143 
144  // check field type
145  for ( const QgsField &sourceField : vl->fields() )
146  {
147  bool found = false;
148  for ( const QgsField &destField : outputFields )
149  {
150  if ( destField.name().compare( sourceField.name(), Qt::CaseInsensitive ) == 0 )
151  {
152  found = true;
153  if ( destField.type() != sourceField.type() )
154  {
155  throw QgsProcessingException( QObject::tr( "%1 field in layer %2 has different data type than in other layers (%3 instead of %4)" )
156  .arg( sourceField.name(), vl->name(), sourceField.typeName(), destField.typeName() ) );
157  }
158  break;
159  }
160  }
161 
162  if ( !found )
163  outputFields.append( sourceField );
164  }
165  }
166 
167  bool addLayerField = false;
168  if ( outputFields.lookupField( QStringLiteral( "layer" ) ) < 0 )
169  {
170  outputFields.append( QgsField( QStringLiteral( "layer" ), QVariant::String, QString(), 100 ) );
171  addLayerField = true;
172  }
173  bool addPathField = false;
174  if ( outputFields.lookupField( QStringLiteral( "path" ) ) < 0 )
175  {
176  outputFields.append( QgsField( QStringLiteral( "path" ), QVariant::String, QString(), 200 ) );
177  addPathField = true;
178  }
179 
180  QString dest;
181  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs, QgsFeatureSink::RegeneratePrimaryKey ) );
182  if ( !sink )
183  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
184 
185  bool hasZ = QgsWkbTypes::hasZ( outputType );
186  bool hasM = QgsWkbTypes::hasM( outputType );
187  bool isMulti = QgsWkbTypes::isMultiType( outputType );
188  double step = totalFeatureCount > 0 ? 100.0 / totalFeatureCount : 1;
189  i = 0;
190  int layerNumber = 0;
191  for ( QgsMapLayer *layer : layers )
192  {
193  layerNumber++;
194  if ( !layer )
195  continue;
196 
197  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
198  if ( !vl )
199  continue;
200 
201  feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( layerNumber ).arg( layers.count() ).arg( layer->name() ) );
202 
203  QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs, context.transformContext() ) );
204  QgsFeature f;
205  while ( it.nextFeature( f ) )
206  {
207  if ( feedback->isCanceled() )
208  break;
209 
210  // ensure feature geometry is of correct type
211  if ( f.hasGeometry() )
212  {
213  bool changed = false;
214  QgsGeometry g = f.geometry();
215  if ( hasZ && !g.constGet()->is3D() )
216  {
217  g.get()->addZValue( 0 );
218  changed = true;
219  }
220  if ( hasM && !g.constGet()->isMeasure() )
221  {
222  g.get()->addMValue( 0 );
223  changed = true;
224  }
225  if ( isMulti && !g.isMultipart() )
226  {
227  g.convertToMultiType();
228  changed = true;
229  }
230  if ( changed )
231  f.setGeometry( g );
232  }
233 
234  // process feature attributes
235  QgsAttributes destAttributes;
236  for ( const QgsField &destField : outputFields )
237  {
238  if ( addLayerField && destField.name() == QLatin1String( "layer" ) )
239  {
240  destAttributes.append( layer->name() );
241  continue;
242  }
243  else if ( addPathField && destField.name() == QLatin1String( "path" ) )
244  {
245  destAttributes.append( layer->publicSource() );
246  continue;
247  }
248 
249  QVariant destAttribute;
250  int sourceIndex = vl->fields().lookupField( destField.name() );
251  if ( sourceIndex >= 0 )
252  {
253  destAttribute = f.attributes().at( sourceIndex );
254  }
255  destAttributes.append( destAttribute );
256  }
257  f.setAttributes( destAttributes );
258 
259  sink->addFeature( f, QgsFeatureSink::FastInsert );
260  i += 1;
261  feedback->setProgress( i * step );
262  }
263  }
264 
265  if ( errored )
266  throw QgsProcessingException( QObject::tr( "Error obtained while merging one or more layers." ) );
267 
268  QVariantMap outputs;
269  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
270  return outputs;
271 }
272 
bool isMeasure() const
Returns true if the geometry contains m values.
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:324
Wrapper for iterator of features from vector data provider or vector layer.
A parameter for processing algorithms which accepts multiple map layers.
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Base class for all map layer types.
Definition: qgsmaplayer.h:78
static Type multiType(Type type)
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:299
Base class for providing feedback from a processing algorithm.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
static bool isMultiType(Type type)
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:560
QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
Container of fields for a vector layer.
Definition: qgsfields.h:42
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:771
A feature sink output for processing algorithms.
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:68
QgsFields fields() const FINAL
Returns the list of fields of this layer.
static Type addM(Type type)
Adds the m dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:892
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
static QString geometryDisplayString(GeometryType type)
Returns a display string for a geometry type.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
static GeometryType geometryType(Type type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:666
static Type addZ(Type type)
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:867
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
A coordinate reference system parameter for processing algorithms.
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 convertToMultiType()
Converts single type geometry into multitype geometry e.g.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
This class represents a coordinate reference system (CRS).
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
static QString displayString(Type type)
Returns a display string type for a WKB type, e.g., the geometry name used in WKT geometry representa...
const QgsCoordinateReferenceSystem & outputCrs
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:82
static bool hasM(Type type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:821
QgsGeometry geometry
Definition: qgsfeature.h:67
bool nextFeature(QgsFeature &f)
A vector of attributes.
Definition: qgsattributes.h:57
Represents a vector layer which manages a vector based data sets.
Contains information about the context in which a processing algorithm is executed.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
QString authid() const
Returns the authority identifier for the CRS.
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:85
bool isValid() const
Returns whether this CRS is correctly initialized and usable.