[FEATURE] Vector layer save as: offer file/layer overwriting, new layer creation, feature and field appending

When saving a vector layer into an existing file, depending on the capabilities
of the output driver, the user can now decide whether:
- to overwrite the whole file
- to overwrite only the target layer (layer name is now configurable)
- to append features to the existing target layer
- to append features, add new fields if there are any.

All above is available for drivers like GPKG, SpatiaLite, FileGDB, ...
For drivers like Shapefile, MapInfo .tab, feature append is also available.
This commit is contained in:
Even Rouault 2016-10-13 00:16:22 +02:00
parent a5ffc6a856
commit 34894c6f5a
8 changed files with 964 additions and 73 deletions

View File

@ -132,6 +132,43 @@ class QgsVectorFileWriter
virtual QVariant convert( int fieldIdxInLayer, const QVariant& value );
};
/** Edition capability flags
* @note Added in QGIS 3.0 */
enum EditionCapability
{
/** Flag to indicate that a new layer can be added to the dataset */
CanAddNewLayer,
/** Flag to indicate that new features can be added to an existing layer */
CanAppendToExistingLayer ,
/** Flag to indicate that new fields can be added to an existing layer. Imply CanAppendToExistingLayer */
CanAddNewFieldsToExistingLayer,
/** Flag to indicate that an existing layer can be deleted */
CanDeleteLayer
};
typedef QFlags<QgsVectorFileWriter::EditionCapability> EditionCapabilities;
/** Enumeration to describe how to handle existing files
@note Added in QGIS 3.0
*/
enum ActionOnExistingFile
{
/** Create or overwrite file */
CreateOrOverwriteFile,
/** Create or overwrite layer */
CreateOrOverwriteLayer,
/** Append features to existing layer, but do not create new fields */
AppendToLayerNoNewFields,
/** Append features to existing layer, and create new fields if needed */
AppendToLayerAddFields
};
/** Write contents of vector layer to an (OGR supported) vector formt
* @param layer layer to write
* @param fileName file name to write to
@ -220,6 +257,88 @@ class QgsVectorFileWriter
FieldValueConverter* fieldValueConverter = nullptr
);
/**
* Options to pass to writeAsVectorFormat()
* @note Added in QGIS 3.0
*/
class SaveVectorOptions
{
public:
/** Constructor */
SaveVectorOptions();
/** Destructor */
virtual ~SaveVectorOptions();
/** OGR driver to use */
QString driverName;
/** Layer name. If let empty, it will be derived from the filename */
QString layerName;
/** Action on existing file */
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile;
/** Encoding to use */
QString fileEncoding;
/** Transform to reproject exported geometries with, or invalid transform
* for no transformation */
QgsCoordinateTransform ct;
/** Write only selected features of layer */
bool onlySelectedFeatures;
/** List of OGR data source creation options */
QStringList datasourceOptions;
/** List of OGR layer creation options */
QStringList layerOptions;
/** Only write geometries */
bool skipAttributeCreation;
/** Attributes to export (empty means all unless skipAttributeCreation is set) */
QgsAttributeList attributes;
/** Symbology to export */
QgsVectorFileWriter::SymbologyExport symbologyExport;
/** Scale of symbology */
double symbologyScale;
/** If not empty, only features intersecting the extent will be saved */
QgsRectangle filterExtent;
/** Set to a valid geometry type to override the default geometry type for the layer. This parameter
* allows for conversion of geometryless tables to null geometries, etc */
QgsWkbTypes::Type overrideGeometryType;
/** Set to true to force creation of multi* geometries */
bool forceMulti;
/** Set to true to include z dimension in output. This option is only valid if overrideGeometryType is set */
bool includeZ;
/** Field value converter */
QgsVectorFileWriter::FieldValueConverter* fieldValueConverter;
};
/** Writes a layer out to a vector file.
* @param layer source layer to write
* @param fileName file name to write to
* @param options options.
* @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
* @param errorMessage pointer to buffer fo error message
* @note added in 3.0
*/
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
const QString& fileName,
const SaveVectorOptions& options,
QString *newFilename = nullptr,
QString *errorMessage = nullptr );
/** Create a new vector file writer */
QgsVectorFileWriter( const QString& vectorFileName,
const QString& fileEncoding,
@ -294,6 +413,27 @@ class QgsVectorFileWriter
*/
static QStringList defaultLayerOptions( const QString& driverName );
/**
* Return edition capabilites for an existing dataset name.
* @note added in QGIS 3.0
*/
static EditionCapabilities editionCapabilities( const QString& datasetName );
/**
* Returns whether the target layer already exists.
* @note added in QGIS 3.0
*/
static bool targetLayerExists( const QString& datasetName,
const QString& layerName );
/**
* Returns whether there are among the attributes specified some that do not exist yet in the layer
* @note added in QGIS 3.0
*/
static bool areThereNewFieldsToCreate( const QString& datasetName,
const QString& layerName,
QgsVectorLayer* layer,
const QgsAttributeList& attributes );
protected:
//! @note not available in python bindings
// OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType );
@ -302,3 +442,5 @@ class QgsVectorFileWriter
QgsVectorFileWriter( const QgsVectorFileWriter& rh );
};
QFlags<QgsVectorFileWriter::EditionCapability> operator|(QgsVectorFileWriter::EditionCapability f1, QFlags<QgsVectorFileWriter::EditionCapability> f2);

View File

