[processing] Fix merge vectors algorithm fails when encountering

layers with different dimensions or single/multi part types

and port algorithm to c++
This commit is contained in:
Nyall Dawson 2017-12-06 12:24:16 +10:00
parent cb94bcf565
commit cb96e1baab
4 changed files with 314 additions and 0 deletions

1
src/analysis/CMakeLists.txt Normal file → Executable file
View File

@ -44,6 +44,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmloadlayer.cpp
processing/qgsalgorithmmeancoordinates.cpp
processing/qgsalgorithmmergelines.cpp
processing/qgsalgorithmmergevector.cpp
processing/qgsalgorithmminimumenclosingcircle.cpp
processing/qgsalgorithmmultiparttosinglepart.cpp
processing/qgsalgorithmorderbyexpression.cpp

View File

@ -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 &parameters, 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

View File

@ -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 &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};
///@endcond PRIVATE
#endif // QGSALGORITHMMERGEVECTOR_H

View File

@ -41,6 +41,7 @@
#include "qgsalgorithmloadlayer.h"
#include "qgsalgorithmmeancoordinates.h"
#include "qgsalgorithmmergelines.h"
#include "qgsalgorithmmergevector.h"
#include "qgsalgorithmminimumenclosingcircle.h"
#include "qgsalgorithmmultiparttosinglepart.h"
#include "qgsalgorithmorderbyexpression.h"
@ -122,6 +123,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsLoadLayerAlgorithm() );
addAlgorithm( new QgsMeanCoordinatesAlgorithm() );
addAlgorithm( new QgsMergeLinesAlgorithm() );
addAlgorithm( new QgsMergeVectorAlgorithm() );
addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() );
addAlgorithm( new QgsMultipartToSinglepartAlgorithm() );
addAlgorithm( new QgsOrderByExpressionAlgorithm() );