Merge pull request #8240 from m-kuhn/mergeVectorLayersDuplicateFid

Ignore FID field when merging vector layers to geopackage
This commit is contained in:
Matthias Kuhn 2018-10-31 13:47:12 +01:00 committed by GitHub
commit 429cd0bacd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 236 additions and 51 deletions

View File

@ -611,7 +611,7 @@ Evaluates the parameter with matching ``name`` to a static boolean value.
%End
QgsFeatureSink *parameterAsSink( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, QString &destinationIdentifier /Out/,
const QgsFields &fields, QgsWkbTypes::Type geometryType = QgsWkbTypes::NoGeometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ) const /Factory/;
const QgsFields &fields, QgsWkbTypes::Type geometryType = QgsWkbTypes::NoGeometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem(), QgsFeatureSink::SinkFlags sinkFlags = 0 ) const /Factory/;
%Docstring
Evaluates the parameter with matching ``name`` to a feature sink.
@ -922,6 +922,13 @@ this is possible to determine in advance.
virtual QgsProcessingFeatureSource::Flag sourceFlags() const;
%Docstring
Returns the processing feature source flags to be used in the algorithm.
%End
virtual QgsFeatureSink::SinkFlags sinkFlags() const;
%Docstring
Returns the feature sink flags to be used for the output.
.. versionadded:: 3.4.1
%End
virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const;

View File

@ -588,7 +588,7 @@ Evaluates the parameter with matching ``definition`` and ``value`` to a static b
static QgsFeatureSink *parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters,
const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs,
QgsProcessingContext &context, QString &destinationIdentifier /Out/ ) /Factory/;
QgsProcessingContext &context, QString &destinationIdentifier /Out/, QgsFeatureSink::SinkFlags sinkFlags = 0 ) /Factory/;
%Docstring
Evaluates the parameter with matching ``definition`` to a feature sink.
@ -605,7 +605,7 @@ This function creates a new object and the caller takes responsibility for delet
static QgsFeatureSink *parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariant &value,
const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs,
QgsProcessingContext &context, QString &destinationIdentifier /Out/ ) /Factory/;
QgsProcessingContext &context, QString &destinationIdentifier /Out/, QgsFeatureSink::SinkFlags sinkFlags = 0 ) /Factory/;
%Docstring
Evaluates the parameter with matching ``definition`` and ``value`` to a feature sink.

View File

@ -22,6 +22,14 @@ An interface for objects which accept features via addFeature(s) methods.
%End
public:
enum SinkFlag
{
RegeneratePrimaryKey,
};
typedef QFlags<QgsFeatureSink::SinkFlag> SinkFlags;
enum Flag
{

View File

@ -408,7 +408,8 @@ Writes a layer out to a vector file.
const QStringList &datasourceOptions = QStringList(),
const QStringList &layerOptions = QStringList(),
QString *newFilename = 0,
QgsVectorFileWriter::SymbologyExport symbologyExport = QgsVectorFileWriter::NoSymbology
QgsVectorFileWriter::SymbologyExport symbologyExport = QgsVectorFileWriter::NoSymbology,
QgsFeatureSink::SinkFlags sinkFlags = 0
);

View File

@ -77,7 +77,8 @@ Writes the contents of vector layer to a different datasource.
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &crs,
bool overwrite = false,
const QMap<QString, QVariant> &options = QMap<QString, QVariant>() );
const QMap<QString, QVariant> &options = QMap<QString, QVariant>(),
QgsFeatureSink::SinkFlags sinkFlags = 0 );
%Docstring
Constructor for QgsVectorLayerExporter.
@ -89,6 +90,7 @@ Constructor for QgsVectorLayerExporter.
not available
:param overwrite: set to true to overwrite any existing data source
:param options: optional provider dataset options
:param sinkFlags: for how to add features
%End

View File

@ -107,7 +107,7 @@ class PointsAlongGeometry(QgisAlgorithm):
fields.append(QgsField('angle', QVariant.Double))
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.Point, source.sourceCrs())
fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey)
if sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

View File

