mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
Merge pull request #8240 from m-kuhn/mergeVectorLayersDuplicateFid
Ignore FID field when merging vector layers to geopackage
This commit is contained in:
commit
429cd0bacd
@ -611,7 +611,7 @@ Evaluates the parameter with matching ``name`` to a static boolean value.
|
||||
%End
|
||||
|
||||
QgsFeatureSink *parameterAsSink( const QVariantMap ¶meters, 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;
|
||||
|
@ -588,7 +588,7 @@ Evaluates the parameter with matching ``definition`` and ``value`` to a static b
|
||||
|
||||
static QgsFeatureSink *parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters,
|
||||
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.
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
BIN
python/plugins/processing/tests/testdata/custom/pol.gpkg
vendored
Normal file
BIN
python/plugins/processing/tests/testdata/custom/pol.gpkg
vendored
Normal file
Binary file not shown.
BIN
python/plugins/processing/tests/testdata/expected/merged_pol.gpkg
vendored
Normal file
BIN
python/plugins/processing/tests/testdata/expected/merged_pol.gpkg
vendored
Normal file
Binary file not shown.
@ -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
|
||||
|
@ -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" ) ) );
|
||||
|
||||
|
@ -178,7 +178,7 @@ QVariantMap QgsMergeVectorAlgorithm::processAlgorithm( const QVariantMap ¶me
|
||||
}
|
||||
|
||||
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" ) ) );
|
||||
|
||||
|
@ -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() )
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -586,9 +586,9 @@ bool QgsProcessingAlgorithm::parameterAsBool( const QVariantMap ¶meters, con
|
||||
return QgsProcessingParameters::parameterAsBool( parameterDefinition( name ), parameters, context );
|
||||
}
|
||||
|
||||
QgsFeatureSink *QgsProcessingAlgorithm::parameterAsSink( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context, QString &destinationIdentifier, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs ) const
|
||||
QgsFeatureSink *QgsProcessingAlgorithm::parameterAsSink( const QVariantMap ¶meters, 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 ¶meters, 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" ) ) );
|
||||
|
||||
|
@ -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 ¶meters, 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
|
||||
|
@ -350,7 +350,7 @@ bool QgsProcessingParameters::parameterAsBool( const QgsProcessingParameterDefin
|
||||
|
||||
QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, 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;
|
||||
}
|
||||
|
||||
|
@ -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 ¶meters,
|
||||
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.
|
||||
|
@ -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() ) );
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user