QGIS/src/analysis/processing/qgsalgorithmmergevector.cpp
Denis Rouzaud 375a0aa253 create scope based enum for QgsMapLayer::LayerType >> QgsMapLayerType
the enum is moved out of the class
this will allow forward declaration more easily since the enum is not nested in the class
2019-03-24 21:40:33 -05:00

274 lines
10 KiB
C++

/***************************************************************************
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"
#include "qgsvectorlayer.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" );
}
QString QgsMergeVectorAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeneral" );
}
void QgsMergeVectorAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS" ), QObject::tr( "Destination CRS" ), QVariant(), true ) );
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"
"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.\n\n"
"Optionally, the destination coordinate reference system (CRS) for the merged layer can be set. If it is not set, the CRS will be "
"taken from the first input layer. All layers will all be reprojected to match this CRS." );
}
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 = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context );
if ( outputCrs.isValid() )
feedback->pushInfo( QObject::tr( "Using specified destination CRS %1" ).arg( outputCrs.authid() ) );
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() != QgsMapLayerType::VectorLayer )
throw QgsProcessingException( QObject::tr( "All layers must be vector layers!" ) );
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
if ( !outputCrs.isValid() && vl->crs().isValid() )
{
outputCrs = vl->crs();
feedback->pushInfo( QObject::tr( "Taking destination CRS %1 from layer" ).arg( outputCrs.authid() ) );
}
// 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 (%3 instead of %4)" )
.arg( sourceField.name(), vl->name(), sourceField.typeName(), destField.typeName() ) );
}
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, QgsFeatureSink::RegeneratePrimaryKey ) );
if ( !sink )
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
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;
int layerNumber = 0;
for ( QgsMapLayer *layer : layers )
{
layerNumber++;
if ( !layer )
continue;
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
if ( !vl )
continue;
feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( layerNumber ).arg( layers.count() ).arg( layer->name() ) );
QgsFeatureIterator it = vl->getFeatures( QgsFeatureRequest().setDestinationCrs( outputCrs, context.transformContext() ) );
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