@ -23,6 +23,7 @@
#include "qgseditorwidgetfactory.h"
#include "qgseditorwidgetregistry.h"
#include <QMessageBox>
#include <QSettings>
#include <QFileDialog>
#include <QTextCodec>
@ -37,6 +38,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( long srsid, QWidget* par
, mLayer( 0 )
, mAttributeTableItemChangedSlotEnabled( true )
, mReplaceRawFieldValuesStateChangedSlotEnabled( true )
, mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
{
setup();
}
@ -46,6 +48,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( QgsVectorLayer *layer, i
, mLayer( layer )
, mAttributeTableItemChangedSlotEnabled( true )
, mReplaceRawFieldValuesStateChangedSlotEnabled( true )
, mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
{
if ( layer )
{
@ -210,6 +213,121 @@ QgsVectorLayerSaveAsDialog::~QgsVectorLayerSaveAsDialog()
void QgsVectorLayerSaveAsDialog::accept()
{
if ( QFile::exists( filename() ) )
{
QgsVectorFileWriter::EditionCapabilities caps =
QgsVectorFileWriter::editionCapabilities( filename() );
bool layerExists = QgsVectorFileWriter::targetLayerExists( filename(),
layername() );
if ( layerExists )
{
if ( !( caps & QgsVectorFileWriter::CanAppendToExistingLayer ) &&
( caps & QgsVectorFileWriter::CanDeleteLayer ) &&
( caps & QgsVectorFileWriter::CanAddNewLayer ) )
{
QMessageBox msgBox;
msgBox.setIcon( QMessageBox::Question );
msgBox.setWindowTitle( tr( "The layer already exists" ) );
msgBox.setText( tr( "Do you want to overwrite the whole file or overwrite the layer?" ) );
QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole );
msgBox.setStandardButtons( QMessageBox::Cancel );
msgBox.setDefaultButton( QMessageBox::Cancel );
int ret = msgBox.exec();
if ( ret == QMessageBox::Cancel )
return;
if ( msgBox.clickedButton() == overwriteFileButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
else if ( msgBox.clickedButton() == overwriteLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
}
else if ( !( caps & QgsVectorFileWriter::CanAppendToExistingLayer ) )
{
if ( QMessageBox::question( this,
tr( "The file already exists" ),
tr( "Do you want to overwrite the existing file?" ) ) == QMessageBox::NoButton )
{
return;
}
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
}
else if (( caps & QgsVectorFileWriter::CanDeleteLayer ) &&
( caps & QgsVectorFileWriter::CanAddNewLayer ) )
{
QMessageBox msgBox;
msgBox.setIcon( QMessageBox::Question );
msgBox.setWindowTitle( tr( "The layer already exists" ) );
msgBox.setText( tr( "Do you want to overwrite the whole file, overwrite the layer or append features to the layer?" ) );
QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole );
QPushButton *appendToLayerButton = msgBox.addButton( tr( "Append to layer" ), QMessageBox::ActionRole );
msgBox.setStandardButtons( QMessageBox::Cancel );
msgBox.setDefaultButton( QMessageBox::Cancel );
int ret = msgBox.exec();
if ( ret == QMessageBox::Cancel )
return;
if ( msgBox.clickedButton() == overwriteFileButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
else if ( msgBox.clickedButton() == overwriteLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
else if ( msgBox.clickedButton() == appendToLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerNoNewFields;
}
else
{
QMessageBox msgBox;
msgBox.setIcon( QMessageBox::Question );
msgBox.setWindowTitle( tr( "The layer already exists" ) );
msgBox.setText( tr( "Do you want to overwrite the whole file or append features to the layer?" ) );
QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
QPushButton *appendToLayerButton = msgBox.addButton( tr( "Append to layer" ), QMessageBox::ActionRole );
msgBox.setStandardButtons( QMessageBox::Cancel );
msgBox.setDefaultButton( QMessageBox::Cancel );
int ret = msgBox.exec();
if ( ret == QMessageBox::Cancel )
return;
if ( msgBox.clickedButton() == overwriteFileButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
else if ( msgBox.clickedButton() == appendToLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerNoNewFields;
}
if ( mActionOnExistingFile == QgsVectorFileWriter::AppendToLayerNoNewFields )
{
if ( QgsVectorFileWriter::areThereNewFieldsToCreate( filename(),
layername(),
mLayer,
selectedAttributes() ) )
{
if ( QMessageBox::question( this,
tr( "The existing layer has different fields" ),
tr( "Do you want to add the missing fields to the layer?" ) ) == QMessageBox::Yes )
{
mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerAddFields;
}
}
}
}
else
{
if (( caps & QgsVectorFileWriter::CanAddNewLayer ) )
{
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
}
else
{
if ( QMessageBox::question( this,
tr( "The file already exists" ),
tr( "Do you want to overwrite the existing file?" ) ) == QMessageBox::NoButton )
{
return;
}
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
}
}
}
QSettings settings;
settings.setValue( "/UI/lastVectorFileFilterDir", QFileInfo( filename() ).absolutePath() );
settings.setValue( "/UI/lastVectorFormat", format() );
@ -226,12 +344,13 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
bool selectAllFields = true;
bool fieldsAsDisplayedValues = false;
if ( format() == "KML" )
const QString sFormat( format() );
if ( sFormat == "KML" )
{
mAttributesSelection->setEnabled( true );
selectAllFields = false;
}
else if ( format() == "DXF" )
else if ( sFormat == "DXF" )
{
mAttributesSelection->setEnabled( false );
selectAllFields = false;
@ -239,7 +358,23 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
else
{
mAttributesSelection->setEnabled( true );
fieldsAsDisplayedValues = ( format() == "CSV" || format() == "XLS" || format() == "XLSX" || format() == "ODS" );
fieldsAsDisplayedValues = ( sFormat == "CSV" || sFormat == "XLS" || sFormat == "XLSX" || sFormat == "ODS" );
}
leLayername->setEnabled( sFormat == "KML" ||
sFormat == "GPKG" ||
sFormat == "XLSX" ||
sFormat == "ODS" ||
sFormat == "FileGDB" ||
sFormat == "SQLite" ||
sFormat == "SpatiaLite" );
if ( !leLayername->isEnabled() )
leLayername->setText( QString() );
else if ( leLayername->text().isEmpty() &&
!leFilename->text().isEmpty() )
{
QString layerName = QFileInfo( leFilename->text() ).baseName();
leLayername->setText( layerName ) ;
}
if ( mLayer )
@ -485,7 +620,14 @@ void QgsVectorLayerSaveAsDialog::on_mAttributeTable_itemChanged( QTableWidgetIte
void QgsVectorLayerSaveAsDialog::on_leFilename_textChanged( const QString& text )
{
buttonBox->button( QDialogButtonBox::Ok )->setEnabled( QFileInfo( text ).absoluteDir().exists() );
buttonBox->button( QDialogButtonBox::Ok )->setEnabled(
!text.isEmpty() && QFileInfo( text ).absoluteDir().exists() );
if ( leLayername->isEnabled() )
{
QString layerName = QFileInfo( text ).baseName();
leLayername->setText( layerName );
}
}
void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked()
@ -493,7 +635,7 @@ void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked()
QSettings settings;
QString dirName = leFilename->text().isEmpty() ? settings.value( "/UI/lastVectorFileFilterDir", QDir::homePath() ).toString() : leFilename->text();
QString filterString = QgsVectorFileWriter::filterForDriver( format() );
QString outputFile = QFileDialog::getSaveFileName( nullptr, tr( "Save layer as..." ), dirName, filterString );
QString outputFile = QFileDialog::getSaveFileName( nullptr, tr( "Save layer as..." ), dirName, filterString, nullptr, QFileDialog::DontConfirmOverwrite );
if ( !outputFile.isNull() )
{
leFilename->setText( outputFile );
@ -511,6 +653,11 @@ QString QgsVectorLayerSaveAsDialog::filename() const
return leFilename->text();
}
QString QgsVectorLayerSaveAsDialog::layername() const
{
return leLayername->text();
}
QString QgsVectorLayerSaveAsDialog::encoding() const
{
return mEncodingComboBox->currentText();
@ -611,7 +758,7 @@ QStringList QgsVectorLayerSaveAsDialog::layerOptions() const
case QgsVectorFileWriter::String:
{
QLineEdit* le = mLayerOptionsGroupBox->findChild<QLineEdit*>( it.key() );
if ( le )
if ( le && !le->text().isEmpty() )
options << QString( "%1=%2" ).arg( it.key(), le->text() );
break;
}
@ -730,6 +877,11 @@ bool QgsVectorLayerSaveAsDialog::includeZ() const
return mIncludeZCheckBox->isChecked();
}
QgsVectorFileWriter::ActionOnExistingFile QgsVectorLayerSaveAsDialog::creationActionOnExistingFile() const
{
return mActionOnExistingFile;
}
void QgsVectorLayerSaveAsDialog::setIncludeZ( bool checked )
{
mIncludeZCheckBox->setChecked( checked );

View File

@ -48,6 +48,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
QString format() const;
QString encoding() const;
QString filename() const;
QString layername() const;
QStringList datasourceOptions() const;
QStringList layerOptions() const;
long crs() const;
@ -100,6 +101,9 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
*/
void setIncludeZ( bool checked );
/** Returns creation action */
QgsVectorFileWriter::ActionOnExistingFile creationActionOnExistingFile() const;
private slots:
void on_mFormatComboBox_currentIndexChanged( int idx );
@ -126,6 +130,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
QgsVectorLayer *mLayer;
bool mAttributeTableItemChangedSlotEnabled;
bool mReplaceRawFieldValuesStateChangedSlotEnabled;
QgsVectorFileWriter::ActionOnExistingFile mActionOnExistingFile;
};
#endif // QGSVECTORLAYERSAVEASDIALOG_H

View File

@ -3521,7 +3521,11 @@ bool QgisApp::addVectorLayers( const QStringList &theLayerQStringList, const QSt
QString base;
if ( dataSourceType == "file" )
{
QFileInfo fi( src );
QString srcWithoutLayername( src );
int posPipe = srcWithoutLayername.indexOf( '|' );
if ( posPipe >= 0 )
srcWithoutLayername.resize( posPipe );
QFileInfo fi( srcWithoutLayername );
base = fi.completeBaseName();
// if needed prompt for zipitem layers
@ -6228,22 +6232,29 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
// No need to use the converter if there is nothing to convert
if ( !dialog->attributesAsDisplayedValues().isEmpty() )
converterPtr = &converter;
QgsVectorFileWriter::SaveVectorOptions options;
options.driverName = format;
options.layerName = dialog->layername();
options.actionOnExistingFile = dialog->creationActionOnExistingFile();
options.fileEncoding = encoding;
options.ct = ct;
options.onlySelectedFeatures = dialog->onlySelected();
options.datasourceOptions = datasourceOptions;
options.layerOptions = dialog->layerOptions();
options.skipAttributeCreation = dialog->selectedAttributes().isEmpty();
options.symbologyExport = static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() );
options.symbologyScale = dialog->scaleDenominator();
if ( dialog->hasFilterExtent() )
options.filterExtent = filterExtent;
options.overrideGeometryType = autoGeometryType ? QgsWkbTypes::Unknown : forcedGeometryType;
options.forceMulti = dialog->forceMulti();
options.includeZ = dialog->includeZ();
options.attributes = dialog->selectedAttributes();
options.fieldValueConverter = converterPtr;
error = QgsVectorFileWriter::writeAsVectorFormat(
vlayer, vectorFilename, encoding, ct, format,
dialog->onlySelected(),
&errorMessage,
datasourceOptions, dialog->layerOptions(),
dialog->selectedAttributes().isEmpty(),
&newFilename,
static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() ),
dialog->scaleDenominator(),
dialog->hasFilterExtent() ? &filterExtent : nullptr,
autoGeometryType ? QgsWkbTypes::Unknown : forcedGeometryType,
dialog->forceMulti(),
dialog->includeZ(),
dialog->selectedAttributes(),
converterPtr
);
vlayer, vectorFilename, options, &newFilename, &errorMessage );
QApplication::restoreOverrideCursor();
@ -6251,7 +6262,10 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
{
if ( dialog->addToCanvas() )
{
addVectorLayers( QStringList( newFilename ), encoding, "file" );
QString uri( newFilename );
if ( !dialog->layername().isEmpty() )
uri += "|layername=" + dialog->layername();
addVectorLayers( QStringList( uri ), encoding, "file" );
}
emit layerSavedAs( vlayer, vectorFilename );
messageBar()->pushMessage( tr( "Saving done" ),

View File

@ -98,7 +98,8 @@ QgsVectorFileWriter::QgsVectorFileWriter(
, mFieldValueConverter( nullptr )
{
init( theVectorFileName, theFileEncoding, fields, geometryType ,
srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr );
srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr,
QString(), CreateOrOverwriteFile );
}
QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
@ -111,7 +112,9 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
const QStringList& layerOptions,
QString* newFilename,
QgsVectorFileWriter::SymbologyExport symbologyExport,
FieldValueConverter* fieldValueConverter )
FieldValueConverter* fieldValueConverter,
const QString& layerName,
ActionOnExistingFile action )
: mDS( nullptr )
, mLayer( nullptr )
, mOgrRef( nullptr )
@ -124,7 +127,8 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
, mFieldValueConverter( nullptr )
{
init( vectorFileName, fileEncoding, fields, geometryType, srs, driverName,
datasourceOptions, layerOptions, newFilename, fieldValueConverter );
datasourceOptions, layerOptions, newFilename, fieldValueConverter,
layerName, action );
}
void QgsVectorFileWriter::init( QString vectorFileName,
@ -136,7 +140,9 @@ void QgsVectorFileWriter::init( QString vectorFileName,
QStringList datasourceOptions,
QStringList layerOptions,
QString* newFilename,
FieldValueConverter* fieldValueConverter )
FieldValueConverter* fieldValueConverter,
const QString& layerNameIn,
ActionOnExistingFile action )
{
mRenderContext.setRendererScale( mSymbologyScaleDenominator );
@ -224,7 +230,8 @@ void QgsVectorFileWriter::init( QString vectorFileName,
}
#endif
deleteShapeFile( vectorFileName );
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
deleteShapeFile( vectorFileName );
}
else
{
@ -247,7 +254,27 @@ void QgsVectorFileWriter::init( QString vectorFileName,
}
}
QFile::remove( vectorFileName );
if ( action == CreateOrOverwriteFile )
{
if ( vectorFileName.endsWith( ".gdb", Qt::CaseInsensitive ) )
{
QDir dir( vectorFileName );
if ( dir.exists() )
{
QFileInfoList fileList = dir.entryInfoList(
QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst );
Q_FOREACH ( QFileInfo info, fileList )
{
QFile::remove( info.absoluteFilePath() );
}
}
QDir().rmdir( vectorFileName );
}
else
{
QFile::remove( vectorFileName );
}
}
}
if ( metadataFound && !metadata.compulsoryEncoding.isEmpty() )
@ -272,7 +299,10 @@ void QgsVectorFileWriter::init( QString vectorFileName,
}
// create the data source
mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options );
if ( action == CreateOrOverwriteFile )
mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options );
else
mDS = OGROpen( TO8F( vectorFileName ), TRUE, nullptr );
if ( options )
{
@ -285,12 +315,43 @@ void QgsVectorFileWriter::init( QString vectorFileName,
if ( !mDS )
{
mError = ErrCreateDataSource;
mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
if ( action == CreateOrOverwriteFile )
mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
else
mErrorMessage = QObject::tr( "opening of data source in update mode failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
return;
}
QgsDebugMsg( "Created data source" );
QString layerName( layerNameIn );
if ( layerName.isEmpty() )
layerName = QFileInfo( vectorFileName ).baseName();
if ( action == CreateOrOverwriteLayer )
{
const int layer_count = OGR_DS_GetLayerCount( mDS );
for ( int i = 0; i < layer_count; i++ )
{
OGRLayerH hLayer = OGR_DS_GetLayer( mDS, i );
if ( EQUAL( OGR_L_GetName( hLayer ), TO8F( layerName ) ) )
{
if ( OGR_DS_DeleteLayer( mDS, i ) != OGRERR_NONE )
{
mError = ErrCreateLayer;
mErrorMessage = QObject::tr( "overwriting of existing layer failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
return;
}
break;
}
}
}
if ( action == CreateOrOverwriteFile )
QgsDebugMsg( "Created data source" );
else
QgsDebugMsg( "Opened data source in update mode" );
// use appropriate codec
mCodec = QTextCodec::codecForName( fileEncoding.toLocal8Bit().constData() );
@ -318,7 +379,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
}
// datasource created, now create the output layer
QString layerName = QFileInfo( vectorFileName ).baseName();
OGRwkbGeometryType wkbType = ogrTypeFromWkbType( geometryType );
// Remove FEATURE_DATASET layer option (used for ESRI File GDB driver) if its value is not set
@ -341,7 +401,10 @@ void QgsVectorFileWriter::init( QString vectorFileName,
// disable encoding conversion of OGR Shapefile layer
CPLSetConfigOption( "SHAPE_ENCODING", "" );
mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options );
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options );
else
mLayer = OGR_DS_GetLayerByName( mDS, TO8F( layerName ) );
if ( options )
{
@ -378,8 +441,12 @@ void QgsVectorFileWriter::init( QString vectorFileName,
if ( !mLayer )
{
mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
else
mErrorMessage = QObject::tr( "opening of layer failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
mError = ErrCreateLayer;
return;
}
@ -397,17 +464,30 @@ void QgsVectorFileWriter::init( QString vectorFileName,
mFieldValueConverter = fieldValueConverter;
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
for ( int fldIdx = 0; ( action == CreateOrOverwriteFile ||
action == CreateOrOverwriteLayer ||
action == AppendToLayerAddFields ) &&
fldIdx < fields.count(); ++fldIdx )
{
QgsField attrField = fields.at( fldIdx );
OGRFieldType ogrType = OFTString; //default to string
if ( fieldValueConverter )
{
attrField = fieldValueConverter->fieldDefinition( fields.at( fldIdx ) );
}
QString name( attrField.name() );
if ( action == AppendToLayerAddFields )
{
int ogrIdx = OGR_FD_GetFieldIndex( defn, mCodec->fromUnicode( name ) );
if ( ogrIdx >= 0 )
{
mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
continue;
}
}
OGRFieldType ogrType = OFTString; //default to string
int ogrWidth = attrField.length();
int ogrPrecision = attrField.precision();
if ( ogrPrecision > 0 )
@ -486,8 +566,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
return;
}
QString name( attrField.name() );
if ( mOgrDriverName == "SQLite" && name.compare( "ogc_fid", Qt::CaseInsensitive ) == 0 )
{
int i;
@ -581,6 +659,18 @@ void QgsVectorFileWriter::init( QString vectorFileName,
mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
}
if ( action == AppendToLayerNoNewFields )
{
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
{
QgsField attrField = fields.at( fldIdx );
QString name( attrField.name() );
int ogrIdx = OGR_FD_GetFieldIndex( defn, mCodec->fromUnicode( name ) );
if ( ogrIdx >= 0 )
mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
}
}
QgsDebugMsg( "Done creating fields" );
mWkbType = geometryType;
@ -2072,7 +2162,8 @@ void QgsVectorFileWriter::resetMap( const QgsAttributeList &attributes )
mAttrIdxToOgrIdx.clear();
for ( int i = 0; i < attributes.size(); i++ )
{
mAttrIdxToOgrIdx.insert( attributes[i], omap[i] );
if ( omap.find( i ) != omap.end() )
mAttrIdxToOgrIdx.insert( attributes[i], omap[i] );
}
}
@ -2161,6 +2252,57 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
bool includeZ,
QgsAttributeList attributes,
FieldValueConverter* fieldValueConverter )
{
SaveVectorOptions options;
options.fileEncoding = fileEncoding;
options.ct = ct;
options.driverName = driverName;
options.onlySelectedFeatures = onlySelected;
options.datasourceOptions = datasourceOptions;
options.layerOptions = layerOptions;
options.skipAttributeCreation = skipAttributeCreation;
options.symbologyExport = symbologyExport;
options.symbologyScale = symbologyScale;
if ( filterExtent )
options.filterExtent = *filterExtent;
options.overrideGeometryType = overrideGeometryType;
options.forceMulti = forceMulti;
options.includeZ = includeZ;
options.attributes = attributes;
options.fieldValueConverter = fieldValueConverter;
return writeAsVectorFormat( layer, fileName, options, newFilename, errorMessage );
}
QgsVectorFileWriter::SaveVectorOptions::SaveVectorOptions()
: driverName( "ESRI Shapefile" )
, layerName( QString() )
, actionOnExistingFile( CreateOrOverwriteFile )
, fileEncoding( QString() )
, ct( QgsCoordinateTransform() )
, onlySelectedFeatures( false )
, datasourceOptions( QStringList() )
, layerOptions( QStringList() )
, skipAttributeCreation( false )
, attributes( QgsAttributeList() )
, symbologyExport( NoSymbology )
, symbologyScale( 1.0 )
, filterExtent( QgsRectangle() )
, overrideGeometryType( QgsWkbTypes::Unknown )
, forceMulti( false )
, fieldValueConverter( nullptr )
{
}
QgsVectorFileWriter::SaveVectorOptions::~SaveVectorOptions()
{
}
QgsVectorFileWriter::WriterError
QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer* layer,
const QString& fileName,
const SaveVectorOptions& options,
QString *newFilename,
QString *errorMessage )
{
if ( !layer )
{
@ -2169,10 +2311,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
bool shallTransform = false;
QgsCoordinateReferenceSystem outputCRS;
if ( ct.isValid() )
if ( options.ct.isValid() )
{
// This means we should transform
outputCRS = ct.destinationCrs();
outputCRS = options.ct.destinationCrs();
shallTransform = true;
}
else
@ -2182,18 +2324,19 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
}
QgsWkbTypes::Type destWkbType = layer->wkbType();
if ( overrideGeometryType != QgsWkbTypes::Unknown )
if ( options.overrideGeometryType != QgsWkbTypes::Unknown )
{
destWkbType = QgsWkbTypes::flatType( overrideGeometryType );
if ( QgsWkbTypes::hasZ( overrideGeometryType ) || includeZ )
destWkbType = QgsWkbTypes::flatType( options.overrideGeometryType );
if ( QgsWkbTypes::hasZ( options.overrideGeometryType ) || options.includeZ )
destWkbType = QgsWkbTypes::addZ( destWkbType );
}
if ( forceMulti )
if ( options.forceMulti )
{
destWkbType = QgsWkbTypes::multiType( destWkbType );
}
if ( skipAttributeCreation )
QgsAttributeList attributes( options.attributes );
if ( options.skipAttributeCreation )
attributes.clear();
else if ( attributes.isEmpty() )
{
@ -2231,7 +2374,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
if ( layer->storageType() == "ESRI Shapefile" && !QgsWkbTypes::isMultiType( destWkbType ) )
{
QgsFeatureRequest req;
if ( onlySelected )
if ( options.onlySelectedFeatures )
{
req.setFilterFids( layer->selectedFeaturesIds() );
}
@ -2265,11 +2408,17 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
}
QgsVectorFileWriter* writer =
new QgsVectorFileWriter( fileName, fileEncoding, fields, destWkbType,
outputCRS, driverName, datasourceOptions, layerOptions,
newFilename, symbologyExport,
fieldValueConverter );
writer->setSymbologyScaleDenominator( symbologyScale );
new QgsVectorFileWriter( fileName,
options.fileEncoding, fields, destWkbType,
outputCRS, options.driverName,
options.datasourceOptions,
options.layerOptions,
newFilename,
options.symbologyExport,
options.fieldValueConverter,
options.layerName,
options.actionOnExistingFile );
writer->setSymbologyScaleDenominator( options.symbologyScale );
if ( newFilename )
{
@ -2302,7 +2451,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
req.setFlags( QgsFeatureRequest::NoGeometry );
}
req.setSubsetOfAttributes( attributes );
if ( onlySelected )
if ( options.onlySelectedFeatures )
req.setFilterFids( layer->selectedFeaturesIds() );
QgsFeatureIterator fit = layer->getFeatures( req );
@ -2318,7 +2467,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
if ( r->capabilities() & QgsFeatureRenderer::SymbolLevels
&& r->usingSymbolLevels() )
{
QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, ct, errorMessage );
QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, options.ct, errorMessage );
delete writer;
return ( error == NoError ) ? NoError : ErrFeatureWriteFailed;
}
@ -2328,9 +2477,9 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
//unit type
QgsUnitTypes::DistanceUnit mapUnits = layer->crs().mapUnits();
if ( ct.isValid() )
if ( options.ct.isValid() )
{
mapUnits = ct.destinationCrs().mapUnits();
mapUnits = options.ct.destinationCrs().mapUnits();
}
writer->startRender( layer );
@ -2358,7 +2507,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
if ( fet.hasGeometry() )
{
QgsGeometry g = fet.geometry();
g.transform( ct );
g.transform( options.ct );
fet.setGeometry( g );
}
}
@ -2376,10 +2525,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
}
}
if ( fet.hasGeometry() && filterExtent && !fet.geometry().intersects( *filterExtent ) )
if ( fet.hasGeometry() && !options.filterExtent.isNull() && !fet.geometry().intersects( options.filterExtent ) )
continue;
if ( attributes.size() < 1 && skipAttributeCreation )
if ( attributes.size() < 1 && options.skipAttributeCreation )
{
fet.initAttributes( 0 );
}
@ -2927,3 +3076,91 @@ QStringList QgsVectorFileWriter::concatenateOptions( const QMap<QString, QgsVect
return list;
}
QgsVectorFileWriter::EditionCapabilities QgsVectorFileWriter::editionCapabilities( const QString& datasetName )
{
OGRSFDriverH hDriver = nullptr;
OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
if ( !hDS )
return 0;
QString drvName = OGR_Dr_GetName( hDriver );
QgsVectorFileWriter::EditionCapabilities caps = 0;
if ( OGR_DS_TestCapability( hDS, ODsCCreateLayer ) )
{
// Shapefile driver returns True for a "foo.shp" dataset name,
// creating "bar.shp" new layer, but this would be a bit confusing
// for the user, so pretent that it does not support that
if ( !( drvName == "ESRI Shapefile" && QFile::exists( datasetName ) ) )
caps |= CanAddNewLayer;
}
if ( OGR_DS_TestCapability( hDS, ODsCDeleteLayer ) )
{
caps |= CanDeleteLayer;
}
int layer_count = OGR_DS_GetLayerCount( hDS );
if ( layer_count )
{
OGRLayerH hLayer = OGR_DS_GetLayer( hDS, 0 );
if ( hLayer )
{
if ( OGR_L_TestCapability( hLayer, OLCSequentialWrite ) )
{
caps |= CanAppendToExistingLayer;
if ( OGR_L_TestCapability( hLayer, OLCCreateField ) )
{
caps |= CanAddNewFieldsToExistingLayer;
}
}
}
}
OGR_DS_Destroy( hDS );
return caps;
}
bool QgsVectorFileWriter::targetLayerExists( const QString& datasetName,
const QString& layerNameIn )
{
OGRSFDriverH hDriver = nullptr;
OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
if ( !hDS )
return false;
QString layerName( layerNameIn );
if ( layerName.isEmpty() )
layerName = QFileInfo( datasetName ).baseName();
bool ret = OGR_DS_GetLayerByName( hDS, TO8F( layerName ) );
OGR_DS_Destroy( hDS );
return ret;
}
bool QgsVectorFileWriter::areThereNewFieldsToCreate( const QString& datasetName,
const QString& layerName,
QgsVectorLayer* layer,
const QgsAttributeList& attributes )
{
OGRSFDriverH hDriver = nullptr;
OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
if ( !hDS )
return false;
OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, TO8F( layerName ) );
if ( !hLayer )
{
OGR_DS_Destroy( hDS );
return false;
}
bool ret = false;
OGRFeatureDefnH defn = OGR_L_GetLayerDefn( hLayer );
Q_FOREACH ( int idx, attributes )
{
QgsField fld = layer->fields().at( idx );
if ( OGR_FD_GetFieldIndex( defn, TO8F( fld.name() ) ) < 0 )
{
ret = true;
break;
}
}
OGR_DS_Destroy( hDS );
return ret;
}

View File

@ -199,6 +199,45 @@ class CORE_EXPORT QgsVectorFileWriter
virtual QVariant convert( int fieldIdxInLayer, const QVariant& value );
};
/** Edition capability flags
* @note Added in QGIS 3.0 */
enum EditionCapability
{
/** Flag to indicate that a new layer can be added to the dataset */
CanAddNewLayer = 1 << 0,
/** Flag to indicate that new features can be added to an existing layer */
CanAppendToExistingLayer = 1 << 1,
/** Flag to indicate that new fields can be added to an existing layer. Imply CanAppendToExistingLayer */
CanAddNewFieldsToExistingLayer = 1 << 2,
/** Flag to indicate that an existing layer can be deleted */
CanDeleteLayer = 1 << 3
};
/** Combination of CanAddNewLayer, CanAppendToExistingLayer, CanAddNewFieldsToExistingLayer or CanDeleteLayer
* @note Added in QGIS 3.0 */
Q_DECLARE_FLAGS( EditionCapabilities, EditionCapability )
/** Enumeration to describe how to handle existing files
@note Added in QGIS 3.0
*/
typedef enum
{
/** Create or overwrite file */
CreateOrOverwriteFile,
/** Create or overwrite layer */
CreateOrOverwriteLayer,
/** Append features to existing layer, but do not create new fields */
AppendToLayerNoNewFields,
/** Append features to existing layer, and create new fields if needed */
AppendToLayerAddFields
} ActionOnExistingFile;
/** Write contents of vector layer to an (OGR supported) vector formt
* @param layer layer to write
* @param fileName file name to write to
@ -287,6 +326,88 @@ class CORE_EXPORT QgsVectorFileWriter
FieldValueConverter* fieldValueConverter = nullptr
);
/** \ingroup core
* Options to pass to writeAsVectorFormat()
* @note Added in QGIS 3.0
*/
class CORE_EXPORT SaveVectorOptions
{
public:
/** Constructor */
SaveVectorOptions();
/** Destructor */
virtual ~SaveVectorOptions();
/** OGR driver to use */
QString driverName;
/** Layer name. If let empty, it will be derived from the filename */
QString layerName;
/** Action on existing file */
ActionOnExistingFile actionOnExistingFile;
/** Encoding to use */
QString fileEncoding;
/** Transform to reproject exported geometries with, or invalid transform
* for no transformation */
QgsCoordinateTransform ct;
/** Write only selected features of layer */
bool onlySelectedFeatures;
/** List of OGR data source creation options */
QStringList datasourceOptions;
/** List of OGR layer creation options */
QStringList layerOptions;
/** Only write geometries */
bool skipAttributeCreation;
/** Attributes to export (empty means all unless skipAttributeCreation is set) */
QgsAttributeList attributes;
/** Symbology to export */
SymbologyExport symbologyExport;
/** Scale of symbology */
double symbologyScale;
/** If not empty, only features intersecting the extent will be saved */
QgsRectangle filterExtent;
/** Set to a valid geometry type to override the default geometry type for the layer. This parameter
* allows for conversion of geometryless tables to null geometries, etc */
QgsWkbTypes::Type overrideGeometryType;
/** Set to true to force creation of multi* geometries */
bool forceMulti;
/** Set to true to include z dimension in output. This option is only valid if overrideGeometryType is set */
bool includeZ;
/** Field value converter */
FieldValueConverter* fieldValueConverter;
};
/** Writes a layer out to a vector file.
* @param layer source layer to write
* @param fileName file name to write to
* @param options options.
* @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
* @param errorMessage pointer to buffer fo error message
* @note added in 3.0
*/
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
const QString& fileName,
const SaveVectorOptions& options,
QString *newFilename = nullptr,
QString *errorMessage = nullptr );
/** Create a new vector file writer */
QgsVectorFileWriter( const QString& vectorFileName,
const QString& fileEncoding,
@ -369,6 +490,28 @@ class CORE_EXPORT QgsVectorFileWriter
*/
static OGRwkbGeometryType ogrTypeFromWkbType( QgsWkbTypes::Type type );
/**
* Return edition capabilites for an existing dataset name.
* @note added in QGIS 3.0
*/
static EditionCapabilities editionCapabilities( const QString& datasetName );
/**
* Returns whether the target layer already exists.
* @note added in QGIS 3.0
*/
static bool targetLayerExists( const QString& datasetName,
const QString& layerName );
/**
* Returns whether there are among the attributes specified some that do not exist yet in the layer
* @note added in QGIS 3.0
*/
static bool areThereNewFieldsToCreate( const QString& datasetName,
const QString& layerName,
QgsVectorLayer* layer,
const QgsAttributeList& attributes );
protected:
//! @note not available in python bindings
OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType );
@ -420,6 +563,8 @@ class CORE_EXPORT QgsVectorFileWriter
* @param newFilename potentially modified file name (output parameter)
* @param symbologyExport symbology to export
* @param fieldValueConverter field value converter (added in QGIS 2.16)
* @param layerName layer name. If let empty, it will be derived from the filename (added in QGIS 3.0)
* @param action action on existing file (added in QGIS 3.0)
*/
QgsVectorFileWriter( const QString& vectorFileName,
const QString& fileEncoding,
@ -431,14 +576,18 @@ class CORE_EXPORT QgsVectorFileWriter
const QStringList &layerOptions,
QString *newFilename,
SymbologyExport symbologyExport,
FieldValueConverter* fieldValueConverter
FieldValueConverter* fieldValueConverter,
const QString& layerName,
ActionOnExistingFile action
);
void init( QString vectorFileName, QString fileEncoding, const QgsFields& fields,
QgsWkbTypes::Type geometryType, QgsCoordinateReferenceSystem srs,
const QString& driverName, QStringList datasourceOptions,
QStringList layerOptions, QString* newFilename,
FieldValueConverter* fieldValueConverter );
FieldValueConverter* fieldValueConverter,
const QString& layerName,
ActionOnExistingFile action );
void resetMap( const QgsAttributeList &attributes );
QgsRenderContext mRenderContext;
@ -467,4 +616,6 @@ class CORE_EXPORT QgsVectorFileWriter
static QStringList concatenateOptions( const QMap<QString, Option*>& options );
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorFileWriter::EditionCapabilities )
#endif

View File

@ -23,21 +23,21 @@
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout_4">
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>CRS</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="1" column="2">
<widget class="QLineEdit" name="leFilename">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<item row="1" column="3">
<widget class="QPushButton" name="browseFilename">
<property name="enabled">
<bool>false</bool>
@ -57,26 +57,43 @@
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<item row="0" column="2" colspan="2">
<widget class="QComboBox" name="mFormatComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Save as</string>
<string>File name</string>
</property>
<property name="buddy">
<cstring>leFilename</cstring>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<item row="3" column="2" colspan="2">
<widget class="QgsProjectionSelectionWidget" name="mCrsSelector" native="true">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Layer name</string>
</property>
<property name="buddy">
<cstring>leFilename</cstring>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QLineEdit" name="leLayername">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -90,8 +107,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>550</width>
<height>953</height>
<width>557</width>
<height>922</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -434,6 +451,7 @@
<tabstop>mFormatComboBox</tabstop>
<tabstop>leFilename</tabstop>
<tabstop>browseFilename</tabstop>
<tabstop>leLayername</tabstop>
<tabstop>mCrsSelector</tabstop>
<tabstop>scrollArea</tabstop>
<tabstop>mEncodingComboBox</tabstop>

View File

@ -29,6 +29,7 @@ from qgis.core import (QgsVectorLayer,
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir
import os
import osgeo.gdal
from osgeo import gdal, ogr
import platform
from qgis.testing import start_app, unittest
from utilities import writeShape, compareWkt
@ -485,6 +486,177 @@ class TestQgsVectorLayer(unittest.TestCase):
options = QgsVectorFileWriter.defaultLayerOptions('GML')
self.assertEqual(options, [])
def testOverwriteLayer(self):
"""Tests writing a layer with a field value converter."""
ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([1])
provider.addFeatures([ft])
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename, update=1)
lyr = ds.GetLayerByName('test')
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 1)
ds.CreateLayer('another_layer')
del f
del lyr
del ds
caps = QgsVectorFileWriter.editionCapabilities(filename)
self.assertTrue((caps & QgsVectorFileWriter.CanAddNewLayer))
self.assertTrue((caps & QgsVectorFileWriter.CanAppendToExistingLayer))
self.assertTrue((caps & QgsVectorFileWriter.CanAddNewFieldsToExistingLayer))
self.assertTrue((caps & QgsVectorFileWriter.CanDeleteLayer))
self.assertTrue(QgsVectorFileWriter.targetLayerExists(filename, 'test'))
self.assertFalse(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0]))
# Test CreateOrOverwriteLayer
ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([2])
provider.addFeatures([ft])
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 2)
# another_layer should still exist
self.assertIsNotNone(ds.GetLayerByName('another_layer'))
del f
del lyr
del ds
# Test CreateOrOverwriteFile
ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([3])
provider.addFeatures([ft])
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 3)
# another_layer should no longer exist
self.assertIsNone(ds.GetLayerByName('another_layer'))
del f
del lyr
del ds
# Test AppendToLayerNoNewFields
ml = QgsVectorLayer('Point?field=firstfield:int&field=secondfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([4, -10])
provider.addFeatures([ft])
self.assertTrue(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0, 1]))
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
options.actionOnExistingFile = QgsVectorFileWriter.AppendToLayerNoNewFields
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertEqual(lyr.GetLayerDefn().GetFieldCount(), 1)
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 3)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 4)
del f
del lyr
del ds
# Test AppendToLayerAddFields
ml = QgsVectorLayer('Point?field=firstfield:int&field=secondfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([5, -1])
provider.addFeatures([ft])
self.assertTrue(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0, 1]))
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
options.actionOnExistingFile = QgsVectorFileWriter.AppendToLayerAddFields
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertEqual(lyr.GetLayerDefn().GetFieldCount(), 2)
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 3)
self.assertFalse(f.IsFieldSet('secondfield'))
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 4)
self.assertFalse(f.IsFieldSet('secondfield'))
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 5)
self.assertEqual(f['secondfield'], -1)
del f
del lyr
del ds
gdal.Unlink(filename)
if __name__ == '__main__':
unittest.main()