Skip to content

Commit

Permalink
[processing] Fix merge vectors algorithm fails when encountering
Browse files Browse the repository at this point in the history
layers with different dimensions or single/multi part types

and port algorithm to c++
  • Loading branch information
nyalldawson committed Dec 6, 2017
1 parent cb94bcf commit cb96e1b
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
255 changes: 255 additions & 0 deletions src/analysis/processing/qgsalgorithmmergevector.cpp
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 &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
56 changes: 56 additions & 0 deletions src/analysis/processing/qgsalgorithmmergevector.h
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 &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

};

///@endcond PRIVATE

#endif // QGSALGORITHMMERGEVECTOR_H


2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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() );
Expand Down

0 comments on commit cb96e1b

Please sign in to comment.