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