-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[processing] Fix merge vectors algorithm fails when encountering
layers with different dimensions or single/multi part types and port algorithm to c++
- Loading branch information
1 parent
cb94bcf
commit cb96e1b
Showing
4 changed files
with
314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
/*************************************************************************** | ||
qgsalgorithmmergevector.cpp | ||
------------------ | ||
begin : December 2017 | ||
copyright : (C) 2017 by Nyall Dawson | ||
email : nyall dot dawson at gmail dot com | ||
***************************************************************************/ | ||
|
||
/*************************************************************************** | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU General Public License as published by * | ||
* the Free Software Foundation; either version 2 of the License, or * | ||
* (at your option) any later version. * | ||
* * | ||
***************************************************************************/ | ||
|
||
#include "qgsalgorithmmergevector.h" | ||
|
||
///@cond PRIVATE | ||
|
||
QString QgsMergeVectorAlgorithm::name() const | ||
{ | ||
return QStringLiteral( "mergevectorlayers" ); | ||
} | ||
|
||
QString QgsMergeVectorAlgorithm::displayName() const | ||
{ | ||
return QObject::tr( "Merge vector layers" ); | ||
} | ||
|
||
QStringList QgsMergeVectorAlgorithm::tags() const | ||
{ | ||
return QObject::tr( "vector,layers,collect,merge,combine" ).split( ',' ); | ||
} | ||
|
||
QString QgsMergeVectorAlgorithm::group() const | ||
{ | ||
return QObject::tr( "Vector general" ); | ||
} | ||
|
||
void QgsMergeVectorAlgorithm::initAlgorithm( const QVariantMap & ) | ||
{ | ||
addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) ); | ||
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Merged" ) ) ); | ||
} | ||
|
||
QString QgsMergeVectorAlgorithm::shortHelpString() const | ||
{ | ||
return QObject::tr( "This algorithm combines multiple vector layers of the same geometry type into a single one.\n\n" | ||
"If attributes tables are different, the attribute table of the resulting layer will contain the attributes " | ||
"from all input layers. New attributes will be added for the original layer name and source.\n\n" | ||
"The layers will all be reprojected to match the coordinate reference system of the first input layer.\n\n" | ||
"If any input layers contain Z or M values, then the output layer will also contain these values. Similarly, " | ||
"if any of the input layers are multi-part, the output layer will also be a multi-part layer." ); | ||
} | ||
|
||
QgsMergeVectorAlgorithm *QgsMergeVectorAlgorithm::createInstance() const | ||
{ | ||
return new QgsMergeVectorAlgorithm(); | ||
} | ||
|
||
QVariantMap QgsMergeVectorAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) | ||
{ | ||
const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ); | ||
|
||
QgsFields outputFields; | ||
long totalFeatureCount = 0; | ||
QgsWkbTypes::Type outputType = QgsWkbTypes::Unknown; | ||
QgsCoordinateReferenceSystem outputCrs; | ||
|
||
bool errored = false; | ||
|
||
// loop through input layers and determine geometry type, crs, fields, total feature count,... | ||
long i = 0; | ||
for ( QgsMapLayer *layer : layers ) | ||
{ | ||
i++; | ||
|
||
if ( feedback->isCanceled() ) | ||
break; | ||
|
||
if ( !layer ) | ||
{ | ||
feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) ); | ||
errored = true; | ||
continue; | ||
} | ||
|
||
if ( layer->type() != QgsMapLayer::VectorLayer ) | ||
throw QgsProcessingException( QObject::tr( "All layers must be vector layers!" ) ); | ||
|
||
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ); | ||
|
||
if ( !outputCrs.isValid() ) | ||
outputCrs = vl->crs(); | ||
|
||
// check wkb type | ||
if ( outputType != QgsWkbTypes::Unknown && outputType != QgsWkbTypes::NoGeometry ) | ||
{ | ||
if ( QgsWkbTypes::geometryType( outputType ) != QgsWkbTypes::geometryType( vl->wkbType() ) ) | ||
throw QgsProcessingException( QObject::tr( "All layers must have same geometry type! Encountered a %1 layer when expecting a %2 layer." ) | ||
.arg( QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( vl->wkbType() ) ), | ||
QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( outputType ) ) ) ); | ||
|
||
if ( QgsWkbTypes::hasM( vl->wkbType() ) && !QgsWkbTypes::hasM( outputType ) ) | ||
{ | ||
outputType = QgsWkbTypes::addM( outputType ); | ||
feedback->pushInfo( QObject::tr( "Found a layer with M values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) ); | ||
} | ||
if ( QgsWkbTypes::hasZ( vl->wkbType() ) && !QgsWkbTypes::hasZ( outputType ) ) | ||
{ | ||
outputType = QgsWkbTypes::addZ( outputType ); | ||
feedback->pushInfo( QObject::tr( "Found a layer with Z values, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) ); | ||
} | ||
if ( QgsWkbTypes::isMultiType( vl->wkbType() ) && !QgsWkbTypes::isMultiType( outputType ) ) | ||
{ | ||
outputType = QgsWkbTypes::multiType( outputType ); | ||
feedback->pushInfo( QObject::tr( "Found a layer with multiparts, upgrading output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) ); | ||
} | ||
} | ||
else | ||
{ | ||
outputType = vl->wkbType(); | ||
feedback->pushInfo( QObject::tr( "Setting output type to %1" ).arg( QgsWkbTypes::displayString( outputType ) ) ); | ||
} | ||
|
||
totalFeatureCount += vl->featureCount(); | ||
|
||
// check field type | ||
for ( const QgsField &sourceField : vl->fields() ) | ||
{ | ||
bool found = false; | ||
for ( const QgsField &destField : outputFields ) | ||
{ | ||
if ( destField.name().compare( sourceField.name(), Qt::CaseInsensitive ) == 0 ) | ||
{ | ||
found = true; | ||
if ( destField.type() != sourceField.type() ) | ||
{ | ||
throw QgsProcessingException( QObject::tr( "%1 field in layer %2 has different data type than in other layers" ) | ||
.arg( sourceField.name() ).arg( i ) ); | ||
} | ||
break; | ||
} | ||
} | ||
|
||
if ( !found ) | ||
outputFields.append( sourceField ); | ||
} | ||
} | ||
|
||
bool addLayerField = false; | ||
if ( outputFields.lookupField( QStringLiteral( "layer" ) ) < 0 ) | ||
{ | ||
outputFields.append( QgsField( QStringLiteral( "layer" ), QVariant::String, QString(), 100 ) ); | ||
addLayerField = true; | ||
} | ||
bool addPathField = false; | ||
if ( outputFields.lookupField( QStringLiteral( "path" ) ) < 0 ) | ||
{ | ||
outputFields.append( QgsField( QStringLiteral( "path" ), QVariant::String, QString(), 200 ) ); | ||
addPathField = true; | ||
} | ||
|
||
QString dest; | ||
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs ) ); | ||
|
||
bool hasZ = QgsWkbTypes::hasZ( outputType ); | ||
bool hasM = QgsWkbTypes::hasM( outputType ); | ||
bool isMulti = QgsWkbTypes::isMultiType( outputType ); | ||
double step = totalFeatureCount > 0 ? 100.0 / totalFeatureCount : 1; | ||
i = 0; | ||
for ( QgsMapLayer *layer : layers ) | ||
{ | ||
if ( !layer ) | ||
continue; | ||
|
||
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ); | ||
if ( !vl ) | ||
continue; | ||
|
||
feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( layers.count() ).arg( layer->name() ) ); | ||
|
||
QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs ) ); | ||
QgsFeature f; | ||
while ( it.nextFeature( f ) ) | ||
{ | ||
if ( feedback->isCanceled() ) | ||
break; | ||
|
||
// ensure feature geometry is of correct type | ||
if ( f.hasGeometry() ) | ||
{ | ||
bool changed = false; | ||
QgsGeometry g = f.geometry(); | ||
if ( hasZ && !g.constGet()->is3D() ) | ||
{ | ||
g.get()->addZValue( 0 ); | ||
changed = true; | ||
} | ||
if ( hasM && !g.constGet()->isMeasure() ) | ||
{ | ||
g.get()->addMValue( 0 ); | ||
changed = true; | ||
} | ||
if ( isMulti && !g.isMultipart() ) | ||
{ | ||
g.convertToMultiType(); | ||
changed = true; | ||
} | ||
if ( changed ) | ||
f.setGeometry( g ); | ||
} | ||
|
||
// process feature attributes | ||
QgsAttributes destAttributes; | ||
for ( const QgsField &destField : outputFields ) | ||
{ | ||
if ( addLayerField && destField.name() == QLatin1String( "layer" ) ) | ||
{ | ||
destAttributes.append( layer->name() ); | ||
continue; | ||
} | ||
else if ( addPathField && destField.name() == QLatin1String( "path" ) ) | ||
{ | ||
destAttributes.append( layer->publicSource() ); | ||
continue; | ||
} | ||
|
||
QVariant destAttribute; | ||
int sourceIndex = vl->fields().lookupField( destField.name() ); | ||
if ( sourceIndex >= 0 ) | ||
{ | ||
destAttribute = f.attributes().at( sourceIndex ); | ||
} | ||
destAttributes.append( destAttribute ); | ||
} | ||
f.setAttributes( destAttributes ); | ||
|
||
sink->addFeature( f, QgsFeatureSink::FastInsert ); | ||
i += 1; | ||
feedback->setProgress( i * step ); | ||
} | ||
} | ||
|
||
if ( errored ) | ||
throw QgsProcessingException( QObject::tr( "Error obtained while merging one or more layers." ) ); | ||
|
||
QVariantMap outputs; | ||
outputs.insert( QStringLiteral( "OUTPUT" ), dest ); | ||
return outputs; | ||
} | ||
|
||
///@endcond |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/*************************************************************************** | ||
qgsalgorithmmergevector.h | ||
------------------ | ||
begin : December 2017 | ||
copyright : (C) 2017 by Nyall Dawson | ||
email : nyall dot dawson at gmail dot com | ||
***************************************************************************/ | ||
|
||
/*************************************************************************** | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU General Public License as published by * | ||
* the Free Software Foundation; either version 2 of the License, or * | ||
* (at your option) any later version. * | ||
* * | ||
***************************************************************************/ | ||
|
||
#ifndef QGSALGORITHMMERGEVECTOR_H | ||
#define QGSALGORITHMMERGEVECTOR_H | ||
|
||
#define SIP_NO_FILE | ||
|
||
#include "qgis.h" | ||
#include "qgsprocessingalgorithm.h" | ||
|
||
///@cond PRIVATE | ||
|
||
/** | ||
* Native vector layer merge algorithm. | ||
*/ | ||
class QgsMergeVectorAlgorithm : public QgsProcessingAlgorithm | ||
{ | ||
|
||
public: | ||
|
||
QgsMergeVectorAlgorithm() = default; | ||
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; | ||
QString name() const override; | ||
QString displayName() const override; | ||
virtual QStringList tags() const override; | ||
QString group() const override; | ||
QString shortHelpString() const override; | ||
QgsMergeVectorAlgorithm *createInstance() const override SIP_FACTORY; | ||
|
||
protected: | ||
|
||
virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, | ||
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; | ||
|
||
}; | ||
|
||
///@endcond PRIVATE | ||
|
||
#endif // QGSALGORITHMMERGEVECTOR_H | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters