Skip to content

Commit cb96e1b

Browse files
committedDec 6, 2017
[processing] Fix merge vectors algorithm fails when encountering
layers with different dimensions or single/multi part types and port algorithm to c++
1 parent cb94bcf commit cb96e1b

File tree

4 files changed

+314
-0
lines changed

4 files changed

+314
-0
lines changed
 

‎src/analysis/CMakeLists.txt

100644100755
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ SET(QGIS_ANALYSIS_SRCS
4444
processing/qgsalgorithmloadlayer.cpp
4545
processing/qgsalgorithmmeancoordinates.cpp
4646
processing/qgsalgorithmmergelines.cpp
47+
processing/qgsalgorithmmergevector.cpp
4748
processing/qgsalgorithmminimumenclosingcircle.cpp
4849
processing/qgsalgorithmmultiparttosinglepart.cpp
4950
processing/qgsalgorithmorderbyexpression.cpp
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
18+
#include "qgsalgorithmmergevector.h"
19+
20+
///@cond PRIVATE
21+
22+
QString QgsMergeVectorAlgorithm::name() const
23+
{
24+
return QStringLiteral( "mergevectorlayers" );
25+
}
26+
27+
QString QgsMergeVectorAlgorithm::displayName() const
28+
{
29+
return QObject::tr( "Merge vector layers" );
30+
}
31+
32+
QStringList QgsMergeVectorAlgorithm::tags() const
33+
{
34+
return QObject::tr( "vector,layers,collect,merge,combine" ).split( ',' );
35+
}
36+
37+
QString QgsMergeVectorAlgorithm::group() const
38+
{
39+
return QObject::tr( "Vector general" );
40+
}
41+
42+
void QgsMergeVectorAlgorithm::initAlgorithm( const QVariantMap & )
43+
{
44+
addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
45+
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Merged" ) ) );
46+
}
47+
48+
QString QgsMergeVectorAlgorithm::shortHelpString() const
49+
{
50+
return QObject::tr( "This algorithm combines multiple vector layers of the same geometry type into a single one.\n\n"
51+
"If attributes tables are different, the attribute table of the resulting layer will contain the attributes "
52+
"from all input layers. New attributes will be added for the original layer name and source.\n\n"
53+
"The layers will all be reprojected to match the coordinate reference system of the first input layer.\n\n"
54+
"If any input layers contain Z or M values, then the output layer will also contain these values. Similarly, "
55+
"if any of the input layers are multi-part, the output layer will also be a multi-part layer." );
56+
}
57+
58+
QgsMergeVectorAlgorithm *QgsMergeVectorAlgorithm::createInstance() const
59+
{
60+
return new QgsMergeVectorAlgorithm();
61+
}
62+
63+
QVariantMap QgsMergeVectorAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
64+
{
65+
const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
66+
67+
QgsFields outputFields;
68+
long totalFeatureCount = 0;
69+
QgsWkbTypes::Type outputType = QgsWkbTypes::Unknown;
70+
QgsCoordinateReferenceSystem outputCrs;
71+
72+
bool errored = false;
73+
74+
// loop through input layers and determine geometry type, crs, fields, total feature count,...
75+
long i = 0;
76+
for ( QgsMapLayer *layer : layers )
77+
{
78+
i++;
79+
80+
if ( feedback->isCanceled() )
81+
break;
82+
83+
if ( !layer )
84+
{
85+
feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
86+
errored = true;
87+
continue;
88+
}
89+
90+
if ( layer->type() != QgsMapLayer::VectorLayer )
91+
throw QgsProcessingException( QObject::tr( "All layers must be vector layers!" ) );
92+
93+
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
94+
95+
if ( !outputCrs.isValid() )
96+
outputCrs = vl->crs();
97+
98+
// check wkb type
99+
if ( outputType != QgsWkbTypes::Unknown && outputType != QgsWkbTypes::NoGeometry )
100+
{
101+
if ( QgsWkbTypes::geometryType( outputType ) != QgsWkbTypes::geometryType( vl->wkbType() ) )
102+
throw QgsProcessingException( QObject::tr( "All layers must have same geometry type! Encountered a %1 layer when expecting a %2 layer." )
103+
.arg( QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( vl->wkbType() ) ),
104+
QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( outputType ) ) ) );
105+
106+
if ( QgsWkbTypes::hasM( vl->wkbType() ) && !QgsWkbTypes::hasM( outputType ) )
107+
{
108+
outputType = QgsWkbTypes::addM( outputType );
109+
feedback->pushInfo( QObject::tr( "Found a layer with M values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
110+
}
111+
if ( QgsWkbTypes::hasZ( vl->wkbType() ) && !QgsWkbTypes::hasZ( outputType ) )
112+
{
113+
outputType = QgsWkbTypes::addZ( outputType );
114+
feedback->pushInfo( QObject::tr( "Found a layer with Z values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
115+
}
116+
if ( QgsWkbTypes::isMultiType( vl->wkbType() ) && !QgsWkbTypes::isMultiType( outputType ) )
117+
{
118+
outputType = QgsWkbTypes::multiType( outputType );
119+
feedback->pushInfo( QObject::tr( "Found a layer with multiparts, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
120+
}
121+
}
122+
else
123+
{
124+
outputType = vl->wkbType();
125+
feedback->pushInfo( QObject::tr( "Setting output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) );
126+
}
127+
128+
totalFeatureCount += vl->featureCount();
129+
130+
// check field type
131+
for ( const QgsField &sourceField : vl->fields() )
132+
{
133+
bool found = false;
134+
for ( const QgsField &destField : outputFields )
135+
{
136+
if ( destField.name().compare( sourceField.name(), Qt::CaseInsensitive ) == 0 )
137+
{
138+
found = true;
139+
if ( destField.type() != sourceField.type() )
140+
{
141+
throw QgsProcessingException( QObject::tr( "%1 field in layer %2 has different data type than in other layers" )
142+
.arg( sourceField.name() ).arg( i ) );
143+
}
144+
break;
145+
}
146+
}
147+
148+
if ( !found )
149+
outputFields.append( sourceField );
150+
}
151+
}
152+
153+
bool addLayerField = false;
154+
if ( outputFields.lookupField( QStringLiteral( "layer" ) ) < 0 )
155+
{
156+
outputFields.append( QgsField( QStringLiteral( "layer" ), QVariant::String, QString(), 100 ) );
157+
addLayerField = true;
158+
}
159+
bool addPathField = false;
160+
if ( outputFields.lookupField( QStringLiteral( "path" ) ) < 0 )
161+
{
162+
outputFields.append( QgsField( QStringLiteral( "path" ), QVariant::String, QString(), 200 ) );
163+
addPathField = true;
164+
}
165+
166+
QString dest;
167+
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs ) );
168+
169+
bool hasZ = QgsWkbTypes::hasZ( outputType );
170+
bool hasM = QgsWkbTypes::hasM( outputType );
171+
bool isMulti = QgsWkbTypes::isMultiType( outputType );
172+
double step = totalFeatureCount > 0 ? 100.0 / totalFeatureCount : 1;
173+
i = 0;
174+
for ( QgsMapLayer *layer : layers )
175+
{
176+
if ( !layer )
177+
continue;
178+
179+
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
180+
if ( !vl )
181+
continue;
182+
183+
feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( layers.count() ).arg( layer->name() ) );
184+
185+
QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs ) );
186+
QgsFeature f;
187+
while ( it.nextFeature( f ) )
188+
{
189+
if ( feedback->isCanceled() )
190+
break;
191+
192+
// ensure feature geometry is of correct type
193+
if ( f.hasGeometry() )
194+
{
195+
bool changed = false;
196+
QgsGeometry g = f.geometry();
197+
if ( hasZ && !g.constGet()->is3D() )
198+
{
199+
g.get()->addZValue( 0 );
200+
changed = true;
201+
}
202+
if ( hasM && !g.constGet()->isMeasure() )
203+
{
204+
g.get()->addMValue( 0 );
205+
changed = true;
206+
}
207+
if ( isMulti && !g.isMultipart() )
208+
{
209+
g.convertToMultiType();
210+
changed = true;
211+
}
212+
if ( changed )
213+
f.setGeometry( g );
214+
}
215+
216+
// process feature attributes
217+
QgsAttributes destAttributes;
218+
for ( const QgsField &destField : outputFields )
219+
{
220+
if ( addLayerField && destField.name() == QLatin1String( "layer" ) )
221+
{
222+
destAttributes.append( layer->name() );
223+
continue;
224+
}
225+
else if ( addPathField && destField.name() == QLatin1String( "path" ) )
226+
{
227+
destAttributes.append( layer->publicSource() );
228+
continue;
229+
}
230+
231+
QVariant destAttribute;
232+
int sourceIndex = vl->fields().lookupField( destField.name() );
233+
if ( sourceIndex >= 0 )
234+
{
235+
destAttribute = f.attributes().at( sourceIndex );
236+
}
237+
destAttributes.append( destAttribute );
238+
}
239+
f.setAttributes( destAttributes );
240+
241+
sink->addFeature( f, QgsFeatureSink::FastInsert );
242+
i += 1;
243+
feedback->setProgress( i * step );
244+
}
245+
}
246+
247+
if ( errored )
248+
throw QgsProcessingException( QObject::tr( "Error obtained while merging one or more layers." ) );
249+
250+
QVariantMap outputs;
251+
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
252+
return outputs;
253+
}
254+
255+
///@endcond
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/***************************************************************************
2+
qgsalgorithmmergevector.h
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+
18+
#ifndef QGSALGORITHMMERGEVECTOR_H
19+
#define QGSALGORITHMMERGEVECTOR_H
20+
21+
#define SIP_NO_FILE
22+
23+
#include "qgis.h"
24+
#include "qgsprocessingalgorithm.h"
25+
26+
///@cond PRIVATE
27+
28+
/**
29+
* Native vector layer merge algorithm.
30+
*/
31+
class QgsMergeVectorAlgorithm : public QgsProcessingAlgorithm
32+
{
33+
34+
public:
35+
36+
QgsMergeVectorAlgorithm() = default;
37+
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
38+
QString name() const override;
39+
QString displayName() const override;
40+
virtual QStringList tags() const override;
41+
QString group() const override;
42+
QString shortHelpString() const override;
43+
QgsMergeVectorAlgorithm *createInstance() const override SIP_FACTORY;
44+
45+
protected:
46+
47+
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
48+
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
49+
50+
};
51+
52+
///@endcond PRIVATE
53+
54+
#endif // QGSALGORITHMMERGEVECTOR_H
55+
56+

‎src/analysis/processing/qgsnativealgorithms.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "qgsalgorithmloadlayer.h"
4242
#include "qgsalgorithmmeancoordinates.h"
4343
#include "qgsalgorithmmergelines.h"
44+
#include "qgsalgorithmmergevector.h"
4445
#include "qgsalgorithmminimumenclosingcircle.h"
4546
#include "qgsalgorithmmultiparttosinglepart.h"
4647
#include "qgsalgorithmorderbyexpression.h"
@@ -122,6 +123,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
122123
addAlgorithm( new QgsLoadLayerAlgorithm() );
123124
addAlgorithm( new QgsMeanCoordinatesAlgorithm() );
124125
addAlgorithm( new QgsMergeLinesAlgorithm() );
126+
addAlgorithm( new QgsMergeVectorAlgorithm() );
125127
addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() );
126128
addAlgorithm( new QgsMultipartToSinglepartAlgorithm() );
127129
addAlgorithm( new QgsOrderByExpressionAlgorithm() );

0 commit comments

Comments
 (0)
Please sign in to comment.