@ -184,12 +184,12 @@ class SpatialJoin(QgisAlgorithm):
out_fields = QgsProcessingUtils.combineFields(source_fields, fields_to_join)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
out_fields, source.wkbType(), source.sourceCrs())
out_fields, source.wkbType(), source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey)
if self.OUTPUT in parameters and parameters[self.OUTPUT] is not None and sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
(non_matching_sink, non_matching_dest_id) = self.parameterAsSink(parameters, self.NON_MATCHING, context,
source.fields(), source.wkbType(), source.sourceCrs())
source.fields(), source.wkbType(), source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey)
if self.NON_MATCHING in parameters and parameters[self.NON_MATCHING] is not None and non_matching_sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.NON_MATCHING))

View File

@ -36,6 +36,7 @@ import shutil
import glob
import hashlib
import tempfile
import re
from osgeo.gdalconst import GA_ReadOnly
from numpy import nan_to_num
@ -199,7 +200,8 @@ class AlgorithmsTest(object):
basename = os.path.basename(param['name'])
else:
basename = os.path.basename(param['name'][0])
filepath = os.path.join(outdir, basename)
filepath = self.uri_path_join(outdir, basename)
return filepath
elif param['type'] == 'rasterhash':
outdir = tempfile.mkdtemp()
@ -215,13 +217,14 @@ class AlgorithmsTest(object):
def load_layers(self, id, param):
layers = []
if param['type'] in ('vector', 'table') and isinstance(param['name'], str):
layers.append(self.load_layer(id, param))
elif param['type'] in ('vector', 'table'):
for n in param['name']:
layer_param = deepcopy(param)
layer_param['name'] = n
layers.append(self.load_layer(id, layer_param))
if param['type'] in ('vector', 'table'):
if isinstance(param['name'], str) or 'uri' in param:
layers.append(self.load_layer(id, param))
else:
for n in param['name']:
layer_param = deepcopy(param)
layer_param['name'] = n
layers.append(self.load_layer(id, layer_param))
else:
layers.append(self.load_layer(id, param))
return layers
@ -230,6 +233,7 @@ class AlgorithmsTest(object):
"""
Loads a layer which was specified as parameter.
"""
filepath = self.filepath_from_param(param)
if 'in_place' in param and param['in_place']:
@ -268,7 +272,22 @@ class AlgorithmsTest(object):
if 'location' in param and param['location'] == 'qgs':
prefix = unitTestDataPath()
return os.path.join(prefix, param['name'])
if 'uri' in param:
path = param['uri']
else:
path = param['name']
return self.uri_path_join(prefix, path)
def uri_path_join(self, prefix, filepath):
if filepath.startswith('ogr:'):
if not prefix[-1] == os.path.sep:
prefix += os.path.sep
filepath = re.sub(r"dbname='", "dbname='{}".format(prefix), filepath)
else:
filepath = os.path.join(prefix, filepath)
return filepath
def check_results(self, results, context, params, expected):
"""

Binary file not shown.

Binary file not shown.

View File

@ -6232,5 +6232,28 @@ tests:
name: expected/interpolate_point_polys.gml
type: vector
- algorithm: native:mergevectorlayers
name: Merge vector layers with conflicting feature ids
params:
LAYERS:
params:
- name: custom/pol.gpkg|layername=pol1
type: vector
- name: custom/pol.gpkg|layername=pol2
type: vector
- name: custom/pol.gpkg|layername=pol3
type: vector
type: multi
results:
OUTPUT:
# If you ever run into this test producing Polygons instead of MultiPolygons as output
# that is totally expected and you are invited to replace the file merged_pol.gpkg with
# a single polygon version.
name: ogr:dbname='expected/merged_pol.gpkg' table="output" (geom) sql=
uri: expected/merged_pol.gpkg|layername=merged_pol
type: vector
compare:
fields:
path: skip
# See ../README.md for a description of the file format

View File

@ -165,13 +165,13 @@ QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &pa
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
input->wkbType(), input->sourceCrs() ) );
input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !sink )
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
QString destNonMatching1;
std::unique_ptr< QgsFeatureSink > sinkNonMatching1( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING" ), context, destNonMatching1, input->fields(),
input->wkbType(), input->sourceCrs() ) );
input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
if ( parameters.value( QStringLiteral( "NON_MATCHING" ) ).isValid() && !sinkNonMatching1 )
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING" ) ) );

View File

@ -178,7 +178,7 @@ QVariantMap QgsMergeVectorAlgorithm::processAlgorithm( const QVariantMap &parame
}
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs ) );
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, outputType, outputCrs, QgsFeatureSink::RegeneratePrimaryKey ) );
if ( !sink )
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );

View File

@ -74,6 +74,11 @@ QgsProcessingFeatureSource::Flag QgsMultipartToSinglepartAlgorithm::sourceFlags(
return QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks;
}
QgsFeatureSink::SinkFlags QgsMultipartToSinglepartAlgorithm::sinkFlags() const
{
return QgsFeatureSink::RegeneratePrimaryKey;
}
QgsFeatureList QgsMultipartToSinglepartAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback * )
{
if ( !feature.hasGeometry() )

View File

@ -49,6 +49,7 @@ class QgsMultipartToSinglepartAlgorithm : public QgsProcessingFeatureBasedAlgori
protected:
QgsProcessingFeatureSource::Flag sourceFlags() const override;
QgsFeatureSink::SinkFlags sinkFlags() const override;
QgsFeatureList processFeature( const QgsFeature &feature,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

View File

@ -348,6 +348,9 @@ void QgsGeometryValidationService::processFeature( QgsVectorLayer *layer, QgsFea
if ( !allErrors.empty() )
mLayerChecks[layer].singleFeatureCheckErrors.insert( fid, allErrors );
if ( !mLayerChecks[layer].singleFeatureCheckErrors.empty() )
layer->setAllowCommit( false );
emit geometryCheckCompleted( layer, fid, allErrors );
}
@ -432,7 +435,7 @@ void QgsGeometryValidationService::triggerTopologyChecks( QgsVectorLayer *layer
connect( futureWatcher, &QFutureWatcherBase::finished, this, [&allErrors, layer, feedbacks, futureWatcher, this]()
{
QgsReadWriteLocker errorLocker( mTopologyCheckLock, QgsReadWriteLocker::Read );
layer->setAllowCommit( allErrors.empty() );
layer->setAllowCommit( allErrors.empty() && mLayerChecks[layer].singleFeatureCheckErrors.empty() );
errorLocker.unlock();
qDeleteAll( feedbacks.values() );
futureWatcher->deleteLater();

View File

@ -586,9 +586,9 @@ bool QgsProcessingAlgorithm::parameterAsBool( const QVariantMap &parameters, con
return QgsProcessingParameters::parameterAsBool( parameterDefinition( name ), parameters, context );
}
QgsFeatureSink *QgsProcessingAlgorithm::parameterAsSink( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, QString &destinationIdentifier, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs ) const
QgsFeatureSink *QgsProcessingAlgorithm::parameterAsSink( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, QString &destinationIdentifier, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, QgsFeatureSink::SinkFlags sinkFlags ) const
{
return QgsProcessingParameters::parameterAsSink( parameterDefinition( name ), parameters, fields, geometryType, crs, context, destinationIdentifier );
return QgsProcessingParameters::parameterAsSink( parameterDefinition( name ), parameters, fields, geometryType, crs, context, destinationIdentifier, sinkFlags );
}
QgsProcessingFeatureSource *QgsProcessingAlgorithm::parameterAsSource( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const
@ -824,6 +824,11 @@ QgsProcessingFeatureSource::Flag QgsProcessingFeatureBasedAlgorithm::sourceFlags
return static_cast<QgsProcessingFeatureSource::Flag>( 0 );
}
QgsFeatureSink::SinkFlags QgsProcessingFeatureBasedAlgorithm::sinkFlags() const
{
return nullptr;
}
QgsWkbTypes::Type QgsProcessingFeatureBasedAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const
{
return inputWkbType;
@ -858,7 +863,8 @@ QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariant
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest,
outputFields( mSource->fields() ),
outputWkbType( mSource->wkbType() ),
outputCrs( mSource->sourceCrs() ) ) );
outputCrs( mSource->sourceCrs() ),
sinkFlags() ) );
if ( !sink )
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );

View File

@ -619,7 +619,7 @@ class CORE_EXPORT QgsProcessingAlgorithm
* This function creates a new object and the caller takes responsibility for deleting the returned object.
*/
QgsFeatureSink *parameterAsSink( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, QString &destinationIdentifier SIP_OUT,
const QgsFields &fields, QgsWkbTypes::Type geometryType = QgsWkbTypes::NoGeometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ) const SIP_FACTORY;
const QgsFields &fields, QgsWkbTypes::Type geometryType = QgsWkbTypes::NoGeometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem(), QgsFeatureSink::SinkFlags sinkFlags = nullptr ) const SIP_FACTORY;
/**
* Evaluates the parameter with matching \a name to a feature source.
@ -929,6 +929,13 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor
*/
virtual QgsProcessingFeatureSource::Flag sourceFlags() const;
/**
* Returns the feature sink flags to be used for the output.
*
* \since QGIS 3.4.1
*/
virtual QgsFeatureSink::SinkFlags sinkFlags() const;
/**
* Maps the input WKB geometry type (\a inputWkbType) to the corresponding
* output WKB type generated by the algorithm. The default behavior is that the algorithm maintains

View File

@ -350,7 +350,7 @@ bool QgsProcessingParameters::parameterAsBool( const QgsProcessingParameterDefin
QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, const QgsFields &fields,
QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs,
QgsProcessingContext &context, QString &destinationIdentifier )
QgsProcessingContext &context, QString &destinationIdentifier, QgsFeatureSink::SinkFlags sinkFlags )
{
QVariant val;
if ( definition )
@ -358,10 +358,10 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar
val = parameters.value( definition->name() );
}
return parameterAsSink( definition, val, fields, geometryType, crs, context, destinationIdentifier );
return parameterAsSink( definition, val, fields, geometryType, crs, context, destinationIdentifier, sinkFlags );
}
QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariant &value, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, QgsProcessingContext &context, QString &destinationIdentifier )
QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariant &value, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, QgsProcessingContext &context, QString &destinationIdentifier, QgsFeatureSink::SinkFlags sinkFlags )
{
QVariant val = value;
@ -374,6 +374,7 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar
QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( val );
destinationProject = fromVar.destinationProject;
createOptions = fromVar.createOptions;
val = fromVar.sink;
destName = fromVar.destinationName;
}
@ -401,7 +402,7 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar
if ( dest.isEmpty() )
return nullptr;
std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( dest, context, fields, geometryType, crs, createOptions ) );
std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( dest, context, fields, geometryType, crs, createOptions, sinkFlags ) );
destinationIdentifier = dest;
if ( destinationProject )
@ -4580,4 +4581,3 @@ bool QgsProcessingParameterDistance::fromVariantMap( const QVariantMap &map )
mParentParameterName = map.value( QStringLiteral( "parent" ) ).toString();
return true;
}

View File

@ -24,6 +24,7 @@
#include "qgsproperty.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsfeaturesource.h"
#include "qgsprocessingutils.h"
#include <QMap>
#include <limits>
@ -662,7 +663,7 @@ class CORE_EXPORT QgsProcessingParameters
*/
static QgsFeatureSink *parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters,
const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs,
QgsProcessingContext &context, QString &destinationIdentifier SIP_OUT ) SIP_FACTORY;
QgsProcessingContext &context, QString &destinationIdentifier SIP_OUT, QgsFeatureSink::SinkFlags sinkFlags = nullptr ) SIP_FACTORY;
/**
* Evaluates the parameter with matching \a definition and \a value to a feature sink.
@ -681,7 +682,7 @@ class CORE_EXPORT QgsProcessingParameters
*/
static QgsFeatureSink *parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariant &value,
const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs,
QgsProcessingContext &context, QString &destinationIdentifier SIP_OUT ) SIP_FACTORY;
QgsProcessingContext &context, QString &destinationIdentifier SIP_OUT, QgsFeatureSink::SinkFlags sinkFlags = nullptr ) SIP_FACTORY;
/**
* Evaluates the parameter with matching \a definition to a feature source.

View File

@ -373,6 +373,7 @@ void QgsProcessingUtils::parseDestinationString( QString &destination, QString &
options.insert( QStringLiteral( "layerName" ), layerName );
}
uri = dsUri.database();
format = QgsVectorFileWriter::driverForExtension( QFileInfo( uri ).completeSuffix() );
}
options.insert( QStringLiteral( "update" ), true );
}
@ -402,7 +403,7 @@ void QgsProcessingUtils::parseDestinationString( QString &destination, QString &
}
}
QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions )
QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, QgsProcessingContext &context, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs, const QVariantMap &createOptions, QgsFeatureSink::SinkFlags sinkFlags )
{
QVariantMap options = createOptions;
if ( !options.contains( QStringLiteral( "fileEncoding" ) ) )
@ -445,13 +446,14 @@ QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, Qgs
bool useWriter = false;
parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
QgsFields newFields = fields;
if ( useWriter && providerKey == QLatin1String( "ogr" ) )
{
// use QgsVectorFileWriter for OGR destinations instead of QgsVectorLayerImport, as that allows
// us to use any OGR format which supports feature addition
QString finalFileName;
std::unique_ptr< QgsVectorFileWriter > writer = qgis::make_unique< QgsVectorFileWriter >( destination, options.value( QStringLiteral( "fileEncoding" ) ).toString(), fields, geometryType, crs, format, QgsVectorFileWriter::defaultDatasetOptions( format ),
QgsVectorFileWriter::defaultLayerOptions( format ), &finalFileName );
std::unique_ptr< QgsVectorFileWriter > writer = qgis::make_unique< QgsVectorFileWriter >( destination, options.value( QStringLiteral( "fileEncoding" ) ).toString(), newFields, geometryType, crs, format, QgsVectorFileWriter::defaultDatasetOptions( format ),
QgsVectorFileWriter::defaultLayerOptions( format ), &finalFileName, QgsVectorFileWriter::NoSymbology, sinkFlags );
if ( writer->hasError() )
{
@ -463,7 +465,7 @@ QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, Qgs
else
{
//create empty layer
std::unique_ptr< QgsVectorLayerExporter > exporter( new QgsVectorLayerExporter( uri, providerKey, fields, geometryType, crs, true, options ) );
std::unique_ptr< QgsVectorLayerExporter > exporter( new QgsVectorLayerExporter( uri, providerKey, newFields, geometryType, crs, true, options, sinkFlags ) );
if ( exporter->errorCode() )
{
throw QgsProcessingException( QObject::tr( "Could not create layer %1: %2" ).arg( destination, exporter->errorMessage() ) );

View File

@ -44,7 +44,6 @@ class QgsProcessingFeatureSource;
*/
class CORE_EXPORT QgsProcessingUtils
{
public:
/**
@ -154,13 +153,13 @@ class CORE_EXPORT QgsProcessingUtils
* The caller takes responsibility for deleting the returned sink.
*/
#ifndef SIP_RUN
static QgsFeatureSink *createFeatureSink(
QString &destination,
QgsProcessingContext &context,
const QgsFields &fields,
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &crs,
const QVariantMap &createOptions = QVariantMap() ) SIP_FACTORY;
static QgsFeatureSink *createFeatureSink( QString &destination,
QgsProcessingContext &context,
const QgsFields &fields,
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &crs,
const QVariantMap &createOptions = QVariantMap(),
QgsFeatureSink::SinkFlags sinkFlags = nullptr ) SIP_FACTORY;
#endif
/**

View File

@ -34,6 +34,28 @@ class CORE_EXPORT QgsFeatureSink
{
public:
/**
* Flags that can be set on a QgsFeatureSink. Not all sinks may implement all flags.
*
* \since QGIS 3.4
*/
enum SinkFlag
{
/**
* This flag indicates, that a primary key field cannot be guaranteed to be unique and
* the sink should ignore it if somehow possible.
* This should for example be set for a geopackage file if the field "fid" has a risk
* to contain duplicate entries. In this case sinks like QgsVectorFileWriter or
* QgsVectorLayerExporter will prefer to regenerate the fid instead of trying to reuse
* the fids provided in addFeature calls.
*
* \since QGIS 3.4
*/
RegeneratePrimaryKey = 1 << 1,
};
Q_DECLARE_FLAGS( SinkFlags, SinkFlag )
//! Flags controlling how features are added to a sink.
enum Flag
{

View File

@ -102,6 +102,7 @@ QgsVectorFileWriter::QgsVectorFileWriter(
const QStringList &layerOptions,
QString *newFilename,
SymbologyExport symbologyExport,
QgsFeatureSink::SinkFlags sinkFlags,
QString *newLayer
)
: mError( NoError )
@ -111,7 +112,7 @@ QgsVectorFileWriter::QgsVectorFileWriter(
{
init( vectorFileName, fileEncoding, fields, geometryType,
srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr,
QString(), CreateOrOverwriteFile, newLayer );
QString(), CreateOrOverwriteFile, newLayer, sinkFlags );
}
QgsVectorFileWriter::QgsVectorFileWriter( const QString &vectorFileName,
@ -135,7 +136,7 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString &vectorFileName,
{
init( vectorFileName, fileEncoding, fields, geometryType, srs, driverName,
datasourceOptions, layerOptions, newFilename, fieldValueConverter,
layerName, action, newLayer );
layerName, action, newLayer, nullptr );
}
bool QgsVectorFileWriter::supportsFeatureStyles( const QString &driverName )
@ -171,7 +172,7 @@ void QgsVectorFileWriter::init( QString vectorFileName,
FieldValueConverter *fieldValueConverter,
const QString &layerNameIn,
ActionOnExistingFile action,
QString *newLayer )
QString *newLayer, SinkFlags sinkFlags )
{
mRenderContext.setRendererScale( mSymbologyScale );
@ -310,6 +311,7 @@ void QgsVectorFileWriter::init( QString vectorFileName,
}
options[ datasourceOptions.size()] = nullptr;
}
mAttrIdxToOgrIdx.remove( 0 );
// create the data source
if ( action == CreateOrOverwriteFile )
@ -694,6 +696,16 @@ void QgsVectorFileWriter::init( QString vectorFileName,
break;
}
// Geopackages require a unique feature id. If the input feature stream cannot guarantee
// the uniqueness of the FID column, we drop it and let OGR generate new ones
if ( sinkFlags.testFlag( QgsFeatureSink::RegeneratePrimaryKey ) && driverName == QLatin1String( "GPKG" ) )
{
int fidIdx = fields.lookupField( QStringLiteral( "FID" ) );
if ( fidIdx >= 0 )
mAttrIdxToOgrIdx.remove( fidIdx );
}
QgsDebugMsg( QStringLiteral( "Done creating fields" ) );
mWkbType = geometryType;

View File

@ -553,7 +553,8 @@ class CORE_EXPORT QgsVectorFileWriter : public QgsFeatureSink
const QStringList &datasourceOptions = QStringList(),
const QStringList &layerOptions = QStringList(),
QString *newFilename = nullptr,
QgsVectorFileWriter::SymbologyExport symbologyExport = QgsVectorFileWriter::NoSymbology
QgsVectorFileWriter::SymbologyExport symbologyExport = QgsVectorFileWriter::NoSymbology,
QgsFeatureSink::SinkFlags sinkFlags = nullptr
#ifndef SIP_RUN
, QString *newLayer = nullptr
#endif
@ -880,7 +881,7 @@ class CORE_EXPORT QgsVectorFileWriter : public QgsFeatureSink
QStringList layerOptions, QString *newFilename,
QgsVectorFileWriter::FieldValueConverter *fieldValueConverter,
const QString &layerName,
QgsVectorFileWriter::ActionOnExistingFile action, QString *newLayer );
QgsVectorFileWriter::ActionOnExistingFile action, QString *newLayer, QgsFeatureSink::SinkFlags sinkFlags );
void resetMap( const QgsAttributeList &attributes );
std::unique_ptr< QgsFeatureRenderer > mRenderer;

View File

@ -51,7 +51,8 @@ QgsVectorLayerExporter::QgsVectorLayerExporter( const QString &uri,
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &crs,
bool overwrite,
const QMap<QString, QVariant> &options )
const QMap<QString, QVariant> &options,
QgsFeatureSink::SinkFlags sinkFlags )
: mErrorCount( 0 )
, mAttributeCount( -1 )
@ -120,6 +121,22 @@ QgsVectorLayerExporter::QgsVectorLayerExporter( const QString &uri,
return;
}
// If the result is a geopackage layer and there is already a field name FID requested which
// might contain duplicates, make sure to generate a new field with a unique name instead
// that will be filled by ogr with unique values.
// HACK sorry
const QString path = QgsProviderRegistry::instance()->decodeUri( QStringLiteral( "ogr" ), uri ).value( QStringLiteral( "path" ) ).toString();
if ( sinkFlags.testFlag( QgsFeatureSink::SinkFlag::RegeneratePrimaryKey ) && path.endsWith( QLatin1String( ".gpkg" ), Qt::CaseInsensitive ) )
{
QString fidName = options.value( QStringLiteral( "FID" ), QStringLiteral( "FID" ) ).toString();
int fidIdx = vectorProvider->fields().lookupField( fidName );
if ( fidIdx != -1 )
{
mOldToNewAttrIdx.remove( fidIdx );
}
}
mProvider = vectorProvider;
mError = NoError;
}

View File

@ -98,6 +98,7 @@ class CORE_EXPORT QgsVectorLayerExporter : public QgsFeatureSink
* not available
* \param overwrite set to true to overwrite any existing data source
* \param options optional provider dataset options
* \param sinkFlags for how to add features
*/
QgsVectorLayerExporter( const QString &uri,
const QString &provider,
@ -105,7 +106,8 @@ class CORE_EXPORT QgsVectorLayerExporter : public QgsFeatureSink
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &crs,
bool overwrite = false,
const QMap<QString, QVariant> &options = QMap<QString, QVariant>() );
const QMap<QString, QVariant> &options = QMap<QString, QVariant>(),
QgsFeatureSink::SinkFlags sinkFlags = nullptr );
//! QgsVectorLayerExporter cannot be copied
QgsVectorLayerExporter( const QgsVectorLayerExporter &rh ) = delete;

View File

@ -24,6 +24,7 @@ from osgeo import gdal, ogr
from qgis.core import (QgsFeature,
QgsCoordinateReferenceSystem,
QgsFeatureRequest,
QgsFeatureSink,
QgsFields,
QgsField,
QgsFieldConstraints,
@ -1113,6 +1114,52 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
self.assertTrue(QgsGeometry.compare(provider_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001),
provider_extent.asPolygon()[0])
def testRegenerateFid(self):
""" Test regenerating feature ids """
fields = QgsFields()
fields.append(QgsField('fid', QVariant.Int))
fields.append(QgsField('f1', QVariant.Int))
tmpfile = os.path.join(self.basetestpath, 'testRegenerateFid.gpkg')
options = {}
options['update'] = True
options['driverName'] = 'GPKG'
options['layerName'] = 'table1'
exporter = QgsVectorLayerExporter(tmpfile, "ogr", fields, QgsWkbTypes.Polygon, QgsCoordinateReferenceSystem(3111), False, options, QgsFeatureSink.RegeneratePrimaryKey)
self.assertFalse(exporter.errorCode(),
'unexpected export error {}: {}'.format(exporter.errorCode(), exporter.errorMessage()))
feat = QgsFeature(fields)
feat['fid'] = 0
feat['f1'] = 10
exporter.addFeature(feat)
feat['fid'] = 0
feat['f1'] = 20
exporter.addFeature(feat)
feat['fid'] = 1
feat['f1'] = 30
exporter.addFeature(feat)
feat['fid'] = 1
feat['f1'] = 40
exporter.addFeature(feat)
del exporter
# make sure layers exist
lyr = QgsVectorLayer('{}|layername=table1'.format(tmpfile), "lyr1", "ogr")
self.assertTrue(lyr.isValid())
self.assertEqual(lyr.crs().authid(), 'EPSG:3111')
self.assertEqual(lyr.wkbType(), QgsWkbTypes.Polygon)
values = set([f['f1'] for f in lyr.getFeatures()])
self.assertEqual(values, set([10, 20, 30, 40]))
fids = set([f['fid'] for f in lyr.getFeatures()])
self.assertEqual(len(fids), 4)
def testTransaction(self):
tmpfile = os.path.join(self.basetestpath, 'testTransaction.gpkg')